@xopcai/xopc 0.0.272 → 0.0.274
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/README.md +1 -5
- package/README.zh-CN.md +1 -5
- package/dist/browser-ext/dist/assets/{auth-C1QI-uMG.js → auth-O8RiOF3l.js} +265 -100
- package/dist/browser-ext/dist/background.js +1 -22
- package/dist/browser-ext/dist/sidepanel.html +1 -1
- package/dist/browser-ext/dist/sidepanel.js +68 -36
- package/dist/browser-ext/manifest.json +1 -12
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-DffqjeqQ.js → activity-store-SJZ5fS17.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cwj4HCYY.js → agent-avatar-dicebear-BckaANxj.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-6CLfLxVc.js → agent-defaults-settings-panel-B8eyC6K1.js} +1 -1
- package/dist/gateway/static/root/assets/agents-CSWj_yo1.js +1 -0
- package/dist/gateway/static/root/assets/{api-CGLdye4i.js → api-CnJAVHBu.js} +1 -1
- package/dist/gateway/static/root/assets/app-link-BiBXVEvp.js +1 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BLujSbaw.js → app-management-settings-panel-DY-Zbgmt.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BXMcueb8.js → appearance-settings-CoJR7ZRl.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BEgy4hY3.js → apps-page-CSMvkuot.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-D4bUampf.js → archive-plugin-BwAlRT53.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-load-R835uxgL.js +1 -0
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-BDTD8YUe.js → attachment-preview-renderer-CfEDFYPE.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +5 -0
- package/dist/gateway/static/root/assets/{automations-page-dQyolNmy.js → automations-page-BEZy0P_u.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-bbvp81h2.js → binary-plugins-CESFx3yb.js} +2 -2
- package/dist/gateway/static/root/assets/{block-editor-C8e6Wri4.js → block-editor-DDJcf2wR.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-oJWJtQa6.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-CAcJQSjN.js → browser-automations-page-BH4lSfr5.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CfOPNnVr.js → channels-status-swr-BTT2sIGg.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-9ZOl92kh.js → chat-terminal-dock-D8U97O95.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-Dj6FRLF9.js → date-picker-7sDSMswL.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D1q89tVc.js → dependency-picker-DMIC86ZW.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DfqZBvhp.js → desktop-pet-BtdiY5jK.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-HCEh2MOm.js → desktop-pet-settings-D_B8azDH.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.js +1 -0
- package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-C8cAWVoK.js → extension-debug-page-CoFKb01d.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-5N87Vaym.js → extension-page-CEkNsCAY.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-D85SAUFP.js → extension-settings-page-Bi2R8P5j.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-BjsDnB_n.js → gateway-config-swr-cd8owslz.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BIf0eLWX.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-BpDWGE1a.js → heartbeat-settings-DQtelFiU.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-1m8xTVS_.js → image-generation-api-DtMRG1oF.js} +1 -1
- package/dist/gateway/static/root/assets/{index-C6PmD0cK.js → index-SxqsTD4i.js} +35 -35
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Byy8mCSq.js → keyboard-shortcuts-settings-BuYmZS1K.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-BPo0wBgM.js → local-apps-page-CExnZyGZ.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-CRNble2Z.js → locale-store-BkJ7g35d.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-X5FdLlXK.js → logs-page-CA5ArMoN.js} +2 -2
- package/dist/gateway/static/root/assets/management-settings-1UiJPrkX.js +1 -0
- package/dist/gateway/static/root/assets/markdown-view-B2IR4E2r.js +201 -0
- package/dist/gateway/static/root/assets/{media-plugins-Bzka9qXu.js → media-plugins-nNFOk9HL.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-DH_HCgmw.js → messages-BGc-OJWg.js} +2 -2
- package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-Ys0p65Lt.js → page-tabs-CRombigq.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-iknB5xEp.js → preview-open-alternatives-Bcuj43lk.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-card-BPZDgNNR.js → proactive-card-Cb3Xeoyx.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-page-CxdZR-p2.js → proactive-page-TbF9KVpm.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-W1o3f-kz.js → product-open-page-DsQcgZkI.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-DmYFpjsH.js → registry-api-CTq8EECj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DP1XyYO8.js → runtime-tools-settings-panel-BudlvBu-.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-BMYKdzsH.js → schema-form-Ch3MEX4h.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-2LGjz7bu.js → settings-form-section-CIA1AA-A.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-7DjpC9_C.js → settings-page-B0Khoduh.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-Dj9jFWJt.js → setup-status-panel-COJFl4N_.js} +1 -1
- package/dist/gateway/static/root/assets/share-preview-page-Cj9XWg-X.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BM4Ezjbx.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-DNEScMZH.js → skills-page-Dyh1kf9t.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-q88l65YU.js → skills-page.utils-DsQZkMti.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-8NIssGKj.js → system-settings-panel-BSho_Tpk.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-BXZG3S25.js → theme-store-mV6Cm2vj.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-CWsRPM84.js → tunnel-api-4DUHxSQG.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-BwQuoTQU.js → use-autosave-BG8Yx7hj.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-Bd03Ic0s.js → voice-api-key-field-DYXwDhHq.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-gggUp3u9.js → voice-config-api-D3DdcH4H.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DF8PR_pK.js → work-discovery-overlay-CQqjzaRo.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiAAWlir.js → workflow-run-setup-panel-C5H-Fd-U.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +6 -0
- package/dist/gateway/static/root/index.html +10 -9
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
- package/dist/src/agent/service.js +20 -16
- package/dist/src/browser/native-messaging-host.d.ts +1 -1
- package/dist/src/browser/native-messaging-host.js +3 -2
- package/dist/src/browser/providers/browser-ext-install.d.ts +6 -0
- package/dist/src/browser/providers/browser-ext-install.js +16 -1
- package/dist/src/cli/commands/gateway/run-foreground.js +8 -5
- package/dist/src/cli/commands/gateway/service.js +4 -3
- package/dist/src/cli/commands/gateway/token.js +28 -1
- package/dist/src/gateway/device-routes.js +4 -3
- package/dist/src/gateway/hono/app.js +5 -2
- package/dist/src/gateway/hono/routes/agent-stream.js +1 -1
- package/dist/src/gateway/hono/routes/browser.js +26 -0
- package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +9 -2
- package/dist/src/gateway/hono/routes/device-pairing-approval.js +50 -10
- package/dist/src/gateway/hono/routes/devices.d.ts +2 -1
- package/dist/src/gateway/hono/routes/devices.js +33 -16
- package/dist/src/gateway/hono/routes/session-input-handler.d.ts +1 -1
- package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
- package/dist/src/gateway/hono/routes/side-chats.js +1 -1
- package/dist/src/gateway/hono/routes/tunnel.js +34 -2
- package/dist/src/gateway/rate-limit/buckets.js +2 -1
- package/dist/src/gateway/service/run-gateway-agent.js +1 -1
- package/dist/src/storage/sqlite/device-pairing-approval.js +4 -4
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/tunnel/xopc-cloud-registration.d.ts +5 -0
- package/dist/src/tunnel/xopc-cloud-registration.js +17 -11
- package/package.json +3 -3
- package/dist/browser-ext/dist/pairing-handoff.js +0 -51
- package/dist/gateway/static/root/assets/agents-B6mu64fY.js +0 -1
- package/dist/gateway/static/root/assets/attachment-load-CBGNA2Ys.js +0 -2
- package/dist/gateway/static/root/assets/attachment-process-heavy-B2_kr5EA.js +0 -11
- package/dist/gateway/static/root/assets/automations-workspace-DxRFrmrV.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-TLAlqcPo.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-z-K_jrZz.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-Dzrk33aB.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-B1dIoRNi.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-CPth92s6.js +0 -1
- package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-Dqx8PmcT.js +0 -1
- package/dist/gateway/static/root/assets/home-page-DJaqpC8-.js +0 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-l2AEiG2C.js +0 -2
- package/dist/gateway/static/root/assets/management-settings-DT6NoIjm.js +0 -1
- package/dist/gateway/static/root/assets/markdown-view-AglreSOj.js +0 -201
- package/dist/gateway/static/root/assets/note-detail-panel-BUJPoaGZ.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BwFpWRuo.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-BU1RKAcy.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-z0X0tL8G.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-NNTut2I8.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-B6SsHlz5.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BQu8bAcN.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BxYdSi8c.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-CMqE0KuK.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BXqSJymN.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-L6dp9v2y.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-D4BucMrZ.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-DryKUMfA.js +0 -6
package/README.md
CHANGED
|
@@ -28,14 +28,10 @@
|
|
|
28
28
|
|
|
29
29
|
<p align="center">
|
|
30
30
|
<a href="https://xopcai.github.io/xopc/xopc-desktop.mp4">
|
|
31
|
-
<img src="docs/public/xopc-desktop-preview.gif" alt="xopc desktop
|
|
31
|
+
<img src="docs/public/xopc-desktop-preview.gif" alt="xopc desktop app demo" width="960">
|
|
32
32
|
</a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
|
-
<p align="center">
|
|
36
|
-
<a href="https://xopcai.github.io/xopc/xopc-desktop.mp4"><strong>Watch the full onboarding video (1:02) →</strong></a>
|
|
37
|
-
</p>
|
|
38
|
-
|
|
39
35
|
> After setup, try: **“One thing I want to move forward this week is ____. Help me find the smallest credible next step.”**
|
|
40
36
|
|
|
41
37
|
<details>
|
package/README.zh-CN.md
CHANGED
|
@@ -28,14 +28,10 @@
|
|
|
28
28
|
|
|
29
29
|
<p align="center">
|
|
30
30
|
<a href="https://xopcai.github.io/xopc/xopc-desktop.mp4">
|
|
31
|
-
<img src="docs/public/xopc-desktop-preview.gif" alt="xopc
|
|
31
|
+
<img src="docs/public/xopc-desktop-preview.gif" alt="xopc 桌面应用演示" width="960">
|
|
32
32
|
</a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
|
-
<p align="center">
|
|
36
|
-
<a href="https://xopcai.github.io/xopc/xopc-desktop.mp4"><strong>观看完整 onboarding 视频(1:02)→</strong></a>
|
|
37
|
-
</p>
|
|
38
|
-
|
|
39
35
|
> 安装后可以直接告诉它:**“这周我最想推进的一件事是 ____。帮我找到最小但可信的下一步。”**
|
|
40
36
|
|
|
41
37
|
<details>
|
|
@@ -5655,7 +5655,34 @@ class RealtimeClient {
|
|
|
5655
5655
|
}
|
|
5656
5656
|
}
|
|
5657
5657
|
|
|
5658
|
-
|
|
5658
|
+
const DEVICE_PAIRING_VERSION = 3;
|
|
5659
|
+
const BROWSER_PAIRING_INVITATION_VERSION = 1;
|
|
5660
|
+
const BROWSER_PAIRING_INVITATION_PREFIX = `XOPC-BROWSER-INVITE-V${BROWSER_PAIRING_INVITATION_VERSION}:`;
|
|
5661
|
+
const devicePairingTargetKindSchema = _enum(["mobile", "browser"]);
|
|
5662
|
+
const devicePairingRouteSchema = strictObject({
|
|
5663
|
+
id: string().trim().min(1).max(80),
|
|
5664
|
+
kind: _enum(["xopc-secure-link", "tailscale", "custom-https", "local-browser"]),
|
|
5665
|
+
url: string().url()
|
|
5666
|
+
}).superRefine((route, context) => {
|
|
5667
|
+
const url = new URL(route.url);
|
|
5668
|
+
const isOrigin = !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
|
|
5669
|
+
const valid = route.kind === "local-browser" ? url.protocol === "http:" && url.hostname === "127.0.0.1" && isOrigin : url.protocol === "https:" && isOrigin;
|
|
5670
|
+
if (!valid) context.addIssue({ code: "custom", path: ["url"], message: "Pairing route must be a secure origin" });
|
|
5671
|
+
});
|
|
5672
|
+
const devicePairingInvitationPayloadSchema = strictObject({
|
|
5673
|
+
version: literal(DEVICE_PAIRING_VERSION),
|
|
5674
|
+
targetKind: devicePairingTargetKindSchema,
|
|
5675
|
+
pairingToken: string().regex(/^xopc_pair_[0-9a-f-]{36}_[A-Za-z0-9_-]{43}$/i),
|
|
5676
|
+
gatewayId: string().uuid(),
|
|
5677
|
+
gatewayName: string().trim().min(1).max(120),
|
|
5678
|
+
gatewayPublicKey: string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
5679
|
+
routes: array(devicePairingRouteSchema).min(1).max(8),
|
|
5680
|
+
expiresAt: number().int().positive()
|
|
5681
|
+
}).superRefine((payload, context) => {
|
|
5682
|
+
if (payload.targetKind === "mobile" && payload.routes.some((route) => route.kind === "local-browser")) {
|
|
5683
|
+
context.addIssue({ code: "custom", path: ["routes"], message: "Mobile invitations require remote routes" });
|
|
5684
|
+
}
|
|
5685
|
+
});
|
|
5659
5686
|
const devicePairingKeySchema = strictObject({
|
|
5660
5687
|
kty: literal("EC"),
|
|
5661
5688
|
crv: literal("P-256"),
|
|
@@ -5691,6 +5718,18 @@ devicePairingProofSchema.extend({
|
|
|
5691
5718
|
idempotencyKey: string().uuid(),
|
|
5692
5719
|
initialRefreshToken: initialDeviceRefreshTokenSchema
|
|
5693
5720
|
});
|
|
5721
|
+
function isRetryableDevicePairingHttpStatus(status) {
|
|
5722
|
+
return status >= 500 || [404, 408, 425, 429].includes(status);
|
|
5723
|
+
}
|
|
5724
|
+
function readBrowserPairingInvitation(value) {
|
|
5725
|
+
const invitation = value.trim();
|
|
5726
|
+
if (!invitation.startsWith(BROWSER_PAIRING_INVITATION_PREFIX)) {
|
|
5727
|
+
throw new Error("Browser pairing invitation is invalid");
|
|
5728
|
+
}
|
|
5729
|
+
const encodedPayload = invitation.slice(BROWSER_PAIRING_INVITATION_PREFIX.length);
|
|
5730
|
+
if (!/^[A-Za-z0-9_-]+$/.test(encodedPayload)) throw new Error("Browser pairing invitation is invalid");
|
|
5731
|
+
return encodedPayload;
|
|
5732
|
+
}
|
|
5694
5733
|
function canonical(value) {
|
|
5695
5734
|
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
5696
5735
|
if (value !== null && typeof value === "object") {
|
|
@@ -5977,12 +6016,13 @@ async function clearBrowserOutboxes() {
|
|
|
5977
6016
|
}
|
|
5978
6017
|
|
|
5979
6018
|
const PROFILE_KEY = "xopc.browser.profile";
|
|
5980
|
-
const
|
|
6019
|
+
const PAIRING_JOURNAL_KEY = "xopc.browser.pairing";
|
|
5981
6020
|
const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
|
|
5982
6021
|
const KEY_DATABASE = "xopc-browser-identity";
|
|
5983
6022
|
const KEY_STORE = "identity";
|
|
5984
6023
|
const KEY_NAME = "device-key";
|
|
5985
6024
|
const NATIVE_HOST_NAME = "ai.xopc.browser";
|
|
6025
|
+
const GATEWAY_REQUEST_TIMEOUT_MS = 8e3;
|
|
5986
6026
|
let refreshTask;
|
|
5987
6027
|
async function browserPublicKeyThumbprint(jwk) {
|
|
5988
6028
|
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
@@ -6014,13 +6054,13 @@ async function discoverLocalGateway(options) {
|
|
|
6014
6054
|
nonce
|
|
6015
6055
|
}
|
|
6016
6056
|
);
|
|
6017
|
-
if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.
|
|
6057
|
+
if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.invitation !== "string" || typeof response.expiresAt !== "number") {
|
|
6018
6058
|
if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
|
|
6019
6059
|
return void 0;
|
|
6020
6060
|
}
|
|
6021
6061
|
return {
|
|
6022
6062
|
gatewayUrl: response.gatewayUrl,
|
|
6023
|
-
|
|
6063
|
+
invitation: response.invitation,
|
|
6024
6064
|
expiresAt: response.expiresAt
|
|
6025
6065
|
};
|
|
6026
6066
|
} catch (cause) {
|
|
@@ -6049,34 +6089,14 @@ function randomNonce(bytes = 24) {
|
|
|
6049
6089
|
function createRefreshToken() {
|
|
6050
6090
|
return `xopc_rt_${crypto.randomUUID()}_${randomNonce(32)}`;
|
|
6051
6091
|
}
|
|
6052
|
-
function
|
|
6053
|
-
const
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
if (!encoded) throw new Error("Pairing link is invalid");
|
|
6059
|
-
const payload = decodeJson(encoded);
|
|
6060
|
-
if (payload.version !== 3 || payload.targetKind !== "browser" || !payload.gatewayId || !payload.pairingToken || !payload.gatewayPublicKey || !Array.isArray(payload.routes)) {
|
|
6061
|
-
throw new Error("Pairing link version is not supported");
|
|
6062
|
-
}
|
|
6063
|
-
if (payload.expiresAt <= Date.now()) throw new Error("Pairing link has expired");
|
|
6064
|
-
if (!payload.routes.length) throw new Error("Pairing link has no Gateway route");
|
|
6065
|
-
for (const route of payload.routes) {
|
|
6066
|
-
const routeUrl = new URL(route.url);
|
|
6067
|
-
if (!route.id || !["http:", "https:"].includes(routeUrl.protocol)) {
|
|
6068
|
-
throw new Error("Pairing link has an invalid Gateway route");
|
|
6069
|
-
}
|
|
6070
|
-
}
|
|
6092
|
+
function parseBrowserPairingInvitation(value, allowExpired = false) {
|
|
6093
|
+
const encoded = readBrowserPairingInvitation(value);
|
|
6094
|
+
const parsed = devicePairingInvitationPayloadSchema.safeParse(decodeJson(encoded));
|
|
6095
|
+
if (!parsed.success || parsed.data.targetKind !== "browser") throw new Error("Pairing invitation version is not supported");
|
|
6096
|
+
const payload = parsed.data;
|
|
6097
|
+
if (!allowExpired && payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
|
|
6071
6098
|
return payload;
|
|
6072
6099
|
}
|
|
6073
|
-
async function takePendingPairingLink() {
|
|
6074
|
-
const stored = await chrome.storage.session.get(PENDING_PAIRING_LINK_KEY);
|
|
6075
|
-
const link = stored[PENDING_PAIRING_LINK_KEY];
|
|
6076
|
-
if (typeof link !== "string") return void 0;
|
|
6077
|
-
await chrome.storage.session.remove(PENDING_PAIRING_LINK_KEY);
|
|
6078
|
-
return link;
|
|
6079
|
-
}
|
|
6080
6100
|
function openIdentityDatabase() {
|
|
6081
6101
|
return new Promise((resolve, reject) => {
|
|
6082
6102
|
const request = indexedDB.open(KEY_DATABASE, 1);
|
|
@@ -6153,13 +6173,18 @@ async function verifyGateway(publicKey, payload, signature) {
|
|
|
6153
6173
|
return false;
|
|
6154
6174
|
}
|
|
6155
6175
|
}
|
|
6156
|
-
|
|
6176
|
+
function requestGatewayOriginPermissions(origins) {
|
|
6177
|
+
const patterns = origins.filter((origin) => {
|
|
6178
|
+
const url = new URL(origin);
|
|
6179
|
+
return !(url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname));
|
|
6180
|
+
}).map((origin) => `${new URL(origin).origin}/*`);
|
|
6181
|
+
if (!patterns.length) return Promise.resolve(true);
|
|
6182
|
+
return chrome.permissions.request({ origins: patterns });
|
|
6183
|
+
}
|
|
6184
|
+
async function hasGatewayOriginPermission(origin) {
|
|
6157
6185
|
const url = new URL(origin);
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
if (!await chrome.permissions.request({ origins: [pattern] })) {
|
|
6161
|
-
throw new Error("Gateway site permission was not granted");
|
|
6162
|
-
}
|
|
6186
|
+
if (url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname)) return true;
|
|
6187
|
+
return chrome.permissions.contains({ origins: [`${url.origin}/*`] });
|
|
6163
6188
|
}
|
|
6164
6189
|
class GatewayHttpError extends Error {
|
|
6165
6190
|
constructor(message, status) {
|
|
@@ -6170,11 +6195,22 @@ class GatewayHttpError extends Error {
|
|
|
6170
6195
|
status;
|
|
6171
6196
|
}
|
|
6172
6197
|
async function post(origin, path, body) {
|
|
6173
|
-
const
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6198
|
+
const controller = new AbortController();
|
|
6199
|
+
const timeout = setTimeout(() => controller.abort(), GATEWAY_REQUEST_TIMEOUT_MS);
|
|
6200
|
+
let response;
|
|
6201
|
+
try {
|
|
6202
|
+
response = await fetch(`${origin}${path}`, {
|
|
6203
|
+
method: "POST",
|
|
6204
|
+
headers: { "Content-Type": "application/json" },
|
|
6205
|
+
body: JSON.stringify(body),
|
|
6206
|
+
signal: controller.signal
|
|
6207
|
+
});
|
|
6208
|
+
} catch (cause) {
|
|
6209
|
+
if (controller.signal.aborted) throw new Error(`Gateway did not respond within ${GATEWAY_REQUEST_TIMEOUT_MS / 1e3} seconds`);
|
|
6210
|
+
throw cause;
|
|
6211
|
+
} finally {
|
|
6212
|
+
clearTimeout(timeout);
|
|
6213
|
+
}
|
|
6178
6214
|
const json = await response.json().catch(() => ({}));
|
|
6179
6215
|
if (!response.ok) {
|
|
6180
6216
|
throw new GatewayHttpError(
|
|
@@ -6217,9 +6253,31 @@ async function signedPairingRequest(pair, payload, origin, requestId, action, co
|
|
|
6217
6253
|
}
|
|
6218
6254
|
return decoded;
|
|
6219
6255
|
}
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6256
|
+
function shouldTryNextRoute(cause) {
|
|
6257
|
+
return !(cause instanceof GatewayHttpError) || isRetryableDevicePairingHttpStatus(cause.status);
|
|
6258
|
+
}
|
|
6259
|
+
function isRouteFailure(cause) {
|
|
6260
|
+
if (cause instanceof GatewayHttpError) return isRetryableDevicePairingHttpStatus(cause.status);
|
|
6261
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
6262
|
+
return /network|fetch|respond|identity|route/i.test(message);
|
|
6263
|
+
}
|
|
6264
|
+
async function signedPairingRequestAcrossRoutes(pair, payload, origins, requestId, action, completion) {
|
|
6265
|
+
let lastRouteError;
|
|
6266
|
+
for (const origin of origins) {
|
|
6267
|
+
try {
|
|
6268
|
+
return { origin, result: await signedPairingRequest(pair, payload, origin, requestId, action, completion) };
|
|
6269
|
+
} catch (cause) {
|
|
6270
|
+
if (!shouldTryNextRoute(cause)) throw cause;
|
|
6271
|
+
lastRouteError = cause;
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
|
|
6275
|
+
}
|
|
6276
|
+
async function refreshAccessToken(gatewayUrls, refreshToken, pair, gateway, operation = {
|
|
6277
|
+
requestId: crypto.randomUUID(),
|
|
6278
|
+
nextRefreshToken: createRefreshToken()
|
|
6279
|
+
}) {
|
|
6280
|
+
const { nextRefreshToken, requestId } = operation;
|
|
6223
6281
|
const timestamp = Date.now();
|
|
6224
6282
|
const nonce = randomNonce();
|
|
6225
6283
|
const credentialId = refreshToken.slice("xopc_rt_".length).split("_")[0];
|
|
@@ -6230,14 +6288,30 @@ ${nonce}
|
|
|
6230
6288
|
${requestId}
|
|
6231
6289
|
${nextRefreshToken}`;
|
|
6232
6290
|
const signature = await sign(pair, proof);
|
|
6233
|
-
const
|
|
6291
|
+
const body = {
|
|
6234
6292
|
refreshToken,
|
|
6235
6293
|
nextRefreshToken,
|
|
6236
6294
|
requestId,
|
|
6237
6295
|
timestamp,
|
|
6238
6296
|
nonce,
|
|
6239
6297
|
signature
|
|
6240
|
-
}
|
|
6298
|
+
};
|
|
6299
|
+
let gatewayUrl;
|
|
6300
|
+
let response;
|
|
6301
|
+
let lastRouteError;
|
|
6302
|
+
for (const origin of gatewayUrls) {
|
|
6303
|
+
try {
|
|
6304
|
+
response = await post(origin, "/api/device-auth/refresh", body);
|
|
6305
|
+
gatewayUrl = origin;
|
|
6306
|
+
break;
|
|
6307
|
+
} catch (cause) {
|
|
6308
|
+
if (!shouldTryNextRoute(cause)) throw cause;
|
|
6309
|
+
lastRouteError = cause;
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
if (!response || !gatewayUrl) {
|
|
6313
|
+
throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
|
|
6314
|
+
}
|
|
6241
6315
|
if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error("Gateway identity could not be verified");
|
|
6242
6316
|
const responseProof = decodeJson(response.signedPayload);
|
|
6243
6317
|
if (responseProof.purpose !== "device-refresh-v3" || responseProof.gatewayId !== gateway.gatewayId || responseProof.requestId !== requestId || responseProof.nonce !== nonce || !(responseProof.expiresAt > Date.now()) || responseProof.expiresAt > Date.now() + 6e4) throw new Error("Gateway refresh response does not match this request");
|
|
@@ -6245,68 +6319,154 @@ ${nextRefreshToken}`;
|
|
|
6245
6319
|
if (!payload?.accessToken || !payload.accessTokenExpiresAt || payload.refreshToken !== nextRefreshToken) {
|
|
6246
6320
|
throw new Error("Gateway returned invalid browser credentials");
|
|
6247
6321
|
}
|
|
6248
|
-
return payload;
|
|
6322
|
+
return { ...payload, gatewayUrl };
|
|
6249
6323
|
}
|
|
6250
6324
|
function wait(milliseconds) {
|
|
6251
6325
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6252
6326
|
}
|
|
6253
|
-
async function pairGateway(
|
|
6254
|
-
const payload =
|
|
6327
|
+
async function pairGateway(invitation, onApproval, requestedOrigin) {
|
|
6328
|
+
const payload = parseBrowserPairingInvitation(invitation, true);
|
|
6329
|
+
const origins = [...new Set(payload.routes.map((route) => new URL(route.url).origin))];
|
|
6330
|
+
const selectedOrigin = requestedOrigin && origins.includes(requestedOrigin) ? requestedOrigin : origins[0];
|
|
6331
|
+
const permissionTask = requestGatewayOriginPermissions([selectedOrigin]);
|
|
6332
|
+
if (!await permissionTask) throw new Error("Gateway site permission was not granted");
|
|
6333
|
+
const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
|
|
6334
|
+
origin,
|
|
6335
|
+
permitted: await hasGatewayOriginPermission(origin)
|
|
6336
|
+
})))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
|
|
6255
6337
|
const pair = await getOrCreateKeyPair();
|
|
6256
|
-
const
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
let lastRouteError;
|
|
6261
|
-
for (const candidate of [...new Set(payload.routes.map((route) => new URL(route.url).origin))]) {
|
|
6262
|
-
await requestGatewayOriginPermission(candidate);
|
|
6263
|
-
try {
|
|
6264
|
-
result = await signedPairingRequest(pair, payload, candidate, requestId, "request");
|
|
6265
|
-
origin = candidate;
|
|
6266
|
-
break;
|
|
6267
|
-
} catch (cause) {
|
|
6268
|
-
if (cause instanceof GatewayHttpError && cause.status < 500 && ![404, 408, 425, 429].includes(cause.status)) {
|
|
6269
|
-
throw cause;
|
|
6270
|
-
}
|
|
6271
|
-
lastRouteError = cause;
|
|
6272
|
-
}
|
|
6338
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6339
|
+
let journal = stored[PAIRING_JOURNAL_KEY];
|
|
6340
|
+
if (journal && journal.payload.pairingToken !== payload.pairingToken) {
|
|
6341
|
+
throw new Error("PAIRING_ALREADY_PENDING");
|
|
6273
6342
|
}
|
|
6274
|
-
if (!
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6343
|
+
if (!journal) {
|
|
6344
|
+
if (payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
|
|
6345
|
+
journal = {
|
|
6346
|
+
invitation: invitation.trim(),
|
|
6347
|
+
payload,
|
|
6348
|
+
requestId: crypto.randomUUID(),
|
|
6349
|
+
idempotencyKey: crypto.randomUUID(),
|
|
6350
|
+
initialRefreshToken: createRefreshToken()
|
|
6351
|
+
};
|
|
6352
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6281
6353
|
}
|
|
6282
|
-
|
|
6283
|
-
|
|
6354
|
+
delete journal.nextOrigin;
|
|
6355
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6356
|
+
const requestId = journal.requestId;
|
|
6357
|
+
const availableOrigins = journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : [selectedOrigin, ...permittedOrigins.filter((candidate) => candidate !== selectedOrigin)];
|
|
6358
|
+
try {
|
|
6359
|
+
let { origin, result } = await signedPairingRequestAcrossRoutes(
|
|
6360
|
+
pair,
|
|
6361
|
+
journal.payload,
|
|
6362
|
+
availableOrigins,
|
|
6363
|
+
requestId,
|
|
6364
|
+
"request"
|
|
6365
|
+
);
|
|
6366
|
+
journal.origin = origin;
|
|
6367
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6368
|
+
if (result.request.status === "pending") onApproval(result.request.confirmationCode);
|
|
6369
|
+
while (result.request.status === "pending") {
|
|
6370
|
+
await wait(1500);
|
|
6371
|
+
const response = await signedPairingRequestAcrossRoutes(
|
|
6372
|
+
pair,
|
|
6373
|
+
journal.payload,
|
|
6374
|
+
[origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
|
|
6375
|
+
requestId,
|
|
6376
|
+
"status"
|
|
6377
|
+
);
|
|
6378
|
+
({ origin, result } = response);
|
|
6379
|
+
journal.origin = origin;
|
|
6380
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6381
|
+
}
|
|
6382
|
+
if (result.request.status !== "approved" && result.request.status !== "completed") {
|
|
6383
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6384
|
+
throw new Error(`Pairing ${result.request.status}`);
|
|
6385
|
+
}
|
|
6386
|
+
if (result.request.status !== "completed") {
|
|
6387
|
+
const response = await signedPairingRequestAcrossRoutes(
|
|
6388
|
+
pair,
|
|
6389
|
+
journal.payload,
|
|
6390
|
+
[origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
|
|
6391
|
+
requestId,
|
|
6392
|
+
"complete",
|
|
6393
|
+
{
|
|
6394
|
+
idempotencyKey: journal.idempotencyKey,
|
|
6395
|
+
initialRefreshToken: journal.initialRefreshToken
|
|
6396
|
+
}
|
|
6397
|
+
);
|
|
6398
|
+
({ origin, result } = response);
|
|
6399
|
+
}
|
|
6400
|
+
if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
|
|
6401
|
+
journal.origin = origin;
|
|
6402
|
+
journal.completed = { deviceId: result.request.deviceId, gatewayName: result.gateway.name };
|
|
6403
|
+
journal.refresh ??= { requestId: crypto.randomUUID(), nextRefreshToken: createRefreshToken() };
|
|
6404
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6405
|
+
const orderedOrigins = [origin, ...permittedOrigins.filter((candidate) => candidate !== origin)];
|
|
6406
|
+
const tokens = await refreshAccessToken(
|
|
6407
|
+
orderedOrigins,
|
|
6408
|
+
journal.initialRefreshToken,
|
|
6409
|
+
pair,
|
|
6410
|
+
journal.payload,
|
|
6411
|
+
journal.refresh
|
|
6412
|
+
);
|
|
6413
|
+
const profile = {
|
|
6414
|
+
gatewayId: journal.payload.gatewayId,
|
|
6415
|
+
gatewayName: journal.completed.gatewayName,
|
|
6416
|
+
gatewayUrl: tokens.gatewayUrl,
|
|
6417
|
+
gatewayUrls: [tokens.gatewayUrl, ...orderedOrigins.filter((candidate) => candidate !== tokens.gatewayUrl)],
|
|
6418
|
+
gatewayPublicKey: journal.payload.gatewayPublicKey,
|
|
6419
|
+
deviceId: journal.completed.deviceId,
|
|
6420
|
+
refreshToken: tokens.refreshToken,
|
|
6421
|
+
accessToken: tokens.accessToken,
|
|
6422
|
+
accessTokenExpiresAt: tokens.accessTokenExpiresAt
|
|
6423
|
+
};
|
|
6424
|
+
await chrome.storage.local.set({ [PROFILE_KEY]: profile });
|
|
6425
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6426
|
+
return profile;
|
|
6427
|
+
} catch (cause) {
|
|
6428
|
+
const nextOrigin = origins.find((origin) => !permittedOrigins.includes(origin));
|
|
6429
|
+
if (nextOrigin && isRouteFailure(cause)) {
|
|
6430
|
+
journal.nextOrigin = nextOrigin;
|
|
6431
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6432
|
+
throw new Error(`PAIRING_ROUTE_PERMISSION_REQUIRED:${nextOrigin}`);
|
|
6433
|
+
}
|
|
6434
|
+
throw cause;
|
|
6284
6435
|
}
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6436
|
+
}
|
|
6437
|
+
async function readPendingBrowserPairing() {
|
|
6438
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6439
|
+
const journal = stored[PAIRING_JOURNAL_KEY];
|
|
6440
|
+
return journal ? { invitation: journal.invitation, ...journal.nextOrigin ? { nextOrigin: journal.nextOrigin } : {} } : void 0;
|
|
6441
|
+
}
|
|
6442
|
+
async function cancelPendingBrowserPairing() {
|
|
6443
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6444
|
+
const journal = stored[PAIRING_JOURNAL_KEY];
|
|
6445
|
+
if (!journal) return;
|
|
6446
|
+
const origins = [...new Set(journal.payload.routes.map((route) => new URL(route.url).origin))];
|
|
6447
|
+
const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
|
|
6448
|
+
origin,
|
|
6449
|
+
permitted: await hasGatewayOriginPermission(origin)
|
|
6450
|
+
})))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
|
|
6451
|
+
try {
|
|
6452
|
+
await signedPairingRequestAcrossRoutes(
|
|
6453
|
+
await getOrCreateKeyPair(),
|
|
6454
|
+
journal.payload,
|
|
6455
|
+
journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : permittedOrigins,
|
|
6456
|
+
journal.requestId,
|
|
6457
|
+
"cancel"
|
|
6458
|
+
);
|
|
6459
|
+
} catch (cause) {
|
|
6460
|
+
if (!(cause instanceof GatewayHttpError) || !["PAIRING_NOT_FOUND", "PAIRING_EXPIRED", "PAIRING_CANCELLED"].includes(cause.message)) throw cause;
|
|
6290
6461
|
}
|
|
6291
|
-
|
|
6292
|
-
const tokens = await refreshAccessToken(origin, initialRefreshToken, pair, payload);
|
|
6293
|
-
const profile = {
|
|
6294
|
-
gatewayId: payload.gatewayId,
|
|
6295
|
-
gatewayName: result.gateway.name,
|
|
6296
|
-
gatewayUrl: origin,
|
|
6297
|
-
gatewayPublicKey: payload.gatewayPublicKey,
|
|
6298
|
-
deviceId: result.request.deviceId,
|
|
6299
|
-
refreshToken: tokens.refreshToken,
|
|
6300
|
-
accessToken: tokens.accessToken,
|
|
6301
|
-
accessTokenExpiresAt: tokens.accessTokenExpiresAt
|
|
6302
|
-
};
|
|
6303
|
-
await chrome.storage.local.set({ [PROFILE_KEY]: profile });
|
|
6304
|
-
return profile;
|
|
6462
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6305
6463
|
}
|
|
6306
6464
|
async function readProfile() {
|
|
6307
|
-
const stored = await chrome.storage.local.get(PROFILE_KEY);
|
|
6465
|
+
const stored = await chrome.storage.local.get([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
|
|
6308
6466
|
const profile = stored[PROFILE_KEY];
|
|
6309
|
-
|
|
6467
|
+
if (!profile?.gatewayId || !profile.gatewayUrl || !profile.refreshToken) return void 0;
|
|
6468
|
+
if (stored[PAIRING_JOURNAL_KEY]) await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6469
|
+
return profile;
|
|
6310
6470
|
}
|
|
6311
6471
|
async function getAccessProfile() {
|
|
6312
6472
|
const profile = await readProfile();
|
|
@@ -6314,8 +6474,13 @@ async function getAccessProfile() {
|
|
|
6314
6474
|
if (profile.accessTokenExpiresAt > Date.now() + 3e4) return profile;
|
|
6315
6475
|
if (refreshTask) return refreshTask;
|
|
6316
6476
|
refreshTask = (async () => {
|
|
6317
|
-
const
|
|
6318
|
-
const
|
|
6477
|
+
const gatewayUrls = profile.gatewayUrls?.length ? profile.gatewayUrls : [profile.gatewayUrl];
|
|
6478
|
+
const tokens = await refreshAccessToken(gatewayUrls, profile.refreshToken, await getOrCreateKeyPair(), profile);
|
|
6479
|
+
const next = {
|
|
6480
|
+
...profile,
|
|
6481
|
+
...tokens,
|
|
6482
|
+
gatewayUrls: [tokens.gatewayUrl, ...gatewayUrls.filter((candidate) => candidate !== tokens.gatewayUrl)]
|
|
6483
|
+
};
|
|
6319
6484
|
await chrome.storage.local.set({ [PROFILE_KEY]: next });
|
|
6320
6485
|
return next;
|
|
6321
6486
|
})();
|
|
@@ -6380,7 +6545,7 @@ async function createBrowserEndpointHello(tools = []) {
|
|
|
6380
6545
|
};
|
|
6381
6546
|
}
|
|
6382
6547
|
async function forgetProfile() {
|
|
6383
|
-
await chrome.storage.local.remove(PROFILE_KEY);
|
|
6548
|
+
await chrome.storage.local.remove([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
|
|
6384
6549
|
}
|
|
6385
6550
|
async function revokeAndForgetProfile() {
|
|
6386
6551
|
try {
|
|
@@ -6403,4 +6568,4 @@ async function revokeAndForgetProfile() {
|
|
|
6403
6568
|
}
|
|
6404
6569
|
}
|
|
6405
6570
|
|
|
6406
|
-
export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T,
|
|
6571
|
+
export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, canonicalJson as b, cancelPendingBrowserPairing as c, captureTabWithPermission as d, createBrowserEndpointHello as e, currentTabDescriptor as f, deleteBrowserOutbox as g, discoverLocalGateway as h, endpointToolContentSchema as i, gatewayFetch as j, getAccessProfile as k, readPendingBrowserPairing as l, readProfile as m, registerBrowserEndpoint as n, revokeAndForgetProfile as o, pairGateway as p, runWithTabSiteAccess as q, readBrowserOutbox as r, setAutoConnectEnabled as s, writeBrowserOutbox as w };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION,
|
|
1
|
+
import { i as endpointToolContentSchema, E as ENDPOINT_PROTOCOL_VERSION, b as canonicalJson, T as TAB_BINDING_PREFIX, d as captureTabWithPermission, P as PENDING_CONTEXT_KEY, m as readProfile, n as registerBrowserEndpoint, k as getAccessProfile, R as RealtimeClient, e as createBrowserEndpointHello, j as gatewayFetch } from './assets/auth-O8RiOF3l.js';
|
|
2
2
|
|
|
3
3
|
const BROWSER_EXTENSION_PROTOCOL_VERSION = 4;
|
|
4
4
|
const BROWSER_CONTROL_ENDPOINT_TOOL_NAME = "browser.control";
|
|
@@ -1253,22 +1253,6 @@ function connectWithLogging() {
|
|
|
1253
1253
|
if (generation !== connectionGeneration && !realtime) connectWithLogging();
|
|
1254
1254
|
});
|
|
1255
1255
|
}
|
|
1256
|
-
async function stagePairingInvite(message, sender) {
|
|
1257
|
-
if (typeof message.pairingLink !== "string" || !sender.url || !sender.tab?.id) {
|
|
1258
|
-
throw new Error("Pairing handoff is missing its source tab");
|
|
1259
|
-
}
|
|
1260
|
-
const source = new URL(sender.url);
|
|
1261
|
-
const invitation = new URL(message.pairingLink);
|
|
1262
|
-
if (source.href !== invitation.href || source.protocol !== "https:" || source.hostname !== "link.xopc.ai" || source.pathname !== "/connect") {
|
|
1263
|
-
throw new Error("Pairing handoff source is not trusted");
|
|
1264
|
-
}
|
|
1265
|
-
parseBrowserPairingLink(invitation.href);
|
|
1266
|
-
const tasks = [
|
|
1267
|
-
chrome.storage.session.set({ [PENDING_PAIRING_LINK_KEY]: invitation.href })
|
|
1268
|
-
];
|
|
1269
|
-
if (message.open) tasks.push(chrome.sidePanel.open({ tabId: sender.tab.id }));
|
|
1270
|
-
await Promise.all(tasks);
|
|
1271
|
-
}
|
|
1272
1256
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
1273
1257
|
if (message.type === "browser/get-status") {
|
|
1274
1258
|
sendResponse({ connected: Boolean(endpointClaim), transport: "gateway-realtime", error: lastConnectionError });
|
|
@@ -1278,11 +1262,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1278
1262
|
disconnect();
|
|
1279
1263
|
connectWithLogging();
|
|
1280
1264
|
sendResponse({ ok: true });
|
|
1281
|
-
} else if (message.type === "browser/stage-pairing-invite") {
|
|
1282
|
-
void stagePairingInvite(message, sender).then(() => sendResponse({ ok: true })).catch((cause) => sendResponse({
|
|
1283
|
-
ok: false,
|
|
1284
|
-
error: cause instanceof Error ? cause.message : String(cause)
|
|
1285
|
-
}));
|
|
1286
1265
|
} else {
|
|
1287
1266
|
sendResponse({ ok: false, error: `Unknown message: ${message.type}` });
|
|
1288
1267
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>xopc</title>
|
|
7
7
|
<script type="module" crossorigin src="./sidepanel.js"></script>
|
|
8
|
-
<link rel="modulepreload" crossorigin href="./assets/auth-
|
|
8
|
+
<link rel="modulepreload" crossorigin href="./assets/auth-O8RiOF3l.js">
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/sidepanel-C86O7q5Y.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|