craftdriver 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +2 -1
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +13 -1
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/mcp/tools.d.ts +1 -2
- package/dist/cli/mcp/tools.d.ts.map +1 -1
- package/dist/cli/mcp/tools.js +18 -0
- package/dist/cli/mcp/tools.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/browser.d.ts +156 -27
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +474 -212
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/capabilities.d.ts +35 -0
- package/dist/lib/capabilities.d.ts.map +1 -0
- package/dist/lib/capabilities.js +58 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/chrome.d.ts +7 -2
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +10 -1
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/driverManager.d.ts +92 -0
- package/dist/lib/driverManager.d.ts.map +1 -1
- package/dist/lib/driverManager.js +229 -10
- package/dist/lib/driverManager.js.map +1 -1
- package/dist/lib/electron.d.ts +60 -0
- package/dist/lib/electron.d.ts.map +1 -0
- package/dist/lib/electron.js +85 -0
- package/dist/lib/electron.js.map +1 -0
- package/dist/lib/electronDeeplink.d.ts +22 -0
- package/dist/lib/electronDeeplink.d.ts.map +1 -0
- package/dist/lib/electronDeeplink.js +118 -0
- package/dist/lib/electronDeeplink.js.map +1 -0
- package/dist/lib/electronDiagnostics.d.ts +49 -0
- package/dist/lib/electronDiagnostics.d.ts.map +1 -0
- package/dist/lib/electronDiagnostics.js +195 -0
- package/dist/lib/electronDiagnostics.js.map +1 -0
- package/dist/lib/electronDialogMock.d.ts +51 -0
- package/dist/lib/electronDialogMock.d.ts.map +1 -0
- package/dist/lib/electronDialogMock.js +106 -0
- package/dist/lib/electronDialogMock.js.map +1 -0
- package/dist/lib/electronFuses.d.ts +13 -0
- package/dist/lib/electronFuses.d.ts.map +1 -0
- package/dist/lib/electronFuses.js +118 -0
- package/dist/lib/electronFuses.js.map +1 -0
- package/dist/lib/electronMainBridge.d.ts +28 -0
- package/dist/lib/electronMainBridge.d.ts.map +1 -0
- package/dist/lib/electronMainBridge.js +270 -0
- package/dist/lib/electronMainBridge.js.map +1 -0
- package/dist/lib/electronMainLogs.d.ts +81 -0
- package/dist/lib/electronMainLogs.d.ts.map +1 -0
- package/dist/lib/electronMainLogs.js +176 -0
- package/dist/lib/electronMainLogs.js.map +1 -0
- package/dist/lib/electronMock.d.ts +52 -0
- package/dist/lib/electronMock.d.ts.map +1 -0
- package/dist/lib/electronMock.js +96 -0
- package/dist/lib/electronMock.js.map +1 -0
- package/dist/lib/electronRemote.d.ts +106 -0
- package/dist/lib/electronRemote.d.ts.map +1 -0
- package/dist/lib/electronRemote.js +406 -0
- package/dist/lib/electronRemote.js.map +1 -0
- package/dist/lib/electronVersions.d.ts +34 -0
- package/dist/lib/electronVersions.d.ts.map +1 -0
- package/dist/lib/electronVersions.js +90 -0
- package/dist/lib/electronVersions.js.map +1 -0
- package/dist/lib/errors.d.ts +27 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +27 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/launchTarget.d.ts +30 -0
- package/dist/lib/launchTarget.d.ts.map +1 -0
- package/dist/lib/launchTarget.js +112 -0
- package/dist/lib/launchTarget.js.map +1 -0
- package/dist/lib/page.d.ts +1 -0
- package/dist/lib/page.d.ts.map +1 -1
- package/dist/lib/page.js +1 -1
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/service.d.ts +21 -0
- package/dist/lib/service.d.ts.map +1 -1
- package/dist/lib/service.js +94 -6
- package/dist/lib/service.js.map +1 -1
- package/dist/lib/tracing.d.ts +20 -4
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js +38 -3
- package/dist/lib/tracing.js.map +1 -1
- package/dist/lib/vibiumTrace.d.ts +14 -0
- package/dist/lib/vibiumTrace.d.ts.map +1 -0
- package/dist/lib/vibiumTrace.js +372 -0
- package/dist/lib/vibiumTrace.js.map +1 -0
- package/docs/api-reference.md +38 -2
- package/docs/browser-api.md +7 -1
- package/docs/electron.md +419 -0
- package/docs/error-codes.md +5 -0
- package/docs/index.md +4 -0
- package/docs/mcp.md +17 -3
- package/docs/public/recipes/trace-failing-test.html +16 -0
- package/docs/public/recipes/vitest-vibium-trace.html +16 -0
- package/docs/public/traces/vitest-login.zip +0 -0
- package/docs/recipes/debug-failing-tests-with-traces.md +117 -0
- package/docs/recipes/electron-app-from-another-repo.md +136 -0
- package/docs/recipes/electron-deep-link.md +117 -0
- package/docs/recipes/electron-mock-apis.md +88 -0
- package/docs/recipes/electron-native-dialog.md +105 -0
- package/docs/recipes.md +29 -10
- package/docs/tracing.md +102 -24
- package/package.json +8 -8
- package/skills/craftdriver/cheatsheet.md +4 -2
- package/skills/craftdriver/patterns.md +2 -1
- package/docs/recipes/trace-failing-test.md +0 -51
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free reader for the Electron **fuse wire** — specifically the
|
|
3
|
+
* `EnableNodeCliInspectArguments` fuse that main-process access
|
|
4
|
+
* (`browser.electron.executeMain` / `mock` / `mockDialog` / `mainLogs`) needs. When
|
|
5
|
+
* the main-process inspector can't be reached, this turns the generic "inspector
|
|
6
|
+
* unreachable" into a precise "the fuse is disabled in this build."
|
|
7
|
+
*
|
|
8
|
+
* The wire format is @electron/fuses' own (verified against a real packaged app):
|
|
9
|
+
* a 32-byte sentinel, then a 1-byte wire version, a 1-byte fuse count, then one
|
|
10
|
+
* ASCII byte per fuse — `'0'` disabled, `'1'` enabled, `'r'` removed.
|
|
11
|
+
* `EnableNodeCliInspectArguments` is fuse index 3. On macOS the wire lives in the
|
|
12
|
+
* **Electron Framework**, not the app executable. We read the bytes directly rather
|
|
13
|
+
* than take an `@electron/fuses` dependency (craftdriver keeps two runtime deps).
|
|
14
|
+
*
|
|
15
|
+
* Best-effort by design: any read/parse problem yields `'unknown'`, so this can only
|
|
16
|
+
* ever *improve* a diagnostic, never invent or block one.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
/** @electron/fuses sentinel that precedes the fuse wire in a packaged binary. */
|
|
21
|
+
const SENTINEL = 'dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX';
|
|
22
|
+
const FUSE_DISABLE = 0x30; // '0'
|
|
23
|
+
const FUSE_ENABLE = 0x31; // '1'
|
|
24
|
+
const FUSE_REMOVED = 0x72; // 'r'
|
|
25
|
+
/** Index of EnableNodeCliInspectArguments in the FuseV1 wire. */
|
|
26
|
+
const INSPECT_ARGS_INDEX = 3;
|
|
27
|
+
/**
|
|
28
|
+
* The binary that actually carries the fuse wire for a given app executable. On
|
|
29
|
+
* macOS that's the Electron Framework inside the `.app`; elsewhere it's the
|
|
30
|
+
* executable itself.
|
|
31
|
+
*/
|
|
32
|
+
export function fuseCarrierBinary(appBinaryPath) {
|
|
33
|
+
const FRAMEWORK = ['Frameworks', 'Electron Framework.framework', 'Electron Framework'];
|
|
34
|
+
if (appBinaryPath.endsWith('.app')) {
|
|
35
|
+
return path.join(appBinaryPath, 'Contents', ...FRAMEWORK);
|
|
36
|
+
}
|
|
37
|
+
if (appBinaryPath.includes('.app/') || appBinaryPath.includes(`.app${path.sep}`)) {
|
|
38
|
+
// .../Contents/MacOS/<exe> -> .../Contents/Frameworks/Electron Framework.framework/…
|
|
39
|
+
return path.resolve(appBinaryPath, '..', '..', ...FRAMEWORK);
|
|
40
|
+
}
|
|
41
|
+
return appBinaryPath;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read the `EnableNodeCliInspectArguments` fuse straight from the packaged binary.
|
|
45
|
+
* Returns `'unknown'` for any older/unfused binary or on any read problem.
|
|
46
|
+
*/
|
|
47
|
+
export function readInspectArgumentsFuse(appBinaryPath) {
|
|
48
|
+
if (!appBinaryPath)
|
|
49
|
+
return 'unknown';
|
|
50
|
+
let fd;
|
|
51
|
+
try {
|
|
52
|
+
const binary = fuseCarrierBinary(appBinaryPath);
|
|
53
|
+
if (!fs.existsSync(binary))
|
|
54
|
+
return 'unknown';
|
|
55
|
+
fd = fs.openSync(binary, 'r');
|
|
56
|
+
const sentinelPos = findSentinel(fd);
|
|
57
|
+
if (sentinelPos === -1)
|
|
58
|
+
return 'unknown';
|
|
59
|
+
// After the sentinel: [version][count][fuse0][fuse1]… — read up to our fuse.
|
|
60
|
+
const wire = Buffer.alloc(2 + INSPECT_ARGS_INDEX + 1);
|
|
61
|
+
const read = fs.readSync(fd, wire, 0, wire.length, sentinelPos + SENTINEL.length);
|
|
62
|
+
if (read < wire.length)
|
|
63
|
+
return 'unknown';
|
|
64
|
+
const count = wire[1];
|
|
65
|
+
if (INSPECT_ARGS_INDEX >= count)
|
|
66
|
+
return 'unknown';
|
|
67
|
+
switch (wire[2 + INSPECT_ARGS_INDEX]) {
|
|
68
|
+
case FUSE_ENABLE:
|
|
69
|
+
return 'enabled';
|
|
70
|
+
case FUSE_DISABLE:
|
|
71
|
+
return 'disabled';
|
|
72
|
+
case FUSE_REMOVED:
|
|
73
|
+
return 'removed';
|
|
74
|
+
default:
|
|
75
|
+
return 'unknown';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return 'unknown';
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (fd !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
fs.closeSync(fd);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* ignore */
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Scan a file for the sentinel, returning its byte offset or -1. Chunked with an
|
|
94
|
+
* overlap so a sentinel split across chunk boundaries is still found (the framework
|
|
95
|
+
* binary can be hundreds of MB — never buffer the whole thing).
|
|
96
|
+
*/
|
|
97
|
+
function findSentinel(fd) {
|
|
98
|
+
const needle = Buffer.from(SENTINEL, 'binary');
|
|
99
|
+
const overlap = needle.length - 1;
|
|
100
|
+
const CHUNK = 1 << 20; // 1 MiB
|
|
101
|
+
const buf = Buffer.alloc(CHUNK + overlap);
|
|
102
|
+
let filePos = 0;
|
|
103
|
+
let carry = 0; // bytes retained from the previous chunk's tail
|
|
104
|
+
for (;;) {
|
|
105
|
+
const read = fs.readSync(fd, buf, carry, CHUNK, filePos);
|
|
106
|
+
if (read <= 0)
|
|
107
|
+
return -1;
|
|
108
|
+
const searchable = carry + read;
|
|
109
|
+
const idx = buf.subarray(0, searchable).indexOf(needle);
|
|
110
|
+
if (idx !== -1)
|
|
111
|
+
return filePos - carry + idx;
|
|
112
|
+
// Retain the last `overlap` bytes so a boundary-straddling match is caught next round.
|
|
113
|
+
buf.copy(buf, 0, searchable - overlap, searchable);
|
|
114
|
+
carry = overlap;
|
|
115
|
+
filePos += read;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=electronFuses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electronFuses.js","sourceRoot":"","sources":["../../src/lib/electronFuses.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** A CDP event frame (no `id`), forwarded to a listener instead of being dropped. */
|
|
2
|
+
export type CdpEventListener = (method: string, params: Record<string, unknown>) => void;
|
|
3
|
+
export declare class ElectronMainBridge {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(host: string, port: number);
|
|
6
|
+
get isConnected(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Register a listener for CDP event frames (those without an `id`, e.g.
|
|
9
|
+
* `Runtime.consoleAPICalled`). Used by the main-process log capture; set it
|
|
10
|
+
* before {@link enableRuntime} so the replayed backlog isn't missed.
|
|
11
|
+
*/
|
|
12
|
+
onEvent(listener: CdpEventListener): void;
|
|
13
|
+
/**
|
|
14
|
+
* Enable the CDP `Runtime` domain so the inspector starts (and replays any
|
|
15
|
+
* buffered) `consoleAPICalled` / `exceptionThrown` events. Idempotent.
|
|
16
|
+
*/
|
|
17
|
+
enableRuntime(timeoutMs?: number): Promise<void>;
|
|
18
|
+
/** Fetch the inspector's WebSocket URL and connect to it. */
|
|
19
|
+
connect(timeoutMs?: number): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Evaluate `fn(electron, ...args)` in the main process and return its (JSON-
|
|
22
|
+
* serializable) result. `awaitPromise` supports async callbacks. A throw in the
|
|
23
|
+
* main process becomes {@link ErrorCode.ELECTRON_MAIN_THREW}.
|
|
24
|
+
*/
|
|
25
|
+
executeMain(fnSource: string, args: unknown[], timeoutMs?: number): Promise<unknown>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=electronMainBridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electronMainBridge.d.ts","sourceRoot":"","sources":["../../src/lib/electronMainBridge.ts"],"names":[],"mappings":"AAwCA,qFAAqF;AACrF,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEzF,qBAAa,kBAAkB;;gBAYjB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAKtC,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAIzC;;;OAGG;IACG,aAAa,CAAC,SAAS,SAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1E,6DAA6D;IACvD,OAAO,CAAC,SAAS,SAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAqEpE;;;;OAIG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EAAE,EACf,SAAS,SAA6B,GACrC,OAAO,CAAC,OAAO,CAAC;IA6Bb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4H7B"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CDP bridge to an Electron app's **main-process** Node inspector, the
|
|
3
|
+
* transport behind `browser.electron.executeMain()`. The app is launched with
|
|
4
|
+
* `--inspect=<host>:<port>` (see Browser.launch's electron path); this connects
|
|
5
|
+
* to that inspector and evaluates user callbacks in the main process with the
|
|
6
|
+
* `electron` module injected as the first callback argument.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately tiny — no `@electron/fuses` dependency and no full CDP client. If
|
|
9
|
+
* the inspector isn't reachable (fuse disabled, wrong port), connect() fails with
|
|
10
|
+
* an actionable {@link ErrorCode.ELECTRON_MAIN_UNAVAILABLE}.
|
|
11
|
+
*/
|
|
12
|
+
import http from 'node:http';
|
|
13
|
+
import { WebSocket } from 'ws';
|
|
14
|
+
import { CraftdriverError, ErrorCode } from './errors.js';
|
|
15
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
16
|
+
const DEFAULT_EXECUTE_TIMEOUT_MS = 30_000;
|
|
17
|
+
/** Guard against a runaway return value flooding the socket (32 MB). */
|
|
18
|
+
const MAX_PAYLOAD_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
const INSPECTOR_CONNECT_POLL_MS = 100;
|
|
20
|
+
export class ElectronMainBridge {
|
|
21
|
+
#host;
|
|
22
|
+
#port;
|
|
23
|
+
#ws;
|
|
24
|
+
#nextId = 1;
|
|
25
|
+
#connected = false;
|
|
26
|
+
#onEvent;
|
|
27
|
+
#pending = new Map();
|
|
28
|
+
constructor(host, port) {
|
|
29
|
+
this.#host = host;
|
|
30
|
+
this.#port = port;
|
|
31
|
+
}
|
|
32
|
+
get isConnected() {
|
|
33
|
+
return this.#connected;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Register a listener for CDP event frames (those without an `id`, e.g.
|
|
37
|
+
* `Runtime.consoleAPICalled`). Used by the main-process log capture; set it
|
|
38
|
+
* before {@link enableRuntime} so the replayed backlog isn't missed.
|
|
39
|
+
*/
|
|
40
|
+
onEvent(listener) {
|
|
41
|
+
this.#onEvent = listener;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Enable the CDP `Runtime` domain so the inspector starts (and replays any
|
|
45
|
+
* buffered) `consoleAPICalled` / `exceptionThrown` events. Idempotent.
|
|
46
|
+
*/
|
|
47
|
+
async enableRuntime(timeoutMs = DEFAULT_EXECUTE_TIMEOUT_MS) {
|
|
48
|
+
await this.#send('Runtime.enable', {}, timeoutMs);
|
|
49
|
+
}
|
|
50
|
+
/** Fetch the inspector's WebSocket URL and connect to it. */
|
|
51
|
+
async connect(timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
52
|
+
if (this.#connected)
|
|
53
|
+
return;
|
|
54
|
+
const deadline = Date.now() + timeoutMs;
|
|
55
|
+
let targets;
|
|
56
|
+
let wsUrl;
|
|
57
|
+
let lastCause;
|
|
58
|
+
while (Date.now() < deadline) {
|
|
59
|
+
try {
|
|
60
|
+
targets = await this.#httpJson(`http://${this.#host}:${this.#port}/json/list`, Math.max(1, deadline - Date.now()));
|
|
61
|
+
wsUrl = targets.find((t) => t.webSocketDebuggerUrl)?.webSocketDebuggerUrl;
|
|
62
|
+
if (wsUrl)
|
|
63
|
+
break;
|
|
64
|
+
lastCause = new Error('inspector exposed no debug target yet');
|
|
65
|
+
}
|
|
66
|
+
catch (cause) {
|
|
67
|
+
lastCause = cause;
|
|
68
|
+
}
|
|
69
|
+
await delay(Math.min(INSPECTOR_CONNECT_POLL_MS, Math.max(0, deadline - Date.now())));
|
|
70
|
+
}
|
|
71
|
+
if (!wsUrl && !targets) {
|
|
72
|
+
throw this.#unavailable(`Could not reach the Electron main-process inspector at ${this.#host}:${this.#port}.`, lastCause);
|
|
73
|
+
}
|
|
74
|
+
if (!wsUrl) {
|
|
75
|
+
throw this.#unavailable(`The Electron main-process inspector at ${this.#host}:${this.#port} exposed no debug target.`, lastCause);
|
|
76
|
+
}
|
|
77
|
+
const wsTimeoutMs = Math.max(1, deadline - Date.now());
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
const ws = new WebSocket(wsUrl, { maxPayload: MAX_PAYLOAD_BYTES });
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
ws.terminate();
|
|
82
|
+
reject(this.#unavailable(`Timed out connecting to the main-process inspector after ${wsTimeoutMs}ms.`));
|
|
83
|
+
}, wsTimeoutMs);
|
|
84
|
+
ws.once('open', () => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
this.#ws = ws;
|
|
87
|
+
this.#connected = true;
|
|
88
|
+
ws.on('message', (data) => this.#onMessage(data));
|
|
89
|
+
ws.on('close', () => {
|
|
90
|
+
this.#connected = false;
|
|
91
|
+
this.#failAllPending(new Error('main-process inspector socket closed'));
|
|
92
|
+
});
|
|
93
|
+
ws.on('error', () => {
|
|
94
|
+
/* surfaced per-request via reject/close */
|
|
95
|
+
});
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
ws.once('error', (err) => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
reject(this.#unavailable('Failed to open the main-process inspector socket.', err));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Evaluate `fn(electron, ...args)` in the main process and return its (JSON-
|
|
106
|
+
* serializable) result. `awaitPromise` supports async callbacks. A throw in the
|
|
107
|
+
* main process becomes {@link ErrorCode.ELECTRON_MAIN_THREW}.
|
|
108
|
+
*/
|
|
109
|
+
async executeMain(fnSource, args, timeoutMs = DEFAULT_EXECUTE_TIMEOUT_MS) {
|
|
110
|
+
if (!this.#connected)
|
|
111
|
+
throw this.#unavailable('Main-process bridge is not connected.');
|
|
112
|
+
// Resolve the electron module inline via the inspector's CommandLineAPI
|
|
113
|
+
// `require` (needs includeCommandLineAPI) and pass it as the callback's first
|
|
114
|
+
// arg. Args ride as a JSON literal spread into the call; the caller validates
|
|
115
|
+
// serializability first (EVAL_BAD_ARG), matching browser.evaluate().
|
|
116
|
+
const expression = `(${fnSource})(require('electron'), ...${JSON.stringify(args)})`;
|
|
117
|
+
const res = await this.#send('Runtime.evaluate', {
|
|
118
|
+
expression,
|
|
119
|
+
awaitPromise: true, // resolve async callbacks before returning
|
|
120
|
+
returnByValue: true,
|
|
121
|
+
includeCommandLineAPI: true, // exposes `require` in the eval
|
|
122
|
+
}, timeoutMs);
|
|
123
|
+
if (res.exceptionDetails) {
|
|
124
|
+
const message = res.exceptionDetails.exception?.description ?? res.exceptionDetails.text ?? 'unknown error';
|
|
125
|
+
throw new CraftdriverError(ErrorCode.ELECTRON_MAIN_THREW, `electron.executeMain() callback threw in the main process: ${message}`, { detail: { host: this.#host, port: this.#port } });
|
|
126
|
+
}
|
|
127
|
+
return deserializeResult(res.result);
|
|
128
|
+
}
|
|
129
|
+
async close() {
|
|
130
|
+
this.#connected = false;
|
|
131
|
+
this.#failAllPending(new Error('main-process bridge closed'));
|
|
132
|
+
const ws = this.#ws;
|
|
133
|
+
this.#ws = undefined;
|
|
134
|
+
if (!ws)
|
|
135
|
+
return;
|
|
136
|
+
await new Promise((resolve) => {
|
|
137
|
+
ws.once('close', () => resolve());
|
|
138
|
+
ws.close();
|
|
139
|
+
// Don't hang teardown on a half-open socket.
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
ws.terminate();
|
|
142
|
+
resolve();
|
|
143
|
+
}, 1_000).unref?.();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
#send(method, params, timeoutMs) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const ws = this.#ws;
|
|
149
|
+
if (!ws) {
|
|
150
|
+
reject(this.#unavailable('Main-process bridge is not connected.'));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const id = this.#nextId++;
|
|
154
|
+
const timer = setTimeout(() => {
|
|
155
|
+
this.#pending.delete(id);
|
|
156
|
+
reject(new CraftdriverError(ErrorCode.ELECTRON_MAIN_UNAVAILABLE, `Main-process command "${method}" timed out after ${timeoutMs}ms.`));
|
|
157
|
+
}, timeoutMs);
|
|
158
|
+
this.#pending.set(id, { resolve, reject, timer });
|
|
159
|
+
ws.send(JSON.stringify({ id, method, params }), (err) => {
|
|
160
|
+
if (err) {
|
|
161
|
+
const p = this.#pending.get(id);
|
|
162
|
+
if (p) {
|
|
163
|
+
clearTimeout(p.timer);
|
|
164
|
+
this.#pending.delete(id);
|
|
165
|
+
}
|
|
166
|
+
reject(this.#unavailable(`Failed to send "${method}" to the main process.`, err));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
#onMessage(data) {
|
|
172
|
+
let msg;
|
|
173
|
+
try {
|
|
174
|
+
msg = JSON.parse(String(data));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return; // ignore non-JSON frames
|
|
178
|
+
}
|
|
179
|
+
if (typeof msg.id !== 'number') {
|
|
180
|
+
// An inspector event (e.g. Runtime.consoleAPICalled), not a command reply.
|
|
181
|
+
if (msg.method && this.#onEvent)
|
|
182
|
+
this.#onEvent(msg.method, msg.params ?? {});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const pending = this.#pending.get(msg.id);
|
|
186
|
+
if (!pending)
|
|
187
|
+
return;
|
|
188
|
+
clearTimeout(pending.timer);
|
|
189
|
+
this.#pending.delete(msg.id);
|
|
190
|
+
if (msg.error) {
|
|
191
|
+
pending.reject(new CraftdriverError(ErrorCode.ELECTRON_MAIN_UNAVAILABLE, `Main-process inspector error: ${msg.error.message ?? 'unknown'}`));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
pending.resolve(msg.result ?? {});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
#failAllPending(error) {
|
|
198
|
+
for (const { reject, timer } of this.#pending.values()) {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
reject(error);
|
|
201
|
+
}
|
|
202
|
+
this.#pending.clear();
|
|
203
|
+
}
|
|
204
|
+
#httpJson(url, timeoutMs) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const req = http.get(url, (res) => {
|
|
207
|
+
if (res.statusCode !== 200) {
|
|
208
|
+
res.resume();
|
|
209
|
+
reject(new Error(`inspector /json/list returned HTTP ${res.statusCode ?? 'unknown'}`));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let body = '';
|
|
213
|
+
res.on('data', (c) => (body += c));
|
|
214
|
+
res.on('end', () => {
|
|
215
|
+
try {
|
|
216
|
+
resolve(JSON.parse(body));
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
req.setTimeout(timeoutMs, () => req.destroy(new Error(`inspector /json/list timed out after ${timeoutMs}ms`)));
|
|
224
|
+
req.on('error', reject);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
#unavailable(message, cause) {
|
|
228
|
+
return new CraftdriverError(ErrorCode.ELECTRON_MAIN_UNAVAILABLE, message, {
|
|
229
|
+
cause,
|
|
230
|
+
hint: 'Enable main-process access with `electron: { mainProcess: true }`, and ensure the app ' +
|
|
231
|
+
'build keeps the EnableNodeCliInspectArguments fuse enabled (the default).',
|
|
232
|
+
detail: { host: this.#host, port: this.#port },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function delay(ms) {
|
|
237
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
238
|
+
}
|
|
239
|
+
function deserializeResult(result) {
|
|
240
|
+
if (!result || result.type === 'undefined')
|
|
241
|
+
return undefined;
|
|
242
|
+
if (result.subtype === 'null')
|
|
243
|
+
return null;
|
|
244
|
+
if (result.unserializableValue !== undefined) {
|
|
245
|
+
throw nonSerializableResult(result, result.unserializableValue);
|
|
246
|
+
}
|
|
247
|
+
if (result.type === 'string' || result.type === 'boolean')
|
|
248
|
+
return result.value;
|
|
249
|
+
if (result.type === 'number') {
|
|
250
|
+
if (typeof result.value === 'number' && Number.isFinite(result.value))
|
|
251
|
+
return result.value;
|
|
252
|
+
throw nonSerializableResult(result);
|
|
253
|
+
}
|
|
254
|
+
if (result.type === 'object' && result.value !== undefined)
|
|
255
|
+
return result.value;
|
|
256
|
+
throw nonSerializableResult(result);
|
|
257
|
+
}
|
|
258
|
+
function nonSerializableResult(result, detailValue) {
|
|
259
|
+
const returnedType = result.subtype ?? result.type;
|
|
260
|
+
const suffix = detailValue ? ` (${detailValue})` : '';
|
|
261
|
+
return new CraftdriverError(ErrorCode.EVAL_BAD_ARG, `executeMain() returned ${returnedType}${suffix}, which is not JSON-serializable. ` +
|
|
262
|
+
'Return a primitive, array, or plain object instead.', {
|
|
263
|
+
detail: {
|
|
264
|
+
returnedType,
|
|
265
|
+
description: result.description,
|
|
266
|
+
unserializableValue: result.unserializableValue,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=electronMainBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electronMainBridge.js","sourceRoot":"","sources":["../../src/lib/electronMainBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Normalized level for a main-process log entry. */
|
|
2
|
+
export type ElectronMainLogLevel = 'log' | 'debug' | 'info' | 'warn' | 'error';
|
|
3
|
+
export interface ElectronMainLog {
|
|
4
|
+
/** `console` for a `console.*` call; `exception` for an uncaught error the inspector reported. */
|
|
5
|
+
type: 'console' | 'exception';
|
|
6
|
+
/** Normalized level. `error` covers `console.error`/`console.assert` and every `exception`. */
|
|
7
|
+
level: ElectronMainLogLevel;
|
|
8
|
+
/** Human-readable, space-joined message text (arguments stringified like the console does). */
|
|
9
|
+
text: string;
|
|
10
|
+
/** Best-effort deserialized arguments: primitives by value, complex values as their description string. */
|
|
11
|
+
args: unknown[];
|
|
12
|
+
/** When the inspector reported the entry. */
|
|
13
|
+
timestamp: Date;
|
|
14
|
+
/** Raw CDP console method for `console` entries (`log`, `warning`, `error`, `trace`, …); absent for exceptions. */
|
|
15
|
+
method?: string;
|
|
16
|
+
/** Formatted call stack, when the inspector provided one. */
|
|
17
|
+
stackTrace?: string;
|
|
18
|
+
}
|
|
19
|
+
export type ElectronMainLogHandler = (log: ElectronMainLog) => void;
|
|
20
|
+
/** Shapes of the two CDP Runtime events we ingest (only the fields we read). */
|
|
21
|
+
interface CdpRemoteObject {
|
|
22
|
+
type: string;
|
|
23
|
+
subtype?: string;
|
|
24
|
+
value?: unknown;
|
|
25
|
+
description?: string;
|
|
26
|
+
}
|
|
27
|
+
interface CdpStackTrace {
|
|
28
|
+
callFrames?: Array<{
|
|
29
|
+
functionName?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
lineNumber?: number;
|
|
32
|
+
columnNumber?: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface ConsoleApiCalledParams {
|
|
36
|
+
type?: string;
|
|
37
|
+
args?: CdpRemoteObject[];
|
|
38
|
+
timestamp?: number;
|
|
39
|
+
stackTrace?: CdpStackTrace;
|
|
40
|
+
}
|
|
41
|
+
export interface ExceptionThrownParams {
|
|
42
|
+
timestamp?: number;
|
|
43
|
+
exceptionDetails?: {
|
|
44
|
+
text?: string;
|
|
45
|
+
exception?: {
|
|
46
|
+
description?: string;
|
|
47
|
+
value?: unknown;
|
|
48
|
+
};
|
|
49
|
+
stackTrace?: CdpStackTrace;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Buffers and dispatches main-process log entries. Reachable via
|
|
54
|
+
* `browser.electron.mainLogs`; its public surface mirrors the renderer
|
|
55
|
+
* {@link LogMonitor} so console monitoring reads the same in both processes.
|
|
56
|
+
*/
|
|
57
|
+
export declare class ElectronMainLogMonitor {
|
|
58
|
+
#private;
|
|
59
|
+
/** Feed a CDP `Runtime.consoleAPICalled` event. */
|
|
60
|
+
ingestConsoleEvent(params: ConsoleApiCalledParams): void;
|
|
61
|
+
/** Feed a CDP `Runtime.exceptionThrown` event (best-effort in Electron main). */
|
|
62
|
+
ingestExceptionEvent(params: ExceptionThrownParams): void;
|
|
63
|
+
/** All captured entries (console + exceptions), oldest first. */
|
|
64
|
+
getLogs(): ElectronMainLog[];
|
|
65
|
+
/** Only error-level entries: `console.error`/`console.assert` and every reported exception. */
|
|
66
|
+
getErrors(): ElectronMainLog[];
|
|
67
|
+
/** Drop everything captured so far. */
|
|
68
|
+
clearLogs(): void;
|
|
69
|
+
/** Subscribe to every entry. Returns an unsubscribe function. */
|
|
70
|
+
onLog(handler: ElectronMainLogHandler): () => void;
|
|
71
|
+
/** Subscribe to error-level entries only. Returns an unsubscribe function. */
|
|
72
|
+
onError(handler: ElectronMainLogHandler): () => void;
|
|
73
|
+
/** Resolve with the first entry matching `predicate` (checked against already-buffered entries first). */
|
|
74
|
+
waitForLog(predicate: (log: ElectronMainLog) => boolean, timeout?: number): Promise<ElectronMainLog>;
|
|
75
|
+
/** Resolve with the first error-level entry matching `predicate` (default: any error). */
|
|
76
|
+
waitForError(predicate?: (log: ElectronMainLog) => boolean, timeout?: number): Promise<ElectronMainLog>;
|
|
77
|
+
/** Throw if any error-level entry was captured — a fail-fast assertion for tests. */
|
|
78
|
+
assertNoErrors(): void;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=electronMainLogs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"electronMainLogs.d.ts","sourceRoot":"","sources":["../../src/lib/electronMainLogs.ts"],"names":[],"mappings":"AAqBA,qDAAqD;AACrD,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,kGAAkG;IAClG,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAC9B,+FAA+F;IAC/F,KAAK,EAAE,oBAAoB,CAAC;IAC5B,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,2GAA2G;IAC3G,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,6CAA6C;IAC7C,SAAS,EAAE,IAAI,CAAC;IAChB,mHAAmH;IACnH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;AAEpE,gFAAgF;AAChF,UAAU,eAAe;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE;AACnG,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzG;AACD,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,aAAa,CAAC;CAC5B;AACD,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;QACtD,UAAU,CAAC,EAAE,aAAa,CAAC;KAC5B,CAAC;CACH;AA8CD;;;;GAIG;AACH,qBAAa,sBAAsB;;IAKjC,mDAAmD;IACnD,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAaxD,iFAAiF;IACjF,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI;IAazD,iEAAiE;IACjE,OAAO,IAAI,eAAe,EAAE;IAI5B,+FAA+F;IAC/F,SAAS,IAAI,eAAe,EAAE;IAI9B,uCAAuC;IACvC,SAAS,IAAI,IAAI;IAIjB,iEAAiE;IACjE,KAAK,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAKlD,8EAA8E;IAC9E,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAKpD,0GAA0G;IAC1G,UAAU,CACR,SAAS,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,EAC5C,OAAO,SAAgC,GACtC,OAAO,CAAC,eAAe,CAAC;IAI3B,0FAA0F;IAC1F,YAAY,CACV,SAAS,GAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAoB,EACzD,OAAO,SAAgC,GACtC,OAAO,CAAC,eAAe,CAAC;IAI3B,qFAAqF;IACrF,cAAc,IAAI,IAAI;CAoDvB"}
|