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/CHANGELOG.md +33 -0
- package/README.md +21 -7
- package/dist/bidi/mux-daemon.js +3775 -0
- package/dist/cli.js +6573 -5644
- package/dist/index.js +373 -210
- package/dist/mcp.js +8334 -7383
- package/package.json +4 -2
- package/src/bidi/driver.ts +329 -30
- package/src/bidi/mux-daemon.ts +122 -0
- package/src/bidi/mux.ts +610 -0
- package/src/bidi/session-lease.ts +95 -3
- package/src/cdp/driver.ts +1 -1
- package/src/driver.ts +3 -0
- package/src/index.ts +6 -0
- package/src/manifest.ts +65 -0
- package/src/toolDocs.ts +20 -0
- package/src/toolGroups.ts +1 -1
- package/src/tools/focus-emulation.ts +185 -0
- package/src/version.ts +1 -1
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
941
|
-
|
|
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,
|
|
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
|
|
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
|
|
3983
|
+
const messages = await readConsoleEntries(cap.file);
|
|
3930
3984
|
return {
|
|
3931
3985
|
target: cap.resolved,
|
|
3932
|
-
count:
|
|
3933
|
-
messages
|
|
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
|
|
4079
|
-
if (!
|
|
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:
|
|
4084
|
-
return { ...
|
|
4138
|
+
const { body, base64Encoded } = await cap.conn.send("Network.getResponseBody", { requestId: match.requestId });
|
|
4139
|
+
return { ...match, body, bodyBase64Encoded: base64Encoded };
|
|
4085
4140
|
} catch {
|
|
4086
|
-
return { ...
|
|
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),
|