@midscene/web 1.10.7-beta-20260722111325.0 → 1.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -139,20 +139,13 @@ const getBridgePageInCliSide = (options)=>{
139
139
  return proxyPage;
140
140
  };
141
141
  class AgentOverChromeBridge extends Agent {
142
- async configureWaterFlowAnimation() {
143
- if (!this.enableWaterFlowAnimation) await this.page.setWaterFlowAnimationEnabled(false);
144
- }
145
142
  async setDestroyOptionsAfterConnect() {
146
143
  if (this.destroyAfterDisconnectFlag) this.page.setDestroyOptions({
147
144
  closeTab: true
148
145
  });
149
146
  }
150
147
  async connectNewTabWithUrl(url, options) {
151
- await this.page.connectNewTabWithUrl(url, {
152
- ...options,
153
- enableWaterFlowAnimation: this.enableWaterFlowAnimation
154
- });
155
- await this.configureWaterFlowAnimation();
148
+ await this.page.connectNewTabWithUrl(url, options);
156
149
  await sleep(500);
157
150
  await this.setDestroyOptionsAfterConnect();
158
151
  }
@@ -163,11 +156,7 @@ class AgentOverChromeBridge extends Agent {
163
156
  return await this.page.setActiveTabId(Number.parseInt(tabId));
164
157
  }
165
158
  async connectCurrentTab(options) {
166
- await this.page.connectCurrentTab({
167
- ...options,
168
- enableWaterFlowAnimation: this.enableWaterFlowAnimation
169
- });
170
- await this.configureWaterFlowAnimation();
159
+ await this.page.connectCurrentTab(options);
171
160
  await sleep(500);
172
161
  await this.setDestroyOptionsAfterConnect();
173
162
  }
@@ -194,9 +183,8 @@ class AgentOverChromeBridge extends Agent {
194
183
  this.page.showStatusMessage(tip);
195
184
  if (originalOnTaskStartTip) originalOnTaskStartTip?.call(this, tip);
196
185
  }
197
- })), _define_property(this, "destroyAfterDisconnectFlag", void 0), _define_property(this, "enableWaterFlowAnimation", void 0);
186
+ })), _define_property(this, "destroyAfterDisconnectFlag", void 0);
198
187
  this.destroyAfterDisconnectFlag = opts?.closeNewTabsAfterDisconnect;
199
- this.enableWaterFlowAnimation = opts?.enableWaterFlowAnimation ?? true;
200
188
  }
201
189
  }
202
190
  export { AgentOverChromeBridge, getBridgePageInCliSide };
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/agent-cli-side.mjs","sources":["../../../src/bridge-mode/agent-cli-side.ts"],"sourcesContent":["import { Agent, type AgentOpt } from '@midscene/core/agent';\nimport type { FileChooserHandler } from '@midscene/core/device';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport { commonWebActionsForWebPage } from '../web-page';\nimport type { KeyboardAction, MouseAction } from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n BridgePageType,\n DefaultBridgeServerHost,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n getBridgeServerHost,\n} from './common';\nimport { BridgeServer } from './io-server';\nimport type { ExtensionBridgePageBrowserSide } from './page-browser-side';\n\ninterface ChromeExtensionPageCliSide extends ExtensionBridgePageBrowserSide {\n showStatusMessage: (message: string) => Promise<void>;\n}\n\ntype BridgeSerializedError = {\n message: string;\n name?: string;\n stack?: string;\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst debug = getDebug('web:bridge:agent-cli-side');\n\nfunction deserializeBridgeError(error: BridgeSerializedError): Error {\n const result = new Error(error.message);\n result.name = error.name || 'Error';\n result.stack = error.stack;\n return result;\n}\n\n// actually, this is a proxy to the page in browser side\nexport const getBridgePageInCliSide = (options?: {\n host?: string;\n port?: number;\n timeout?: number | false;\n closeConflictServer?: boolean;\n}): ChromeExtensionPageCliSide => {\n const host = options?.host || DefaultBridgeServerHost;\n const port = options?.port || DefaultBridgeServerPort;\n const server = new BridgeServer(\n host,\n port,\n undefined,\n undefined,\n options?.closeConflictServer,\n );\n server.listen({\n timeout: options?.timeout,\n });\n\n let fileChooserEnabled = false;\n let fileChooserError: Error | undefined;\n\n const fileChooserBridgeMethods = new Set<string>([\n BridgeEvent.RegisterFileChooserAccept,\n BridgeEvent.ClearFileChooserAccept,\n BridgeEvent.GetFileChooserError,\n ]);\n\n const syncFileChooserError = async () => {\n if (!fileChooserEnabled) return;\n try {\n const error = await server.call<BridgeSerializedError | undefined>(\n BridgeEvent.GetFileChooserError,\n [],\n 5000,\n );\n if (error) {\n fileChooserError = deserializeBridgeError(error);\n }\n } catch (error) {\n fileChooserError =\n error instanceof Error ? error : new Error(String(error));\n }\n };\n\n const bridgeCaller = (method: string, timeout?: number) => {\n return async (...args: any[]) => {\n try {\n const response = await server.call(method, args, timeout);\n if (!fileChooserBridgeMethods.has(method)) {\n await syncFileChooserError();\n }\n return response;\n } catch (error) {\n if (!fileChooserBridgeMethods.has(method)) {\n await syncFileChooserError();\n }\n throw error;\n }\n };\n };\n const page = {\n showStatusMessage: async (message: string) => {\n await server.call(BridgeEvent.UpdateAgentStatus, [message]);\n },\n };\n\n const proxyPage = new Proxy(page, {\n get(target, prop, receiver) {\n assert(typeof prop === 'string', 'prop must be a string');\n\n if (prop === 'toJSON') {\n return () => {\n return {\n interfaceType: BridgePageType,\n };\n };\n }\n\n if (prop === 'interfaceType') {\n return BridgePageType;\n }\n\n if (prop === 'actionSpace') {\n return () => commonWebActionsForWebPage(proxyPage);\n }\n\n if (Object.keys(page).includes(prop)) {\n return page[prop as keyof typeof page];\n }\n\n if (prop === 'registerFileChooserListener') {\n return async (\n handler: (chooser: FileChooserHandler) => Promise<void>,\n ) => {\n fileChooserEnabled = true;\n fileChooserError = undefined;\n try {\n await handler({\n accept: async (files: string[]) => {\n await server.call(BridgeEvent.RegisterFileChooserAccept, [\n files,\n ]);\n },\n });\n } catch (error) {\n fileChooserEnabled = false;\n fileChooserError =\n error instanceof Error ? error : new Error(String(error));\n throw fileChooserError;\n }\n\n return {\n dispose: () => {\n fileChooserEnabled = false;\n server\n .call(BridgeEvent.ClearFileChooserAccept, [], 5000)\n .catch((error) => {\n debug(\n 'failed to clear bridge file chooser accept: %O',\n error,\n );\n });\n },\n getError: async () => {\n await syncFileChooserError();\n return fileChooserError;\n },\n };\n };\n }\n\n if (prop === 'mouse') {\n const mouse: MouseAction = {\n click: bridgeCaller(MouseEvent.Click),\n wheel: bridgeCaller(MouseEvent.Wheel),\n move: bridgeCaller(MouseEvent.Move),\n drag: bridgeCaller(MouseEvent.Drag),\n };\n return mouse;\n }\n\n if (prop === 'keyboard') {\n const keyboard: KeyboardAction = {\n type: bridgeCaller(KeyboardEvent.Type),\n press: bridgeCaller(KeyboardEvent.Press),\n };\n return keyboard;\n }\n\n if (prop === 'destroy') {\n return async (...args: any[]) => {\n try {\n const caller = bridgeCaller('destroy');\n await caller(...args);\n } catch (e) {\n // console.error('error calling destroy', e);\n }\n return server.close();\n };\n }\n\n // Special handling for methods that support timeout in options\n if (prop === 'connectNewTabWithUrl') {\n return async (url: string, options?: BridgeConnectTabOptions) => {\n const timeout = options?.timeout;\n const caller = bridgeCaller(prop, timeout);\n return await caller(url, options);\n };\n }\n\n if (prop === 'connectCurrentTab') {\n return async (options?: BridgeConnectTabOptions) => {\n const timeout = options?.timeout;\n const caller = bridgeCaller(prop, timeout);\n return await caller(options);\n };\n }\n\n return bridgeCaller(prop);\n },\n }) as ChromeExtensionPageCliSide;\n\n return proxyPage;\n};\n\nexport class AgentOverChromeBridge extends Agent<ChromeExtensionPageCliSide> {\n private destroyAfterDisconnectFlag?: boolean;\n\n private enableWaterFlowAnimation: boolean;\n\n constructor(\n opts?: AgentOpt & {\n /**\n * Enable remote access to the bridge server.\n * - false (default): Only localhost can connect (most secure)\n * - true: Allow remote machines to connect (binds to 0.0.0.0)\n */\n allowRemoteAccess?: boolean;\n /**\n * Custom host to bind the bridge server to.\n * Overrides allowRemoteAccess if specified.\n */\n host?: string;\n /**\n * Custom port for the bridge server.\n * @default 3766\n */\n port?: number;\n closeNewTabsAfterDisconnect?: boolean;\n serverListeningTimeout?: number | false;\n closeConflictServer?: boolean;\n /**\n * Show the blue water-flow border and mouse pointer while controlling\n * the page.\n * @default true\n */\n enableWaterFlowAnimation?: boolean;\n },\n ) {\n const host = getBridgeServerHost({\n host: opts?.host,\n allowRemoteAccess: opts?.allowRemoteAccess,\n });\n const page = getBridgePageInCliSide({\n host,\n port: opts?.port,\n timeout: opts?.serverListeningTimeout,\n closeConflictServer: opts?.closeConflictServer,\n });\n const originalOnTaskStartTip = opts?.onTaskStartTip;\n super(\n page,\n Object.assign(opts || {}, {\n onTaskStartTip: (tip: string) => {\n this.page.showStatusMessage(tip);\n if (originalOnTaskStartTip) {\n originalOnTaskStartTip?.call(this, tip);\n }\n },\n }),\n );\n this.destroyAfterDisconnectFlag = opts?.closeNewTabsAfterDisconnect;\n this.enableWaterFlowAnimation = opts?.enableWaterFlowAnimation ?? true;\n }\n\n private async configureWaterFlowAnimation() {\n if (!this.enableWaterFlowAnimation) {\n await this.page.setWaterFlowAnimationEnabled(false);\n }\n }\n\n async setDestroyOptionsAfterConnect() {\n if (this.destroyAfterDisconnectFlag) {\n this.page.setDestroyOptions({\n closeTab: true,\n });\n }\n }\n\n async connectNewTabWithUrl(url: string, options?: BridgeConnectTabOptions) {\n await this.page.connectNewTabWithUrl(url, {\n ...options,\n enableWaterFlowAnimation: this.enableWaterFlowAnimation,\n });\n await this.configureWaterFlowAnimation();\n await sleep(500);\n await this.setDestroyOptionsAfterConnect();\n }\n\n async getBrowserTabList() {\n return await this.page.getBrowserTabList();\n }\n\n async setActiveTabId(tabId: string) {\n return await this.page.setActiveTabId(Number.parseInt(tabId));\n }\n\n async connectCurrentTab(options?: BridgeConnectTabOptions) {\n await this.page.connectCurrentTab({\n ...options,\n enableWaterFlowAnimation: this.enableWaterFlowAnimation,\n });\n await this.configureWaterFlowAnimation();\n await sleep(500);\n await this.setDestroyOptionsAfterConnect();\n }\n\n async destroy(closeNewTabsAfterDisconnect?: boolean) {\n if (typeof closeNewTabsAfterDisconnect === 'boolean') {\n await this.page.setDestroyOptions({\n closeTab: closeNewTabsAfterDisconnect,\n });\n }\n await super.destroy();\n }\n}\n"],"names":["sleep","ms","Promise","resolve","setTimeout","debug","getDebug","deserializeBridgeError","error","result","Error","getBridgePageInCliSide","options","host","DefaultBridgeServerHost","port","DefaultBridgeServerPort","server","BridgeServer","undefined","fileChooserEnabled","fileChooserError","fileChooserBridgeMethods","Set","BridgeEvent","syncFileChooserError","String","bridgeCaller","method","timeout","args","response","page","message","proxyPage","Proxy","target","prop","receiver","assert","BridgePageType","commonWebActionsForWebPage","Object","handler","files","mouse","MouseEvent","keyboard","KeyboardEvent","caller","e","url","AgentOverChromeBridge","Agent","tabId","Number","closeNewTabsAfterDisconnect","opts","getBridgeServerHost","originalOnTaskStartTip","tip"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/agent-cli-side.mjs","sources":["../../../src/bridge-mode/agent-cli-side.ts"],"sourcesContent":["import { Agent, type AgentOpt } from '@midscene/core/agent';\nimport type { FileChooserHandler } from '@midscene/core/device';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport { commonWebActionsForWebPage } from '../web-page';\nimport type { KeyboardAction, MouseAction } from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n BridgePageType,\n DefaultBridgeServerHost,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n getBridgeServerHost,\n} from './common';\nimport { BridgeServer } from './io-server';\nimport type { ExtensionBridgePageBrowserSide } from './page-browser-side';\n\ninterface ChromeExtensionPageCliSide extends ExtensionBridgePageBrowserSide {\n showStatusMessage: (message: string) => Promise<void>;\n}\n\ntype BridgeSerializedError = {\n message: string;\n name?: string;\n stack?: string;\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst debug = getDebug('web:bridge:agent-cli-side');\n\nfunction deserializeBridgeError(error: BridgeSerializedError): Error {\n const result = new Error(error.message);\n result.name = error.name || 'Error';\n result.stack = error.stack;\n return result;\n}\n\n// actually, this is a proxy to the page in browser side\nexport const getBridgePageInCliSide = (options?: {\n host?: string;\n port?: number;\n timeout?: number | false;\n closeConflictServer?: boolean;\n}): ChromeExtensionPageCliSide => {\n const host = options?.host || DefaultBridgeServerHost;\n const port = options?.port || DefaultBridgeServerPort;\n const server = new BridgeServer(\n host,\n port,\n undefined,\n undefined,\n options?.closeConflictServer,\n );\n server.listen({\n timeout: options?.timeout,\n });\n\n let fileChooserEnabled = false;\n let fileChooserError: Error | undefined;\n\n const fileChooserBridgeMethods = new Set<string>([\n BridgeEvent.RegisterFileChooserAccept,\n BridgeEvent.ClearFileChooserAccept,\n BridgeEvent.GetFileChooserError,\n ]);\n\n const syncFileChooserError = async () => {\n if (!fileChooserEnabled) return;\n try {\n const error = await server.call<BridgeSerializedError | undefined>(\n BridgeEvent.GetFileChooserError,\n [],\n 5000,\n );\n if (error) {\n fileChooserError = deserializeBridgeError(error);\n }\n } catch (error) {\n fileChooserError =\n error instanceof Error ? error : new Error(String(error));\n }\n };\n\n const bridgeCaller = (method: string, timeout?: number) => {\n return async (...args: any[]) => {\n try {\n const response = await server.call(method, args, timeout);\n if (!fileChooserBridgeMethods.has(method)) {\n await syncFileChooserError();\n }\n return response;\n } catch (error) {\n if (!fileChooserBridgeMethods.has(method)) {\n await syncFileChooserError();\n }\n throw error;\n }\n };\n };\n const page = {\n showStatusMessage: async (message: string) => {\n await server.call(BridgeEvent.UpdateAgentStatus, [message]);\n },\n };\n\n const proxyPage = new Proxy(page, {\n get(target, prop, receiver) {\n assert(typeof prop === 'string', 'prop must be a string');\n\n if (prop === 'toJSON') {\n return () => {\n return {\n interfaceType: BridgePageType,\n };\n };\n }\n\n if (prop === 'interfaceType') {\n return BridgePageType;\n }\n\n if (prop === 'actionSpace') {\n return () => commonWebActionsForWebPage(proxyPage);\n }\n\n if (Object.keys(page).includes(prop)) {\n return page[prop as keyof typeof page];\n }\n\n if (prop === 'registerFileChooserListener') {\n return async (\n handler: (chooser: FileChooserHandler) => Promise<void>,\n ) => {\n fileChooserEnabled = true;\n fileChooserError = undefined;\n try {\n await handler({\n accept: async (files: string[]) => {\n await server.call(BridgeEvent.RegisterFileChooserAccept, [\n files,\n ]);\n },\n });\n } catch (error) {\n fileChooserEnabled = false;\n fileChooserError =\n error instanceof Error ? error : new Error(String(error));\n throw fileChooserError;\n }\n\n return {\n dispose: () => {\n fileChooserEnabled = false;\n server\n .call(BridgeEvent.ClearFileChooserAccept, [], 5000)\n .catch((error) => {\n debug(\n 'failed to clear bridge file chooser accept: %O',\n error,\n );\n });\n },\n getError: async () => {\n await syncFileChooserError();\n return fileChooserError;\n },\n };\n };\n }\n\n if (prop === 'mouse') {\n const mouse: MouseAction = {\n click: bridgeCaller(MouseEvent.Click),\n wheel: bridgeCaller(MouseEvent.Wheel),\n move: bridgeCaller(MouseEvent.Move),\n drag: bridgeCaller(MouseEvent.Drag),\n };\n return mouse;\n }\n\n if (prop === 'keyboard') {\n const keyboard: KeyboardAction = {\n type: bridgeCaller(KeyboardEvent.Type),\n press: bridgeCaller(KeyboardEvent.Press),\n };\n return keyboard;\n }\n\n if (prop === 'destroy') {\n return async (...args: any[]) => {\n try {\n const caller = bridgeCaller('destroy');\n await caller(...args);\n } catch (e) {\n // console.error('error calling destroy', e);\n }\n return server.close();\n };\n }\n\n // Special handling for methods that support timeout in options\n if (prop === 'connectNewTabWithUrl') {\n return async (url: string, options?: BridgeConnectTabOptions) => {\n const timeout = options?.timeout;\n const caller = bridgeCaller(prop, timeout);\n return await caller(url, options);\n };\n }\n\n if (prop === 'connectCurrentTab') {\n return async (options?: BridgeConnectTabOptions) => {\n const timeout = options?.timeout;\n const caller = bridgeCaller(prop, timeout);\n return await caller(options);\n };\n }\n\n return bridgeCaller(prop);\n },\n }) as ChromeExtensionPageCliSide;\n\n return proxyPage;\n};\n\nexport class AgentOverChromeBridge extends Agent<ChromeExtensionPageCliSide> {\n private destroyAfterDisconnectFlag?: boolean;\n\n constructor(\n opts?: AgentOpt & {\n /**\n * Enable remote access to the bridge server.\n * - false (default): Only localhost can connect (most secure)\n * - true: Allow remote machines to connect (binds to 0.0.0.0)\n */\n allowRemoteAccess?: boolean;\n /**\n * Custom host to bind the bridge server to.\n * Overrides allowRemoteAccess if specified.\n */\n host?: string;\n /**\n * Custom port for the bridge server.\n * @default 3766\n */\n port?: number;\n closeNewTabsAfterDisconnect?: boolean;\n serverListeningTimeout?: number | false;\n closeConflictServer?: boolean;\n },\n ) {\n const host = getBridgeServerHost({\n host: opts?.host,\n allowRemoteAccess: opts?.allowRemoteAccess,\n });\n const page = getBridgePageInCliSide({\n host,\n port: opts?.port,\n timeout: opts?.serverListeningTimeout,\n closeConflictServer: opts?.closeConflictServer,\n });\n const originalOnTaskStartTip = opts?.onTaskStartTip;\n super(\n page,\n Object.assign(opts || {}, {\n onTaskStartTip: (tip: string) => {\n this.page.showStatusMessage(tip);\n if (originalOnTaskStartTip) {\n originalOnTaskStartTip?.call(this, tip);\n }\n },\n }),\n );\n this.destroyAfterDisconnectFlag = opts?.closeNewTabsAfterDisconnect;\n }\n\n async setDestroyOptionsAfterConnect() {\n if (this.destroyAfterDisconnectFlag) {\n this.page.setDestroyOptions({\n closeTab: true,\n });\n }\n }\n\n async connectNewTabWithUrl(url: string, options?: BridgeConnectTabOptions) {\n await this.page.connectNewTabWithUrl(url, options);\n await sleep(500);\n await this.setDestroyOptionsAfterConnect();\n }\n\n async getBrowserTabList() {\n return await this.page.getBrowserTabList();\n }\n\n async setActiveTabId(tabId: string) {\n return await this.page.setActiveTabId(Number.parseInt(tabId));\n }\n\n async connectCurrentTab(options?: BridgeConnectTabOptions) {\n await this.page.connectCurrentTab(options);\n await sleep(500);\n await this.setDestroyOptionsAfterConnect();\n }\n\n async destroy(closeNewTabsAfterDisconnect?: boolean) {\n if (typeof closeNewTabsAfterDisconnect === 'boolean') {\n await this.page.setDestroyOptions({\n closeTab: closeNewTabsAfterDisconnect,\n });\n }\n await super.destroy();\n }\n}\n"],"names":["sleep","ms","Promise","resolve","setTimeout","debug","getDebug","deserializeBridgeError","error","result","Error","getBridgePageInCliSide","options","host","DefaultBridgeServerHost","port","DefaultBridgeServerPort","server","BridgeServer","undefined","fileChooserEnabled","fileChooserError","fileChooserBridgeMethods","Set","BridgeEvent","syncFileChooserError","String","bridgeCaller","method","timeout","args","response","page","message","proxyPage","Proxy","target","prop","receiver","assert","BridgePageType","commonWebActionsForWebPage","Object","handler","files","mouse","MouseEvent","keyboard","KeyboardEvent","caller","e","url","AgentOverChromeBridge","Agent","tabId","Number","closeNewTabsAfterDisconnect","opts","getBridgeServerHost","originalOnTaskStartTip","tip"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/common.mjs","sources":["../../../src/bridge-mode/common.ts"],"sourcesContent":["export const DefaultBridgeServerHost = '127.0.0.1';\nexport const DefaultBridgeServerPort = 3766;\nexport const DefaultLocalEndpoint = `http://${DefaultBridgeServerHost}:${DefaultBridgeServerPort}`;\nexport const BridgeCallTimeout = 30000;\n\n/**\n * Get the server host based on configuration options.\n * Priority: explicit host > allowRemoteAccess > default (127.0.0.1)\n */\nexport function getBridgeServerHost(options?: {\n host?: string;\n allowRemoteAccess?: boolean;\n}): string {\n if (options?.host) {\n return options.host;\n }\n if (options?.allowRemoteAccess) {\n return '0.0.0.0';\n }\n return DefaultBridgeServerHost;\n}\n\nexport enum BridgeEvent {\n Call = 'bridge-call',\n CallResponse = 'bridge-call-response',\n UpdateAgentStatus = 'bridge-update-agent-status',\n Message = 'bridge-message',\n Connected = 'bridge-connected',\n Refused = 'bridge-refused',\n ConnectNewTabWithUrl = 'connectNewTabWithUrl',\n ConnectCurrentTab = 'connectCurrentTab',\n GetBrowserTabList = 'getBrowserTabList',\n SetDestroyOptions = 'setDestroyOptions',\n SetActiveTabId = 'setActiveTabId',\n RegisterFileChooserAccept = 'registerFileChooserAccept',\n ClearFileChooserAccept = 'clearFileChooserAccept',\n GetFileChooserError = 'getFileChooserError',\n}\n\nexport const BridgeSignalKill = 'MIDSCENE_BRIDGE_SIGNAL_KILL';\n\nexport interface BridgeConnectTabOptions {\n /**\n * If true, the page will always track the active tab.\n * @default true\n */\n forceSameTabNavigation?: boolean;\n /**\n * Custom timeout for connecting to the tab in milliseconds.\n * @default 30000 (30 seconds)\n */\n timeout?: number;\n /**\n * Whether the blue water-flow animation border and mouse pointer\n * overlay should be shown while controlling the page.\n * @default true\n */\n enableWaterFlowAnimation?: boolean;\n}\n\nexport enum MouseEvent {\n PREFIX = 'mouse.',\n Click = 'mouse.click',\n Wheel = 'mouse.wheel',\n Move = 'mouse.move',\n Drag = 'mouse.drag',\n}\n\nexport enum KeyboardEvent {\n PREFIX = 'keyboard.',\n Type = 'keyboard.type',\n Press = 'keyboard.press',\n}\n\nexport const BridgePageType = 'page-over-chrome-extension-bridge';\n\nexport const BridgeErrorCodeNoClientConnected = 'no-client-connected';\n\nexport interface BridgeCall {\n method: string;\n args: any[];\n response: any;\n callTime: number;\n responseTime: number;\n callback: (error: Error | undefined, response: any) => void;\n error?: Error;\n}\n\nexport interface BridgeCallRequest {\n id: string;\n method: string;\n args: any[];\n}\n\nexport interface BridgeCallResponse {\n id: string;\n response: any;\n error?: any;\n}\n\nexport interface BridgeConnectedEventPayload {\n version: string;\n}\n"],"names":["DefaultBridgeServerHost","DefaultBridgeServerPort","DefaultLocalEndpoint","BridgeCallTimeout","getBridgeServerHost","options","BridgeEvent","BridgeSignalKill","MouseEvent","KeyboardEvent","BridgePageType","BridgeErrorCodeNoClientConnected"],"mappings":"AAAO,MAAMA,0BAA0B;AAChC,MAAMC,0BAA0B;AAChC,MAAMC,uBAAuB,CAAC,OAAO,EAAEF,wBAAwB,CAAC,EAAEC,yBAAyB;AAC3F,MAAME,oBAAoB;AAM1B,SAASC,oBAAoBC,OAGnC;IACC,IAAIA,SAAS,MACX,OAAOA,QAAQ,IAAI;IAErB,IAAIA,SAAS,mBACX,OAAO;IAET,OAAOL;AACT;AAEO,IAAKM,qBAAWA,WAAAA,GAAAA,SAAXA,WAAW;;;;;;;;;;;;;;;WAAXA;;AAiBL,MAAMC,mBAAmB;AAqBzB,IAAKC,oBAAUA,WAAAA,GAAAA,SAAVA,UAAU;;;;;;WAAVA;;AAQL,IAAKC,uBAAaA,WAAAA,GAAAA,SAAbA,aAAa;;;;WAAbA;;AAML,MAAMC,iBAAiB;AAEvB,MAAMC,mCAAmC"}
1
+ {"version":3,"file":"bridge-mode/common.mjs","sources":["../../../src/bridge-mode/common.ts"],"sourcesContent":["export const DefaultBridgeServerHost = '127.0.0.1';\nexport const DefaultBridgeServerPort = 3766;\nexport const DefaultLocalEndpoint = `http://${DefaultBridgeServerHost}:${DefaultBridgeServerPort}`;\nexport const BridgeCallTimeout = 30000;\n\n/**\n * Get the server host based on configuration options.\n * Priority: explicit host > allowRemoteAccess > default (127.0.0.1)\n */\nexport function getBridgeServerHost(options?: {\n host?: string;\n allowRemoteAccess?: boolean;\n}): string {\n if (options?.host) {\n return options.host;\n }\n if (options?.allowRemoteAccess) {\n return '0.0.0.0';\n }\n return DefaultBridgeServerHost;\n}\n\nexport enum BridgeEvent {\n Call = 'bridge-call',\n CallResponse = 'bridge-call-response',\n UpdateAgentStatus = 'bridge-update-agent-status',\n Message = 'bridge-message',\n Connected = 'bridge-connected',\n Refused = 'bridge-refused',\n ConnectNewTabWithUrl = 'connectNewTabWithUrl',\n ConnectCurrentTab = 'connectCurrentTab',\n GetBrowserTabList = 'getBrowserTabList',\n SetDestroyOptions = 'setDestroyOptions',\n SetActiveTabId = 'setActiveTabId',\n RegisterFileChooserAccept = 'registerFileChooserAccept',\n ClearFileChooserAccept = 'clearFileChooserAccept',\n GetFileChooserError = 'getFileChooserError',\n}\n\nexport const BridgeSignalKill = 'MIDSCENE_BRIDGE_SIGNAL_KILL';\n\nexport interface BridgeConnectTabOptions {\n /**\n * If true, the page will always track the active tab.\n * @default true\n */\n forceSameTabNavigation?: boolean;\n /**\n * Custom timeout for connecting to the tab in milliseconds.\n * @default 30000 (30 seconds)\n */\n timeout?: number;\n}\n\nexport enum MouseEvent {\n PREFIX = 'mouse.',\n Click = 'mouse.click',\n Wheel = 'mouse.wheel',\n Move = 'mouse.move',\n Drag = 'mouse.drag',\n}\n\nexport enum KeyboardEvent {\n PREFIX = 'keyboard.',\n Type = 'keyboard.type',\n Press = 'keyboard.press',\n}\n\nexport const BridgePageType = 'page-over-chrome-extension-bridge';\n\nexport const BridgeErrorCodeNoClientConnected = 'no-client-connected';\n\nexport interface BridgeCall {\n method: string;\n args: any[];\n response: any;\n callTime: number;\n responseTime: number;\n callback: (error: Error | undefined, response: any) => void;\n error?: Error;\n}\n\nexport interface BridgeCallRequest {\n id: string;\n method: string;\n args: any[];\n}\n\nexport interface BridgeCallResponse {\n id: string;\n response: any;\n error?: any;\n}\n\nexport interface BridgeConnectedEventPayload {\n version: string;\n}\n"],"names":["DefaultBridgeServerHost","DefaultBridgeServerPort","DefaultLocalEndpoint","BridgeCallTimeout","getBridgeServerHost","options","BridgeEvent","BridgeSignalKill","MouseEvent","KeyboardEvent","BridgePageType","BridgeErrorCodeNoClientConnected"],"mappings":"AAAO,MAAMA,0BAA0B;AAChC,MAAMC,0BAA0B;AAChC,MAAMC,uBAAuB,CAAC,OAAO,EAAEF,wBAAwB,CAAC,EAAEC,yBAAyB;AAC3F,MAAME,oBAAoB;AAM1B,SAASC,oBAAoBC,OAGnC;IACC,IAAIA,SAAS,MACX,OAAOA,QAAQ,IAAI;IAErB,IAAIA,SAAS,mBACX,OAAO;IAET,OAAOL;AACT;AAEO,IAAKM,qBAAWA,WAAAA,GAAAA,SAAXA,WAAW;;;;;;;;;;;;;;;WAAXA;;AAiBL,MAAMC,mBAAmB;AAezB,IAAKC,oBAAUA,WAAAA,GAAAA,SAAVA,UAAU;;;;;;WAAVA;;AAQL,IAAKC,uBAAaA,WAAAA,GAAAA,SAAbA,aAAa;;;;WAAbA;;AAML,MAAMC,iBAAiB;AAEvB,MAAMC,mCAAmC"}
@@ -23,7 +23,7 @@ class BridgeClient {
23
23
  ]
24
24
  } : {},
25
25
  query: {
26
- version: "1.10.7-beta-20260722111325.0"
26
+ version: "1.10.7"
27
27
  }
28
28
  });
29
29
  const timeout = setTimeout(()=>{
@@ -102,7 +102,7 @@ class BridgeServer {
102
102
  logMsg('one client connected');
103
103
  this.socket = socket;
104
104
  const clientVersion = socket.handshake.query.version;
105
- logMsg(`Bridge connected, cli-side version v1.10.7-beta-20260722111325.0, browser-side version v${clientVersion}`);
105
+ logMsg(`Bridge connected, cli-side version v1.10.7, browser-side version v${clientVersion}`);
106
106
  socket.on(BridgeEvent.CallResponse, (params)=>{
107
107
  const id = params.id;
108
108
  const response = params.response;
@@ -126,7 +126,7 @@ class BridgeServer {
126
126
  setTimeout(()=>{
127
127
  this.onConnect?.();
128
128
  const payload = {
129
- version: "1.10.7-beta-20260722111325.0"
129
+ version: "1.10.7"
130
130
  };
131
131
  socket.emit(BridgeEvent.Connected, payload);
132
132
  Promise.resolve().then(()=>{
@@ -1 +1 @@
1
- {"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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"}
1
+ {"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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"}
@@ -100,7 +100,7 @@ class ExtensionBridgePageBrowserSide extends page {
100
100
  throw new Error('Connection denied by user');
101
101
  }
102
102
  }
103
- this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.7-beta-20260722111325.0`, 'log');
103
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.7`, 'log');
104
104
  }
105
105
  async connect() {
106
106
  return await this.setupBridgeClient();
@@ -108,7 +108,6 @@ class ExtensionBridgePageBrowserSide extends page {
108
108
  async connectNewTabWithUrl(url, options = {
109
109
  forceSameTabNavigation: true
110
110
  }) {
111
- if (void 0 !== options.enableWaterFlowAnimation) this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;
112
111
  const tab = await chrome.tabs.create({
113
112
  url
114
113
  });
@@ -123,7 +122,6 @@ class ExtensionBridgePageBrowserSide extends page {
123
122
  async connectCurrentTab(options = {
124
123
  forceSameTabNavigation: true
125
124
  }) {
126
- if (void 0 !== options.enableWaterFlowAnimation) this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;
127
125
  const tabs = await chrome.tabs.query({
128
126
  active: true,
129
127
  currentWindow: true
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n // Generic method lookup. Methods like `setWaterFlowAnimationEnabled`\n // and `getWaterFlowAnimationEnabled` are inherited from\n // ChromeExtensionProxyPage and found via `this[method]`.\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n // Otherwise the animation gets injected during the connection flow\n // before the CLI side's setWaterFlowAnimationEnabled(false) arrives.\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","undefined","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
@@ -125,7 +125,6 @@ class ChromeExtensionProxyPage {
125
125
  });
126
126
  }
127
127
  async showMousePointer(x, y) {
128
- if (!this.waterFlowAnimationEnabled) return;
129
128
  const pointerScript = `(() => {
130
129
  if(typeof window.midsceneWaterFlowAnimation !== 'undefined') {
131
130
  window.midsceneWaterFlowAnimation.enable();
@@ -172,7 +171,6 @@ class ChromeExtensionProxyPage {
172
171
  }, 'Runtime.evaluate', {
173
172
  expression: limitOpenNewTabScript
174
173
  });
175
- if (!this.waterFlowAnimationEnabled) return;
176
174
  const script = await injectWaterFlowAnimation();
177
175
  await chrome.debugger.sendCommand({
178
176
  tabId
@@ -188,18 +186,6 @@ class ChromeExtensionProxyPage {
188
186
  expression: script
189
187
  });
190
188
  }
191
- async setWaterFlowAnimationEnabled(enabled) {
192
- this.waterFlowAnimationEnabled = enabled;
193
- if (!enabled) {
194
- const script = await injectStopWaterFlowAnimation();
195
- await this.sendCommandToDebugger('Runtime.evaluate', {
196
- expression: script
197
- });
198
- }
199
- }
200
- getWaterFlowAnimationEnabled() {
201
- return this.waterFlowAnimationEnabled;
202
- }
203
189
  async sendCommandToDebugger(command, params, retryCount = 0) {
204
190
  const MAX_RETRIES = 2;
205
191
  const tabId = await this.getTabIdOrConnectToCurrentTab();
@@ -742,7 +728,6 @@ class ChromeExtensionProxyPage {
742
728
  _define_property(this, "bridgeFileChooserDispose", void 0);
743
729
  _define_property(this, "bridgeFileChooserGetError", void 0);
744
730
  _define_property(this, "isMobileEmulation", null);
745
- _define_property(this, "waterFlowAnimationEnabled", true);
746
731
  _define_property(this, "_continueWhenFailedToAttachDebugger", false);
747
732
  _define_property(this, "latestMouseX", 100);
748
733
  _define_property(this, "latestMouseY", 100);