all repos — sz-chrome-remote-extension @ master

pi-chrome, patched to drive a signed-in browser on another host through a broker, with an MCP server

test-suite/unit/grant-scope.test.mjs (view raw)

 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);
});