@xopcai/xopc 0.0.266 → 0.0.268
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/dist/assets/{auth-CknJ00ae.js → auth-cwEj8rFb.js} +735 -224
- package/dist/browser-ext/dist/assets/sidepanel-C86O7q5Y.css +299 -0
- package/dist/browser-ext/dist/background.js +699 -170
- package/dist/browser-ext/dist/sidepanel.html +3 -3
- package/dist/browser-ext/dist/sidepanel.js +9608 -777
- package/dist/browser-ext/manifest.json +3 -6
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Qrp5ALCF.js → agent-avatar-dicebear-B9WObxjr.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DwinST48.js → agent-defaults-settings-panel-J1YFNG26.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-Dlr0YyNt.js → agents-BJuTIRPD.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-bLNvz93c.js → app-management-settings-panel-Bbn9mDfL.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-D5qgIepF.js → appearance-settings-Deo5EsIc.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BQVlXhIb.js → apps-page-Cz4d7ErX.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-BEA7VAA4.js → archive-plugin-CxJTU8MU.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-B1RyF3uu.js → attachment-load-C71Cukgp.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BfH0FdKa.js → automations-page-_J7UZ55Q.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-C37AlD3E.js → automations-workspace-Bn8nwUOL.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-EU884ZXi.js → binary-plugins-CX8fC8Zt.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Bjr3naHb.js → block-editor-Ci9fjoQL.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-CDrYrNGy.js → browser-automations-page-rDSx0ozx.js} +1 -1
- package/dist/gateway/static/root/assets/browser-settings-page-B9RA6x4x.js +8 -0
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-slt1o04n.js → capabilities-settings-panel-C1GUih8m.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-oHTTX6Jy.js → channels-settings-CEhVLJNa.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DEYQdv6n.js → chat-terminal-dock-DMSl8KxS.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-olrbxHIT.js → connectors-page-IQAQDSmO.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-expression-DFrbDWpP.js → cron-expression-BByq6Uh8.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-CYriYKpH.js → date-picker-m6JZCUEW.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-XY3qUErs.js → dependency-picker-CwCAjbaJ.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Dsc__ynA.js → desktop-pet-DSmeEREd.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-EpuX3C08.js → desktop-pet-settings-MfKceMoe.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-B_NeLV4K.js → extension-debug-page-CwvWy26h.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-D3JC6mLB.js → extension-page-DLrbVBHM.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CXTQt1lp.js → extension-settings-page-BI6C4PM-.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-yeYm_UPE.js → gateway-settings-B3H7Vfbh.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-coh1LFsg.js → heartbeat-settings-CUl832Tb.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-CAgoBZqp.js → home-page-CT4JL2jm.js} +1 -1
- package/dist/gateway/static/root/assets/{index-Cn__9Un2.js → index-wKE8Q2FX.js} +7 -7
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Cu1bI3Bg.js → keyboard-shortcuts-settings-r4DCS19O.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-C9s2lFaI.js → local-app-workbench-page-qwLuir8A.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-CJ03Umjr.js → local-apps-page-DkfCRLUh.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-CY_DQKJ2.js → logs-page-DrNaaPWc.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-YPEemeMo.js → management-settings-Cfx4ATTa.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BjT6jlrA.js → media-plugins-DOLjWd3y.js} +1 -1
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-A8eLtV6v.js → mobile-pairing-wizard-BGW9joAb.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-panel-DHT2QksJ.js → note-detail-panel-Dbj5OvXE.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-BRFQiivM.js → notes-page-5hRB3WXc.js} +1 -1
- package/dist/gateway/static/root/assets/{page-tabs-DNUwulnl.js → page-tabs-_SGXMT2O.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B33KsKkJ.js → preview-open-alternatives-lVetfCWk.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-CItEFVt9.js → project-detail-page-CblWLA8k.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-Bmy-c_lL.js → projects-page-CGk-kzqs.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-H6B1cA2V.js → remote-access-hub-Du8PweyU.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BsCovC39.js → runtime-tools-settings-panel-BDqVrUEj.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-NsPb_Vam.js → schema-form-8RQgFJJO.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-CyYLSXk_.js → sessions-page-BFm94dJC.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-C0H-cToA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-BzPIPe1-.js → settings-form-section-j4UjGM-f.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-4idpMhsw.js → settings-page-lMi50B_s.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-D2ds_TTD.js → setup-status-panel-Dx4Fy3rU.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-Djo3VTaC.js → share-preview-page-aSyKRH5y.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-fhRzNfRp.js → shares-settings-CqF9s0pl.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-reload-api-Ct8tUju_.js → skill-reload-api-BBL_ZUSC.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-riM7FytZ.js → skills-page-C2_CAvxW.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-B3G8LBb-.js → skills-page.utils-B00e4Q30.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CW91jjnB.js → system-settings-panel-CulAkrsV.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-BBXKvl0k.js → task-detail-page-D1FmRFIn.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-DILER0Yb.js → tunnel-api-C01xgCWR.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-GxnP8vLO.js → use-autosave-DfSJ3c2p.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-I1H4XRfA.js → user-model-page-BGC_5baJ.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-CI3sXyl4.js → voice-api-key-field-9v-miN8o.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DZC9Exn3.js → work-discovery-overlay-CREYgh-1.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CaBaQER2.js → workflow-run-setup-panel-BfCWdw8F.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-C-_Dxihw.js → workflows-page-CvmKQkD0.js} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/packages/browser-control-contract/src/index.js +30 -1
- package/dist/src/agent/agent-manager.js +4 -4
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/embedded/model-runtime.js +1 -1
- package/dist/src/agent/embedded/run-for-session.js +1 -1
- package/dist/src/agent/external-tools/endpoint-provider.js +3 -1
- package/dist/src/agent/image/tool-model-config.js +1 -1
- package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
- package/dist/src/agent/service.js +2 -2
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -3
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/ai-assist/text-assist.js +1 -1
- package/dist/src/auth/credentials.js +2 -2
- package/dist/src/auth/oauth/xopc-cloud.js +1 -1
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +2 -2
- package/dist/src/browser/automations/runtime-service.d.ts +2 -0
- package/dist/src/browser/automations/runtime-service.js +1 -1
- package/dist/src/browser/drivers/create-driver.d.ts +2 -1
- package/dist/src/browser/drivers/create-driver.js +4 -6
- package/dist/src/browser/drivers/extension-driver.d.ts +7 -2
- package/dist/src/browser/enrollment.d.ts +8 -0
- package/dist/src/browser/enrollment.js +25 -0
- package/dist/src/browser/extension-identity.d.ts +2 -0
- package/dist/src/browser/extension-identity.js +5 -0
- package/dist/src/browser/native-messaging-host.d.ts +6 -3
- package/dist/src/browser/native-messaging-host.js +23 -5
- package/dist/src/browser/providers/browser-ext-install.d.ts +4 -1
- package/dist/src/browser/providers/browser-ext-install.js +47 -8
- package/dist/src/browser/providers/extension.js +1 -1
- package/dist/src/browser/providers/realtime-extension.d.ts +29 -0
- package/dist/src/browser/providers/realtime-extension.js +83 -0
- package/dist/src/browser/runtime/browser-runtime.js +3 -2
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/config/index.js +4 -4
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +1 -1
- package/dist/src/endpoint-tools/policy.js +10 -3
- package/dist/src/extensions/lockfile.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/file-path-classifier.js +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +6 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/routes/browser.js +20 -30
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/security/origin-check.js +5 -0
- package/dist/src/gateway/service/config-coordinator.d.ts +0 -2
- package/dist/src/gateway/service/config-coordinator.js +1 -3
- package/dist/src/gateway/service.d.ts +0 -11
- package/dist/src/gateway/service.js +11 -89
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/register-runtime-providers.js +1 -1
- package/dist/src/storage/sqlite/device-pairing-approval.js +24 -3
- package/dist/src/storage/sqlite/device-pairing-repository.d.ts +10 -1
- package/dist/src/storage/sqlite/device-pairing-repository.js +6 -5
- package/dist/src/storage/sqlite/migrations/157_browser_endpoint_principals.sql +50 -0
- package/dist/src/storage/sqlite/migrations/158_browser_native_enrollment.sql +4 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tui/tui-startup-resources.js +1 -1
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/work-discovery/service.js +1 -1
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/package.json +3 -3
- package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +0 -113
- package/dist/gateway/static/root/assets/browser-settings-page-DOmUkj2k.js +0 -8
- package/dist/gateway/static/root/assets/settings-advanced-gate-3T4rPk4A.js +0 -1
|
@@ -5099,6 +5099,562 @@ function superRefine(fn, params) {
|
|
|
5099
5099
|
return _superRefine(fn, params);
|
|
5100
5100
|
}
|
|
5101
5101
|
|
|
5102
|
+
const ENDPOINT_PROTOCOL_VERSION = 2;
|
|
5103
|
+
const endpointKindSchema = _enum(["web", "desktop", "mobile", "browser"]);
|
|
5104
|
+
const endpointAvailabilitySchema = _enum(["foreground", "background"]);
|
|
5105
|
+
const endpointEffectSchema = _enum(["read", "write", "destructive"]);
|
|
5106
|
+
const endpointConfirmationSchema = _enum(["never", "always"]);
|
|
5107
|
+
const endpointResultKindSchema = _enum(["text", "json", "file"]);
|
|
5108
|
+
const endpointSensitivitySchema = _enum(["public", "personal"]);
|
|
5109
|
+
const identifierSchema = string().min(1).max(160);
|
|
5110
|
+
const turnTokenSchema = string().min(32).max(160).regex(/^[A-Za-z0-9_-]+$/);
|
|
5111
|
+
const turnOriginSchema = discriminatedUnion("type", [
|
|
5112
|
+
strictObject({ type: literal("endpoint"), endpointId: identifierSchema }),
|
|
5113
|
+
strictObject({ type: literal("channel"), channel: string().min(1).max(64) }),
|
|
5114
|
+
strictObject({
|
|
5115
|
+
type: literal("system"),
|
|
5116
|
+
source: _enum(["cli", "automation", "heartbeat", "workflow", "internal"])
|
|
5117
|
+
})
|
|
5118
|
+
]);
|
|
5119
|
+
const endpointTurnOriginSchema = turnOriginSchema.options[0];
|
|
5120
|
+
endpointTurnOriginSchema.extend({ token: turnTokenSchema });
|
|
5121
|
+
const messageIdSchema$1 = uuid();
|
|
5122
|
+
const timestampSchema$1 = number().int().nonnegative();
|
|
5123
|
+
const jsonObjectSchema = record(string(), unknown());
|
|
5124
|
+
const endpointToolDescriptorSchema = strictObject({
|
|
5125
|
+
name: string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120),
|
|
5126
|
+
title: string().min(1).max(120),
|
|
5127
|
+
description: string().min(1).max(2e3),
|
|
5128
|
+
inputSchema: jsonObjectSchema,
|
|
5129
|
+
outputSchema: jsonObjectSchema,
|
|
5130
|
+
policyId: string().regex(/^[a-z][a-z0-9_.-]+$/).max(120),
|
|
5131
|
+
sensitivity: endpointSensitivitySchema,
|
|
5132
|
+
effect: endpointEffectSchema,
|
|
5133
|
+
confirmation: endpointConfirmationSchema,
|
|
5134
|
+
requiresForeground: boolean(),
|
|
5135
|
+
requiredPermissions: array(string().min(1).max(120)).max(20),
|
|
5136
|
+
timeoutMs: number().int().min(1e3).max(3e5),
|
|
5137
|
+
maxConcurrency: number().int().min(1).max(4),
|
|
5138
|
+
supportsCancellation: boolean(),
|
|
5139
|
+
idempotent: boolean(),
|
|
5140
|
+
resultKinds: array(endpointResultKindSchema).min(1).max(3)
|
|
5141
|
+
});
|
|
5142
|
+
strictObject({
|
|
5143
|
+
principalId: identifierSchema,
|
|
5144
|
+
displayName: string().min(1).max(120),
|
|
5145
|
+
kind: endpointKindSchema,
|
|
5146
|
+
platform: string().min(1).max(80),
|
|
5147
|
+
publicKey: string().min(32).max(2048)
|
|
5148
|
+
});
|
|
5149
|
+
strictObject({
|
|
5150
|
+
endpointId: identifierSchema
|
|
5151
|
+
});
|
|
5152
|
+
const backgroundCapabilitySchema = string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120);
|
|
5153
|
+
strictObject({
|
|
5154
|
+
jobId: uuid(),
|
|
5155
|
+
endpointId: identifierSchema,
|
|
5156
|
+
capability: backgroundCapabilitySchema,
|
|
5157
|
+
input: jsonObjectSchema,
|
|
5158
|
+
idempotencyKey: identifierSchema,
|
|
5159
|
+
consentGrantId: identifierSchema,
|
|
5160
|
+
createdAt: timestampSchema$1,
|
|
5161
|
+
expiresAt: timestampSchema$1
|
|
5162
|
+
});
|
|
5163
|
+
strictObject({
|
|
5164
|
+
jobId: uuid(),
|
|
5165
|
+
status: _enum(["queued", "running", "succeeded", "failed", "cancelled"]),
|
|
5166
|
+
updatedAt: timestampSchema$1,
|
|
5167
|
+
output: jsonObjectSchema.optional(),
|
|
5168
|
+
error: strictObject({
|
|
5169
|
+
code: identifierSchema,
|
|
5170
|
+
message: string().min(1).max(2e3)
|
|
5171
|
+
}).optional()
|
|
5172
|
+
});
|
|
5173
|
+
strictObject({
|
|
5174
|
+
eventId: uuid(),
|
|
5175
|
+
endpointId: identifierSchema,
|
|
5176
|
+
subscriptionId: identifierSchema,
|
|
5177
|
+
eventType: backgroundCapabilitySchema,
|
|
5178
|
+
data: jsonObjectSchema,
|
|
5179
|
+
occurredAt: timestampSchema$1
|
|
5180
|
+
});
|
|
5181
|
+
const endpointHelloPayloadSchema = strictObject({
|
|
5182
|
+
principalId: identifierSchema,
|
|
5183
|
+
endpointId: identifierSchema,
|
|
5184
|
+
connectionInstanceId: uuid(),
|
|
5185
|
+
displayName: string().min(1).max(120),
|
|
5186
|
+
kind: endpointKindSchema,
|
|
5187
|
+
platform: string().min(1).max(80),
|
|
5188
|
+
appVersion: string().min(1).max(80),
|
|
5189
|
+
availability: endpointAvailabilitySchema,
|
|
5190
|
+
nonce: identifierSchema,
|
|
5191
|
+
signedAt: timestampSchema$1,
|
|
5192
|
+
signature: string().min(16).max(1024),
|
|
5193
|
+
tools: array(endpointToolDescriptorSchema).max(100)
|
|
5194
|
+
});
|
|
5195
|
+
const toolInvokePayloadSchema = strictObject({
|
|
5196
|
+
invocationId: uuid(),
|
|
5197
|
+
toolCallId: identifierSchema,
|
|
5198
|
+
toolName: endpointToolDescriptorSchema.shape.name,
|
|
5199
|
+
arguments: jsonObjectSchema,
|
|
5200
|
+
descriptorRevision: string().min(1).max(128),
|
|
5201
|
+
confirmationRequired: boolean(),
|
|
5202
|
+
deadlineAt: timestampSchema$1,
|
|
5203
|
+
uploadGrant: strictObject({
|
|
5204
|
+
path: string().startsWith("/").max(500),
|
|
5205
|
+
token: identifierSchema,
|
|
5206
|
+
maxBytes: number().int().positive(),
|
|
5207
|
+
maxFiles: number().int().min(1).max(20),
|
|
5208
|
+
expiresAt: timestampSchema$1
|
|
5209
|
+
}).optional()
|
|
5210
|
+
});
|
|
5211
|
+
const toolCancelPayloadSchema = strictObject({
|
|
5212
|
+
invocationId: uuid(),
|
|
5213
|
+
reason: string().min(1).max(500)
|
|
5214
|
+
});
|
|
5215
|
+
const invocationPayloadSchema = strictObject({ invocationId: uuid() });
|
|
5216
|
+
const endpointToolContentSchema = discriminatedUnion("type", [
|
|
5217
|
+
strictObject({ type: literal("text"), text: string().max(256 * 1024) }),
|
|
5218
|
+
strictObject({ type: literal("json"), value: unknown() }),
|
|
5219
|
+
strictObject({
|
|
5220
|
+
type: literal("file"),
|
|
5221
|
+
fileId: identifierSchema,
|
|
5222
|
+
name: string().min(1).max(255),
|
|
5223
|
+
mimeType: string().min(1).max(255),
|
|
5224
|
+
size: number().int().nonnegative(),
|
|
5225
|
+
sha256: string().regex(/^[a-f0-9]{64}$/)
|
|
5226
|
+
})
|
|
5227
|
+
]);
|
|
5228
|
+
const endpointToolErrorCodeSchema = _enum([
|
|
5229
|
+
"ENDPOINT_OFFLINE",
|
|
5230
|
+
"ENDPOINT_NOT_FOREGROUND",
|
|
5231
|
+
"ENDPOINT_DISCONNECTED",
|
|
5232
|
+
"TOOL_NOT_FOUND",
|
|
5233
|
+
"TOOL_REVISION_MISMATCH",
|
|
5234
|
+
"INVALID_ARGUMENTS",
|
|
5235
|
+
"PERMISSION_DENIED",
|
|
5236
|
+
"USER_DENIED",
|
|
5237
|
+
"TOOL_BUSY",
|
|
5238
|
+
"TOOL_TIMEOUT",
|
|
5239
|
+
"TOOL_CANCELLED",
|
|
5240
|
+
"RESULT_TOO_LARGE",
|
|
5241
|
+
"PROTOCOL_ERROR"
|
|
5242
|
+
]);
|
|
5243
|
+
const envelope$1 = (type, payload) => strictObject({
|
|
5244
|
+
protocolVersion: literal(ENDPOINT_PROTOCOL_VERSION),
|
|
5245
|
+
messageId: messageIdSchema$1,
|
|
5246
|
+
type: literal(type),
|
|
5247
|
+
sentAt: timestampSchema$1,
|
|
5248
|
+
payload
|
|
5249
|
+
});
|
|
5250
|
+
const clientEndpointMessageSchema = discriminatedUnion("type", [
|
|
5251
|
+
envelope$1("endpoint.availability_changed", strictObject({ availability: endpointAvailabilitySchema })),
|
|
5252
|
+
envelope$1("tool.received", invocationPayloadSchema),
|
|
5253
|
+
envelope$1("tool.progress", strictObject({
|
|
5254
|
+
invocationId: uuid(),
|
|
5255
|
+
message: string().max(500).optional(),
|
|
5256
|
+
percent: number().min(0).max(100).optional()
|
|
5257
|
+
})),
|
|
5258
|
+
envelope$1("tool.result", strictObject({
|
|
5259
|
+
invocationId: uuid(),
|
|
5260
|
+
content: array(endpointToolContentSchema).min(1).max(20),
|
|
5261
|
+
details: jsonObjectSchema.optional()
|
|
5262
|
+
})),
|
|
5263
|
+
envelope$1("tool.error", strictObject({
|
|
5264
|
+
invocationId: uuid(),
|
|
5265
|
+
code: endpointToolErrorCodeSchema,
|
|
5266
|
+
message: string().min(1).max(2e3)
|
|
5267
|
+
})),
|
|
5268
|
+
envelope$1("tool.cancelled", invocationPayloadSchema)
|
|
5269
|
+
]);
|
|
5270
|
+
const serverEndpointMessageSchema = discriminatedUnion("type", [
|
|
5271
|
+
envelope$1("tool.invoke", toolInvokePayloadSchema),
|
|
5272
|
+
envelope$1("tool.cancel", toolCancelPayloadSchema)
|
|
5273
|
+
]);
|
|
5274
|
+
function canonicalJson(value) {
|
|
5275
|
+
if (value === null) return "null";
|
|
5276
|
+
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
5277
|
+
if (typeof value === "number") {
|
|
5278
|
+
if (!Number.isFinite(value)) throw new Error("Canonical JSON only supports finite numbers");
|
|
5279
|
+
return JSON.stringify(value);
|
|
5280
|
+
}
|
|
5281
|
+
if (typeof value !== "object") throw new Error(`Canonical JSON does not support ${typeof value}`);
|
|
5282
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
5283
|
+
const prototype = Object.getPrototypeOf(value);
|
|
5284
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
5285
|
+
throw new Error("Canonical JSON only supports plain objects");
|
|
5286
|
+
}
|
|
5287
|
+
const record = value;
|
|
5288
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
5289
|
+
}
|
|
5290
|
+
function endpointHelloSigningPayload(payload) {
|
|
5291
|
+
const { signature: _signature, ...unsigned } = payload;
|
|
5292
|
+
return canonicalJson(unsigned);
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
const REALTIME_PROTOCOL_VERSION = 1;
|
|
5296
|
+
const REALTIME_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
5297
|
+
const REALTIME_HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
5298
|
+
const realtimeClientKindSchema = _enum([
|
|
5299
|
+
"web",
|
|
5300
|
+
"desktop",
|
|
5301
|
+
"mobile",
|
|
5302
|
+
"browser_extension",
|
|
5303
|
+
"tui",
|
|
5304
|
+
"mcp"
|
|
5305
|
+
]);
|
|
5306
|
+
const realtimeTopicSchema = string().min(1).max(512);
|
|
5307
|
+
const realtimeEventNameSchema = string().regex(/^[a-z][a-z0-9_.-]*$/).max(160);
|
|
5308
|
+
const messageIdSchema = uuid();
|
|
5309
|
+
const timestampSchema = number().int().nonnegative();
|
|
5310
|
+
const sequenceSchema = number().int().positive();
|
|
5311
|
+
const envelope = (kind, payload) => strictObject({
|
|
5312
|
+
protocolVersion: literal(REALTIME_PROTOCOL_VERSION),
|
|
5313
|
+
messageId: messageIdSchema,
|
|
5314
|
+
kind: literal(kind),
|
|
5315
|
+
sentAt: timestampSchema,
|
|
5316
|
+
payload
|
|
5317
|
+
});
|
|
5318
|
+
const realtimeSubscriptionSchema = strictObject({
|
|
5319
|
+
topic: realtimeTopicSchema,
|
|
5320
|
+
afterSeq: number().int().nonnegative().optional()
|
|
5321
|
+
});
|
|
5322
|
+
discriminatedUnion("kind", [
|
|
5323
|
+
envelope("realtime.hello", strictObject({
|
|
5324
|
+
ticket: string().min(32).max(512),
|
|
5325
|
+
clientId: string().min(1).max(160),
|
|
5326
|
+
clientKind: realtimeClientKindSchema,
|
|
5327
|
+
subscriptions: array(realtimeSubscriptionSchema).max(100).default([]),
|
|
5328
|
+
endpoint: endpointHelloPayloadSchema.optional()
|
|
5329
|
+
})),
|
|
5330
|
+
envelope("realtime.subscribe", strictObject({
|
|
5331
|
+
subscriptions: array(realtimeSubscriptionSchema).min(1).max(100)
|
|
5332
|
+
})),
|
|
5333
|
+
envelope("realtime.unsubscribe", strictObject({
|
|
5334
|
+
topics: array(realtimeTopicSchema).min(1).max(100)
|
|
5335
|
+
})),
|
|
5336
|
+
envelope("realtime.ping", strictObject({})),
|
|
5337
|
+
envelope("endpoint.message", clientEndpointMessageSchema)
|
|
5338
|
+
]);
|
|
5339
|
+
const realtimeEventPayloadSchema = strictObject({
|
|
5340
|
+
topic: realtimeTopicSchema,
|
|
5341
|
+
seq: sequenceSchema,
|
|
5342
|
+
event: realtimeEventNameSchema,
|
|
5343
|
+
data: unknown()
|
|
5344
|
+
});
|
|
5345
|
+
const serverRealtimeMessageSchema = discriminatedUnion("kind", [
|
|
5346
|
+
envelope("realtime.ready", strictObject({
|
|
5347
|
+
connectionId: uuid(),
|
|
5348
|
+
heartbeatIntervalMs: literal(REALTIME_HEARTBEAT_INTERVAL_MS),
|
|
5349
|
+
heartbeatTimeoutMs: literal(REALTIME_HEARTBEAT_TIMEOUT_MS),
|
|
5350
|
+
endpoint: strictObject({
|
|
5351
|
+
endpointId: string().min(1).max(160),
|
|
5352
|
+
turnToken: string().min(32).max(160)
|
|
5353
|
+
}).optional()
|
|
5354
|
+
})),
|
|
5355
|
+
envelope("realtime.event", realtimeEventPayloadSchema),
|
|
5356
|
+
envelope("realtime.subscribed", strictObject({
|
|
5357
|
+
topic: realtimeTopicSchema,
|
|
5358
|
+
cursor: number().int().nonnegative()
|
|
5359
|
+
})),
|
|
5360
|
+
envelope("realtime.gap", strictObject({
|
|
5361
|
+
topic: realtimeTopicSchema,
|
|
5362
|
+
requestedSeq: number().int().nonnegative(),
|
|
5363
|
+
earliestSeq: sequenceSchema,
|
|
5364
|
+
recoverable: boolean()
|
|
5365
|
+
})),
|
|
5366
|
+
envelope("realtime.pong", strictObject({})),
|
|
5367
|
+
envelope("realtime.error", strictObject({
|
|
5368
|
+
code: string().min(1).max(80),
|
|
5369
|
+
message: string().min(1).max(500)
|
|
5370
|
+
})),
|
|
5371
|
+
envelope("endpoint.message", serverEndpointMessageSchema)
|
|
5372
|
+
]);
|
|
5373
|
+
function parseServerRealtimeMessage(value) {
|
|
5374
|
+
return serverRealtimeMessageSchema.parse(value);
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
function frameText(data) {
|
|
5378
|
+
if (typeof data === "string") return data;
|
|
5379
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
5380
|
+
if (ArrayBuffer.isView(data)) {
|
|
5381
|
+
const bytes = Uint8Array.from(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
5382
|
+
return new TextDecoder().decode(bytes);
|
|
5383
|
+
}
|
|
5384
|
+
throw new Error("Realtime server sent a non-text frame");
|
|
5385
|
+
}
|
|
5386
|
+
function isNonRetryableCloseCode(code) {
|
|
5387
|
+
return code === 1009 || code === 4400 || code === 4401 || code === 4413;
|
|
5388
|
+
}
|
|
5389
|
+
class RealtimeClient {
|
|
5390
|
+
constructor(options) {
|
|
5391
|
+
this.options = options;
|
|
5392
|
+
}
|
|
5393
|
+
options;
|
|
5394
|
+
desiredSubscriptions = /* @__PURE__ */ new Map();
|
|
5395
|
+
cursors = /* @__PURE__ */ new Map();
|
|
5396
|
+
socket;
|
|
5397
|
+
reconnectTimer;
|
|
5398
|
+
heartbeatTimer;
|
|
5399
|
+
connectionTimer;
|
|
5400
|
+
ticketAbort;
|
|
5401
|
+
generation = 0;
|
|
5402
|
+
reconnectAttempts = 0;
|
|
5403
|
+
shouldReconnect = false;
|
|
5404
|
+
ready = false;
|
|
5405
|
+
lastServerMessageAt = 0;
|
|
5406
|
+
endpointBinding;
|
|
5407
|
+
reconnectWhenSocketOpens = false;
|
|
5408
|
+
connect() {
|
|
5409
|
+
if (this.shouldReconnect) return;
|
|
5410
|
+
this.shouldReconnect = true;
|
|
5411
|
+
void this.open(false);
|
|
5412
|
+
}
|
|
5413
|
+
disconnect() {
|
|
5414
|
+
this.shouldReconnect = false;
|
|
5415
|
+
this.reconnectWhenSocketOpens = false;
|
|
5416
|
+
this.generation += 1;
|
|
5417
|
+
this.clearTimers();
|
|
5418
|
+
this.ready = false;
|
|
5419
|
+
this.endpointBinding?.onDisconnected?.();
|
|
5420
|
+
this.socket?.close(1e3, "Client disconnected");
|
|
5421
|
+
this.socket = void 0;
|
|
5422
|
+
this.options.onStateChange?.("disconnected");
|
|
5423
|
+
}
|
|
5424
|
+
reconnect() {
|
|
5425
|
+
this.reconnectWhenSocketOpens = false;
|
|
5426
|
+
this.generation += 1;
|
|
5427
|
+
this.clearTimers();
|
|
5428
|
+
this.ready = false;
|
|
5429
|
+
this.endpointBinding?.onDisconnected?.();
|
|
5430
|
+
this.socket?.close(1e3, "Client reconnecting");
|
|
5431
|
+
this.socket = void 0;
|
|
5432
|
+
this.reconnectAttempts = 0;
|
|
5433
|
+
this.shouldReconnect = true;
|
|
5434
|
+
void this.open(true);
|
|
5435
|
+
}
|
|
5436
|
+
subscribe(topic, afterSeq) {
|
|
5437
|
+
this.desiredSubscriptions.set(topic, afterSeq);
|
|
5438
|
+
if (afterSeq !== void 0) this.cursors.set(topic, afterSeq);
|
|
5439
|
+
if (this.ready) {
|
|
5440
|
+
this.sendSubscriptions([{ topic, afterSeq: this.cursors.get(topic) ?? afterSeq }]);
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
unsubscribe(topic) {
|
|
5444
|
+
this.desiredSubscriptions.delete(topic);
|
|
5445
|
+
if (this.ready) this.send(this.clientMessage("realtime.unsubscribe", { topics: [topic] }));
|
|
5446
|
+
}
|
|
5447
|
+
sendEndpointMessage(message) {
|
|
5448
|
+
if (!this.ready) throw new Error("Realtime connection is not ready");
|
|
5449
|
+
this.send(this.clientMessage("endpoint.message", message));
|
|
5450
|
+
}
|
|
5451
|
+
setEndpoint(binding) {
|
|
5452
|
+
this.endpointBinding = binding;
|
|
5453
|
+
this.refreshEndpointConnection();
|
|
5454
|
+
}
|
|
5455
|
+
clearEndpoint(binding) {
|
|
5456
|
+
if (binding && this.endpointBinding !== binding) return;
|
|
5457
|
+
const previous = this.endpointBinding;
|
|
5458
|
+
this.endpointBinding = void 0;
|
|
5459
|
+
previous?.onDisconnected?.();
|
|
5460
|
+
this.refreshEndpointConnection();
|
|
5461
|
+
}
|
|
5462
|
+
refreshEndpointConnection() {
|
|
5463
|
+
if (!this.shouldReconnect) return;
|
|
5464
|
+
if (this.socket?.readyState === 0) {
|
|
5465
|
+
this.reconnectWhenSocketOpens = true;
|
|
5466
|
+
return;
|
|
5467
|
+
}
|
|
5468
|
+
this.reconnect();
|
|
5469
|
+
}
|
|
5470
|
+
async open(reconnecting) {
|
|
5471
|
+
const generation = ++this.generation;
|
|
5472
|
+
const timeoutMs = this.options.connectionTimeoutMs ?? 15e3;
|
|
5473
|
+
const ticketAbort = new AbortController();
|
|
5474
|
+
this.ticketAbort?.abort();
|
|
5475
|
+
this.ticketAbort = ticketAbort;
|
|
5476
|
+
let socket;
|
|
5477
|
+
let closed = false;
|
|
5478
|
+
let messageQueue;
|
|
5479
|
+
const failAttempt = (error, retry = true) => {
|
|
5480
|
+
if (closed || generation !== this.generation) return;
|
|
5481
|
+
closed = true;
|
|
5482
|
+
this.clearConnectionTimer();
|
|
5483
|
+
ticketAbort.abort();
|
|
5484
|
+
if (this.ticketAbort === ticketAbort) this.ticketAbort = void 0;
|
|
5485
|
+
this.ready = false;
|
|
5486
|
+
this.endpointBinding?.onDisconnected?.();
|
|
5487
|
+
this.clearHeartbeat();
|
|
5488
|
+
if (this.socket === socket) this.socket = void 0;
|
|
5489
|
+
if (socket) {
|
|
5490
|
+
socket.onopen = null;
|
|
5491
|
+
socket.onmessage = null;
|
|
5492
|
+
socket.onerror = null;
|
|
5493
|
+
socket.onclose = null;
|
|
5494
|
+
socket.close(4e3, error.slice(0, 120));
|
|
5495
|
+
}
|
|
5496
|
+
if (this.shouldReconnect && retry) this.scheduleReconnect(error);
|
|
5497
|
+
else if (this.shouldReconnect) {
|
|
5498
|
+
this.shouldReconnect = false;
|
|
5499
|
+
this.options.onStateChange?.("error", error);
|
|
5500
|
+
} else this.options.onStateChange?.("disconnected");
|
|
5501
|
+
};
|
|
5502
|
+
this.connectionTimer = setTimeout(() => failAttempt("Realtime connection timed out"), timeoutMs);
|
|
5503
|
+
this.options.onStateChange?.(reconnecting ? "reconnecting" : "connecting");
|
|
5504
|
+
try {
|
|
5505
|
+
const [ticket, endpoint] = await Promise.all([
|
|
5506
|
+
this.options.issueTicket(ticketAbort.signal),
|
|
5507
|
+
this.endpointBinding?.createHello()
|
|
5508
|
+
]);
|
|
5509
|
+
if (closed || !this.shouldReconnect || generation !== this.generation) return;
|
|
5510
|
+
socket = this.options.createWebSocket(this.options.getWebSocketUrl());
|
|
5511
|
+
this.socket = socket;
|
|
5512
|
+
socket.onopen = () => {
|
|
5513
|
+
if (generation !== this.generation) return;
|
|
5514
|
+
if (this.reconnectWhenSocketOpens) {
|
|
5515
|
+
this.reconnect();
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
const subscriptions = [...this.desiredSubscriptions].map(([topic, afterSeq]) => ({
|
|
5519
|
+
topic,
|
|
5520
|
+
afterSeq: this.cursors.get(topic) ?? afterSeq
|
|
5521
|
+
}));
|
|
5522
|
+
this.send(this.clientMessage("realtime.hello", {
|
|
5523
|
+
ticket,
|
|
5524
|
+
clientId: this.options.clientId,
|
|
5525
|
+
clientKind: this.options.clientKind,
|
|
5526
|
+
subscriptions,
|
|
5527
|
+
...endpoint ? { endpoint } : {}
|
|
5528
|
+
}));
|
|
5529
|
+
};
|
|
5530
|
+
socket.onmessage = (event) => {
|
|
5531
|
+
if (generation !== this.generation) return;
|
|
5532
|
+
const process = () => {
|
|
5533
|
+
if (closed || generation !== this.generation) return;
|
|
5534
|
+
return this.handleServerMessage(frameText(event.data));
|
|
5535
|
+
};
|
|
5536
|
+
if (!messageQueue) {
|
|
5537
|
+
try {
|
|
5538
|
+
const pending = process();
|
|
5539
|
+
if (!pending) return;
|
|
5540
|
+
const current2 = pending.catch((error) => failAttempt(error instanceof Error ? error.message : "Invalid realtime server message")).then(() => void 0);
|
|
5541
|
+
messageQueue = current2;
|
|
5542
|
+
void current2.finally(() => {
|
|
5543
|
+
if (messageQueue === current2) messageQueue = void 0;
|
|
5544
|
+
});
|
|
5545
|
+
} catch (error) {
|
|
5546
|
+
failAttempt(error instanceof Error ? error.message : "Invalid realtime server message");
|
|
5547
|
+
}
|
|
5548
|
+
return;
|
|
5549
|
+
}
|
|
5550
|
+
const current = messageQueue.then(process).catch((error) => failAttempt(error instanceof Error ? error.message : "Invalid realtime server message")).then(() => void 0);
|
|
5551
|
+
messageQueue = current;
|
|
5552
|
+
void current.finally(() => {
|
|
5553
|
+
if (messageQueue === current) messageQueue = void 0;
|
|
5554
|
+
});
|
|
5555
|
+
};
|
|
5556
|
+
socket.onerror = () => {
|
|
5557
|
+
};
|
|
5558
|
+
socket.onclose = (event) => {
|
|
5559
|
+
failAttempt(
|
|
5560
|
+
event.reason || `WebSocket closed (${event.code ?? 0})`,
|
|
5561
|
+
!isNonRetryableCloseCode(event.code)
|
|
5562
|
+
);
|
|
5563
|
+
};
|
|
5564
|
+
} catch (error) {
|
|
5565
|
+
failAttempt(error instanceof Error ? error.message : "Realtime connection failed");
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
handleServerMessage(text) {
|
|
5569
|
+
const message = parseServerRealtimeMessage(JSON.parse(text));
|
|
5570
|
+
this.lastServerMessageAt = Date.now();
|
|
5571
|
+
if (message.kind === "realtime.ready") {
|
|
5572
|
+
this.ready = true;
|
|
5573
|
+
this.reconnectAttempts = 0;
|
|
5574
|
+
this.clearConnectionTimer();
|
|
5575
|
+
this.ticketAbort = void 0;
|
|
5576
|
+
this.options.onStateChange?.("connected");
|
|
5577
|
+
if (message.payload.endpoint) this.endpointBinding?.onReady(message.payload.endpoint);
|
|
5578
|
+
this.clearHeartbeat();
|
|
5579
|
+
this.heartbeatTimer = setInterval(() => {
|
|
5580
|
+
if (!this.ready) return;
|
|
5581
|
+
if (Date.now() - this.lastServerMessageAt >= message.payload.heartbeatTimeoutMs) {
|
|
5582
|
+
this.reconnect();
|
|
5583
|
+
return;
|
|
5584
|
+
}
|
|
5585
|
+
this.send(this.clientMessage("realtime.ping", {}));
|
|
5586
|
+
}, message.payload.heartbeatIntervalMs);
|
|
5587
|
+
} else if (message.kind === "realtime.event") {
|
|
5588
|
+
const cursor = this.cursors.get(message.payload.topic);
|
|
5589
|
+
if (cursor !== void 0 && message.payload.seq <= cursor) return;
|
|
5590
|
+
this.options.onEvent?.(message.payload);
|
|
5591
|
+
this.cursors.set(message.payload.topic, message.payload.seq);
|
|
5592
|
+
} else if (message.kind === "realtime.subscribed") {
|
|
5593
|
+
this.cursors.set(message.payload.topic, message.payload.cursor);
|
|
5594
|
+
} else if (message.kind === "realtime.gap") {
|
|
5595
|
+
this.cursors.set(message.payload.topic, message.payload.earliestSeq - 1);
|
|
5596
|
+
return this.options.onGap?.(message.payload);
|
|
5597
|
+
} else if (message.kind === "realtime.error") {
|
|
5598
|
+
this.options.onStateChange?.("error", message.payload.message);
|
|
5599
|
+
} else if (message.kind === "endpoint.message") {
|
|
5600
|
+
this.endpointBinding?.onMessage(message.payload);
|
|
5601
|
+
this.options.onEndpointMessage?.(message.payload);
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
sendSubscriptions(subscriptions) {
|
|
5605
|
+
this.send(this.clientMessage("realtime.subscribe", { subscriptions }));
|
|
5606
|
+
}
|
|
5607
|
+
clientMessage(kind, payload) {
|
|
5608
|
+
const messageId = this.options.createMessageId?.() ?? globalThis.crypto?.randomUUID?.();
|
|
5609
|
+
if (!messageId) throw new Error("RealtimeClient requires createMessageId when crypto.randomUUID is unavailable");
|
|
5610
|
+
return {
|
|
5611
|
+
protocolVersion: REALTIME_PROTOCOL_VERSION,
|
|
5612
|
+
messageId,
|
|
5613
|
+
kind,
|
|
5614
|
+
sentAt: Date.now(),
|
|
5615
|
+
payload
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
5618
|
+
send(message) {
|
|
5619
|
+
if (!this.socket || this.socket.readyState !== 1) return;
|
|
5620
|
+
this.socket.send(JSON.stringify(message));
|
|
5621
|
+
}
|
|
5622
|
+
scheduleReconnect(error) {
|
|
5623
|
+
if (!this.shouldReconnect) return;
|
|
5624
|
+
this.reconnectAttempts += 1;
|
|
5625
|
+
const max = this.options.maxReconnectAttempts;
|
|
5626
|
+
if (max !== void 0 && this.reconnectAttempts > max) {
|
|
5627
|
+
this.shouldReconnect = false;
|
|
5628
|
+
this.options.onStateChange?.("error", error);
|
|
5629
|
+
return;
|
|
5630
|
+
}
|
|
5631
|
+
this.options.onStateChange?.("reconnecting", error);
|
|
5632
|
+
const cap = Math.min(3e4, 1e3 * 2 ** Math.min(this.reconnectAttempts - 1, 5));
|
|
5633
|
+
const delay = Math.round(cap * (0.5 + Math.random() * 0.5));
|
|
5634
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5635
|
+
this.reconnectTimer = setTimeout(() => {
|
|
5636
|
+
this.reconnectTimer = void 0;
|
|
5637
|
+
void this.open(true);
|
|
5638
|
+
}, delay);
|
|
5639
|
+
}
|
|
5640
|
+
clearHeartbeat() {
|
|
5641
|
+
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
5642
|
+
this.heartbeatTimer = void 0;
|
|
5643
|
+
}
|
|
5644
|
+
clearTimers() {
|
|
5645
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5646
|
+
this.reconnectTimer = void 0;
|
|
5647
|
+
this.clearConnectionTimer();
|
|
5648
|
+
this.ticketAbort?.abort();
|
|
5649
|
+
this.ticketAbort = void 0;
|
|
5650
|
+
this.clearHeartbeat();
|
|
5651
|
+
}
|
|
5652
|
+
clearConnectionTimer() {
|
|
5653
|
+
if (this.connectionTimer) clearTimeout(this.connectionTimer);
|
|
5654
|
+
this.connectionTimer = void 0;
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5102
5658
|
const devicePairingKeySchema = strictObject({
|
|
5103
5659
|
kty: literal("EC"),
|
|
5104
5660
|
crv: literal("P-256"),
|
|
@@ -5201,15 +5757,57 @@ function truncateUtf8(value, maxBytes) {
|
|
|
5201
5757
|
return { value: value.slice(0, low), truncated: true };
|
|
5202
5758
|
}
|
|
5203
5759
|
function sanitizedUrl(raw) {
|
|
5204
|
-
|
|
5760
|
+
let url;
|
|
5761
|
+
try {
|
|
5762
|
+
url = new URL(raw);
|
|
5763
|
+
} catch {
|
|
5764
|
+
throw new Error("This tab does not have a valid web address");
|
|
5765
|
+
}
|
|
5205
5766
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5206
|
-
throw new Error("
|
|
5767
|
+
throw new Error("Chrome does not allow xopc to read this page. Open a regular http(s) page and try again.");
|
|
5768
|
+
}
|
|
5769
|
+
if (url.hostname === "chromewebstore.google.com" || url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
|
|
5770
|
+
throw new Error("Chrome does not allow extensions to read Chrome Web Store pages.");
|
|
5207
5771
|
}
|
|
5208
5772
|
url.username = "";
|
|
5209
5773
|
url.password = "";
|
|
5210
5774
|
url.hash = "";
|
|
5211
5775
|
return url.toString();
|
|
5212
5776
|
}
|
|
5777
|
+
function siteAccessError(cause, url) {
|
|
5778
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
5779
|
+
const hostname = new URL(url).hostname;
|
|
5780
|
+
if (/cannot access|cannot be scripted|missing host permission|extensions gallery|chrome web store/i.test(message)) {
|
|
5781
|
+
return new Error(`Chrome blocked access to ${hostname}. Check xopc's site access for this page and try again.`);
|
|
5782
|
+
}
|
|
5783
|
+
return cause instanceof Error ? cause : new Error(message);
|
|
5784
|
+
}
|
|
5785
|
+
async function runWithTabSiteAccess(tabId, operation) {
|
|
5786
|
+
const tab = await chrome.tabs.get(tabId);
|
|
5787
|
+
if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) {
|
|
5788
|
+
throw new Error("The selected tab is no longer available");
|
|
5789
|
+
}
|
|
5790
|
+
const url = sanitizedUrl(tab.url);
|
|
5791
|
+
const originPattern = `${new URL(url).origin}/*`;
|
|
5792
|
+
const alreadyGranted = await chrome.permissions.contains({ origins: [originPattern] });
|
|
5793
|
+
let newlyGranted = false;
|
|
5794
|
+
if (!alreadyGranted) {
|
|
5795
|
+
let granted = false;
|
|
5796
|
+
try {
|
|
5797
|
+
granted = await chrome.permissions.request({ origins: [originPattern] });
|
|
5798
|
+
} catch (cause) {
|
|
5799
|
+
throw siteAccessError(cause, url);
|
|
5800
|
+
}
|
|
5801
|
+
if (!granted) throw new Error(`Allow xopc to access ${new URL(url).hostname} to continue.`);
|
|
5802
|
+
newlyGranted = true;
|
|
5803
|
+
}
|
|
5804
|
+
try {
|
|
5805
|
+
return await operation({ ...tab, id: tab.id, windowId: tab.windowId, url });
|
|
5806
|
+
} catch (cause) {
|
|
5807
|
+
if (newlyGranted) await chrome.permissions.remove({ origins: [originPattern] }).catch(() => false);
|
|
5808
|
+
throw siteAccessError(cause, url);
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5213
5811
|
async function sha256(value) {
|
|
5214
5812
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
5215
5813
|
return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -5259,6 +5857,9 @@ async function captureTabPage(tabId, mode) {
|
|
|
5259
5857
|
const raw = result[0]?.result;
|
|
5260
5858
|
if (!raw) throw new Error("Could not read this page");
|
|
5261
5859
|
const url = sanitizedUrl(raw.url);
|
|
5860
|
+
if (new URL(url).origin !== new URL(sanitizedUrl(tab.url)).origin) {
|
|
5861
|
+
throw new Error("The page changed while xopc was reading it. Try again.");
|
|
5862
|
+
}
|
|
5262
5863
|
const selected = raw.selection ? truncateUtf8(raw.selection, MAX_BROWSER_SELECTION_BYTES) : void 0;
|
|
5263
5864
|
const page = raw.text ? truncateUtf8(raw.text, MAX_BROWSER_PAGE_TEXT_BYTES) : void 0;
|
|
5264
5865
|
const canonical = {
|
|
@@ -5278,16 +5879,8 @@ ${raw.timeOrigin}`),
|
|
|
5278
5879
|
truncated: selected?.truncated === true || page?.truncated === true
|
|
5279
5880
|
};
|
|
5280
5881
|
}
|
|
5281
|
-
async function captureTabWithPermission(tabId,
|
|
5282
|
-
|
|
5283
|
-
const alreadyGranted = await chrome.permissions.contains({ origins: [originPattern] });
|
|
5284
|
-
const granted = alreadyGranted || await chrome.permissions.request({ origins: [originPattern] });
|
|
5285
|
-
if (!granted) throw new Error("Site access is required to attach this tab");
|
|
5286
|
-
try {
|
|
5287
|
-
return await captureTabPage(tabId, mode);
|
|
5288
|
-
} finally {
|
|
5289
|
-
if (!alreadyGranted) await chrome.permissions.remove({ origins: [originPattern] });
|
|
5290
|
-
}
|
|
5882
|
+
async function captureTabWithPermission(tabId, mode) {
|
|
5883
|
+
return runWithTabSiteAccess(tabId, () => captureTabPage(tabId, mode));
|
|
5291
5884
|
}
|
|
5292
5885
|
async function activeTabId() {
|
|
5293
5886
|
return (await chrome.tabs.query({ active: true, currentWindow: true }))[0]?.id;
|
|
@@ -5295,229 +5888,132 @@ async function activeTabId() {
|
|
|
5295
5888
|
async function currentTabDescriptor() {
|
|
5296
5889
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
5297
5890
|
if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error("No active web page");
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5891
|
+
return runWithTabSiteAccess(tab.id, async (accessibleTab) => {
|
|
5892
|
+
const result = await chrome.scripting.executeScript({
|
|
5893
|
+
target: { tabId: accessibleTab.id },
|
|
5894
|
+
func: () => ({ url: location.href, timeOrigin: performance.timeOrigin })
|
|
5895
|
+
});
|
|
5896
|
+
const marker = result[0]?.result;
|
|
5897
|
+
if (!marker) throw new Error("Could not identify this page");
|
|
5898
|
+
const currentUrl = sanitizedUrl(marker.url);
|
|
5899
|
+
if (new URL(currentUrl).origin !== new URL(accessibleTab.url).origin) {
|
|
5900
|
+
throw new Error("The page changed while xopc was connecting to it. Try again.");
|
|
5901
|
+
}
|
|
5902
|
+
return {
|
|
5903
|
+
tabId: String(accessibleTab.id),
|
|
5904
|
+
windowId: String(accessibleTab.windowId),
|
|
5905
|
+
documentId: await sha256(`${currentUrl}
|
|
5310
5906
|
${marker.timeOrigin}`),
|
|
5311
|
-
|
|
5312
|
-
|
|
5907
|
+
urlOrigin: new URL(currentUrl).origin
|
|
5908
|
+
};
|
|
5909
|
+
});
|
|
5313
5910
|
}
|
|
5314
5911
|
|
|
5315
|
-
const
|
|
5316
|
-
const
|
|
5317
|
-
const
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
const
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
const
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
});
|
|
5355
|
-
strictObject({
|
|
5356
|
-
principalId: identifierSchema,
|
|
5357
|
-
displayName: string().min(1).max(120),
|
|
5358
|
-
kind: endpointKindSchema,
|
|
5359
|
-
platform: string().min(1).max(80),
|
|
5360
|
-
publicKey: string().min(32).max(2048)
|
|
5361
|
-
});
|
|
5362
|
-
strictObject({
|
|
5363
|
-
endpointId: identifierSchema
|
|
5364
|
-
});
|
|
5365
|
-
const backgroundCapabilitySchema = string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120);
|
|
5366
|
-
strictObject({
|
|
5367
|
-
jobId: uuid(),
|
|
5368
|
-
endpointId: identifierSchema,
|
|
5369
|
-
capability: backgroundCapabilitySchema,
|
|
5370
|
-
input: jsonObjectSchema,
|
|
5371
|
-
idempotencyKey: identifierSchema,
|
|
5372
|
-
consentGrantId: identifierSchema,
|
|
5373
|
-
createdAt: timestampSchema,
|
|
5374
|
-
expiresAt: timestampSchema
|
|
5375
|
-
});
|
|
5376
|
-
strictObject({
|
|
5377
|
-
jobId: uuid(),
|
|
5378
|
-
status: _enum(["queued", "running", "succeeded", "failed", "cancelled"]),
|
|
5379
|
-
updatedAt: timestampSchema,
|
|
5380
|
-
output: jsonObjectSchema.optional(),
|
|
5381
|
-
error: strictObject({
|
|
5382
|
-
code: identifierSchema,
|
|
5383
|
-
message: string().min(1).max(2e3)
|
|
5384
|
-
}).optional()
|
|
5385
|
-
});
|
|
5386
|
-
strictObject({
|
|
5387
|
-
eventId: uuid(),
|
|
5388
|
-
endpointId: identifierSchema,
|
|
5389
|
-
subscriptionId: identifierSchema,
|
|
5390
|
-
eventType: backgroundCapabilitySchema,
|
|
5391
|
-
data: jsonObjectSchema,
|
|
5392
|
-
occurredAt: timestampSchema
|
|
5393
|
-
});
|
|
5394
|
-
const endpointHelloPayloadSchema = strictObject({
|
|
5395
|
-
principalId: identifierSchema,
|
|
5396
|
-
endpointId: identifierSchema,
|
|
5397
|
-
connectionInstanceId: uuid(),
|
|
5398
|
-
displayName: string().min(1).max(120),
|
|
5399
|
-
kind: endpointKindSchema,
|
|
5400
|
-
platform: string().min(1).max(80),
|
|
5401
|
-
appVersion: string().min(1).max(80),
|
|
5402
|
-
availability: endpointAvailabilitySchema,
|
|
5403
|
-
nonce: identifierSchema,
|
|
5404
|
-
signedAt: timestampSchema,
|
|
5405
|
-
signature: string().min(16).max(1024),
|
|
5406
|
-
tools: array(endpointToolDescriptorSchema).max(100)
|
|
5407
|
-
});
|
|
5408
|
-
const toolInvokePayloadSchema = strictObject({
|
|
5409
|
-
invocationId: uuid(),
|
|
5410
|
-
toolCallId: identifierSchema,
|
|
5411
|
-
toolName: endpointToolDescriptorSchema.shape.name,
|
|
5412
|
-
arguments: jsonObjectSchema,
|
|
5413
|
-
descriptorRevision: string().min(1).max(128),
|
|
5414
|
-
confirmationRequired: boolean(),
|
|
5415
|
-
deadlineAt: timestampSchema,
|
|
5416
|
-
uploadGrant: strictObject({
|
|
5417
|
-
path: string().startsWith("/").max(500),
|
|
5418
|
-
token: identifierSchema,
|
|
5419
|
-
maxBytes: number().int().positive(),
|
|
5420
|
-
maxFiles: number().int().min(1).max(20),
|
|
5421
|
-
expiresAt: timestampSchema
|
|
5422
|
-
}).optional()
|
|
5423
|
-
});
|
|
5424
|
-
const toolCancelPayloadSchema = strictObject({
|
|
5425
|
-
invocationId: uuid(),
|
|
5426
|
-
reason: string().min(1).max(500)
|
|
5427
|
-
});
|
|
5428
|
-
const invocationPayloadSchema = strictObject({ invocationId: uuid() });
|
|
5429
|
-
const endpointToolContentSchema = discriminatedUnion("type", [
|
|
5430
|
-
strictObject({ type: literal("text"), text: string().max(256 * 1024) }),
|
|
5431
|
-
strictObject({ type: literal("json"), value: unknown() }),
|
|
5432
|
-
strictObject({
|
|
5433
|
-
type: literal("file"),
|
|
5434
|
-
fileId: identifierSchema,
|
|
5435
|
-
name: string().min(1).max(255),
|
|
5436
|
-
mimeType: string().min(1).max(255),
|
|
5437
|
-
size: number().int().nonnegative(),
|
|
5438
|
-
sha256: string().regex(/^[a-f0-9]{64}$/)
|
|
5439
|
-
})
|
|
5440
|
-
]);
|
|
5441
|
-
const endpointToolErrorCodeSchema = _enum([
|
|
5442
|
-
"ENDPOINT_OFFLINE",
|
|
5443
|
-
"ENDPOINT_NOT_FOREGROUND",
|
|
5444
|
-
"ENDPOINT_DISCONNECTED",
|
|
5445
|
-
"TOOL_NOT_FOUND",
|
|
5446
|
-
"TOOL_REVISION_MISMATCH",
|
|
5447
|
-
"INVALID_ARGUMENTS",
|
|
5448
|
-
"PERMISSION_DENIED",
|
|
5449
|
-
"USER_DENIED",
|
|
5450
|
-
"TOOL_BUSY",
|
|
5451
|
-
"TOOL_TIMEOUT",
|
|
5452
|
-
"TOOL_CANCELLED",
|
|
5453
|
-
"RESULT_TOO_LARGE",
|
|
5454
|
-
"PROTOCOL_ERROR"
|
|
5455
|
-
]);
|
|
5456
|
-
const envelope = (type, payload) => strictObject({
|
|
5457
|
-
protocolVersion: literal(ENDPOINT_PROTOCOL_VERSION),
|
|
5458
|
-
messageId: messageIdSchema,
|
|
5459
|
-
type: literal(type),
|
|
5460
|
-
sentAt: timestampSchema,
|
|
5461
|
-
payload
|
|
5462
|
-
});
|
|
5463
|
-
const clientEndpointMessageSchema = discriminatedUnion("type", [
|
|
5464
|
-
envelope("endpoint.availability_changed", strictObject({ availability: endpointAvailabilitySchema })),
|
|
5465
|
-
envelope("tool.received", invocationPayloadSchema),
|
|
5466
|
-
envelope("tool.progress", strictObject({
|
|
5467
|
-
invocationId: uuid(),
|
|
5468
|
-
message: string().max(500).optional(),
|
|
5469
|
-
percent: number().min(0).max(100).optional()
|
|
5470
|
-
})),
|
|
5471
|
-
envelope("tool.result", strictObject({
|
|
5472
|
-
invocationId: uuid(),
|
|
5473
|
-
content: array(endpointToolContentSchema).min(1).max(20),
|
|
5474
|
-
details: jsonObjectSchema.optional()
|
|
5475
|
-
})),
|
|
5476
|
-
envelope("tool.error", strictObject({
|
|
5477
|
-
invocationId: uuid(),
|
|
5478
|
-
code: endpointToolErrorCodeSchema,
|
|
5479
|
-
message: string().min(1).max(2e3)
|
|
5480
|
-
})),
|
|
5481
|
-
envelope("tool.cancelled", invocationPayloadSchema)
|
|
5482
|
-
]);
|
|
5483
|
-
const serverEndpointMessageSchema = discriminatedUnion("type", [
|
|
5484
|
-
envelope("tool.invoke", toolInvokePayloadSchema),
|
|
5485
|
-
envelope("tool.cancel", toolCancelPayloadSchema)
|
|
5486
|
-
]);
|
|
5487
|
-
function canonicalJson(value) {
|
|
5488
|
-
if (value === null) return "null";
|
|
5489
|
-
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
5490
|
-
if (typeof value === "number") {
|
|
5491
|
-
if (!Number.isFinite(value)) throw new Error("Canonical JSON only supports finite numbers");
|
|
5492
|
-
return JSON.stringify(value);
|
|
5912
|
+
const DATABASE_NAME = "xopc-browser-chat-state";
|
|
5913
|
+
const DATABASE_VERSION = 1;
|
|
5914
|
+
const OUTBOX_STORE = "outbox";
|
|
5915
|
+
function openDatabase() {
|
|
5916
|
+
return new Promise((resolve, reject) => {
|
|
5917
|
+
const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
|
|
5918
|
+
request.onupgradeneeded = () => {
|
|
5919
|
+
if (!request.result.objectStoreNames.contains(OUTBOX_STORE)) {
|
|
5920
|
+
request.result.createObjectStore(OUTBOX_STORE);
|
|
5921
|
+
}
|
|
5922
|
+
};
|
|
5923
|
+
request.onsuccess = () => resolve(request.result);
|
|
5924
|
+
request.onerror = () => reject(request.error ?? new Error("Could not open the browser chat outbox"));
|
|
5925
|
+
});
|
|
5926
|
+
}
|
|
5927
|
+
function transactionResult(database, mode, createRequest) {
|
|
5928
|
+
return new Promise((resolve, reject) => {
|
|
5929
|
+
const transaction = database.transaction(OUTBOX_STORE, mode);
|
|
5930
|
+
const request = createRequest(transaction.objectStore(OUTBOX_STORE));
|
|
5931
|
+
let result;
|
|
5932
|
+
request.onsuccess = () => {
|
|
5933
|
+
result = request.result;
|
|
5934
|
+
};
|
|
5935
|
+
request.onerror = () => reject(request.error ?? new Error("Browser chat outbox request failed"));
|
|
5936
|
+
transaction.oncomplete = () => resolve(result);
|
|
5937
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("Browser chat outbox transaction failed"));
|
|
5938
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("Browser chat outbox transaction was aborted"));
|
|
5939
|
+
});
|
|
5940
|
+
}
|
|
5941
|
+
async function readBrowserOutbox(sessionKey) {
|
|
5942
|
+
const database = await openDatabase();
|
|
5943
|
+
try {
|
|
5944
|
+
return await transactionResult(
|
|
5945
|
+
database,
|
|
5946
|
+
"readonly",
|
|
5947
|
+
(store) => store.get(sessionKey)
|
|
5948
|
+
);
|
|
5949
|
+
} finally {
|
|
5950
|
+
database.close();
|
|
5493
5951
|
}
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
const
|
|
5497
|
-
|
|
5498
|
-
|
|
5952
|
+
}
|
|
5953
|
+
async function writeBrowserOutbox(sessionKey, value) {
|
|
5954
|
+
const database = await openDatabase();
|
|
5955
|
+
try {
|
|
5956
|
+
await transactionResult(database, "readwrite", (store) => store.put(value, sessionKey));
|
|
5957
|
+
} finally {
|
|
5958
|
+
database.close();
|
|
5499
5959
|
}
|
|
5500
|
-
const record = value;
|
|
5501
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
5502
5960
|
}
|
|
5503
|
-
function
|
|
5504
|
-
const
|
|
5505
|
-
|
|
5961
|
+
async function deleteBrowserOutbox(sessionKey) {
|
|
5962
|
+
const database = await openDatabase();
|
|
5963
|
+
try {
|
|
5964
|
+
await transactionResult(database, "readwrite", (store) => store.delete(sessionKey));
|
|
5965
|
+
} finally {
|
|
5966
|
+
database.close();
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
async function clearBrowserOutboxes() {
|
|
5970
|
+
const database = await openDatabase();
|
|
5971
|
+
try {
|
|
5972
|
+
await transactionResult(database, "readwrite", (store) => store.clear());
|
|
5973
|
+
} finally {
|
|
5974
|
+
database.close();
|
|
5975
|
+
}
|
|
5506
5976
|
}
|
|
5507
5977
|
|
|
5508
5978
|
const PROFILE_KEY = "xopc.browser.profile";
|
|
5979
|
+
const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
|
|
5509
5980
|
const KEY_DATABASE = "xopc-browser-identity";
|
|
5510
5981
|
const KEY_STORE = "identity";
|
|
5511
5982
|
const KEY_NAME = "device-key";
|
|
5512
5983
|
const NATIVE_HOST_NAME = "ai.xopc.browser";
|
|
5513
5984
|
let refreshTask;
|
|
5514
|
-
async function
|
|
5985
|
+
async function browserPublicKeyThumbprint(jwk) {
|
|
5986
|
+
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
5987
|
+
return encodeBase64Url(new Uint8Array(await crypto.subtle.digest(
|
|
5988
|
+
"SHA-256",
|
|
5989
|
+
new TextEncoder().encode(canonical)
|
|
5990
|
+
)));
|
|
5991
|
+
}
|
|
5992
|
+
async function isAutoConnectEnabled() {
|
|
5993
|
+
const stored = await chrome.storage.local.get(AUTO_CONNECT_KEY);
|
|
5994
|
+
return stored[AUTO_CONNECT_KEY] !== false;
|
|
5995
|
+
}
|
|
5996
|
+
async function setAutoConnectEnabled(enabled) {
|
|
5997
|
+
await chrome.storage.local.set({ [AUTO_CONNECT_KEY]: enabled });
|
|
5998
|
+
}
|
|
5999
|
+
async function discoverLocalGateway(options) {
|
|
5515
6000
|
try {
|
|
6001
|
+
if (!options?.force && !await isAutoConnectEnabled()) return void 0;
|
|
6002
|
+
const pair = await getOrCreateKeyPair();
|
|
6003
|
+
const key = await publicKeyJwk(pair);
|
|
6004
|
+
const nonce = randomNonce();
|
|
5516
6005
|
const response = await chrome.runtime.sendNativeMessage(
|
|
5517
6006
|
NATIVE_HOST_NAME,
|
|
5518
|
-
{
|
|
6007
|
+
{
|
|
6008
|
+
type: "bootstrap",
|
|
6009
|
+
extensionId: chrome.runtime.id,
|
|
6010
|
+
publicKeyJwk: key,
|
|
6011
|
+
publicKeyThumbprint: await browserPublicKeyThumbprint(key),
|
|
6012
|
+
nonce
|
|
6013
|
+
}
|
|
5519
6014
|
);
|
|
5520
6015
|
if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.pairingLink !== "string" || typeof response.expiresAt !== "number") {
|
|
6016
|
+
if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
|
|
5521
6017
|
return void 0;
|
|
5522
6018
|
}
|
|
5523
6019
|
return {
|
|
@@ -5525,7 +6021,8 @@ async function discoverLocalGateway() {
|
|
|
5525
6021
|
pairingLink: response.pairingLink,
|
|
5526
6022
|
expiresAt: response.expiresAt
|
|
5527
6023
|
};
|
|
5528
|
-
} catch {
|
|
6024
|
+
} catch (cause) {
|
|
6025
|
+
if (options?.force) throw cause;
|
|
5529
6026
|
return void 0;
|
|
5530
6027
|
}
|
|
5531
6028
|
}
|
|
@@ -5598,6 +6095,14 @@ async function getOrCreateKeyPair() {
|
|
|
5598
6095
|
}).finally(() => database.close());
|
|
5599
6096
|
return pair;
|
|
5600
6097
|
}
|
|
6098
|
+
async function forgetIdentity() {
|
|
6099
|
+
await new Promise((resolve, reject) => {
|
|
6100
|
+
const request = indexedDB.deleteDatabase(KEY_DATABASE);
|
|
6101
|
+
request.onsuccess = () => resolve();
|
|
6102
|
+
request.onerror = () => reject(request.error);
|
|
6103
|
+
request.onblocked = () => reject(new Error("Browser identity is still in use"));
|
|
6104
|
+
});
|
|
6105
|
+
}
|
|
5601
6106
|
async function publicKeyJwk(pair) {
|
|
5602
6107
|
const exported = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
5603
6108
|
if (!exported.x || !exported.y) throw new Error("Browser identity key export failed");
|
|
@@ -5718,7 +6223,7 @@ async function pairGateway(link, onApproval) {
|
|
|
5718
6223
|
const requestId = crypto.randomUUID();
|
|
5719
6224
|
const initialRefreshToken = createRefreshToken();
|
|
5720
6225
|
let result = await signedPairingRequest(pair, payload, origin, requestId, "request");
|
|
5721
|
-
onApproval(result.request.confirmationCode);
|
|
6226
|
+
if (result.request.status === "pending") onApproval(result.request.confirmationCode);
|
|
5722
6227
|
while (result.request.status === "pending") {
|
|
5723
6228
|
await wait(1500);
|
|
5724
6229
|
result = await signedPairingRequest(pair, payload, origin, requestId, "status");
|
|
@@ -5801,7 +6306,7 @@ async function registerBrowserEndpoint() {
|
|
|
5801
6306
|
});
|
|
5802
6307
|
if (!response.ok) throw new Error(`Browser endpoint registration failed (${response.status})`);
|
|
5803
6308
|
}
|
|
5804
|
-
async function createBrowserEndpointHello() {
|
|
6309
|
+
async function createBrowserEndpointHello(tools = []) {
|
|
5805
6310
|
const profile = await getAccessProfile();
|
|
5806
6311
|
const pair = await getOrCreateKeyPair();
|
|
5807
6312
|
const unsigned = {
|
|
@@ -5816,7 +6321,7 @@ async function createBrowserEndpointHello() {
|
|
|
5816
6321
|
nonce: crypto.randomUUID(),
|
|
5817
6322
|
signedAt: Date.now(),
|
|
5818
6323
|
signature: "pending",
|
|
5819
|
-
tools
|
|
6324
|
+
tools
|
|
5820
6325
|
};
|
|
5821
6326
|
return {
|
|
5822
6327
|
...unsigned,
|
|
@@ -5835,10 +6340,16 @@ async function revokeAndForgetProfile() {
|
|
|
5835
6340
|
});
|
|
5836
6341
|
} finally {
|
|
5837
6342
|
await forgetProfile();
|
|
6343
|
+
try {
|
|
6344
|
+
await clearBrowserOutboxes();
|
|
6345
|
+
} finally {
|
|
6346
|
+
try {
|
|
6347
|
+
await forgetIdentity();
|
|
6348
|
+
} finally {
|
|
6349
|
+
await setAutoConnectEnabled(false);
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
5838
6352
|
}
|
|
5839
6353
|
}
|
|
5840
|
-
async function signBrowserBridgeChallenge(payload) {
|
|
5841
|
-
return sign(await getOrCreateKeyPair(), payload);
|
|
5842
|
-
}
|
|
5843
6354
|
|
|
5844
|
-
export { PENDING_CONTEXT_KEY as P,
|
|
6355
|
+
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 };
|