aboutsummaryrefslogtreecommitdiff
path: root/extensions/chrome-profile-bridge/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/chrome-profile-bridge/index.ts')
-rw-r--r--extensions/chrome-profile-bridge/index.ts39
1 files changed, 23 insertions, 16 deletions
diff --git a/extensions/chrome-profile-bridge/index.ts b/extensions/chrome-profile-bridge/index.ts
index e3e5a04..fdfa39c 100644
--- a/extensions/chrome-profile-bridge/index.ts
+++ b/extensions/chrome-profile-bridge/index.ts
@@ -759,13 +759,25 @@ export default function (pi: ExtensionAPI): void {
}, { triggerTurn: false });
};
- // Close THIS session's dedicated automation window/tab. Fire-and-forget and best-effort: it
- // must never block /quit, /reload, revoke, or session end, and the service-worker side only
- // ever closes targets this session created itself (never user tabs/windows, never another
- // session's target). Errors (bridge down, target already closed) are intentionally swallowed.
- const cleanupAutomationTargetBestEffort = (): void => {
+ // Bound the entire request, including shared-owner forwarding/takeover. Revoke can launch
+ // this in the background; shutdown must wait before tearing down the bridge.
+ const cleanupAutomationTargetBestEffort = async (timeoutMs = 2_000): Promise<void> => {
const sessionKey = sessionKeyFor(sessionCtx);
- void bridge.send("automation.cleanup", sessionKey !== undefined ? { sessionKey } : {}, 3_000).catch(() => undefined);
+ if (sessionKey === undefined) return; // Never clean up an unscoped/default session.
+ const controller = new AbortController();
+ let timer: ReturnType<typeof setTimeout> | undefined;
+ try {
+ await Promise.race([
+ bridge.send("automation.cleanup", { sessionKey }, timeoutMs, controller.signal).catch(() => undefined),
+ new Promise<void>((resolveCleanup) => {
+ timer = setTimeout(() => { controller.abort(); resolveCleanup(); }, timeoutMs);
+ }),
+ ]);
+ } catch {
+ // Shutdown/revoke remains best-effort when Chrome or its bridge is unavailable.
+ } finally {
+ clearTimeout(timer);
+ }
};
const lockChromeControl = (logAction?: "revoked" | "expired"): void => {
@@ -778,7 +790,7 @@ export default function (pi: ExtensionAPI): void {
chromeAuthorizedUntil = undefined;
persistAuth();
// Revoking control ends pi-chrome's automation for this session; tidy up the target we own.
- cleanupAutomationTargetBestEffort();
+ void cleanupAutomationTargetBestEffort();
};
const authSummary = (): string => {
@@ -925,17 +937,12 @@ export default function (pi: ExtensionAPI): void {
updateChromeStatus(ctx);
});
- pi.on("session_shutdown", (event) => {
+ pi.on("session_shutdown", async (event) => {
clearAuthExpiryTimer();
clearCountdownInterval();
- // Tidy up this session's dedicated automation window on real session end, but NOT on
- // "reload": /reload tears down and re-evaluates this module while the *same* session
- // (same sessionKey) continues, so we keep the window so it is reused, not churned. The
- // call is fire-and-forget and runs before bridge.stop() so it never blocks shutdown.
- // (Owner-session quit may not deliver in time since stop() closes the bridge server;
- // that only ever leaves a clearly pi-chrome window for the user to close — never a user
- // tab — and /chrome revoke remains the reliable, bridge-alive cleanup path.)
- if (event?.reason !== "reload") cleanupAutomationTargetBestEffort();
+ // /reload continues the same session. On exit, give Chrome a bounded opportunity to
+ // close this session's created tabs and ungroup adopted tabs before stopping the broker.
+ if (event?.reason !== "reload") await cleanupAutomationTargetBestEffort();
bridge.stop();
if (globalState[PI_CHROME_GLOBAL_KEY]?.token === instanceToken) {
delete globalState[PI_CHROME_GLOBAL_KEY];