@deeeed/metamask-harness 0.77.0 → 0.79.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +8 -5
  3. package/adapters/extension/build-handoff.cjs +2 -2
  4. package/adapters/extension/ensure-browser.sh +10 -12
  5. package/adapters/extension/inject.mjs +10 -7
  6. package/adapters/extension/launch-browser.cjs +7 -10
  7. package/adapters/extension/live.sh +1 -2
  8. package/adapters/extension/reattach.sh +3 -3
  9. package/adapters/extension/sidepanel-toggle.sh +4 -4
  10. package/adapters/extension/wallet-fixture-state.cjs +2 -2
  11. package/adapters/manifest.json +0 -24
  12. package/adapters/shared/ensure-runner-deps.sh +2 -2
  13. package/adapters/shared/harness-source-fingerprint.mjs +16 -2
  14. package/adapters/shared/runner-deps-ready.mjs +1 -1
  15. package/adapters/terminal/launch.mjs +2 -2
  16. package/adapters/terminal/lib/cdp-client.mjs +1 -1
  17. package/adapters/terminal/wallet-host.mjs +1 -1
  18. package/bin/mm-harness +2 -2
  19. package/dist/adapters/core/run.js +1 -1
  20. package/dist/adapters/core/surface.js +6 -4
  21. package/dist/adapters/extension/doctor-checks.js +2 -2
  22. package/dist/adapters/extension/launched-browser.js +1 -1
  23. package/dist/adapters/extension/runtime-decision.js +1 -1
  24. package/dist/adapters/extension/runtime.js +4 -4
  25. package/dist/adapters/extension/surface.js +5 -3
  26. package/dist/adapters/extension/validation-process-ownership.js +1 -1
  27. package/dist/adapters/mobile/prepare.js +1 -1
  28. package/dist/adapters/mobile/run.js +1 -1
  29. package/dist/adapters/mobile/runtime-decision.js +3 -3
  30. package/dist/adapters/mobile/surface.js +5 -3
  31. package/dist/adapters/mobile/video-recorder.js +1 -1
  32. package/dist/adapters/registry.js +2 -14
  33. package/dist/adapters/terminal/surface.js +5 -3
  34. package/dist/checkout-lock.js +1 -1
  35. package/dist/command-journal.js +1 -1
  36. package/dist/commands/launch/extension.js +2 -2
  37. package/dist/commands/prepare.js +2 -2
  38. package/dist/commands/shared.js +1 -1
  39. package/dist/discovery.js +1 -1
  40. package/dist/doctor.js +1 -1
  41. package/dist/harness.js +3 -3
  42. package/dist/paths.js +15 -15
  43. package/dist/recipe-assistance-context.js +1 -1
  44. package/dist/review/knowledge.js +1 -1
  45. package/dist/run-recording.js +1 -1
  46. package/dist/task-view.js +1 -1
  47. package/docs/CONTRIBUTING.md +2 -2
  48. package/docs/cli-contract.md +6 -17
  49. package/library/actions/extension/platform/cdp.mjs +5 -7
  50. package/library/actions/terminal/platform/page.mjs +2 -2
  51. package/package.json +6 -4
  52. package/adapters/extension/lib/chrome-args.cjs +0 -365
  53. package/adapters/extension/lib/extension-id.cjs +0 -36
  54. package/adapters/extension/lib/macos-focus.cjs +0 -108
  55. package/adapters/extension/lib/playwright-cdp.cjs +0 -50
  56. package/adapters/extension/lib/validation-launch-supervisor.cjs +0 -292
  57. package/adapters/extension/lib/validation-process-ownership.cjs +0 -125
  58. package/adapters/shared/browser-cdp.cjs +0 -542
  59. package/adapters/shared/browser-resolver.cjs +0 -1007
@@ -1,365 +0,0 @@
1
- 'use strict';
2
- const crypto = require('node:crypto');
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const RUNTIME_IDENTITY_FILENAME = 'extension-runtime-identity.json';
7
- const RUNTIME_NONCE_PREFIX = '--mm-harness-runtime-nonce=';
8
- const DETACHED_LAUNCH_UNPROVEN_FILENAME = '.mm-harness-detached-launch-unproven';
9
- // chrome-args.cjs — single source of truth for the remote-debugging launch flags
10
- // shared by BOTH extension launchers: the fresh spawn (launch-browser.cjs) and the
11
- // reopen path (ensure-browser.sh → reopen-browser.sh). Keeping the debug-port trio
12
- // here means `--remote-allow-origins` can never drift away from the port it scopes:
13
- // the two are declared once and always travel together. Without the allow-origins
14
- // entry Chrome 403s every browser DevTools WebSocket on the debug port and
15
- // `mm-harness debug` cannot attach a visible console.
16
-
17
- // The exact origin allowed is the locally-served bundled DevTools frontend, which
18
- // Chrome serves at http://127.0.0.1:<cdpPort>. Scoped to that origin — never `*`.
19
- function remoteDebuggingArgs(cdpPort) {
20
- const port = Number(cdpPort);
21
- if (!Number.isInteger(port) || port <= 0) {
22
- throw new Error(`remoteDebuggingArgs: invalid cdp port: ${cdpPort}`);
23
- }
24
- return [
25
- '--remote-debugging-address=127.0.0.1',
26
- `--remote-debugging-port=${port}`,
27
- `--remote-allow-origins=http://127.0.0.1:${port}`,
28
- ];
29
- }
30
-
31
- // An isolated automation profile must never ask for or write credentials in the
32
- // operator's macOS login keychain. Chromium's mock keychain keeps profile-local
33
- // encryption deterministic and prevents modal prompts from blocking startup.
34
- function isolatedProfileArgs(platform = process.platform) {
35
- return platform === 'darwin' ? ['--use-mock-keychain'] : [];
36
- }
37
-
38
- // Keep requestAnimationFrame, transitions, and input hit testing alive when the
39
- // slot-owned browser is occluded by the operator's other windows.
40
- function automationRuntimeArgs() {
41
- return [
42
- '--disable-background-timer-throttling',
43
- '--disable-backgrounding-occluded-windows',
44
- '--disable-renderer-backgrounding',
45
- ];
46
- }
47
-
48
- function createRuntimeIdentityNonce() {
49
- return crypto.randomBytes(32).toString('hex');
50
- }
51
-
52
- function runtimeIdentityArgs(nonce) {
53
- if (!/^[a-f0-9]{64}$/u.test(nonce)) {
54
- throw new Error('runtimeIdentityArgs: nonce must be 32 bytes encoded as lowercase hex');
55
- }
56
- return ['--enable-automation', `${RUNTIME_NONCE_PREFIX}${nonce}`];
57
- }
58
-
59
- function runtimeIdentityPath(runtimeDir) {
60
- return path.join(path.resolve(runtimeDir), RUNTIME_IDENTITY_FILENAME);
61
- }
62
-
63
- function detachedLaunchUnprovenPath(profile) {
64
- return path.join(path.resolve(profile), DETACHED_LAUNCH_UNPROVEN_FILENAME);
65
- }
66
-
67
- function validationPortQuarantineRoot() {
68
- if (typeof process.getuid !== 'function') {
69
- throw new Error('Extension validation port quarantine requires a POSIX user identity.');
70
- }
71
- const uid = process.getuid();
72
- const root = path.join(fs.realpathSync('/tmp'), `mm-harness-extension-validation-${uid}`);
73
- try {
74
- fs.mkdirSync(root, { mode: 0o700 });
75
- } catch (error) {
76
- if (error.code !== 'EEXIST') throw error;
77
- }
78
- const stat = fs.lstatSync(root);
79
- if (
80
- stat.isSymbolicLink() ||
81
- !stat.isDirectory() ||
82
- stat.uid !== uid ||
83
- (stat.mode & 0o777) !== 0o700
84
- ) {
85
- throw new Error(`Extension validation quarantine root is not an owner-only directory: ${root}`);
86
- }
87
- return root;
88
- }
89
-
90
- function validationPortQuarantinePath(port) {
91
- const numericPort = Number(port);
92
- if (!Number.isInteger(numericPort) || numericPort <= 0 || numericPort > 65535) {
93
- throw new Error(`validationPortQuarantinePath: invalid cdp port: ${port}`);
94
- }
95
- return path.join(validationPortQuarantineRoot(), `port-${numericPort}.json`);
96
- }
97
-
98
- function readValidationPortQuarantine(port) {
99
- const destination = validationPortQuarantinePath(port);
100
- let descriptor;
101
- try {
102
- descriptor = fs.openSync(destination, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
103
- const stat = fs.fstatSync(descriptor);
104
- if (
105
- !stat.isFile() ||
106
- stat.uid !== process.getuid() ||
107
- stat.size <= 0 ||
108
- stat.size > 4_096 ||
109
- (stat.mode & 0o777) !== 0o600
110
- ) {
111
- return { path: destination };
112
- }
113
- const value = JSON.parse(fs.readFileSync(descriptor, 'utf8'));
114
- if (
115
- value.schemaVersion !== 1 ||
116
- value.port !== Number(port) ||
117
- typeof value.profile !== 'string' ||
118
- (value.lease !== undefined && !/^[a-f0-9]{64}$/u.test(value.lease))
119
- ) {
120
- return { path: destination };
121
- }
122
- return {
123
- path: destination,
124
- profile: value.profile,
125
- ...(value.lease === undefined ? {} : { lease: value.lease }),
126
- };
127
- } catch (error) {
128
- if (error.code === 'ENOENT') return null;
129
- return { path: destination };
130
- } finally {
131
- if (descriptor !== undefined) fs.closeSync(descriptor);
132
- }
133
- }
134
-
135
- function writeValidationPortQuarantine(port, profile, reason, allowExisting, lease) {
136
- const destination = validationPortQuarantinePath(port);
137
- const pending = path.join(
138
- path.dirname(destination),
139
- `.port-${Number(port)}.${process.pid}.${crypto.randomBytes(16).toString('hex')}.pending`,
140
- );
141
- let descriptor;
142
- try {
143
- descriptor = fs.openSync(
144
- pending,
145
- fs.constants.O_WRONLY |
146
- fs.constants.O_CREAT |
147
- fs.constants.O_EXCL |
148
- fs.constants.O_NOFOLLOW,
149
- 0o600,
150
- );
151
- fs.fchmodSync(descriptor, 0o600);
152
- fs.writeFileSync(descriptor, `${JSON.stringify({
153
- schemaVersion: 1,
154
- port: Number(port),
155
- profile: path.resolve(profile),
156
- reason: String(reason).slice(0, 1_024),
157
- ...(lease === undefined ? {} : { lease }),
158
- })}\n`, 'utf8');
159
- fs.fsyncSync(descriptor);
160
- fs.closeSync(descriptor);
161
- descriptor = undefined;
162
- fs.linkSync(pending, destination);
163
- return destination;
164
- } catch (error) {
165
- if (allowExisting && error.code === 'EEXIST') return destination;
166
- throw error;
167
- } finally {
168
- if (descriptor !== undefined) {
169
- try {
170
- fs.closeSync(descriptor);
171
- } catch {}
172
- }
173
- try {
174
- fs.unlinkSync(pending);
175
- } catch (error) {
176
- if (error.code !== 'ENOENT') throw error;
177
- }
178
- }
179
- }
180
-
181
- function markValidationPortLaunchUnproven(port, profile, lease) {
182
- if (lease !== undefined && !/^[a-f0-9]{64}$/u.test(lease)) {
183
- throw new Error('markValidationPortLaunchUnproven: invalid lease');
184
- }
185
- return writeValidationPortQuarantine(
186
- port,
187
- profile,
188
- 'A detached browser launch was submitted but ownership was not proven.',
189
- false,
190
- lease,
191
- );
192
- }
193
-
194
- function quarantineValidationPort(port, profile, reason) {
195
- return writeValidationPortQuarantine(port, profile, reason, true, undefined);
196
- }
197
-
198
- function clearValidationPortQuarantine(port, profile, lease) {
199
- const quarantine = readValidationPortQuarantine(port);
200
- if (!quarantine) return;
201
- if (
202
- quarantine.profile !== path.resolve(profile) ||
203
- (quarantine.lease !== undefined && quarantine.lease !== lease)
204
- ) {
205
- throw new Error(`Extension validation port ${port} is quarantined for another or invalid profile.`);
206
- }
207
- fs.unlinkSync(quarantine.path);
208
- }
209
-
210
- function shellQuote(value) {
211
- return `'${String(value).replaceAll("'", `'"'"'`)}'`;
212
- }
213
-
214
- function validationLaunchQuarantineError(port, ownerProfile, requestedProfile) {
215
- const portMarker = validationPortQuarantinePath(port);
216
- const profiles = [...new Set([ownerProfile, requestedProfile]
217
- .filter((profile) => typeof profile === 'string')
218
- .map((profile) => path.resolve(profile)))];
219
- const markers = [
220
- ...profiles.map(detachedLaunchUnprovenPath),
221
- portMarker,
222
- ];
223
- const profileText = profiles.length > 0 ? ` or profile ${profiles.join(' or ')}` : '';
224
- return new Error(
225
- `Refusing to launch on CDP port ${port} because its detached browser state is quarantined. ` +
226
- `Next: confirm no browser uses port ${port}${profileText}, then run: rm -- ${markers.map(shellQuote).join(' ')}`,
227
- );
228
- }
229
-
230
- function writeDetachedLaunchMarker(profile, reason, allowExisting) {
231
- const destination = detachedLaunchUnprovenPath(profile);
232
- fs.mkdirSync(path.dirname(destination), { recursive: true });
233
- let descriptor;
234
- try {
235
- descriptor = fs.openSync(
236
- destination,
237
- fs.constants.O_WRONLY |
238
- fs.constants.O_CREAT |
239
- fs.constants.O_EXCL |
240
- fs.constants.O_NOFOLLOW,
241
- 0o600,
242
- );
243
- fs.fchmodSync(descriptor, 0o600);
244
- fs.writeFileSync(descriptor, `${reason}\n`, 'utf8');
245
- fs.fsyncSync(descriptor);
246
- fs.closeSync(descriptor);
247
- return destination;
248
- } catch (error) {
249
- if (descriptor !== undefined) {
250
- try {
251
- fs.closeSync(descriptor);
252
- } catch {}
253
- }
254
- if (allowExisting && error.code === 'EEXIST') return destination;
255
- throw error;
256
- }
257
- }
258
-
259
- function markDetachedLaunchUnproven(profile) {
260
- return writeDetachedLaunchMarker(
261
- profile,
262
- 'A detached browser launch was submitted but ownership was not proven.',
263
- false,
264
- );
265
- }
266
-
267
- function quarantineDetachedLaunch(profile, reason) {
268
- return writeDetachedLaunchMarker(profile, reason, true);
269
- }
270
-
271
- function hasDetachedLaunchUnproven(profile) {
272
- return fs.lstatSync(detachedLaunchUnprovenPath(profile), { throwIfNoEntry: false }) !== undefined;
273
- }
274
-
275
- function clearDetachedLaunchUnproven(profile) {
276
- const destination = detachedLaunchUnprovenPath(profile);
277
- const stat = fs.lstatSync(destination, { throwIfNoEntry: false });
278
- if (!stat) return;
279
- if (stat.isSymbolicLink() || !stat.isFile()) {
280
- throw new Error(`Detached launch marker is not a regular file: ${destination}`);
281
- }
282
- fs.unlinkSync(destination);
283
- }
284
-
285
- function removeRuntimeIdentity(runtimeDir) {
286
- fs.rmSync(runtimeIdentityPath(runtimeDir), { force: true });
287
- }
288
-
289
- function writeRuntimeIdentity(runtimeDir, identity) {
290
- assertRuntimeIdentity(identity);
291
- const destination = runtimeIdentityPath(runtimeDir);
292
- const directory = path.dirname(destination);
293
- fs.mkdirSync(directory, { recursive: true });
294
- const temporary = path.join(
295
- directory,
296
- `.${RUNTIME_IDENTITY_FILENAME}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`,
297
- );
298
- let descriptor;
299
- try {
300
- descriptor = fs.openSync(
301
- temporary,
302
- fs.constants.O_WRONLY |
303
- fs.constants.O_CREAT |
304
- fs.constants.O_EXCL |
305
- fs.constants.O_NOFOLLOW,
306
- 0o600,
307
- );
308
- fs.fchmodSync(descriptor, 0o600);
309
- fs.writeFileSync(descriptor, `${JSON.stringify(identity)}\n`, 'utf8');
310
- fs.fsyncSync(descriptor);
311
- fs.closeSync(descriptor);
312
- descriptor = undefined;
313
- fs.renameSync(temporary, destination);
314
- } catch (error) {
315
- if (descriptor !== undefined) {
316
- try {
317
- fs.closeSync(descriptor);
318
- } catch {}
319
- }
320
- fs.rmSync(temporary, { force: true });
321
- throw error;
322
- }
323
- }
324
-
325
- function assertRuntimeIdentity(identity) {
326
- if (
327
- !identity ||
328
- !Number.isInteger(identity.port) ||
329
- identity.port <= 0 ||
330
- identity.port > 65535 ||
331
- !Number.isInteger(identity.pid) ||
332
- identity.pid <= 0 ||
333
- !Number.isInteger(identity.startedAt) ||
334
- identity.startedAt <= 0 ||
335
- !/^[a-f0-9]{64}$/u.test(identity.nonce)
336
- ) {
337
- throw new Error('writeRuntimeIdentity: invalid runtime identity');
338
- }
339
- }
340
-
341
- module.exports = {
342
- DETACHED_LAUNCH_UNPROVEN_FILENAME,
343
- RUNTIME_IDENTITY_FILENAME,
344
- RUNTIME_NONCE_PREFIX,
345
- automationRuntimeArgs,
346
- clearDetachedLaunchUnproven,
347
- clearValidationPortQuarantine,
348
- createRuntimeIdentityNonce,
349
- detachedLaunchUnprovenPath,
350
- hasDetachedLaunchUnproven,
351
- isolatedProfileArgs,
352
- markDetachedLaunchUnproven,
353
- markValidationPortLaunchUnproven,
354
- quarantineDetachedLaunch,
355
- quarantineValidationPort,
356
- readValidationPortQuarantine,
357
- remoteDebuggingArgs,
358
- removeRuntimeIdentity,
359
- runtimeIdentityArgs,
360
- runtimeIdentityPath,
361
- shellQuote,
362
- validationLaunchQuarantineError,
363
- validationPortQuarantinePath,
364
- writeRuntimeIdentity,
365
- };
@@ -1,36 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('node:crypto');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- // Chromium extension IDs encode each SHA-256 nibble as a-p.
8
- const CHROMIUM_EXTENSION_ID_ALPHABET = 'abcdefghijklmnop';
9
-
10
- function extensionIdFromManifestKey(keyBase64) {
11
- if (typeof keyBase64 !== 'string' || !keyBase64) return '';
12
- const digest = crypto.createHash('sha256').update(Buffer.from(keyBase64, 'base64')).digest();
13
- return [...digest.subarray(0, 16)]
14
- .map((byte) => `${CHROMIUM_EXTENSION_ID_ALPHABET[byte >> 4]}${CHROMIUM_EXTENSION_ID_ALPHABET[byte & 0x0f]}`)
15
- .join('');
16
- }
17
-
18
- function extensionIdFromManifestFile(manifestPath) {
19
- try {
20
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
21
- return extensionIdFromManifestKey(manifest.key);
22
- } catch {
23
- return '';
24
- }
25
- }
26
-
27
- function extensionIdFromExtensionDir(extensionDir) {
28
- return extensionIdFromManifestFile(path.join(extensionDir, 'manifest.json'));
29
- }
30
-
31
- module.exports = {
32
- CHROMIUM_EXTENSION_ID_ALPHABET,
33
- extensionIdFromManifestKey,
34
- extensionIdFromManifestFile,
35
- extensionIdFromExtensionDir,
36
- };
@@ -1,108 +0,0 @@
1
- 'use strict';
2
-
3
- // macOS focus helpers for launchers that start a headed browser in the
4
- // background. One owner (the launcher) and one decision: after the launch
5
- // settles, if the frontmost process is one of OUR browser pids, give the front
6
- // back to the app that had it, once, by pid. Anything else in front (the
7
- // operator's own Chrome included, whatever its bundle) is left alone. A restore
8
- // only activates an already-running app (never `open -a`, which can launch).
9
- // MM_HARNESS_FOCUS_HOLD=0 makes every function here a no-op (tests).
10
-
11
- const { execFileSync } = require('node:child_process');
12
-
13
- // Upper bound for one Launch Services query or activation.
14
- const LS_TIMEOUT_MS = 2000;
15
- // Activates a running app by pid; prints "missing" for a pid with no app.
16
- const ACTIVATE_BY_PID = [
17
- 'function run(argv) {',
18
- " ObjC.import('AppKit');",
19
- ' const app = $.NSRunningApplication.runningApplicationWithProcessIdentifier(Number(argv[0]));',
20
- " if (!app || app.isNil()) return 'missing';",
21
- " return app.activateWithOptions($.NSApplicationActivateIgnoringOtherApps) ? 'ok' : 'failed';",
22
- '}',
23
- ].join('\n');
24
-
25
- function macFocusDisabled() {
26
- return process.platform !== 'darwin' ||
27
- process.env.MM_HARNESS_FOCUS_HOLD === '0' ||
28
- process.env.MM_HARNESS_FOCUS_BROWSER === '1';
29
- }
30
-
31
- // At most one restore per launcher process.
32
- let restored = false;
33
-
34
- function lsappinfo(args) {
35
- return execFileSync('lsappinfo', args, {
36
- encoding: 'utf8',
37
- env: { ...process.env },
38
- stdio: ['ignore', 'pipe', 'ignore'],
39
- timeout: LS_TIMEOUT_MS,
40
- });
41
- }
42
-
43
- function parseLsappinfo(info) {
44
- const pid = Number.parseInt((info.match(/\bpid\s*=\s*(\d+)/u) || info.match(/"pid"=(\d+)/u) || [])[1], 10);
45
- const bundlePath = (info.match(/bundle path="([^"]+)"/u) || info.match(/"LSBundlePath"="([^"]+)"/u) || [])[1];
46
- const name = (info.match(/^"([^"]+)"/mu) || info.match(/"LSDisplayName"="([^"]+)"/u) || [])[1];
47
- if (!Number.isInteger(pid) || pid <= 0) return null;
48
- return { pid, bundlePath: bundlePath || null, name: name || bundlePath || `pid ${pid}` };
49
- }
50
-
51
- // The frontmost app as { pid, bundlePath, name }, or null when unknown.
52
- function captureMacFrontmost() {
53
- if (macFocusDisabled()) return null;
54
- try {
55
- const asn = lsappinfo(['front']).trim();
56
- return asn ? parseLsappinfo(lsappinfo(['info', asn])) : null;
57
- } catch {
58
- // Best effort: lsappinfo missing, timed out, or the app exited.
59
- return null;
60
- }
61
- }
62
-
63
- function macFrontmostPid() {
64
- return captureMacFrontmost()?.pid ?? null;
65
- }
66
-
67
- // Bring an already-running app to the front by pid. Never launches anything.
68
- function activateMacAppByPid(pid) {
69
- if (macFocusDisabled() || !Number.isInteger(pid) || pid <= 0) return false;
70
- try {
71
- return execFileSync('osascript', ['-l', 'JavaScript', '-e', ACTIVATE_BY_PID, String(pid)], {
72
- encoding: 'utf8',
73
- env: { ...process.env },
74
- stdio: ['ignore', 'pipe', 'ignore'],
75
- timeout: LS_TIMEOUT_MS,
76
- }).trim() === 'ok';
77
- } catch {
78
- // Best effort: osascript failed or timed out; the operator keeps whatever is in front.
79
- return false;
80
- }
81
- }
82
-
83
- // One check, at most one restore: 'restored' when our browser had the front
84
- // and `previous` got it back, 'kept' when anything else is in front (left as
85
- // is), 'off' when focus handling is disabled or there is nothing to restore.
86
- // A click on the slot window right after launch can therefore be undone once.
87
- function restoreFrontmostIfOurs(previous, ourPids) {
88
- if (macFocusDisabled() || restored || !Number.isInteger(previous?.pid) || previous.pid <= 0) return 'off';
89
- const ours = new Set((ourPids || []).filter((pid) => Number.isInteger(pid) && pid > 0));
90
- if (ours.size === 0 || ours.has(previous.pid)) return 'off';
91
- const front = macFrontmostPid();
92
- if (front === null || !ours.has(front)) return 'kept';
93
- restored = true;
94
- return activateMacAppByPid(previous.pid) ? 'restored' : 'kept';
95
- }
96
-
97
- function macBackgroundOpenArgs(application, chromeArgs) {
98
- return ['-g', '-n', '-a', application, '--args', ...chromeArgs];
99
- }
100
-
101
- module.exports = {
102
- activateMacAppByPid,
103
- captureMacFrontmost,
104
- macBackgroundOpenArgs,
105
- macFocusDisabled,
106
- macFrontmostPid,
107
- restoreFrontmostIfOurs,
108
- };
@@ -1,50 +0,0 @@
1
- 'use strict';
2
-
3
- const path = require('node:path');
4
- const { createRequire } = require('node:module');
5
-
6
- function loadChromium() {
7
- const runtimeRequire = createRequire(path.join(process.cwd(), 'package.json'));
8
- try {
9
- return runtimeRequire('@playwright/test').chromium;
10
- } catch {
11
- return runtimeRequire('playwright').chromium;
12
- }
13
- }
14
-
15
- async function connectBrowserViaCdp(endpoint) {
16
- return loadChromium().connectOverCDP(endpoint);
17
- }
18
-
19
- function buildEvaluationExpression(callbackOrExpression, argument) {
20
- if (typeof callbackOrExpression === 'string') return callbackOrExpression;
21
- if (typeof callbackOrExpression !== 'function') {
22
- throw new TypeError('CDP evaluation requires a function or expression string.');
23
- }
24
- return argument === undefined
25
- ? `(${callbackOrExpression.toString()})()`
26
- : `(${callbackOrExpression.toString()})(${JSON.stringify(argument)})`;
27
- }
28
-
29
- async function evaluatePageViaCdp(page, callbackOrExpression, argument) {
30
- const session = await page.context().newCDPSession(page);
31
- try {
32
- const response = await session.send('Runtime.evaluate', {
33
- expression: buildEvaluationExpression(callbackOrExpression, argument),
34
- awaitPromise: true,
35
- returnByValue: true,
36
- });
37
- if (response.exceptionDetails) {
38
- const detail =
39
- response.exceptionDetails.exception?.description ??
40
- response.exceptionDetails.text ??
41
- 'unknown evaluation failure';
42
- throw new Error(`CDP evaluation failed: ${detail}`);
43
- }
44
- return response.result?.value;
45
- } finally {
46
- await session.detach().catch(() => {});
47
- }
48
- }
49
-
50
- module.exports = { buildEvaluationExpression, connectBrowserViaCdp, evaluatePageViaCdp };