@monoes/monobrowse 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +68 -0
- package/dist/src/__tests__/adapters.test.d.ts +2 -0
- package/dist/src/__tests__/adapters.test.d.ts.map +1 -0
- package/dist/src/__tests__/adapters.test.js +104 -0
- package/dist/src/__tests__/adapters.test.js.map +1 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts.map +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js +0 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +0 -0
- package/dist/src/__tests__/box-model.test.d.ts +0 -0
- package/dist/src/__tests__/box-model.test.d.ts.map +0 -0
- package/dist/src/__tests__/box-model.test.js +0 -0
- package/dist/src/__tests__/box-model.test.js.map +0 -0
- package/dist/src/__tests__/browser-launch.test.d.ts +0 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +0 -0
- package/dist/src/__tests__/browser-launch.test.js +0 -0
- package/dist/src/__tests__/browser-launch.test.js.map +0 -0
- package/dist/src/__tests__/cdp-client.test.d.ts +2 -0
- package/dist/src/__tests__/cdp-client.test.d.ts.map +1 -0
- package/dist/src/__tests__/cdp-client.test.js +326 -0
- package/dist/src/__tests__/cdp-client.test.js.map +1 -0
- package/dist/src/__tests__/close-browser.test.d.ts +2 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/close-browser.test.js +188 -0
- package/dist/src/__tests__/close-browser.test.js.map +1 -0
- package/dist/src/__tests__/console-log.test.d.ts +2 -0
- package/dist/src/__tests__/console-log.test.d.ts.map +1 -0
- package/dist/src/__tests__/console-log.test.js +195 -0
- package/dist/src/__tests__/console-log.test.js.map +1 -0
- package/dist/src/__tests__/find.test.d.ts +2 -0
- package/dist/src/__tests__/find.test.d.ts.map +1 -0
- package/dist/src/__tests__/find.test.js +299 -0
- package/dist/src/__tests__/find.test.js.map +1 -0
- package/dist/src/__tests__/frame-command.test.d.ts +2 -0
- package/dist/src/__tests__/frame-command.test.d.ts.map +1 -0
- package/dist/src/__tests__/frame-command.test.js +85 -0
- package/dist/src/__tests__/frame-command.test.js.map +1 -0
- package/dist/src/__tests__/output.test.d.ts +0 -0
- package/dist/src/__tests__/output.test.d.ts.map +0 -0
- package/dist/src/__tests__/output.test.js +0 -0
- package/dist/src/__tests__/output.test.js.map +0 -0
- package/dist/src/__tests__/ref-cache.test.d.ts +0 -0
- package/dist/src/__tests__/ref-cache.test.d.ts.map +0 -0
- package/dist/src/__tests__/ref-cache.test.js +43 -2
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +2 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -0
- package/dist/src/__tests__/session-state.test.js +203 -0
- package/dist/src/__tests__/session-state.test.js.map +1 -0
- package/dist/src/__tests__/snapshot.test.d.ts +2 -0
- package/dist/src/__tests__/snapshot.test.d.ts.map +1 -0
- package/dist/src/__tests__/snapshot.test.js +262 -0
- package/dist/src/__tests__/snapshot.test.js.map +1 -0
- package/dist/src/__tests__/storage-eval.test.d.ts +2 -0
- package/dist/src/__tests__/storage-eval.test.d.ts.map +1 -0
- package/dist/src/__tests__/storage-eval.test.js +133 -0
- package/dist/src/__tests__/storage-eval.test.js.map +1 -0
- package/dist/src/__tests__/tabs.test.d.ts +2 -0
- package/dist/src/__tests__/tabs.test.d.ts.map +1 -0
- package/dist/src/__tests__/tabs.test.js +161 -0
- package/dist/src/__tests__/tabs.test.js.map +1 -0
- package/dist/src/browser/action-builder/analyzer.d.ts +0 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +0 -0
- package/dist/src/browser/action-builder/analyzer.js +0 -0
- package/dist/src/browser/action-builder/analyzer.js.map +0 -0
- package/dist/src/browser/action-builder/types.d.ts +0 -0
- package/dist/src/browser/action-builder/types.d.ts.map +0 -0
- package/dist/src/browser/action-builder/types.js +0 -0
- package/dist/src/browser/action-builder/types.js.map +0 -0
- package/dist/src/browser/actions.d.ts +0 -0
- package/dist/src/browser/actions.d.ts.map +0 -0
- package/dist/src/browser/actions.js +0 -0
- package/dist/src/browser/actions.js.map +0 -0
- package/dist/src/browser/adapters/gemini.d.ts +0 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +0 -0
- package/dist/src/browser/adapters/gemini.js +0 -0
- package/dist/src/browser/adapters/gemini.js.map +0 -0
- package/dist/src/browser/adapters/google.d.ts +0 -0
- package/dist/src/browser/adapters/google.d.ts.map +0 -0
- package/dist/src/browser/adapters/google.js +0 -0
- package/dist/src/browser/adapters/google.js.map +0 -0
- package/dist/src/browser/adapters/index.d.ts +0 -0
- package/dist/src/browser/adapters/index.d.ts.map +0 -0
- package/dist/src/browser/adapters/index.js +0 -0
- package/dist/src/browser/adapters/index.js.map +0 -0
- package/dist/src/browser/adapters/instagram.d.ts +0 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +0 -0
- package/dist/src/browser/adapters/instagram.js +0 -0
- package/dist/src/browser/adapters/instagram.js.map +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +0 -0
- package/dist/src/browser/adapters/linkedin.js +0 -0
- package/dist/src/browser/adapters/linkedin.js.map +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +0 -0
- package/dist/src/browser/adapters/microsoft.js +0 -0
- package/dist/src/browser/adapters/microsoft.js.map +0 -0
- package/dist/src/browser/adapters/x.d.ts +0 -0
- package/dist/src/browser/adapters/x.d.ts.map +0 -0
- package/dist/src/browser/adapters/x.js +0 -0
- package/dist/src/browser/adapters/x.js.map +0 -0
- package/dist/src/browser/batch.d.ts +0 -0
- package/dist/src/browser/batch.d.ts.map +0 -0
- package/dist/src/browser/batch.js +0 -0
- package/dist/src/browser/batch.js.map +0 -0
- package/dist/src/browser/browser.d.ts +13 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +99 -16
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts +4 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +45 -16
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts +0 -0
- package/dist/src/browser/console-log.d.ts.map +0 -0
- package/dist/src/browser/console-log.js +0 -0
- package/dist/src/browser/console-log.js.map +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +0 -0
- package/dist/src/browser/dashboard/api-types.js +0 -0
- package/dist/src/browser/dashboard/api-types.js.map +0 -0
- package/dist/src/browser/dashboard/server.d.ts +0 -0
- package/dist/src/browser/dashboard/server.d.ts.map +0 -0
- package/dist/src/browser/dashboard/server.js +0 -0
- package/dist/src/browser/dashboard/server.js.map +0 -0
- package/dist/src/browser/dashboard/ui.html +0 -0
- package/dist/src/browser/dialog.d.ts +0 -0
- package/dist/src/browser/dialog.d.ts.map +0 -0
- package/dist/src/browser/dialog.js +0 -0
- package/dist/src/browser/dialog.js.map +0 -0
- package/dist/src/browser/emulation.d.ts +0 -0
- package/dist/src/browser/emulation.d.ts.map +0 -0
- package/dist/src/browser/emulation.js +0 -0
- package/dist/src/browser/emulation.js.map +0 -0
- package/dist/src/browser/find.d.ts +0 -0
- package/dist/src/browser/find.d.ts.map +0 -0
- package/dist/src/browser/find.js +0 -0
- package/dist/src/browser/find.js.map +0 -0
- package/dist/src/browser/har.d.ts +0 -0
- package/dist/src/browser/har.d.ts.map +0 -0
- package/dist/src/browser/har.js +0 -0
- package/dist/src/browser/har.js.map +0 -0
- package/dist/src/browser/index.d.ts +0 -0
- package/dist/src/browser/index.d.ts.map +0 -0
- package/dist/src/browser/index.js +0 -0
- package/dist/src/browser/index.js.map +0 -0
- package/dist/src/browser/network.d.ts +0 -0
- package/dist/src/browser/network.d.ts.map +0 -0
- package/dist/src/browser/network.js +0 -0
- package/dist/src/browser/network.js.map +0 -0
- package/dist/src/browser/pdf.d.ts +0 -0
- package/dist/src/browser/pdf.d.ts.map +0 -0
- package/dist/src/browser/pdf.js +0 -0
- package/dist/src/browser/pdf.js.map +0 -0
- package/dist/src/browser/profiler.d.ts +0 -0
- package/dist/src/browser/profiler.d.ts.map +0 -0
- package/dist/src/browser/profiler.js +0 -0
- package/dist/src/browser/profiler.js.map +0 -0
- package/dist/src/browser/record.d.ts +0 -0
- package/dist/src/browser/record.d.ts.map +0 -0
- package/dist/src/browser/record.js +0 -0
- package/dist/src/browser/record.js.map +0 -0
- package/dist/src/browser/ref-cache.d.ts +6 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +21 -3
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts +0 -0
- package/dist/src/browser/screenshot.d.ts.map +0 -0
- package/dist/src/browser/screenshot.js +0 -0
- package/dist/src/browser/screenshot.js.map +0 -0
- package/dist/src/browser/session.d.ts +0 -0
- package/dist/src/browser/session.d.ts.map +0 -0
- package/dist/src/browser/session.js +0 -0
- package/dist/src/browser/session.js.map +0 -0
- package/dist/src/browser/snapshot.d.ts +0 -0
- package/dist/src/browser/snapshot.d.ts.map +0 -0
- package/dist/src/browser/snapshot.js +0 -0
- package/dist/src/browser/snapshot.js.map +0 -0
- package/dist/src/browser/storage.d.ts +0 -0
- package/dist/src/browser/storage.d.ts.map +0 -0
- package/dist/src/browser/storage.js +0 -0
- package/dist/src/browser/storage.js.map +0 -0
- package/dist/src/browser/tabs.d.ts +5 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +4 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts +0 -0
- package/dist/src/browser/trace.d.ts.map +0 -0
- package/dist/src/browser/trace.js +0 -0
- package/dist/src/browser/trace.js.map +0 -0
- package/dist/src/browser/types.d.ts +4 -0
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +0 -0
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts +0 -0
- package/dist/src/browser/vitals.d.ts.map +0 -0
- package/dist/src/browser/vitals.js +0 -0
- package/dist/src/browser/vitals.js.map +0 -0
- package/dist/src/browser/wait.d.ts +0 -0
- package/dist/src/browser/wait.d.ts.map +0 -0
- package/dist/src/browser/wait.js +0 -0
- package/dist/src/browser/wait.js.map +0 -0
- package/dist/src/cli/action.d.ts +0 -0
- package/dist/src/cli/action.d.ts.map +0 -0
- package/dist/src/cli/action.js +0 -0
- package/dist/src/cli/action.js.map +0 -0
- package/dist/src/cli/commands.d.ts +0 -0
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +96 -4
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.d.ts +0 -0
- package/dist/src/cli/output.d.ts.map +0 -0
- package/dist/src/cli/output.js +0 -0
- package/dist/src/cli/output.js.map +0 -0
- package/dist/src/cli/platform.d.ts +0 -0
- package/dist/src/cli/platform.d.ts.map +0 -0
- package/dist/src/cli/platform.js +0 -0
- package/dist/src/cli/platform.js.map +0 -0
- package/dist/src/cli/types.d.ts +0 -0
- package/dist/src/cli/types.d.ts.map +0 -0
- package/dist/src/cli/types.js +0 -0
- package/dist/src/cli/types.js.map +0 -0
- package/dist/src/cli.d.ts +0 -0
- package/dist/src/cli.d.ts.map +0 -0
- package/dist/src/cli.js.map +0 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.d.ts.map +0 -0
- package/dist/src/index.js +0 -0
- package/dist/src/index.js.map +0 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -8
- package/src/__tests__/adapters.test.ts +125 -0
- package/src/__tests__/batch-eval-parsing.test.ts +0 -0
- package/src/__tests__/box-model.test.ts +0 -0
- package/src/__tests__/browser-launch.test.ts +0 -0
- package/src/__tests__/cdp-client.test.ts +375 -0
- package/src/__tests__/close-browser.test.ts +205 -0
- package/src/__tests__/console-log.test.ts +250 -0
- package/src/__tests__/find.test.ts +354 -0
- package/src/__tests__/frame-command.test.ts +98 -0
- package/src/__tests__/output.test.ts +0 -0
- package/src/__tests__/ref-cache.test.ts +47 -2
- package/src/__tests__/session-state.test.ts +251 -0
- package/src/__tests__/snapshot.test.ts +331 -0
- package/src/__tests__/storage-eval.test.ts +171 -0
- package/src/__tests__/tabs.test.ts +183 -0
- package/src/browser/action-builder/analyzer.ts +0 -0
- package/src/browser/action-builder/types.ts +0 -0
- package/src/browser/actions.ts +0 -0
- package/src/browser/adapters/gemini.ts +0 -0
- package/src/browser/adapters/google.ts +0 -0
- package/src/browser/adapters/index.ts +0 -0
- package/src/browser/adapters/instagram.ts +0 -0
- package/src/browser/adapters/linkedin.ts +0 -0
- package/src/browser/adapters/microsoft.ts +0 -0
- package/src/browser/adapters/x.ts +0 -0
- package/src/browser/batch.ts +0 -0
- package/src/browser/browser.ts +101 -16
- package/src/browser/cdp.ts +52 -15
- package/src/browser/console-log.ts +0 -0
- package/src/browser/dashboard/api-types.ts +0 -0
- package/src/browser/dashboard/server.ts +0 -0
- package/src/browser/dashboard/ui.html +0 -0
- package/src/browser/dialog.ts +0 -0
- package/src/browser/emulation.ts +0 -0
- package/src/browser/find.ts +0 -0
- package/src/browser/har.ts +0 -0
- package/src/browser/index.ts +0 -0
- package/src/browser/network.ts +0 -0
- package/src/browser/pdf.ts +0 -0
- package/src/browser/profiler.ts +0 -0
- package/src/browser/record.ts +0 -0
- package/src/browser/ref-cache.ts +29 -6
- package/src/browser/screenshot.ts +0 -0
- package/src/browser/session.ts +0 -0
- package/src/browser/snapshot.ts +0 -0
- package/src/browser/storage.ts +0 -0
- package/src/browser/tabs.ts +12 -4
- package/src/browser/trace.ts +0 -0
- package/src/browser/types.ts +4 -0
- package/src/browser/vitals.ts +0 -0
- package/src/browser/wait.ts +0 -0
- package/src/cli/action.ts +0 -0
- package/src/cli/commands.ts +90 -4
- package/src/cli/output.ts +0 -0
- package/src/cli/platform.ts +0 -0
- package/src/cli/types.ts +0 -0
- package/src/cli.ts +0 -0
- package/src/index.ts +0 -0
package/src/browser/cdp.ts
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
2
|
import type { CdpCommand, CdpResponse, CdpTarget } from './types.js';
|
|
3
3
|
|
|
4
|
+
// send() had no timeout of its own — a command whose response never arrives
|
|
5
|
+
// (wedged/crashed renderer, dropped socket that never fires 'close') left a
|
|
6
|
+
// pendingCommands entry forever, hanging whatever awaited it (and, since
|
|
7
|
+
// most CLI actions await a single send(), the whole CLI process). Ad-hoc
|
|
8
|
+
// timeouts were previously patched in at a handful of call sites (e.g.
|
|
9
|
+
// actions.ts's evaluateJs) instead of here, so most commands had no
|
|
10
|
+
// protection at all. This is the default for every send() unless a caller
|
|
11
|
+
// passes 0 (no timeout) or a different value.
|
|
12
|
+
export const DEFAULT_CDP_SEND_TIMEOUT_MS = 30_000;
|
|
13
|
+
|
|
4
14
|
export class CdpClient {
|
|
5
15
|
private ws: WebSocket | null = null;
|
|
6
|
-
private pendingCommands = new Map<number, { resolve: (r: CdpResponse) => void; reject: (e: Error) => void }>();
|
|
16
|
+
private pendingCommands = new Map<number, { resolve: (r: CdpResponse) => void; reject: (e: Error) => void; timer?: ReturnType<typeof setTimeout> }>();
|
|
7
17
|
private eventListeners = new Map<string, Set<(params: Record<string, unknown>, sessionId?: string) => void>>();
|
|
8
18
|
private nextId = 1;
|
|
9
19
|
private connected = false;
|
|
10
20
|
|
|
21
|
+
constructor() {
|
|
22
|
+
// Target.targetCrashed/targetDestroyed fire over the same WS connection
|
|
23
|
+
// as command responses, but a crashed renderer's connection can otherwise
|
|
24
|
+
// sit open with no 'close'/'error' event ever firing — flush pending
|
|
25
|
+
// commands here so send() doesn't have to wait out its own timeout.
|
|
26
|
+
this.on('Target.targetCrashed', () => this.flushPending(new Error('CDP target crashed')));
|
|
27
|
+
this.on('Target.targetDestroyed', () => this.flushPending(new Error('CDP target destroyed')));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private flushPending(err: Error): void {
|
|
31
|
+
this.connected = false;
|
|
32
|
+
for (const { reject: r, timer } of this.pendingCommands.values()) {
|
|
33
|
+
if (timer) clearTimeout(timer);
|
|
34
|
+
r(err);
|
|
35
|
+
}
|
|
36
|
+
this.pendingCommands.clear();
|
|
37
|
+
}
|
|
38
|
+
|
|
11
39
|
async connect(wsUrl: string): Promise<void> {
|
|
12
40
|
return new Promise((resolve, reject) => {
|
|
13
41
|
this.ws = new WebSocket(wsUrl);
|
|
@@ -22,18 +50,12 @@ export class CdpClient {
|
|
|
22
50
|
reject(err);
|
|
23
51
|
} else {
|
|
24
52
|
// Post-connect: flush all pending commands — 'close' may not fire on all platforms
|
|
25
|
-
this.
|
|
26
|
-
for (const { reject: r } of this.pendingCommands.values()) r(err);
|
|
27
|
-
this.pendingCommands.clear();
|
|
53
|
+
this.flushPending(err instanceof Error ? err : new Error(String(err)));
|
|
28
54
|
}
|
|
29
55
|
});
|
|
30
56
|
|
|
31
57
|
this.ws.on('close', () => {
|
|
32
|
-
this.
|
|
33
|
-
for (const { reject: r } of this.pendingCommands.values()) {
|
|
34
|
-
r(new Error('CDP connection closed'));
|
|
35
|
-
}
|
|
36
|
-
this.pendingCommands.clear();
|
|
58
|
+
this.flushPending(new Error('CDP connection closed'));
|
|
37
59
|
});
|
|
38
60
|
|
|
39
61
|
this.ws.on('message', (data) => {
|
|
@@ -62,7 +84,12 @@ export class CdpClient {
|
|
|
62
84
|
});
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
send<T = Record<string, unknown>>(
|
|
87
|
+
send<T = Record<string, unknown>>(
|
|
88
|
+
method: string,
|
|
89
|
+
params?: Record<string, unknown>,
|
|
90
|
+
sessionId?: string,
|
|
91
|
+
timeoutMs: number = DEFAULT_CDP_SEND_TIMEOUT_MS
|
|
92
|
+
): Promise<T> {
|
|
66
93
|
return new Promise((resolve, reject) => {
|
|
67
94
|
if (!this.ws || !this.connected) {
|
|
68
95
|
reject(new Error('CDP not connected'));
|
|
@@ -76,12 +103,24 @@ export class CdpClient {
|
|
|
76
103
|
const id = this.nextId++;
|
|
77
104
|
const cmd: CdpCommand = { id, method, params };
|
|
78
105
|
if (sessionId) cmd.sessionId = sessionId;
|
|
106
|
+
|
|
107
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
108
|
+
if (timeoutMs > 0) {
|
|
109
|
+
timer = setTimeout(() => {
|
|
110
|
+
this.pendingCommands.delete(id);
|
|
111
|
+
reject(new Error(`CDP command "${method}" timed out after ${timeoutMs}ms`));
|
|
112
|
+
}, timeoutMs);
|
|
113
|
+
timer.unref?.();
|
|
114
|
+
}
|
|
115
|
+
|
|
79
116
|
this.pendingCommands.set(id, {
|
|
80
|
-
resolve: (r) => resolve((r.result ?? {}) as T),
|
|
81
|
-
reject,
|
|
117
|
+
resolve: (r) => { if (timer) clearTimeout(timer); resolve((r.result ?? {}) as T); },
|
|
118
|
+
reject: (e) => { if (timer) clearTimeout(timer); reject(e); },
|
|
119
|
+
timer,
|
|
82
120
|
});
|
|
83
121
|
this.ws.send(JSON.stringify(cmd), (err) => {
|
|
84
122
|
if (err) {
|
|
123
|
+
if (timer) clearTimeout(timer);
|
|
85
124
|
this.pendingCommands.delete(id);
|
|
86
125
|
reject(err);
|
|
87
126
|
}
|
|
@@ -120,9 +159,7 @@ export class CdpClient {
|
|
|
120
159
|
}
|
|
121
160
|
|
|
122
161
|
close(): void {
|
|
123
|
-
this.
|
|
124
|
-
for (const { reject: r } of this.pendingCommands.values()) r(new Error('CDP connection closed'));
|
|
125
|
-
this.pendingCommands.clear();
|
|
162
|
+
this.flushPending(new Error('CDP connection closed'));
|
|
126
163
|
this.ws?.close();
|
|
127
164
|
this.ws = null;
|
|
128
165
|
this.eventListeners.clear();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/browser/dialog.ts
CHANGED
|
File without changes
|
package/src/browser/emulation.ts
CHANGED
|
File without changes
|
package/src/browser/find.ts
CHANGED
|
File without changes
|
package/src/browser/har.ts
CHANGED
|
File without changes
|
package/src/browser/index.ts
CHANGED
|
File without changes
|
package/src/browser/network.ts
CHANGED
|
File without changes
|
package/src/browser/pdf.ts
CHANGED
|
File without changes
|
package/src/browser/profiler.ts
CHANGED
|
File without changes
|
package/src/browser/record.ts
CHANGED
|
File without changes
|
package/src/browser/ref-cache.ts
CHANGED
|
@@ -97,14 +97,29 @@ export async function clearRefCache(): Promise<void> {
|
|
|
97
97
|
* falling back to the hardcoded default port and launching/attaching to a
|
|
98
98
|
* second, unrelated Chrome instance.
|
|
99
99
|
*/
|
|
100
|
-
export async function saveActivePort(
|
|
100
|
+
export async function saveActivePort(
|
|
101
|
+
port: number,
|
|
102
|
+
opts?: { launched?: boolean; pid?: number; userDataDir?: string }
|
|
103
|
+
): Promise<void> {
|
|
101
104
|
try {
|
|
102
105
|
await mkdir(CACHE_DIR, { recursive: true });
|
|
103
106
|
// `launched` records provenance: true = monobrowse spawned this Chrome
|
|
104
107
|
// (safe to Browser.close later); false = attached to a browser someone
|
|
105
108
|
// else owns (must never be killed). Absent (old files) reads as launched
|
|
106
109
|
// — matches pre-flag behavior where every persisted port came from open.
|
|
107
|
-
|
|
110
|
+
//
|
|
111
|
+
// `pid`/`userDataDir` are persisted so a LATER, fresh CLI process (each
|
|
112
|
+
// command is its own node process — see module header) can still find
|
|
113
|
+
// and kill the Chrome this one launched. Without this, closeBrowser()'s
|
|
114
|
+
// PID-kill fallback can never fire outside the launching process, since
|
|
115
|
+
// the in-memory launchedPids Map (browser.ts) is empty there.
|
|
116
|
+
await writeFile(PORT_FILE, JSON.stringify({
|
|
117
|
+
port,
|
|
118
|
+
launched: opts?.launched !== false,
|
|
119
|
+
pid: opts?.pid,
|
|
120
|
+
userDataDir: opts?.userDataDir,
|
|
121
|
+
savedAt: Date.now(),
|
|
122
|
+
}));
|
|
108
123
|
} catch {
|
|
109
124
|
// Best-effort — persistence failure just means the next process falls
|
|
110
125
|
// back to the hardcoded default port, matching prior behavior.
|
|
@@ -126,13 +141,21 @@ export async function loadActivePort(): Promise<number | null> {
|
|
|
126
141
|
return (await loadActivePortInfo())?.port ?? null;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
|
-
/** Load the persisted active port with its provenance flag. */
|
|
130
|
-
export async function loadActivePortInfo(): Promise<
|
|
144
|
+
/** Load the persisted active port with its provenance flag, PID, user-data-dir, and save timestamp. */
|
|
145
|
+
export async function loadActivePortInfo(): Promise<
|
|
146
|
+
{ port: number; launched: boolean; pid?: number; userDataDir?: string; savedAt?: number } | null
|
|
147
|
+
> {
|
|
131
148
|
try {
|
|
132
149
|
const raw = await readFile(PORT_FILE, 'utf8');
|
|
133
|
-
const data = JSON.parse(raw) as { port?: unknown; launched?: unknown };
|
|
150
|
+
const data = JSON.parse(raw) as { port?: unknown; launched?: unknown; pid?: unknown; userDataDir?: unknown; savedAt?: unknown };
|
|
134
151
|
if (typeof data.port === 'number' && Number.isInteger(data.port) && data.port >= 1024 && data.port <= 65535) {
|
|
135
|
-
return {
|
|
152
|
+
return {
|
|
153
|
+
port: data.port,
|
|
154
|
+
launched: data.launched !== false,
|
|
155
|
+
pid: typeof data.pid === 'number' && Number.isInteger(data.pid) && data.pid > 0 ? data.pid : undefined,
|
|
156
|
+
userDataDir: typeof data.userDataDir === 'string' ? data.userDataDir : undefined,
|
|
157
|
+
savedAt: typeof data.savedAt === 'number' ? data.savedAt : undefined,
|
|
158
|
+
};
|
|
136
159
|
}
|
|
137
160
|
return null;
|
|
138
161
|
} catch {
|
|
File without changes
|
package/src/browser/session.ts
CHANGED
|
File without changes
|
package/src/browser/snapshot.ts
CHANGED
|
File without changes
|
package/src/browser/storage.ts
CHANGED
|
File without changes
|
package/src/browser/tabs.ts
CHANGED
|
@@ -24,11 +24,16 @@ export async function activateTab(client: CdpClient, oldSessionId: string, targe
|
|
|
24
24
|
return result.sessionId;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface FrameSwitchResult {
|
|
28
|
+
url: string | null;
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
export async function switchToFrame(
|
|
28
33
|
client: CdpClient,
|
|
29
34
|
sessionId: string,
|
|
30
35
|
frameSelector: string
|
|
31
|
-
): Promise<
|
|
36
|
+
): Promise<FrameSwitchResult> {
|
|
32
37
|
const { result } = await client.send<{ result: { result: { value: string | null } } }>(
|
|
33
38
|
'Runtime.evaluate',
|
|
34
39
|
{
|
|
@@ -50,7 +55,7 @@ export async function switchToFrame(
|
|
|
50
55
|
const match = frameTree.childFrames?.find(
|
|
51
56
|
(cf) => cf.frame.url === frameSrc || cf.frame.id === frameSelector
|
|
52
57
|
);
|
|
53
|
-
if (!match) return frameSrc;
|
|
58
|
+
if (!match) return { url: frameSrc, sessionId: null };
|
|
54
59
|
|
|
55
60
|
const targets = await client.send<{ targetInfos: Array<{ targetId: string; type: string; url: string }> }>(
|
|
56
61
|
'Target.getTargets', {}
|
|
@@ -59,8 +64,11 @@ export async function switchToFrame(
|
|
|
59
64
|
(t) => t.type === 'iframe' && t.url === match.frame.url
|
|
60
65
|
);
|
|
61
66
|
if (oopif) {
|
|
62
|
-
await client.send
|
|
67
|
+
const attached = await client.send<{ sessionId: string }>(
|
|
68
|
+
'Target.attachToTarget', { targetId: oopif.targetId, flatten: true }
|
|
69
|
+
);
|
|
70
|
+
return { url: frameSrc, sessionId: attached.sessionId };
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
return frameSrc;
|
|
73
|
+
return { url: frameSrc, sessionId: null };
|
|
66
74
|
}
|
package/src/browser/trace.ts
CHANGED
|
File without changes
|
package/src/browser/types.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface BrowserConfig {
|
|
|
9
9
|
executablePath?: string;
|
|
10
10
|
userDataDir?: string;
|
|
11
11
|
args?: string[];
|
|
12
|
+
/** Milliseconds to wait for the launched Chrome to open its CDP port
|
|
13
|
+
* before giving up. Default 10000 — raise this for environments where
|
|
14
|
+
* process spawn is consistently slower (e.g. constrained CI runners). */
|
|
15
|
+
launchTimeoutMs?: number;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export interface SessionState {
|
package/src/browser/vitals.ts
CHANGED
|
File without changes
|
package/src/browser/wait.ts
CHANGED
|
File without changes
|
package/src/cli/action.ts
CHANGED
|
File without changes
|
package/src/cli/commands.ts
CHANGED
|
@@ -16,11 +16,52 @@ let _sessionId = '';
|
|
|
16
16
|
let _targetId = '';
|
|
17
17
|
let _port = DEFAULT_PORT;
|
|
18
18
|
let _refs: Map<string, ElementRef> = new Map();
|
|
19
|
+
// Saved parent sessionId when inside an iframe — restored by `frame main`.
|
|
20
|
+
let _parentSessionId = '';
|
|
19
21
|
|
|
20
22
|
async function getBrowser() {
|
|
21
23
|
return import('../index.js');
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
// Best-effort cleanup on Ctrl-C / kill so a launched Chrome doesn't linger as
|
|
27
|
+
// an orphan process when a command is interrupted mid-flight (e.g. during a
|
|
28
|
+
// long-running wait/eval that CdpClient.send()'s own timeout hasn't tripped
|
|
29
|
+
// yet).
|
|
30
|
+
//
|
|
31
|
+
// This module is imported for EVERY `monomind` CLI invocation (commands/
|
|
32
|
+
// index.ts statically imports browse.ts, which imports this file) — not just
|
|
33
|
+
// browse commands — so the handler is only *registered* once a browser
|
|
34
|
+
// session is actually launched here (see ensureSignalCleanupHandlers(),
|
|
35
|
+
// called from the `open` action below), not at module load. Registering
|
|
36
|
+
// unconditionally at import time would call process.exit() on Ctrl-C for
|
|
37
|
+
// every unrelated monomind command (e.g. a long-running `org run` daemon),
|
|
38
|
+
// pre-empting whatever other SIGINT/SIGTERM handling that process needs.
|
|
39
|
+
const SIGNAL_EXIT_CODES: Record<'SIGINT' | 'SIGTERM', number> = { SIGINT: 130, SIGTERM: 143 };
|
|
40
|
+
let _signalHandlersRegistered = false;
|
|
41
|
+
function ensureSignalCleanupHandlers(): void {
|
|
42
|
+
if (_signalHandlersRegistered) return;
|
|
43
|
+
_signalHandlersRegistered = true;
|
|
44
|
+
for (const sig of ['SIGINT', 'SIGTERM'] as const) {
|
|
45
|
+
process.on(sig, () => {
|
|
46
|
+
void (async () => {
|
|
47
|
+
try {
|
|
48
|
+
const browser = await getBrowser();
|
|
49
|
+
if (_client) {
|
|
50
|
+
await browser.closeBrowser(_client, _port);
|
|
51
|
+
} else {
|
|
52
|
+
const pid = browser.getLaunchedPid(_port);
|
|
53
|
+
if (pid !== undefined) {
|
|
54
|
+
try { process.kill(pid, 'SIGKILL'); } catch { /* already exited */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// best-effort — never block process exit on cleanup failure
|
|
59
|
+
}
|
|
60
|
+
})().finally(() => process.exit(SIGNAL_EXIT_CODES[sig]));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
24
65
|
// Each CLI invocation is a fresh process, so this module's `_port` variable
|
|
25
66
|
// is always freshly initialized to DEFAULT_PORT — a `--port 9333` passed to
|
|
26
67
|
// an earlier `open` command has no effect on this process's own default.
|
|
@@ -62,6 +103,7 @@ async function ensureConnected(port: number, targetId?: string) {
|
|
|
62
103
|
_client = conn.client;
|
|
63
104
|
_sessionId = conn.sessionId;
|
|
64
105
|
_targetId = conn.target.id;
|
|
106
|
+
_parentSessionId = '';
|
|
65
107
|
_refs = new Map();
|
|
66
108
|
await hydrateRefsFromCache(browser, _targetId, conn.target.url);
|
|
67
109
|
}
|
|
@@ -157,6 +199,7 @@ async function switchToHeaded(url: string, port: number): Promise<void> {
|
|
|
157
199
|
_client.close();
|
|
158
200
|
_client = null;
|
|
159
201
|
_sessionId = '';
|
|
202
|
+
_parentSessionId = '';
|
|
160
203
|
_targetId = '';
|
|
161
204
|
}
|
|
162
205
|
|
|
@@ -203,6 +246,7 @@ async function switchToHeaded(url: string, port: number): Promise<void> {
|
|
|
203
246
|
_client.close();
|
|
204
247
|
_client = null;
|
|
205
248
|
_sessionId = '';
|
|
249
|
+
_parentSessionId = '';
|
|
206
250
|
_targetId = '';
|
|
207
251
|
|
|
208
252
|
// Relaunch headless and restore session
|
|
@@ -277,14 +321,40 @@ const openCommand: Command = {
|
|
|
277
321
|
prevClient.close();
|
|
278
322
|
_client = null;
|
|
279
323
|
_sessionId = '';
|
|
324
|
+
_parentSessionId = '';
|
|
280
325
|
_targetId = '';
|
|
281
326
|
_refs = new Map();
|
|
282
327
|
}
|
|
283
328
|
|
|
284
329
|
_port = await browser.launchBrowser({ port, headless: !forceHeaded });
|
|
285
330
|
// Persist the active port so subsequent CLI invocations (each a fresh
|
|
286
|
-
// process) default to attaching here instead of hardcoded 9222.
|
|
287
|
-
|
|
331
|
+
// process) default to attaching here instead of hardcoded 9222. Also
|
|
332
|
+
// persist the launched PID/userDataDir so a later process's closeBrowser
|
|
333
|
+
// can still kill this Chrome even though launchedPids (browser.ts) is
|
|
334
|
+
// per-process and empty there.
|
|
335
|
+
//
|
|
336
|
+
// launchBrowser() can either LAUNCH a fresh Chrome or ATTACH to one
|
|
337
|
+
// already listening on the requested port (see its own "attach if
|
|
338
|
+
// already Chrome" comment) — it returns only a port number, with no
|
|
339
|
+
// signal telling this caller which happened. getLaunchedPid(_port) is
|
|
340
|
+
// undefined on the attach path (this process never spawned anything).
|
|
341
|
+
// Bug fixed here: unconditionally saving {pid: undefined, ...} on
|
|
342
|
+
// attach used to CLOBBER a real PID a previous `open` had already
|
|
343
|
+
// persisted for this exact port, destroying the only way a later
|
|
344
|
+
// process's closeBrowser() PID-kill fallback could ever find it.
|
|
345
|
+
const freshPid = browser.getLaunchedPid(_port);
|
|
346
|
+
const freshUserDataDir = browser.getLaunchedUserDataDir(_port);
|
|
347
|
+
if (freshPid !== undefined) {
|
|
348
|
+
await browser.saveActivePort(_port, { pid: freshPid, userDataDir: freshUserDataDir });
|
|
349
|
+
} else {
|
|
350
|
+
// Attach path: preserve whatever PID/userDataDir was already on file
|
|
351
|
+
// for this port (most likely from the process that originally
|
|
352
|
+
// launched it) instead of overwriting with undefined.
|
|
353
|
+
const existing = await browser.loadActivePortInfo();
|
|
354
|
+
const preserved = existing && existing.port === _port ? existing : undefined;
|
|
355
|
+
await browser.saveActivePort(_port, { pid: preserved?.pid, userDataDir: preserved?.userDataDir });
|
|
356
|
+
}
|
|
357
|
+
ensureSignalCleanupHandlers();
|
|
288
358
|
const conn = await browser.connectToTarget(_port);
|
|
289
359
|
_client = conn.client;
|
|
290
360
|
_sessionId = conn.sessionId;
|
|
@@ -1275,6 +1345,7 @@ const closeCommand: Command = {
|
|
|
1275
1345
|
client.close();
|
|
1276
1346
|
_client = null;
|
|
1277
1347
|
_sessionId = '';
|
|
1348
|
+
_parentSessionId = '';
|
|
1278
1349
|
_targetId = '';
|
|
1279
1350
|
_refs = new Map();
|
|
1280
1351
|
// Session is gone — forget the persisted port and stale refs so later
|
|
@@ -1919,10 +1990,23 @@ const frameCommand: Command = {
|
|
|
1919
1990
|
if (!target) throw new Error('Usage: monomind browse frame <selector>|main');
|
|
1920
1991
|
|
|
1921
1992
|
if (target === 'main') {
|
|
1993
|
+
if (_parentSessionId) {
|
|
1994
|
+
// Detach from the OOPIF session (best-effort — it may already be gone)
|
|
1995
|
+
await client.send('Target.detachFromTarget', { sessionId: _sessionId }).catch(() => {});
|
|
1996
|
+
_sessionId = _parentSessionId;
|
|
1997
|
+
_parentSessionId = '';
|
|
1998
|
+
}
|
|
1922
1999
|
output.printSuccess('Switched to main frame');
|
|
1923
2000
|
} else {
|
|
1924
|
-
const
|
|
1925
|
-
|
|
2001
|
+
const frameResult = await browser.switchToFrame(client, sessionId, target);
|
|
2002
|
+
if (frameResult.sessionId) {
|
|
2003
|
+
// Save the parent session so `frame main` can restore it
|
|
2004
|
+
_parentSessionId = _sessionId;
|
|
2005
|
+
_sessionId = frameResult.sessionId;
|
|
2006
|
+
// Enable CDP domains on the iframe's session so subsequent commands work
|
|
2007
|
+
await browser.enableSessionDomains(client, _sessionId);
|
|
2008
|
+
}
|
|
2009
|
+
output.printSuccess(`Switched to frame: ${frameResult.url ?? target}`);
|
|
1926
2010
|
}
|
|
1927
2011
|
return { success: true };
|
|
1928
2012
|
},
|
|
@@ -1974,6 +2058,7 @@ const tabCommand: Command = {
|
|
|
1974
2058
|
client.close();
|
|
1975
2059
|
_client = null;
|
|
1976
2060
|
_sessionId = '';
|
|
2061
|
+
_parentSessionId = '';
|
|
1977
2062
|
_targetId = '';
|
|
1978
2063
|
_refs = new Map();
|
|
1979
2064
|
} else {
|
|
@@ -2726,6 +2811,7 @@ const connectCommand: Command = {
|
|
|
2726
2811
|
prevClient.close();
|
|
2727
2812
|
_client = null;
|
|
2728
2813
|
_sessionId = '';
|
|
2814
|
+
_parentSessionId = '';
|
|
2729
2815
|
_targetId = '';
|
|
2730
2816
|
_refs = new Map();
|
|
2731
2817
|
}
|
package/src/cli/output.ts
CHANGED
|
File without changes
|
package/src/cli/platform.ts
CHANGED
|
File without changes
|
package/src/cli/types.ts
CHANGED
|
File without changes
|
package/src/cli.ts
CHANGED
|
File without changes
|
package/src/index.ts
CHANGED
|
File without changes
|