diff options
| -rw-r--r-- | extensions/chrome-profile-bridge/index.ts | 16 | ||||
| -rw-r--r-- | test-suite/unit/grant-scope.test.mjs | 30 |
2 files changed, 35 insertions, 11 deletions
diff --git a/extensions/chrome-profile-bridge/index.ts b/extensions/chrome-profile-bridge/index.ts index 0338c5c..97b9f33 100644 --- a/extensions/chrome-profile-bridge/index.ts +++ b/extensions/chrome-profile-bridge/index.ts @@ -924,9 +924,6 @@ export default function (pi: ExtensionAPI): void { const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT); let backgroundEnabled = true; let chromeAuthorizedUntil: number | "indefinite" | undefined; - // Whether the current grant stood in from PI_CHROME_AUTHORIZE rather than being given by - // the user this session. The footer announces only the latter. - let envGranted = false; // Restore an authorization that survived a /reload. Drop it if it already expired. const persistedAuth = globalState[PI_CHROME_AUTH_KEY]; if (persistedAuth) { @@ -1026,7 +1023,6 @@ export default function (pi: ExtensionAPI): void { chromeToolsUsable = false; if (logAction && wasUsable) logChromeToolChange(logAction, { authorizedUntil: undefined }); chromeAuthorizedUntil = undefined; - envGranted = false; persistAuth(); // Revoking control ends pi-chrome's automation for this session; tidy up the target we own. void cleanupAutomationTargetBestEffort(); @@ -1083,8 +1079,16 @@ export default function (pi: ExtensionAPI): void { return typeof id === "string" && id ? `session:${id}` : undefined; }; + // FORK: with PI_CHROME_AUTHORIZE set, Chrome control is standing configuration, not session + // state, so the footer never shows. Read from the environment on every update, not from any + // saved state, so no grant restored across /reload or a session switch can bring it back. + const envAuthorizes = (): boolean => { + const value = process.env.PI_CHROME_AUTHORIZE?.trim(); + return !!value && parseAuthorizeArg(value) !== undefined; + }; + const updateChromeStatus = (ctx: ExtensionContext): void => { - if (chromeControlAuthorized() && !envGranted) { + if (chromeControlAuthorized() && !envAuthorizes()) { ctx.ui.setStatus("chrome", ctx.ui.theme.fg("success", "●") + " Chrome Bridge" + authCountdownLabel()); } else { ctx.ui.setStatus("chrome", undefined); @@ -1180,7 +1184,6 @@ export default function (pi: ExtensionAPI): void { const grant = parseAuthorizeArg(autoGrant); if (grant) { chromeAuthorizedUntil = grant.until; - envGranted = true; } else { ctx.ui.notify(`Ignoring PI_CHROME_AUTHORIZE="${autoGrant}": use minutes (15m, 30, 45) or indefinite.`, "warning"); } @@ -1415,7 +1418,6 @@ Usage rules: } const wasUsable = chromeToolsUsable; chromeAuthorizedUntil = until; - envGranted = false; persistAuth(); activateChromeTools(); chromeToolsUsable = true; diff --git a/test-suite/unit/grant-scope.test.mjs b/test-suite/unit/grant-scope.test.mjs index e7ce044..7717131 100644 --- a/test-suite/unit/grant-scope.test.mjs +++ b/test-suite/unit/grant-scope.test.mjs @@ -24,6 +24,8 @@ function piProcess(env) { setTimeout, clearTimeout, setInterval, clearInterval, }); let runtime; + // A grant an earlier runtime left on globalThis, e.g. a build that saved the env grant there. + const seedGrant = (until) => vm.runInContext(`globalThis.__piChromeProfileBridgeAuth__ = ${JSON.stringify({ until })}`, context); // Evaluate the module again and run its factory, as pi does at startup, on /reload and on a // session switch. const load = () => { @@ -65,7 +67,7 @@ function piProcess(env) { 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") }; + return { load, replace, seedGrant, end: () => runtime?.shutdown("quit") }; } test("PI_CHROME_AUTHORIZE grants every runtime without announcing it", async (t) => { @@ -104,18 +106,38 @@ test("/chrome authorize survives /reload and ends with its session", async (t) = } }); -test("/chrome authorize over PI_CHROME_AUTHORIZE is announced, and a switch falls back to the env grant", async (t) => { +test("with PI_CHROME_AUTHORIZE set, /chrome authorize does not bring the footer, 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\)$/); + assert.equal(first.chromeTools(), true); + assert.equal(first.footer(), undefined); const reloaded = await pi.replace("reload"); - assert.match(reloaded.footer(), /^● Chrome Bridge \(\d+m\)$/); + assert.equal(reloaded.footer(), undefined); const next = await pi.replace("new"); assert.equal(next.chromeTools(), true); assert.equal(next.footer(), undefined); }); + +test("with PI_CHROME_AUTHORIZE set, a grant left on globalThis by an older build never shows the footer", async (t) => { + for (const until of ["indefinite", Date.now() + 3_600_000]) { + const pi = piProcess({ PI_CHROME_AUTHORIZE: "indefinite" }); + t.after(() => pi.end()); + pi.seedGrant(until); + const first = pi.load(); + await first.start(); + assert.equal(first.chromeTools(), true); + assert.equal(first.footer(), undefined, `footer at start, saved until ${until}`); + for (const reason of ["reload", "new", "resume", "fork"]) { + pi.seedGrant(until); + const next = await pi.replace(reason); + assert.equal(next.chromeTools(), true, `tools after ${reason}`); + assert.equal(next.footer(), undefined, `footer after ${reason}, saved until ${until}`); + } + await pi.end(); + } +}); |
