@midscene/web 1.10.5 → 1.10.6-beta-20260717061640.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/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/chrome-extension/page.mjs +57 -4
- package/dist/es/chrome-extension/page.mjs.map +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/puppeteer/base-page.mjs +103 -13
- package/dist/es/puppeteer/base-page.mjs.map +1 -1
- package/dist/es/web-page.mjs +17 -5
- package/dist/es/web-page.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/chrome-extension/page.js +57 -4
- package/dist/lib/chrome-extension/page.js.map +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/puppeteer/base-page.js +102 -12
- package/dist/lib/puppeteer/base-page.js.map +1 -1
- package/dist/lib/web-page.js +17 -5
- package/dist/lib/web-page.js.map +1 -1
- package/dist/types/puppeteer/base-page.d.ts +2 -0
- package/package.json +4 -4
package/dist/es/web-page.mjs
CHANGED
|
@@ -82,38 +82,45 @@ class AbstractWebPage extends AbstractInterface {
|
|
|
82
82
|
}
|
|
83
83
|
async clearInput(element) {}
|
|
84
84
|
}
|
|
85
|
+
const scheduleWebVisualUpdate = (page)=>{
|
|
86
|
+
if (page.schedulePendingVisualUpdate) return void page.schedulePendingVisualUpdate();
|
|
87
|
+
const pendingRefresh = page.flushPendingVisualUpdate?.();
|
|
88
|
+
pendingRefresh?.catch(()=>void 0);
|
|
89
|
+
};
|
|
85
90
|
function createWebInputPrimitives(page) {
|
|
86
|
-
const scheduleVisualUpdate = ()=>
|
|
87
|
-
if (page.schedulePendingVisualUpdate) return void page.schedulePendingVisualUpdate();
|
|
88
|
-
const pendingRefresh = page.flushPendingVisualUpdate?.();
|
|
89
|
-
pendingRefresh?.catch(()=>void 0);
|
|
90
|
-
};
|
|
91
|
+
const scheduleVisualUpdate = ()=>scheduleWebVisualUpdate(page);
|
|
91
92
|
return {
|
|
92
93
|
pointer: {
|
|
93
94
|
tap: async ({ x, y })=>{
|
|
94
95
|
await page.mouse.click(x, y, {
|
|
95
96
|
button: 'left'
|
|
96
97
|
});
|
|
98
|
+
scheduleVisualUpdate();
|
|
97
99
|
},
|
|
98
100
|
rightClick: async ({ x, y })=>{
|
|
99
101
|
await page.mouse.click(x, y, {
|
|
100
102
|
button: 'right'
|
|
101
103
|
});
|
|
104
|
+
scheduleVisualUpdate();
|
|
102
105
|
},
|
|
103
106
|
doubleClick: async ({ x, y })=>{
|
|
104
107
|
await page.mouse.click(x, y, {
|
|
105
108
|
button: 'left',
|
|
106
109
|
count: 2
|
|
107
110
|
});
|
|
111
|
+
scheduleVisualUpdate();
|
|
108
112
|
},
|
|
109
113
|
hover: async ({ x, y })=>{
|
|
110
114
|
await page.mouse.move(x, y);
|
|
115
|
+
scheduleVisualUpdate();
|
|
111
116
|
},
|
|
112
117
|
dragAndDrop: async (from, to)=>{
|
|
113
118
|
await page.mouse.drag(from, to);
|
|
119
|
+
scheduleVisualUpdate();
|
|
114
120
|
},
|
|
115
121
|
longPress: async ({ x, y }, opts)=>{
|
|
116
122
|
await page.longPress(x, y, opts?.duration);
|
|
123
|
+
scheduleVisualUpdate();
|
|
117
124
|
}
|
|
118
125
|
},
|
|
119
126
|
keyboard: {
|
|
@@ -195,6 +202,7 @@ function createWebInputPrimitives(page) {
|
|
|
195
202
|
else await page.scrollDown(param?.distance || void 0, startingPoint);
|
|
196
203
|
await sleep(500);
|
|
197
204
|
}
|
|
205
|
+
scheduleVisualUpdate();
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
};
|
|
@@ -216,6 +224,7 @@ const commonWebActionsForWebPage = (page, includeTouchEvents = false)=>{
|
|
|
216
224
|
call: async (param)=>{
|
|
217
225
|
if (!page.navigate) throw new Error('Navigate operation is not supported on this page type');
|
|
218
226
|
await page.navigate(param.url);
|
|
227
|
+
scheduleWebVisualUpdate(page);
|
|
219
228
|
}
|
|
220
229
|
}),
|
|
221
230
|
defineAction({
|
|
@@ -224,6 +233,7 @@ const commonWebActionsForWebPage = (page, includeTouchEvents = false)=>{
|
|
|
224
233
|
call: async ()=>{
|
|
225
234
|
if (!page.reload) throw new Error('Reload operation is not supported on this page type');
|
|
226
235
|
await page.reload();
|
|
236
|
+
scheduleWebVisualUpdate(page);
|
|
227
237
|
}
|
|
228
238
|
}),
|
|
229
239
|
defineAction({
|
|
@@ -232,6 +242,7 @@ const commonWebActionsForWebPage = (page, includeTouchEvents = false)=>{
|
|
|
232
242
|
call: async ()=>{
|
|
233
243
|
if (!page.goBack) throw new Error('GoBack operation is not supported on this page type');
|
|
234
244
|
await page.goBack();
|
|
245
|
+
scheduleWebVisualUpdate(page);
|
|
235
246
|
}
|
|
236
247
|
}),
|
|
237
248
|
defineAction({
|
|
@@ -240,6 +251,7 @@ const commonWebActionsForWebPage = (page, includeTouchEvents = false)=>{
|
|
|
240
251
|
call: async ()=>{
|
|
241
252
|
if (!page.goForward) throw new Error('GoForward operation is not supported on this page type');
|
|
242
253
|
await page.goForward();
|
|
254
|
+
scheduleWebVisualUpdate(page);
|
|
243
255
|
}
|
|
244
256
|
})
|
|
245
257
|
];
|
package/dist/es/web-page.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-page.mjs","sources":["../../src/web-page.ts"],"sourcesContent":["import type { Point } from '@midscene/core';\nimport { z } from '@midscene/core';\nimport {\n AbstractInterface,\n type BrowserInputPrimitives,\n type DeviceAction,\n defineAction,\n defineActionsFromInputPrimitives,\n} from '@midscene/core/device';\n\nimport { sleep } from '@midscene/core/utils';\nimport type { ElementInfo } from '@midscene/shared/extractor';\nimport { transformHotkeyInput } from '@midscene/shared/us-keyboard-layout';\n\nconst navigateParamSchema = z.object({\n url: z\n .string()\n .describe(\n 'The URL to navigate to. Must start with https://, file://, or a similar protocol.',\n ),\n});\n\nfunction normalizeKeyInputs(value: string | string[]): string[] {\n const inputs = Array.isArray(value) ? value : [value];\n const result: string[] = [];\n\n for (const input of inputs) {\n if (typeof input !== 'string') {\n result.push(input as unknown as string);\n continue;\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n result.push(input);\n continue;\n }\n\n let normalized = trimmed;\n if (normalized.length > 1 && normalized.includes('+')) {\n normalized = normalized.replace(/\\s*\\+\\s*/g, ' ');\n }\n if (/\\s/.test(normalized)) {\n normalized = normalized.replace(/\\s+/g, ' ');\n }\n\n const transformed = transformHotkeyInput(normalized);\n if (transformed.length === 1 && transformed[0] === '' && trimmed !== '') {\n result.push(input);\n continue;\n }\n if (transformed.length === 0) {\n result.push(input);\n continue;\n }\n\n result.push(...transformed);\n }\n\n return result;\n}\n\nexport function getKeyCommands(\n value: string | string[],\n): Array<{ key: string; command?: string }> {\n const keys = normalizeKeyInputs(value);\n\n return keys.reduce((acc: Array<{ key: string; command?: string }>, k) => {\n const includeMeta = keys.includes('Meta') || keys.includes('Control');\n if (includeMeta && (k === 'a' || k === 'A')) {\n return acc.concat([{ key: k, command: 'SelectAll' }]);\n }\n if (includeMeta && (k === 'c' || k === 'C')) {\n return acc.concat([{ key: k, command: 'Copy' }]);\n }\n if (includeMeta && (k === 'v' || k === 'V')) {\n return acc.concat([{ key: k, command: 'Paste' }]);\n }\n return acc.concat([{ key: k }]);\n }, []);\n}\n\n// this is copied from puppeteer, but we don't want to import puppeteer here\nexport declare type KeyInput =\n | '0'\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | '7'\n | '8'\n | '9'\n | 'Power'\n | 'Eject'\n | 'Abort'\n | 'Help'\n | 'Backspace'\n | 'Tab'\n | 'Numpad5'\n | 'NumpadEnter'\n | 'Enter'\n | '\\r'\n | '\\n'\n | 'ShiftLeft'\n | 'ShiftRight'\n | 'ControlLeft'\n | 'ControlRight'\n | 'AltLeft'\n | 'AltRight'\n | 'Pause'\n | 'CapsLock'\n | 'Escape'\n | 'Convert'\n | 'NonConvert'\n | 'Space'\n | 'Numpad9'\n | 'PageUp'\n | 'Numpad3'\n | 'PageDown'\n | 'End'\n | 'Numpad1'\n | 'Home'\n | 'Numpad7'\n | 'ArrowLeft'\n | 'Numpad4'\n | 'Numpad8'\n | 'ArrowUp'\n | 'ArrowRight'\n | 'Numpad6'\n | 'Numpad2'\n | 'ArrowDown'\n | 'Select'\n | 'Open'\n | 'PrintScreen'\n | 'Insert'\n | 'Numpad0'\n | 'Delete'\n | 'NumpadDecimal'\n | 'Digit0'\n | 'Digit1'\n | 'Digit2'\n | 'Digit3'\n | 'Digit4'\n | 'Digit5'\n | 'Digit6'\n | 'Digit7'\n | 'Digit8'\n | 'Digit9'\n | 'KeyA'\n | 'KeyB'\n | 'KeyC'\n | 'KeyD'\n | 'KeyE'\n | 'KeyF'\n | 'KeyG'\n | 'KeyH'\n | 'KeyI'\n | 'KeyJ'\n | 'KeyK'\n | 'KeyL'\n | 'KeyM'\n | 'KeyN'\n | 'KeyO'\n | 'KeyP'\n | 'KeyQ'\n | 'KeyR'\n | 'KeyS'\n | 'KeyT'\n | 'KeyU'\n | 'KeyV'\n | 'KeyW'\n | 'KeyX'\n | 'KeyY'\n | 'KeyZ'\n | 'MetaLeft'\n | 'MetaRight'\n | 'ContextMenu'\n | 'NumpadMultiply'\n | 'NumpadAdd'\n | 'NumpadSubtract'\n | 'NumpadDivide'\n | 'F1'\n | 'F2'\n | 'F3'\n | 'F4'\n | 'F5'\n | 'F6'\n | 'F7'\n | 'F8'\n | 'F9'\n | 'F10'\n | 'F11'\n | 'F12'\n | 'F13'\n | 'F14'\n | 'F15'\n | 'F16'\n | 'F17'\n | 'F18'\n | 'F19'\n | 'F20'\n | 'F21'\n | 'F22'\n | 'F23'\n | 'F24'\n | 'NumLock'\n | 'ScrollLock'\n | 'AudioVolumeMute'\n | 'AudioVolumeDown'\n | 'AudioVolumeUp'\n | 'MediaTrackNext'\n | 'MediaTrackPrevious'\n | 'MediaStop'\n | 'MediaPlayPause'\n | 'Semicolon'\n | 'Equal'\n | 'NumpadEqual'\n | 'Comma'\n | 'Minus'\n | 'Period'\n | 'Slash'\n | 'Backquote'\n | 'BracketLeft'\n | 'Backslash'\n | 'BracketRight'\n | 'Quote'\n | 'AltGraph'\n | 'Props'\n | 'Cancel'\n | 'Clear'\n | 'Shift'\n | 'Control'\n | 'Alt'\n | 'Accept'\n | 'ModeChange'\n | ' '\n | 'Print'\n | 'Execute'\n | '\\u0000'\n | 'a'\n | 'b'\n | 'c'\n | 'd'\n | 'e'\n | 'f'\n | 'g'\n | 'h'\n | 'i'\n | 'j'\n | 'k'\n | 'l'\n | 'm'\n | 'n'\n | 'o'\n | 'p'\n | 'q'\n | 'r'\n | 's'\n | 't'\n | 'u'\n | 'v'\n | 'w'\n | 'x'\n | 'y'\n | 'z'\n | 'Meta'\n | '*'\n | '+'\n | '-'\n | '/'\n | ';'\n | '='\n | ','\n | '.'\n | '`'\n | '['\n | '\\\\'\n | ']'\n | \"'\"\n | 'Attn'\n | 'CrSel'\n | 'ExSel'\n | 'EraseEof'\n | 'Play'\n | 'ZoomOut'\n | ')'\n | '!'\n | '@'\n | '#'\n | '$'\n | '%'\n | '^'\n | '&'\n | '('\n | 'A'\n | 'B'\n | 'C'\n | 'D'\n | 'E'\n | 'F'\n | 'G'\n | 'H'\n | 'I'\n | 'J'\n | 'K'\n | 'L'\n | 'M'\n | 'N'\n | 'O'\n | 'P'\n | 'Q'\n | 'R'\n | 'S'\n | 'T'\n | 'U'\n | 'V'\n | 'W'\n | 'X'\n | 'Y'\n | 'Z'\n | ':'\n | '<'\n | '_'\n | '>'\n | '?'\n | '~'\n | '{'\n | '|'\n | '}'\n | '\"'\n | 'SoftLeft'\n | 'SoftRight'\n | 'Camera'\n | 'Call'\n | 'EndCall'\n | 'VolumeDown'\n | 'VolumeUp';\n\nexport type MouseButton = 'left' | 'right' | 'middle';\n\nexport interface MouseAction {\n click: (\n x: number,\n y: number,\n options: { button: MouseButton; count?: number },\n ) => Promise<void>;\n wheel: (deltaX: number, deltaY: number) => Promise<void>;\n move: (x: number, y: number) => Promise<void>;\n drag: (\n from: { x: number; y: number },\n to: { x: number; y: number },\n ) => Promise<void>;\n}\n\nexport interface KeyboardAction {\n type: (text: string, options?: { delay?: number }) => Promise<void>;\n press: (\n action:\n | { key: KeyInput; command?: string }\n | { key: KeyInput; command?: string }[],\n ) => Promise<void>;\n}\n\nexport interface ChromePageDestroyOptions {\n closeTab?: boolean; // should close the tab when the page object is destroyed\n}\n\nexport abstract class AbstractWebPage extends AbstractInterface {\n navigate?(url: string): Promise<void>;\n reload?(): Promise<void>;\n goBack?(): Promise<void>;\n goForward?(): Promise<void>;\n stopLoading?(): Promise<void>;\n navigationState?(): Promise<{ isLoading: boolean }>;\n flushPendingVisualUpdate?(): Promise<void>;\n schedulePendingVisualUpdate?(): void;\n waitForDomQuiet?(opts?: {\n quietMs?: number;\n timeoutMs?: number;\n target?: ElementInfo;\n }): Promise<void>;\n\n get mouse(): MouseAction {\n return {\n click: async (\n x: number,\n y: number,\n options: { button: MouseButton },\n ) => {},\n wheel: async (deltaX: number, deltaY: number) => {},\n move: async (x: number, y: number) => {},\n drag: async (\n from: { x: number; y: number },\n to: { x: number; y: number },\n ) => {},\n };\n }\n\n get keyboard(): KeyboardAction {\n return {\n type: async (text: string, options?: { delay?: number }) => {},\n press: async (\n action:\n | { key: KeyInput; command?: string }\n | { key: KeyInput; command?: string }[],\n ) => {},\n };\n }\n\n async clearInput(element?: ElementInfo): Promise<void> {}\n\n abstract scrollUntilTop(startingPoint?: Point): Promise<void>;\n abstract scrollUntilBottom(startingPoint?: Point): Promise<void>;\n abstract scrollUntilLeft(startingPoint?: Point): Promise<void>;\n abstract scrollUntilRight(startingPoint?: Point): Promise<void>;\n abstract scrollUp(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollDown(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollLeft(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollRight(distance?: number, startingPoint?: Point): Promise<void>;\n abstract longPress(x: number, y: number, duration?: number): Promise<void>;\n abstract swipe(\n from: { x: number; y: number },\n to: { x: number; y: number },\n duration?: number,\n ): Promise<void>;\n abstract pinch(\n centerX: number,\n centerY: number,\n startDistance: number,\n endDistance: number,\n duration?: number,\n ): Promise<void>;\n}\n\nexport function createWebInputPrimitives(\n page: AbstractWebPage,\n): BrowserInputPrimitives {\n const scheduleVisualUpdate = () => {\n if (page.schedulePendingVisualUpdate) {\n page.schedulePendingVisualUpdate();\n return;\n }\n\n const pendingRefresh = page.flushPendingVisualUpdate?.();\n void pendingRefresh?.catch(() => undefined);\n };\n\n return {\n pointer: {\n tap: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'left' });\n },\n rightClick: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'right' });\n },\n doubleClick: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'left', count: 2 });\n },\n hover: async ({ x, y }) => {\n await page.mouse.move(x, y);\n },\n dragAndDrop: async (from, to) => {\n await page.mouse.drag(from, to);\n },\n longPress: async ({ x, y }, opts) => {\n await page.longPress(x, y, opts?.duration);\n },\n },\n keyboard: {\n typeText: async (value, opts) => {\n const element = opts?.target;\n if (element && opts?.replace !== false) {\n await page.clearInput(element as ElementInfo);\n // Frameworks (React/Vue/etc.) often re-render in response to\n // the `input` event fired by clearing. If that re-render lands\n // between clearInput returning and the first typed character,\n // the keypresses can be dropped. Wait for the DOM to settle\n // before starting to type.\n await page.waitForDomQuiet?.({ target: element as ElementInfo });\n } else if (element && opts?.focusOnly) {\n const target = element as ElementInfo;\n await page.mouse.click(target.center[0], target.center[1], {\n button: 'left',\n });\n await page.keyboard.press([{ key: 'End' }]);\n }\n\n if (opts?.focusOnly) {\n return;\n }\n\n const keyboardTypeOptions =\n opts?.keyboardTypeDelay === undefined\n ? undefined\n : { delay: opts.keyboardTypeDelay };\n await page.keyboard.type(value, keyboardTypeOptions);\n scheduleVisualUpdate();\n },\n keyboardPress: async (keyName, opts) => {\n const element = opts?.target as\n | { center: [number, number] }\n | undefined;\n if (element) {\n await page.mouse.click(element.center[0], element.center[1], {\n button: 'left',\n });\n }\n\n const keys = getKeyCommands(keyName);\n await page.keyboard.press(keys as any);\n scheduleVisualUpdate();\n },\n cursorMove: async (direction, times = 1) => {\n const arrowKey = direction === 'left' ? 'ArrowLeft' : 'ArrowRight';\n for (let i = 0; i < times; i++) {\n await page.keyboard.press([{ key: arrowKey as any }]);\n await sleep(100);\n }\n },\n clearInput: async (target) => {\n await page.clearInput(target as ElementInfo | undefined);\n },\n },\n touch: {\n pinch: async ({ x, y }, opts) => {\n await page.pinch(\n x,\n y,\n opts.startDistance,\n opts.endDistance,\n opts.duration,\n );\n },\n swipe: async (from, to, opts) => {\n await page.swipe(from, to, opts?.duration);\n },\n },\n scroll: {\n scroll: async (param) => {\n const element = param.locate;\n const startingPoint = element\n ? {\n left: element.center[0],\n top: element.center[1],\n }\n : undefined;\n const scrollToEventName = param?.scrollType;\n if (scrollToEventName === 'scrollToTop') {\n await page.scrollUntilTop(startingPoint);\n } else if (scrollToEventName === 'scrollToBottom') {\n await page.scrollUntilBottom(startingPoint);\n } else if (scrollToEventName === 'scrollToRight') {\n await page.scrollUntilRight(startingPoint);\n } else if (scrollToEventName === 'scrollToLeft') {\n await page.scrollUntilLeft(startingPoint);\n } else if (scrollToEventName === 'singleAction' || !scrollToEventName) {\n if (param?.direction === 'down' || !param || !param.direction) {\n await page.scrollDown(param?.distance || undefined, startingPoint);\n } else if (param.direction === 'up') {\n await page.scrollUp(param.distance || undefined, startingPoint);\n } else if (param.direction === 'left') {\n await page.scrollLeft(param.distance || undefined, startingPoint);\n } else if (param.direction === 'right') {\n await page.scrollRight(param.distance || undefined, startingPoint);\n } else {\n throw new Error(`Unknown scroll direction: ${param.direction}`);\n }\n await sleep(500);\n } else {\n throw new Error(\n `Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(\n param,\n )}`,\n );\n }\n },\n },\n };\n}\n\nexport const commonWebActionsForWebPage = <T extends AbstractWebPage>(\n page: T,\n includeTouchEvents = false,\n): DeviceAction<any>[] => {\n const input = createWebInputPrimitives(page);\n return [\n ...defineActionsFromInputPrimitives(input, {\n size: () => page.size(),\n includeSwipe: includeTouchEvents,\n }),\n\n defineAction<typeof navigateParamSchema, { url: string }>({\n name: 'Navigate',\n description:\n 'Navigate the browser to a specified URL. Opens the URL in the current tab.',\n paramSchema: navigateParamSchema,\n sample: {\n url: 'https://www.example.com',\n },\n call: async (param) => {\n if (!page.navigate) {\n throw new Error(\n 'Navigate operation is not supported on this page type',\n );\n }\n await page.navigate(param.url);\n },\n }),\n\n defineAction({\n name: 'Reload',\n description: 'Reload the current page',\n call: async () => {\n if (!page.reload) {\n throw new Error(\n 'Reload operation is not supported on this page type',\n );\n }\n await page.reload();\n },\n }),\n\n defineAction({\n name: 'GoBack',\n description: 'Navigate back in browser history',\n call: async () => {\n if (!page.goBack) {\n throw new Error(\n 'GoBack operation is not supported on this page type',\n );\n }\n await page.goBack();\n },\n }),\n defineAction({\n name: 'GoForward',\n description: 'Navigate forward in browser history',\n call: async () => {\n if (!page.goForward) {\n throw new Error(\n 'GoForward operation is not supported on this page type',\n );\n }\n await page.goForward();\n },\n }),\n ];\n};\n"],"names":["navigateParamSchema","z","normalizeKeyInputs","value","inputs","Array","result","input","trimmed","normalized","transformed","transformHotkeyInput","getKeyCommands","keys","acc","k","includeMeta","AbstractWebPage","AbstractInterface","x","y","options","deltaX","deltaY","from","to","text","action","element","createWebInputPrimitives","page","scheduleVisualUpdate","pendingRefresh","undefined","opts","target","keyboardTypeOptions","keyName","direction","times","arrowKey","i","sleep","param","startingPoint","scrollToEventName","Error","JSON","commonWebActionsForWebPage","includeTouchEvents","defineActionsFromInputPrimitives","defineAction"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web-page.mjs","sources":["../../src/web-page.ts"],"sourcesContent":["import type { Point } from '@midscene/core';\nimport { z } from '@midscene/core';\nimport {\n AbstractInterface,\n type BrowserInputPrimitives,\n type DeviceAction,\n defineAction,\n defineActionsFromInputPrimitives,\n} from '@midscene/core/device';\n\nimport { sleep } from '@midscene/core/utils';\nimport type { ElementInfo } from '@midscene/shared/extractor';\nimport { transformHotkeyInput } from '@midscene/shared/us-keyboard-layout';\n\nconst navigateParamSchema = z.object({\n url: z\n .string()\n .describe(\n 'The URL to navigate to. Must start with https://, file://, or a similar protocol.',\n ),\n});\n\nfunction normalizeKeyInputs(value: string | string[]): string[] {\n const inputs = Array.isArray(value) ? value : [value];\n const result: string[] = [];\n\n for (const input of inputs) {\n if (typeof input !== 'string') {\n result.push(input as unknown as string);\n continue;\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n result.push(input);\n continue;\n }\n\n let normalized = trimmed;\n if (normalized.length > 1 && normalized.includes('+')) {\n normalized = normalized.replace(/\\s*\\+\\s*/g, ' ');\n }\n if (/\\s/.test(normalized)) {\n normalized = normalized.replace(/\\s+/g, ' ');\n }\n\n const transformed = transformHotkeyInput(normalized);\n if (transformed.length === 1 && transformed[0] === '' && trimmed !== '') {\n result.push(input);\n continue;\n }\n if (transformed.length === 0) {\n result.push(input);\n continue;\n }\n\n result.push(...transformed);\n }\n\n return result;\n}\n\nexport function getKeyCommands(\n value: string | string[],\n): Array<{ key: string; command?: string }> {\n const keys = normalizeKeyInputs(value);\n\n return keys.reduce((acc: Array<{ key: string; command?: string }>, k) => {\n const includeMeta = keys.includes('Meta') || keys.includes('Control');\n if (includeMeta && (k === 'a' || k === 'A')) {\n return acc.concat([{ key: k, command: 'SelectAll' }]);\n }\n if (includeMeta && (k === 'c' || k === 'C')) {\n return acc.concat([{ key: k, command: 'Copy' }]);\n }\n if (includeMeta && (k === 'v' || k === 'V')) {\n return acc.concat([{ key: k, command: 'Paste' }]);\n }\n return acc.concat([{ key: k }]);\n }, []);\n}\n\n// this is copied from puppeteer, but we don't want to import puppeteer here\nexport declare type KeyInput =\n | '0'\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | '7'\n | '8'\n | '9'\n | 'Power'\n | 'Eject'\n | 'Abort'\n | 'Help'\n | 'Backspace'\n | 'Tab'\n | 'Numpad5'\n | 'NumpadEnter'\n | 'Enter'\n | '\\r'\n | '\\n'\n | 'ShiftLeft'\n | 'ShiftRight'\n | 'ControlLeft'\n | 'ControlRight'\n | 'AltLeft'\n | 'AltRight'\n | 'Pause'\n | 'CapsLock'\n | 'Escape'\n | 'Convert'\n | 'NonConvert'\n | 'Space'\n | 'Numpad9'\n | 'PageUp'\n | 'Numpad3'\n | 'PageDown'\n | 'End'\n | 'Numpad1'\n | 'Home'\n | 'Numpad7'\n | 'ArrowLeft'\n | 'Numpad4'\n | 'Numpad8'\n | 'ArrowUp'\n | 'ArrowRight'\n | 'Numpad6'\n | 'Numpad2'\n | 'ArrowDown'\n | 'Select'\n | 'Open'\n | 'PrintScreen'\n | 'Insert'\n | 'Numpad0'\n | 'Delete'\n | 'NumpadDecimal'\n | 'Digit0'\n | 'Digit1'\n | 'Digit2'\n | 'Digit3'\n | 'Digit4'\n | 'Digit5'\n | 'Digit6'\n | 'Digit7'\n | 'Digit8'\n | 'Digit9'\n | 'KeyA'\n | 'KeyB'\n | 'KeyC'\n | 'KeyD'\n | 'KeyE'\n | 'KeyF'\n | 'KeyG'\n | 'KeyH'\n | 'KeyI'\n | 'KeyJ'\n | 'KeyK'\n | 'KeyL'\n | 'KeyM'\n | 'KeyN'\n | 'KeyO'\n | 'KeyP'\n | 'KeyQ'\n | 'KeyR'\n | 'KeyS'\n | 'KeyT'\n | 'KeyU'\n | 'KeyV'\n | 'KeyW'\n | 'KeyX'\n | 'KeyY'\n | 'KeyZ'\n | 'MetaLeft'\n | 'MetaRight'\n | 'ContextMenu'\n | 'NumpadMultiply'\n | 'NumpadAdd'\n | 'NumpadSubtract'\n | 'NumpadDivide'\n | 'F1'\n | 'F2'\n | 'F3'\n | 'F4'\n | 'F5'\n | 'F6'\n | 'F7'\n | 'F8'\n | 'F9'\n | 'F10'\n | 'F11'\n | 'F12'\n | 'F13'\n | 'F14'\n | 'F15'\n | 'F16'\n | 'F17'\n | 'F18'\n | 'F19'\n | 'F20'\n | 'F21'\n | 'F22'\n | 'F23'\n | 'F24'\n | 'NumLock'\n | 'ScrollLock'\n | 'AudioVolumeMute'\n | 'AudioVolumeDown'\n | 'AudioVolumeUp'\n | 'MediaTrackNext'\n | 'MediaTrackPrevious'\n | 'MediaStop'\n | 'MediaPlayPause'\n | 'Semicolon'\n | 'Equal'\n | 'NumpadEqual'\n | 'Comma'\n | 'Minus'\n | 'Period'\n | 'Slash'\n | 'Backquote'\n | 'BracketLeft'\n | 'Backslash'\n | 'BracketRight'\n | 'Quote'\n | 'AltGraph'\n | 'Props'\n | 'Cancel'\n | 'Clear'\n | 'Shift'\n | 'Control'\n | 'Alt'\n | 'Accept'\n | 'ModeChange'\n | ' '\n | 'Print'\n | 'Execute'\n | '\\u0000'\n | 'a'\n | 'b'\n | 'c'\n | 'd'\n | 'e'\n | 'f'\n | 'g'\n | 'h'\n | 'i'\n | 'j'\n | 'k'\n | 'l'\n | 'm'\n | 'n'\n | 'o'\n | 'p'\n | 'q'\n | 'r'\n | 's'\n | 't'\n | 'u'\n | 'v'\n | 'w'\n | 'x'\n | 'y'\n | 'z'\n | 'Meta'\n | '*'\n | '+'\n | '-'\n | '/'\n | ';'\n | '='\n | ','\n | '.'\n | '`'\n | '['\n | '\\\\'\n | ']'\n | \"'\"\n | 'Attn'\n | 'CrSel'\n | 'ExSel'\n | 'EraseEof'\n | 'Play'\n | 'ZoomOut'\n | ')'\n | '!'\n | '@'\n | '#'\n | '$'\n | '%'\n | '^'\n | '&'\n | '('\n | 'A'\n | 'B'\n | 'C'\n | 'D'\n | 'E'\n | 'F'\n | 'G'\n | 'H'\n | 'I'\n | 'J'\n | 'K'\n | 'L'\n | 'M'\n | 'N'\n | 'O'\n | 'P'\n | 'Q'\n | 'R'\n | 'S'\n | 'T'\n | 'U'\n | 'V'\n | 'W'\n | 'X'\n | 'Y'\n | 'Z'\n | ':'\n | '<'\n | '_'\n | '>'\n | '?'\n | '~'\n | '{'\n | '|'\n | '}'\n | '\"'\n | 'SoftLeft'\n | 'SoftRight'\n | 'Camera'\n | 'Call'\n | 'EndCall'\n | 'VolumeDown'\n | 'VolumeUp';\n\nexport type MouseButton = 'left' | 'right' | 'middle';\n\nexport interface MouseAction {\n click: (\n x: number,\n y: number,\n options: { button: MouseButton; count?: number },\n ) => Promise<void>;\n wheel: (deltaX: number, deltaY: number) => Promise<void>;\n move: (x: number, y: number) => Promise<void>;\n drag: (\n from: { x: number; y: number },\n to: { x: number; y: number },\n ) => Promise<void>;\n}\n\nexport interface KeyboardAction {\n type: (text: string, options?: { delay?: number }) => Promise<void>;\n press: (\n action:\n | { key: KeyInput; command?: string }\n | { key: KeyInput; command?: string }[],\n ) => Promise<void>;\n}\n\nexport interface ChromePageDestroyOptions {\n closeTab?: boolean; // should close the tab when the page object is destroyed\n}\n\nexport abstract class AbstractWebPage extends AbstractInterface {\n navigate?(url: string): Promise<void>;\n reload?(): Promise<void>;\n goBack?(): Promise<void>;\n goForward?(): Promise<void>;\n stopLoading?(): Promise<void>;\n navigationState?(): Promise<{ isLoading: boolean }>;\n flushPendingVisualUpdate?(): Promise<void>;\n schedulePendingVisualUpdate?(): void;\n waitForDomQuiet?(opts?: {\n quietMs?: number;\n timeoutMs?: number;\n target?: ElementInfo;\n }): Promise<void>;\n\n get mouse(): MouseAction {\n return {\n click: async (\n x: number,\n y: number,\n options: { button: MouseButton },\n ) => {},\n wheel: async (deltaX: number, deltaY: number) => {},\n move: async (x: number, y: number) => {},\n drag: async (\n from: { x: number; y: number },\n to: { x: number; y: number },\n ) => {},\n };\n }\n\n get keyboard(): KeyboardAction {\n return {\n type: async (text: string, options?: { delay?: number }) => {},\n press: async (\n action:\n | { key: KeyInput; command?: string }\n | { key: KeyInput; command?: string }[],\n ) => {},\n };\n }\n\n async clearInput(element?: ElementInfo): Promise<void> {}\n\n abstract scrollUntilTop(startingPoint?: Point): Promise<void>;\n abstract scrollUntilBottom(startingPoint?: Point): Promise<void>;\n abstract scrollUntilLeft(startingPoint?: Point): Promise<void>;\n abstract scrollUntilRight(startingPoint?: Point): Promise<void>;\n abstract scrollUp(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollDown(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollLeft(distance?: number, startingPoint?: Point): Promise<void>;\n abstract scrollRight(distance?: number, startingPoint?: Point): Promise<void>;\n abstract longPress(x: number, y: number, duration?: number): Promise<void>;\n abstract swipe(\n from: { x: number; y: number },\n to: { x: number; y: number },\n duration?: number,\n ): Promise<void>;\n abstract pinch(\n centerX: number,\n centerY: number,\n startDistance: number,\n endDistance: number,\n duration?: number,\n ): Promise<void>;\n}\n\nconst scheduleWebVisualUpdate = (page: AbstractWebPage): void => {\n if (page.schedulePendingVisualUpdate) {\n page.schedulePendingVisualUpdate();\n return;\n }\n\n const pendingRefresh = page.flushPendingVisualUpdate?.();\n void pendingRefresh?.catch(() => undefined);\n};\n\nexport function createWebInputPrimitives(\n page: AbstractWebPage,\n): BrowserInputPrimitives {\n const scheduleVisualUpdate = () => scheduleWebVisualUpdate(page);\n\n return {\n pointer: {\n tap: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'left' });\n scheduleVisualUpdate();\n },\n rightClick: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'right' });\n scheduleVisualUpdate();\n },\n doubleClick: async ({ x, y }) => {\n await page.mouse.click(x, y, { button: 'left', count: 2 });\n scheduleVisualUpdate();\n },\n hover: async ({ x, y }) => {\n await page.mouse.move(x, y);\n scheduleVisualUpdate();\n },\n dragAndDrop: async (from, to) => {\n await page.mouse.drag(from, to);\n scheduleVisualUpdate();\n },\n longPress: async ({ x, y }, opts) => {\n await page.longPress(x, y, opts?.duration);\n scheduleVisualUpdate();\n },\n },\n keyboard: {\n typeText: async (value, opts) => {\n const element = opts?.target;\n if (element && opts?.replace !== false) {\n await page.clearInput(element as ElementInfo);\n // Frameworks (React/Vue/etc.) often re-render in response to\n // the `input` event fired by clearing. If that re-render lands\n // between clearInput returning and the first typed character,\n // the keypresses can be dropped. Wait for the DOM to settle\n // before starting to type.\n await page.waitForDomQuiet?.({ target: element as ElementInfo });\n } else if (element && opts?.focusOnly) {\n const target = element as ElementInfo;\n await page.mouse.click(target.center[0], target.center[1], {\n button: 'left',\n });\n await page.keyboard.press([{ key: 'End' }]);\n }\n\n if (opts?.focusOnly) {\n return;\n }\n\n const keyboardTypeOptions =\n opts?.keyboardTypeDelay === undefined\n ? undefined\n : { delay: opts.keyboardTypeDelay };\n await page.keyboard.type(value, keyboardTypeOptions);\n scheduleVisualUpdate();\n },\n keyboardPress: async (keyName, opts) => {\n const element = opts?.target as\n | { center: [number, number] }\n | undefined;\n if (element) {\n await page.mouse.click(element.center[0], element.center[1], {\n button: 'left',\n });\n }\n\n const keys = getKeyCommands(keyName);\n await page.keyboard.press(keys as any);\n scheduleVisualUpdate();\n },\n cursorMove: async (direction, times = 1) => {\n const arrowKey = direction === 'left' ? 'ArrowLeft' : 'ArrowRight';\n for (let i = 0; i < times; i++) {\n await page.keyboard.press([{ key: arrowKey as any }]);\n await sleep(100);\n }\n },\n clearInput: async (target) => {\n await page.clearInput(target as ElementInfo | undefined);\n },\n },\n touch: {\n pinch: async ({ x, y }, opts) => {\n await page.pinch(\n x,\n y,\n opts.startDistance,\n opts.endDistance,\n opts.duration,\n );\n },\n swipe: async (from, to, opts) => {\n await page.swipe(from, to, opts?.duration);\n },\n },\n scroll: {\n scroll: async (param) => {\n const element = param.locate;\n const startingPoint = element\n ? {\n left: element.center[0],\n top: element.center[1],\n }\n : undefined;\n const scrollToEventName = param?.scrollType;\n if (scrollToEventName === 'scrollToTop') {\n await page.scrollUntilTop(startingPoint);\n } else if (scrollToEventName === 'scrollToBottom') {\n await page.scrollUntilBottom(startingPoint);\n } else if (scrollToEventName === 'scrollToRight') {\n await page.scrollUntilRight(startingPoint);\n } else if (scrollToEventName === 'scrollToLeft') {\n await page.scrollUntilLeft(startingPoint);\n } else if (scrollToEventName === 'singleAction' || !scrollToEventName) {\n if (param?.direction === 'down' || !param || !param.direction) {\n await page.scrollDown(param?.distance || undefined, startingPoint);\n } else if (param.direction === 'up') {\n await page.scrollUp(param.distance || undefined, startingPoint);\n } else if (param.direction === 'left') {\n await page.scrollLeft(param.distance || undefined, startingPoint);\n } else if (param.direction === 'right') {\n await page.scrollRight(param.distance || undefined, startingPoint);\n } else {\n throw new Error(`Unknown scroll direction: ${param.direction}`);\n }\n await sleep(500);\n } else {\n throw new Error(\n `Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(\n param,\n )}`,\n );\n }\n scheduleVisualUpdate();\n },\n },\n };\n}\n\nexport const commonWebActionsForWebPage = <T extends AbstractWebPage>(\n page: T,\n includeTouchEvents = false,\n): DeviceAction<any>[] => {\n const input = createWebInputPrimitives(page);\n return [\n ...defineActionsFromInputPrimitives(input, {\n size: () => page.size(),\n includeSwipe: includeTouchEvents,\n }),\n\n defineAction<typeof navigateParamSchema, { url: string }>({\n name: 'Navigate',\n description:\n 'Navigate the browser to a specified URL. Opens the URL in the current tab.',\n paramSchema: navigateParamSchema,\n sample: {\n url: 'https://www.example.com',\n },\n call: async (param) => {\n if (!page.navigate) {\n throw new Error(\n 'Navigate operation is not supported on this page type',\n );\n }\n await page.navigate(param.url);\n scheduleWebVisualUpdate(page);\n },\n }),\n\n defineAction({\n name: 'Reload',\n description: 'Reload the current page',\n call: async () => {\n if (!page.reload) {\n throw new Error(\n 'Reload operation is not supported on this page type',\n );\n }\n await page.reload();\n scheduleWebVisualUpdate(page);\n },\n }),\n\n defineAction({\n name: 'GoBack',\n description: 'Navigate back in browser history',\n call: async () => {\n if (!page.goBack) {\n throw new Error(\n 'GoBack operation is not supported on this page type',\n );\n }\n await page.goBack();\n scheduleWebVisualUpdate(page);\n },\n }),\n defineAction({\n name: 'GoForward',\n description: 'Navigate forward in browser history',\n call: async () => {\n if (!page.goForward) {\n throw new Error(\n 'GoForward operation is not supported on this page type',\n );\n }\n await page.goForward();\n scheduleWebVisualUpdate(page);\n },\n }),\n ];\n};\n"],"names":["navigateParamSchema","z","normalizeKeyInputs","value","inputs","Array","result","input","trimmed","normalized","transformed","transformHotkeyInput","getKeyCommands","keys","acc","k","includeMeta","AbstractWebPage","AbstractInterface","x","y","options","deltaX","deltaY","from","to","text","action","element","scheduleWebVisualUpdate","page","pendingRefresh","undefined","createWebInputPrimitives","scheduleVisualUpdate","opts","target","keyboardTypeOptions","keyName","direction","times","arrowKey","i","sleep","param","startingPoint","scrollToEventName","Error","JSON","commonWebActionsForWebPage","includeTouchEvents","defineActionsFromInputPrimitives","defineAction"],"mappings":"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"}
|
|
@@ -131,7 +131,7 @@ class BridgeServer {
|
|
|
131
131
|
(0, shared_utils_namespaceObject.logMsg)('one client connected');
|
|
132
132
|
this.socket = socket;
|
|
133
133
|
const clientVersion = socket.handshake.query.version;
|
|
134
|
-
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.10.
|
|
134
|
+
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.10.6-beta-20260717061640.0, browser-side version v${clientVersion}`);
|
|
135
135
|
socket.on(external_common_js_namespaceObject.BridgeEvent.CallResponse, (params)=>{
|
|
136
136
|
const id = params.id;
|
|
137
137
|
const response = params.response;
|
|
@@ -155,7 +155,7 @@ class BridgeServer {
|
|
|
155
155
|
setTimeout(()=>{
|
|
156
156
|
this.onConnect?.();
|
|
157
157
|
const payload = {
|
|
158
|
-
version: "1.10.
|
|
158
|
+
version: "1.10.6-beta-20260717061640.0"
|
|
159
159
|
};
|
|
160
160
|
socket.emit(external_common_js_namespaceObject.BridgeEvent.Connected, payload);
|
|
161
161
|
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 {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bridge-mode/io-server.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 {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
|
|
@@ -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.10.
|
|
141
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.6-beta-20260717061640.0`, '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":"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"}
|
|
@@ -49,6 +49,42 @@ const debug = (0, logger_namespaceObject.getDebug)('web:chrome-extension:page');
|
|
|
49
49
|
function sleep(ms) {
|
|
50
50
|
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
51
51
|
}
|
|
52
|
+
const NAVIGATION_COMPLETE_TIMEOUT_MS = 30000;
|
|
53
|
+
function isBlankUrl(url) {
|
|
54
|
+
if (!url) return true;
|
|
55
|
+
return 'about:blank' === url || url.startsWith('chrome://newtab');
|
|
56
|
+
}
|
|
57
|
+
function waitForTabNavigationComplete(tabId, targetUrl, updatedTab, timeoutMs = NAVIGATION_COMPLETE_TIMEOUT_MS) {
|
|
58
|
+
return new Promise((resolve)=>{
|
|
59
|
+
let settled = false;
|
|
60
|
+
const finish = ()=>{
|
|
61
|
+
if (settled) return;
|
|
62
|
+
settled = true;
|
|
63
|
+
chrome.tabs.onUpdated.removeListener(onUpdated);
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
resolve();
|
|
66
|
+
};
|
|
67
|
+
const isTargetTab = (tab)=>{
|
|
68
|
+
if (!tab) return false;
|
|
69
|
+
return tab.url === targetUrl || tab.pendingUrl === targetUrl;
|
|
70
|
+
};
|
|
71
|
+
const isReady = (tab, navigationUpdateObserved)=>{
|
|
72
|
+
if (!tab || 'complete' !== tab.status) return false;
|
|
73
|
+
const currentUrl = tab.url || tab.pendingUrl || '';
|
|
74
|
+
const isExpectedUrlKind = isBlankUrl(targetUrl) ? isBlankUrl(currentUrl) : !isBlankUrl(currentUrl);
|
|
75
|
+
return isExpectedUrlKind && (navigationUpdateObserved || isTargetTab(tab));
|
|
76
|
+
};
|
|
77
|
+
const onUpdated = (id, info, tab)=>{
|
|
78
|
+
if (id === tabId && isReady(tab, 'complete' === info.status)) finish();
|
|
79
|
+
};
|
|
80
|
+
chrome.tabs.onUpdated.addListener(onUpdated);
|
|
81
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
82
|
+
if (isReady(updatedTab, true)) return void finish();
|
|
83
|
+
chrome.tabs.get(tabId).then((tab)=>{
|
|
84
|
+
if (isReady(tab, false)) finish();
|
|
85
|
+
}).catch(()=>{});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
52
88
|
function hasFlatNodeAttribute(attributes, name) {
|
|
53
89
|
if (!attributes) return false;
|
|
54
90
|
for(let i = 0; i < attributes.length; i += 2)if (attributes[i] === name) return true;
|
|
@@ -326,9 +362,25 @@ class ChromeExtensionProxyPage {
|
|
|
326
362
|
const startTime = Date.now();
|
|
327
363
|
let lastReadyState = '';
|
|
328
364
|
while(Date.now() - startTime < timeout){
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
365
|
+
let result;
|
|
366
|
+
try {
|
|
367
|
+
result = await this.sendCommandToDebugger('Runtime.evaluate', {
|
|
368
|
+
expression: 'document.readyState'
|
|
369
|
+
});
|
|
370
|
+
} catch (error) {
|
|
371
|
+
debug('Failed to probe document readyState; retrying after attach', {
|
|
372
|
+
error
|
|
373
|
+
});
|
|
374
|
+
try {
|
|
375
|
+
await this.ensureDebuggerAttached();
|
|
376
|
+
} catch (attachError) {
|
|
377
|
+
debug('Failed to reattach debugger while waiting for navigation', {
|
|
378
|
+
error: attachError
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
await sleep(300);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
332
384
|
lastReadyState = result.result.value;
|
|
333
385
|
if ('complete' === lastReadyState) return void await new Promise((resolve)=>setTimeout(resolve, 300));
|
|
334
386
|
await new Promise((resolve)=>setTimeout(resolve, 300));
|
|
@@ -425,9 +477,10 @@ class ChromeExtensionProxyPage {
|
|
|
425
477
|
}
|
|
426
478
|
async navigate(url) {
|
|
427
479
|
const tabId = await this.getTabIdOrConnectToCurrentTab();
|
|
428
|
-
await chrome.tabs.update(tabId, {
|
|
480
|
+
const updatedTab = await chrome.tabs.update(tabId, {
|
|
429
481
|
url
|
|
430
482
|
});
|
|
483
|
+
await waitForTabNavigationComplete(tabId, url, updatedTab);
|
|
431
484
|
await this.waitUntilNetworkIdle();
|
|
432
485
|
}
|
|
433
486
|
async reload() {
|