@xopcai/xopc 0.0.273 → 0.0.275
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/dist/browser-ext/_locales/en/messages.json +215 -0
- package/dist/browser-ext/_locales/zh_CN/messages.json +215 -0
- package/dist/browser-ext/dist/assets/{auth-DkwOUI6_.js → auth-ClQTK565.js} +291 -115
- package/dist/browser-ext/dist/assets/{sidepanel-C86O7q5Y.css → sidepanel-CeGqObOa.css} +41 -20
- package/dist/browser-ext/dist/background.js +36 -4
- package/dist/browser-ext/dist/sidepanel.html +2 -2
- package/dist/browser-ext/dist/sidepanel.js +457 -310
- package/dist/browser-ext/manifest.json +6 -4
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-cF7IX2RP.js → activity-store-SJZ5fS17.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BrgxX8nG.js → agent-avatar-dicebear-Dx6x7MXq.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DvhuRCLA.js → agent-defaults-settings-panel-BuxTpjkf.js} +1 -1
- package/dist/gateway/static/root/assets/agents-_f9HEOol.js +1 -0
- package/dist/gateway/static/root/assets/{api-CYkdUpkx.js → api-CnJAVHBu.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-DfoNzWIa.js → app-management-settings-panel-BYKWXcVl.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-czbLdCqj.js → appearance-settings-BmhSJc4X.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-DYTnK9lL.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-D14P13mX.js → archive-plugin-BY6ncIeE.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-load-B6cw8KtO.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-DM7ZEewI.js → automations-page-D1WwsSf3.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-CjF56MI_.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CxNg6InF.js → binary-plugins-CC-wYm0K.js} +2 -2
- package/dist/gateway/static/root/assets/{block-editor-D22VcYnC.js → block-editor-D8rh9KNe.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-6NWTLEpd.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-BrqySdbW.js → browser-automations-page-CMrffyct.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-1onvP1Se.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C9ZSJxjr.js +8 -0
- package/dist/gateway/static/root/assets/{channels-settings-yHsY174W.js → channels-settings-CvLTnI99.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-BUZimNmp.js → channels-status-swr-BTT2sIGg.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-CZPBNpMI.js → chat-terminal-dock-F_dUdZ8l.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-D7NHV8B2.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-Da_0QyLe.js → date-picker-MHuajwQ-.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D_djqzy7.js → dependency-picker-BcMJJI9X.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Celmbw-T.js → desktop-pet-DlNm_7H0.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CqNNgF45.js → desktop-pet-settings-BI1JQMnE.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-Cp5jO5B_.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-ku3kCjvH.js → extension-debug-page-DZr0GkkA.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C7FpHwL4.js → extension-page-QsHoQ6VX.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-OMj2tU2_.js → extension-settings-page-I2YX4Tzb.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-6eb1vKE1.js → gateway-config-swr-cd8owslz.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-GwfQu8rN.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DUFFCU8H.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CSFzOACM.js → heartbeat-settings-D-hVYXzL.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-SEW_0k8Q.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-C1cHRBRp.js → image-generation-api-DtMRG1oF.js} +1 -1
- package/dist/gateway/static/root/assets/{index-C6OWYFtQ.js → index-DfZZLrWp.js} +35 -35
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BuvD3iv8.js → keyboard-shortcuts-settings-D_I-hiG2.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-CaC4-THD.js → local-app-workbench-page-BcY7wIHs.js} +2 -2
- package/dist/gateway/static/root/assets/{local-apps-page-DdJ8zwBp.js → local-apps-page-DniZuXJJ.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-BBSCIl4I.js → locale-store-BkJ7g35d.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BaR2he0K.js → logs-page-DIiOMrJ_.js} +2 -2
- package/dist/gateway/static/root/assets/management-settings-BPw41YX6.js +1 -0
- package/dist/gateway/static/root/assets/{markdown-view-c5vtnYe8.js → markdown-view-B2IR4E2r.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-D_a6GDY6.js → media-plugins-BoP3LSQz.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-DjuBdDCn.js → messages-BGc-OJWg.js} +2 -2
- package/dist/gateway/static/root/assets/note-detail-panel-BI2xr54R.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-CM7Ns8i1.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-C7Le9bSI.js → page-tabs-B21WfV92.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B2pOxJBK.js → preview-open-alternatives-BZoDpPqV.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-card-BlQ1vCtV.js → proactive-card-Bw9CbzUX.js} +1 -1
- package/dist/gateway/static/root/assets/{proactive-page-CghKTfYa.js → proactive-page-Czw_CEDB.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-DpjiHgwu.js → product-open-page-DsQcgZkI.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-jOECabbf.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-Ck9j5q0x.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CyQ4k1Ux.js → registry-api-CTq8EECj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-2xcpaLTC.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Bl4ArLHf.js → runtime-tools-settings-panel-Bz5gIRjy.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-KQFmfWd4.js → schema-form-CqM0ntNm.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CE9MK60k.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D0grhQan.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-Dbnls9ty.js → settings-form-section-LqcDYJ2m.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CDKirYRG.js → settings-page-BpvBmheO.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CYuqNzWH.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-F3qhAryJ.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-B4wSoQM3.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C3LD7fkR.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-Bd0_ED_p.js → skills-page-D9fI548d.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-rA-XT-YG.js → skills-page.utils-DZNmyYqU.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CSkMrHLz.js → system-settings-panel-CMsmFOhV.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-DRwTJ8E0.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-DWpIEWen.js → theme-store-mV6Cm2vj.js} +1 -1
- package/dist/gateway/static/root/assets/tunnel-api-DGaqXwql.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-DkQAIAuB.js → use-autosave-bXCl0Y0j.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-B3RjDSri.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BrqcH0P-.js → voice-api-key-field-Cx9rl7mP.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-BJ95Z3pT.js → voice-config-api-D3DdcH4H.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-vfTWKm13.js → work-discovery-overlay-ywJpaqKp.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CHdr_ng9.js → workflow-run-setup-panel-DyGukxOL.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-CifIqO2B.js +6 -0
- package/dist/gateway/static/root/index.html +9 -9
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
- package/dist/src/agent/child-agent-factory.d.ts +3 -0
- package/dist/src/agent/child-agent-factory.js +2 -1
- package/dist/src/agent/external-tools/endpoint-provider.js +1 -1
- package/dist/src/agent/service.js +20 -16
- package/dist/src/agent/tools/factory.d.ts +1 -0
- package/dist/src/agent/tools/factory.js +21 -4
- package/dist/src/agent/workflow/workflow-child-tools.js +2 -0
- 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 +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +8 -3
- package/dist/src/browser/providers/realtime-extension.d.ts +4 -1
- package/dist/src/browser/providers/realtime-extension.js +26 -10
- package/dist/src/browser/readiness.d.ts +4 -1
- package/dist/src/browser/readiness.js +11 -5
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/gateway/gateway-workflow-host.types.d.ts +2 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- 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 +24 -11
- 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/endpoint-tools.js +9 -0
- package/dist/src/gateway/hono/routes/models.js +6 -0
- 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/hono/routes/voice.js +1 -1
- package/dist/src/gateway/rate-limit/buckets.js +2 -1
- package/dist/src/gateway/service/run-gateway-agent.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/providers/model-catalog-persistence.js +4 -0
- package/dist/src/providers/model-catalog-types.d.ts +1 -0
- package/dist/src/providers/xopc-cloud-model-source.js +12 -1
- package/dist/src/storage/sqlite/device-pairing-approval.js +3 -1
- package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +1 -0
- package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +9 -1
- package/dist/src/storage/sqlite/index.d.ts +1 -1
- package/dist/src/storage/sqlite/index.js +2 -2
- 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/dist/src/workflows/service/workflow-run-service.js +4 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/agents-C0By2FIo.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-BSJXKWcE.js +0 -1
- package/dist/gateway/static/root/assets/attachment-load-Dok_B1Gv.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--1TsZf3O.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DOHgQer_.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Bpse_5K-.js +0 -8
- package/dist/gateway/static/root/assets/connectors-page-B_wLKGnH.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-B5_w9qlm.js +0 -1
- package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CH-6vkgN.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BJebAKB-.js +0 -1
- package/dist/gateway/static/root/assets/management-settings-r-iYWZFY.js +0 -1
- package/dist/gateway/static/root/assets/note-detail-panel-BV6nbgac.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BmLKoWwi.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-CV8Lh296.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-COweOaJP.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-ChPPGVU5.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DgmHibOK.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DV7kMco2.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-SQmtWoOs.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-DwgLjS1m.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-Cs5bUrYw.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DyKrjq7F.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-CEQU-L_G.js +0 -1
- package/dist/gateway/static/root/assets/tunnel-api-COJcGnEi.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-BQAU55lf.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-hwRJD7vR.js +0 -6
|
@@ -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") {
|
|
@@ -5743,6 +5782,62 @@ array(browserPageContextInputSchema).max(MAX_BROWSER_CONTEXTS_PER_TURN).superRef
|
|
|
5743
5782
|
}
|
|
5744
5783
|
});
|
|
5745
5784
|
|
|
5785
|
+
const LOCALE_STORAGE_KEY = "xopc.sidepanel.locale";
|
|
5786
|
+
const LOCALE_PATHS = {
|
|
5787
|
+
en: "_locales/en/messages.json",
|
|
5788
|
+
"zh-CN": "_locales/zh_CN/messages.json"
|
|
5789
|
+
};
|
|
5790
|
+
let activeLocale;
|
|
5791
|
+
let activeCatalog;
|
|
5792
|
+
function isExtensionLocale(value) {
|
|
5793
|
+
return value === "en" || value === "zh-CN";
|
|
5794
|
+
}
|
|
5795
|
+
function browserLocale() {
|
|
5796
|
+
return chrome.i18n.getUILanguage().replaceAll("_", "-").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
5797
|
+
}
|
|
5798
|
+
function messageWithSubstitutions(entry, substitutions) {
|
|
5799
|
+
const values = substitutions === void 0 ? [] : Array.isArray(substitutions) ? substitutions : [substitutions];
|
|
5800
|
+
const valueAt = (content) => content.replace(/\$(\d+)/g, (_match, index) => values[Number(index) - 1] ?? "");
|
|
5801
|
+
let message = entry.message;
|
|
5802
|
+
for (const [name, placeholder] of Object.entries(entry.placeholders ?? {})) {
|
|
5803
|
+
message = message.replaceAll(new RegExp(`\\$${name}\\$`, "gi"), valueAt(placeholder.content));
|
|
5804
|
+
}
|
|
5805
|
+
let sequentialIndex = 0;
|
|
5806
|
+
return message.replace(/%(?:(\d+)\$)?s/g, (_match, position) => {
|
|
5807
|
+
const index = position ? Number(position) - 1 : sequentialIndex++;
|
|
5808
|
+
return values[index] ?? "";
|
|
5809
|
+
}).replace(/\$(\d+)/g, (_match, index) => values[Number(index) - 1] ?? "");
|
|
5810
|
+
}
|
|
5811
|
+
function t(key, substitutions) {
|
|
5812
|
+
const entry = activeCatalog?.[key];
|
|
5813
|
+
return entry ? messageWithSubstitutions(entry, substitutions) : chrome.i18n.getMessage(key, substitutions) || key;
|
|
5814
|
+
}
|
|
5815
|
+
function extensionLocale() {
|
|
5816
|
+
return activeLocale ?? chrome.i18n.getUILanguage().replaceAll("_", "-");
|
|
5817
|
+
}
|
|
5818
|
+
async function loadExtensionLocalePreference() {
|
|
5819
|
+
const stored = await chrome.storage.local.get(LOCALE_STORAGE_KEY);
|
|
5820
|
+
const locale = isExtensionLocale(stored[LOCALE_STORAGE_KEY]) ? stored[LOCALE_STORAGE_KEY] : browserLocale();
|
|
5821
|
+
await applyExtensionLocale(locale);
|
|
5822
|
+
return locale;
|
|
5823
|
+
}
|
|
5824
|
+
async function saveExtensionLocalePreference(locale) {
|
|
5825
|
+
await chrome.storage.local.set({ [LOCALE_STORAGE_KEY]: locale });
|
|
5826
|
+
await applyExtensionLocale(locale);
|
|
5827
|
+
}
|
|
5828
|
+
async function applyExtensionLocale(locale) {
|
|
5829
|
+
const response = await fetch(chrome.runtime.getURL(LOCALE_PATHS[locale]));
|
|
5830
|
+
if (!response.ok) throw new Error(`Could not load extension locale: ${locale}`);
|
|
5831
|
+
activeCatalog = await response.json();
|
|
5832
|
+
activeLocale = locale;
|
|
5833
|
+
initializeDocumentLocale();
|
|
5834
|
+
}
|
|
5835
|
+
function initializeDocumentLocale() {
|
|
5836
|
+
document.documentElement.lang = extensionLocale();
|
|
5837
|
+
document.documentElement.dir = chrome.i18n.getMessage("@@bidi_dir") || "ltr";
|
|
5838
|
+
document.title = t("appName");
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5746
5841
|
const PENDING_CONTEXT_KEY = "xopc.browser.pending-context";
|
|
5747
5842
|
const TAB_BINDING_PREFIX = "xopc.browser.tab-binding.";
|
|
5748
5843
|
function truncateUtf8(value, maxBytes) {
|
|
@@ -5762,13 +5857,13 @@ function sanitizedUrl(raw) {
|
|
|
5762
5857
|
try {
|
|
5763
5858
|
url = new URL(raw);
|
|
5764
5859
|
} catch {
|
|
5765
|
-
throw new Error("
|
|
5860
|
+
throw new Error(t("errorInvalidWebAddress"));
|
|
5766
5861
|
}
|
|
5767
5862
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5768
|
-
throw new Error(
|
|
5863
|
+
throw new Error(t("errorRestrictedPage"));
|
|
5769
5864
|
}
|
|
5770
5865
|
if (url.hostname === "chromewebstore.google.com" || url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
|
|
5771
|
-
throw new Error("
|
|
5866
|
+
throw new Error(t("errorWebStorePage"));
|
|
5772
5867
|
}
|
|
5773
5868
|
url.username = "";
|
|
5774
5869
|
url.password = "";
|
|
@@ -5779,14 +5874,14 @@ function siteAccessError(cause, url) {
|
|
|
5779
5874
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
5780
5875
|
const hostname = new URL(url).hostname;
|
|
5781
5876
|
if (/cannot access|cannot be scripted|missing host permission|extensions gallery|chrome web store/i.test(message)) {
|
|
5782
|
-
return new Error(
|
|
5877
|
+
return new Error(t("errorSiteAccessBlocked", hostname));
|
|
5783
5878
|
}
|
|
5784
5879
|
return cause instanceof Error ? cause : new Error(message);
|
|
5785
5880
|
}
|
|
5786
5881
|
async function runWithTabSiteAccess(tabId, operation) {
|
|
5787
5882
|
const tab = await chrome.tabs.get(tabId);
|
|
5788
5883
|
if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) {
|
|
5789
|
-
throw new Error("
|
|
5884
|
+
throw new Error(t("errorTabUnavailable"));
|
|
5790
5885
|
}
|
|
5791
5886
|
const url = sanitizedUrl(tab.url);
|
|
5792
5887
|
const originPattern = `${new URL(url).origin}/*`;
|
|
@@ -5799,7 +5894,7 @@ async function runWithTabSiteAccess(tabId, operation) {
|
|
|
5799
5894
|
} catch (cause) {
|
|
5800
5895
|
throw siteAccessError(cause, url);
|
|
5801
5896
|
}
|
|
5802
|
-
if (!granted) throw new Error(
|
|
5897
|
+
if (!granted) throw new Error(t("errorSiteAccessRequired", new URL(url).hostname));
|
|
5803
5898
|
newlyGranted = true;
|
|
5804
5899
|
}
|
|
5805
5900
|
try {
|
|
@@ -5813,15 +5908,15 @@ async function sha256(value) {
|
|
|
5813
5908
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
5814
5909
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5815
5910
|
}
|
|
5816
|
-
function extractPage(mode) {
|
|
5911
|
+
function extractPage(mode, messages) {
|
|
5817
5912
|
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
5818
5913
|
const selection = normalize(window.getSelection()?.toString() ?? "");
|
|
5819
5914
|
if (mode === "selection") {
|
|
5820
|
-
if (!selection) throw new Error(
|
|
5915
|
+
if (!selection) throw new Error(messages.selectText);
|
|
5821
5916
|
return { title: document.title, url: location.href, timeOrigin: performance.timeOrigin, selection };
|
|
5822
5917
|
}
|
|
5823
5918
|
const source = document.querySelector("article, main") ?? document.body;
|
|
5824
|
-
if (!source) throw new Error(
|
|
5919
|
+
if (!source) throw new Error(messages.noReadableContent);
|
|
5825
5920
|
const clone = source.cloneNode(true);
|
|
5826
5921
|
clone.querySelectorAll([
|
|
5827
5922
|
"script",
|
|
@@ -5843,23 +5938,26 @@ function extractPage(mode) {
|
|
|
5843
5938
|
"[inert]"
|
|
5844
5939
|
].join(",")).forEach((element) => element.remove());
|
|
5845
5940
|
const text = normalize(clone.innerText || clone.textContent || "");
|
|
5846
|
-
if (!text) throw new Error(
|
|
5941
|
+
if (!text) throw new Error(messages.noReadableContent);
|
|
5847
5942
|
return { title: document.title, url: location.href, timeOrigin: performance.timeOrigin, text };
|
|
5848
5943
|
}
|
|
5849
5944
|
async function captureTabPage(tabId, mode) {
|
|
5850
5945
|
const tab = await chrome.tabs.get(tabId);
|
|
5851
|
-
if (!tab.url) throw new Error("
|
|
5946
|
+
if (!tab.url) throw new Error(t("errorTabUnavailable"));
|
|
5852
5947
|
sanitizedUrl(tab.url);
|
|
5853
5948
|
const result = await chrome.scripting.executeScript({
|
|
5854
5949
|
target: { tabId },
|
|
5855
5950
|
func: extractPage,
|
|
5856
|
-
args: [mode
|
|
5951
|
+
args: [mode, {
|
|
5952
|
+
selectText: t("errorSelectText"),
|
|
5953
|
+
noReadableContent: t("errorNoReadableContent")
|
|
5954
|
+
}]
|
|
5857
5955
|
});
|
|
5858
5956
|
const raw = result[0]?.result;
|
|
5859
|
-
if (!raw) throw new Error("
|
|
5957
|
+
if (!raw) throw new Error(t("errorReadPage"));
|
|
5860
5958
|
const url = sanitizedUrl(raw.url);
|
|
5861
5959
|
if (new URL(url).origin !== new URL(sanitizedUrl(tab.url)).origin) {
|
|
5862
|
-
throw new Error("
|
|
5960
|
+
throw new Error(t("errorPageChangedReading"));
|
|
5863
5961
|
}
|
|
5864
5962
|
const selected = raw.selection ? truncateUtf8(raw.selection, MAX_BROWSER_SELECTION_BYTES) : void 0;
|
|
5865
5963
|
const page = raw.text ? truncateUtf8(raw.text, MAX_BROWSER_PAGE_TEXT_BYTES) : void 0;
|
|
@@ -5888,17 +5986,17 @@ async function activeTabId() {
|
|
|
5888
5986
|
}
|
|
5889
5987
|
async function currentTabDescriptor() {
|
|
5890
5988
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
5891
|
-
if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error("
|
|
5989
|
+
if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error(t("errorNoActivePage"));
|
|
5892
5990
|
return runWithTabSiteAccess(tab.id, async (accessibleTab) => {
|
|
5893
5991
|
const result = await chrome.scripting.executeScript({
|
|
5894
5992
|
target: { tabId: accessibleTab.id },
|
|
5895
5993
|
func: () => ({ url: location.href, timeOrigin: performance.timeOrigin })
|
|
5896
5994
|
});
|
|
5897
5995
|
const marker = result[0]?.result;
|
|
5898
|
-
if (!marker) throw new Error("
|
|
5996
|
+
if (!marker) throw new Error(t("errorIdentifyPage"));
|
|
5899
5997
|
const currentUrl = sanitizedUrl(marker.url);
|
|
5900
5998
|
if (new URL(currentUrl).origin !== new URL(accessibleTab.url).origin) {
|
|
5901
|
-
throw new Error("
|
|
5999
|
+
throw new Error(t("errorPageChangedConnecting"));
|
|
5902
6000
|
}
|
|
5903
6001
|
return {
|
|
5904
6002
|
tabId: String(accessibleTab.id),
|
|
@@ -5922,7 +6020,7 @@ function openDatabase() {
|
|
|
5922
6020
|
}
|
|
5923
6021
|
};
|
|
5924
6022
|
request.onsuccess = () => resolve(request.result);
|
|
5925
|
-
request.onerror = () => reject(request.error ?? new Error("
|
|
6023
|
+
request.onerror = () => reject(request.error ?? new Error(t("errorOpenOutbox")));
|
|
5926
6024
|
});
|
|
5927
6025
|
}
|
|
5928
6026
|
function transactionResult(database, mode, createRequest) {
|
|
@@ -5933,10 +6031,10 @@ function transactionResult(database, mode, createRequest) {
|
|
|
5933
6031
|
request.onsuccess = () => {
|
|
5934
6032
|
result = request.result;
|
|
5935
6033
|
};
|
|
5936
|
-
request.onerror = () => reject(request.error ?? new Error("
|
|
6034
|
+
request.onerror = () => reject(request.error ?? new Error(t("errorOutboxRequest")));
|
|
5937
6035
|
transaction.oncomplete = () => resolve(result);
|
|
5938
|
-
transaction.onerror = () => reject(transaction.error ?? new Error("
|
|
5939
|
-
transaction.onabort = () => reject(transaction.error ?? new Error("
|
|
6036
|
+
transaction.onerror = () => reject(transaction.error ?? new Error(t("errorOutboxTransaction")));
|
|
6037
|
+
transaction.onabort = () => reject(transaction.error ?? new Error(t("errorOutboxAborted")));
|
|
5940
6038
|
});
|
|
5941
6039
|
}
|
|
5942
6040
|
async function readBrowserOutbox(sessionKey) {
|
|
@@ -5977,6 +6075,7 @@ async function clearBrowserOutboxes() {
|
|
|
5977
6075
|
}
|
|
5978
6076
|
|
|
5979
6077
|
const PROFILE_KEY = "xopc.browser.profile";
|
|
6078
|
+
const PAIRING_JOURNAL_KEY = "xopc.browser.pairing";
|
|
5980
6079
|
const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
|
|
5981
6080
|
const KEY_DATABASE = "xopc-browser-identity";
|
|
5982
6081
|
const KEY_STORE = "identity";
|
|
@@ -6014,13 +6113,13 @@ async function discoverLocalGateway(options) {
|
|
|
6014
6113
|
nonce
|
|
6015
6114
|
}
|
|
6016
6115
|
);
|
|
6017
|
-
if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.
|
|
6018
|
-
if (options?.force) throw new Error(response.error || "
|
|
6116
|
+
if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.invitation !== "string" || typeof response.expiresAt !== "number") {
|
|
6117
|
+
if (options?.force) throw new Error(response.error || t("errorInvalidLocalHostResponse"));
|
|
6019
6118
|
return void 0;
|
|
6020
6119
|
}
|
|
6021
6120
|
return {
|
|
6022
6121
|
gatewayUrl: response.gatewayUrl,
|
|
6023
|
-
|
|
6122
|
+
invitation: response.invitation,
|
|
6024
6123
|
expiresAt: response.expiresAt
|
|
6025
6124
|
};
|
|
6026
6125
|
} catch (cause) {
|
|
@@ -6049,25 +6148,12 @@ function randomNonce(bytes = 24) {
|
|
|
6049
6148
|
function createRefreshToken() {
|
|
6050
6149
|
return `xopc_rt_${crypto.randomUUID()}_${randomNonce(32)}`;
|
|
6051
6150
|
}
|
|
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
|
-
}
|
|
6151
|
+
function parseBrowserPairingInvitation(value, allowExpired = false) {
|
|
6152
|
+
const encoded = readBrowserPairingInvitation(value);
|
|
6153
|
+
const parsed = devicePairingInvitationPayloadSchema.safeParse(decodeJson(encoded));
|
|
6154
|
+
if (!parsed.success || parsed.data.targetKind !== "browser") throw new Error("Pairing invitation version is not supported");
|
|
6155
|
+
const payload = parsed.data;
|
|
6156
|
+
if (!allowExpired && payload.expiresAt <= Date.now()) throw new Error("Pairing invitation has expired");
|
|
6071
6157
|
return payload;
|
|
6072
6158
|
}
|
|
6073
6159
|
function openIdentityDatabase() {
|
|
@@ -6111,12 +6197,12 @@ async function forgetIdentity() {
|
|
|
6111
6197
|
const request = indexedDB.deleteDatabase(KEY_DATABASE);
|
|
6112
6198
|
request.onsuccess = () => resolve();
|
|
6113
6199
|
request.onerror = () => reject(request.error);
|
|
6114
|
-
request.onblocked = () => reject(new Error("
|
|
6200
|
+
request.onblocked = () => reject(new Error(t("errorIdentityInUse")));
|
|
6115
6201
|
});
|
|
6116
6202
|
}
|
|
6117
6203
|
async function publicKeyJwk(pair) {
|
|
6118
6204
|
const exported = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
6119
|
-
if (!exported.x || !exported.y) throw new Error("
|
|
6205
|
+
if (!exported.x || !exported.y) throw new Error(t("errorIdentityExport"));
|
|
6120
6206
|
return { kty: "EC", crv: "P-256", x: exported.x, y: exported.y };
|
|
6121
6207
|
}
|
|
6122
6208
|
async function sign(pair, text) {
|
|
@@ -6154,6 +6240,11 @@ function requestGatewayOriginPermissions(origins) {
|
|
|
6154
6240
|
if (!patterns.length) return Promise.resolve(true);
|
|
6155
6241
|
return chrome.permissions.request({ origins: patterns });
|
|
6156
6242
|
}
|
|
6243
|
+
async function hasGatewayOriginPermission(origin) {
|
|
6244
|
+
const url = new URL(origin);
|
|
6245
|
+
if (url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname)) return true;
|
|
6246
|
+
return chrome.permissions.contains({ origins: [`${url.origin}/*`] });
|
|
6247
|
+
}
|
|
6157
6248
|
class GatewayHttpError extends Error {
|
|
6158
6249
|
constructor(message, status) {
|
|
6159
6250
|
super(message);
|
|
@@ -6174,7 +6265,7 @@ async function post(origin, path, body) {
|
|
|
6174
6265
|
signal: controller.signal
|
|
6175
6266
|
});
|
|
6176
6267
|
} catch (cause) {
|
|
6177
|
-
if (controller.signal.aborted) throw new Error(
|
|
6268
|
+
if (controller.signal.aborted) throw new Error(t("errorGatewayTimeout", String(GATEWAY_REQUEST_TIMEOUT_MS / 1e3)));
|
|
6178
6269
|
throw cause;
|
|
6179
6270
|
} finally {
|
|
6180
6271
|
clearTimeout(timeout);
|
|
@@ -6182,7 +6273,7 @@ async function post(origin, path, body) {
|
|
|
6182
6273
|
const json = await response.json().catch(() => ({}));
|
|
6183
6274
|
if (!response.ok) {
|
|
6184
6275
|
throw new GatewayHttpError(
|
|
6185
|
-
json.error?.code ?? json.error?.message ??
|
|
6276
|
+
json.error?.code ?? json.error?.message ?? t("errorGatewayStatus", String(response.status)),
|
|
6186
6277
|
response.status
|
|
6187
6278
|
);
|
|
6188
6279
|
}
|
|
@@ -6210,19 +6301,24 @@ async function signedPairingRequest(pair, payload, origin, requestId, action, co
|
|
|
6210
6301
|
const path = action === "request" ? "/api/device-pairing/requests" : `/api/device-pairing/requests/${requestId}/${action}`;
|
|
6211
6302
|
const response = await post(origin, path, { ...body, signature });
|
|
6212
6303
|
if (typeof response.signedPayload !== "string" || typeof response.signature !== "string") {
|
|
6213
|
-
throw new Error("
|
|
6304
|
+
throw new Error(t("errorUnsignedPairing"));
|
|
6214
6305
|
}
|
|
6215
6306
|
if (!await verifyGateway(payload.gatewayPublicKey, response.signedPayload, response.signature)) {
|
|
6216
|
-
throw new Error("
|
|
6307
|
+
throw new Error(t("errorGatewayVerification"));
|
|
6217
6308
|
}
|
|
6218
6309
|
const decoded = decodeJson(response.signedPayload);
|
|
6219
6310
|
if (decoded.gateway.id !== payload.gatewayId || decoded.nonce !== nonce || decoded.request.requestId !== requestId) {
|
|
6220
|
-
throw new Error("
|
|
6311
|
+
throw new Error(t("errorPairingMismatch"));
|
|
6221
6312
|
}
|
|
6222
6313
|
return decoded;
|
|
6223
6314
|
}
|
|
6224
6315
|
function shouldTryNextRoute(cause) {
|
|
6225
|
-
return !(cause instanceof GatewayHttpError) ||
|
|
6316
|
+
return !(cause instanceof GatewayHttpError) || isRetryableDevicePairingHttpStatus(cause.status);
|
|
6317
|
+
}
|
|
6318
|
+
function isRouteFailure(cause) {
|
|
6319
|
+
if (cause instanceof GatewayHttpError) return isRetryableDevicePairingHttpStatus(cause.status);
|
|
6320
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
6321
|
+
return /network|fetch|respond|identity|route/i.test(message);
|
|
6226
6322
|
}
|
|
6227
6323
|
async function signedPairingRequestAcrossRoutes(pair, payload, origins, requestId, action, completion) {
|
|
6228
6324
|
let lastRouteError;
|
|
@@ -6236,9 +6332,11 @@ async function signedPairingRequestAcrossRoutes(pair, payload, origins, requestI
|
|
|
6236
6332
|
}
|
|
6237
6333
|
throw lastRouteError instanceof Error ? lastRouteError : new Error("No Gateway route could be reached");
|
|
6238
6334
|
}
|
|
6239
|
-
async function refreshAccessToken(gatewayUrls, refreshToken, pair, gateway
|
|
6240
|
-
|
|
6241
|
-
|
|
6335
|
+
async function refreshAccessToken(gatewayUrls, refreshToken, pair, gateway, operation = {
|
|
6336
|
+
requestId: crypto.randomUUID(),
|
|
6337
|
+
nextRefreshToken: createRefreshToken()
|
|
6338
|
+
}) {
|
|
6339
|
+
const { nextRefreshToken, requestId } = operation;
|
|
6242
6340
|
const timestamp = Date.now();
|
|
6243
6341
|
const nonce = randomNonce();
|
|
6244
6342
|
const credentialId = refreshToken.slice("xopc_rt_".length).split("_")[0];
|
|
@@ -6271,89 +6369,167 @@ ${nextRefreshToken}`;
|
|
|
6271
6369
|
}
|
|
6272
6370
|
}
|
|
6273
6371
|
if (!response || !gatewayUrl) {
|
|
6274
|
-
throw lastRouteError instanceof Error ? lastRouteError : new Error("
|
|
6372
|
+
throw lastRouteError instanceof Error ? lastRouteError : new Error(t("errorNoReachableGatewayRoute"));
|
|
6275
6373
|
}
|
|
6276
|
-
if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error("
|
|
6374
|
+
if (typeof response.signedPayload !== "string" || typeof response.signature !== "string" || !await verifyGateway(gateway.gatewayPublicKey, response.signedPayload, response.signature)) throw new Error(t("errorGatewayVerification"));
|
|
6277
6375
|
const responseProof = decodeJson(response.signedPayload);
|
|
6278
6376
|
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");
|
|
6279
6377
|
const payload = responseProof.tokens;
|
|
6280
6378
|
if (!payload?.accessToken || !payload.accessTokenExpiresAt || payload.refreshToken !== nextRefreshToken) {
|
|
6281
|
-
throw new Error("
|
|
6379
|
+
throw new Error(t("errorInvalidCredentials"));
|
|
6282
6380
|
}
|
|
6283
6381
|
return { ...payload, gatewayUrl };
|
|
6284
6382
|
}
|
|
6285
6383
|
function wait(milliseconds) {
|
|
6286
6384
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6287
6385
|
}
|
|
6288
|
-
async function pairGateway(
|
|
6289
|
-
const payload =
|
|
6386
|
+
async function pairGateway(invitation, onApproval, requestedOrigin) {
|
|
6387
|
+
const payload = parseBrowserPairingInvitation(invitation, true);
|
|
6290
6388
|
const origins = [...new Set(payload.routes.map((route) => new URL(route.url).origin))];
|
|
6291
|
-
const
|
|
6389
|
+
const selectedOrigin = requestedOrigin && origins.includes(requestedOrigin) ? requestedOrigin : origins[0];
|
|
6390
|
+
const permissionTask = requestGatewayOriginPermissions([selectedOrigin]);
|
|
6292
6391
|
if (!await permissionTask) throw new Error("Gateway site permission was not granted");
|
|
6392
|
+
const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
|
|
6393
|
+
origin,
|
|
6394
|
+
permitted: await hasGatewayOriginPermission(origin)
|
|
6395
|
+
})))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
|
|
6293
6396
|
const pair = await getOrCreateKeyPair();
|
|
6294
|
-
const
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
payload,
|
|
6299
|
-
origins,
|
|
6300
|
-
requestId,
|
|
6301
|
-
"request"
|
|
6302
|
-
);
|
|
6303
|
-
if (result.request.status === "pending") onApproval(result.request.confirmationCode);
|
|
6304
|
-
while (result.request.status === "pending") {
|
|
6305
|
-
await wait(1500);
|
|
6306
|
-
const response = await signedPairingRequestAcrossRoutes(
|
|
6307
|
-
pair,
|
|
6308
|
-
payload,
|
|
6309
|
-
[origin, ...origins.filter((candidate) => candidate !== origin)],
|
|
6310
|
-
requestId,
|
|
6311
|
-
"status"
|
|
6312
|
-
);
|
|
6313
|
-
({ origin, result } = response);
|
|
6397
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6398
|
+
let journal = stored[PAIRING_JOURNAL_KEY];
|
|
6399
|
+
if (journal && journal.payload.pairingToken !== payload.pairingToken) {
|
|
6400
|
+
throw new Error("PAIRING_ALREADY_PENDING");
|
|
6314
6401
|
}
|
|
6315
|
-
if (
|
|
6316
|
-
throw new Error(
|
|
6402
|
+
if (!journal) {
|
|
6403
|
+
if (payload.expiresAt <= Date.now()) throw new Error(t("errorExpiredPairingLink"));
|
|
6404
|
+
journal = {
|
|
6405
|
+
invitation: invitation.trim(),
|
|
6406
|
+
payload,
|
|
6407
|
+
requestId: crypto.randomUUID(),
|
|
6408
|
+
idempotencyKey: crypto.randomUUID(),
|
|
6409
|
+
initialRefreshToken: createRefreshToken()
|
|
6410
|
+
};
|
|
6411
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6317
6412
|
}
|
|
6318
|
-
|
|
6319
|
-
|
|
6413
|
+
delete journal.nextOrigin;
|
|
6414
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6415
|
+
const requestId = journal.requestId;
|
|
6416
|
+
const availableOrigins = journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : [selectedOrigin, ...permittedOrigins.filter((candidate) => candidate !== selectedOrigin)];
|
|
6417
|
+
try {
|
|
6418
|
+
let { origin, result } = await signedPairingRequestAcrossRoutes(
|
|
6320
6419
|
pair,
|
|
6321
|
-
payload,
|
|
6322
|
-
|
|
6420
|
+
journal.payload,
|
|
6421
|
+
availableOrigins,
|
|
6323
6422
|
requestId,
|
|
6324
|
-
"
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6423
|
+
"request"
|
|
6424
|
+
);
|
|
6425
|
+
journal.origin = origin;
|
|
6426
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6427
|
+
if (result.request.status === "pending") onApproval(result.request.confirmationCode);
|
|
6428
|
+
while (result.request.status === "pending") {
|
|
6429
|
+
await wait(1500);
|
|
6430
|
+
const response = await signedPairingRequestAcrossRoutes(
|
|
6431
|
+
pair,
|
|
6432
|
+
journal.payload,
|
|
6433
|
+
[origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
|
|
6434
|
+
requestId,
|
|
6435
|
+
"status"
|
|
6436
|
+
);
|
|
6437
|
+
({ origin, result } = response);
|
|
6438
|
+
journal.origin = origin;
|
|
6439
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6440
|
+
}
|
|
6441
|
+
if (result.request.status !== "approved" && result.request.status !== "completed") {
|
|
6442
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6443
|
+
throw new Error(t("errorPairingStatus", result.request.status));
|
|
6444
|
+
}
|
|
6445
|
+
if (result.request.status !== "completed") {
|
|
6446
|
+
const response = await signedPairingRequestAcrossRoutes(
|
|
6447
|
+
pair,
|
|
6448
|
+
journal.payload,
|
|
6449
|
+
[origin, ...permittedOrigins.filter((candidate) => candidate !== origin)],
|
|
6450
|
+
requestId,
|
|
6451
|
+
"complete",
|
|
6452
|
+
{
|
|
6453
|
+
idempotencyKey: journal.idempotencyKey,
|
|
6454
|
+
initialRefreshToken: journal.initialRefreshToken
|
|
6455
|
+
}
|
|
6456
|
+
);
|
|
6457
|
+
({ origin, result } = response);
|
|
6458
|
+
}
|
|
6459
|
+
if (!result.request.deviceId) throw new Error(t("errorBrowserNotRegistered"));
|
|
6460
|
+
journal.origin = origin;
|
|
6461
|
+
journal.completed = { deviceId: result.request.deviceId, gatewayName: result.gateway.name };
|
|
6462
|
+
journal.refresh ??= { requestId: crypto.randomUUID(), nextRefreshToken: createRefreshToken() };
|
|
6463
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6464
|
+
const orderedOrigins = [origin, ...permittedOrigins.filter((candidate) => candidate !== origin)];
|
|
6465
|
+
const tokens = await refreshAccessToken(
|
|
6466
|
+
orderedOrigins,
|
|
6467
|
+
journal.initialRefreshToken,
|
|
6468
|
+
pair,
|
|
6469
|
+
journal.payload,
|
|
6470
|
+
journal.refresh
|
|
6471
|
+
);
|
|
6472
|
+
const profile = {
|
|
6473
|
+
gatewayId: journal.payload.gatewayId,
|
|
6474
|
+
gatewayName: journal.completed.gatewayName,
|
|
6475
|
+
gatewayUrl: tokens.gatewayUrl,
|
|
6476
|
+
gatewayUrls: [tokens.gatewayUrl, ...orderedOrigins.filter((candidate) => candidate !== tokens.gatewayUrl)],
|
|
6477
|
+
gatewayPublicKey: journal.payload.gatewayPublicKey,
|
|
6478
|
+
deviceId: journal.completed.deviceId,
|
|
6479
|
+
refreshToken: tokens.refreshToken,
|
|
6480
|
+
accessToken: tokens.accessToken,
|
|
6481
|
+
accessTokenExpiresAt: tokens.accessTokenExpiresAt
|
|
6482
|
+
};
|
|
6483
|
+
await chrome.storage.local.set({ [PROFILE_KEY]: profile });
|
|
6484
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6485
|
+
return profile;
|
|
6486
|
+
} catch (cause) {
|
|
6487
|
+
const nextOrigin = origins.find((origin) => !permittedOrigins.includes(origin));
|
|
6488
|
+
if (nextOrigin && isRouteFailure(cause)) {
|
|
6489
|
+
journal.nextOrigin = nextOrigin;
|
|
6490
|
+
await chrome.storage.local.set({ [PAIRING_JOURNAL_KEY]: journal });
|
|
6491
|
+
throw new Error(`PAIRING_ROUTE_PERMISSION_REQUIRED:${nextOrigin}`);
|
|
6492
|
+
}
|
|
6493
|
+
throw cause;
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
async function readPendingBrowserPairing() {
|
|
6497
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6498
|
+
const journal = stored[PAIRING_JOURNAL_KEY];
|
|
6499
|
+
return journal ? { invitation: journal.invitation, ...journal.nextOrigin ? { nextOrigin: journal.nextOrigin } : {} } : void 0;
|
|
6500
|
+
}
|
|
6501
|
+
async function cancelPendingBrowserPairing() {
|
|
6502
|
+
const stored = await chrome.storage.local.get(PAIRING_JOURNAL_KEY);
|
|
6503
|
+
const journal = stored[PAIRING_JOURNAL_KEY];
|
|
6504
|
+
if (!journal) return;
|
|
6505
|
+
const origins = [...new Set(journal.payload.routes.map((route) => new URL(route.url).origin))];
|
|
6506
|
+
const permittedOrigins = (await Promise.all(origins.map(async (origin) => ({
|
|
6507
|
+
origin,
|
|
6508
|
+
permitted: await hasGatewayOriginPermission(origin)
|
|
6509
|
+
})))).filter((candidate) => candidate.permitted).map((candidate) => candidate.origin);
|
|
6510
|
+
try {
|
|
6511
|
+
await signedPairingRequestAcrossRoutes(
|
|
6512
|
+
await getOrCreateKeyPair(),
|
|
6513
|
+
journal.payload,
|
|
6514
|
+
journal.origin && permittedOrigins.includes(journal.origin) ? [journal.origin, ...permittedOrigins.filter((candidate) => candidate !== journal.origin)] : permittedOrigins,
|
|
6515
|
+
journal.requestId,
|
|
6516
|
+
"cancel"
|
|
6329
6517
|
);
|
|
6330
|
-
|
|
6518
|
+
} catch (cause) {
|
|
6519
|
+
if (!(cause instanceof GatewayHttpError) || !["PAIRING_NOT_FOUND", "PAIRING_EXPIRED", "PAIRING_CANCELLED"].includes(cause.message)) throw cause;
|
|
6331
6520
|
}
|
|
6332
|
-
|
|
6333
|
-
const orderedOrigins = [origin, ...origins.filter((candidate) => candidate !== origin)];
|
|
6334
|
-
const tokens = await refreshAccessToken(orderedOrigins, initialRefreshToken, pair, payload);
|
|
6335
|
-
const profile = {
|
|
6336
|
-
gatewayId: payload.gatewayId,
|
|
6337
|
-
gatewayName: result.gateway.name,
|
|
6338
|
-
gatewayUrl: tokens.gatewayUrl,
|
|
6339
|
-
gatewayUrls: [tokens.gatewayUrl, ...orderedOrigins.filter((candidate) => candidate !== tokens.gatewayUrl)],
|
|
6340
|
-
gatewayPublicKey: payload.gatewayPublicKey,
|
|
6341
|
-
deviceId: result.request.deviceId,
|
|
6342
|
-
refreshToken: tokens.refreshToken,
|
|
6343
|
-
accessToken: tokens.accessToken,
|
|
6344
|
-
accessTokenExpiresAt: tokens.accessTokenExpiresAt
|
|
6345
|
-
};
|
|
6346
|
-
await chrome.storage.local.set({ [PROFILE_KEY]: profile });
|
|
6347
|
-
return profile;
|
|
6521
|
+
await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6348
6522
|
}
|
|
6349
6523
|
async function readProfile() {
|
|
6350
|
-
const stored = await chrome.storage.local.get(PROFILE_KEY);
|
|
6524
|
+
const stored = await chrome.storage.local.get([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
|
|
6351
6525
|
const profile = stored[PROFILE_KEY];
|
|
6352
|
-
|
|
6526
|
+
if (!profile?.gatewayId || !profile.gatewayUrl || !profile.refreshToken) return void 0;
|
|
6527
|
+
if (stored[PAIRING_JOURNAL_KEY]) await chrome.storage.local.remove(PAIRING_JOURNAL_KEY);
|
|
6528
|
+
return profile;
|
|
6353
6529
|
}
|
|
6354
6530
|
async function getAccessProfile() {
|
|
6355
6531
|
const profile = await readProfile();
|
|
6356
|
-
if (!profile) throw new Error("
|
|
6532
|
+
if (!profile) throw new Error(t("errorBrowserNotPaired"));
|
|
6357
6533
|
if (profile.accessTokenExpiresAt > Date.now() + 3e4) return profile;
|
|
6358
6534
|
if (refreshTask) return refreshTask;
|
|
6359
6535
|
refreshTask = (async () => {
|
|
@@ -6403,7 +6579,7 @@ async function registerBrowserEndpoint() {
|
|
|
6403
6579
|
publicKey
|
|
6404
6580
|
})
|
|
6405
6581
|
});
|
|
6406
|
-
if (!response.ok) throw new Error(
|
|
6582
|
+
if (!response.ok) throw new Error(t("errorEndpointRegistration", String(response.status)));
|
|
6407
6583
|
}
|
|
6408
6584
|
async function createBrowserEndpointHello(tools = []) {
|
|
6409
6585
|
const profile = await getAccessProfile();
|
|
@@ -6428,7 +6604,7 @@ async function createBrowserEndpointHello(tools = []) {
|
|
|
6428
6604
|
};
|
|
6429
6605
|
}
|
|
6430
6606
|
async function forgetProfile() {
|
|
6431
|
-
await chrome.storage.local.remove(PROFILE_KEY);
|
|
6607
|
+
await chrome.storage.local.remove([PROFILE_KEY, PAIRING_JOURNAL_KEY]);
|
|
6432
6608
|
}
|
|
6433
6609
|
async function revokeAndForgetProfile() {
|
|
6434
6610
|
try {
|
|
@@ -6451,4 +6627,4 @@ async function revokeAndForgetProfile() {
|
|
|
6451
6627
|
}
|
|
6452
6628
|
}
|
|
6453
6629
|
|
|
6454
|
-
export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a,
|
|
6630
|
+
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, extensionLocale as j, gatewayFetch as k, getAccessProfile as l, loadExtensionLocalePreference as m, readPendingBrowserPairing as n, readProfile as o, pairGateway as p, registerBrowserEndpoint as q, readBrowserOutbox as r, revokeAndForgetProfile as s, runWithTabSiteAccess as t, saveExtensionLocalePreference as u, setAutoConnectEnabled as v, t as w, writeBrowserOutbox as x };
|