@wong2kim/wmux 1.1.2 → 2.0.0
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/README.md +64 -25
- package/dist/cli/shared/rpc.js +6 -0
- package/dist/cli/shared/types.js +108 -4
- package/dist/mcp/mcp/playwright/PlaywrightEngine.js +193 -86
- package/dist/mcp/mcp/playwright/anti-detection.js +12 -7
- package/dist/mcp/mcp/playwright/security.js +29 -0
- package/dist/mcp/mcp/playwright/tools/extraction.js +3 -3
- package/dist/mcp/mcp/playwright/tools/file.js +4 -4
- package/dist/mcp/mcp/playwright/tools/inspection.js +93 -29
- package/dist/mcp/mcp/playwright/tools/interaction.js +207 -137
- package/dist/mcp/mcp/playwright/tools/navigation.js +37 -14
- package/dist/mcp/mcp/playwright/tools/state.js +4 -4
- package/dist/mcp/mcp/playwright/tools/utility.js +2 -2
- package/dist/mcp/mcp/playwright/tools/wait.js +10 -2
- package/dist/mcp/shared/rpc.js +6 -0
- package/dist/mcp/shared/types.js +108 -4
- package/package.json +6 -5
|
@@ -3,32 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PlaywrightEngine = void 0;
|
|
4
4
|
const playwright_core_1 = require("playwright-core");
|
|
5
5
|
const wmux_client_1 = require("../wmux-client");
|
|
6
|
-
const MAX_CONNECT_RETRIES =
|
|
7
|
-
const RETRY_DELAY_MS =
|
|
8
|
-
const PAGE_FIND_RETRIES =
|
|
9
|
-
const PAGE_FIND_DELAY_MS =
|
|
6
|
+
const MAX_CONNECT_RETRIES = 1;
|
|
7
|
+
const RETRY_DELAY_MS = 500;
|
|
8
|
+
const PAGE_FIND_RETRIES = 1;
|
|
9
|
+
const PAGE_FIND_DELAY_MS = 300;
|
|
10
10
|
function sleep(ms) {
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Returns true if the URL belongs to the Electron main renderer window.
|
|
15
|
-
* Navigating these pages would destroy the app — they must never be returned.
|
|
16
15
|
*/
|
|
17
16
|
function isElectronShellUrl(url) {
|
|
18
17
|
return (url.startsWith('http://localhost:') ||
|
|
18
|
+
url.startsWith('http://127.0.0.1:') ||
|
|
19
19
|
url.startsWith('devtools://') ||
|
|
20
20
|
url.startsWith('chrome://'));
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* PlaywrightEngine -- singleton wrapper around playwright-core's Chromium CDP connection.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Strategy: Connect to the Electron browser endpoint, then use CDP Target domain
|
|
26
|
+
* to discover and attach to webview targets that aren't visible as regular pages.
|
|
27
27
|
*/
|
|
28
28
|
class PlaywrightEngine {
|
|
29
29
|
constructor() {
|
|
30
30
|
this.browser = null;
|
|
31
31
|
this.cdpPort = null;
|
|
32
|
+
this.playwrightFailed = false;
|
|
32
33
|
}
|
|
33
34
|
static getInstance() {
|
|
34
35
|
if (!PlaywrightEngine.instance) {
|
|
@@ -43,25 +44,30 @@ class PlaywrightEngine {
|
|
|
43
44
|
await this.disconnect();
|
|
44
45
|
this.browser = await playwright_core_1.chromium.connectOverCDP(`http://localhost:${cdpPort}`);
|
|
45
46
|
this.cdpPort = cdpPort;
|
|
46
|
-
console.
|
|
47
|
+
console.error(`[PlaywrightEngine] Connected to CDP on port ${cdpPort}`);
|
|
48
|
+
// Enable auto-attach so Electron webview targets become discoverable as Playwright pages.
|
|
49
|
+
// Without this, <webview> tags in Electron are separate renderer processes that
|
|
50
|
+
// don't appear in browser.contexts().pages().
|
|
51
|
+
try {
|
|
52
|
+
const session = await this.browser.newBrowserCDPSession();
|
|
53
|
+
await session.send('Target.setAutoAttach', {
|
|
54
|
+
autoAttach: true,
|
|
55
|
+
waitForDebuggerOnStart: false,
|
|
56
|
+
flatten: true,
|
|
57
|
+
});
|
|
58
|
+
console.error(`[PlaywrightEngine] Auto-attach enabled`);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error('[PlaywrightEngine] setAutoAttach warning:', err instanceof Error ? err.message : String(err));
|
|
62
|
+
}
|
|
47
63
|
}
|
|
48
64
|
async disconnect() {
|
|
49
65
|
if (this.browser) {
|
|
50
66
|
this.browser = null;
|
|
51
67
|
this.cdpPort = null;
|
|
52
|
-
console.
|
|
68
|
+
console.error('[PlaywrightEngine] Disconnected');
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Force reconnect — drops existing connection and creates a fresh one.
|
|
57
|
-
* Needed when new webviews are created after the initial connection,
|
|
58
|
-
* because connectOverCDP only discovers targets at connection time.
|
|
59
|
-
*/
|
|
60
|
-
async reconnect() {
|
|
61
|
-
const info = (await (0, wmux_client_1.sendRpc)('browser.cdp.info'));
|
|
62
|
-
await this.disconnect();
|
|
63
|
-
await this.connect(info.cdpPort);
|
|
64
|
-
}
|
|
65
71
|
async ensureConnected() {
|
|
66
72
|
if (this.browser?.isConnected())
|
|
67
73
|
return;
|
|
@@ -72,7 +78,7 @@ class PlaywrightEngine {
|
|
|
72
78
|
return;
|
|
73
79
|
}
|
|
74
80
|
catch (err) {
|
|
75
|
-
console.
|
|
81
|
+
console.error(`[PlaywrightEngine] Connection attempt ${attempt}/${MAX_CONNECT_RETRIES} failed:`, err instanceof Error ? err.message : String(err));
|
|
76
82
|
if (attempt < MAX_CONNECT_RETRIES) {
|
|
77
83
|
await sleep(RETRY_DELAY_MS);
|
|
78
84
|
}
|
|
@@ -93,89 +99,190 @@ class PlaywrightEngine {
|
|
|
93
99
|
return pages;
|
|
94
100
|
}
|
|
95
101
|
/**
|
|
96
|
-
*
|
|
102
|
+
* Find a webview page using multiple strategies:
|
|
103
|
+
* 1. Check existing Playwright pages (works if webview is in a discoverable context)
|
|
104
|
+
* 2. Use CDP Target domain to find and attach to webview targets directly
|
|
105
|
+
* 3. Fetch /json endpoint for target discovery
|
|
97
106
|
*/
|
|
98
|
-
async
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* Try to find a Playwright Page that corresponds to a registered webview target.
|
|
106
|
-
* Returns null if no safe page can be found.
|
|
107
|
-
*/
|
|
108
|
-
async findWebviewPage(allPages, target) {
|
|
109
|
-
// Strategy 1: Match by targetId → URL from /json endpoint
|
|
110
|
-
if (target) {
|
|
107
|
+
async getPage(surfaceId) {
|
|
108
|
+
// Fast-fail if Playwright has already failed to find webview pages.
|
|
109
|
+
// MCP tools with RPC fallbacks will skip directly to the fast RPC path.
|
|
110
|
+
if (this.playwrightFailed)
|
|
111
|
+
return null;
|
|
112
|
+
await this.ensureConnected();
|
|
113
|
+
for (let attempt = 1; attempt <= PAGE_FIND_RETRIES; attempt++) {
|
|
111
114
|
try {
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
// Strategy 1: Check existing pages
|
|
116
|
+
const allPages = this.getAllPages();
|
|
117
|
+
console.error(`[PlaywrightEngine] Attempt ${attempt}: ${allPages.length} pages in ${this.browser?.contexts().length ?? 0} contexts`);
|
|
118
|
+
const safePage = allPages.find((p) => !isElectronShellUrl(p.url()));
|
|
119
|
+
if (safePage) {
|
|
120
|
+
console.error(`[PlaywrightEngine] Found page via contexts: ${safePage.url()}`);
|
|
121
|
+
return safePage;
|
|
122
|
+
}
|
|
123
|
+
// Strategy 2: Use CDP Target.getTargets to find webview targets
|
|
124
|
+
if (this.browser) {
|
|
125
|
+
const page = await this.findViaTargetDomain(surfaceId);
|
|
126
|
+
if (page)
|
|
127
|
+
return page;
|
|
128
|
+
}
|
|
129
|
+
// Strategy 3: Use /json endpoint + match registered targets
|
|
130
|
+
if (this.cdpPort) {
|
|
131
|
+
const page = await this.findViaJsonEndpoint(surfaceId);
|
|
132
|
+
if (page)
|
|
133
|
+
return page;
|
|
134
|
+
}
|
|
135
|
+
if (attempt < PAGE_FIND_RETRIES) {
|
|
136
|
+
console.error(`[PlaywrightEngine] No page found, reconnecting... (${attempt}/${PAGE_FIND_RETRIES})`);
|
|
137
|
+
await sleep(PAGE_FIND_DELAY_MS);
|
|
138
|
+
await this.disconnect();
|
|
139
|
+
await this.ensureConnected();
|
|
124
140
|
}
|
|
125
141
|
}
|
|
126
|
-
catch {
|
|
127
|
-
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.error(`[PlaywrightEngine] getPage attempt ${attempt} failed:`, err instanceof Error ? err.message : String(err));
|
|
144
|
+
if (attempt < PAGE_FIND_RETRIES) {
|
|
145
|
+
await sleep(PAGE_FIND_DELAY_MS);
|
|
146
|
+
await this.disconnect();
|
|
147
|
+
await this.ensureConnected();
|
|
148
|
+
}
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const candidates = allPages.filter((p) => !isElectronShellUrl(p.url()));
|
|
133
|
-
if (candidates.length > 0) {
|
|
134
|
-
return candidates[0];
|
|
135
|
-
}
|
|
151
|
+
console.error('[PlaywrightEngine] No webview page found after all retries — marking as failed');
|
|
152
|
+
this.playwrightFailed = true;
|
|
136
153
|
return null;
|
|
137
154
|
}
|
|
138
155
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* Includes retry logic: if no webview page is found on the first attempt,
|
|
142
|
-
* reconnects to CDP (to discover newly created webview targets) and retries.
|
|
156
|
+
* Use CDP Target domain to discover webview targets and create a page for them.
|
|
143
157
|
*/
|
|
144
|
-
async
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
async findViaTargetDomain(surfaceId) {
|
|
159
|
+
if (!this.browser)
|
|
160
|
+
return null;
|
|
161
|
+
try {
|
|
162
|
+
// Get the default context's first page to create a CDP session
|
|
163
|
+
const defaultContext = this.browser.contexts()[0];
|
|
164
|
+
if (!defaultContext) {
|
|
165
|
+
console.error('[PlaywrightEngine] No default context available');
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
let cdpSession;
|
|
169
|
+
const existingPages = defaultContext.pages();
|
|
170
|
+
if (existingPages.length > 0) {
|
|
171
|
+
cdpSession = await existingPages[0].context().newCDPSession(existingPages[0]);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
cdpSession = await this.browser.newBrowserCDPSession();
|
|
153
175
|
}
|
|
154
|
-
// Get
|
|
176
|
+
// Get all targets
|
|
177
|
+
const { targetInfos } = await cdpSession.send('Target.getTargets');
|
|
178
|
+
console.error(`[PlaywrightEngine] CDP targets: ${targetInfos.map(t => `${t.type}:${t.url.substring(0, 40)}`).join(', ')}`);
|
|
179
|
+
// Get registered wmux targets for matching
|
|
155
180
|
const info = (await (0, wmux_client_1.sendRpc)('browser.cdp.info'));
|
|
156
|
-
const
|
|
181
|
+
const wmuxTarget = surfaceId
|
|
157
182
|
? info.targets.find((t) => t.surfaceId === surfaceId)
|
|
158
183
|
: info.targets[0];
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
// Find the webview target — match by targetId from WebviewCdpManager
|
|
185
|
+
let webviewTarget = wmuxTarget
|
|
186
|
+
? targetInfos.find((t) => t.targetId === wmuxTarget.targetId)
|
|
187
|
+
: undefined;
|
|
188
|
+
// Fallback: find any page target that isn't the Electron shell
|
|
189
|
+
if (!webviewTarget) {
|
|
190
|
+
webviewTarget = targetInfos.find((t) => t.type === 'page' && !isElectronShellUrl(t.url) && t.url !== 'about:blank');
|
|
166
191
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return
|
|
170
|
-
// Page not found — reconnect and retry (new webview might not be visible yet)
|
|
171
|
-
if (attempt < PAGE_FIND_RETRIES) {
|
|
172
|
-
console.log(`[PlaywrightEngine] Webview page not found, reconnecting... (${attempt}/${PAGE_FIND_RETRIES})`);
|
|
173
|
-
await sleep(PAGE_FIND_DELAY_MS);
|
|
174
|
-
await this.reconnect();
|
|
192
|
+
if (!webviewTarget) {
|
|
193
|
+
console.error('[PlaywrightEngine] No webview target found in Target.getTargets');
|
|
194
|
+
return null;
|
|
175
195
|
}
|
|
196
|
+
console.error(`[PlaywrightEngine] Found webview target: ${webviewTarget.targetId} url=${webviewTarget.url}`);
|
|
197
|
+
// Try to attach to the target and get a page
|
|
198
|
+
// Attach with flatten:true creates a session in the current connection
|
|
199
|
+
if (!webviewTarget.attached) {
|
|
200
|
+
await cdpSession.send('Target.attachToTarget', {
|
|
201
|
+
targetId: webviewTarget.targetId,
|
|
202
|
+
flatten: true,
|
|
203
|
+
});
|
|
204
|
+
console.error(`[PlaywrightEngine] Attached to target ${webviewTarget.targetId}`);
|
|
205
|
+
}
|
|
206
|
+
// After attaching, check if new pages appeared
|
|
207
|
+
await sleep(500);
|
|
208
|
+
const newPages = this.getAllPages();
|
|
209
|
+
console.error(`[PlaywrightEngine] After attach: ${newPages.length} pages`);
|
|
210
|
+
const matchedPage = newPages.find((p) => !isElectronShellUrl(p.url()));
|
|
211
|
+
if (matchedPage) {
|
|
212
|
+
console.error(`[PlaywrightEngine] Found page after attach: ${matchedPage.url()}`);
|
|
213
|
+
return matchedPage;
|
|
214
|
+
}
|
|
215
|
+
// If pages still empty, try creating a new CDP connection specifically to the webview
|
|
216
|
+
// by reconnecting — this forces Playwright to re-discover all targets
|
|
217
|
+
console.error('[PlaywrightEngine] Attach did not create a page, will retry with reconnect');
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
console.error('[PlaywrightEngine] findViaTargetDomain error:', err instanceof Error ? err.message : String(err));
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Use the /json HTTP endpoint to find webview targets and attach via CDP.
|
|
227
|
+
*/
|
|
228
|
+
async findViaJsonEndpoint(surfaceId) {
|
|
229
|
+
if (!this.cdpPort || !this.browser)
|
|
230
|
+
return null;
|
|
231
|
+
try {
|
|
232
|
+
const resp = await fetch(`http://127.0.0.1:${this.cdpPort}/json`);
|
|
233
|
+
const targets = (await resp.json());
|
|
234
|
+
console.error(`[PlaywrightEngine] /json targets: ${targets.map(t => `${t.type}:${t.url.substring(0, 40)}`).join(', ')}`);
|
|
235
|
+
// Get registered wmux targets
|
|
236
|
+
const info = (await (0, wmux_client_1.sendRpc)('browser.cdp.info'));
|
|
237
|
+
const wmuxTarget = surfaceId
|
|
238
|
+
? info.targets.find((t) => t.surfaceId === surfaceId)
|
|
239
|
+
: info.targets[0];
|
|
240
|
+
// Find the webview in /json
|
|
241
|
+
let jsonTarget = wmuxTarget
|
|
242
|
+
? targets.find((t) => t.id === wmuxTarget.targetId)
|
|
243
|
+
: undefined;
|
|
244
|
+
if (!jsonTarget) {
|
|
245
|
+
jsonTarget = targets.find((t) => t.type === 'page' && !isElectronShellUrl(t.url) && t.url !== 'about:blank');
|
|
246
|
+
}
|
|
247
|
+
if (!jsonTarget) {
|
|
248
|
+
console.error('[PlaywrightEngine] No webview found in /json');
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
console.error(`[PlaywrightEngine] Found target in /json: ${jsonTarget.id} url=${jsonTarget.url}`);
|
|
252
|
+
// Attach to the target via browser-level CDP session (don't disconnect!)
|
|
253
|
+
try {
|
|
254
|
+
const session = await this.browser.newBrowserCDPSession();
|
|
255
|
+
// Re-enable auto-attach to pick up the webview target
|
|
256
|
+
await session.send('Target.setAutoAttach', {
|
|
257
|
+
autoAttach: true,
|
|
258
|
+
waitForDebuggerOnStart: false,
|
|
259
|
+
flatten: true,
|
|
260
|
+
});
|
|
261
|
+
// Also explicitly attach to the discovered target
|
|
262
|
+
await session.send('Target.attachToTarget', {
|
|
263
|
+
targetId: jsonTarget.id,
|
|
264
|
+
flatten: true,
|
|
265
|
+
});
|
|
266
|
+
console.error(`[PlaywrightEngine] Attached to target ${jsonTarget.id} via /json`);
|
|
267
|
+
// Brief wait for Playwright to process the attached target
|
|
268
|
+
await sleep(200);
|
|
269
|
+
const pages = this.getAllPages();
|
|
270
|
+
console.error(`[PlaywrightEngine] After /json attach: ${pages.length} pages`);
|
|
271
|
+
const matchedPage = pages.find((p) => !isElectronShellUrl(p.url()));
|
|
272
|
+
if (matchedPage) {
|
|
273
|
+
console.error(`[PlaywrightEngine] Found page via /json attach: ${matchedPage.url()}`);
|
|
274
|
+
return matchedPage;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (attachErr) {
|
|
278
|
+
console.error(`[PlaywrightEngine] /json attach failed: ${attachErr instanceof Error ? attachErr.message : String(attachErr)}`);
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
console.error('[PlaywrightEngine] findViaJsonEndpoint error:', err instanceof Error ? err.message : String(err));
|
|
284
|
+
return null;
|
|
176
285
|
}
|
|
177
|
-
console.warn('[PlaywrightEngine] No webview page found after all retries');
|
|
178
|
-
return null;
|
|
179
286
|
}
|
|
180
287
|
async getBrowser() {
|
|
181
288
|
await this.ensureConnected();
|
|
@@ -24,21 +24,26 @@ async function applyAntiDetection(page) {
|
|
|
24
24
|
// CDP-powered evaluate with user gesture
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
/**
|
|
27
|
-
* Evaluate a JavaScript expression in the page context
|
|
28
|
-
* `
|
|
27
|
+
* Evaluate a JavaScript expression in the page context via CDP
|
|
28
|
+
* `Runtime.evaluate`.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* @param page - The Playwright page to evaluate in.
|
|
31
|
+
* @param expression - The JavaScript expression to evaluate.
|
|
32
|
+
* @param options - Optional settings.
|
|
33
|
+
* @param options.userGesture - When `true`, the evaluation is treated as if
|
|
34
|
+
* triggered by a user gesture (transient activation). Defaults to `false`
|
|
35
|
+
* to follow the principle of least privilege. Callers that genuinely need
|
|
36
|
+
* user activation (e.g. opening popups, triggering downloads) should
|
|
37
|
+
* explicitly pass `true`.
|
|
33
38
|
*
|
|
34
39
|
* Internally opens a CDP session and calls `Runtime.evaluate`.
|
|
35
40
|
*/
|
|
36
|
-
async function evaluateWithGesture(page, expression) {
|
|
41
|
+
async function evaluateWithGesture(page, expression, options) {
|
|
37
42
|
const client = await page.context().newCDPSession(page);
|
|
38
43
|
try {
|
|
39
44
|
const result = await client.send('Runtime.evaluate', {
|
|
40
45
|
expression,
|
|
41
|
-
userGesture:
|
|
46
|
+
userGesture: options?.userGesture ?? false,
|
|
42
47
|
returnByValue: true,
|
|
43
48
|
awaitPromise: true,
|
|
44
49
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Dangerous pattern detection for browser code execution
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectDangerousPatterns = detectDangerousPatterns;
|
|
7
|
+
const DANGEROUS_PATTERNS = [
|
|
8
|
+
{ pattern: /\bfetch\s*\(/, label: 'fetch()' },
|
|
9
|
+
{ pattern: /\bXMLHttpRequest\b/, label: 'XMLHttpRequest' },
|
|
10
|
+
{ pattern: /\bWebSocket\b/, label: 'WebSocket' },
|
|
11
|
+
{ pattern: /\bnavigator\.sendBeacon\b/, label: 'sendBeacon' },
|
|
12
|
+
{ pattern: /\brequire\s*\(/, label: 'require()' },
|
|
13
|
+
{ pattern: /\bimport\s*\(/, label: 'dynamic import()' },
|
|
14
|
+
{ pattern: /\beval\s*\(/, label: 'eval()' },
|
|
15
|
+
{ pattern: /\bnew\s+Function\b/, label: 'new Function()' },
|
|
16
|
+
{ pattern: /\bdocument\.cookie\b/, label: 'document.cookie access' },
|
|
17
|
+
{ pattern: /\blocalStorage\b/, label: 'localStorage access' },
|
|
18
|
+
{ pattern: /\bsessionStorage\b/, label: 'sessionStorage access' },
|
|
19
|
+
{ pattern: /\bindexedDB\b/, label: 'indexedDB access' },
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Detect dangerous patterns in a JavaScript code string.
|
|
23
|
+
* Returns an array of human-readable labels for each matched pattern.
|
|
24
|
+
*/
|
|
25
|
+
function detectDangerousPatterns(code) {
|
|
26
|
+
return DANGEROUS_PATTERNS
|
|
27
|
+
.filter(({ pattern }) => pattern.test(code))
|
|
28
|
+
.map(({ label }) => label);
|
|
29
|
+
}
|
|
@@ -33,7 +33,7 @@ function registerExtractionTools(server) {
|
|
|
33
33
|
try {
|
|
34
34
|
const page = await engine.getPage(surfaceId);
|
|
35
35
|
if (!page) {
|
|
36
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
36
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
37
37
|
}
|
|
38
38
|
const snapshot = await (0, dom_intelligence_1.getSmartSnapshot)(page, {
|
|
39
39
|
maxContentLength: maxContentLength ?? 3000,
|
|
@@ -86,7 +86,7 @@ function registerExtractionTools(server) {
|
|
|
86
86
|
try {
|
|
87
87
|
const page = await engine.getPage(surfaceId);
|
|
88
88
|
if (!page) {
|
|
89
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
89
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
90
90
|
}
|
|
91
91
|
const markdown = await (0, markdown_extractor_1.extractMarkdown)(page, {
|
|
92
92
|
selector,
|
|
@@ -120,7 +120,7 @@ function registerExtractionTools(server) {
|
|
|
120
120
|
try {
|
|
121
121
|
const page = await engine.getPage(surfaceId);
|
|
122
122
|
if (!page) {
|
|
123
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
123
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
124
124
|
}
|
|
125
125
|
const records = await (0, markdown_extractor_1.extractStructuredData)(page, goal, fields);
|
|
126
126
|
return {
|
|
@@ -69,7 +69,7 @@ function registerFileTools(server) {
|
|
|
69
69
|
try {
|
|
70
70
|
const page = await engine.getPage(surfaceId);
|
|
71
71
|
if (!page) {
|
|
72
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
72
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
73
73
|
}
|
|
74
74
|
if (ref) {
|
|
75
75
|
const el = await (0, snapshot_1.resolveRef)(page, ref);
|
|
@@ -119,7 +119,7 @@ function registerFileTools(server) {
|
|
|
119
119
|
try {
|
|
120
120
|
const page = await engine.getPage(surfaceId);
|
|
121
121
|
if (!page) {
|
|
122
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
122
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
123
123
|
}
|
|
124
124
|
const el = await (0, snapshot_1.resolveRef)(page, ref);
|
|
125
125
|
if (!el) {
|
|
@@ -177,7 +177,7 @@ function registerFileTools(server) {
|
|
|
177
177
|
try {
|
|
178
178
|
const page = await engine.getPage(surfaceId);
|
|
179
179
|
if (!page) {
|
|
180
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
180
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
181
181
|
}
|
|
182
182
|
const download = await page.waitForEvent('download', {
|
|
183
183
|
timeout: resolvedTimeout,
|
|
@@ -243,7 +243,7 @@ function registerFileTools(server) {
|
|
|
243
243
|
try {
|
|
244
244
|
const page = await engine.getPage(surfaceId);
|
|
245
245
|
if (!page) {
|
|
246
|
-
throw new Error('No browser page available. Call browser_open first.');
|
|
246
|
+
throw new Error('No browser page available. Call browser_open with a URL first to establish a CDP connection (required even if a browser panel is already visible).');
|
|
247
247
|
}
|
|
248
248
|
page.once('dialog', async (dialog) => {
|
|
249
249
|
if (accept) {
|