{"record":{"id":"071d750ef5c5ac85","repo":"moeru-ai/airi","slug":"wrapped-extension-peer-client-does-not-support-eve","errorCode":null,"errorMessage":"Wrapped extension peer client does not support event listeners.","messagePattern":"Wrapped extension peer client does not support event listeners\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server-sdk/src/extension-peer.ts","lineNumber":132,"sourceCode":"        },\n        possibleEvents: input.possibleEvents ?? [],\n        permissions: input.permissions,\n        configSchema: input.configSchema,\n        dependencies: input.dependencies,\n      },\n    })\n  }\n\n  send(data: WebSocketEventOptionalSource<C>): boolean {\n    return this.client.send(data)\n  }\n\n  onEvent<E extends keyof WebSocketEvents<C>>(\n    event: E,\n    callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n  ): () => void {\n    if (!this.client.onEvent) {\n      throw new Error('Wrapped extension peer client does not support event listeners.')\n    }\n\n    return this.client.onEvent(event, callback)\n  }\n\n  close(): void {\n    this.client.close()\n  }\n}\n\n/** Creates a websocket extension peer over a server-sdk protocol client. */\nexport function createWebSocketExtensionPeer<C = undefined>(\n  options: WebSocketExtensionPeerOptions<C>,\n): WebSocketExtensionPeer<C> {\n  return new WebSocketExtensionPeer(options)\n}\n","sourceCodeStart":114,"sourceCodeEnd":149,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/server-sdk/src/extension-peer.ts#L114-L149","documentation":"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.","triggerScenarios":"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.","commonSituations":"Tests with fake clients, adapter layers that translate another RPC lib into the client interface, or version mismatches after a partial monorepo upgrade.","solutions":["Wrap the full client returned by createClient()/createWebSocketClient, which implements onEvent","Upgrade the wrapped implementation to a version that exposes onEvent","Feature-detect before subscribing: skip or degrade when `typeof client.onEvent !== 'function'`"],"exampleFix":"// before\ncreateWebSocketExtensionPeer({ send: (d) => ws.send(d), close: () => ws.close() } as any).onEvent('x', cb)\n// after\nimport { createWebSocketClient } from '@proj-airi/server-sdk'\ncreateWebSocketExtensionPeer(createWebSocketClient({ url, events })).onEvent('x', cb)","handlingStrategy":"type-guard","validationCode":"if (!('onEvent' in client) || typeof client.onEvent !== 'function')\n  throw new Error('Cannot subscribe: wrap a full client that implements onEvent')","typeGuard":"type WithEventListeners<T> = T & { onEvent: (event: string, cb: (data: unknown) => void) => () => void }\n\nfunction supportsEventListeners<T extends { onEvent?: unknown }>(client: T): client is T & WithEventListeners<T> {\n  return typeof client.onEvent === 'function'\n}","tryCatchPattern":"try {\n  peer.onEvent('player-heartbeat-set', handler)\n}\ncatch (error) {\n  if (error instanceof Error && error.message.includes('does not support event listeners')) {\n    // degrade: run without live events instead of crashing startup\n    return\n  }\n  throw error\n}","preventionTips":["Wrap the client returned by createClient, not hand-rolled stubs","Feature-detect onEvent before subscribing when adapters may vary","In tests, mock onEvent so the peer exercises the real subscription path"],"tags":["websocket","server-sdk","adapter","duck-typing"],"backgroundTag":"missing-interface-method","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}