@midscene/web 1.9.2-beta-20260608084200.0 → 1.9.2
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.
- package/dist/es/bridge-mode/io-client.mjs +1 -1
- package/dist/es/bridge-mode/io-server.mjs +2 -2
- package/dist/es/bridge-mode/io-server.mjs.map +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/mcp-server.mjs +1 -1
- package/dist/es/mcp-tools-puppeteer.mjs +9 -36
- package/dist/es/mcp-tools-puppeteer.mjs.map +1 -1
- package/dist/es/puppeteer/agent-launcher.mjs +7 -0
- package/dist/es/puppeteer/agent-launcher.mjs.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +1 -1
- package/dist/lib/bridge-mode/io-server.js +2 -2
- package/dist/lib/bridge-mode/io-server.js.map +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/mcp-server.js +1 -1
- package/dist/lib/mcp-tools-puppeteer.js +9 -36
- package/dist/lib/mcp-tools-puppeteer.js.map +1 -1
- package/dist/lib/puppeteer/agent-launcher.js +7 -0
- package/dist/lib/puppeteer/agent-launcher.js.map +1 -1
- package/package.json +4 -4
|
@@ -86,7 +86,7 @@ class BridgeServer {
|
|
|
86
86
|
logMsg('one client connected');
|
|
87
87
|
this.socket = socket;
|
|
88
88
|
const clientVersion = socket.handshake.query.version;
|
|
89
|
-
logMsg(`Bridge connected, cli-side version v1.9.2
|
|
89
|
+
logMsg(`Bridge connected, cli-side version v1.9.2, browser-side version v${clientVersion}`);
|
|
90
90
|
socket.on(BridgeEvent.CallResponse, (params)=>{
|
|
91
91
|
const id = params.id;
|
|
92
92
|
const response = params.response;
|
|
@@ -110,7 +110,7 @@ class BridgeServer {
|
|
|
110
110
|
setTimeout(()=>{
|
|
111
111
|
this.onConnect?.();
|
|
112
112
|
const payload = {
|
|
113
|
-
version: "1.9.2
|
|
113
|
+
version: "1.9.2"
|
|
114
114
|
};
|
|
115
115
|
socket.emit(BridgeEvent.Connected, payload);
|
|
116
116
|
Promise.resolve().then(()=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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 { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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.9.2
|
|
103
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.9.2`, 'log');
|
|
104
104
|
}
|
|
105
105
|
async connect() {
|
|
106
106
|
return await this.setupBridgeClient();
|
|
@@ -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 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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"}
|
|
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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"}
|
package/dist/es/cli.mjs
CHANGED
|
@@ -18,7 +18,7 @@ Promise.resolve().then(()=>{
|
|
|
18
18
|
return runToolsCLI(tools, 'midscene-web', {
|
|
19
19
|
stripPrefix: 'web_',
|
|
20
20
|
argv: parsedOptions.argv,
|
|
21
|
-
version: "1.9.2
|
|
21
|
+
version: "1.9.2",
|
|
22
22
|
extraCommands: createReportCliCommands()
|
|
23
23
|
});
|
|
24
24
|
}).catch((e)=>{
|
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -7,7 +7,7 @@ class WebMCPServer extends BaseMCPServer {
|
|
|
7
7
|
constructor(toolsManager){
|
|
8
8
|
super({
|
|
9
9
|
name: '@midscene/web-bridge-mcp',
|
|
10
|
-
version: "1.9.2
|
|
10
|
+
version: "1.9.2",
|
|
11
11
|
description: 'Control the browser using natural language commands'
|
|
12
12
|
}, toolsManager);
|
|
13
13
|
}
|
|
@@ -22,7 +22,6 @@ function _define_property(obj, key, value) {
|
|
|
22
22
|
}
|
|
23
23
|
const ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');
|
|
24
24
|
const USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');
|
|
25
|
-
const DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30000;
|
|
26
25
|
const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;
|
|
27
26
|
function buildDetachedChromeArgs(options) {
|
|
28
27
|
const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;
|
|
@@ -42,16 +41,6 @@ function buildDetachedChromeArgs(options) {
|
|
|
42
41
|
'--force-color-profile=srgb'
|
|
43
42
|
];
|
|
44
43
|
}
|
|
45
|
-
function terminateDetachedChrome(proc) {
|
|
46
|
-
if (proc.killed || null !== proc.exitCode || null !== proc.signalCode) return;
|
|
47
|
-
if ('win32' !== process.platform && proc.pid) try {
|
|
48
|
-
process.kill(-proc.pid, 'SIGKILL');
|
|
49
|
-
return;
|
|
50
|
-
} catch {}
|
|
51
|
-
try {
|
|
52
|
-
proc.kill('SIGKILL');
|
|
53
|
-
} catch {}
|
|
54
|
-
}
|
|
55
44
|
class PuppeteerBrowserManager {
|
|
56
45
|
get endpointFile() {
|
|
57
46
|
return this.persistence.endpointFile || ENDPOINT_FILE;
|
|
@@ -128,36 +117,20 @@ class PuppeteerBrowserManager {
|
|
|
128
117
|
proc.unref();
|
|
129
118
|
return new Promise((resolve, reject)=>{
|
|
130
119
|
let output = '';
|
|
131
|
-
let settled = false;
|
|
132
|
-
const cleanup = ()=>{
|
|
133
|
-
clearTimeout(timeout);
|
|
134
|
-
proc.stderr.removeListener('data', onData);
|
|
135
|
-
proc.removeListener('exit', onExit);
|
|
136
|
-
};
|
|
137
|
-
const resolveOnce = (value)=>{
|
|
138
|
-
if (settled) return;
|
|
139
|
-
settled = true;
|
|
140
|
-
cleanup();
|
|
141
|
-
resolve(value);
|
|
142
|
-
};
|
|
143
|
-
const rejectOnce = (error, terminate = false)=>{
|
|
144
|
-
if (settled) return;
|
|
145
|
-
settled = true;
|
|
146
|
-
if (terminate) terminateDetachedChrome(proc);
|
|
147
|
-
cleanup();
|
|
148
|
-
reject(error);
|
|
149
|
-
};
|
|
150
120
|
const onData = (data)=>{
|
|
151
121
|
output += data.toString();
|
|
152
122
|
const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/);
|
|
153
|
-
if (match)
|
|
123
|
+
if (match) {
|
|
124
|
+
proc.stderr.removeListener('data', onData);
|
|
125
|
+
resolve(match[1]);
|
|
126
|
+
}
|
|
154
127
|
};
|
|
155
128
|
proc.stderr.on('data', onData);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
129
|
+
proc.on('exit', (code)=>{
|
|
130
|
+
proc.stderr.removeListener('data', onData);
|
|
131
|
+
reject(new Error(`Chrome exited with code ${code} before DevTools was ready.\nChrome stderr: ${output}\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`));
|
|
132
|
+
});
|
|
133
|
+
setTimeout(()=>reject(new Error(`Chrome launch timeout.\nChrome stderr: ${output}\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`)), 15000);
|
|
161
134
|
});
|
|
162
135
|
}
|
|
163
136
|
constructor(persistence = {}){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n `--window-size=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(endpointFile);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n }\n\n async closeBrowser(): Promise<void> {\n const endpointFile = this.endpointFile;\n if (!existsSync(endpointFile)) return;\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(endpointFile);\n } catch {}\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n let settled = false;\n const cleanup = () => {\n clearTimeout(timeout);\n proc.stderr!.removeListener('data', onData);\n proc.removeListener('exit', onExit);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n };\n proc.on('exit', onExit);\n\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n true,\n ),\n DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n );\n });\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","PuppeteerBrowserManager","endpointFile","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","userDataDir","mkdir","args","spawn","Promise","resolve","reject","output","settled","cleanup","clearTimeout","timeout","onData","onExit","resolveOnce","value","rejectOnce","error","terminate","data","match","code","signal","Error","setTimeout","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","navigateToUrl","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n `--window-size=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(endpointFile);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n }\n\n async closeBrowser(): Promise<void> {\n const endpointFile = this.endpointFile;\n if (!existsSync(endpointFile)) return;\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(endpointFile);\n } catch {}\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","PuppeteerBrowserManager","endpointFile","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","userDataDir","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","navigateToUrl","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":"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"}
|
|
@@ -124,6 +124,13 @@ async function launchPuppeteerPage(target, preference, browser, existingPage) {
|
|
|
124
124
|
await browserInstance.setCookie(...JSON.parse(cookieFileContent));
|
|
125
125
|
}
|
|
126
126
|
if (ua) await page.setUserAgent(ua);
|
|
127
|
+
if (target.extraHTTPHeaders) {
|
|
128
|
+
const normalizedHeaders = Object.fromEntries(Object.entries(target.extraHTTPHeaders).map(([key, value])=>[
|
|
129
|
+
key,
|
|
130
|
+
String(value)
|
|
131
|
+
]));
|
|
132
|
+
await page.setExtraHTTPHeaders(normalizedHeaders);
|
|
133
|
+
}
|
|
127
134
|
if (viewportConfig) await page.setViewport(viewportConfig);
|
|
128
135
|
const waitForNetworkIdleTimeout = 'number' == typeof target.waitForNetworkIdle?.timeout ? target.waitForNetworkIdle.timeout : defaultWaitForNetworkIdleTimeout;
|
|
129
136
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"puppeteer/agent-launcher.mjs","sources":["../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, { type Browser, type Page } from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n}\n"],"names":["defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"puppeteer/agent-launcher.mjs","sources":["../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, { type Browser, type Page } from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (target.extraHTTPHeaders) {\n // YAML may parse unquoted values into booleans/numbers (e.g. `yes` -> true),\n // but Puppeteer requires string header values, so normalize them here.\n const normalizedHeaders = Object.fromEntries(\n Object.entries(target.extraHTTPHeaders).map(([key, value]) => [\n key,\n String(value),\n ]),\n );\n await page.setExtraHTTPHeaders(normalizedHeaders);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n}\n"],"names":["defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","normalizedHeaders","Object","key","value","String","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"mappings":"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"}
|
|
@@ -115,7 +115,7 @@ class BridgeServer {
|
|
|
115
115
|
(0, shared_utils_namespaceObject.logMsg)('one client connected');
|
|
116
116
|
this.socket = socket;
|
|
117
117
|
const clientVersion = socket.handshake.query.version;
|
|
118
|
-
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.9.2
|
|
118
|
+
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.9.2, browser-side version v${clientVersion}`);
|
|
119
119
|
socket.on(external_common_js_namespaceObject.BridgeEvent.CallResponse, (params)=>{
|
|
120
120
|
const id = params.id;
|
|
121
121
|
const response = params.response;
|
|
@@ -139,7 +139,7 @@ class BridgeServer {
|
|
|
139
139
|
setTimeout(()=>{
|
|
140
140
|
this.onConnect?.();
|
|
141
141
|
const payload = {
|
|
142
|
-
version: "1.9.2
|
|
142
|
+
version: "1.9.2"
|
|
143
143
|
};
|
|
144
144
|
socket.emit(external_common_js_namespaceObject.BridgeEvent.Connected, payload);
|
|
145
145
|
Promise.resolve().then(()=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mode/io-server.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/io-server.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["__webpack_require__","definition","key","Object","obj","prop","Symbol","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/io-server.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["__webpack_require__","definition","key","Object","obj","prop","Symbol","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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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"}
|
|
@@ -138,7 +138,7 @@ class ExtensionBridgePageBrowserSide extends page_js_default() {
|
|
|
138
138
|
throw new Error('Connection denied by user');
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.9.2
|
|
141
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.9.2`, 'log');
|
|
142
142
|
}
|
|
143
143
|
async connect() {
|
|
144
144
|
return await this.setupBridgeClient();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mode/page-browser-side.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","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":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","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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"}
|
|
1
|
+
{"version":3,"file":"bridge-mode/page-browser-side.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","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":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","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":";;;IACAA,oBAAoB,CAAC,GAAG,CAACC;QACxB,IAAIC,SAASD,UAAUA,OAAO,UAAU,GACvC,IAAOA,MAAM,CAAC,UAAU,GACxB,IAAOA;QACRD,oBAAoB,CAAC,CAACE,QAAQ;YAAE,GAAGA;QAAO;QAC1C,OAAOA;IACR;;;ICPAF,oBAAoB,CAAC,GAAG,CAAC,UAASG;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGH,oBAAoB,CAAC,CAACG,YAAYC,QAAQ,CAACJ,oBAAoB,CAAC,CAAC,UAASI,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAJ,oBAAoB,CAAC,GAAG,CAACM,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFP,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOQ,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;ACYA,MAAMI,0BAA0B;AAEhC,SAASC,WAAWC,GAAuB;IACzC,IAAI,CAACA,KAAK,OAAO;IACjB,OAAOA,AAAQ,kBAARA,OAAyBA,IAAI,UAAU,CAAC;AACjD;AAKA,SAASC,6BACPC,KAAa,EACbC,SAAiB,EACjBC,YAAYN,uBAAuB;IAEnC,OAAO,IAAIO,QAAQ,CAACC;QAClB,IAAIC,UAAU;QACd,MAAMC,SAAS;YACb,IAAID,SAAS;YACbA,UAAU;YACV,IAAI;gBACFE,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAACC;YACvC,EAAE,OAAM,CAAC;YACTC,aAAaC;YACbN;QACF;QAEA,MAAMO,UAAU,CAACC;YACf,IAAI,CAACA,KAAK,OAAO;YACjB,IAAIA,AAAe,eAAfA,IAAI,MAAM,EAAiB,OAAO;YACtC,MAAMC,aAAaD,IAAI,GAAG,IAAIA,IAAI,UAAU,IAAI;YAGhD,IAAIf,WAAWgB,eAAe,CAAChB,WAAWI,YAAY,OAAO;YAC7D,OAAO;QACT;QAEA,MAAMO,YAAY,CAChBM,IACAC,OACAH;YAEA,IAAIE,OAAOd,OAAO;YAClB,IAAIW,QAAQC,MAAMN;QACpB;QAEAC,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAACC;QAClC,MAAME,QAAQM,WAAWV,QAAQJ;QAIjCK,OAAO,IAAI,CACR,GAAG,CAACP,OACJ,IAAI,CAAC,CAACY;YACL,IAAID,QAAQC,MAAMN;QACpB,GACC,KAAK,CAAC,KAAO;IAClB;AACF;AAEO,MAAMW,uCAAuCC;IAuBlD,MAAc,oBAAoB;QAChC,MAAMC,WACJ,IAAI,CAAC,cAAc,IAAI,CAAC,eAAe,EAAEC,mCAAAA,uBAAuBA,EAAE;QAMpE,IAAIC,0BAAsD,KAAO;QACjE,IAAI,IAAI,CAAC,mBAAmB,EAC1B,IAAI,CAAC,mBAAmB,GAAG,IAAIlB,QAAiB,CAACC;YAC/CiB,0BAA0BjB;QAC5B;QAGF,IAAI,CAAC,YAAY,GAAG,IAAIkB,sCAAAA,YAAYA,CAClCH,UACA,OAAOI,QAAQC;YAEb,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC5B,MAAMC,UAAU,MAAM,IAAI,CAAC,mBAAmB;gBAC9C,IAAI,CAACA,SACH,MAAM,IAAIC,MAAM;YAEpB;YAEA,IAAI,CAAC,YAAY,CAAC,CAAC,2BAA2B,EAAEH,QAAQ,EAAE;YAC1D,IAAIA,WAAWI,mCAAAA,WAAAA,CAAAA,oBAAgC,EAC7C,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CACpC,IAAI,EACJH;YAIJ,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,cAA0B,EACvC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAEH;YAGzC,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,YAAY,CAACH,IAAI,CAAC,EAAE,EAAY;YAG9C,MAAMxB,QAAQ,MAAM,IAAI,CAAC,cAAc;YACvC,IAAI,CAACA,SAASA,AAAU,MAAVA,OACZ,MAAM,IAAI0B,MAAM;YAKlB,IAAIH,OAAO,UAAU,CAACK,mCAAAA,UAAAA,CAAAA,MAAiB,GAAG;gBACxC,MAAMC,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,KAAK,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAEL;YAClD;YAEA,IAAID,OAAO,UAAU,CAACO,mCAAAA,aAAAA,CAAAA,MAAoB,GAAG;gBAC3C,MAAMD,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,QAAQ,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAEL;YACxD;YAEA,IAAI,CAAC,IAAI,CAACD,OAAyC,EAAE,YACnD,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEA,QAAQ,EAAE;YAInD,IAAI;gBAEF,MAAMQ,SAAS,MAAM,IAAI,CAACR,OAAyC,IAC9DC;gBAEL,OAAOO;YACT,EAAE,OAAOC,GAAG;gBACV,MAAMC,eAAeD,aAAaN,QAAQM,EAAE,OAAO,GAAG;gBACtD,IAAI,CAAC,YAAY,CACf,CAAC,sBAAsB,EAAET,OAAO,EAAE,EAAEU,cAAc,EAClD;gBAEF,MAAM,IAAIP,MAAMO,cAAc;oBAAE,OAAOD;gBAAE;YAC3C;QACF,GAEA,IACS,IAAI,CAAC,OAAO;QAGvB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO;QAG/B,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,IAAI,CAAC,YAAY,CAAC,oCAAoC;YACtD,MAAMP,UAAU,MAAM,IAAI,CAAC,mBAAmB;YAC9CJ,wBAAwBI;YACxB,IAAI,CAAC,mBAAmB,GAAG;YAE3B,IAAI,CAACA,SAAS;gBACZ,IAAI,CAAC,YAAY,CAAC,6BAA6B;gBAC/C,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC5B,IAAI,CAAC,YAAY,GAAG;gBACpB,MAAM,IAAIC,MAAM;YAClB;QACF;QAEA,IAAI,CAAC,YAAY,CACf,uCAAuC,IAAI,CAAC,YAAY,CAAC,aAAa,+BAAwC,EAC9G;IAEJ;IAEA,MAAa,UAAU;QACrB,OAAO,MAAM,IAAI,CAAC,iBAAiB;IACrC;IAEA,MAAa,qBACX5B,GAAW,EACXoC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QACA,MAAMtB,MAAM,MAAML,OAAO,IAAI,CAAC,MAAM,CAAC;YAAET;QAAI;QAC3C,MAAME,QAAQY,IAAI,EAAE;QACpBuB,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOnC,OAAO;QAGd,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEF,KAAK,EAAE;QAC9C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAACE;QAE7B,IAAIkC,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAShC,MAAMnC,6BAA6BC,OAAOF;QAE1C,MAAM,IAAI,CAAC,cAAc,CAACE;IAC5B;IAEA,MAAa,kBACXkC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QACA,MAAME,OAAO,MAAM7B,OAAO,IAAI,CAAC,KAAK,CAAC;YAAE,QAAQ;YAAM,eAAe;QAAK;QACzE,MAAMP,QAAQoC,IAAI,CAAC,EAAE,EAAE;QACvBD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOnC,OAAO;QAEd,IAAI,CAAC,YAAY,CAAC,CAAC,0BAA0B,EAAEoC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE;QAE/D,IAAIF,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAGhC,MAAM,IAAI,CAAC,cAAc,CAAClC;IAC5B;IAEA,MAAa,kBAAkBkC,OAAiC,EAAE;QAChE,IAAI,CAAC,cAAc,GAAGA;IACxB;IAEA,MAAM,UAAU;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,GAAG;YACvE,IAAI,CAAC,YAAY,CAAC,+CAA+C;YACjE,KAAK,MAAMlC,SAAS,IAAI,CAAC,kBAAkB,CACzC,MAAMO,OAAO,IAAI,CAAC,MAAM,CAACP;YAE3B,IAAI,CAAC,kBAAkB,GAAG,EAAE;QAC9B;QAEA,MAAM,KAAK,CAAC;QAEZ,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,YAAY,CAAC,UAAU;YAC5B,IAAI,CAAC,YAAY,GAAG;YACpB,IAAI,CAAC,YAAY;QACnB;IACF;IA7MA,YACSqC,cAAuB,EACvBC,eAA2B,KAAO,CAAC,EACnCC,eAGK,KAAO,CAAC,EACpBC,yBAAyB,IAAI,EACtBC,mBAA4C,CACnD;QACA,KAAK,CAACD,yBAAAA,iBAAAA,IAAAA,EAAAA,kBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,uBAAAA,KAAAA,IAnBR,uBAAO,gBAAP,SAEA,uBAAQ,kBAAR,SAEA,uBAAQ,sBAAR,SAGA,uBAAQ,uBAAR,cAGSH,cAAc,GAAdA,gBAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CAKAE,mBAAmB,GAAnBA,qBAAAA,IAAAA,CAjBF,YAAY,GAAwB,WAInC,kBAAkB,GAAa,EAAE,OAGjC,mBAAmB,GAA4B;IAavD;AAmMF"}
|
package/dist/lib/cli.js
CHANGED
|
@@ -42,7 +42,7 @@ Promise.resolve().then(()=>{
|
|
|
42
42
|
return (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-web', {
|
|
43
43
|
stripPrefix: 'web_',
|
|
44
44
|
argv: parsedOptions.argv,
|
|
45
|
-
version: "1.9.2
|
|
45
|
+
version: "1.9.2",
|
|
46
46
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
47
47
|
});
|
|
48
48
|
}).catch((e)=>{
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -37,7 +37,7 @@ class WebMCPServer extends mcp_namespaceObject.BaseMCPServer {
|
|
|
37
37
|
constructor(toolsManager){
|
|
38
38
|
super({
|
|
39
39
|
name: '@midscene/web-bridge-mcp',
|
|
40
|
-
version: "1.9.2
|
|
40
|
+
version: "1.9.2",
|
|
41
41
|
description: 'Control the browser using natural language commands'
|
|
42
42
|
}, toolsManager);
|
|
43
43
|
}
|
|
@@ -62,7 +62,6 @@ function _define_property(obj, key, value) {
|
|
|
62
62
|
}
|
|
63
63
|
const ENDPOINT_FILE = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), 'midscene-puppeteer-endpoint');
|
|
64
64
|
const USER_DATA_DIR = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), 'midscene-puppeteer-profile');
|
|
65
|
-
const DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30000;
|
|
66
65
|
const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;
|
|
67
66
|
function buildDetachedChromeArgs(options) {
|
|
68
67
|
const viewport = options.viewport ?? viewport_js_namespaceObject.defaultPuppeteerWindowViewportSize;
|
|
@@ -82,16 +81,6 @@ function buildDetachedChromeArgs(options) {
|
|
|
82
81
|
'--force-color-profile=srgb'
|
|
83
82
|
];
|
|
84
83
|
}
|
|
85
|
-
function terminateDetachedChrome(proc) {
|
|
86
|
-
if (proc.killed || null !== proc.exitCode || null !== proc.signalCode) return;
|
|
87
|
-
if ('win32' !== process.platform && proc.pid) try {
|
|
88
|
-
process.kill(-proc.pid, 'SIGKILL');
|
|
89
|
-
return;
|
|
90
|
-
} catch {}
|
|
91
|
-
try {
|
|
92
|
-
proc.kill('SIGKILL');
|
|
93
|
-
} catch {}
|
|
94
|
-
}
|
|
95
84
|
class PuppeteerBrowserManager {
|
|
96
85
|
get endpointFile() {
|
|
97
86
|
return this.persistence.endpointFile || ENDPOINT_FILE;
|
|
@@ -168,36 +157,20 @@ class PuppeteerBrowserManager {
|
|
|
168
157
|
proc.unref();
|
|
169
158
|
return new Promise((resolve, reject)=>{
|
|
170
159
|
let output = '';
|
|
171
|
-
let settled = false;
|
|
172
|
-
const cleanup = ()=>{
|
|
173
|
-
clearTimeout(timeout);
|
|
174
|
-
proc.stderr.removeListener('data', onData);
|
|
175
|
-
proc.removeListener('exit', onExit);
|
|
176
|
-
};
|
|
177
|
-
const resolveOnce = (value)=>{
|
|
178
|
-
if (settled) return;
|
|
179
|
-
settled = true;
|
|
180
|
-
cleanup();
|
|
181
|
-
resolve(value);
|
|
182
|
-
};
|
|
183
|
-
const rejectOnce = (error, terminate = false)=>{
|
|
184
|
-
if (settled) return;
|
|
185
|
-
settled = true;
|
|
186
|
-
if (terminate) terminateDetachedChrome(proc);
|
|
187
|
-
cleanup();
|
|
188
|
-
reject(error);
|
|
189
|
-
};
|
|
190
160
|
const onData = (data)=>{
|
|
191
161
|
output += data.toString();
|
|
192
162
|
const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/);
|
|
193
|
-
if (match)
|
|
163
|
+
if (match) {
|
|
164
|
+
proc.stderr.removeListener('data', onData);
|
|
165
|
+
resolve(match[1]);
|
|
166
|
+
}
|
|
194
167
|
};
|
|
195
168
|
proc.stderr.on('data', onData);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
169
|
+
proc.on('exit', (code)=>{
|
|
170
|
+
proc.stderr.removeListener('data', onData);
|
|
171
|
+
reject(new Error(`Chrome exited with code ${code} before DevTools was ready.\nChrome stderr: ${output}\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`));
|
|
172
|
+
});
|
|
173
|
+
setTimeout(()=>reject(new Error(`Chrome launch timeout.\nChrome stderr: ${output}\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`)), 15000);
|
|
201
174
|
});
|
|
202
175
|
}
|
|
203
176
|
constructor(persistence = {}){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools-puppeteer.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { type ChildProcess, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n `--window-size=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(endpointFile);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n }\n\n async closeBrowser(): Promise<void> {\n const endpointFile = this.endpointFile;\n if (!existsSync(endpointFile)) return;\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(endpointFile);\n } catch {}\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n let settled = false;\n const cleanup = () => {\n clearTimeout(timeout);\n proc.stderr!.removeListener('data', onData);\n proc.removeListener('exit', onExit);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n };\n proc.on('exit', onExit);\n\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n true,\n ),\n DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n );\n });\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","PuppeteerBrowserManager","endpointFile","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","userDataDir","mkdir","args","spawn","Promise","resolve","reject","output","settled","cleanup","clearTimeout","timeout","onData","onExit","resolveOnce","value","rejectOnce","error","terminate","data","match","code","signal","Error","setTimeout","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","navigateToUrl","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":";;;IACAA,oBAAoB,CAAC,GAAG,CAACC;QACxB,IAAIC,SAASD,UAAUA,OAAO,UAAU,GACvC,IAAOA,MAAM,CAAC,UAAU,GACxB,IAAOA;QACRD,oBAAoB,CAAC,CAACE,QAAQ;YAAE,GAAGA;QAAO;QAC1C,OAAOA;IACR;;;ICPAF,oBAAoB,CAAC,GAAG,CAAC,UAASG;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGH,oBAAoB,CAAC,CAACG,YAAYC,QAAQ,CAACJ,oBAAoB,CAAC,CAAC,UAASI,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAJ,oBAAoB,CAAC,GAAG,CAACM,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFP,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOQ,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACcA,MAAMI,gBAAgBC,AAAAA,IAAAA,mCAAAA,IAAAA,AAAAA,EAAKC,AAAAA,IAAAA,iCAAAA,MAAAA,AAAAA,KAAU;AACrC,MAAMC,gBAAgBF,AAAAA,IAAAA,mCAAAA,IAAAA,AAAAA,EAAKC,AAAAA,IAAAA,iCAAAA,MAAAA,AAAAA,KAAU;AACrC,MAAME,oCAAoC;AAEnC,MAAMC,0BAA0BL;AAWhC,SAASM,wBAAwBC,OAGvC;IACC,MAAMC,WAAWD,QAAQ,QAAQ,IAAIE,4BAAAA,kCAAkCA;IAEvE,OAAO;QACL;QACA,CAAC,gBAAgB,EAAEF,QAAQ,WAAW,EAAE;QACxC;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,CAAC,cAAc,EAAEC,SAAS,KAAK,CAAC,CAAC,EAAEA,SAAS,MAAM,EAAE;QACpD;KACD;AACH;AAEA,SAASE,wBAAwBC,IAAkB;IACjD,IAAIA,KAAK,MAAM,IAAIA,AAAkB,SAAlBA,KAAK,QAAQ,IAAaA,AAAoB,SAApBA,KAAK,UAAU,EAC1D;IAGF,IAAIC,AAAqB,YAArBA,QAAQ,QAAQ,IAAgBD,KAAK,GAAG,EAC1C,IAAI;QACFC,QAAQ,IAAI,CAAC,CAACD,KAAK,GAAG,EAAE;QACxB;IACF,EAAE,OAAM,CAAC;IAGX,IAAI;QACFA,KAAK,IAAI,CAAC;IACZ,EAAE,OAAM,CAAC;AACX;AAMA,MAAME;IAKJ,IAAY,eAAe;QACzB,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,IAAIb;IAC1C;IAEA,IAAY,cAAc;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC,WAAW,IAAIG;IACzC;IAEA,MAAM,YACJK,QAAuB,EACyB;QAChD,MAAMM,eAAe,IAAI,CAAC,YAAY;QACtC,IAAIC,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWD,eACb,IAAI;YACF,MAAME,WAAY,OAAMC,AAAAA,IAAAA,yBAAAA,QAAAA,AAAAA,EAASH,cAAc,QAAO,EAAG,IAAI;YAC7D,MAAMI,UAAU,MAAMC,kCAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;gBACnB,iBAAiB;YACnB;YACA,OAAO;gBAAEE;gBAAS,QAAQ;YAAK;QACjC,EAAE,OAAM;YACN,IAAI;gBACF,MAAME,AAAAA,IAAAA,yBAAAA,MAAAA,AAAAA,EAAON;YACf,EAAE,OAAM,CAAC;QACX;QAGF,MAAMO,aAAa,MAAM,IAAI,CAAC,oBAAoB,CAACb;QACnD,MAAMc,AAAAA,IAAAA,yBAAAA,SAAAA,AAAAA,EAAUR,cAAcO;QAE9B,MAAMH,UAAU,MAAMC,kCAAAA,OAAiB,CAAC;YACtC,mBAAmBE;YACnB,iBAAiB;QACnB;QACA,OAAO;YAAEH;YAAS,QAAQ;QAAM;IAClC;IAEA,MAAM,eAA8B;QAClC,MAAMJ,eAAe,IAAI,CAAC,YAAY;QACtC,IAAI,CAACC,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWD,eAAe;QAC/B,IAAI;YACF,MAAME,WAAY,OAAMC,AAAAA,IAAAA,yBAAAA,QAAAA,AAAAA,EAASH,cAAc,QAAO,EAAG,IAAI;YAC7D,MAAMI,UAAU,MAAMC,kCAAAA,OAAiB,CAAC;gBACtC,mBAAmBH;YACrB;YACA,MAAME,QAAQ,KAAK;QACrB,EAAE,OAAM,CAAC;QACT,IAAI;YACF,MAAME,AAAAA,IAAAA,yBAAAA,MAAAA,AAAAA,EAAON;QACf,EAAE,OAAM,CAAC;IACX;IAEA,aAAmB;QACjB,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,aAAa,CAAC,UAAU;YAC7B,IAAI,CAAC,aAAa,GAAG;QACvB;IACF;IAEA,MAAM,qBAAqBN,QAAuB,EAAmB;QACnE,MAAMe,aAAaC,AAAAA,IAAAA,4BAAAA,iBAAAA,AAAAA;QACnB,MAAMC,cAAc,IAAI,CAAC,WAAW;QAEpC,MAAMC,AAAAA,IAAAA,yBAAAA,KAAAA,AAAAA,EAAMD,aAAa;YAAE,WAAW;QAAK;QAE3C,MAAME,OAAOrB,wBAAwB;YACnCmB;YACAjB;QACF;QAEA,MAAMG,OAAOiB,AAAAA,IAAAA,4CAAAA,KAAAA,AAAAA,EAAML,YAAYI,MAAM;YACnC,UAAU;YACV,OAAO;gBAAC;gBAAU;gBAAU;aAAO;QACrC;QACAhB,KAAK,KAAK;QAEV,OAAO,IAAIkB,QAAgB,CAACC,SAASC;YACnC,IAAIC,SAAS;YACb,IAAIC,UAAU;YACd,MAAMC,UAAU;gBACdC,aAAaC;gBACbzB,KAAK,MAAM,CAAE,cAAc,CAAC,QAAQ0B;gBACpC1B,KAAK,cAAc,CAAC,QAAQ2B;YAC9B;YACA,MAAMC,cAAc,CAACC;gBACnB,IAAIP,SAAS;gBACbA,UAAU;gBACVC;gBACAJ,QAAQU;YACV;YACA,MAAMC,aAAa,CAACC,OAAcC,YAAY,KAAK;gBACjD,IAAIV,SAAS;gBACbA,UAAU;gBACV,IAAIU,WACFjC,wBAAwBC;gBAE1BuB;gBACAH,OAAOW;YACT;YACA,MAAML,SAAS,CAACO;gBACdZ,UAAUY,KAAK,QAAQ;gBACvB,MAAMC,QAAQb,OAAO,KAAK,CAAC;gBAC3B,IAAIa,OACFN,YAAYM,KAAK,CAAC,EAAE;YAExB;YACAlC,KAAK,MAAM,CAAE,EAAE,CAAC,QAAQ0B;YAExB,MAAMC,SAAS,CAACQ,MAAqBC;gBACnCN,WACE,IAAIO,MACF,CAAC,wBAAwB,EAAEF,QAAQC,OAAO,4CAA4C,EAAEf,OAAO,uEAAuE,CAAC;YAG7K;YACArB,KAAK,EAAE,CAAC,QAAQ2B;YAEhB,MAAMF,UAAUa,WACd,IACER,WACE,IAAIO,MACF,CAAC,uCAAuC,EAAEhB,OAAO,uEAAuE,CAAC,GAE3H,OAEJ5B;QAEJ;IACF;IAlIA,YAA6B8C,cAA2C,CAAC,CAAC,CAAE;;QAF5E;aAE6BA,WAAW,GAAXA;aAF7B,aAAa,GAAmB;IAE6C;AAmI/E;AAEA,MAAMC,wBAAwB,IAAItC;AAM3B,MAAMuC,kCAAkCC,2BAAAA,iBAAiBA;IAepD,0BAA0B;QAClC,OAAO;IACT;IAEU,wBAAwB;QAChC,OAAO,IAAIC,yCAAAA,UAAUA,CAAC;YACpB,YAAYC,qBAAAA,cAAAA,CAAAA,MAAqB,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU,IAAI,CAAC,QAAQ,IAAIC,4BAAAA,6BAA6BA;YACxD,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YAAYC,aAAsB,EAA2B;QAE3E,IAAI,IAAI,CAAC,KAAK,IAAIA,eAAe;YAC/B,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAM,CAAC;YACT,IAAI,CAAC,KAAK,GAAGC;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAEjC,MAAM,EAAEzC,OAAO,EAAE0C,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,CAC/D,IAAI,CAAC,QAAQ;QAEf,IAAI,CAAC,cAAc,CAAC,aAAa,GAAG1C;QAEpC,MAAM2C,QAAQ,MAAM3C,QAAQ,KAAK;QACjC,IAAI4C;QAEJ,IAAIJ,eAAe;YACjBI,OAAO,MAAM5C,QAAQ,OAAO;YAC5B,IAAI,IAAI,CAAC,QAAQ,EACf,MAAM4C,KAAK,WAAW,CAAC,IAAI,CAAC,QAAQ;YAEtC,MAAMA,KAAK,IAAI,CAACJ,eAAe;gBAC7B,SAAS;gBACT,WAAW;YACb;QACF,OAAO;YAEL,MAAMK,WAAWF,MAAM,MAAM,CAAC,CAACG,IAAM,eAAe,IAAI,CAACA,EAAE,GAAG;YAC9DF,OACEC,SAAS,MAAM,GAAG,IACdA,QAAQ,CAACA,SAAS,MAAM,GAAG,EAAE,GAC7BF,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,IAAK,MAAM3C,QAAQ,OAAO;YAEvD,IAAI0C,QACF,MAAME,KAAK,YAAY;YAEzB,IAAI,IAAI,CAAC,QAAQ,EACf,MAAMA,KAAK,WAAW,CAAC,IAAI,CAAC,QAAQ;QAExC;QAEA,MAAMG,gBAAgB,IAAI,CAAC,yBAAyB;QACpD,IAAI,CAAC,KAAK,GAAG,IAAIC,yBAAAA,cAAcA,CAACJ,MAAkC;YAChE,GAAIG,iBAAiB,CAAC,CAAC;QACzB;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAa,UAAyB;QACpC,MAAM,KAAK,CAAC;QACZ,IAAI,CAAC,cAAc,CAAC,UAAU;IAChC;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,qBAAAA,CAAAA,CAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOxC;oBACd,MAAM,EAAEyC,GAAG,EAAE,GAAGzC;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGgC;oBACf;oBAEA,MAAMU,gBAAgB,IAAI,CAAC,yBAAyB,CAClDD,OAAO;oBAET,IAAI,CAAC,sBAAsB,CAACC;oBAC5B,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAACD;oBAEpC,MAAME,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQH,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEG,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGX;oBACf;oBACA,IAAI,CAAC,cAAc,CAAC,UAAU;oBAC9B,OAAO,IAAI,CAAC,eAAe,CACzB;gBAEJ;YACF;YACA;gBACE,MAAM;gBACN,aAAa;gBACb,QAAQ,CAAC;gBACT,SAAS;oBACP,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGA;oBACf;oBACA,MAAM,IAAI,CAAC,cAAc,CAAC,YAAY;oBACtC,OAAO,IAAI,CAAC,eAAe,CAAC;gBAC9B;YACF;SACD;IACH;IA1JA,YACEnD,QAAuB,EACvBD,UAA4C,CAAC,CAAC,CAC9C;QACA,KAAK,IAPP,uBAAiB,YAAjB,SACA,uBAAiB,kBAAjB;QAOE,IAAI,CAAC,QAAQ,GAAGC,WAAW;YAAE,GAAGA,QAAQ;QAAC,IAAImD;QAC7C,IAAI,CAAC,cAAc,GAAGpD,QAAQ,WAAW,GACrC,IAAIM,wBAAwBN,QAAQ,WAAW,IAC/C4C;IACN;AAkJF"}
|
|
1
|
+
{"version":3,"file":"mcp-tools-puppeteer.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n `--window-size=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(endpointFile);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n }\n\n async closeBrowser(): Promise<void> {\n const endpointFile = this.endpointFile;\n if (!existsSync(endpointFile)) return;\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(endpointFile);\n } catch {}\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(reportOptions ?? {}),\n });\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","PuppeteerBrowserManager","endpointFile","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","userDataDir","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","navigateToUrl","undefined","reused","pages","page","webPages","p","reportOptions","PuppeteerAgent","z","url","reportSession","screenshot","label"],"mappings":"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"}
|
|
@@ -170,6 +170,13 @@ async function launchPuppeteerPage(target, preference, browser, existingPage) {
|
|
|
170
170
|
await browserInstance.setCookie(...JSON.parse(cookieFileContent));
|
|
171
171
|
}
|
|
172
172
|
if (ua) await page.setUserAgent(ua);
|
|
173
|
+
if (target.extraHTTPHeaders) {
|
|
174
|
+
const normalizedHeaders = Object.fromEntries(Object.entries(target.extraHTTPHeaders).map(([key, value])=>[
|
|
175
|
+
key,
|
|
176
|
+
String(value)
|
|
177
|
+
]));
|
|
178
|
+
await page.setExtraHTTPHeaders(normalizedHeaders);
|
|
179
|
+
}
|
|
173
180
|
if (viewportConfig) await page.setViewport(viewportConfig);
|
|
174
181
|
const waitForNetworkIdleTimeout = 'number' == typeof target.waitForNetworkIdle?.timeout ? target.waitForNetworkIdle.timeout : defaultWaitForNetworkIdleTimeout;
|
|
175
182
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"puppeteer/agent-launcher.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { readFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, { type Browser, type Page } from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"puppeteer/agent-launcher.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { readFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, { type Browser, type Page } from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (target.extraHTTPHeaders) {\n // YAML may parse unquoted values into booleans/numbers (e.g. `yes` -> true),\n // but Puppeteer requires string header values, so normalize them here.\n const normalizedHeaders = Object.fromEntries(\n Object.entries(target.extraHTTPHeaders).map(([key, value]) => [\n key,\n String(value),\n ]),\n );\n await page.setExtraHTTPHeaders(normalizedHeaders);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","normalizedHeaders","value","String","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"Browser use",
|
|
9
9
|
"Android use"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.9.2
|
|
11
|
+
"version": "1.9.2",
|
|
12
12
|
"repository": "https://github.com/web-infra-dev/midscene",
|
|
13
13
|
"homepage": "https://midscenejs.com/",
|
|
14
14
|
"main": "./dist/lib/index.js",
|
|
@@ -111,9 +111,9 @@
|
|
|
111
111
|
"socket.io": "^4.8.1",
|
|
112
112
|
"socket.io-client": "4.8.1",
|
|
113
113
|
"ws": "^8.18.1",
|
|
114
|
-
"@midscene/
|
|
115
|
-
"@midscene/
|
|
116
|
-
"@midscene/
|
|
114
|
+
"@midscene/playground": "1.9.2",
|
|
115
|
+
"@midscene/shared": "1.9.2",
|
|
116
|
+
"@midscene/core": "1.9.2"
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
119
|
"@playwright/test": "^1.45.0",
|