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
|
// Exercise the shipped cleanup helper and shutdown listener, without opening a bridge or Chrome.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { stripTypeScriptTypes } from "node:module";
const source = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/index.ts", import.meta.url), "utf8");
function section(start, end) {
const from = source.indexOf(start);
const to = source.indexOf(end, from);
assert.ok(from >= 0 && to > from, `Missing source section: ${start}`);
return source.slice(from, to);
}
const helper = stripTypeScriptTypes(section("const cleanupAutomationTargetBestEffort =", "const lockChromeControl ="));
const listener = section('pi.on("session_shutdown",', 'pi.on("before_agent_start",');
function harness(send, sessionKey = "session:test") {
const timers = new Map();
const events = [];
let shutdown;
const token = Symbol();
const globalState = { loaded: { token }, auth: { until: "indefinite" } };
const context = {
AbortController, Promise,
setTimeout(fn, ms) { const key = Symbol(); timers.set(key, { fn, ms }); return key; },
clearTimeout(key) { timers.delete(key); },
bridge: { send(...args) { events.push("send"); return send(...args); }, stop() { events.push("stop"); } },
sessionCtx: {}, sessionKeyFor: () => sessionKey ?? undefined,
clearAuthExpiryTimer() {}, clearCountdownInterval() {},
globalState, PI_CHROME_GLOBAL_KEY: "loaded", PI_CHROME_AUTH_KEY: "auth", instanceToken: token,
pi: { on(name, handler) { assert.equal(name, "session_shutdown"); shutdown = handler; } },
};
vm.runInNewContext(`${helper}\n${listener}`, context);
return { shutdown, timers, events, globalState };
}
// Delivery must settle before bridge.stop; reload does not issue cleanup at all.
{
let complete;
let request;
const h = harness((...args) => {
request = args;
return new Promise((resolve) => { complete = resolve; });
});
const done = h.shutdown({ reason: "quit" });
assert.deepEqual(h.events, ["send"]);
assert.equal(request[0], "automation.cleanup");
assert.equal(request[1].sessionKey, "session:test");
assert.equal(request[2], 2000);
assert.equal(request[3].aborted, false);
complete();
await done;
assert.deepEqual(h.events, ["send", "stop"]);
assert.equal(h.timers.size, 0);
assert.equal(h.globalState.loaded, undefined);
}
{
const h = harness(() => { throw new Error("reload must not send"); });
await h.shutdown({ reason: "reload" });
assert.deepEqual(h.events, ["stop"]);
assert.equal(h.timers.size, 0);
}
// Even an owner request that never settles cannot hold up shutdown past its budget.
{
let signal;
const h = harness((_action, _params, _timeout, s) => { signal = s; return new Promise(() => {}); });
const done = h.shutdown({});
const [timer] = h.timers.values();
assert.equal(timer.ms, 2000);
timer.fn();
await done;
assert.equal(signal.aborted, true);
assert.deepEqual(h.events, ["send", "stop"]);
assert.equal(h.timers.size, 0);
}
for (const reason of ["quit", "new", "resume", "fork"]) {
const h = harness(() => Promise.resolve({}));
await h.shutdown({ reason });
assert.deepEqual(h.events, ["send", "stop"]);
// A /chrome authorize grant stays with its session: a session switch drops it, quit leaves it
// for a parent session sharing this process.
assert.equal("auth" in h.globalState, reason === "quit", `grant after ${reason}`);
}
{
const h = harness(() => Promise.resolve({}));
await h.shutdown({ reason: "reload" });
assert.deepEqual(h.globalState.auth, { until: "indefinite" }, "grant after reload");
}
for (const send of [() => Promise.reject(new Error("offline")), () => { throw new Error("offline"); }]) {
const h = harness(send);
await h.shutdown({ reason: "exit" });
assert.deepEqual(h.events, ["send", "stop"]);
assert.equal(h.timers.size, 0);
}
{
const h = harness(() => { throw new Error("unscoped cleanup must not send"); }, null);
await h.shutdown({ reason: "exit" });
assert.deepEqual(h.events, ["stop"]);
}
console.log("session-cleanup: shutdown ordering, reload, deadline, failures, missing identity, and grant scope passed");
|