cdp-toolkit 1.8.0 → 1.9.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 +119 -0
- package/CONTRACT.md +4 -0
- package/README.md +23 -4
- package/dist/cli.js +338 -111
- package/dist/index.js +319 -110
- package/dist/mcp.js +380 -151
- package/package.json +3 -2
- package/src/bidi/driver.ts +28 -0
- package/src/cdp/driver.ts +16 -1
- package/src/cdp/workers.ts +177 -0
- package/src/cli.ts +3 -1
- package/src/client.ts +52 -2
- package/src/driver.ts +27 -1
- package/src/leases-tools.ts +2 -2
- package/src/manifest.ts +44 -40
- package/src/mcp.ts +1 -1
- package/src/origins.ts +41 -1
- package/src/shared-tools.ts +104 -3
- package/src/types.ts +20 -0
- package/src/workers.ts +159 -0
package/src/workers.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "worker:<substring>" arm of the TargetSelector grammar.
|
|
3
|
+
*
|
|
4
|
+
* WHAT IT IS FOR. An MV3 extension keeps its logic in a background SERVICE
|
|
5
|
+
* WORKER, not in a page, so `chrome.storage.local` and everything else an
|
|
6
|
+
* extension owns is unreachable through a page-typed selector. This arm names
|
|
7
|
+
* that worker by a substring of its url, which for an extension is
|
|
8
|
+
* `chrome-extension://<id>/<script>` — so `worker:<extension-id>` and
|
|
9
|
+
* `worker:background.js` both work.
|
|
10
|
+
*
|
|
11
|
+
* WHY DATA, NEVER THROWS (the resolveLiveLabel precedent in origins.ts). The
|
|
12
|
+
* grammar has three independent resolver copies — `pickTarget` in client.ts,
|
|
13
|
+
* `pickPage` in shared-tools.ts, `pickContext` in bidi/driver.ts — each with its
|
|
14
|
+
* own error class (CdpError / SharedToolError / DriverError). A shared resolver
|
|
15
|
+
* that threw would force one class on all three, so this returns the facts and
|
|
16
|
+
* each caller raises its own error. What IS shared is the message TEXT below,
|
|
17
|
+
* because the teaching in it (MV3 eviction) is the point of the feature and
|
|
18
|
+
* three hand-written variants of it would drift apart within one release.
|
|
19
|
+
*
|
|
20
|
+
* WHY THE ALL-TARGETS LISTING. Chrome's `/json/list` only reports a worker that
|
|
21
|
+
* is currently RUNNING, and the page-only listing that the other arms resolve
|
|
22
|
+
* against never contains one at all. So this arm reads the unfiltered listing.
|
|
23
|
+
* That does NOT merge the two id sets that reap and leases keep apart: see
|
|
24
|
+
* ReapInput.livePageIds's header, and the lease bypass in client.ts's
|
|
25
|
+
* resolveTarget.
|
|
26
|
+
*
|
|
27
|
+
* THE MEASURED FACT THE MESSAGES TEACH (Chrome 151.0.7922.109, probed live for
|
|
28
|
+
* 1.9.0, transcript in the release evidence): an idle MV3 service worker is
|
|
29
|
+
* evicted within seconds of its last activity, and once evicted it is absent
|
|
30
|
+
* from `/json/list` AND from `Target.getTargets` under every filter — it is not
|
|
31
|
+
* a target at all. "No such worker" therefore almost never means "wrong id"; it
|
|
32
|
+
* means "asleep". A miss message that did not say so would send a caller
|
|
33
|
+
* hunting for a typo that is not there.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Selector prefix owned by this arm. */
|
|
37
|
+
export const WORKER_SELECTOR_PREFIX = "worker:";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* CDP target types this arm resolves. `service_worker` is the MV3 case that
|
|
41
|
+
* motivated the feature; `shared_worker` is included because it presents the
|
|
42
|
+
* same way (own target, own webSocketDebuggerUrl, evaluable over Runtime) and
|
|
43
|
+
* excluding it would be an arbitrary gap. A DEDICATED (per-page) worker is NOT
|
|
44
|
+
* here: it has no independent existence to select by url.
|
|
45
|
+
*/
|
|
46
|
+
export const WORKER_TARGET_TYPES: ReadonlySet<string> = new Set(["service_worker", "shared_worker"]);
|
|
47
|
+
|
|
48
|
+
/** The three fields every listing shape (CDP Target, PageInfo) already carries. */
|
|
49
|
+
export interface WorkerTargetLike {
|
|
50
|
+
id: string;
|
|
51
|
+
url: string;
|
|
52
|
+
type?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isWorkerSelector(selector: string | undefined): selector is string {
|
|
56
|
+
return typeof selector === "string" && selector.startsWith(WORKER_SELECTOR_PREFIX);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The substring half of "worker:<substring>". May be empty; callers reject that. */
|
|
60
|
+
export function workerNeedle(selector: string): string {
|
|
61
|
+
return selector.slice(WORKER_SELECTOR_PREFIX.length);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isWorkerTargetType(type: string | undefined): boolean {
|
|
65
|
+
return type !== undefined && WORKER_TARGET_TYPES.has(type);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WorkerResolution<T> {
|
|
69
|
+
/** Running worker targets whose url contains the needle. */
|
|
70
|
+
matches: T[];
|
|
71
|
+
/** Every running worker target, for a miss message that lists the alternatives. */
|
|
72
|
+
liveWorkers: T[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pure: split a target listing into "workers matching the needle" and "all
|
|
77
|
+
* running workers". An EMPTY needle matches nothing here rather than matching
|
|
78
|
+
* everything, because `"anything".includes("")` is true and a bare `worker:`
|
|
79
|
+
* would otherwise silently mean "whichever worker happens to be first".
|
|
80
|
+
* Callers reject the empty needle outright with WORKER_EMPTY_NEEDLE_MESSAGE.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveWorkerTargets<T extends WorkerTargetLike>(targets: readonly T[], needle: string): WorkerResolution<T> {
|
|
83
|
+
const liveWorkers = targets.filter((t) => isWorkerTargetType(t.type));
|
|
84
|
+
return {
|
|
85
|
+
matches: needle === "" ? [] : liveWorkers.filter((t) => t.url.includes(needle)),
|
|
86
|
+
liveWorkers,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* -------------------------------- shared message text -------------------------------- */
|
|
91
|
+
|
|
92
|
+
export const WORKER_EMPTY_NEEDLE_MESSAGE =
|
|
93
|
+
"'worker:' needs a substring of the worker's url, e.g. worker:<extension-id> or worker:background.js";
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The Firefox refusal. A PARAM-level gap, exactly like drag's mode:'html5'
|
|
97
|
+
* (see resolveDragMode): evaluate_script stays in tools/list on Firefox because
|
|
98
|
+
* every page selector still works there, and only this one arm is refused.
|
|
99
|
+
* Naming the reason matters — WebDriver BiDi has no extension-worker target
|
|
100
|
+
* concept to map onto, so this is a protocol gap, not an unimplemented to-do.
|
|
101
|
+
*/
|
|
102
|
+
export const WORKER_SELECTOR_UNSUPPORTED_MESSAGE =
|
|
103
|
+
"target 'worker:<substring>' selects a service/shared worker, which this backend cannot address " +
|
|
104
|
+
"(WebDriver BiDi has no extension service-worker target; Capability 'worker.targets' is Chrome-only). " +
|
|
105
|
+
"Use --browser chrome to evaluate in an extension's background worker.";
|
|
106
|
+
|
|
107
|
+
/** Refusal from the page-only resolvers (close_page / select_page / release_page). */
|
|
108
|
+
export const WORKER_SELECTOR_PAGE_ONLY_MESSAGE =
|
|
109
|
+
"target 'worker:<substring>' selects a service/shared worker, and this tool is page-only " +
|
|
110
|
+
"(closing, selecting or releasing a worker is meaningless). evaluate_script is the one tool that " +
|
|
111
|
+
"accepts a worker: target.";
|
|
112
|
+
|
|
113
|
+
function urlList(workers: readonly WorkerTargetLike[]): string {
|
|
114
|
+
return workers.map((w) => w.url).join(", ");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Ambiguity never resolves to a silent first match: name every candidate. */
|
|
118
|
+
export function workerAmbiguityMessage(needle: string, matches: readonly WorkerTargetLike[]): string {
|
|
119
|
+
return (
|
|
120
|
+
`'worker:${needle}' matches more than one running worker: ` +
|
|
121
|
+
matches.map((m) => `${m.url} (${m.id})`).join(", ") +
|
|
122
|
+
". Narrow the substring, or pass the worker's target id directly."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The miss message, and the whole reason this module owns its text.
|
|
128
|
+
*
|
|
129
|
+
* `wakeAttempted` distinguishes the two genuinely different failures, which a
|
|
130
|
+
* single message could only blur:
|
|
131
|
+
* - wake:false — the worker may exist and simply be asleep. Say so, and say
|
|
132
|
+
* what would have been done about it.
|
|
133
|
+
* - wake:true and it still did not appear — a start was actually requested
|
|
134
|
+
* and Chrome produced no worker. `registeredScopes` is what separates
|
|
135
|
+
* "nothing by that name is installed" from "installed but it would not
|
|
136
|
+
* start", so it is included when the inventory could be read.
|
|
137
|
+
*/
|
|
138
|
+
export function workerMissMessage(
|
|
139
|
+
needle: string,
|
|
140
|
+
opts: { wakeAttempted: boolean; liveWorkers?: readonly WorkerTargetLike[]; registeredScopes?: readonly string[] },
|
|
141
|
+
): string {
|
|
142
|
+
const running = opts.liveWorkers?.length ? ` (running workers: ${urlList(opts.liveWorkers)})` : " (no workers are running)";
|
|
143
|
+
if (!opts.wakeAttempted) {
|
|
144
|
+
return (
|
|
145
|
+
`no running worker whose url contains '${needle}'${running}. ` +
|
|
146
|
+
"An MV3 extension service worker is idle-evicted after seconds of inactivity and then appears in NO target " +
|
|
147
|
+
"listing, so this is usually a sleeping worker rather than a wrong substring. You passed wake:false; " +
|
|
148
|
+
"wake:true (the default) asks Chrome to start it first."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const scopes = opts.registeredScopes?.length
|
|
152
|
+
? ` Registered service-worker scopes in this browser: ${opts.registeredScopes.join(", ")}.`
|
|
153
|
+
: " No service-worker registrations were reported by this browser at all.";
|
|
154
|
+
return (
|
|
155
|
+
`no worker whose url contains '${needle}' could be reached${running}. ` +
|
|
156
|
+
`A start was requested (wake:true) and no matching worker appeared.${scopes} ` +
|
|
157
|
+
"If the extension is installed but its worker never starts, check the worker script for a top-level throw."
|
|
158
|
+
);
|
|
159
|
+
}
|