moeru-ai/airi · error · Error

Wrapped extension peer client does not support event listene

Error message

Wrapped extension peer client does not support event listeners.

What it means

createWebSocketExtensionPeer wraps an existing protocol client (server-sdk/src/extension-peer.ts). Its onEvent() forwards to this.client.onEvent, but only checks for the method at call time: if the wrapped client object does not implement onEvent (falsy), it throws this error rather than silently dropping the subscription. It signals you wrapped a send/close-only object and then tried to subscribe to events.

Source

Thrown at packages/server-sdk/src/extension-peer.ts:132

        },
        possibleEvents: input.possibleEvents ?? [],
        permissions: input.permissions,
        configSchema: input.configSchema,
        dependencies: input.dependencies,
      },
    })
  }

  send(data: WebSocketEventOptionalSource<C>): boolean {
    return this.client.send(data)
  }

  onEvent<E extends keyof WebSocketEvents<C>>(
    event: E,
    callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,
  ): () => void {
    if (!this.client.onEvent) {
      throw new Error('Wrapped extension peer client does not support event listeners.')
    }

    return this.client.onEvent(event, callback)
  }

  close(): void {
    this.client.close()
  }
}

/** Creates a websocket extension peer over a server-sdk protocol client. */
export function createWebSocketExtensionPeer<C = undefined>(
  options: WebSocketExtensionPeerOptions<C>,
): WebSocketExtensionPeer<C> {
  return new WebSocketExtensionPeer(options)
}

View on GitHub (pinned to 677329427f)

Solutions

  1. Wrap the full client returned by createClient()/createWebSocketClient, which implements onEvent
  2. Upgrade the wrapped implementation to a version that exposes onEvent
  3. Feature-detect before subscribing: skip or degrade when `typeof client.onEvent !== 'function'`

Example fix

// before
createWebSocketExtensionPeer({ send: (d) => ws.send(d), close: () => ws.close() } as any).onEvent('x', cb)
// after
import { createWebSocketClient } from '@proj-airi/server-sdk'
createWebSocketExtensionPeer(createWebSocketClient({ url, events })).onEvent('x', cb)
Defensive patterns

Strategy: type-guard

Validate before calling

if (!('onEvent' in client) || typeof client.onEvent !== 'function')
  throw new Error('Cannot subscribe: wrap a full client that implements onEvent')

Type guard

type WithEventListeners<T> = T & { onEvent: (event: string, cb: (data: unknown) => void) => () => void }

function supportsEventListeners<T extends { onEvent?: unknown }>(client: T): client is T & WithEventListeners<T> {
  return typeof client.onEvent === 'function'
}

Try / catch

try {
  peer.onEvent('player-heartbeat-set', handler)
}
catch (error) {
  if (error instanceof Error && error.message.includes('does not support event listeners')) {
    // degrade: run without live events instead of crashing startup
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Passing a hand-rolled or mock client that implements only send()/close(), wrapping an older server-sdk client version from before onEvent existed, or a custom transport adapter that never exposed event listeners.

Common situations: Tests with fake clients, adapter layers that translate another RPC lib into the client interface, or version mismatches after a partial monorepo upgrade.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/071d750ef5c5ac85. Report an issue: GitHub.