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
|
// Which grant a session starts with, and whether the "● Chrome Bridge" footer announces it, across
// /reload and session switches. Pi re-evaluates the extension for each runtime but keeps globalThis
// for the life of the process, so each runtime here is a fresh evaluation of the shipped module in
// one shared context. The bridge binds an ephemeral loopback port; no Chrome is involved.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { createRequire, stripTypeScriptTypes } from "node:module";
import { test } from "node:test";
const indexUrl = new URL("../../extensions/chrome-profile-bridge/index.ts", import.meta.url);
const stubbed = stripTypeScriptTypes(fs.readFileSync(indexUrl, "utf8"), { mode: "transform" })
.replace(/^import \{ Text \} from "@earendil-works\/pi-tui";$/m, "const Text = class {};")
.replace(/^import \{ Type \} from "typebox";$/m, "const Type = new Proxy({}, { get: () => (value = {}) => value });")
.replace(/^import \{([^}]*)\} from "(node:[^"]+)";$/gm, (_m, names, mod) => `const {${names.replace(/\btype\s+\w+\s*,?/g, "")}} = require("${mod}");`)
.replace(/^export default function/m, "module.exports.default = function");
assert.doesNotMatch(stubbed, /^import /m, "every import must be stubbed");
// One pi process: a shared context, so globalThis outlives each runtime.
function piProcess(env) {
const fakeProcess = Object.create(process, { env: { value: { ...env, PI_CHROME_BRIDGE_PORT: "0" } } });
const context = vm.createContext({
process: fakeProcess, console, Buffer, URL, AbortController, fetch,
setTimeout, clearTimeout, setInterval, clearInterval,
});
let runtime;
// Evaluate the module again and run its factory, as pi does at startup, on /reload and on a
// session switch.
const load = () => {
const module = { exports: {} };
vm.runInContext(`(function (module, exports, require, __dirname) {\n${stubbed}\n})`, context)(
module, module.exports, createRequire(indexUrl), new URL(".", indexUrl).pathname,
);
const handlers = {}, commands = {}, status = {};
let active = [];
const pi = {
on: (name, fn) => { handlers[name] = fn; },
registerTool() {}, registerEntryRenderer() {}, appendEntry() {},
registerCommand: (name, command) => { commands[name] = command; },
getActiveTools: () => active,
setActiveTools: (tools) => { active = tools; },
};
module.exports.default(pi);
const ctx = {
cwd: process.cwd(),
ui: {
setStatus: (key, value) => { status[key] = value; },
notify() {}, confirm: async () => true, theme: { fg: (_color, text) => text },
},
};
runtime = {
footer: () => status.chrome,
chromeTools: () => active.some((name) => name.startsWith("chrome_")),
start: () => handlers.session_start({ type: "session_start" }, ctx),
shutdown: (reason) => handlers.session_shutdown({ type: "session_shutdown", reason }),
chrome: (args) => commands.chrome.handler(args, ctx),
};
return runtime;
};
// End the current runtime for `reason`, then start the next one.
const replace = async (reason) => {
await runtime.shutdown(reason);
const next = load();
await next.start();
return next;
};
// Stops the bridge and timers of the live runtime, so a failed assertion cannot hang the run.
return { load, replace, end: () => runtime?.shutdown("quit") };
}
test("PI_CHROME_AUTHORIZE grants every runtime without announcing it", async (t) => {
const pi = piProcess({ PI_CHROME_AUTHORIZE: "indefinite" });
t.after(() => pi.end());
const first = pi.load();
await first.start();
assert.equal(first.chromeTools(), true);
assert.equal(first.footer(), undefined);
for (const reason of ["reload", "new", "resume", "fork"]) {
const next = await pi.replace(reason);
assert.equal(next.chromeTools(), true, `tools after ${reason}`);
assert.equal(next.footer(), undefined, `footer after ${reason}`);
}
});
test("/chrome authorize survives /reload and ends with its session", async (t) => {
const pi = piProcess({});
t.after(() => pi.end());
const first = pi.load();
await first.start();
assert.equal(first.chromeTools(), false);
await first.chrome("authorize indefinite");
assert.equal(first.chromeTools(), true);
assert.equal(first.footer(), "● Chrome Bridge (indefinite)");
const reloaded = await pi.replace("reload");
assert.equal(reloaded.chromeTools(), true);
assert.equal(reloaded.footer(), "● Chrome Bridge (indefinite)");
for (const reason of ["new", "resume", "fork"]) {
await (await pi.replace("reload")).chrome("authorize indefinite");
const next = await pi.replace(reason);
assert.equal(next.chromeTools(), false, `tools after ${reason}`);
assert.equal(next.footer(), undefined, `footer after ${reason}`);
}
});
test("/chrome authorize over PI_CHROME_AUTHORIZE is announced, and a switch falls back to the env grant", async (t) => {
const pi = piProcess({ PI_CHROME_AUTHORIZE: "indefinite" });
t.after(() => pi.end());
const first = pi.load();
await first.start();
await first.chrome("authorize 15m");
assert.match(first.footer(), /^● Chrome Bridge \(\d+m\)$/);
const reloaded = await pi.replace("reload");
assert.match(reloaded.footer(), /^● Chrome Bridge \(\d+m\)$/);
const next = await pi.replace("new");
assert.equal(next.chromeTools(), true);
assert.equal(next.footer(), undefined);
});
|