@monoes/monobrowse 1.0.11 → 1.0.12
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/src/__tests__/adapters.test.js +5 -2
- package/dist/src/__tests__/adapters.test.js.map +1 -1
- package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
- package/dist/src/__tests__/box-model.test.js +9 -5
- package/dist/src/__tests__/box-model.test.js.map +1 -1
- package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +17 -10
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/cdp-client.test.js +9 -3
- package/dist/src/__tests__/cdp-client.test.js.map +1 -1
- package/dist/src/__tests__/close-browser.test.d.ts +13 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
- package/dist/src/__tests__/close-browser.test.js +5 -5
- package/dist/src/__tests__/close-browser.test.js.map +1 -1
- package/dist/src/__tests__/console-log.test.js +17 -6
- package/dist/src/__tests__/console-log.test.js.map +1 -1
- package/dist/src/__tests__/find.test.js +26 -6
- package/dist/src/__tests__/find.test.js.map +1 -1
- package/dist/src/__tests__/frame-command.test.js +9 -3
- package/dist/src/__tests__/frame-command.test.js.map +1 -1
- package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
- package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
- package/dist/src/__tests__/output.test.js +1 -1
- package/dist/src/__tests__/output.test.js.map +1 -1
- package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
- package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
- package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +18 -10
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +8 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
- package/dist/src/__tests__/session-state.test.js +10 -10
- package/dist/src/__tests__/session-state.test.js.map +1 -1
- package/dist/src/__tests__/snapshot.test.js +44 -14
- package/dist/src/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/__tests__/storage-eval.test.js +14 -6
- package/dist/src/__tests__/storage-eval.test.js.map +1 -1
- package/dist/src/__tests__/tabs.test.js +25 -9
- package/dist/src/__tests__/tabs.test.js.map +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
- package/dist/src/browser/action-builder/analyzer.js +25 -9
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts.map +1 -1
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +16 -6
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
- package/dist/src/browser/adapters/gemini.js.map +1 -1
- package/dist/src/browser/adapters/google.d.ts.map +1 -1
- package/dist/src/browser/adapters/google.js.map +1 -1
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +3 -3
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
- package/dist/src/browser/adapters/instagram.js.map +1 -1
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
- package/dist/src/browser/adapters/linkedin.js.map +1 -1
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
- package/dist/src/browser/adapters/microsoft.js.map +1 -1
- package/dist/src/browser/adapters/x.d.ts.map +1 -1
- package/dist/src/browser/adapters/x.js.map +1 -1
- package/dist/src/browser/browser.d.ts +30 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +122 -15
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -8
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts.map +1 -1
- package/dist/src/browser/console-log.js +2 -2
- package/dist/src/browser/console-log.js.map +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +29 -19
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +153 -165
- package/dist/src/browser/dialog.d.ts.map +1 -1
- package/dist/src/browser/dialog.js +3 -1
- package/dist/src/browser/dialog.js.map +1 -1
- package/dist/src/browser/emulation.d.ts.map +1 -1
- package/dist/src/browser/emulation.js +21 -6
- package/dist/src/browser/emulation.js.map +1 -1
- package/dist/src/browser/find.d.ts +4 -4
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +14 -6
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +14 -7
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/browser/index.d.ts +16 -16
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +16 -16
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +14 -4
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts.map +1 -1
- package/dist/src/browser/pdf.js +3 -3
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +10 -5
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +1 -1
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +18 -7
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +9 -4
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +11 -7
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +1 -1
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +27 -8
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +1 -1
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +8 -4
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts.map +1 -1
- package/dist/src/browser/storage.js.map +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +9 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts.map +1 -1
- package/dist/src/browser/trace.js +3 -3
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +23 -4
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts.map +1 -1
- package/dist/src/browser/vitals.js +1 -1
- package/dist/src/browser/vitals.js.map +1 -1
- package/dist/src/browser/wait.d.ts.map +1 -1
- package/dist/src/browser/wait.js +11 -5
- package/dist/src/browser/wait.js.map +1 -1
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +14 -11
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +453 -159
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.js +5 -5
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/cli/platform.d.ts.map +1 -1
- package/dist/src/cli/platform.js +15 -11
- package/dist/src/cli/platform.js.map +1 -1
- package/dist/src/cli/types.d.ts +1 -1
- package/dist/src/cli/types.d.ts.map +1 -1
- package/dist/src/cli.js +21 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/adapters.test.ts +15 -8
- package/src/__tests__/batch-eval-parsing.test.ts +4 -2
- package/src/__tests__/box-model.test.ts +9 -5
- package/src/__tests__/browser-launch.test.ts +26 -15
- package/src/__tests__/cdp-client.test.ts +25 -9
- package/src/__tests__/close-browser.test.ts +11 -6
- package/src/__tests__/console-log.test.ts +36 -13
- package/src/__tests__/find.test.ts +58 -20
- package/src/__tests__/frame-command.test.ts +12 -6
- package/src/__tests__/open-url-navigate-error.test.ts +2 -4
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/reap-idle-browser.test.ts +276 -0
- package/src/__tests__/ref-cache.test.ts +25 -12
- package/src/__tests__/session-state.test.ts +40 -28
- package/src/__tests__/snapshot.test.ts +60 -21
- package/src/__tests__/storage-eval.test.ts +30 -16
- package/src/__tests__/tabs.test.ts +26 -10
- package/src/browser/action-builder/analyzer.ts +39 -15
- package/src/browser/action-builder/types.ts +45 -8
- package/src/browser/actions.ts +288 -123
- package/src/browser/adapters/gemini.ts +7 -3
- package/src/browser/adapters/google.ts +5 -3
- package/src/browser/adapters/index.ts +5 -4
- package/src/browser/adapters/instagram.ts +4 -2
- package/src/browser/adapters/linkedin.ts +4 -2
- package/src/browser/adapters/microsoft.ts +9 -5
- package/src/browser/adapters/x.ts +7 -3
- package/src/browser/browser.ts +188 -42
- package/src/browser/cdp.ts +43 -11
- package/src/browser/console-log.ts +24 -7
- package/src/browser/dashboard/server.ts +44 -25
- package/src/browser/dashboard/ui.html +153 -165
- package/src/browser/dialog.ts +25 -8
- package/src/browser/emulation.ts +86 -36
- package/src/browser/find.ts +110 -47
- package/src/browser/har.ts +58 -26
- package/src/browser/index.ts +16 -16
- package/src/browser/network.ts +147 -46
- package/src/browser/pdf.ts +4 -4
- package/src/browser/profiler.ts +23 -10
- package/src/browser/record.ts +38 -19
- package/src/browser/ref-cache.ts +38 -17
- package/src/browser/screenshot.ts +58 -32
- package/src/browser/session.ts +51 -18
- package/src/browser/snapshot.ts +34 -20
- package/src/browser/storage.ts +73 -17
- package/src/browser/tabs.ts +29 -17
- package/src/browser/trace.ts +14 -10
- package/src/browser/types.ts +23 -4
- package/src/browser/vitals.ts +33 -16
- package/src/browser/wait.ts +47 -21
- package/src/cli/action.ts +52 -21
- package/src/cli/commands.ts +1093 -385
- package/src/cli/output.ts +5 -5
- package/src/cli/platform.ts +44 -23
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +34 -18
- package/src/index.ts +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
1
4
|
import type { CdpClient } from './cdp.js';
|
|
2
|
-
import type { ElementRef } from './types.js';
|
|
3
5
|
import { getObjectIdForRef } from './snapshot.js';
|
|
4
|
-
import {
|
|
5
|
-
import { join, dirname } from 'path';
|
|
6
|
-
import { tmpdir } from 'os';
|
|
6
|
+
import type { ElementRef } from './types.js';
|
|
7
7
|
|
|
8
8
|
export interface ScreenshotOptions {
|
|
9
9
|
path?: string;
|
|
@@ -17,16 +17,21 @@ export interface ScreenshotOptions {
|
|
|
17
17
|
async function getViewportTopLeft(
|
|
18
18
|
client: CdpClient,
|
|
19
19
|
sessionId: string,
|
|
20
|
-
ref: ElementRef
|
|
20
|
+
ref: ElementRef,
|
|
21
21
|
): Promise<{ x: number; y: number } | null> {
|
|
22
22
|
const objectId = await getObjectIdForRef(client, sessionId, ref).catch(() => null);
|
|
23
23
|
if (!objectId) return null;
|
|
24
24
|
try {
|
|
25
|
-
const r = await client.send<{ result: { value?: string } }>(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const r = await client.send<{ result: { value?: string } }>(
|
|
26
|
+
'Runtime.callFunctionOn',
|
|
27
|
+
{
|
|
28
|
+
functionDeclaration:
|
|
29
|
+
'function() { var r=this.getBoundingClientRect(); if(r.width===0&&r.height===0)return null; return JSON.stringify({x:Math.round(r.left),y:Math.round(r.top)}); }',
|
|
30
|
+
objectId,
|
|
31
|
+
returnByValue: true,
|
|
32
|
+
},
|
|
33
|
+
sessionId,
|
|
34
|
+
);
|
|
30
35
|
if (!r.result?.value) return null;
|
|
31
36
|
return JSON.parse(r.result.value) as { x: number; y: number };
|
|
32
37
|
} catch {
|
|
@@ -37,7 +42,7 @@ async function getViewportTopLeft(
|
|
|
37
42
|
async function injectAnnotationOverlay(
|
|
38
43
|
client: CdpClient,
|
|
39
44
|
sessionId: string,
|
|
40
|
-
refs: Map<string, ElementRef
|
|
45
|
+
refs: Map<string, ElementRef>,
|
|
41
46
|
): Promise<void> {
|
|
42
47
|
const badges: Array<{ num: number; x: number; y: number }> = [];
|
|
43
48
|
for (const [key, ref] of refs) {
|
|
@@ -49,8 +54,11 @@ async function injectAnnotationOverlay(
|
|
|
49
54
|
if (badges.length === 0) return;
|
|
50
55
|
|
|
51
56
|
const badgesJson = JSON.stringify(badges);
|
|
52
|
-
await client
|
|
53
|
-
|
|
57
|
+
await client
|
|
58
|
+
.send(
|
|
59
|
+
'Runtime.evaluate',
|
|
60
|
+
{
|
|
61
|
+
expression: `(function(){
|
|
54
62
|
var p=document.getElementById('__mm_ann__');if(p)p.remove();
|
|
55
63
|
var c=document.createElement('div');
|
|
56
64
|
c.id='__mm_ann__';
|
|
@@ -64,21 +72,30 @@ async function injectAnnotationOverlay(
|
|
|
64
72
|
});
|
|
65
73
|
document.body.appendChild(c);
|
|
66
74
|
})()`,
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
returnByValue: false,
|
|
76
|
+
},
|
|
77
|
+
sessionId,
|
|
78
|
+
)
|
|
79
|
+
.catch(() => {});
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
async function removeAnnotationOverlay(client: CdpClient, sessionId: string): Promise<void> {
|
|
72
|
-
await client
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
await client
|
|
84
|
+
.send(
|
|
85
|
+
'Runtime.evaluate',
|
|
86
|
+
{
|
|
87
|
+
expression: `(function(){var e=document.getElementById('__mm_ann__');if(e)e.remove();})()`,
|
|
88
|
+
returnByValue: false,
|
|
89
|
+
},
|
|
90
|
+
sessionId,
|
|
91
|
+
)
|
|
92
|
+
.catch(() => {});
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
export async function captureScreenshot(
|
|
79
96
|
client: CdpClient,
|
|
80
97
|
sessionId: string,
|
|
81
|
-
options: ScreenshotOptions = {}
|
|
98
|
+
options: ScreenshotOptions = {},
|
|
82
99
|
): Promise<{ path: string; dataUrl: string }> {
|
|
83
100
|
const format = options.format ?? 'png';
|
|
84
101
|
const params: Record<string, unknown> = { format };
|
|
@@ -88,10 +105,15 @@ export async function captureScreenshot(
|
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
if (options.fullPage) {
|
|
91
|
-
const dims = await client.send<{ result: { value: string } }>(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
const dims = await client.send<{ result: { value: string } }>(
|
|
109
|
+
'Runtime.evaluate',
|
|
110
|
+
{
|
|
111
|
+
expression:
|
|
112
|
+
'JSON.stringify({w: document.documentElement.scrollWidth, h: document.documentElement.scrollHeight})',
|
|
113
|
+
returnByValue: true,
|
|
114
|
+
},
|
|
115
|
+
sessionId,
|
|
116
|
+
);
|
|
95
117
|
const { w, h } = JSON.parse(dims.result?.value ?? '{"w":1280,"h":720}');
|
|
96
118
|
params.clip = { x: 0, y: 0, width: w, height: h, scale: 1 };
|
|
97
119
|
params.captureBeyondViewport = true;
|
|
@@ -123,20 +145,24 @@ export async function setViewport(
|
|
|
123
145
|
client: CdpClient,
|
|
124
146
|
sessionId: string,
|
|
125
147
|
width: number,
|
|
126
|
-
height: number
|
|
148
|
+
height: number,
|
|
127
149
|
): Promise<void> {
|
|
128
|
-
await client.send(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
await client.send(
|
|
151
|
+
'Emulation.setDeviceMetricsOverride',
|
|
152
|
+
{
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
deviceScaleFactor: 1,
|
|
156
|
+
mobile: false,
|
|
157
|
+
},
|
|
158
|
+
sessionId,
|
|
159
|
+
);
|
|
134
160
|
}
|
|
135
161
|
|
|
136
162
|
export async function setUserAgent(
|
|
137
163
|
client: CdpClient,
|
|
138
164
|
sessionId: string,
|
|
139
|
-
userAgent: string
|
|
165
|
+
userAgent: string,
|
|
140
166
|
): Promise<void> {
|
|
141
167
|
await client.send('Emulation.setUserAgentOverride', { userAgent }, sessionId);
|
|
142
168
|
}
|
package/src/browser/session.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { readFile, writeFile
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { SessionState, CdpCookie } from './types.js';
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
5
4
|
import type { CdpClient } from './cdp.js';
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getCookiesForUrls,
|
|
7
|
+
getLocalStorage,
|
|
8
|
+
getSessionStorage,
|
|
9
|
+
setCookies,
|
|
10
|
+
setLocalStorage,
|
|
11
|
+
setSessionStorage,
|
|
12
|
+
} from './network.js';
|
|
13
|
+
import type { SessionState } from './types.js';
|
|
7
14
|
|
|
8
15
|
// Session state files contain cookies/localStorage — treat as secrets:
|
|
9
16
|
// owner-only read/write on the file (0600) and owner-only access on the
|
|
@@ -23,7 +30,12 @@ function validateFilePath(filePath: string): void {
|
|
|
23
30
|
if (!filePath || filePath.includes('\x00')) {
|
|
24
31
|
throw new Error(`Invalid file path: ${JSON.stringify(filePath)}`);
|
|
25
32
|
}
|
|
26
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
filePath.includes('/../') ||
|
|
35
|
+
filePath.startsWith('../') ||
|
|
36
|
+
filePath.endsWith('/..') ||
|
|
37
|
+
filePath.includes('\\')
|
|
38
|
+
) {
|
|
27
39
|
throw new Error(`Invalid file path (traversal not allowed): ${JSON.stringify(filePath)}`);
|
|
28
40
|
}
|
|
29
41
|
}
|
|
@@ -34,7 +46,7 @@ export async function saveSession(
|
|
|
34
46
|
targetId: string,
|
|
35
47
|
name: string,
|
|
36
48
|
url: string,
|
|
37
|
-
title: string
|
|
49
|
+
title: string,
|
|
38
50
|
): Promise<string> {
|
|
39
51
|
validateSessionName(name);
|
|
40
52
|
await mkdir(SESSION_DIR, { recursive: true, mode: SESSION_DIR_MODE });
|
|
@@ -43,7 +55,15 @@ export async function saveSession(
|
|
|
43
55
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
44
56
|
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
45
57
|
|
|
46
|
-
const state: SessionState = {
|
|
58
|
+
const state: SessionState = {
|
|
59
|
+
targetId,
|
|
60
|
+
sessionId,
|
|
61
|
+
url,
|
|
62
|
+
title,
|
|
63
|
+
cookies,
|
|
64
|
+
localStorage,
|
|
65
|
+
sessionStorage,
|
|
66
|
+
};
|
|
47
67
|
const filePath = join(SESSION_DIR, `${name}.json`);
|
|
48
68
|
await writeFile(filePath, JSON.stringify(state, null, 2), { mode: SESSION_FILE_MODE });
|
|
49
69
|
return filePath;
|
|
@@ -52,7 +72,7 @@ export async function saveSession(
|
|
|
52
72
|
export async function loadSession(
|
|
53
73
|
client: CdpClient,
|
|
54
74
|
sessionId: string,
|
|
55
|
-
name: string
|
|
75
|
+
name: string,
|
|
56
76
|
): Promise<SessionState> {
|
|
57
77
|
validateSessionName(name);
|
|
58
78
|
const filePath = join(SESSION_DIR, `${name}.json`);
|
|
@@ -61,15 +81,18 @@ export async function loadSession(
|
|
|
61
81
|
throw err;
|
|
62
82
|
});
|
|
63
83
|
const state: SessionState = JSON.parse(raw);
|
|
64
|
-
if (!Array.isArray(state.cookies))
|
|
84
|
+
if (!Array.isArray(state.cookies))
|
|
85
|
+
throw new Error(`Invalid session file: cookies is not an array`);
|
|
65
86
|
|
|
66
87
|
await setCookies(client, sessionId, state.cookies);
|
|
67
88
|
if (state.localStorage) {
|
|
68
|
-
if (typeof state.localStorage !== 'object' || Array.isArray(state.localStorage))
|
|
89
|
+
if (typeof state.localStorage !== 'object' || Array.isArray(state.localStorage))
|
|
90
|
+
throw new Error('Invalid session file: localStorage is not a plain object');
|
|
69
91
|
await setLocalStorage(client, sessionId, state.localStorage);
|
|
70
92
|
}
|
|
71
93
|
if (state.sessionStorage) {
|
|
72
|
-
if (typeof state.sessionStorage !== 'object' || Array.isArray(state.sessionStorage))
|
|
94
|
+
if (typeof state.sessionStorage !== 'object' || Array.isArray(state.sessionStorage))
|
|
95
|
+
throw new Error('Invalid session file: sessionStorage is not a plain object');
|
|
73
96
|
await setSessionStorage(client, sessionId, state.sessionStorage);
|
|
74
97
|
}
|
|
75
98
|
|
|
@@ -82,21 +105,29 @@ export async function saveStateFile(
|
|
|
82
105
|
targetId: string,
|
|
83
106
|
filePath: string,
|
|
84
107
|
url: string,
|
|
85
|
-
title: string
|
|
108
|
+
title: string,
|
|
86
109
|
): Promise<void> {
|
|
87
110
|
validateFilePath(filePath);
|
|
88
111
|
await mkdir(dirname(filePath), { recursive: true, mode: SESSION_DIR_MODE });
|
|
89
112
|
const cookies = await getCookiesForUrls(client, sessionId, [url]);
|
|
90
113
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
91
114
|
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
92
|
-
const state: SessionState = {
|
|
115
|
+
const state: SessionState = {
|
|
116
|
+
targetId,
|
|
117
|
+
sessionId,
|
|
118
|
+
url,
|
|
119
|
+
title,
|
|
120
|
+
cookies,
|
|
121
|
+
localStorage,
|
|
122
|
+
sessionStorage,
|
|
123
|
+
};
|
|
93
124
|
await writeFile(filePath, JSON.stringify(state, null, 2), { mode: SESSION_FILE_MODE });
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
export async function loadStateFile(
|
|
97
128
|
client: CdpClient,
|
|
98
129
|
sessionId: string,
|
|
99
|
-
filePath: string
|
|
130
|
+
filePath: string,
|
|
100
131
|
): Promise<SessionState> {
|
|
101
132
|
validateFilePath(filePath);
|
|
102
133
|
const raw = await readFile(filePath, 'utf8');
|
|
@@ -104,18 +135,20 @@ export async function loadStateFile(
|
|
|
104
135
|
if (!Array.isArray(state.cookies)) throw new Error(`Invalid state file: cookies is not an array`);
|
|
105
136
|
await setCookies(client, sessionId, state.cookies);
|
|
106
137
|
if (state.localStorage) {
|
|
107
|
-
if (typeof state.localStorage !== 'object' || Array.isArray(state.localStorage))
|
|
138
|
+
if (typeof state.localStorage !== 'object' || Array.isArray(state.localStorage))
|
|
139
|
+
throw new Error('Invalid state file: localStorage is not a plain object');
|
|
108
140
|
await setLocalStorage(client, sessionId, state.localStorage);
|
|
109
141
|
}
|
|
110
142
|
if (state.sessionStorage) {
|
|
111
|
-
if (typeof state.sessionStorage !== 'object' || Array.isArray(state.sessionStorage))
|
|
143
|
+
if (typeof state.sessionStorage !== 'object' || Array.isArray(state.sessionStorage))
|
|
144
|
+
throw new Error('Invalid state file: sessionStorage is not a plain object');
|
|
112
145
|
await setSessionStorage(client, sessionId, state.sessionStorage);
|
|
113
146
|
}
|
|
114
147
|
return state;
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
export async function listSessions(): Promise<string[]> {
|
|
118
|
-
const { readdir } = await import('fs/promises');
|
|
151
|
+
const { readdir } = await import('node:fs/promises');
|
|
119
152
|
const files = await readdir(SESSION_DIR).catch((err: NodeJS.ErrnoException) => {
|
|
120
153
|
if (err.code === 'ENOENT') return [] as string[];
|
|
121
154
|
throw err;
|
package/src/browser/snapshot.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { getCurrentTitle, getCurrentUrl } from './browser.js';
|
|
1
2
|
import type { CdpClient } from './cdp.js';
|
|
2
3
|
import type { ElementRef, SnapshotOptions, SnapshotResult } from './types.js';
|
|
3
4
|
import { INTERACTIVE_ROLES } from './types.js';
|
|
4
|
-
import { getCurrentUrl, getCurrentTitle } from './browser.js';
|
|
5
5
|
|
|
6
6
|
interface AXNode {
|
|
7
7
|
nodeId: number;
|
|
@@ -20,7 +20,7 @@ interface AXNode {
|
|
|
20
20
|
export async function captureSnapshot(
|
|
21
21
|
client: CdpClient,
|
|
22
22
|
sessionId: string,
|
|
23
|
-
options: SnapshotOptions = {}
|
|
23
|
+
options: SnapshotOptions = {},
|
|
24
24
|
): Promise<SnapshotResult> {
|
|
25
25
|
const { interactiveOnly = false, compact = false, maxDepth, selector } = options;
|
|
26
26
|
|
|
@@ -28,25 +28,39 @@ export async function captureSnapshot(
|
|
|
28
28
|
let nodes: AXNode[];
|
|
29
29
|
if (selector) {
|
|
30
30
|
const doc = await client.send<{ root: { nodeId: number } }>('DOM.getDocument', {}, sessionId);
|
|
31
|
-
const found = await client
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const found = await client
|
|
32
|
+
.send<{ nodeId: number }>(
|
|
33
|
+
'DOM.querySelector',
|
|
34
|
+
{ nodeId: doc.root.nodeId, selector },
|
|
35
|
+
sessionId,
|
|
36
|
+
)
|
|
37
|
+
.catch(() => ({ nodeId: 0 }));
|
|
36
38
|
|
|
37
39
|
if (found.nodeId) {
|
|
38
|
-
const partial = await client
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const partial = await client
|
|
41
|
+
.send<{ nodes: AXNode[] }>(
|
|
42
|
+
'Accessibility.getPartialAXTree',
|
|
43
|
+
{ nodeId: found.nodeId, fetchRelatives: false },
|
|
44
|
+
sessionId,
|
|
45
|
+
)
|
|
46
|
+
.catch(async () =>
|
|
47
|
+
client.send<{ nodes: AXNode[] }>('Accessibility.getFullAXTree', {}, sessionId),
|
|
48
|
+
);
|
|
43
49
|
nodes = partial.nodes;
|
|
44
50
|
} else {
|
|
45
|
-
const full = await client.send<{ nodes: AXNode[] }>(
|
|
51
|
+
const full = await client.send<{ nodes: AXNode[] }>(
|
|
52
|
+
'Accessibility.getFullAXTree',
|
|
53
|
+
{},
|
|
54
|
+
sessionId,
|
|
55
|
+
);
|
|
46
56
|
nodes = full.nodes;
|
|
47
57
|
}
|
|
48
58
|
} else {
|
|
49
|
-
const full = await client.send<{ nodes: AXNode[] }>(
|
|
59
|
+
const full = await client.send<{ nodes: AXNode[] }>(
|
|
60
|
+
'Accessibility.getFullAXTree',
|
|
61
|
+
{},
|
|
62
|
+
sessionId,
|
|
63
|
+
);
|
|
50
64
|
nodes = full.nodes;
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -192,10 +206,10 @@ function extractProperties(node: AXNode): {
|
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
export async function resolveRef(
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
_client: CdpClient,
|
|
210
|
+
_sessionId: string,
|
|
197
211
|
refs: Map<string, ElementRef>,
|
|
198
|
-
refKey: string
|
|
212
|
+
refKey: string,
|
|
199
213
|
): Promise<ElementRef> {
|
|
200
214
|
const ref = refs.get(refKey);
|
|
201
215
|
if (!ref) throw new Error(`Element ref @${refKey} not found. Run snapshot first.`);
|
|
@@ -205,14 +219,14 @@ export async function resolveRef(
|
|
|
205
219
|
export async function getObjectIdForRef(
|
|
206
220
|
client: CdpClient,
|
|
207
221
|
sessionId: string,
|
|
208
|
-
ref: ElementRef
|
|
222
|
+
ref: ElementRef,
|
|
209
223
|
): Promise<string | null> {
|
|
210
224
|
if (!ref.backendDOMNodeId) return null;
|
|
211
225
|
|
|
212
226
|
const result = await client.send<{ object: { objectId?: string } }>(
|
|
213
227
|
'DOM.resolveNode',
|
|
214
228
|
{ backendNodeId: ref.backendDOMNodeId },
|
|
215
|
-
sessionId
|
|
229
|
+
sessionId,
|
|
216
230
|
);
|
|
217
231
|
return result.object?.objectId ?? null;
|
|
218
232
|
}
|
|
@@ -231,7 +245,7 @@ export async function getObjectIdForRef(
|
|
|
231
245
|
export async function getElementBox(
|
|
232
246
|
client: CdpClient,
|
|
233
247
|
sessionId: string,
|
|
234
|
-
ref: ElementRef
|
|
248
|
+
ref: ElementRef,
|
|
235
249
|
): Promise<{ x: number; y: number; width: number; height: number } | null> {
|
|
236
250
|
if (!ref.backendDOMNodeId) return null;
|
|
237
251
|
|
package/src/browser/storage.ts
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
import type { CdpClient } from './cdp.js';
|
|
2
1
|
import { evaluateJs } from './actions.js';
|
|
2
|
+
import type { CdpClient } from './cdp.js';
|
|
3
3
|
|
|
4
|
-
export async function getLocalStorageKey(
|
|
5
|
-
|
|
4
|
+
export async function getLocalStorageKey(
|
|
5
|
+
client: CdpClient,
|
|
6
|
+
sessionId: string,
|
|
7
|
+
key: string,
|
|
8
|
+
): Promise<string | null> {
|
|
9
|
+
const result = await evaluateJs(
|
|
10
|
+
client,
|
|
11
|
+
sessionId,
|
|
12
|
+
`localStorage.getItem(${JSON.stringify(key)})`,
|
|
13
|
+
);
|
|
6
14
|
return result as string | null;
|
|
7
15
|
}
|
|
8
16
|
|
|
9
|
-
export async function setLocalStorageKey(
|
|
10
|
-
|
|
17
|
+
export async function setLocalStorageKey(
|
|
18
|
+
client: CdpClient,
|
|
19
|
+
sessionId: string,
|
|
20
|
+
key: string,
|
|
21
|
+
value: string,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
await evaluateJs(
|
|
24
|
+
client,
|
|
25
|
+
sessionId,
|
|
26
|
+
`localStorage.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)})`,
|
|
27
|
+
);
|
|
11
28
|
}
|
|
12
29
|
|
|
13
|
-
export async function removeLocalStorageKey(
|
|
30
|
+
export async function removeLocalStorageKey(
|
|
31
|
+
client: CdpClient,
|
|
32
|
+
sessionId: string,
|
|
33
|
+
key: string,
|
|
34
|
+
): Promise<void> {
|
|
14
35
|
await evaluateJs(client, sessionId, `localStorage.removeItem(${JSON.stringify(key)})`);
|
|
15
36
|
}
|
|
16
37
|
|
|
@@ -18,25 +39,53 @@ export async function clearLocalStorage(client: CdpClient, sessionId: string): P
|
|
|
18
39
|
await evaluateJs(client, sessionId, 'localStorage.clear()');
|
|
19
40
|
}
|
|
20
41
|
|
|
21
|
-
export async function getAllLocalStorage(
|
|
42
|
+
export async function getAllLocalStorage(
|
|
43
|
+
client: CdpClient,
|
|
44
|
+
sessionId: string,
|
|
45
|
+
): Promise<Record<string, string>> {
|
|
22
46
|
const result = await evaluateJs(
|
|
23
47
|
client,
|
|
24
48
|
sessionId,
|
|
25
|
-
'JSON.stringify(Object.fromEntries(Object.entries(localStorage)))'
|
|
49
|
+
'JSON.stringify(Object.fromEntries(Object.entries(localStorage)))',
|
|
26
50
|
);
|
|
27
|
-
try {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(result as string);
|
|
53
|
+
} catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
28
56
|
}
|
|
29
57
|
|
|
30
|
-
export async function getSessionStorageKey(
|
|
31
|
-
|
|
58
|
+
export async function getSessionStorageKey(
|
|
59
|
+
client: CdpClient,
|
|
60
|
+
sessionId: string,
|
|
61
|
+
key: string,
|
|
62
|
+
): Promise<string | null> {
|
|
63
|
+
const result = await evaluateJs(
|
|
64
|
+
client,
|
|
65
|
+
sessionId,
|
|
66
|
+
`sessionStorage.getItem(${JSON.stringify(key)})`,
|
|
67
|
+
);
|
|
32
68
|
return result as string | null;
|
|
33
69
|
}
|
|
34
70
|
|
|
35
|
-
export async function setSessionStorageKey(
|
|
36
|
-
|
|
71
|
+
export async function setSessionStorageKey(
|
|
72
|
+
client: CdpClient,
|
|
73
|
+
sessionId: string,
|
|
74
|
+
key: string,
|
|
75
|
+
value: string,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await evaluateJs(
|
|
78
|
+
client,
|
|
79
|
+
sessionId,
|
|
80
|
+
`sessionStorage.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)})`,
|
|
81
|
+
);
|
|
37
82
|
}
|
|
38
83
|
|
|
39
|
-
export async function removeSessionStorageKey(
|
|
84
|
+
export async function removeSessionStorageKey(
|
|
85
|
+
client: CdpClient,
|
|
86
|
+
sessionId: string,
|
|
87
|
+
key: string,
|
|
88
|
+
): Promise<void> {
|
|
40
89
|
await evaluateJs(client, sessionId, `sessionStorage.removeItem(${JSON.stringify(key)})`);
|
|
41
90
|
}
|
|
42
91
|
|
|
@@ -44,11 +93,18 @@ export async function clearSessionStorage(client: CdpClient, sessionId: string):
|
|
|
44
93
|
await evaluateJs(client, sessionId, 'sessionStorage.clear()');
|
|
45
94
|
}
|
|
46
95
|
|
|
47
|
-
export async function getAllSessionStorage(
|
|
96
|
+
export async function getAllSessionStorage(
|
|
97
|
+
client: CdpClient,
|
|
98
|
+
sessionId: string,
|
|
99
|
+
): Promise<Record<string, string>> {
|
|
48
100
|
const result = await evaluateJs(
|
|
49
101
|
client,
|
|
50
102
|
sessionId,
|
|
51
|
-
'JSON.stringify(Object.fromEntries(Object.entries(sessionStorage)))'
|
|
103
|
+
'JSON.stringify(Object.fromEntries(Object.entries(sessionStorage)))',
|
|
52
104
|
);
|
|
53
|
-
try {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(result as string);
|
|
107
|
+
} catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
54
110
|
}
|
package/src/browser/tabs.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CdpClient } from './cdp.js';
|
|
2
|
+
import { fetchNewTarget, fetchTargets } from './cdp.js';
|
|
2
3
|
import type { CdpTarget } from './types.js';
|
|
3
|
-
import { fetchTargets, fetchNewTarget } from './cdp.js';
|
|
4
4
|
|
|
5
5
|
export async function listTabs(port: number): Promise<CdpTarget[]> {
|
|
6
6
|
const targets = await fetchTargets(port);
|
|
@@ -11,16 +11,27 @@ export async function newTab(port: number, url = 'about:blank'): Promise<CdpTarg
|
|
|
11
11
|
return fetchNewTarget(port, url);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export async function closeTab(
|
|
14
|
+
export async function closeTab(
|
|
15
|
+
client: CdpClient,
|
|
16
|
+
_sessionId: string,
|
|
17
|
+
targetId: string,
|
|
18
|
+
): Promise<void> {
|
|
15
19
|
await client.send('Target.closeTarget', { targetId });
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
export async function activateTab(
|
|
22
|
+
export async function activateTab(
|
|
23
|
+
client: CdpClient,
|
|
24
|
+
oldSessionId: string,
|
|
25
|
+
targetId: string,
|
|
26
|
+
): Promise<string> {
|
|
19
27
|
if (oldSessionId) {
|
|
20
28
|
await client.send('Target.detachFromTarget', { sessionId: oldSessionId }).catch(() => {});
|
|
21
29
|
}
|
|
22
30
|
await client.send('Target.activateTarget', { targetId });
|
|
23
|
-
const result = await client.send<{ sessionId: string }>('Target.attachToTarget', {
|
|
31
|
+
const result = await client.send<{ sessionId: string }>('Target.attachToTarget', {
|
|
32
|
+
targetId,
|
|
33
|
+
flatten: true,
|
|
34
|
+
});
|
|
24
35
|
return result.sessionId;
|
|
25
36
|
}
|
|
26
37
|
|
|
@@ -32,7 +43,7 @@ export interface FrameSwitchResult {
|
|
|
32
43
|
export async function switchToFrame(
|
|
33
44
|
client: CdpClient,
|
|
34
45
|
sessionId: string,
|
|
35
|
-
frameSelector: string
|
|
46
|
+
frameSelector: string,
|
|
36
47
|
): Promise<FrameSwitchResult> {
|
|
37
48
|
const { result } = await client.send<{ result: { result: { value: string | null } } }>(
|
|
38
49
|
'Runtime.evaluate',
|
|
@@ -44,29 +55,30 @@ export async function switchToFrame(
|
|
|
44
55
|
})()`,
|
|
45
56
|
returnByValue: true,
|
|
46
57
|
},
|
|
47
|
-
sessionId
|
|
58
|
+
sessionId,
|
|
48
59
|
);
|
|
49
60
|
const frameSrc = result?.result?.value ?? null;
|
|
50
61
|
|
|
51
62
|
const { frameTree } = await client.send<{
|
|
52
|
-
frameTree: {
|
|
63
|
+
frameTree: {
|
|
64
|
+
childFrames?: Array<{ frame: { id: string; url: string; securityOrigin: string } }>;
|
|
65
|
+
};
|
|
53
66
|
}>('Page.getFrameTree', {}, sessionId);
|
|
54
67
|
|
|
55
68
|
const match = frameTree.childFrames?.find(
|
|
56
|
-
(cf) => cf.frame.url === frameSrc || cf.frame.id === frameSelector
|
|
69
|
+
(cf) => cf.frame.url === frameSrc || cf.frame.id === frameSelector,
|
|
57
70
|
);
|
|
58
71
|
if (!match) return { url: frameSrc, sessionId: null };
|
|
59
72
|
|
|
60
|
-
const targets = await client.send<{
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
const oopif = targets.targetInfos.find(
|
|
64
|
-
(t) => t.type === 'iframe' && t.url === match.frame.url
|
|
65
|
-
);
|
|
73
|
+
const targets = await client.send<{
|
|
74
|
+
targetInfos: Array<{ targetId: string; type: string; url: string }>;
|
|
75
|
+
}>('Target.getTargets', {});
|
|
76
|
+
const oopif = targets.targetInfos.find((t) => t.type === 'iframe' && t.url === match.frame.url);
|
|
66
77
|
if (oopif) {
|
|
67
|
-
const attached = await client.send<{ sessionId: string }>(
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
const attached = await client.send<{ sessionId: string }>('Target.attachToTarget', {
|
|
79
|
+
targetId: oopif.targetId,
|
|
80
|
+
flatten: true,
|
|
81
|
+
});
|
|
70
82
|
return { url: frameSrc, sessionId: attached.sessionId };
|
|
71
83
|
}
|
|
72
84
|
|
package/src/browser/trace.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
1
4
|
import type { CdpClient } from './cdp.js';
|
|
2
|
-
import { writeFile } from 'fs/promises';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { tmpdir } from 'os';
|
|
5
5
|
|
|
6
6
|
export interface TraceOptions {
|
|
7
7
|
path?: string;
|
|
@@ -27,7 +27,7 @@ const _sessions = new Map<string, { events: unknown[]; offData: () => void }>();
|
|
|
27
27
|
export async function startTrace(
|
|
28
28
|
client: CdpClient,
|
|
29
29
|
sessionId: string,
|
|
30
|
-
options: TraceOptions = {}
|
|
30
|
+
options: TraceOptions = {},
|
|
31
31
|
): Promise<void> {
|
|
32
32
|
if (_sessions.has(sessionId)) {
|
|
33
33
|
throw new Error('Trace already in progress for this session');
|
|
@@ -47,12 +47,16 @@ export async function startTrace(
|
|
|
47
47
|
if (options.screenshots) cats.push('disabled-by-default-devtools.screenshot');
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
|
-
await client.send(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
await client.send(
|
|
51
|
+
'Tracing.start',
|
|
52
|
+
{
|
|
53
|
+
traceConfig: {
|
|
54
|
+
includedCategories: cats.filter((c) => !c.startsWith('-')),
|
|
55
|
+
excludedCategories: cats.filter((c) => c.startsWith('-')).map((c) => c.slice(1)),
|
|
56
|
+
},
|
|
54
57
|
},
|
|
55
|
-
|
|
58
|
+
sessionId,
|
|
59
|
+
);
|
|
56
60
|
} catch (err) {
|
|
57
61
|
offData();
|
|
58
62
|
_sessions.delete(sessionId);
|
|
@@ -63,7 +67,7 @@ export async function startTrace(
|
|
|
63
67
|
export async function stopTrace(
|
|
64
68
|
client: CdpClient,
|
|
65
69
|
sessionId: string,
|
|
66
|
-
outputPath?: string
|
|
70
|
+
outputPath?: string,
|
|
67
71
|
): Promise<string> {
|
|
68
72
|
const state = _sessions.get(sessionId);
|
|
69
73
|
if (!state) throw new Error('No active trace for this session');
|