1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
// Bridge transport resilience in the shipped worker, plus same-process (subagent) loading in index.ts.
// Worker timers are scaled 1000x down so the real 45s /next deadline elapses in ~45ms.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { stripTypeScriptTypes } from "node:module";
import { test } from "node:test";
const workerSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/browser-extension/service_worker.js", import.meta.url), "utf8");
const indexSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/index.ts", import.meta.url), "utf8");
function loadWorker(fetchImpl) {
const listener = { addListener() {}, removeListener() {} };
const chrome = {
runtime: { id: "test", getManifest: () => ({ version: "0.0.1" }), getURL: (f) => `chrome-extension://test/${f}`, onInstalled: listener, onStartup: listener, reload() {} },
alarms: { create() {}, onAlarm: listener }, action: { onClicked: listener }, webNavigation: { onCommitted: listener },
debugger: { onDetach: listener }, scripting: {}, tabs: { onUpdated: listener },
};
const sandbox = {
chrome, console, AbortController, URL,
navigator: { userAgent: "unit-test" },
setTimeout: (fn, ms, ...args) => setTimeout(fn, Math.ceil((ms || 0) / 1000), ...args),
clearTimeout, setInterval: () => 0, clearInterval() {},
fetch: fetchImpl,
};
sandbox.self = sandbox;
vm.runInNewContext(workerSource, sandbox);
return sandbox;
}
function response(status, body, headers = {}) {
return { ok: status >= 200 && status < 300, status, headers: { get: (name) => headers[name.toLowerCase()] ?? null }, json: async () => body };
}
test("a half-open /next long poll is aborted at its deadline and polling resumes", async () => {
const requests = [];
let aborted = 0;
const worker = loadWorker((url, options = {}) => {
requests.push({ url, options });
if (requests.length === 1) {
// Zombie socket: never settles unless aborted.
return new Promise((_resolve, reject) => {
options.signal.addEventListener("abort", () => {
aborted++;
const error = new Error("The operation was aborted");
error.name = "AbortError";
reject(error);
});
});
}
return Promise.reject(new Error("bridge down"));
});
const first = worker.pollLoop();
assert.ok(requests[0]?.options.signal, "the /next fetch carries an abort signal");
// Resolves only after the scaled 45s deadline fires and the 2s backoff elapses.
await first;
assert.equal(aborted, 1, "the stalled /next fetch was aborted");
await worker.pollLoop();
assert.equal(requests.length, 2, "a later poll opens a fresh /next request instead of staying parked");
});
test("a command result is posted exactly once, retried on transport failure, and not retried on 4xx", async () => {
const posts = [];
let postStatus = [500, 200];
const worker = loadWorker(async (url, options = {}) => {
if (url.includes("/result")) {
posts.push(JSON.parse(options.body));
const status = postStatus.shift() ?? 200;
if (status === 0) throw new Error("fetch failed");
return response(status, {});
}
throw new Error("not polling in this test");
});
await worker.handleCommand({ id: "cmd-1", action: "tab.version", params: {} });
assert.equal(posts.length, 2, "one retry after HTTP 500");
assert.ok(posts.every((p) => p.id === "cmd-1" && p.ok === true), "the retry re-sends the success payload, not an error payload");
posts.length = 0;
postStatus = [0, 0, 0];
await worker.handleCommand({ id: "cmd-2", action: "tab.version", params: {} });
assert.equal(posts.length, 3, "network failures retry up to the attempt limit");
assert.ok(posts.every((p) => p.ok === true), "a failed success-post never turns into a second, error result");
posts.length = 0;
postStatus = [404];
await worker.handleCommand({ id: "cmd-3", action: "no.such.action", params: {} });
assert.equal(posts.length, 1, "4xx (unknown command id) is final");
assert.equal(posts[0].ok, false);
assert.match(posts[0].error, /Unknown action/);
});
test("cdp.call widens only its own command deadline", () => {
const worker = loadWorker(async () => { throw new Error("offline"); });
assert.equal(worker.commandTimeoutMs("page.click", { timeoutMs: 90_000 }), 25_000);
assert.equal(worker.commandTimeoutMs("cdp.call", {}), 25_000);
assert.equal(worker.commandTimeoutMs("cdp.call", { timeoutMs: 60_000 }), 65_000);
assert.equal(worker.commandTimeoutMs("cdp.call", { timeoutMs: 10_000_000 }), 125_000, "capped below the MV3 worker lifetime");
});
// ---- index.ts: a subagent session loads pi-chrome again in the same process. ----
function loadFactoryPrelude(globalState, root) {
const from = indexSource.indexOf("\tconst alreadyLoaded = globalState[PI_CHROME_GLOBAL_KEY];");
const to = indexSource.indexOf("\tconst bridge = new ChromeProfileBridge(", from);
assert.ok(from > 0 && to > from, "factory prelude section");
const body = stripTypeScriptTypes(`(() => {\n${indexSource.slice(from, to)}\nreturn true;\n})()`);
const warnings = [];
const token = Symbol(root);
const sandbox = {
globalState, currentRoot: root, instanceToken: token, PI_CHROME_GLOBAL_KEY: "loaded", PI_CHROME_VERSION: "9.9.9",
console: { warn: (msg) => warnings.push(msg) },
};
const loaded = vm.runInNewContext(body, sandbox);
return { loaded: loaded === true, token, warnings };
}
test("a same-root second load (subagent session) is not skipped, and does not steal the singleton flag", () => {
const globalState = {};
const parent = loadFactoryPrelude(globalState, "/pkg/pi-chrome");
assert.equal(parent.loaded, true);
assert.equal(globalState.loaded.token, parent.token);
const subagent = loadFactoryPrelude(globalState, "/pkg/pi-chrome");
assert.equal(subagent.loaded, true, "subagent gets its own pi-chrome instance and chrome_* tools");
assert.equal(subagent.warnings.length, 0);
assert.equal(globalState.loaded.token, parent.token, "only the first instance owns (and later clears) the flag");
});
test("a second install root is still treated as a duplicate", () => {
const globalState = {};
loadFactoryPrelude(globalState, "/npm/pi-chrome");
const other = loadFactoryPrelude(globalState, "/checkout/pi-chrome");
assert.equal(other.loaded, false);
assert.match(other.warnings[0], /already loaded from \/npm\/pi-chrome/);
});
test("a stale same-root flag from an older release does not block loading", () => {
const globalState = { loaded: { version: "0.15.19", root: "/pkg/pi-chrome" } };
assert.equal(loadFactoryPrelude(globalState, "/pkg/pi-chrome").loaded, true);
});
|