cdp-toolkit 2.1.0 → 2.3.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/dist/index.js CHANGED
@@ -1,9 +1,20 @@
1
+ var __esm = (fn, res, err) => () => {
2
+ if (fn)
3
+ try {
4
+ res = fn(fn = 0);
5
+ } catch (e) {
6
+ err = [e];
7
+ }
8
+ if (err)
9
+ throw err[0];
10
+ return res;
11
+ };
12
+
1
13
  // src/leases.ts
2
14
  import { AsyncLocalStorage } from "node:async_hooks";
3
15
  import { randomBytes } from "node:crypto";
4
16
  import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
5
17
  import { join } from "node:path";
6
- var DEFAULT_LEASE_TTL_MS = 900000;
7
18
  function leaseDir() {
8
19
  return process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
9
20
  }
@@ -11,7 +22,6 @@ function leaseTtlMs() {
11
22
  const raw = Number(process.env.CDP_LEASE_TTL_MS);
12
23
  return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_LEASE_TTL_MS;
13
24
  }
14
- var longLived = false;
15
25
  function markLongLivedProcess(value = true) {
16
26
  longLived = value;
17
27
  }
@@ -49,17 +59,6 @@ function tokenParts(token) {
49
59
  return;
50
60
  return { backend, targetId, nonce };
51
61
  }
52
-
53
- class LeaseConflictError extends Error {
54
- targetId;
55
- holder;
56
- constructor(message, targetId, holder) {
57
- super(message);
58
- this.targetId = targetId;
59
- this.holder = holder;
60
- this.name = "LeaseConflictError";
61
- }
62
- }
63
62
  async function readLease(backend, targetId) {
64
63
  let raw;
65
64
  try {
@@ -150,7 +149,6 @@ async function releaseLeaseFor(backend, targetId) {
150
149
  });
151
150
  return { released: true };
152
151
  }
153
- var leaseScope = new AsyncLocalStorage;
154
152
  function withLeaseScope(lease, fn) {
155
153
  return leaseScope.run(lease, fn);
156
154
  }
@@ -217,7 +215,6 @@ async function assertLeaseOk(backend, targetId, ctx = {}) {
217
215
  }
218
216
  await touchLease(held, now);
219
217
  }
220
- var LEASE_FILE_RE = /^lease-(chrome|firefox)-(.+)\.json$/;
221
218
  async function listLeases(opts = {}) {
222
219
  const now = opts.now ?? Date.now();
223
220
  let names = [];
@@ -275,6 +272,21 @@ async function listLeases(opts = {}) {
275
272
  }
276
273
  return out;
277
274
  }
275
+ var DEFAULT_LEASE_TTL_MS = 900000, longLived = false, LeaseConflictError, leaseScope, LEASE_FILE_RE;
276
+ var init_leases = __esm(() => {
277
+ LeaseConflictError = class LeaseConflictError extends Error {
278
+ targetId;
279
+ holder;
280
+ constructor(message, targetId, holder) {
281
+ super(message);
282
+ this.targetId = targetId;
283
+ this.holder = holder;
284
+ this.name = "LeaseConflictError";
285
+ }
286
+ };
287
+ leaseScope = new AsyncLocalStorage;
288
+ LEASE_FILE_RE = /^lease-(chrome|firefox)-(.+)\.json$/;
289
+ });
278
290
 
279
291
  // src/origins.ts
280
292
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
@@ -285,7 +297,6 @@ function safeId2(id) {
285
297
  function originFile(backend, targetId) {
286
298
  return join2(leaseDir(), `origin-${backend}-${safeId2(targetId)}.json`);
287
299
  }
288
- var ORIGIN_FILE_RE = /^origin-(chrome|firefox)-(.+)\.json$/;
289
300
  async function recordOrigin(backend, targetId, opts = {}) {
290
301
  const record = {
291
302
  backend,
@@ -401,16 +412,13 @@ async function resolveLiveLabel(backend, label, liveIds) {
401
412
  }
402
413
  return { matchIds: [...matchIds], knownLabels: [...knownLabels].sort() };
403
414
  }
415
+ var ORIGIN_FILE_RE;
416
+ var init_origins = __esm(() => {
417
+ init_leases();
418
+ ORIGIN_FILE_RE = /^origin-(chrome|firefox)-(.+)\.json$/;
419
+ });
404
420
 
405
421
  // src/workers.ts
406
- var WORKER_SELECTOR_PREFIX = "worker:";
407
- var WORKER_CAPABLE_TOOLS = [
408
- "evaluate_script",
409
- "list_network_requests",
410
- "get_network_request",
411
- "list_console_messages"
412
- ];
413
- var WORKER_TARGET_TYPES = new Set(["service_worker", "shared_worker"]);
414
422
  function isWorkerSelector(selector) {
415
423
  return typeof selector === "string" && selector.startsWith(WORKER_SELECTOR_PREFIX);
416
424
  }
@@ -427,9 +435,6 @@ function resolveWorkerTargets(targets, needle) {
427
435
  liveWorkers
428
436
  };
429
437
  }
430
- var WORKER_EMPTY_NEEDLE_MESSAGE = "'worker:' needs a substring of the worker's url, e.g. worker:<extension-id> or worker:background.js";
431
- var WORKER_SELECTOR_UNSUPPORTED_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, which this backend cannot address " + "(WebDriver BiDi has no extension service-worker target; Capability 'worker.targets' is Chrome-only). " + "Use --browser chrome to evaluate in, or record console/network from, an extension's background worker.";
432
- var WORKER_SELECTOR_PAGE_ONLY_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, and this tool is page-only " + "(closing, selecting or releasing a worker is meaningless). The tools that accept a worker: target are " + `${WORKER_CAPABLE_TOOLS.join(", ")}.`;
433
438
  function workerWakeMisuseMessage(kind) {
434
439
  if (kind === "not-a-worker-selector") {
435
440
  return "'wake' only applies to a target of the form 'worker:<substring>'. A page is never asleep, and a bare " + "worker target id cannot be woken either: Chrome destroys the target when an MV3 worker is evicted and " + "mints a NEW id when it restarts, so only the url-substring form can name a worker that is not running.";
@@ -454,22 +459,20 @@ function workerMissMessage(needle, opts) {
454
459
  const scopes = opts.registeredScopes?.length ? ` Registered service-worker scopes in this browser: ${opts.registeredScopes.join(", ")}.` : " No service-worker registrations were reported by this browser at all.";
455
460
  return `no worker whose url contains '${needle}' could be reached${running}. ` + `A start was requested (wake:true) and no matching worker appeared.${scopes} ` + "If the extension is installed but its worker never starts, check the worker script for a top-level throw.";
456
461
  }
462
+ var WORKER_SELECTOR_PREFIX = "worker:", WORKER_CAPABLE_TOOLS, WORKER_TARGET_TYPES, WORKER_EMPTY_NEEDLE_MESSAGE = "'worker:' needs a substring of the worker's url, e.g. worker:<extension-id> or worker:background.js", WORKER_SELECTOR_UNSUPPORTED_MESSAGE, WORKER_SELECTOR_PAGE_ONLY_MESSAGE;
463
+ var init_workers = __esm(() => {
464
+ WORKER_CAPABLE_TOOLS = [
465
+ "evaluate_script",
466
+ "list_network_requests",
467
+ "get_network_request",
468
+ "list_console_messages"
469
+ ];
470
+ WORKER_TARGET_TYPES = new Set(["service_worker", "shared_worker"]);
471
+ WORKER_SELECTOR_UNSUPPORTED_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, which this backend cannot address " + "(WebDriver BiDi has no extension service-worker target; Capability 'worker.targets' is Chrome-only). " + "Use --browser chrome to evaluate in, or record console/network from, an extension's background worker.";
472
+ WORKER_SELECTOR_PAGE_ONLY_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, and this tool is page-only " + "(closing, selecting or releasing a worker is meaningless). The tools that accept a worker: target are " + `${WORKER_CAPABLE_TOOLS.join(", ")}.`;
473
+ });
457
474
 
458
475
  // src/client.ts
459
- var BASE = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
460
- var DEFAULT_TIMEOUT_MS = Number(process.env.CDP_TIMEOUT_MS ?? 15000);
461
-
462
- class CdpError extends Error {
463
- code;
464
- data;
465
- constructor(message, code, data) {
466
- super(message);
467
- this.code = code;
468
- this.data = data;
469
- this.name = "CdpError";
470
- }
471
- }
472
-
473
476
  class CdpConnection {
474
477
  wsUrl;
475
478
  opts;
@@ -688,31 +691,29 @@ async function withPage(selector, fn, opts = {}) {
688
691
  conn.close();
689
692
  }
690
693
  }
694
+ var BASE, DEFAULT_TIMEOUT_MS, CdpError;
695
+ var init_client = __esm(() => {
696
+ init_leases();
697
+ init_origins();
698
+ init_workers();
699
+ BASE = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
700
+ DEFAULT_TIMEOUT_MS = Number(process.env.CDP_TIMEOUT_MS ?? 15000);
701
+ CdpError = class CdpError extends Error {
702
+ code;
703
+ data;
704
+ constructor(message, code, data) {
705
+ super(message);
706
+ this.code = code;
707
+ this.data = data;
708
+ this.name = "CdpError";
709
+ }
710
+ };
711
+ });
691
712
 
692
713
  // src/activity.ts
693
- var BEACON_DATA_GLOBAL = "__cdpToolkitLastInput";
694
- var BEACON_INSTALLED_GLOBAL = "__cdpToolkitBeacon";
695
- var BEACON_EVENTS = ["pointerdown", "mousedown", "keydown", "wheel", "touchstart"];
696
- var BEACON_BODY = `
697
- if (!window.${BEACON_INSTALLED_GLOBAL}) {
698
- window.${BEACON_INSTALLED_GLOBAL} = true;
699
- var mark = function () { window.${BEACON_DATA_GLOBAL} = Date.now(); };
700
- var events = ${JSON.stringify([...BEACON_EVENTS])};
701
- for (var i = 0; i < events.length; i++) {
702
- window.addEventListener(events[i], mark, { capture: true, passive: true });
703
- }
704
- }`;
705
- var BEACON_SOURCE = `(function () {${BEACON_BODY}
706
- })();`;
707
- var BEACON_FUNCTION_DECLARATION = `function () {${BEACON_BODY}
708
- }`;
709
- var BEACON_READ_EXPRESSION = `(typeof window.${BEACON_DATA_GLOBAL} === "number" ? window.${BEACON_DATA_GLOBAL} : null)`;
710
- var DISPATCH_ATTRIBUTION_WINDOW_MS = 1500;
711
714
  function dispatchKey(backend, targetId) {
712
715
  return `${backend}:${targetId}`;
713
716
  }
714
- var dispatchLog = new Map;
715
- var DISPATCH_LOG_LIMIT = 512;
716
717
  function recordDispatch(backend, targetId, now = Date.now()) {
717
718
  const key = dispatchKey(backend, targetId);
718
719
  dispatchLog.delete(key);
@@ -724,7 +725,6 @@ function recordDispatch(backend, targetId, now = Date.now()) {
724
725
  dispatchLog.delete(oldest.value);
725
726
  }
726
727
  }
727
- var NON_SYNTHESIZING_INPUT_METHODS = new Set(["Input.setInterceptDrags", "Input.setIgnoreInputEvents"]);
728
728
  async function sendInput(conn, backend, targetId, method, params = {}) {
729
729
  if (!NON_SYNTHESIZING_INPUT_METHODS.has(method))
730
730
  recordDispatch(backend, targetId);
@@ -743,7 +743,6 @@ function humanActiveMs(beaconTs, dispatchAt, now = Date.now(), windowMs = DISPAT
743
743
  return null;
744
744
  return Math.max(0, now - beaconTs);
745
745
  }
746
- var CONTENTION_WINDOW_MS = 30000;
747
746
  function contentionWarning(ms, opts) {
748
747
  if (!opts.takeover)
749
748
  return;
@@ -765,8 +764,6 @@ async function readHumanActiveMs(driver, backend, targetId, now = Date.now()) {
765
764
  const beaconTs = await driver.readActivityBeacon(targetId).catch(() => null);
766
765
  return humanActiveMs(beaconTs, lastDispatchAt(backend, targetId), now);
767
766
  }
768
- var RENDERER_PROBE_TIMEOUT_MS = 500;
769
- var RENDERER_PROBE_EXPRESSION = `[1, ${BEACON_READ_EXPRESSION}]`;
770
767
  function rendererProbeSupported(driver) {
771
768
  return typeof driver.probeRenderer === "function";
772
769
  }
@@ -823,123 +820,27 @@ class BeaconSessions {
823
820
  } catch {}
824
821
  }
825
822
  }
826
-
827
- // src/tools/snapshot.ts
828
- var INTERACTIVE_ROLES = new Set([
829
- "button",
830
- "link",
831
- "textbox",
832
- "searchbox",
833
- "checkbox",
834
- "radio",
835
- "combobox",
836
- "listbox",
837
- "option",
838
- "menuitem",
839
- "menuitemcheckbox",
840
- "menuitemradio",
841
- "tab",
842
- "switch",
843
- "slider",
844
- "spinbutton",
845
- "textfield",
846
- "MenuItem",
847
- "Button"
848
- ]);
849
- async function resolveUid(conn, uid) {
850
- const { object } = await conn.send("DOM.resolveNode", {
851
- backendNodeId: uid
852
- });
853
- if (!object?.objectId) {
854
- throw new Error(`resolveUid: DOM.resolveNode returned no objectId for uid ${uid}`);
855
- }
856
- return { objectId: object.objectId };
857
- }
858
-
859
- // src/cdp/workers.ts
860
- var INVENTORY_FIRST_EVENT_MS = 1500;
861
- var INVENTORY_SETTLE_MS = 250;
862
- var WAKE_POLL_MS = 300;
863
- var WAKE_POLL_ATTEMPTS = 30;
864
- var NO_HOST_PAGE_MESSAGE = "cannot start a service worker: the ServiceWorker CDP domain is only reachable over a page session, " + "and this browser has no page target open. Open any tab (new_page) and retry — the tab does not have " + "to belong to the extension.";
865
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
866
- async function requestWorkerStart(needle) {
867
- const conn = await openBrowser();
868
- try {
869
- const host = (await listTargets()).find((t) => t.type === "page");
870
- if (!host)
871
- throw new CdpError(NO_HOST_PAGE_MESSAGE);
872
- const { sessionId } = await conn.send("Target.attachToTarget", {
873
- targetId: host.id,
874
- flatten: true
875
- });
876
- if (!sessionId)
877
- throw new CdpError("could not attach a session to host the ServiceWorker domain");
878
- const scopeById = new Map;
879
- const versions = [];
880
- conn.on("ServiceWorker.workerRegistrationUpdated", (params) => {
881
- for (const r of params.registrations ?? []) {
882
- if (r.isDeleted === true)
883
- continue;
884
- scopeById.set(String(r.registrationId), String(r.scopeURL ?? ""));
885
- }
886
- });
887
- conn.on("ServiceWorker.workerVersionUpdated", (params) => {
888
- for (const v of params.versions ?? []) {
889
- versions.push({ registrationId: String(v.registrationId), scriptURL: String(v.scriptURL ?? "") });
890
- }
891
- });
892
- await conn.send("ServiceWorker.enable", {}, { sessionId });
893
- await conn.waitFor("ServiceWorker.workerVersionUpdated", undefined, INVENTORY_FIRST_EVENT_MS).catch(() => {
894
- return;
895
- });
896
- await sleep(INVENTORY_SETTLE_MS);
897
- const scopes = [...new Set(scopeById.values())].filter((s) => s.length > 0).sort();
898
- const matched = new Set;
899
- for (const v of versions) {
900
- const scope = scopeById.get(v.registrationId);
901
- if (scope === undefined)
902
- continue;
903
- if (v.scriptURL.includes(needle) || scope.includes(needle))
904
- matched.add(scope);
905
- }
906
- if (matched.size === 0)
907
- return { startRequested: false, scopes };
908
- if (matched.size > 1) {
909
- throw new CdpError(`'worker:${needle}' matches more than one registered service-worker scope: ${[...matched].sort().join(", ")}. ` + "Narrow the substring.");
910
- }
911
- await conn.send("ServiceWorker.startWorker", { scopeURL: [...matched][0] }, { sessionId });
912
- return { startRequested: true, scopes };
913
- } finally {
914
- conn.close();
915
- }
916
- }
917
- async function resolveWorkerSelector(selector, opts) {
918
- const needle = workerNeedle(selector);
919
- if (needle === "")
920
- throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
921
- const first = resolveWorkerTargets(await listTargets(), needle);
922
- if (first.matches.length > 1)
923
- throw new CdpError(workerAmbiguityMessage(needle, first.matches));
924
- if (first.matches.length === 1)
925
- return first.matches[0];
926
- if (!opts.wake) {
927
- throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers: first.liveWorkers }));
928
- }
929
- const { startRequested, scopes } = await requestWorkerStart(needle);
930
- if (startRequested) {
931
- for (let i = 0;i < WAKE_POLL_ATTEMPTS; i++) {
932
- await sleep(WAKE_POLL_MS);
933
- const again = resolveWorkerTargets(await listTargets(), needle);
934
- if (again.matches.length > 1)
935
- throw new CdpError(workerAmbiguityMessage(needle, again.matches));
936
- if (again.matches.length === 1)
937
- return again.matches[0];
823
+ var BEACON_DATA_GLOBAL = "__cdpToolkitLastInput", BEACON_INSTALLED_GLOBAL = "__cdpToolkitBeacon", BEACON_EVENTS, BEACON_BODY, BEACON_SOURCE, BEACON_FUNCTION_DECLARATION, BEACON_READ_EXPRESSION, DISPATCH_ATTRIBUTION_WINDOW_MS = 1500, dispatchLog, DISPATCH_LOG_LIMIT = 512, NON_SYNTHESIZING_INPUT_METHODS, CONTENTION_WINDOW_MS = 30000, RENDERER_PROBE_TIMEOUT_MS = 500, RENDERER_PROBE_EXPRESSION;
824
+ var init_activity = __esm(() => {
825
+ BEACON_EVENTS = ["pointerdown", "mousedown", "keydown", "wheel", "touchstart"];
826
+ BEACON_BODY = `
827
+ if (!window.${BEACON_INSTALLED_GLOBAL}) {
828
+ window.${BEACON_INSTALLED_GLOBAL} = true;
829
+ var mark = function () { window.${BEACON_DATA_GLOBAL} = Date.now(); };
830
+ var events = ${JSON.stringify([...BEACON_EVENTS])};
831
+ for (var i = 0; i < events.length; i++) {
832
+ window.addEventListener(events[i], mark, { capture: true, passive: true });
938
833
  }
939
- }
940
- const final = resolveWorkerTargets(await listTargets(), needle);
941
- throw new CdpError(workerMissMessage(needle, { wakeAttempted: true, liveWorkers: final.liveWorkers, registeredScopes: scopes }));
942
- }
834
+ }`;
835
+ BEACON_SOURCE = `(function () {${BEACON_BODY}
836
+ })();`;
837
+ BEACON_FUNCTION_DECLARATION = `function () {${BEACON_BODY}
838
+ }`;
839
+ BEACON_READ_EXPRESSION = `(typeof window.${BEACON_DATA_GLOBAL} === "number" ? window.${BEACON_DATA_GLOBAL} : null)`;
840
+ dispatchLog = new Map;
841
+ NON_SYNTHESIZING_INPUT_METHODS = new Set(["Input.setInterceptDrags", "Input.setIgnoreInputEvents"]);
842
+ RENDERER_PROBE_EXPRESSION = `[1, ${BEACON_READ_EXPRESSION}]`;
843
+ });
943
844
 
944
845
  // src/tools/network_mock.ts
945
846
  function escapeRegExpChar(ch) {
@@ -991,7 +892,6 @@ function effectiveAction(rule, rnd) {
991
892
  return "fail";
992
893
  return rule.action;
993
894
  }
994
- var sessions = new Map;
995
895
  function delay(ms) {
996
896
  return new Promise((r) => setTimeout(r, ms));
997
897
  }
@@ -1117,28 +1017,15 @@ async function clearMocks(args = {}) {
1117
1017
  }
1118
1018
  return { cleared };
1119
1019
  }
1020
+ var sessions;
1021
+ var init_network_mock = __esm(() => {
1022
+ init_client();
1023
+ sessions = new Map;
1024
+ });
1120
1025
 
1121
1026
  // src/driver.ts
1122
1027
  import { mkdir as mkdir3, readFile as readFile3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
1123
1028
  import { join as join3 } from "node:path";
1124
- var UID_STAMP_ATTR = "data-cdp-uid";
1125
- var LEGACY_NUMERIC_UID = /^[0-9]+$/;
1126
- var REQUIRED_CAPABILITIES = {
1127
- performance_start_trace: ["trace.performance"],
1128
- performance_stop_trace: ["trace.performance"],
1129
- performance_analyze_insight: ["trace.performance"],
1130
- performance_trace: ["trace.performance"],
1131
- take_heapsnapshot: ["heap.snapshot"],
1132
- lighthouse_audit: ["audit.lighthouse"],
1133
- start_screen_recording: ["capture.screencast"],
1134
- stop_screen_recording: ["capture.screencast"],
1135
- mock_request: ["network.intercept"],
1136
- list_mocks: ["network.intercept"],
1137
- clear_mocks: ["network.intercept"],
1138
- dispatch_mouse: ["input.raw"],
1139
- wait_for_download: ["browser.downloads"],
1140
- grant_permissions: ["browser.permissions"]
1141
- };
1142
1029
  function isDriverError(e) {
1143
1030
  return e instanceof Error && typeof e.code === "string";
1144
1031
  }
@@ -1185,8 +1072,158 @@ async function clearViewportRecord(scheme, targetId) {
1185
1072
  await unlink3(viewportRecordFile(scheme, targetId));
1186
1073
  } catch {}
1187
1074
  }
1075
+ var UID_STAMP_ATTR = "data-cdp-uid", LEGACY_NUMERIC_UID, REQUIRED_CAPABILITIES;
1076
+ var init_driver = __esm(() => {
1077
+ init_leases();
1078
+ LEGACY_NUMERIC_UID = /^[0-9]+$/;
1079
+ REQUIRED_CAPABILITIES = {
1080
+ performance_start_trace: ["trace.performance"],
1081
+ performance_stop_trace: ["trace.performance"],
1082
+ performance_analyze_insight: ["trace.performance"],
1083
+ performance_trace: ["trace.performance"],
1084
+ take_heapsnapshot: ["heap.snapshot"],
1085
+ lighthouse_audit: ["audit.lighthouse"],
1086
+ start_screen_recording: ["capture.screencast"],
1087
+ stop_screen_recording: ["capture.screencast"],
1088
+ mock_request: ["network.intercept"],
1089
+ list_mocks: ["network.intercept"],
1090
+ clear_mocks: ["network.intercept"],
1091
+ dispatch_mouse: ["input.raw"],
1092
+ focus_emulation: ["emulate.focus"],
1093
+ click_focus_gated: ["emulate.focus"],
1094
+ wait_for_download: ["browser.downloads"],
1095
+ grant_permissions: ["browser.permissions"]
1096
+ };
1097
+ });
1098
+
1099
+ // src/cdp/driver.ts
1100
+ init_client();
1101
+ init_leases();
1102
+ init_activity();
1103
+
1104
+ // src/tools/snapshot.ts
1105
+ init_client();
1106
+ var INTERACTIVE_ROLES = new Set([
1107
+ "button",
1108
+ "link",
1109
+ "textbox",
1110
+ "searchbox",
1111
+ "checkbox",
1112
+ "radio",
1113
+ "combobox",
1114
+ "listbox",
1115
+ "option",
1116
+ "menuitem",
1117
+ "menuitemcheckbox",
1118
+ "menuitemradio",
1119
+ "tab",
1120
+ "switch",
1121
+ "slider",
1122
+ "spinbutton",
1123
+ "textfield",
1124
+ "MenuItem",
1125
+ "Button"
1126
+ ]);
1127
+ async function resolveUid(conn, uid) {
1128
+ const { object } = await conn.send("DOM.resolveNode", {
1129
+ backendNodeId: uid
1130
+ });
1131
+ if (!object?.objectId) {
1132
+ throw new Error(`resolveUid: DOM.resolveNode returned no objectId for uid ${uid}`);
1133
+ }
1134
+ return { objectId: object.objectId };
1135
+ }
1136
+
1137
+ // src/cdp/workers.ts
1138
+ init_client();
1139
+ init_workers();
1140
+ var INVENTORY_FIRST_EVENT_MS = 1500;
1141
+ var INVENTORY_SETTLE_MS = 250;
1142
+ var WAKE_POLL_MS = 300;
1143
+ var WAKE_POLL_ATTEMPTS = 30;
1144
+ var NO_HOST_PAGE_MESSAGE = "cannot start a service worker: the ServiceWorker CDP domain is only reachable over a page session, " + "and this browser has no page target open. Open any tab (new_page) and retry — the tab does not have " + "to belong to the extension.";
1145
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1146
+ async function requestWorkerStart(needle) {
1147
+ const conn = await openBrowser();
1148
+ try {
1149
+ const host = (await listTargets()).find((t) => t.type === "page");
1150
+ if (!host)
1151
+ throw new CdpError(NO_HOST_PAGE_MESSAGE);
1152
+ const { sessionId } = await conn.send("Target.attachToTarget", {
1153
+ targetId: host.id,
1154
+ flatten: true
1155
+ });
1156
+ if (!sessionId)
1157
+ throw new CdpError("could not attach a session to host the ServiceWorker domain");
1158
+ const scopeById = new Map;
1159
+ const versions = [];
1160
+ conn.on("ServiceWorker.workerRegistrationUpdated", (params) => {
1161
+ for (const r of params.registrations ?? []) {
1162
+ if (r.isDeleted === true)
1163
+ continue;
1164
+ scopeById.set(String(r.registrationId), String(r.scopeURL ?? ""));
1165
+ }
1166
+ });
1167
+ conn.on("ServiceWorker.workerVersionUpdated", (params) => {
1168
+ for (const v of params.versions ?? []) {
1169
+ versions.push({ registrationId: String(v.registrationId), scriptURL: String(v.scriptURL ?? "") });
1170
+ }
1171
+ });
1172
+ await conn.send("ServiceWorker.enable", {}, { sessionId });
1173
+ await conn.waitFor("ServiceWorker.workerVersionUpdated", undefined, INVENTORY_FIRST_EVENT_MS).catch(() => {
1174
+ return;
1175
+ });
1176
+ await sleep(INVENTORY_SETTLE_MS);
1177
+ const scopes = [...new Set(scopeById.values())].filter((s) => s.length > 0).sort();
1178
+ const matched = new Set;
1179
+ for (const v of versions) {
1180
+ const scope = scopeById.get(v.registrationId);
1181
+ if (scope === undefined)
1182
+ continue;
1183
+ if (v.scriptURL.includes(needle) || scope.includes(needle))
1184
+ matched.add(scope);
1185
+ }
1186
+ if (matched.size === 0)
1187
+ return { startRequested: false, scopes };
1188
+ if (matched.size > 1) {
1189
+ throw new CdpError(`'worker:${needle}' matches more than one registered service-worker scope: ${[...matched].sort().join(", ")}. ` + "Narrow the substring.");
1190
+ }
1191
+ await conn.send("ServiceWorker.startWorker", { scopeURL: [...matched][0] }, { sessionId });
1192
+ return { startRequested: true, scopes };
1193
+ } finally {
1194
+ conn.close();
1195
+ }
1196
+ }
1197
+ async function resolveWorkerSelector(selector, opts) {
1198
+ const needle = workerNeedle(selector);
1199
+ if (needle === "")
1200
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
1201
+ const first = resolveWorkerTargets(await listTargets(), needle);
1202
+ if (first.matches.length > 1)
1203
+ throw new CdpError(workerAmbiguityMessage(needle, first.matches));
1204
+ if (first.matches.length === 1)
1205
+ return first.matches[0];
1206
+ if (!opts.wake) {
1207
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers: first.liveWorkers }));
1208
+ }
1209
+ const { startRequested, scopes } = await requestWorkerStart(needle);
1210
+ if (startRequested) {
1211
+ for (let i = 0;i < WAKE_POLL_ATTEMPTS; i++) {
1212
+ await sleep(WAKE_POLL_MS);
1213
+ const again = resolveWorkerTargets(await listTargets(), needle);
1214
+ if (again.matches.length > 1)
1215
+ throw new CdpError(workerAmbiguityMessage(needle, again.matches));
1216
+ if (again.matches.length === 1)
1217
+ return again.matches[0];
1218
+ }
1219
+ }
1220
+ const final = resolveWorkerTargets(await listTargets(), needle);
1221
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: true, liveWorkers: final.liveWorkers, registeredScopes: scopes }));
1222
+ }
1188
1223
 
1189
1224
  // src/cdp/driver.ts
1225
+ init_network_mock();
1226
+ init_driver();
1190
1227
  function driverError(code, message, data) {
1191
1228
  return Object.assign(new Error(message), { code, ...data !== undefined ? { data } : {} });
1192
1229
  }
@@ -1249,12 +1286,12 @@ async function awaitScrollSettle(conn) {
1249
1286
  return;
1250
1287
  });
1251
1288
  }
1252
- async function setValueOnObject(conn, objectId, value, sendInput2) {
1289
+ async function setValueOnObject(conn, objectId, value, sendInput) {
1253
1290
  await focusElement(conn, objectId);
1254
1291
  const fn = "function(){if('value'in this){this.value='';this.dispatchEvent(new Event('input',{bubbles:true}));}else if(this.isContentEditable){this.textContent='';}}";
1255
1292
  await conn.send("Runtime.callFunctionOn", { objectId, functionDeclaration: fn, returnByValue: true });
1256
1293
  if (value.length)
1257
- await sendInput2("Input.insertText", { text: value });
1294
+ await sendInput("Input.insertText", { text: value });
1258
1295
  }
1259
1296
  var NAMED_KEYS = {
1260
1297
  enter: { key: "Enter", code: "Enter", keyCode: 13, text: "\r" },
@@ -1524,6 +1561,7 @@ var CDP_CAPABILITIES = new Set([
1524
1561
  "capture.screencast",
1525
1562
  "input.raw",
1526
1563
  "input.html5Drag",
1564
+ "emulate.focus",
1527
1565
  "browser.downloads",
1528
1566
  "browser.permissions",
1529
1567
  "worker.targets"
@@ -2287,7 +2325,13 @@ function createCdpDriver() {
2287
2325
  return new CdpBrowserDriver;
2288
2326
  }
2289
2327
 
2328
+ // src/leases-tools.ts
2329
+ init_activity();
2330
+ init_origins();
2331
+
2290
2332
  // src/reap.ts
2333
+ init_leases();
2334
+ init_origins();
2291
2335
  function staleAgentTabs(input) {
2292
2336
  const live = new Set(input.livePageIds);
2293
2337
  const out = [];
@@ -2334,6 +2378,7 @@ async function reapStaleAgentTabs(driver, backend) {
2334
2378
  }
2335
2379
 
2336
2380
  // src/shared-tools.ts
2381
+ init_driver();
2337
2382
  import { mkdir as mkdir5, writeFile as writeFile4 } from "node:fs/promises";
2338
2383
  import { dirname as dirname2, join as join4 } from "node:path";
2339
2384
 
@@ -2850,6 +2895,10 @@ async function stitchInto(fh, bands) {
2850
2895
  }
2851
2896
 
2852
2897
  // src/shared-tools.ts
2898
+ init_leases();
2899
+ init_activity();
2900
+ init_origins();
2901
+ init_workers();
2853
2902
  var ARTIFACT_DIR = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2854
2903
  var STATE_DIR = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
2855
2904
 
@@ -3581,6 +3630,8 @@ var SHARED_TOOLS = {
3581
3630
  };
3582
3631
 
3583
3632
  // src/leases-tools.ts
3633
+ init_leases();
3634
+
3584
3635
  class LeaseToolError extends Error {
3585
3636
  }
3586
3637
  function backendOf2(driver) {
@@ -3696,10 +3747,13 @@ var LEASE_TOOLS = {
3696
3747
  };
3697
3748
 
3698
3749
  // src/tools/console.ts
3750
+ init_client();
3699
3751
  import { readFile as readFile4 } from "node:fs/promises";
3700
3752
 
3701
3753
  // src/tools/recorder.ts
3754
+ init_client();
3702
3755
  import { mkdir as mkdir6, appendFile, writeFile as writeFile5, copyFile } from "node:fs/promises";
3756
+ init_workers();
3703
3757
  function artifactDir() {
3704
3758
  return process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
3705
3759
  }
@@ -3926,11 +3980,11 @@ async function listConsoleMessages(args = {}) {
3926
3980
  if (args.reload) {
3927
3981
  const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS, { wake: args.wake });
3928
3982
  await cap.stop();
3929
- const messages2 = await readConsoleEntries(cap.file);
3983
+ const messages = await readConsoleEntries(cap.file);
3930
3984
  return {
3931
3985
  target: cap.resolved,
3932
- count: messages2.length,
3933
- messages: messages2,
3986
+ count: messages.length,
3987
+ messages,
3934
3988
  droppedWrites: cap.droppedWrites()
3935
3989
  };
3936
3990
  }
@@ -3954,6 +4008,7 @@ async function getConsoleMessage(args = {}) {
3954
4008
  }
3955
4009
 
3956
4010
  // src/tools/network.ts
4011
+ init_client();
3957
4012
  import { readFile as readFile5 } from "node:fs/promises";
3958
4013
  var DEFAULT_CAPTURE_MS2 = 2500;
3959
4014
  function asHeaders(v) {
@@ -4075,15 +4130,15 @@ async function getNetworkRequest(args = {}) {
4075
4130
  const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS2, { wake: args.wake });
4076
4131
  try {
4077
4132
  await cap.flush();
4078
- const match2 = (await readNetworkRequests(cap.file)).find((r) => r.url.includes(args.url));
4079
- if (!match2) {
4133
+ const match = (await readNetworkRequests(cap.file)).find((r) => r.url.includes(args.url));
4134
+ if (!match) {
4080
4135
  throw new CdpError(`no network request matched ${describeSelector()} in fresh capture`);
4081
4136
  }
4082
4137
  try {
4083
- const { body, base64Encoded } = await cap.conn.send("Network.getResponseBody", { requestId: match2.requestId });
4084
- return { ...match2, body, bodyBase64Encoded: base64Encoded };
4138
+ const { body, base64Encoded } = await cap.conn.send("Network.getResponseBody", { requestId: match.requestId });
4139
+ return { ...match, body, bodyBase64Encoded: base64Encoded };
4085
4140
  } catch {
4086
- return { ...match2, body: undefined, bodyBase64Encoded: undefined };
4141
+ return { ...match, body: undefined, bodyBase64Encoded: undefined };
4087
4142
  }
4088
4143
  } finally {
4089
4144
  await cap.stop();
@@ -4105,6 +4160,7 @@ async function getNetworkRequest(args = {}) {
4105
4160
  }
4106
4161
 
4107
4162
  // src/tools/performance.ts
4163
+ init_client();
4108
4164
  import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile6, stat, unlink as unlink4 } from "node:fs/promises";
4109
4165
  import { join as join5 } from "node:path";
4110
4166
  var ARTIFACT_DIR2 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
@@ -4337,6 +4393,7 @@ async function performanceAnalyzeInsight(args = {}) {
4337
4393
  }
4338
4394
 
4339
4395
  // src/tools/screencast.ts
4396
+ init_client();
4340
4397
  import { spawn } from "node:child_process";
4341
4398
  import { mkdir as mkdir8, rm as rm2, stat as stat2, writeFile as writeFile7 } from "node:fs/promises";
4342
4399
  import { join as join6 } from "node:path";
@@ -4701,6 +4758,7 @@ ffmpeg said: ${res.stderr.trim().slice(0, 800)}`);
4701
4758
  }
4702
4759
 
4703
4760
  // src/tools/heap.ts
4761
+ init_client();
4704
4762
  import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
4705
4763
  import { join as join7 } from "node:path";
4706
4764
  var ARTIFACT_DIR4 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
@@ -4741,6 +4799,7 @@ async function takeHeapsnapshot(args = {}) {
4741
4799
  }
4742
4800
 
4743
4801
  // src/tools/lighthouse.ts
4802
+ init_client();
4744
4803
  import { spawn as spawn2 } from "node:child_process";
4745
4804
  import { mkdir as mkdir10, readFile as readFile7, stat as stat3 } from "node:fs/promises";
4746
4805
  var ARTIFACT_DIR5 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
@@ -4876,7 +4935,12 @@ function truncate(s, max = 600) {
4876
4935
  return t.length > max ? `${t.slice(0, max)}…` : t;
4877
4936
  }
4878
4937
 
4938
+ // src/index.ts
4939
+ init_network_mock();
4940
+
4879
4941
  // src/tools/dispatch-mouse.ts
4942
+ init_activity();
4943
+ init_client();
4880
4944
  var CLICK_MODIFIER_BITS2 = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
4881
4945
  var BUTTONS = new Set(["left", "right", "middle"]);
4882
4946
  var CDP_EVENT_TYPE = {
@@ -4918,11 +4982,106 @@ async function dispatchMouse(args) {
4918
4982
  });
4919
4983
  }
4920
4984
 
4985
+ // src/tools/focus-emulation.ts
4986
+ init_activity();
4987
+ init_client();
4988
+ function validateFocusEmulationArgs(args) {
4989
+ if (typeof args.enabled !== "boolean") {
4990
+ throw new Error("focus_emulation requires { enabled } of true or false");
4991
+ }
4992
+ }
4993
+ function validateClickFocusGatedArgs(args) {
4994
+ const hasSel = typeof args.selector === "string" && args.selector.length > 0;
4995
+ const hasText = typeof args.text === "string" && args.text.length > 0;
4996
+ if (hasSel === hasText) {
4997
+ throw new Error("click_focus_gated requires exactly one of { selector } or { text }");
4998
+ }
4999
+ if (args.timeoutMs !== undefined && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) {
5000
+ throw new Error("click_focus_gated: timeoutMs must be a positive number");
5001
+ }
5002
+ if (args.pollMs !== undefined && (!Number.isFinite(args.pollMs) || args.pollMs <= 0)) {
5003
+ throw new Error("click_focus_gated: pollMs must be a positive number");
5004
+ }
5005
+ }
5006
+ var FIND_BUTTON = `(() => {
5007
+ const SEL = %SEL%;
5008
+ const TEXT = %TEXT%;
5009
+ let btn = null;
5010
+ let matchedBy = null;
5011
+ if (SEL) {
5012
+ btn = document.querySelector(SEL);
5013
+ if (btn) matchedBy = "selector";
5014
+ }
5015
+ if (!btn && TEXT) {
5016
+ btn = Array.from(document.querySelectorAll("button,[role=button],input[type=submit]"))
5017
+ .find((b) => (b.textContent || b.value || "").trim() === TEXT);
5018
+ if (btn) matchedBy = "text";
5019
+ }
5020
+ if (!btn) return null;
5021
+ const r = btn.getBoundingClientRect();
5022
+ if (r.width === 0 || r.height === 0) return null;
5023
+ return { x: r.left + r.width / 2, y: r.top + r.height / 2,
5024
+ disabled: !!btn.disabled || btn.getAttribute("aria-disabled") === "true",
5025
+ matchedBy, hasFocus: document.hasFocus(), visibility: document.visibilityState };
5026
+ })()`;
5027
+ function findButtonExpression(selector, text) {
5028
+ return FIND_BUTTON.replace("%SEL%", JSON.stringify(selector ?? null)).replace("%TEXT%", JSON.stringify(text ?? null));
5029
+ }
5030
+ async function focusEmulation(args) {
5031
+ validateFocusEmulationArgs(args);
5032
+ return withPage(args.target, async (conn) => {
5033
+ await conn.send("Emulation.setFocusEmulationEnabled", { enabled: args.enabled });
5034
+ return { enabled: args.enabled };
5035
+ }, { lease: args.lease });
5036
+ }
5037
+ async function clickFocusGated(args) {
5038
+ validateClickFocusGatedArgs(args);
5039
+ const timeoutMs = args.timeoutMs ?? 30000;
5040
+ const pollMs = args.pollMs ?? 500;
5041
+ const start = Date.now();
5042
+ return withPage(args.target, async (conn, target) => {
5043
+ await conn.send("Emulation.setFocusEmulationEnabled", { enabled: true });
5044
+ try {
5045
+ const expression = findButtonExpression(args.selector, args.text);
5046
+ for (;; ) {
5047
+ const { result } = await conn.send("Runtime.evaluate", {
5048
+ expression,
5049
+ returnByValue: true
5050
+ });
5051
+ const btn = result?.value;
5052
+ if (btn && !btn.disabled) {
5053
+ const base = { x: btn.x, y: btn.y, button: "left", buttons: 1, clickCount: 1 };
5054
+ await sendInput(conn, "chrome", target.id, "Input.dispatchMouseEvent", { type: "mouseMoved", x: btn.x, y: btn.y });
5055
+ await sendInput(conn, "chrome", target.id, "Input.dispatchMouseEvent", { ...base, type: "mousePressed" });
5056
+ await sendInput(conn, "chrome", target.id, "Input.dispatchMouseEvent", { ...base, type: "mouseReleased" });
5057
+ return {
5058
+ clicked: true,
5059
+ x: btn.x,
5060
+ y: btn.y,
5061
+ matchedBy: btn.matchedBy,
5062
+ elapsedMs: Date.now() - start
5063
+ };
5064
+ }
5065
+ if (Date.now() - start >= timeoutMs) {
5066
+ throw new Error(`click_focus_gated: button never became clickable within ${timeoutMs}ms` + (btn ? " (found but stayed disabled — focus emulation may not satisfy this gate)" : " (never located)"));
5067
+ }
5068
+ await new Promise((r) => setTimeout(r, pollMs));
5069
+ }
5070
+ } finally {
5071
+ if (!args.keepFocus) {
5072
+ await conn.send("Emulation.setFocusEmulationEnabled", { enabled: false }).catch(() => {});
5073
+ }
5074
+ }
5075
+ }, { lease: args.lease, timeoutMs: timeoutMs + 5000 });
5076
+ }
5077
+
4921
5078
  // src/tools/downloads.ts
5079
+ init_client();
4922
5080
  import { readdir as readdir3, rename as rename2, stat as stat4 } from "node:fs/promises";
4923
5081
  import { basename, extname, join as join9 } from "node:path";
4924
5082
 
4925
5083
  // src/tools/browser-session.ts
5084
+ init_client();
4926
5085
  import { mkdir as mkdir11 } from "node:fs/promises";
4927
5086
  import { join as join8 } from "node:path";
4928
5087
  var ARTIFACT_DIR6 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
@@ -5095,6 +5254,7 @@ async function waitForDownload(args = {}) {
5095
5254
  }
5096
5255
 
5097
5256
  // src/tools/permissions.ts
5257
+ init_client();
5098
5258
  function originOf(url) {
5099
5259
  try {
5100
5260
  const o = new URL(url).origin;
@@ -5144,6 +5304,7 @@ async function grantPermissions(args = {}) {
5144
5304
  }
5145
5305
 
5146
5306
  // src/index.ts
5307
+ init_client();
5147
5308
  var cdpDriver = createCdpDriver();
5148
5309
  function onCdp(fn) {
5149
5310
  return (args) => fn(cdpDriver, args);
@@ -5189,6 +5350,8 @@ var TOOLS = {
5189
5350
  list_mocks: listMocks,
5190
5351
  clear_mocks: clearMocks,
5191
5352
  dispatch_mouse: dispatchMouse,
5353
+ focus_emulation: focusEmulation,
5354
+ click_focus_gated: clickFocusGated,
5192
5355
  wait_for_download: waitForDownload,
5193
5356
  grant_permissions: grantPermissions,
5194
5357
  claim_page: onCdp(LEASE_TOOLS.claim_page),