pi-chrome 0.15.12 → 0.15.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-facing changes to `pi-chrome`.
|
|
4
4
|
|
|
5
|
+
## 0.15.14 — 2026-05-14
|
|
6
|
+
|
|
7
|
+
- **Clearer consent wait state.** After the Chrome approval page opens, Pi now says “Approve or deny the Chrome approval page to continue” instead of looking stuck at the launch step.
|
|
8
|
+
|
|
9
|
+
## 0.15.13 — 2026-05-14
|
|
10
|
+
|
|
11
|
+
- **Fix Chrome-side consent hang.** `/chrome authorize` now launches the browser consent page as a short command, then polls for the decision. This avoids holding one long extension command open while the user reads/clicks the page, which could leave Pi stuck at “Opening Chrome approval page…”.
|
|
12
|
+
|
|
5
13
|
## 0.15.12 — 2026-05-14
|
|
6
14
|
|
|
7
15
|
- **Docs accuracy.** Clarified that the bundled Chrome extension currently polls `127.0.0.1:17318`; custom bridge ports are not supported without editing/reloading the extension source. Also softened the unpacked-extension rationale to avoid overstating Web Store limitations and fixed stale strict-CSP guidance for `chrome_evaluate`.
|
|
@@ -4,7 +4,8 @@ const POLL_ERROR_BACKOFF_MS = 2000;
|
|
|
4
4
|
const CONSENT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
5
5
|
let polling = false;
|
|
6
6
|
let nextConsentRequestId = 1;
|
|
7
|
-
const pendingConsentRequests = new Map(); // id -> { request,
|
|
7
|
+
const pendingConsentRequests = new Map(); // id -> { request, timer, tabId }
|
|
8
|
+
const completedConsentRequests = new Map(); // id -> { approved, reason, id, decidedAt }
|
|
8
9
|
|
|
9
10
|
// =================== Chrome input (CDP) layer ===================
|
|
10
11
|
// Tracks which tabs we have attached chrome.debugger to.
|
|
@@ -607,35 +608,45 @@ function consentRequestSnapshot(id, request) {
|
|
|
607
608
|
};
|
|
608
609
|
}
|
|
609
610
|
|
|
611
|
+
function completeBrowserConsent(id, approved, reason, closeTab = true) {
|
|
612
|
+
const pending = pendingConsentRequests.get(id);
|
|
613
|
+
if (!pending) return completedConsentRequests.get(id) || null;
|
|
614
|
+
pendingConsentRequests.delete(id);
|
|
615
|
+
clearTimeout(pending.timer);
|
|
616
|
+
if (closeTab && pending.tabId) chrome.tabs.remove(pending.tabId).catch(() => undefined);
|
|
617
|
+
const result = { approved, reason, id, decidedAt: Date.now() };
|
|
618
|
+
completedConsentRequests.set(id, result);
|
|
619
|
+
setTimeout(() => completedConsentRequests.delete(id), 10 * 60 * 1000);
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
|
|
610
623
|
async function requestBrowserConsent(params) {
|
|
611
624
|
const id = String(nextConsentRequestId++);
|
|
612
625
|
const request = {
|
|
613
626
|
...params,
|
|
614
627
|
requestedAt: Date.now(),
|
|
615
628
|
};
|
|
629
|
+
const timer = setTimeout(() => completeBrowserConsent(id, false, "timed out waiting for browser approval"), CONSENT_TIMEOUT_MS);
|
|
630
|
+
pendingConsentRequests.set(id, { request, timer, tabId: null });
|
|
616
631
|
const url = chrome.runtime.getURL(`consent.html?id=${encodeURIComponent(id)}`);
|
|
617
|
-
const decision = new Promise((resolve) => {
|
|
618
|
-
const finish = (approved, reason) => {
|
|
619
|
-
const pending = pendingConsentRequests.get(id);
|
|
620
|
-
if (!pending) return;
|
|
621
|
-
pendingConsentRequests.delete(id);
|
|
622
|
-
clearTimeout(pending.timer);
|
|
623
|
-
if (pending.tabId) chrome.tabs.remove(pending.tabId).catch(() => undefined);
|
|
624
|
-
resolve({ approved, reason, id, decidedAt: Date.now() });
|
|
625
|
-
};
|
|
626
|
-
const timer = setTimeout(() => finish(false, "timed out waiting for browser approval"), CONSENT_TIMEOUT_MS);
|
|
627
|
-
pendingConsentRequests.set(id, { request, resolve: finish, timer, tabId: null });
|
|
628
|
-
});
|
|
629
632
|
try {
|
|
630
633
|
const tab = await chrome.tabs.create({ url, active: true });
|
|
631
634
|
if (tab.windowId !== undefined) await chrome.windows.update(tab.windowId, { focused: true }).catch(() => undefined);
|
|
632
635
|
const pending = pendingConsentRequests.get(id);
|
|
633
636
|
if (pending) pending.tabId = tab.id;
|
|
634
637
|
} catch (error) {
|
|
635
|
-
|
|
636
|
-
if (pending) pending.resolve(false, `could not open consent tab: ${error?.message || error}`);
|
|
638
|
+
completeBrowserConsent(id, false, `could not open consent tab: ${error?.message || error}`, false);
|
|
637
639
|
}
|
|
638
|
-
return
|
|
640
|
+
return { id, opened: pendingConsentRequests.has(id), timeoutMs: CONSENT_TIMEOUT_MS };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function browserConsentStatus(params) {
|
|
644
|
+
const id = String(params?.id || "");
|
|
645
|
+
const completed = completedConsentRequests.get(id);
|
|
646
|
+
if (completed) return { state: "done", ...completed };
|
|
647
|
+
const pending = pendingConsentRequests.get(id);
|
|
648
|
+
if (pending) return { state: "pending", id, requestedAt: pending.request.requestedAt, timeoutMs: CONSENT_TIMEOUT_MS };
|
|
649
|
+
return { state: "missing", id, approved: false, reason: "consent request expired or not found" };
|
|
639
650
|
}
|
|
640
651
|
|
|
641
652
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
@@ -648,12 +659,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
648
659
|
}
|
|
649
660
|
if (message.type === "piChromeConsentDecision") {
|
|
650
661
|
const id = String(message.id || "");
|
|
651
|
-
const
|
|
652
|
-
if (!
|
|
662
|
+
const result = completeBrowserConsent(id, message.approved === true, message.approved === true ? "approved in Chrome" : "denied in Chrome");
|
|
663
|
+
if (!result) {
|
|
653
664
|
sendResponse({ ok: false, error: "Consent request expired or not found" });
|
|
654
665
|
return true;
|
|
655
666
|
}
|
|
656
|
-
pending.resolve(message.approved === true, message.approved === true ? "approved in Chrome" : "denied in Chrome");
|
|
657
667
|
sendResponse({ ok: true });
|
|
658
668
|
return true;
|
|
659
669
|
}
|
|
@@ -662,11 +672,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
662
672
|
|
|
663
673
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
664
674
|
for (const [id, pending] of pendingConsentRequests) {
|
|
665
|
-
if (pending.tabId === tabId)
|
|
666
|
-
pending.resolve(false, "consent tab closed");
|
|
667
|
-
pendingConsentRequests.delete(id);
|
|
668
|
-
clearTimeout(pending.timer);
|
|
669
|
-
}
|
|
675
|
+
if (pending.tabId === tabId) completeBrowserConsent(id, false, "consent tab closed", false);
|
|
670
676
|
}
|
|
671
677
|
});
|
|
672
678
|
|
|
@@ -767,6 +773,8 @@ async function dispatch(action, params) {
|
|
|
767
773
|
};
|
|
768
774
|
case "consent.request":
|
|
769
775
|
return requestBrowserConsent(params);
|
|
776
|
+
case "consent.status":
|
|
777
|
+
return browserConsentStatus(params);
|
|
770
778
|
case "tab.list":
|
|
771
779
|
return (await chrome.tabs.query({})).map(formatTab);
|
|
772
780
|
case "tab.new": {
|
|
@@ -463,6 +463,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
463
463
|
return bridge.send(action, params, timeoutMs);
|
|
464
464
|
};
|
|
465
465
|
|
|
466
|
+
const sleep = (ms: number) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
467
|
+
|
|
466
468
|
// Translate the public `background` parameter (default false = visible/foreground) into the
|
|
467
469
|
// service worker's wire-level `foreground` flag, accepting legacy `foreground` as a fallback.
|
|
468
470
|
const withBackground = <T extends Record<string, unknown>>(params: T): T => {
|
|
@@ -610,14 +612,14 @@ Usage rules:
|
|
|
610
612
|
|
|
611
613
|
const authorizeFor = async (ctx: ExtensionContext, label: string, until: number | "indefinite") => {
|
|
612
614
|
ctx.ui.notify("Opening Chrome approval page…", "info");
|
|
613
|
-
let
|
|
615
|
+
let request: { id?: string; opened?: boolean; timeoutMs?: number };
|
|
614
616
|
try {
|
|
615
|
-
|
|
617
|
+
request = (await bridge.send("consent.request", {
|
|
616
618
|
durationLabel: label,
|
|
617
619
|
workspace: workspaceCwd(ctx),
|
|
618
620
|
pid: process.pid,
|
|
619
621
|
piChromeVersion: PI_CHROME_VERSION,
|
|
620
|
-
},
|
|
622
|
+
}, 10_000)) as { id?: string; opened?: boolean; timeoutMs?: number };
|
|
621
623
|
} catch (error) {
|
|
622
624
|
const message = (error as Error).message;
|
|
623
625
|
const hint = message.includes("Unknown action: consent.request")
|
|
@@ -626,12 +628,37 @@ Usage rules:
|
|
|
626
628
|
ctx.ui.notify(`Chrome approval failed: ${message}\n${hint}`, "warning");
|
|
627
629
|
return;
|
|
628
630
|
}
|
|
629
|
-
if (!
|
|
630
|
-
ctx.ui.notify(
|
|
631
|
+
if (!request.id) {
|
|
632
|
+
ctx.ui.notify("Chrome approval failed: companion extension did not return a consent request id.", "warning");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (!request.opened) {
|
|
636
|
+
ctx.ui.notify("Chrome approval failed: companion extension could not open the consent page.", "warning");
|
|
631
637
|
return;
|
|
632
638
|
}
|
|
633
|
-
|
|
634
|
-
|
|
639
|
+
ctx.ui.notify("Approve or deny the Chrome approval page to continue.", "info");
|
|
640
|
+
|
|
641
|
+
const deadline = Date.now() + (request.timeoutMs ?? 5 * 60_000);
|
|
642
|
+
while (Date.now() < deadline + 2_000) {
|
|
643
|
+
await sleep(700);
|
|
644
|
+
let status: { state?: string; approved?: boolean; reason?: string };
|
|
645
|
+
try {
|
|
646
|
+
status = (await bridge.send("consent.status", { id: request.id }, 5_000)) as { state?: string; approved?: boolean; reason?: string };
|
|
647
|
+
} catch (error) {
|
|
648
|
+
ctx.ui.notify(`Chrome approval failed: ${(error as Error).message}`, "warning");
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (status.state !== "pending") {
|
|
652
|
+
if (!status.approved) {
|
|
653
|
+
ctx.ui.notify(`Chrome control remains locked${status.reason ? ` (${status.reason})` : ""}.`, "info");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
chromeAuthorizedUntil = until;
|
|
657
|
+
ctx.ui.notify(`Chrome control authorized for ${label}.`, "info");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
ctx.ui.notify("Chrome control remains locked (timed out waiting for browser approval).", "info");
|
|
635
662
|
};
|
|
636
663
|
|
|
637
664
|
const parseAuthorizeArg = (arg: string): { label: string; until: number | "indefinite" } | undefined => {
|