@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.
Files changed (171) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +215 -0
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +215 -0
  3. package/dist/browser-ext/dist/assets/{auth-DkwOUI6_.js → auth-ClQTK565.js} +291 -115
  4. package/dist/browser-ext/dist/assets/{sidepanel-C86O7q5Y.css → sidepanel-CeGqObOa.css} +41 -20
  5. package/dist/browser-ext/dist/background.js +36 -4
  6. package/dist/browser-ext/dist/sidepanel.html +2 -2
  7. package/dist/browser-ext/dist/sidepanel.js +457 -310
  8. package/dist/browser-ext/manifest.json +6 -4
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/gateway/static/root/assets/{activity-store-cF7IX2RP.js → activity-store-SJZ5fS17.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BrgxX8nG.js → agent-avatar-dicebear-Dx6x7MXq.js} +1 -1
  12. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DvhuRCLA.js → agent-defaults-settings-panel-BuxTpjkf.js} +1 -1
  13. package/dist/gateway/static/root/assets/agents-_f9HEOol.js +1 -0
  14. package/dist/gateway/static/root/assets/{api-CYkdUpkx.js → api-CnJAVHBu.js} +1 -1
  15. package/dist/gateway/static/root/assets/{app-management-settings-panel-DfoNzWIa.js → app-management-settings-panel-BYKWXcVl.js} +1 -1
  16. package/dist/gateway/static/root/assets/{appearance-settings-czbLdCqj.js → appearance-settings-BmhSJc4X.js} +1 -1
  17. package/dist/gateway/static/root/assets/apps-page-DYTnK9lL.js +1 -0
  18. package/dist/gateway/static/root/assets/{archive-plugin-D14P13mX.js → archive-plugin-BY6ncIeE.js} +1 -1
  19. package/dist/gateway/static/root/assets/attachment-load-B6cw8KtO.js +1 -0
  20. package/dist/gateway/static/root/assets/{attachment-preview-renderer-BDTD8YUe.js → attachment-preview-renderer-CfEDFYPE.js} +1 -1
  21. package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +5 -0
  22. package/dist/gateway/static/root/assets/{automations-page-DM7ZEewI.js → automations-page-D1WwsSf3.js} +1 -1
  23. package/dist/gateway/static/root/assets/automations-workspace-CjF56MI_.js +6 -0
  24. package/dist/gateway/static/root/assets/{binary-plugins-CxNg6InF.js → binary-plugins-CC-wYm0K.js} +2 -2
  25. package/dist/gateway/static/root/assets/{block-editor-D22VcYnC.js → block-editor-D8rh9KNe.js} +3 -3
  26. package/dist/gateway/static/root/assets/{browser-automation-inputs-6NWTLEpd.js → browser-automation-inputs-DGCqIPXT.js} +1 -1
  27. package/dist/gateway/static/root/assets/{browser-automations-page-BrqySdbW.js → browser-automations-page-CMrffyct.js} +2 -2
  28. package/dist/gateway/static/root/assets/browser-settings-page-1onvP1Se.js +8 -0
  29. package/dist/gateway/static/root/assets/capabilities-settings-panel-C9ZSJxjr.js +8 -0
  30. package/dist/gateway/static/root/assets/{channels-settings-yHsY174W.js → channels-settings-CvLTnI99.js} +1 -1
  31. package/dist/gateway/static/root/assets/{channels-status-swr-BUZimNmp.js → channels-status-swr-BTT2sIGg.js} +1 -1
  32. package/dist/gateway/static/root/assets/{chat-terminal-dock-CZPBNpMI.js → chat-terminal-dock-F_dUdZ8l.js} +2 -2
  33. package/dist/gateway/static/root/assets/connectors-page-D7NHV8B2.js +2 -0
  34. package/dist/gateway/static/root/assets/{date-picker-Da_0QyLe.js → date-picker-MHuajwQ-.js} +1 -1
  35. package/dist/gateway/static/root/assets/{dependency-picker-D_djqzy7.js → dependency-picker-BcMJJI9X.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-Celmbw-T.js → desktop-pet-DlNm_7H0.js} +1 -1
  37. package/dist/gateway/static/root/assets/{desktop-pet-settings-CqNNgF45.js → desktop-pet-settings-BI1JQMnE.js} +1 -1
  38. package/dist/gateway/static/root/assets/device-pairing-wizard-Cp5jO5B_.js +1 -0
  39. package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +1 -0
  40. package/dist/gateway/static/root/assets/{extension-debug-page-ku3kCjvH.js → extension-debug-page-DZr0GkkA.js} +1 -1
  41. package/dist/gateway/static/root/assets/{extension-page-C7FpHwL4.js → extension-page-QsHoQ6VX.js} +1 -1
  42. package/dist/gateway/static/root/assets/{extension-settings-page-OMj2tU2_.js → extension-settings-page-I2YX4Tzb.js} +1 -1
  43. package/dist/gateway/static/root/assets/{gateway-config-swr-6eb1vKE1.js → gateway-config-swr-cd8owslz.js} +1 -1
  44. package/dist/gateway/static/root/assets/gateway-settings-GwfQu8rN.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-DUFFCU8H.js → gateway-startup-retry-CVTa5HSS.js} +1 -1
  46. package/dist/gateway/static/root/assets/{heartbeat-settings-CSFzOACM.js → heartbeat-settings-D-hVYXzL.js} +1 -1
  47. package/dist/gateway/static/root/assets/home-page-SEW_0k8Q.js +1 -0
  48. package/dist/gateway/static/root/assets/{image-generation-api-C1cHRBRp.js → image-generation-api-DtMRG1oF.js} +1 -1
  49. package/dist/gateway/static/root/assets/{index-C6OWYFtQ.js → index-DfZZLrWp.js} +35 -35
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BuvD3iv8.js → keyboard-shortcuts-settings-D_I-hiG2.js} +1 -1
  51. package/dist/gateway/static/root/assets/{local-app-workbench-page-CaC4-THD.js → local-app-workbench-page-BcY7wIHs.js} +2 -2
  52. package/dist/gateway/static/root/assets/{local-apps-page-DdJ8zwBp.js → local-apps-page-DniZuXJJ.js} +1 -1
  53. package/dist/gateway/static/root/assets/{locale-store-BBSCIl4I.js → locale-store-BkJ7g35d.js} +1 -1
  54. package/dist/gateway/static/root/assets/{logs-page-BaR2he0K.js → logs-page-DIiOMrJ_.js} +2 -2
  55. package/dist/gateway/static/root/assets/management-settings-BPw41YX6.js +1 -0
  56. package/dist/gateway/static/root/assets/{markdown-view-c5vtnYe8.js → markdown-view-B2IR4E2r.js} +1 -1
  57. package/dist/gateway/static/root/assets/{media-plugins-D_a6GDY6.js → media-plugins-BoP3LSQz.js} +1 -1
  58. package/dist/gateway/static/root/assets/{messages-DjuBdDCn.js → messages-BGc-OJWg.js} +2 -2
  59. package/dist/gateway/static/root/assets/note-detail-panel-BI2xr54R.js +5 -0
  60. package/dist/gateway/static/root/assets/notes-page-CM7Ns8i1.js +8 -0
  61. package/dist/gateway/static/root/assets/{page-tabs-C7Le9bSI.js → page-tabs-B21WfV92.js} +1 -1
  62. package/dist/gateway/static/root/assets/{preview-open-alternatives-B2pOxJBK.js → preview-open-alternatives-BZoDpPqV.js} +1 -1
  63. package/dist/gateway/static/root/assets/{proactive-card-BlQ1vCtV.js → proactive-card-Bw9CbzUX.js} +1 -1
  64. package/dist/gateway/static/root/assets/{proactive-page-CghKTfYa.js → proactive-page-Czw_CEDB.js} +1 -1
  65. package/dist/gateway/static/root/assets/{product-open-page-DpjiHgwu.js → product-open-page-DsQcgZkI.js} +1 -1
  66. package/dist/gateway/static/root/assets/project-detail-page-jOECabbf.js +1 -0
  67. package/dist/gateway/static/root/assets/projects-page-Ck9j5q0x.js +1 -0
  68. package/dist/gateway/static/root/assets/{registry-api-CyQ4k1Ux.js → registry-api-CTq8EECj.js} +1 -1
  69. package/dist/gateway/static/root/assets/remote-access-hub-2xcpaLTC.js +1 -0
  70. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Bl4ArLHf.js → runtime-tools-settings-panel-Bz5gIRjy.js} +2 -2
  71. package/dist/gateway/static/root/assets/{schema-form-KQFmfWd4.js → schema-form-CqM0ntNm.js} +1 -1
  72. package/dist/gateway/static/root/assets/sessions-page-CE9MK60k.js +1 -0
  73. package/dist/gateway/static/root/assets/settings-advanced-gate-D0grhQan.js +1 -0
  74. package/dist/gateway/static/root/assets/{settings-form-section-Dbnls9ty.js → settings-form-section-LqcDYJ2m.js} +1 -1
  75. package/dist/gateway/static/root/assets/{settings-page-CDKirYRG.js → settings-page-BpvBmheO.js} +1 -1
  76. package/dist/gateway/static/root/assets/setup-status-panel-CYuqNzWH.js +1 -0
  77. package/dist/gateway/static/root/assets/share-preview-page-F3qhAryJ.js +2 -0
  78. package/dist/gateway/static/root/assets/shares-settings-B4wSoQM3.js +1 -0
  79. package/dist/gateway/static/root/assets/skill-reload-api-C3LD7fkR.js +1 -0
  80. package/dist/gateway/static/root/assets/{skills-page-Bd0_ED_p.js → skills-page-D9fI548d.js} +1 -1
  81. package/dist/gateway/static/root/assets/{skills-page.utils-rA-XT-YG.js → skills-page.utils-DZNmyYqU.js} +1 -1
  82. package/dist/gateway/static/root/assets/{system-settings-panel-CSkMrHLz.js → system-settings-panel-CMsmFOhV.js} +1 -1
  83. package/dist/gateway/static/root/assets/task-detail-page-DRwTJ8E0.js +1 -0
  84. package/dist/gateway/static/root/assets/{theme-store-DWpIEWen.js → theme-store-mV6Cm2vj.js} +1 -1
  85. package/dist/gateway/static/root/assets/tunnel-api-DGaqXwql.js +1 -0
  86. package/dist/gateway/static/root/assets/{use-autosave-DkQAIAuB.js → use-autosave-bXCl0Y0j.js} +1 -1
  87. package/dist/gateway/static/root/assets/user-model-page-B3RjDSri.js +2 -0
  88. package/dist/gateway/static/root/assets/{voice-api-key-field-BrqcH0P-.js → voice-api-key-field-Cx9rl7mP.js} +1 -1
  89. package/dist/gateway/static/root/assets/{voice-config-api-BJ95Z3pT.js → voice-config-api-D3DdcH4H.js} +1 -1
  90. package/dist/gateway/static/root/assets/{work-discovery-overlay-vfTWKm13.js → work-discovery-overlay-ywJpaqKp.js} +1 -1
  91. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CHdr_ng9.js → workflow-run-setup-panel-DyGukxOL.js} +2 -2
  92. package/dist/gateway/static/root/assets/workflows-page-CifIqO2B.js +6 -0
  93. package/dist/gateway/static/root/index.html +9 -9
  94. package/dist/package.js +1 -1
  95. package/dist/packages/gateway-contract/src/device-pairing.js +41 -2
  96. package/dist/src/agent/child-agent-factory.d.ts +3 -0
  97. package/dist/src/agent/child-agent-factory.js +2 -1
  98. package/dist/src/agent/external-tools/endpoint-provider.js +1 -1
  99. package/dist/src/agent/service.js +20 -16
  100. package/dist/src/agent/tools/factory.d.ts +1 -0
  101. package/dist/src/agent/tools/factory.js +21 -4
  102. package/dist/src/agent/workflow/workflow-child-tools.js +2 -0
  103. package/dist/src/browser/native-messaging-host.d.ts +1 -1
  104. package/dist/src/browser/native-messaging-host.js +3 -2
  105. package/dist/src/browser/providers/browser-ext-install.d.ts +1 -1
  106. package/dist/src/browser/providers/browser-ext-install.js +8 -3
  107. package/dist/src/browser/providers/realtime-extension.d.ts +4 -1
  108. package/dist/src/browser/providers/realtime-extension.js +26 -10
  109. package/dist/src/browser/readiness.d.ts +4 -1
  110. package/dist/src/browser/readiness.js +11 -5
  111. package/dist/src/cli/commands/onboard.js +1 -1
  112. package/dist/src/gateway/gateway-workflow-host.types.d.ts +2 -0
  113. package/dist/src/gateway/heartbeat/service.js +1 -1
  114. package/dist/src/gateway/hono/app.js +5 -2
  115. package/dist/src/gateway/hono/routes/agent-stream.js +1 -1
  116. package/dist/src/gateway/hono/routes/browser.js +24 -11
  117. package/dist/src/gateway/hono/routes/device-pairing-approval.d.ts +9 -2
  118. package/dist/src/gateway/hono/routes/device-pairing-approval.js +50 -10
  119. package/dist/src/gateway/hono/routes/devices.d.ts +2 -1
  120. package/dist/src/gateway/hono/routes/devices.js +33 -16
  121. package/dist/src/gateway/hono/routes/endpoint-tools.js +9 -0
  122. package/dist/src/gateway/hono/routes/models.js +6 -0
  123. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +1 -1
  124. package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
  125. package/dist/src/gateway/hono/routes/side-chats.js +1 -1
  126. package/dist/src/gateway/hono/routes/tunnel.js +34 -2
  127. package/dist/src/gateway/hono/routes/voice.js +1 -1
  128. package/dist/src/gateway/rate-limit/buckets.js +2 -1
  129. package/dist/src/gateway/service/run-gateway-agent.js +1 -1
  130. package/dist/src/heartbeat/index.js +1 -1
  131. package/dist/src/providers/model-catalog-persistence.js +4 -0
  132. package/dist/src/providers/model-catalog-types.d.ts +1 -0
  133. package/dist/src/providers/xopc-cloud-model-source.js +12 -1
  134. package/dist/src/storage/sqlite/device-pairing-approval.js +3 -1
  135. package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +1 -0
  136. package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +9 -1
  137. package/dist/src/storage/sqlite/index.d.ts +1 -1
  138. package/dist/src/storage/sqlite/index.js +2 -2
  139. package/dist/src/tui/tui.js +1 -1
  140. package/dist/src/tunnel/xopc-cloud-registration.d.ts +5 -0
  141. package/dist/src/tunnel/xopc-cloud-registration.js +17 -11
  142. package/dist/src/workflows/service/workflow-run-service.js +4 -1
  143. package/package.json +2 -2
  144. package/dist/gateway/static/root/assets/agents-C0By2FIo.js +0 -1
  145. package/dist/gateway/static/root/assets/apps-page-BSJXKWcE.js +0 -1
  146. package/dist/gateway/static/root/assets/attachment-load-Dok_B1Gv.js +0 -2
  147. package/dist/gateway/static/root/assets/attachment-process-heavy-B2_kr5EA.js +0 -11
  148. package/dist/gateway/static/root/assets/automations-workspace--1TsZf3O.js +0 -6
  149. package/dist/gateway/static/root/assets/browser-settings-page-DOHgQer_.js +0 -8
  150. package/dist/gateway/static/root/assets/capabilities-settings-panel-Bpse_5K-.js +0 -8
  151. package/dist/gateway/static/root/assets/connectors-page-B_wLKGnH.js +0 -2
  152. package/dist/gateway/static/root/assets/device-pairing-wizard-B5_w9qlm.js +0 -1
  153. package/dist/gateway/static/root/assets/excel-worksheet-utils-EyvwNT4X.js +0 -1
  154. package/dist/gateway/static/root/assets/gateway-settings-CH-6vkgN.js +0 -1
  155. package/dist/gateway/static/root/assets/home-page-BJebAKB-.js +0 -1
  156. package/dist/gateway/static/root/assets/management-settings-r-iYWZFY.js +0 -1
  157. package/dist/gateway/static/root/assets/note-detail-panel-BV6nbgac.js +0 -5
  158. package/dist/gateway/static/root/assets/notes-page-BmLKoWwi.js +0 -8
  159. package/dist/gateway/static/root/assets/project-detail-page-CV8Lh296.js +0 -1
  160. package/dist/gateway/static/root/assets/projects-page-COweOaJP.js +0 -1
  161. package/dist/gateway/static/root/assets/remote-access-hub-ChPPGVU5.js +0 -1
  162. package/dist/gateway/static/root/assets/sessions-page-DgmHibOK.js +0 -1
  163. package/dist/gateway/static/root/assets/settings-advanced-gate-DV7kMco2.js +0 -1
  164. package/dist/gateway/static/root/assets/setup-status-panel-SQmtWoOs.js +0 -1
  165. package/dist/gateway/static/root/assets/share-preview-page-DwgLjS1m.js +0 -2
  166. package/dist/gateway/static/root/assets/shares-settings-Cs5bUrYw.js +0 -1
  167. package/dist/gateway/static/root/assets/skill-reload-api-DyKrjq7F.js +0 -1
  168. package/dist/gateway/static/root/assets/task-detail-page-CEQU-L_G.js +0 -1
  169. package/dist/gateway/static/root/assets/tunnel-api-COJcGnEi.js +0 -1
  170. package/dist/gateway/static/root/assets/user-model-page-BQAU55lf.js +0 -2
  171. 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
- _enum(["mobile", "browser"]);
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("This tab does not have a valid web address");
5860
+ throw new Error(t("errorInvalidWebAddress"));
5766
5861
  }
5767
5862
  if (url.protocol !== "http:" && url.protocol !== "https:") {
5768
- throw new Error("Chrome does not allow xopc to read this page. Open a regular http(s) page and try again.");
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("Chrome does not allow extensions to read Chrome Web Store pages.");
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(`Chrome blocked access to ${hostname}. Check xopc's site access for this page and try again.`);
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("The selected tab is no longer available");
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(`Allow xopc to access ${new URL(url).hostname} to continue.`);
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("Select text on the page first");
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("This page has no readable content");
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("This page has no readable content");
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("The selected tab is no longer available");
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("Could not read this page");
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("The page changed while xopc was reading it. Try again.");
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("No active web page");
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("Could not identify this page");
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("The page changed while xopc was connecting to it. Try again.");
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("Could not open the browser chat outbox"));
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("Browser chat outbox request failed"));
6034
+ request.onerror = () => reject(request.error ?? new Error(t("errorOutboxRequest")));
5937
6035
  transaction.oncomplete = () => resolve(result);
5938
- transaction.onerror = () => reject(transaction.error ?? new Error("Browser chat outbox transaction failed"));
5939
- transaction.onabort = () => reject(transaction.error ?? new Error("Browser chat outbox transaction was aborted"));
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.pairingLink !== "string" || typeof response.expiresAt !== "number") {
6018
- if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
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
- pairingLink: response.pairingLink,
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 parseBrowserPairingLink(value) {
6053
- const url = new URL(value.trim());
6054
- if (url.protocol !== "https:" || url.hostname !== "link.xopc.ai" || url.pathname !== "/connect") {
6055
- throw new Error("Pairing link is not from xopc");
6056
- }
6057
- const encoded = new URLSearchParams(url.hash.slice(1)).get("p");
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("Browser identity is still in use"));
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("Browser identity key export failed");
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(`Gateway did not respond within ${GATEWAY_REQUEST_TIMEOUT_MS / 1e3} seconds`);
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 ?? `Gateway returned ${response.status}`,
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("Gateway pairing response is unsigned");
6304
+ throw new Error(t("errorUnsignedPairing"));
6214
6305
  }
6215
6306
  if (!await verifyGateway(payload.gatewayPublicKey, response.signedPayload, response.signature)) {
6216
- throw new Error("Gateway identity could not be verified");
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("Gateway pairing response does not match this request");
6311
+ throw new Error(t("errorPairingMismatch"));
6221
6312
  }
6222
6313
  return decoded;
6223
6314
  }
6224
6315
  function shouldTryNextRoute(cause) {
6225
- return !(cause instanceof GatewayHttpError) || cause.status >= 500 || [404, 408, 425, 429].includes(cause.status);
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
- const nextRefreshToken = createRefreshToken();
6241
- const requestId = crypto.randomUUID();
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("No Gateway route could be reached");
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("Gateway identity could not be verified");
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("Gateway returned invalid browser credentials");
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(link, onApproval) {
6289
- const payload = parseBrowserPairingLink(link);
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 permissionTask = requestGatewayOriginPermissions(origins);
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 requestId = crypto.randomUUID();
6295
- const initialRefreshToken = createRefreshToken();
6296
- let { origin, result } = await signedPairingRequestAcrossRoutes(
6297
- pair,
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 (result.request.status !== "approved" && result.request.status !== "completed") {
6316
- throw new Error(`Pairing ${result.request.status}`);
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
- if (result.request.status !== "completed") {
6319
- const response = await signedPairingRequestAcrossRoutes(
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
- [origin, ...origins.filter((candidate) => candidate !== origin)],
6420
+ journal.payload,
6421
+ availableOrigins,
6323
6422
  requestId,
6324
- "complete",
6325
- {
6326
- idempotencyKey: crypto.randomUUID(),
6327
- initialRefreshToken
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
- ({ origin, result } = response);
6518
+ } catch (cause) {
6519
+ if (!(cause instanceof GatewayHttpError) || !["PAIRING_NOT_FOUND", "PAIRING_EXPIRED", "PAIRING_CANCELLED"].includes(cause.message)) throw cause;
6331
6520
  }
6332
- if (!result.request.deviceId) throw new Error("Gateway did not register the browser");
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
- return profile?.gatewayId && profile.gatewayUrl && profile.refreshToken ? profile : void 0;
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("Browser is not paired");
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(`Browser endpoint registration failed (${response.status})`);
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, captureTabWithPermission as b, canonicalJson as c, createBrowserEndpointHello as d, currentTabDescriptor as e, deleteBrowserOutbox as f, discoverLocalGateway as g, endpointToolContentSchema as h, gatewayFetch as i, getAccessProfile as j, readProfile as k, registerBrowserEndpoint as l, revokeAndForgetProfile as m, runWithTabSiteAccess as n, pairGateway as p, readBrowserOutbox as r, setAutoConnectEnabled as s, writeBrowserOutbox as w };
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 };