{"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import {\n  type IncomingMessage,\n  type ServerResponse,\n  createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n  type BridgeCall,\n  type BridgeCallResponse,\n  BridgeCallTimeout,\n  type BridgeConnectedEventPayload,\n  BridgeErrorCodeNoClientConnected,\n  BridgeEvent,\n  BridgeSignalKill,\n  DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n  if (!origin) return true;\n  if (origin.startsWith('chrome-extension://')) return true;\n  return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n  try {\n    const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n      query: {\n        [BridgeSignalKill]: 1,\n      },\n    });\n    await sleep(300);\n    await client.close();\n  } catch (e) {\n    debug('failed to kill port: %O', e);\n  }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n  private callId = 0;\n  private io: Server | null = null;\n  private socket: ServerSocket | null = null;\n  private listeningTimeoutId: NodeJS.Timeout | null = null;\n  private listeningTimerFlag = false;\n  private connectionTipTimer: NodeJS.Timeout | null = null;\n  public calls: Record<string, BridgeCall> = {};\n\n  private connectionLost = false;\n  private connectionLostReason = '';\n\n  constructor(\n    public host: string,\n    public port: number,\n    public onConnect?: () => void,\n    public onDisconnect?: (reason: string) => void,\n    public closeConflictServer?: boolean,\n  ) {}\n\n  async listen(\n    opts: {\n      timeout?: number | false;\n    } = {},\n  ): Promise<void> {\n    const { timeout = 30000 } = opts;\n\n    if (this.closeConflictServer) {\n      await killRunningServer(this.port, this.host);\n    }\n\n    return new Promise((resolve, reject) => {\n      if (this.listeningTimerFlag) {\n        return reject(new Error('already listening'));\n      }\n      this.listeningTimerFlag = true;\n\n      this.listeningTimeoutId = timeout\n        ? setTimeout(() => {\n            reject(\n              new Error(\n                `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n              ),\n            );\n          }, timeout)\n        : null;\n\n      this.connectionTipTimer =\n        !timeout || timeout > 3000\n          ? setTimeout(() => {\n              logMsg('waiting for bridge to connect...');\n            }, 2000)\n          : null;\n\n      // Create HTTP server — no /bridge-auth endpoint needed since we use\n      // pure Origin checking in the Socket.IO middleware below.\n      const httpServer = createServer(\n        (_req: IncomingMessage, res: ServerResponse) => {\n          res.writeHead(404);\n          res.end();\n        },\n      );\n\n      // Set up HTTP server event listeners FIRST\n      httpServer.once('listening', () => {\n        resolve();\n      });\n\n      httpServer.once('error', (err: Error) => {\n        reject(new Error(`Bridge Listening Error: ${err.message}`));\n      });\n\n      // Start listening BEFORE creating Socket.IO Server\n      // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n      // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n      if (this.host === '127.0.0.1') {\n        httpServer.listen(this.port);\n      } else {\n        httpServer.listen(this.port, this.host);\n      }\n\n      // Now create Socket.IO Server attached to the already-listening HTTP server\n      this.io = new Server(httpServer, {\n        maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n        // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n        // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n        // be too short if the SW is suspended between alarm pings.\n        pingTimeout: 60000,\n      });\n\n      this.io.use((socket, next) => {\n        // Reject connections from untrusted browser origins to prevent\n        // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n        // The browser automatically sets the Origin header on WebSocket\n        // handshakes, and JavaScript cannot override it.\n        const origin = socket.handshake.headers.origin;\n        if (!isTrustedOrigin(origin)) {\n          debug('connection rejected: untrusted origin=%s', origin);\n          return next(new Error('origin not allowed'));\n        }\n\n        // Allow new connections to replace old ones (reconnection after\n        // extension Stop→Start). If the old socket is already disconnected\n        // or unresponsive, accept the new connection immediately.\n        if (\n          socket.handshake.url.includes(BridgeSignalKill) === false &&\n          this.socket?.connected\n        ) {\n          return next(new Error('server already connected by another client'));\n        }\n        next();\n      });\n\n      this.io.on('connection', (socket) => {\n        // check the connection url\n        const url = socket.handshake.url;\n        if (url.includes(BridgeSignalKill)) {\n          console.warn('kill signal received, closing bridge server');\n          return this.close();\n        }\n\n        this.connectionLost = false;\n        this.connectionLostReason = '';\n        this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n        this.listeningTimeoutId = null;\n        this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n        this.connectionTipTimer = null;\n        if (this.socket?.connected) {\n          socket.emit(BridgeEvent.Refused);\n          socket.disconnect();\n          logMsg(\n            'refused new connection: server already connected by another client',\n          );\n          return;\n        }\n\n        // Clean up stale old socket if it exists but is no longer connected\n        if (this.socket) {\n          try {\n            this.socket.disconnect();\n          } catch (e) {\n            logMsg(`failed to disconnect stale socket: ${e}`);\n          }\n          this.socket = null;\n        }\n\n        try {\n          logMsg('one client connected');\n          this.socket = socket;\n\n          const clientVersion = socket.handshake.query.version;\n          logMsg(\n            `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n          );\n\n          socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n            const id = params.id;\n            const response = params.response;\n            const error = params.error;\n\n            this.triggerCallResponseCallback(id, error, response);\n          });\n\n          socket.on('disconnect', (reason: string) => {\n            this.connectionLost = true;\n            this.connectionLostReason = reason;\n            this.socket = null;\n\n            // flush all pending calls as error and clean up completed calls\n            for (const id in this.calls) {\n              const call = this.calls[id];\n\n              if (!call.responseTime) {\n                const errorMessage = this.connectionLostErrorMsg();\n                this.triggerCallResponseCallback(\n                  id,\n                  new Error(errorMessage),\n                  null,\n                );\n              }\n            }\n\n            // Clean up completed calls to prevent memory leaks in long-running sessions\n            for (const id in this.calls) {\n              if (this.calls[id].responseTime) {\n                delete this.calls[id];\n              }\n            }\n\n            this.onDisconnect?.(reason);\n          });\n\n          setTimeout(() => {\n            this.onConnect?.();\n\n            const payload = {\n              version: __VERSION__,\n            } as BridgeConnectedEventPayload;\n            socket.emit(BridgeEvent.Connected, payload);\n            Promise.resolve().then(() => {\n              for (const id in this.calls) {\n                if (this.calls[id].callTime === 0) {\n                  this.emitCall(id);\n                }\n              }\n            });\n          }, 0);\n        } catch (e) {\n          logMsg(`failed to handle connection event: ${e}`);\n        }\n      });\n\n      this.io.on('close', () => {\n        this.close();\n      });\n    });\n  }\n\n  private connectionLostErrorMsg = () => {\n    return `Connection lost, reason: ${this.connectionLostReason}`;\n  };\n\n  private async triggerCallResponseCallback(\n    id: string | number,\n    error: Error | string | null,\n    response: any,\n  ) {\n    const call = this.calls[id];\n    if (!call) {\n      throw new Error(`call ${id} not found`);\n    }\n    // Ensure error is always an Error object (bridge client may send strings)\n    if (error) {\n      call.error =\n        error instanceof Error\n          ? error\n          : new Error(typeof error === 'string' ? error : String(error));\n    } else {\n      call.error = undefined;\n    }\n    call.response = response;\n    call.responseTime = Date.now();\n\n    call.callback(call.error, response);\n  }\n\n  private async emitCall(id: string) {\n    const call = this.calls[id];\n    if (!call) {\n      throw new Error(`call ${id} not found`);\n    }\n\n    if (this.connectionLost) {\n      const message = `Connection lost, reason: ${this.connectionLostReason}`;\n      call.callback(new Error(message), null);\n      return;\n    }\n\n    if (this.socket) {\n      this.socket.emit(BridgeEvent.Call, {\n        id,\n        method: call.method,\n        args: call.args,\n      });\n      call.callTime = Date.now();\n    }\n  }\n\n  async call<T = any>(\n    method: string,\n    args: any[],\n    timeout = BridgeCallTimeout,\n  ): Promise<T> {\n    const id = `${this.callId++}`;\n\n    return new Promise((resolve, reject) => {\n      const timeoutId = setTimeout(() => {\n        logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n        this.calls[id].error = new Error(\n          `Bridge call timeout after ${timeout}ms: ${method}`,\n        );\n        reject(this.calls[id].error);\n      }, timeout);\n\n      this.calls[id] = {\n        method,\n        args,\n        response: null,\n        callTime: 0,\n        responseTime: 0,\n        callback: (error: Error | undefined, response: any) => {\n          clearTimeout(timeoutId);\n          if (error) {\n            reject(error);\n          } else {\n            resolve(response);\n          }\n        },\n      };\n\n      this.emitCall(id);\n    });\n  }\n\n  // do NOT restart after close\n  async close() {\n    this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n    this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n    const closeProcess = this.io?.close();\n    this.io = null;\n\n    return closeProcess;\n  }\n}\n"],"names":["debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}