@monoes/monobrowse 1.0.2 → 1.0.4
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__/batch-eval-parsing.test.d.ts +2 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts.map +1 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js +47 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -0
- package/dist/src/__tests__/output.test.d.ts +2 -0
- package/dist/src/__tests__/output.test.d.ts.map +1 -0
- package/dist/src/__tests__/output.test.js +54 -0
- package/dist/src/__tests__/output.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.d.ts +2 -0
- package/dist/src/__tests__/ref-cache.test.d.ts.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +145 -0
- package/dist/src/__tests__/ref-cache.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 +1 -1
- 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 +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +19 -2
- package/dist/src/browser/actions.js.map +1 -1
- 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 +10 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +111 -3
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts +0 -0
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +1 -1
- 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 +1 -1
- package/dist/src/browser/dashboard/server.js +37 -2
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +1 -1
- 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 +1 -1
- 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 +1 -0
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +1 -0
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts +7 -0
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +10 -0
- package/dist/src/browser/network.js.map +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/src/browser/record.d.ts +3 -0
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +37 -5
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts +41 -0
- package/dist/src/browser/ref-cache.d.ts.map +1 -0
- package/dist/src/browser/ref-cache.js +120 -0
- package/dist/src/browser/ref-cache.js.map +1 -0
- 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 +1 -1
- package/dist/src/browser/session.d.ts +0 -0
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +12 -7
- package/dist/src/browser/session.js.map +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +20 -4
- 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 +1 -1
- package/dist/src/browser/types.d.ts +0 -0
- package/dist/src/browser/types.d.ts.map +0 -0
- package/dist/src/browser/types.js +0 -0
- package/dist/src/browser/types.js.map +0 -0
- 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 +1 -1
- package/dist/src/cli/action.js +87 -2
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts +22 -0
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +276 -28
- 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 +1 -1
- 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 +1 -1
- 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 +2 -2
- package/src/__tests__/batch-eval-parsing.test.ts +52 -0
- package/src/__tests__/output.test.ts +62 -0
- package/src/__tests__/ref-cache.test.ts +175 -0
- package/src/browser/action-builder/analyzer.ts +0 -0
- package/src/browser/action-builder/types.ts +0 -0
- package/src/browser/actions.ts +22 -2
- 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 +115 -2
- package/src/browser/cdp.ts +1 -1
- package/src/browser/console-log.ts +0 -0
- package/src/browser/dashboard/api-types.ts +0 -0
- package/src/browser/dashboard/server.ts +37 -2
- package/src/browser/dashboard/ui.html +1 -1
- 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 +1 -0
- package/src/browser/network.ts +11 -0
- package/src/browser/pdf.ts +0 -0
- package/src/browser/profiler.ts +0 -0
- package/src/browser/record.ts +43 -6
- package/src/browser/ref-cache.ts +141 -0
- package/src/browser/screenshot.ts +0 -0
- package/src/browser/session.ts +13 -7
- package/src/browser/snapshot.ts +0 -0
- package/src/browser/storage.ts +0 -0
- package/src/browser/tabs.ts +36 -7
- package/src/browser/trace.ts +0 -0
- package/src/browser/types.ts +0 -0
- package/src/browser/vitals.ts +0 -0
- package/src/browser/wait.ts +0 -0
- package/src/cli/action.ts +85 -3
- package/src/cli/commands.ts +272 -28
- 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/dist/package.json +0 -71
- package/dist/src/browser/playbook/browser-handlers.d.ts +0 -3
- package/dist/src/browser/playbook/browser-handlers.d.ts.map +0 -1
- package/dist/src/browser/playbook/browser-handlers.js +0 -377
- package/dist/src/browser/playbook/browser-handlers.js.map +0 -1
- package/dist/src/browser/playbook/builtin-handlers.d.ts +0 -3
- package/dist/src/browser/playbook/builtin-handlers.d.ts.map +0 -1
- package/dist/src/browser/playbook/builtin-handlers.js +0 -364
- package/dist/src/browser/playbook/builtin-handlers.js.map +0 -1
- package/dist/src/browser/playbook/index.d.ts +0 -3
- package/dist/src/browser/playbook/index.d.ts.map +0 -1
- package/dist/src/browser/playbook/index.js +0 -3
- package/dist/src/browser/playbook/index.js.map +0 -1
- package/dist/src/browser/playbook/store.d.ts +0 -8
- package/dist/src/browser/playbook/store.d.ts.map +0 -1
- package/dist/src/browser/playbook/store.js +0 -87
- package/dist/src/browser/playbook/store.js.map +0 -1
- package/dist/src/browser/workflow/builtin-handlers.d.ts +0 -3
- package/dist/src/browser/workflow/builtin-handlers.d.ts.map +0 -1
- package/dist/src/browser/workflow/builtin-handlers.js +0 -343
- package/dist/src/browser/workflow/builtin-handlers.js.map +0 -1
- package/dist/src/browser/workflow/engine.d.ts +0 -10
- package/dist/src/browser/workflow/engine.d.ts.map +0 -1
- package/dist/src/browser/workflow/engine.js +0 -174
- package/dist/src/browser/workflow/engine.js.map +0 -1
- package/dist/src/browser/workflow/expression.d.ts +0 -4
- package/dist/src/browser/workflow/expression.d.ts.map +0 -1
- package/dist/src/browser/workflow/expression.js +0 -89
- package/dist/src/browser/workflow/expression.js.map +0 -1
- package/dist/src/browser/workflow/index.d.ts +0 -6
- package/dist/src/browser/workflow/index.d.ts.map +0 -1
- package/dist/src/browser/workflow/index.js +0 -6
- package/dist/src/browser/workflow/index.js.map +0 -1
- package/dist/src/browser/workflow/store.d.ts +0 -8
- package/dist/src/browser/workflow/store.d.ts.map +0 -1
- package/dist/src/browser/workflow/store.js +0 -87
- package/dist/src/browser/workflow/store.js.map +0 -1
- package/dist/src/browser/workflow/types.d.ts +0 -62
- package/dist/src/browser/workflow/types.d.ts.map +0 -1
- package/dist/src/browser/workflow/types.js +0 -2
- package/dist/src/browser/workflow/types.js.map +0 -1
- package/dist/src/cli/playbook.d.ts +0 -3
- package/dist/src/cli/playbook.d.ts.map +0 -1
- package/dist/src/cli/playbook.js +0 -265
- package/dist/src/cli/playbook.js.map +0 -1
- package/dist/src/cli/workflow.d.ts +0 -2
- package/dist/src/cli/workflow.d.ts.map +0 -1
- package/dist/src/cli/workflow.js +0 -3
- package/dist/src/cli/workflow.js.map +0 -1
package/src/browser/record.ts
CHANGED
|
@@ -15,8 +15,20 @@ export interface RecordOptions {
|
|
|
15
15
|
export interface RecordingState {
|
|
16
16
|
frames: string[];
|
|
17
17
|
offScreencast: (() => void) | null;
|
|
18
|
+
totalBytes: number;
|
|
19
|
+
autoStopped: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
// Unlike har.ts's MAX_REQUESTS_PER_SESSION cap on the analogous network-request
|
|
23
|
+
// buffer, the screencast frame buffer previously had no size or count cap at
|
|
24
|
+
// all — recording for even a few minutes at normal frame rates can accumulate
|
|
25
|
+
// hundreds of MB of base64-encoded JPEG data in memory, and stopRecording()'s
|
|
26
|
+
// single JSON.stringify() of the whole frame array can exceed V8's max string
|
|
27
|
+
// length and crash the process. Cap total accumulated base64 bytes (simpler to
|
|
28
|
+
// check per-push than re-measuring the whole array) and auto-stop recording
|
|
29
|
+
// once the budget is exhausted.
|
|
30
|
+
const MAX_SCREENCAST_BYTES = 200 * 1024 * 1024; // ~200 MB of accumulated base64 frame data
|
|
31
|
+
|
|
20
32
|
const _sessions = new Map<string, RecordingState>();
|
|
21
33
|
|
|
22
34
|
export async function startRecording(
|
|
@@ -24,17 +36,38 @@ export async function startRecording(
|
|
|
24
36
|
sessionId: string,
|
|
25
37
|
options: RecordOptions = {}
|
|
26
38
|
): Promise<void> {
|
|
27
|
-
|
|
39
|
+
const existing = _sessions.get(sessionId);
|
|
40
|
+
if (existing && !existing.autoStopped) {
|
|
28
41
|
throw new Error('Recording already in progress for this session');
|
|
29
42
|
}
|
|
43
|
+
if (existing) {
|
|
44
|
+
// Previous recording hit the buffer cap and auto-stopped but was never
|
|
45
|
+
// explicitly saved via stopRecording() — drop it so a fresh recording
|
|
46
|
+
// can start (its frames are lost; the auto-stop log already warned the
|
|
47
|
+
// caller to save via "record stop" before starting a new recording).
|
|
48
|
+
_sessions.delete(sessionId);
|
|
49
|
+
}
|
|
30
50
|
|
|
31
|
-
const state: RecordingState = { frames: [], offScreencast: null };
|
|
51
|
+
const state: RecordingState = { frames: [], offScreencast: null, totalBytes: 0, autoStopped: false };
|
|
32
52
|
_sessions.set(sessionId, state);
|
|
33
53
|
|
|
34
54
|
state.offScreencast = client.on('Page.screencastFrame', async (params, sid) => {
|
|
35
55
|
if (sid !== sessionId) return;
|
|
36
56
|
const { data, sessionId: frameSessionId } = params as { data: string; sessionId: number };
|
|
37
|
-
state.
|
|
57
|
+
if (!state.autoStopped) {
|
|
58
|
+
state.frames.push(data);
|
|
59
|
+
state.totalBytes += data.length;
|
|
60
|
+
if (state.totalBytes >= MAX_SCREENCAST_BYTES) {
|
|
61
|
+
state.autoStopped = true;
|
|
62
|
+
state.offScreencast?.();
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.error(
|
|
65
|
+
`[monobrowse] Screen recording auto-stopped: reached ${Math.round(MAX_SCREENCAST_BYTES / (1024 * 1024))}MB ` +
|
|
66
|
+
`of buffered frame data (${state.frames.length} frames). Call "record stop" to save what was captured.`
|
|
67
|
+
);
|
|
68
|
+
await client.send('Page.stopScreencast', {}, sessionId).catch(() => {});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
38
71
|
await client.send('Page.screencastFrameAck', { sessionId: frameSessionId }, sessionId).catch(() => {});
|
|
39
72
|
});
|
|
40
73
|
|
|
@@ -62,7 +95,11 @@ export async function stopRecording(
|
|
|
62
95
|
if (!state) throw new Error('No active recording for this session');
|
|
63
96
|
|
|
64
97
|
try {
|
|
65
|
-
|
|
98
|
+
// If auto-stop already sent Page.stopScreencast and unsubscribed, don't
|
|
99
|
+
// send it again — Chrome errors on stopping an already-stopped screencast.
|
|
100
|
+
if (!state.autoStopped) {
|
|
101
|
+
await client.send('Page.stopScreencast', {}, sessionId);
|
|
102
|
+
}
|
|
66
103
|
} finally {
|
|
67
104
|
state.offScreencast?.();
|
|
68
105
|
_sessions.delete(sessionId);
|
|
@@ -73,9 +110,9 @@ export async function stopRecording(
|
|
|
73
110
|
return path;
|
|
74
111
|
}
|
|
75
112
|
|
|
76
|
-
export function getRecordingStatus(sessionId: string): { recording: boolean; frames: number } {
|
|
113
|
+
export function getRecordingStatus(sessionId: string): { recording: boolean; frames: number; autoStopped: boolean } {
|
|
77
114
|
const state = _sessions.get(sessionId);
|
|
78
|
-
return { recording: !!state, frames: state?.frames.length ?? 0 };
|
|
115
|
+
return { recording: !!state && !state.autoStopped, frames: state?.frames.length ?? 0, autoStopped: state?.autoStopped ?? false };
|
|
79
116
|
}
|
|
80
117
|
|
|
81
118
|
export async function saveFrameAsPng(
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AX-tree ref cache persistence.
|
|
3
|
+
*
|
|
4
|
+
* `snapshot` and `find` are separate CLI invocations — each starts a fresh
|
|
5
|
+
* node process, so the in-memory ElementRef Map built by captureSnapshot()
|
|
6
|
+
* does not survive between them. This module persists a lightweight lookup
|
|
7
|
+
* index (ref -> role/name/nodeId/backendDOMNodeId, i.e. everything CDP needs
|
|
8
|
+
* to re-target an element) to disk so a later process can rehydrate it.
|
|
9
|
+
*
|
|
10
|
+
* The full AX-tree text/dump is intentionally NOT persisted — only the
|
|
11
|
+
* per-ref index, to keep the file small.
|
|
12
|
+
*/
|
|
13
|
+
import { readFile, writeFile, mkdir, rm } from 'fs/promises';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import type { ElementRef } from './types.js';
|
|
16
|
+
|
|
17
|
+
const CACHE_DIR = join(process.cwd(), '.monomind', 'monobrowse');
|
|
18
|
+
const CACHE_FILE = join(CACHE_DIR, 'ax-snapshot.json');
|
|
19
|
+
const PORT_FILE = join(CACHE_DIR, 'active-port.json');
|
|
20
|
+
|
|
21
|
+
/** Snapshot older than this is flagged as possibly stale (page may have changed). */
|
|
22
|
+
export const REF_CACHE_STALE_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
interface RefCacheFile {
|
|
25
|
+
targetId: string;
|
|
26
|
+
url: string;
|
|
27
|
+
savedAt: number;
|
|
28
|
+
refs: ElementRef[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RefCacheEntry {
|
|
32
|
+
refs: Map<string, ElementRef>;
|
|
33
|
+
url: string;
|
|
34
|
+
savedAt: number;
|
|
35
|
+
ageMs: number;
|
|
36
|
+
stale: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Persist the current ref index for a target so a later CLI process can read it back. */
|
|
40
|
+
export async function saveRefCache(
|
|
41
|
+
targetId: string,
|
|
42
|
+
url: string,
|
|
43
|
+
refs: Map<string, ElementRef>
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
try {
|
|
46
|
+
await mkdir(CACHE_DIR, { recursive: true });
|
|
47
|
+
const data: RefCacheFile = {
|
|
48
|
+
targetId,
|
|
49
|
+
url,
|
|
50
|
+
savedAt: Date.now(),
|
|
51
|
+
refs: [...refs.values()],
|
|
52
|
+
};
|
|
53
|
+
await writeFile(CACHE_FILE, JSON.stringify(data));
|
|
54
|
+
} catch {
|
|
55
|
+
// Best-effort — persistence failure just means cross-process rehydration
|
|
56
|
+
// won't work; in-process (same run) behavior is unaffected.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load the persisted ref index, scoped to the given targetId (a cache from a
|
|
62
|
+
* different tab/target is ignored). Returns null if no usable cache exists —
|
|
63
|
+
* callers fall back to an empty in-memory Map, matching prior behavior.
|
|
64
|
+
*/
|
|
65
|
+
export async function loadRefCache(targetId: string): Promise<RefCacheEntry | null> {
|
|
66
|
+
try {
|
|
67
|
+
const raw = await readFile(CACHE_FILE, 'utf8');
|
|
68
|
+
const data: RefCacheFile = JSON.parse(raw);
|
|
69
|
+
if (!data || !Array.isArray(data.refs) || data.targetId !== targetId) return null;
|
|
70
|
+
|
|
71
|
+
const refs = new Map<string, ElementRef>();
|
|
72
|
+
for (const ref of data.refs) {
|
|
73
|
+
if (ref && typeof ref.ref === 'string') refs.set(ref.ref, ref);
|
|
74
|
+
}
|
|
75
|
+
if (refs.size === 0) return null;
|
|
76
|
+
|
|
77
|
+
const ageMs = Date.now() - data.savedAt;
|
|
78
|
+
return { refs, url: data.url, savedAt: data.savedAt, ageMs, stale: ageMs > REF_CACHE_STALE_MS };
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Drop the persisted ref index — call whenever refs are invalidated (navigation, tab switch, close). */
|
|
85
|
+
export async function clearRefCache(): Promise<void> {
|
|
86
|
+
try {
|
|
87
|
+
await rm(CACHE_FILE, { force: true });
|
|
88
|
+
} catch {
|
|
89
|
+
// Nothing to clear, or not writable — non-fatal.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Persist the "active" CDP port so a later CLI invocation (each command is a
|
|
95
|
+
* fresh process — see module header) can find the browser a prior `open
|
|
96
|
+
* --port N` attached to, instead of every subsequent command silently
|
|
97
|
+
* falling back to the hardcoded default port and launching/attaching to a
|
|
98
|
+
* second, unrelated Chrome instance.
|
|
99
|
+
*/
|
|
100
|
+
export async function saveActivePort(port: number, opts?: { launched?: boolean }): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
await mkdir(CACHE_DIR, { recursive: true });
|
|
103
|
+
// `launched` records provenance: true = monobrowse spawned this Chrome
|
|
104
|
+
// (safe to Browser.close later); false = attached to a browser someone
|
|
105
|
+
// else owns (must never be killed). Absent (old files) reads as launched
|
|
106
|
+
// — matches pre-flag behavior where every persisted port came from open.
|
|
107
|
+
await writeFile(PORT_FILE, JSON.stringify({ port, launched: opts?.launched !== false, savedAt: Date.now() }));
|
|
108
|
+
} catch {
|
|
109
|
+
// Best-effort — persistence failure just means the next process falls
|
|
110
|
+
// back to the hardcoded default port, matching prior behavior.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Forget the persisted active port (session closed) so later invocations
|
|
115
|
+
* fall back to the default instead of chasing a dead endpoint. */
|
|
116
|
+
export async function clearActivePort(): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
await rm(PORT_FILE, { force: true });
|
|
119
|
+
} catch {
|
|
120
|
+
// Best-effort — a stale port file only costs one failed probe later.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Load the persisted active port, or null if none was ever saved / it's unreadable. */
|
|
125
|
+
export async function loadActivePort(): Promise<number | null> {
|
|
126
|
+
return (await loadActivePortInfo())?.port ?? null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Load the persisted active port with its provenance flag. */
|
|
130
|
+
export async function loadActivePortInfo(): Promise<{ port: number; launched: boolean } | null> {
|
|
131
|
+
try {
|
|
132
|
+
const raw = await readFile(PORT_FILE, 'utf8');
|
|
133
|
+
const data = JSON.parse(raw) as { port?: unknown; launched?: unknown };
|
|
134
|
+
if (typeof data.port === 'number' && Number.isInteger(data.port) && data.port >= 1024 && data.port <= 65535) {
|
|
135
|
+
return { port: data.port, launched: data.launched !== false };
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
File without changes
|
package/src/browser/session.ts
CHANGED
|
@@ -3,7 +3,13 @@ import { join, dirname } from 'path';
|
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import type { SessionState, CdpCookie } from './types.js';
|
|
5
5
|
import type { CdpClient } from './cdp.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getCookiesForUrls, setCookies, getLocalStorage, setLocalStorage, getSessionStorage, setSessionStorage } from './network.js';
|
|
7
|
+
|
|
8
|
+
// Session state files contain cookies/localStorage — treat as secrets:
|
|
9
|
+
// owner-only read/write on the file (0600) and owner-only access on the
|
|
10
|
+
// containing directory (0700) so other local users can't read session tokens.
|
|
11
|
+
const SESSION_FILE_MODE = 0o600;
|
|
12
|
+
const SESSION_DIR_MODE = 0o700;
|
|
7
13
|
|
|
8
14
|
const SESSION_DIR = join(homedir(), '.monomind', 'browser-sessions');
|
|
9
15
|
|
|
@@ -31,15 +37,15 @@ export async function saveSession(
|
|
|
31
37
|
title: string
|
|
32
38
|
): Promise<string> {
|
|
33
39
|
validateSessionName(name);
|
|
34
|
-
await mkdir(SESSION_DIR, { recursive: true });
|
|
40
|
+
await mkdir(SESSION_DIR, { recursive: true, mode: SESSION_DIR_MODE });
|
|
35
41
|
|
|
36
|
-
const cookies = await
|
|
42
|
+
const cookies = await getCookiesForUrls(client, sessionId, [url]);
|
|
37
43
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
38
44
|
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
39
45
|
|
|
40
46
|
const state: SessionState = { targetId, sessionId, url, title, cookies, localStorage, sessionStorage };
|
|
41
47
|
const filePath = join(SESSION_DIR, `${name}.json`);
|
|
42
|
-
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
48
|
+
await writeFile(filePath, JSON.stringify(state, null, 2), { mode: SESSION_FILE_MODE });
|
|
43
49
|
return filePath;
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -79,12 +85,12 @@ export async function saveStateFile(
|
|
|
79
85
|
title: string
|
|
80
86
|
): Promise<void> {
|
|
81
87
|
validateFilePath(filePath);
|
|
82
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
83
|
-
const cookies = await
|
|
88
|
+
await mkdir(dirname(filePath), { recursive: true, mode: SESSION_DIR_MODE });
|
|
89
|
+
const cookies = await getCookiesForUrls(client, sessionId, [url]);
|
|
84
90
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
85
91
|
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
86
92
|
const state: SessionState = { targetId, sessionId, url, title, cookies, localStorage, sessionStorage };
|
|
87
|
-
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
93
|
+
await writeFile(filePath, JSON.stringify(state, null, 2), { mode: SESSION_FILE_MODE });
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
export async function loadStateFile(
|
package/src/browser/snapshot.ts
CHANGED
|
File without changes
|
package/src/browser/storage.ts
CHANGED
|
File without changes
|
package/src/browser/tabs.ts
CHANGED
|
@@ -25,13 +25,42 @@ export async function activateTab(client: CdpClient, oldSessionId: string, targe
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export async function switchToFrame(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
client: CdpClient,
|
|
29
|
+
sessionId: string,
|
|
30
|
+
frameSelector: string
|
|
31
31
|
): Promise<string | null> {
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const { result } = await client.send<{ result: { result: { value: string | null } } }>(
|
|
33
|
+
'Runtime.evaluate',
|
|
34
|
+
{
|
|
35
|
+
expression: `(() => {
|
|
36
|
+
const el = document.querySelector(${JSON.stringify(frameSelector)});
|
|
37
|
+
if (!el || el.tagName !== 'IFRAME') return null;
|
|
38
|
+
return el.src || null;
|
|
39
|
+
})()`,
|
|
40
|
+
returnByValue: true,
|
|
41
|
+
},
|
|
42
|
+
sessionId
|
|
36
43
|
);
|
|
44
|
+
const frameSrc = result?.result?.value ?? null;
|
|
45
|
+
|
|
46
|
+
const { frameTree } = await client.send<{
|
|
47
|
+
frameTree: { childFrames?: Array<{ frame: { id: string; url: string; securityOrigin: string } }> };
|
|
48
|
+
}>('Page.getFrameTree', {}, sessionId);
|
|
49
|
+
|
|
50
|
+
const match = frameTree.childFrames?.find(
|
|
51
|
+
(cf) => cf.frame.url === frameSrc || cf.frame.id === frameSelector
|
|
52
|
+
);
|
|
53
|
+
if (!match) return frameSrc;
|
|
54
|
+
|
|
55
|
+
const targets = await client.send<{ targetInfos: Array<{ targetId: string; type: string; url: string }> }>(
|
|
56
|
+
'Target.getTargets', {}
|
|
57
|
+
);
|
|
58
|
+
const oopif = targets.targetInfos.find(
|
|
59
|
+
(t) => t.type === 'iframe' && t.url === match.frame.url
|
|
60
|
+
);
|
|
61
|
+
if (oopif) {
|
|
62
|
+
await client.send('Target.attachToTarget', { targetId: oopif.targetId, flatten: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return frameSrc;
|
|
37
66
|
}
|
package/src/browser/trace.ts
CHANGED
|
File without changes
|
package/src/browser/types.ts
CHANGED
|
File without changes
|
package/src/browser/vitals.ts
CHANGED
|
File without changes
|
package/src/browser/wait.ts
CHANGED
|
File without changes
|
package/src/cli/action.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { analyzePageForAction, type AnalyzerPage } from '../index.js';
|
|
7
|
-
import type { ActionDef } from '../index.js';
|
|
7
|
+
import type { ActionDef, StepDef } from '../index.js';
|
|
8
8
|
|
|
9
9
|
async function readAction(filePath: string): Promise<ActionDef> {
|
|
10
10
|
return JSON.parse(await readFile(filePath, 'utf8')) as ActionDef;
|
|
@@ -96,8 +96,90 @@ export function createActionCommand(): Command {
|
|
|
96
96
|
if (eq > 0) params[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
97
97
|
}
|
|
98
98
|
console.log(`Running action: ${actionId} with account: ${opts.account}`);
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
|
|
100
|
+
const custom = await getCustomActions();
|
|
101
|
+
const def = custom.find(a => a.id === actionId);
|
|
102
|
+
if (!def) {
|
|
103
|
+
console.error(`Action not found in custom actions: ${actionId}`);
|
|
104
|
+
console.error('Built-in actions require adapter step definitions — use "action build" to create a custom action.');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { connectToTarget } = await import('../browser/browser.js');
|
|
109
|
+
const { openUrl } = await import('../browser/browser.js');
|
|
110
|
+
const { clickElement, fillElement, evaluateJs } = await import('../browser/actions.js');
|
|
111
|
+
const { findBySelector } = await import('../browser/find.js');
|
|
112
|
+
const { waitFor } = await import('../browser/wait.js');
|
|
113
|
+
const port = parseInt(process.env.MONOBROWSE_PORT ?? '9222', 10);
|
|
114
|
+
const { client, sessionId } = await connectToTarget(port);
|
|
115
|
+
const refs = new Map<string, import('../browser/types.js').ElementRef>();
|
|
116
|
+
|
|
117
|
+
const interpolate = (s: string) =>
|
|
118
|
+
s.replace(/\{\{(\w+)\}\}/g, (_, k: string) => params[k] ?? `{{${k}}}`);
|
|
119
|
+
|
|
120
|
+
async function runSteps(steps: StepDef[]): Promise<void> {
|
|
121
|
+
for (const step of steps) {
|
|
122
|
+
switch (step.type) {
|
|
123
|
+
case 'navigate':
|
|
124
|
+
await openUrl(client, sessionId, interpolate(step.url));
|
|
125
|
+
console.log(` navigate → ${step.url}`);
|
|
126
|
+
break;
|
|
127
|
+
case 'find': {
|
|
128
|
+
for (const sel of step.selectors) {
|
|
129
|
+
const found = await findBySelector(client, sessionId, refs, interpolate(sel)).catch(() => null);
|
|
130
|
+
if (found) { refs.set(step.as, found); break; }
|
|
131
|
+
}
|
|
132
|
+
console.log(` find → ${step.as}`);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'click': {
|
|
136
|
+
const ref = refs.get(step.target);
|
|
137
|
+
if (!ref) throw new Error(`Element "${step.target}" not found`);
|
|
138
|
+
await clickElement(client, sessionId, ref);
|
|
139
|
+
console.log(` click → ${step.target}`);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'type': {
|
|
143
|
+
const ref = refs.get(step.target);
|
|
144
|
+
if (!ref) throw new Error(`Element "${step.target}" not found`);
|
|
145
|
+
await fillElement(client, sessionId, ref, interpolate(step.text));
|
|
146
|
+
console.log(` type → ${step.target}`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'wait':
|
|
150
|
+
if (step.condition === 'network_idle') {
|
|
151
|
+
await waitFor(client, sessionId, { load: 'networkidle', timeout: step.timeout });
|
|
152
|
+
} else if (step.condition === 'selector' && step.selector) {
|
|
153
|
+
await waitFor(client, sessionId, { selector: step.selector, timeout: step.timeout });
|
|
154
|
+
} else if (step.condition === 'duration') {
|
|
155
|
+
await new Promise(r => setTimeout(r, step.timeout ?? 1000));
|
|
156
|
+
}
|
|
157
|
+
console.log(` wait → ${step.condition}`);
|
|
158
|
+
break;
|
|
159
|
+
case 'extract': {
|
|
160
|
+
const ref = refs.get(step.target);
|
|
161
|
+
if (!ref) throw new Error(`Element "${step.target}" not found`);
|
|
162
|
+
const val = step.attribute
|
|
163
|
+
? await evaluateJs(client, sessionId, `document.querySelector('[data-ref="${ref.ref}"]')?.getAttribute('${step.attribute}')`)
|
|
164
|
+
: await evaluateJs(client, sessionId, `document.querySelector('[data-ref="${ref.ref}"]')?.textContent`);
|
|
165
|
+
console.log(` extract → ${step.as}: ${val}`);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case 'condition': {
|
|
169
|
+
const result = await evaluateJs(client, sessionId, step.expression);
|
|
170
|
+
if (result) {
|
|
171
|
+
await runSteps(step.then);
|
|
172
|
+
} else if (step.else) {
|
|
173
|
+
await runSteps(step.else);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await runSteps(def.steps);
|
|
182
|
+
console.log(`\nAction ${actionId} completed successfully.`);
|
|
101
183
|
});
|
|
102
184
|
|
|
103
185
|
cmd
|