{"record":{"id":"bdf2f46ac45a75f1","repo":"moeru-ai/airi","slug":"airi-host-websocket-bridge-is-unavailable","errorCode":null,"errorMessage":"AIRI host websocket bridge is unavailable","messagePattern":"AIRI host websocket bridge is unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-pocket/src/modules/server-channel-qr-probe.ts","lineNumber":10,"sourceCode":"import type { ServerChannelQrPayload } from '@proj-airi/stage-shared/server-channel-qr'\n\nimport { errorMessageFrom } from '@moeru/std'\nimport { Client, createTextProtocolConnector, WebSocketEventSource } from '@proj-airi/server-sdk'\n\nimport { getHostWebSocketConnector } from './websocket-bridge'\n\nexport async function probeServerChannelQrPayload(payload: ServerChannelQrPayload) {\n  if (!payload.urls.some(url => getHostWebSocketConnector(url))) {\n    throw new Error('AIRI host websocket bridge is unavailable')\n  }\n\n  const errors: string[] = []\n\n  for (const url of payload.urls) {\n    const connector = getHostWebSocketConnector(url)\n    if (!connector) {\n      throw new Error('AIRI host websocket bridge is unavailable')\n    }\n\n    const client = new Client({\n      autoConnect: false,\n      autoReconnect: false,\n      connectTimeoutMs: 2_000,\n      name: WebSocketEventSource.StageWeb,\n      token: payload.authToken,\n      url,\n      connector: createTextProtocolConnector(connector),","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-pocket/src/modules/server-channel-qr-probe.ts#L1-L28","documentation":"Thrown by probeServerChannelQrPayload in stage-pocket when no candidate QR URL yields a usable host websocket connector. The connector comes from getHostWebSocketConnector, which returns undefined unless a native host bridge object (window.AiriHostBridge or window.webkit.messageHandlers.airiHostBridge) has been injected by the embedding native shell. The check runs once across payload.urls, but the bridge presence is global, so absence fails every URL identically.","triggerScenarios":"Calling probeServerChannelQrPayload(payload) from a stage-pocket webview where the native shell has not injected the AiriHostBridge / webkit.messageHandlers.airiHostBridge objects. This occurs before any websocket connection attempt, so it fails fast regardless of how many URLs the QR payload carries.","commonSituations":"Running stage-pocket in a plain browser tab or Capacitor webview that lacks the native bridge wiring; opening the pocket app before the native module registered its message handler; debugging the renderer outside the host app shell; a native shell regression that renames or removes the injected global.","solutions":["Run the probe only inside the host native shell that injects window.AiriHostBridge (or the webkit.messageHandlers.airiHostBridge equivalent); verify the bridge is present before calling probeServerChannelQrPayload.","Gate the call with a pre-check: if (!window.AiriHostBridge && !window.webkit?.messageHandlers?.airiHostBridge) surface a dedicated 'host bridge missing' message instead of invoking the probe.","If developing in a browser, skip the QR probe flow or substitute a direct websocket connector for local testing.","Confirm the native shell's bridge registration code runs before the renderer calls any server-channel-qr logic."],"exampleFix":"// before\nawait probeServerChannelQrPayload(payload)\n\n// after\nconst bridgePresent = !!(window.AiriHostBridge || window.webkit?.messageHandlers?.airiHostBridge)\nif (!bridgePresent) {\n  throw new Error('Open this flow inside the AIRI host app: native websocket bridge not injected.')\n}\nawait probeServerChannelQrPayload(payload)","handlingStrategy":"validation","validationCode":"function isHostBridgeAvailable(): boolean {\n  return !!(window.AiriHostBridge || window.webkit?.messageHandlers?.airiHostBridge)\n}\n\n// before probing:\nif (!isHostBridgeAvailable()) {\n  throw new Error('Open this flow inside the AIRI host app: native websocket bridge not injected.')\n}","typeGuard":"function isHostBridgeAvailable(): boolean {\n  return !!(window.AiriHostBridge || window.webkit?.messageHandlers?.airiHostBridge)\n}","tryCatchPattern":null,"preventionTips":["Run stage-pocket server-channel-qr flows only inside the native shell that injects the host bridge.","Surface a dedicated 'bridge missing' message in the UI before calling probeServerChannelQrPayload.","In tests, stub window.AiriHostBridge before exercising any connector code."],"tags":["websocket","native-bridge","stage-pocket","capacitor","runtime-environment"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}