@midscene/web 1.5.8-beta-20260325025832.0 → 1.5.8

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.
@@ -35,10 +35,6 @@ __webpack_require__.r(__webpack_exports__);
35
35
  __webpack_require__.d(__webpack_exports__, {
36
36
  WebCdpMidsceneTools: ()=>WebCdpMidsceneTools
37
37
  });
38
- const external_node_child_process_namespaceObject = require("node:child_process");
39
- const external_node_fs_namespaceObject = require("node:fs");
40
- const external_node_os_namespaceObject = require("node:os");
41
- const external_node_path_namespaceObject = require("node:path");
42
38
  const core_namespaceObject = require("@midscene/core");
43
39
  const mcp_namespaceObject = require("@midscene/shared/mcp");
44
40
  const external_puppeteer_core_namespaceObject = require("puppeteer-core");
@@ -55,74 +51,6 @@ function _define_property(obj, key, value) {
55
51
  else obj[key] = value;
56
52
  return obj;
57
53
  }
58
- const PROXY_ENDPOINT_FILE = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), 'midscene-cdp-proxy-endpoint');
59
- const PROXY_PID_FILE = (0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), 'midscene-cdp-proxy-pid');
60
- function isProxyAlive() {
61
- if (!(0, external_node_fs_namespaceObject.existsSync)(PROXY_PID_FILE)) return false;
62
- try {
63
- const pid = Number((0, external_node_fs_namespaceObject.readFileSync)(PROXY_PID_FILE, 'utf-8').trim());
64
- process.kill(pid, 0);
65
- return true;
66
- } catch {
67
- return false;
68
- }
69
- }
70
- function readProxyEndpoint() {
71
- if (!(0, external_node_fs_namespaceObject.existsSync)(PROXY_ENDPOINT_FILE)) return null;
72
- try {
73
- return (0, external_node_fs_namespaceObject.readFileSync)(PROXY_ENDPOINT_FILE, 'utf-8').trim();
74
- } catch {
75
- return null;
76
- }
77
- }
78
- function spawnProxy(chromeEndpoint) {
79
- return new Promise((resolve, reject)=>{
80
- const proxyScript = (0, external_node_path_namespaceObject.join)(__dirname, 'cdp-proxy.js');
81
- const proc = (0, external_node_child_process_namespaceObject.spawn)(process.execPath, [
82
- proxyScript,
83
- chromeEndpoint
84
- ], {
85
- detached: true,
86
- stdio: [
87
- 'ignore',
88
- 'pipe',
89
- 'ignore'
90
- ]
91
- });
92
- proc.unref();
93
- let output = '';
94
- const onData = (chunk)=>{
95
- output += chunk.toString();
96
- const lines = output.split('\n');
97
- for (const line of lines)if (line.trim()) try {
98
- const parsed = JSON.parse(line);
99
- if (parsed.endpoint) {
100
- proc.stdout.removeListener('data', onData);
101
- resolve(parsed.endpoint);
102
- return;
103
- }
104
- } catch {}
105
- };
106
- proc.stdout.on('data', onData);
107
- proc.on('error', (err)=>reject(new Error(`Failed to spawn proxy: ${err.message}`)));
108
- proc.on('exit', (code)=>{
109
- if (!output.includes('"endpoint"')) reject(new Error(`Proxy exited with code ${code} before ready`));
110
- });
111
- setTimeout(()=>reject(new Error('Proxy startup timeout (10s)')), 10000);
112
- });
113
- }
114
- async function getProxyEndpoint(chromeEndpoint) {
115
- if (isProxyAlive()) {
116
- const endpoint = readProxyEndpoint();
117
- if (endpoint) return endpoint;
118
- }
119
- try {
120
- return await spawnProxy(chromeEndpoint);
121
- } catch (err) {
122
- console.error(`[cdp] proxy failed, falling back to direct connection: ${err}`);
123
- return chromeEndpoint;
124
- }
125
- }
126
54
  class WebCdpMidsceneTools extends mcp_namespaceObject.BaseMidsceneTools {
127
55
  createTemporaryDevice() {
128
56
  return new external_static_index_js_namespaceObject.StaticPage({
@@ -144,32 +72,21 @@ class WebCdpMidsceneTools extends mcp_namespaceObject.BaseMidsceneTools {
144
72
  this.agent = void 0;
145
73
  }
146
74
  if (this.agent) return this.agent;
147
- if (!this.activeBrowser) {
148
- const endpoint = await getProxyEndpoint(this.cdpEndpoint);
149
- this.activeBrowser = await external_puppeteer_core_default().connect({
150
- browserWSEndpoint: endpoint,
151
- defaultViewport: null
152
- });
153
- }
75
+ if (!this.activeBrowser) this.activeBrowser = await external_puppeteer_core_default().connect({
76
+ browserWSEndpoint: this.cdpEndpoint,
77
+ defaultViewport: null
78
+ });
154
79
  const browser = this.activeBrowser;
155
80
  const pages = await browser.pages();
156
- const webPages = pages.filter((p)=>/^https?:\/\//.test(p.url()));
157
81
  let page;
158
- if (navigateToUrl) if (webPages.length > 0) {
159
- page = webPages[webPages.length - 1];
160
- await page.bringToFront();
161
- await page.goto(navigateToUrl, {
162
- timeout: 30000,
163
- waitUntil: 'domcontentloaded'
164
- });
165
- } else {
82
+ if (navigateToUrl) {
166
83
  page = await browser.newPage();
167
84
  await page.goto(navigateToUrl, {
168
85
  timeout: 30000,
169
86
  waitUntil: 'domcontentloaded'
170
87
  });
171
- }
172
- else {
88
+ } else {
89
+ const webPages = pages.filter((p)=>/^https?:\/\//.test(p.url()));
173
90
  page = webPages.length > 0 ? webPages[webPages.length - 1] : pages[pages.length - 1] || await browser.newPage();
174
91
  await page.bringToFront();
175
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools-cdp.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-cdp.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, readFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst PROXY_ENDPOINT_FILE = join(tmpdir(), 'midscene-cdp-proxy-endpoint');\nconst PROXY_PID_FILE = join(tmpdir(), 'midscene-cdp-proxy-pid');\n\n/**\n * Check if a previously spawned proxy process is still alive.\n */\nfunction isProxyAlive(): boolean {\n if (!existsSync(PROXY_PID_FILE)) return false;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 0); // signal 0 = existence check\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the proxy endpoint written by cdp-proxy.ts.\n */\nfunction readProxyEndpoint(): string | null {\n if (!existsSync(PROXY_ENDPOINT_FILE)) return null;\n try {\n return readFileSync(PROXY_ENDPOINT_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Spawn the CDP proxy process and wait for it to print the endpoint.\n */\nfunction spawnProxy(chromeEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const proxyScript = join(__dirname, 'cdp-proxy.js');\n const proc = spawn(process.execPath, [proxyScript, chromeEndpoint], {\n detached: true,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n proc.unref();\n\n let output = '';\n const onData = (chunk: Buffer) => {\n output += chunk.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n if (parsed.endpoint) {\n proc.stdout!.removeListener('data', onData);\n resolve(parsed.endpoint);\n return;\n }\n } catch {}\n }\n };\n proc.stdout!.on('data', onData);\n\n proc.on('error', (err) =>\n reject(new Error(`Failed to spawn proxy: ${err.message}`)),\n );\n proc.on('exit', (code) => {\n if (!output.includes('\"endpoint\"')) {\n reject(new Error(`Proxy exited with code ${code} before ready`));\n }\n });\n\n setTimeout(() => reject(new Error('Proxy startup timeout (10s)')), 10000);\n });\n}\n\n/**\n * Get the proxy endpoint, spawning the proxy if needed.\n * Falls back to direct connection if proxy cannot be started.\n */\nasync function getProxyEndpoint(chromeEndpoint: string): Promise<string> {\n // If proxy is alive and endpoint file exists, reuse it\n if (isProxyAlive()) {\n const endpoint = readProxyEndpoint();\n if (endpoint) return endpoint;\n }\n\n // Spawn a new proxy\n try {\n return await spawnProxy(chromeEndpoint);\n } catch (err) {\n console.error(\n `[cdp] proxy failed, falling back to direct connection: ${err}`,\n );\n return chromeEndpoint;\n }\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\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 (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n const pages = await browser.pages();\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Reuse the last web page\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n await page.bringToFront();\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. 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 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 via CDP 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 (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","PROXY_ENDPOINT_FILE","join","tmpdir","PROXY_PID_FILE","isProxyAlive","existsSync","pid","Number","readFileSync","process","readProxyEndpoint","spawnProxy","chromeEndpoint","Promise","resolve","reject","proxyScript","__dirname","proc","spawn","output","onData","chunk","lines","line","parsed","JSON","err","Error","code","setTimeout","getProxyEndpoint","endpoint","console","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","undefined","puppeteer","browser","pages","webPages","p","page","PuppeteerAgent","z","args","url","screenshot","label","cdpEndpoint"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-tools-cdp.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-cdp.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 { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\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 (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to the existing browser via CDP endpoint\n if (!this.activeBrowser) {\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: this.cdpEndpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\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 await page.bringToFront();\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. 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 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 via CDP 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 (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","console","undefined","puppeteer","browser","pages","page","webPages","p","PuppeteerAgent","z","args","url","screenshot","label","cdpEndpoint"],"mappings":"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"}
@@ -6,9 +6,6 @@ import { StaticPage } from './static';
6
6
  * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.
7
7
  * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects
8
8
  * to a browser that is already running with remote debugging enabled.
9
- *
10
- * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups
11
- * when Chrome's settings-based remote debugging is used.
12
9
  */
13
10
  export declare class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {
14
11
  private cdpEndpoint;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "Browser use",
9
9
  "Android use"
10
10
  ],
11
- "version": "1.5.8-beta-20260325025832.0",
11
+ "version": "1.5.8",
12
12
  "repository": "https://github.com/web-infra-dev/midscene",
13
13
  "homepage": "https://midscenejs.com/",
14
14
  "main": "./dist/lib/index.js",
@@ -109,9 +109,9 @@
109
109
  "puppeteer-core": "24.6.0",
110
110
  "socket.io": "^4.8.1",
111
111
  "socket.io-client": "4.8.1",
112
- "@midscene/core": "1.5.8-beta-20260325025832.0",
113
- "@midscene/playground": "1.5.8-beta-20260325025832.0",
114
- "@midscene/shared": "1.5.8-beta-20260325025832.0"
112
+ "@midscene/shared": "1.5.8",
113
+ "@midscene/playground": "1.5.8",
114
+ "@midscene/core": "1.5.8"
115
115
  },
116
116
  "devDependencies": {
117
117
  "@playwright/test": "^1.45.0",
@@ -1,213 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { existsSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { createServer } from "node:http";
4
- import { connect } from "node:net";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { URL } from "node:url";
8
- const IDLE_TIMEOUT_MS = 300000;
9
- const PROXY_ENDPOINT_FILE = join(tmpdir(), 'midscene-cdp-proxy-endpoint');
10
- const PROXY_PID_FILE = join(tmpdir(), 'midscene-cdp-proxy-pid');
11
- const chromeEndpoint = process.argv[2];
12
- if (!chromeEndpoint) {
13
- process.stderr.write('Usage: node cdp-proxy.js <chrome-ws-endpoint>\n');
14
- process.exit(1);
15
- }
16
- function cleanup() {
17
- try {
18
- if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);
19
- } catch {}
20
- try {
21
- if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);
22
- } catch {}
23
- }
24
- function shutdown(reason) {
25
- process.stderr.write(`[cdp-proxy] shutting down: ${reason}\n`);
26
- cleanup();
27
- process.exit(0);
28
- }
29
- process.on('SIGTERM', ()=>shutdown('SIGTERM'));
30
- process.on('SIGINT', ()=>shutdown('SIGINT'));
31
- process.on('uncaughtException', (e)=>shutdown(`uncaught: ${e.message}`));
32
- let idleTimer = null;
33
- function resetIdleTimer() {
34
- if (idleTimer) clearTimeout(idleTimer);
35
- idleTimer = setTimeout(()=>shutdown('idle timeout (5min)'), IDLE_TIMEOUT_MS);
36
- }
37
- resetIdleTimer();
38
- function encodeFrame(data, opcode = 0x01, mask = false) {
39
- const len = data.length;
40
- let headerLen = 2;
41
- if (len > 65535) headerLen += 8;
42
- else if (len > 125) headerLen += 2;
43
- if (mask) headerLen += 4;
44
- const header = Buffer.alloc(headerLen);
45
- header[0] = 0x80 | opcode;
46
- let offset = 1;
47
- if (len > 65535) {
48
- header[offset++] = (mask ? 0x80 : 0) | 127;
49
- header.writeBigUInt64BE(BigInt(len), offset);
50
- offset += 8;
51
- } else if (len > 125) {
52
- header[offset++] = (mask ? 0x80 : 0) | 126;
53
- header.writeUInt16BE(len, offset);
54
- offset += 2;
55
- } else header[offset++] = (mask ? 0x80 : 0) | len;
56
- if (mask) {
57
- const maskBytes = Buffer.from([
58
- 256 * Math.random(),
59
- 256 * Math.random(),
60
- 256 * Math.random(),
61
- 256 * Math.random()
62
- ]);
63
- maskBytes.copy(header, offset);
64
- const masked = Buffer.alloc(len);
65
- for(let i = 0; i < len; i++)masked[i] = data[i] ^ maskBytes[3 & i];
66
- return Buffer.concat([
67
- header,
68
- masked
69
- ]);
70
- }
71
- return Buffer.concat([
72
- header,
73
- data
74
- ]);
75
- }
76
- function parseFrame(buf) {
77
- if (buf.length < 2) return null;
78
- const opcode = 0x0f & buf[0];
79
- const isMasked = (0x80 & buf[1]) !== 0;
80
- let payloadLen = 0x7f & buf[1];
81
- let offset = 2;
82
- if (126 === payloadLen) {
83
- if (buf.length < 4) return null;
84
- payloadLen = buf.readUInt16BE(2);
85
- offset = 4;
86
- } else if (127 === payloadLen) {
87
- if (buf.length < 10) return null;
88
- payloadLen = Number(buf.readBigUInt64BE(2));
89
- offset = 10;
90
- }
91
- if (isMasked) offset += 4;
92
- if (buf.length < offset + payloadLen) return null;
93
- let payload;
94
- if (isMasked) {
95
- const maskKey = buf.subarray(offset - 4, offset);
96
- payload = Buffer.alloc(payloadLen);
97
- for(let i = 0; i < payloadLen; i++)payload[i] = buf[offset + i] ^ maskKey[3 & i];
98
- } else payload = buf.subarray(offset, offset + payloadLen);
99
- return {
100
- opcode,
101
- payload,
102
- total: offset + payloadLen
103
- };
104
- }
105
- const chromeUrl = new URL(chromeEndpoint);
106
- const chromeHost = chromeUrl.hostname;
107
- const chromePort = Number(chromeUrl.port) || 80;
108
- const chromePath = chromeUrl.pathname || '/devtools/browser';
109
- let upstream;
110
- let upstreamReady = false;
111
- let upstreamBuf = Buffer.alloc(0);
112
- const clients = new Set();
113
- function connectUpstream() {
114
- upstream = connect({
115
- host: chromeHost,
116
- port: chromePort
117
- }, ()=>{
118
- const key = createHash('sha1').update(String(Date.now())).digest('base64').substring(0, 22);
119
- const req = [
120
- `GET ${chromePath} HTTP/1.1`,
121
- `Host: ${chromeHost}:${chromePort}`,
122
- 'Upgrade: websocket',
123
- 'Connection: Upgrade',
124
- 'Sec-WebSocket-Version: 13',
125
- `Sec-WebSocket-Key: ${key}`,
126
- '',
127
- ''
128
- ].join('\r\n');
129
- upstream.write(req);
130
- });
131
- upstream.on('data', (chunk)=>{
132
- upstreamBuf = Buffer.concat([
133
- upstreamBuf,
134
- chunk
135
- ]);
136
- if (!upstreamReady) {
137
- const idx = upstreamBuf.indexOf('\r\n\r\n');
138
- if (-1 === idx) return;
139
- const headers = upstreamBuf.subarray(0, idx).toString();
140
- if (!headers.includes('101')) return void shutdown(`upstream handshake failed: ${headers.split('\r\n')[0]}`);
141
- upstreamReady = true;
142
- upstreamBuf = upstreamBuf.subarray(idx + 4);
143
- onUpstreamReady();
144
- }
145
- while(upstreamBuf.length > 0){
146
- const frame = parseFrame(upstreamBuf);
147
- if (!frame) break;
148
- if (0x08 === frame.opcode) return void shutdown('upstream sent close frame');
149
- resetIdleTimer();
150
- const outFrame = encodeFrame(frame.payload, frame.opcode, false);
151
- for (const client of clients)if (!client.destroyed) client.write(outFrame);
152
- upstreamBuf = upstreamBuf.subarray(frame.total);
153
- }
154
- });
155
- upstream.on('error', (err)=>shutdown(`upstream error: ${err.message}`));
156
- upstream.on('close', ()=>shutdown('upstream closed'));
157
- }
158
- const httpServer = createServer((_req, res)=>{
159
- res.writeHead(404);
160
- res.end();
161
- });
162
- httpServer.on('upgrade', (req, socket, head)=>{
163
- const key = req.headers['sec-websocket-key'];
164
- if (!key) return void socket.destroy();
165
- const accept = createHash('sha1').update(`${key}258EAFA5-E914-47DA-95CA-5AB5DC085B63`).digest('base64');
166
- socket.write([
167
- 'HTTP/1.1 101 WebSocket Protocol Handshake',
168
- 'Upgrade: WebSocket',
169
- 'Connection: Upgrade',
170
- `Sec-WebSocket-Accept: ${accept}`,
171
- '',
172
- ''
173
- ].join('\r\n'));
174
- clients.add(socket);
175
- resetIdleTimer();
176
- let clientBuf = Buffer.from(head);
177
- socket.on('data', (chunk)=>{
178
- clientBuf = Buffer.concat([
179
- clientBuf,
180
- chunk
181
- ]);
182
- while(clientBuf.length > 0){
183
- const frame = parseFrame(clientBuf);
184
- if (!frame) break;
185
- if (0x08 === frame.opcode) {
186
- clients.delete(socket);
187
- socket.destroy();
188
- return;
189
- }
190
- resetIdleTimer();
191
- const outFrame = encodeFrame(frame.payload, frame.opcode, true);
192
- if (!upstream.destroyed) upstream.write(outFrame);
193
- clientBuf = clientBuf.subarray(frame.total);
194
- }
195
- });
196
- socket.on('close', ()=>clients.delete(socket));
197
- socket.on('error', ()=>clients.delete(socket));
198
- });
199
- function onUpstreamReady() {
200
- httpServer.listen(0, '127.0.0.1', ()=>{
201
- const addr = httpServer.address();
202
- if (!addr || 'string' == typeof addr) return void shutdown('failed to get server address');
203
- const proxyEndpoint = `ws://127.0.0.1:${addr.port}/devtools/browser`;
204
- writeFileSync(PROXY_ENDPOINT_FILE, proxyEndpoint);
205
- writeFileSync(PROXY_PID_FILE, String(process.pid));
206
- process.stdout.write(`${JSON.stringify({
207
- endpoint: proxyEndpoint
208
- })}\n`);
209
- });
210
- }
211
- connectUpstream();
212
-
213
- //# sourceMappingURL=cdp-proxy.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cdp-proxy.mjs","sources":["../../src/cdp-proxy.ts"],"sourcesContent":["/**\n * CDP WebSocket Proxy — standalone process.\n *\n * Holds a single persistent WebSocket connection to Chrome's CDP endpoint and\n * exposes a local WebSocket server. Midscene CLI processes connect to the proxy\n * instead of Chrome directly, so Chrome's \"Allow remote debugging\" permission\n * popup only fires once (when the proxy connects).\n *\n * Exit conditions:\n * 1. Upstream Chrome connection closes or errors.\n * 2. No downstream client message for IDLE_TIMEOUT_MS (default 5 min).\n * 3. SIGTERM / SIGINT.\n *\n * Usage (spawned by mcp-tools-cdp.ts):\n * node cdp-proxy.js <chrome-ws-endpoint>\n *\n * On startup, prints the proxy endpoint to stdout as a single JSON line:\n * {\"endpoint\":\"ws://127.0.0.1:<port>/devtools/browser\"}\n * and writes the same endpoint to PROXY_ENDPOINT_FILE.\n *\n * Implementation uses only Node.js built-ins (no `ws` dependency).\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { type IncomingMessage, createServer } from 'node:http';\nimport { type Socket, connect as netConnect } from 'node:net';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { URL } from 'node:url';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nconst IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst PROXY_ENDPOINT_FILE = join(tmpdir(), 'midscene-cdp-proxy-endpoint');\nconst PROXY_PID_FILE = join(tmpdir(), 'midscene-cdp-proxy-pid');\n\nconst chromeEndpoint = process.argv[2];\nif (!chromeEndpoint) {\n process.stderr.write('Usage: node cdp-proxy.js <chrome-ws-endpoint>\\n');\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Cleanup helper\n// ---------------------------------------------------------------------------\n\nfunction cleanup() {\n try {\n if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);\n } catch {}\n}\n\nfunction shutdown(reason: string) {\n process.stderr.write(`[cdp-proxy] shutting down: ${reason}\\n`);\n cleanup();\n process.exit(0);\n}\n\nprocess.on('SIGTERM', () => shutdown('SIGTERM'));\nprocess.on('SIGINT', () => shutdown('SIGINT'));\nprocess.on('uncaughtException', (e) => shutdown(`uncaught: ${e.message}`));\n\n// ---------------------------------------------------------------------------\n// Idle timer\n// ---------------------------------------------------------------------------\n\nlet idleTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction resetIdleTimer() {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(\n () => shutdown('idle timeout (5min)'),\n IDLE_TIMEOUT_MS,\n );\n}\n\nresetIdleTimer();\n\n// ---------------------------------------------------------------------------\n// Minimal WebSocket frame helpers (RFC 6455)\n// ---------------------------------------------------------------------------\n\nfunction encodeFrame(data: Buffer, opcode = 0x01, mask = false): Buffer {\n const len = data.length;\n let headerLen = 2;\n if (len > 65535) headerLen += 8;\n else if (len > 125) headerLen += 2;\n if (mask) headerLen += 4;\n\n const header = Buffer.alloc(headerLen);\n header[0] = 0x80 | opcode; // FIN + opcode\n let offset = 1;\n\n if (len > 65535) {\n header[offset++] = (mask ? 0x80 : 0) | 127;\n header.writeBigUInt64BE(BigInt(len), offset);\n offset += 8;\n } else if (len > 125) {\n header[offset++] = (mask ? 0x80 : 0) | 126;\n header.writeUInt16BE(len, offset);\n offset += 2;\n } else {\n header[offset++] = (mask ? 0x80 : 0) | len;\n }\n\n if (mask) {\n const maskBytes = Buffer.from([\n Math.random() * 256,\n Math.random() * 256,\n Math.random() * 256,\n Math.random() * 256,\n ]);\n maskBytes.copy(header, offset);\n const masked = Buffer.alloc(len);\n for (let i = 0; i < len; i++) masked[i] = data[i] ^ maskBytes[i & 3];\n return Buffer.concat([header, masked]);\n }\n\n return Buffer.concat([header, data]);\n}\n\ninterface ParsedFrame {\n opcode: number;\n payload: Buffer;\n total: number;\n}\n\nfunction parseFrame(buf: Buffer): ParsedFrame | null {\n if (buf.length < 2) return null;\n const opcode = buf[0] & 0x0f;\n const isMasked = (buf[1] & 0x80) !== 0;\n let payloadLen = buf[1] & 0x7f;\n let offset = 2;\n\n if (payloadLen === 126) {\n if (buf.length < 4) return null;\n payloadLen = buf.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (buf.length < 10) return null;\n payloadLen = Number(buf.readBigUInt64BE(2));\n offset = 10;\n }\n\n if (isMasked) offset += 4;\n if (buf.length < offset + payloadLen) return null;\n\n let payload: Buffer;\n if (isMasked) {\n const maskKey = buf.subarray(offset - 4, offset);\n payload = Buffer.alloc(payloadLen);\n for (let i = 0; i < payloadLen; i++)\n payload[i] = buf[offset + i] ^ maskKey[i & 3];\n } else {\n payload = buf.subarray(offset, offset + payloadLen);\n }\n\n return { opcode, payload, total: offset + payloadLen };\n}\n\n// ---------------------------------------------------------------------------\n// Upstream: connect to Chrome via raw TCP + WebSocket handshake\n// ---------------------------------------------------------------------------\n\nconst chromeUrl = new URL(chromeEndpoint);\nconst chromeHost = chromeUrl.hostname;\nconst chromePort = Number(chromeUrl.port) || 80;\nconst chromePath = chromeUrl.pathname || '/devtools/browser';\n\nlet upstream: Socket;\nlet upstreamReady = false;\nlet upstreamBuf = Buffer.alloc(0);\n\n// Track all downstream client sockets\nconst clients = new Set<Socket>();\n\nfunction connectUpstream() {\n upstream = netConnect({ host: chromeHost, port: chromePort }, () => {\n // Send WebSocket upgrade request\n const key = createHash('sha1')\n .update(String(Date.now()))\n .digest('base64')\n .substring(0, 22);\n const req = [\n `GET ${chromePath} HTTP/1.1`,\n `Host: ${chromeHost}:${chromePort}`,\n 'Upgrade: websocket',\n 'Connection: Upgrade',\n 'Sec-WebSocket-Version: 13',\n `Sec-WebSocket-Key: ${key}`,\n '',\n '',\n ].join('\\r\\n');\n upstream.write(req);\n });\n\n upstream.on('data', (chunk: Buffer) => {\n upstreamBuf = Buffer.concat([upstreamBuf, chunk]);\n\n if (!upstreamReady) {\n const idx = upstreamBuf.indexOf('\\r\\n\\r\\n');\n if (idx === -1) return;\n const headers = upstreamBuf.subarray(0, idx).toString();\n if (!headers.includes('101')) {\n shutdown(`upstream handshake failed: ${headers.split('\\r\\n')[0]}`);\n return;\n }\n upstreamReady = true;\n upstreamBuf = upstreamBuf.subarray(idx + 4);\n onUpstreamReady();\n }\n\n // Parse and forward frames to all downstream clients\n while (upstreamBuf.length > 0) {\n const frame = parseFrame(upstreamBuf);\n if (!frame) break;\n\n if (frame.opcode === 0x08) {\n // Close frame\n shutdown('upstream sent close frame');\n return;\n }\n\n resetIdleTimer();\n\n // Re-encode as unmasked server frame and send to all clients\n const outFrame = encodeFrame(frame.payload, frame.opcode, false);\n for (const client of clients) {\n if (!client.destroyed) client.write(outFrame);\n }\n\n upstreamBuf = upstreamBuf.subarray(frame.total);\n }\n });\n\n upstream.on('error', (err) => shutdown(`upstream error: ${err.message}`));\n upstream.on('close', () => shutdown('upstream closed'));\n}\n\n// ---------------------------------------------------------------------------\n// Downstream: HTTP server that upgrades to WebSocket\n// ---------------------------------------------------------------------------\n\nconst httpServer = createServer((_req, res) => {\n res.writeHead(404);\n res.end();\n});\n\nhttpServer.on(\n 'upgrade',\n (req: IncomingMessage, socket: Socket, head: Buffer) => {\n const key = req.headers['sec-websocket-key'];\n if (!key) {\n socket.destroy();\n return;\n }\n\n // Complete WebSocket handshake\n const accept = createHash('sha1')\n .update(`${key}258EAFA5-E914-47DA-95CA-5AB5DC085B63`)\n .digest('base64');\n socket.write(\n [\n 'HTTP/1.1 101 WebSocket Protocol Handshake',\n 'Upgrade: WebSocket',\n 'Connection: Upgrade',\n `Sec-WebSocket-Accept: ${accept}`,\n '',\n '',\n ].join('\\r\\n'),\n );\n\n clients.add(socket);\n resetIdleTimer();\n\n let clientBuf = Buffer.from(head);\n\n socket.on('data', (chunk: Buffer) => {\n clientBuf = Buffer.concat([clientBuf, chunk]);\n\n while (clientBuf.length > 0) {\n const frame = parseFrame(clientBuf);\n if (!frame) break;\n\n if (frame.opcode === 0x08) {\n // Close frame from client\n clients.delete(socket);\n socket.destroy();\n return;\n }\n\n resetIdleTimer();\n\n // Re-encode as masked client frame and send upstream\n const outFrame = encodeFrame(frame.payload, frame.opcode, true);\n if (!upstream.destroyed) upstream.write(outFrame);\n\n clientBuf = clientBuf.subarray(frame.total);\n }\n });\n\n socket.on('close', () => clients.delete(socket));\n socket.on('error', () => clients.delete(socket));\n },\n);\n\n// ---------------------------------------------------------------------------\n// Start\n// ---------------------------------------------------------------------------\n\nfunction onUpstreamReady() {\n httpServer.listen(0, '127.0.0.1', () => {\n const addr = httpServer.address();\n if (!addr || typeof addr === 'string') {\n shutdown('failed to get server address');\n return;\n }\n\n const proxyEndpoint = `ws://127.0.0.1:${addr.port}/devtools/browser`;\n\n writeFileSync(PROXY_ENDPOINT_FILE, proxyEndpoint);\n writeFileSync(PROXY_PID_FILE, String(process.pid));\n\n // Print to stdout so the spawner can read it\n process.stdout.write(`${JSON.stringify({ endpoint: proxyEndpoint })}\\n`);\n });\n}\n\nconnectUpstream();\n"],"names":["IDLE_TIMEOUT_MS","PROXY_ENDPOINT_FILE","join","tmpdir","PROXY_PID_FILE","chromeEndpoint","process","cleanup","existsSync","unlinkSync","shutdown","reason","e","idleTimer","resetIdleTimer","clearTimeout","setTimeout","encodeFrame","data","opcode","mask","len","headerLen","header","Buffer","offset","BigInt","maskBytes","Math","masked","i","parseFrame","buf","isMasked","payloadLen","Number","payload","maskKey","chromeUrl","URL","chromeHost","chromePort","chromePath","upstream","upstreamReady","upstreamBuf","clients","Set","connectUpstream","netConnect","key","createHash","String","Date","req","chunk","idx","headers","onUpstreamReady","frame","outFrame","client","err","httpServer","createServer","_req","res","socket","head","accept","clientBuf","addr","proxyEndpoint","writeFileSync","JSON"],"mappings":"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"}