@dxos/edge-client 0.10.0 → 0.11.0
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/lib/chunk-edge-ws-muxer.mjs +322 -0
- package/dist/lib/chunk-edge-ws-muxer.mjs.map +1 -0
- package/dist/lib/cors-proxy.mjs +29 -0
- package/dist/lib/cors-proxy.mjs.map +1 -0
- package/dist/lib/edge-ws-muxer.mjs +2 -0
- package/dist/lib/index.mjs +1769 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/service.mjs +138 -0
- package/dist/lib/service.mjs.map +1 -0
- package/dist/lib/testing.mjs +160 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/base-http-client.d.ts +18 -0
- package/dist/types/src/base-http-client.d.ts.map +1 -1
- package/dist/types/src/browser-rendering.d.ts +0 -7
- package/dist/types/src/browser-rendering.d.ts.map +1 -1
- package/dist/types/src/edge-client.d.ts +22 -2
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +86 -2
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.d.ts +19 -0
- package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.test.d.ts +2 -0
- package/dist/types/src/edge-ws-connection.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/protocol.d.ts +2 -1
- package/dist/types/src/protocol.d.ts.map +1 -1
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -20
- package/src/base-http-client.ts +71 -3
- package/src/browser-rendering.ts +0 -9
- package/src/edge-client.ts +21 -3
- package/src/edge-http-client.test.ts +121 -0
- package/src/edge-http-client.ts +156 -2
- package/src/edge-ws-connection.test.ts +219 -0
- package/src/edge-ws-connection.ts +97 -28
- package/src/index.ts +1 -0
- package/src/protocol.ts +11 -2
- package/src/testing/test-utils.ts +5 -1
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs +0 -310
- package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs +0 -30
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs.map +0 -7
- package/dist/lib/neutral/cors-proxy.mjs +0 -8
- package/dist/lib/neutral/cors-proxy.mjs.map +0 -7
- package/dist/lib/neutral/edge-ws-muxer.mjs +0 -12
- package/dist/lib/neutral/edge-ws-muxer.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -1524
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/service/index.mjs +0 -134
- package/dist/lib/neutral/service/index.mjs.map +0 -7
- package/dist/lib/neutral/testing/index.mjs +0 -161
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/auth.ts","../../src/edge-identity.ts","../../src/edge-ws-connection.ts","../../src/errors.ts","../../src/utils.ts","../../src/edge-client.ts","../../src/http-client.ts","../../src/base-http-client.ts","../../src/edge-ai-http-client.ts","../../src/edge-http-client.ts","../../src/hub-http-client.ts","../../src/browser-rendering.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { createCredential, createDidFromIdentityKey, signPresentation } from '@dxos/credentials';\nimport { type Signer } from '@dxos/crypto';\nimport { invariant } from '@dxos/invariant';\nimport { Keyring } from '@dxos/keyring';\nimport { IdentityDid, PublicKey } from '@dxos/keys';\nimport { type Chain, type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';\n\nimport type { EdgeIdentity } from './edge-identity';\n\n/**\n * Edge identity backed by a device key without a credential chain.\n */\nexport const createDeviceEdgeIdentity = async (signer: Signer, key: PublicKey): Promise<EdgeIdentity> => {\n return {\n identityDid: await createDidFromIdentityKey(key),\n peerKey: key.toHex(),\n presentCredentials: async ({ challenge }) => {\n return signPresentation({\n presentation: {\n credentials: [\n // Verifier requires at least one credential in the presentation to establish the subject.\n await createCredential({\n assertion: {\n '@type': 'dxos.halo.credentials.Auth',\n },\n issuer: key,\n subject: key,\n signer,\n }),\n ],\n },\n signer,\n signerKey: key,\n nonce: challenge,\n });\n },\n };\n};\n\n/**\n * Edge identity backed by a chain of credentials.\n */\nexport const createChainEdgeIdentity = async (\n signer: Signer,\n identityKey: PublicKey,\n peerKey: PublicKey,\n chain: Chain | undefined,\n credentials: Credential[],\n): Promise<EdgeIdentity> => {\n const credentialsToSign =\n credentials.length > 0\n ? credentials\n : [\n await createCredential({\n assertion: {\n '@type': 'dxos.halo.credentials.Auth',\n },\n issuer: identityKey,\n subject: identityKey,\n signer,\n chain,\n signingKey: peerKey,\n }),\n ];\n\n return {\n identityDid: await createDidFromIdentityKey(identityKey),\n peerKey: peerKey.toHex(),\n presentCredentials: async ({ challenge }) => {\n // TODO: make chain required after device invitation flow update release\n invariant(chain);\n return signPresentation({\n presentation: {\n credentials: credentialsToSign,\n },\n signer,\n nonce: challenge,\n signerKey: peerKey,\n chain,\n });\n },\n };\n};\n\n/**\n * Edge identity backed by a random ephemeral key without HALO.\n */\nexport const createEphemeralEdgeIdentity = async (): Promise<EdgeIdentity> => {\n const keyring = new Keyring();\n const key = await keyring.createKey();\n return createDeviceEdgeIdentity(keyring, key);\n};\n\n/**\n * Creates a HALO chain of credentials to act as an edge identity.\n */\nexport const createTestHaloEdgeIdentity = async (\n signer: Signer,\n identityKey: PublicKey,\n deviceKey: PublicKey,\n): Promise<EdgeIdentity> => {\n const deviceAdmission = await createCredential({\n assertion: {\n '@type': 'dxos.halo.credentials.AuthorizedDevice',\n deviceKey,\n identityKey,\n },\n issuer: identityKey,\n subject: deviceKey,\n signer,\n });\n return createChainEdgeIdentity(signer, identityKey, deviceKey, { credential: deviceAdmission }, [\n await createCredential({\n assertion: {\n '@type': 'dxos.halo.credentials.Auth',\n },\n issuer: identityKey,\n subject: identityKey,\n signer,\n }),\n ]);\n};\n\nexport const createStubEdgeIdentity = (): EdgeIdentity => {\n const deviceKey = PublicKey.random();\n return {\n // Random placeholder DID — the stub never authenticates or connects; a real identity replaces it.\n identityDid: IdentityDid.random(),\n peerKey: deviceKey.toHex(),\n presentCredentials: async () => {\n throw new Error('Stub identity does not support authentication.');\n },\n };\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\nimport { schema } from '@dxos/protocols/proto';\nimport { type Presentation } from '@dxos/protocols/proto/dxos/halo/credentials';\n\nexport interface EdgeIdentity {\n peerKey: string;\n /**\n * Identity DID (`did:halo:…`) — the public identity segment of the edge WebSocket path.\n * The router keys connections by the DID.\n */\n identityDid: string;\n /**\n * Returns credential presentation issued by the identity key.\n * Presentation must have the provided challenge.\n * Presentation may include ServiceAccess credentials.\n */\n presentCredentials({ challenge }: { challenge: Uint8Array }): Promise<Presentation>;\n}\n\nexport const handleAuthChallenge = async (failedResponse: Response, identity: EdgeIdentity): Promise<Uint8Array> => {\n invariant(failedResponse.status === 401);\n\n const headerValue = failedResponse.headers.get('Www-Authenticate');\n invariant(headerValue?.startsWith('VerifiablePresentation challenge='));\n\n const challenge = headerValue?.slice('VerifiablePresentation challenge='.length);\n invariant(challenge);\n\n const presentation = await identity.presentCredentials({ challenge: Buffer.from(challenge, 'base64') });\n return schema.getCodecForType('dxos.halo.credentials.Presentation').encode(presentation);\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport WebSocket from 'isomorphic-ws';\n\nimport { Mutex, scheduleTask, scheduleTaskInterval } from '@dxos/async';\nimport { Context, Resource } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { log, logInfo } from '@dxos/log';\nimport { EdgeWebsocketProtocol } from '@dxos/protocols';\nimport { buf } from '@dxos/protocols/buf';\nimport { type Message, MessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\n\nimport { protocol } from './defs';\nimport { type EdgeIdentity } from './edge-identity';\nimport { CLOUDFLARE_MESSAGE_MAX_BYTES, WebSocketMuxer } from './edge-ws-muxer';\nimport { toUint8Array } from './protocol';\n\nconst SIGNAL_KEEPALIVE_INTERVAL = 4_000;\nconst SIGNAL_KEEPALIVE_TIMEOUT = 12_000;\n/**\n * Watchdog self-check: if the inactivity timer fires later than its schedule by more than this,\n * the local event loop was starved (heavy WASM sync compute pins it for seconds at a time) —\n * our pings were not being sent and inbound pongs were not being processed, so the silence says\n * nothing about the connection. Probe and re-arm instead of restarting.\n */\nconst KEEPALIVE_WATCHDOG_LATE_TOLERANCE = 3_000;\n\nexport type EdgeWsConnectionCallbacks = {\n onConnected: () => void;\n onMessage: (message: Message) => void;\n onRestartRequired: () => void;\n};\n\nexport class EdgeWsConnection extends Resource {\n private _inactivityTimeoutCtx: Context | undefined;\n private _ws: WebSocket | undefined;\n private _wsMuxer: WebSocketMuxer | undefined;\n private _lastReceivedMessageTimestamp = Date.now();\n\n private _openTimestamp: number | undefined;\n\n // Latency tracking.\n private _pingTimestamp: number | undefined;\n private _lastPingSentTimestamp = 0;\n private _rtt = 0;\n\n // Rate tracking with sliding window.\n private _uploadRate = 0;\n private _downloadRate = 0;\n private readonly _rateWindow = 10000; // 10 second sliding window.\n private readonly _rateUpdateInterval = 1000; // Update rates every second.\n private _bytesSamples: Array<{ timestamp: number; sent: number; received: number }> = [];\n\n private _messagesSent = 0;\n private _messagesReceived = 0;\n\n /**\n * WebSocket frames arrive in order, but converting frame data to bytes is async\n * (the `Blob` fallback path awaits `blob.arrayBuffer()`), and concurrent conversions\n * are not guaranteed to complete in arrival order. Segmented-message reassembly in\n * `WebSocketMuxer` requires chunks to reach `receiveData` in arrival order, so message\n * processing is serialized through this lock.\n */\n private readonly _receiveMutex = new Mutex();\n\n constructor(\n private readonly _identity: EdgeIdentity,\n private readonly _connectionInfo: { url: URL; protocolHeader?: string; headers?: Record<string, string> },\n private readonly _callbacks: EdgeWsConnectionCallbacks,\n ) {\n super();\n }\n\n @logInfo\n public get info() {\n return {\n open: this.isOpen,\n identity: this._identity.identityDid,\n device: this._identity.peerKey,\n };\n }\n\n public get rtt(): number {\n return this._rtt;\n }\n\n public get uptime(): number {\n return this._openTimestamp ? (Date.now() - this._openTimestamp) / 1000 : 0;\n }\n\n public get uploadRate(): number {\n return this._uploadRate;\n }\n\n public get downloadRate(): number {\n return this._downloadRate;\n }\n\n public get messagesSent(): number {\n return this._messagesSent;\n }\n\n public get messagesReceived(): number {\n return this._messagesReceived;\n }\n\n public send(message: Message): void {\n invariant(this._ws);\n invariant(this._wsMuxer);\n log('sending...', { peerKey: this._identity.peerKey, payload: protocol.getPayloadType(message) });\n this._messagesSent++;\n if (this._ws?.protocol.includes(EdgeWebsocketProtocol.V0)) {\n const binary = buf.toBinary(MessageSchema, message);\n if (binary.length > CLOUDFLARE_MESSAGE_MAX_BYTES) {\n log.error('Message dropped because it was too large (>1MB).', {\n byteLength: binary.byteLength,\n serviceId: message.serviceId,\n payload: protocol.getPayloadType(message),\n });\n return;\n }\n this._recordBytes(binary.byteLength, 0);\n this._ws.send(binary);\n } else {\n // For muxer, we need to track the size of the message being sent.\n const binary = buf.toBinary(MessageSchema, message);\n this._recordBytes(binary.byteLength, 0);\n this._wsMuxer.send(message).catch((e) => log.catch(e));\n }\n }\n\n protected override async _open(): Promise<void> {\n const baseProtocols = [...Object.values(EdgeWebsocketProtocol)];\n this._ws = new WebSocket(\n this._connectionInfo.url.toString(),\n this._connectionInfo.protocolHeader\n ? [...baseProtocols, this._connectionInfo.protocolHeader]\n : [...baseProtocols],\n this._connectionInfo.headers ? { headers: this._connectionInfo.headers } : undefined,\n );\n // Deliver frame data as `ArrayBuffer` rather than `Blob` so bytes are available\n // synchronously; avoids the async `blob.arrayBuffer()` reads that can otherwise\n // complete out of arrival order (see `_receiveChain`).\n this._ws.binaryType = 'arraybuffer';\n const muxer = new WebSocketMuxer(this._ws);\n this._wsMuxer = muxer;\n\n this._ws.onopen = () => {\n if (this.isOpen) {\n log('connected');\n this._openTimestamp = Date.now();\n this._callbacks.onConnected();\n this._scheduleHeartbeats();\n this._scheduleRateCalculation();\n } else {\n log.verbose('connected after becoming inactive', { currentIdentity: this._identity });\n }\n };\n this._ws.onclose = (event: WebSocket.CloseEvent) => {\n if (this.isOpen) {\n log.warn('server disconnected', { code: event.code, reason: event.reason });\n this._callbacks.onRestartRequired();\n muxer.destroy();\n }\n };\n this._ws.onerror = (event: WebSocket.ErrorEvent) => {\n if (this.isOpen) {\n log.warn('edge connection socket error', { error: event.error, info: event.message });\n this._callbacks.onRestartRequired();\n } else {\n log.verbose('error ignored on closed connection', { error: event.error });\n }\n };\n /**\n * https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/data\n */\n this._ws.onmessage = (event: WebSocket.MessageEvent) => {\n if (!this.isOpen) {\n log.verbose('message ignored on closed connection', { event: event.type });\n return;\n }\n this._lastReceivedMessageTimestamp = Date.now();\n if (event.data === '__pong__') {\n // Calculate latency.\n if (this._pingTimestamp) {\n this._rtt = Date.now() - this._pingTimestamp;\n this._pingTimestamp = undefined;\n }\n this._rescheduleHeartbeatTimeout();\n return;\n }\n\n // `_receiveMessage` serializes on `_receiveMutex`; `acquire` enqueues synchronously,\n // so locks are taken in arrival order regardless of async conversion timing.\n void this._receiveMessage(event.data, muxer).catch((err) => log.catch(err));\n };\n }\n\n private async _receiveMessage(data: WebSocket.Data, muxer: WebSocketMuxer): Promise<void> {\n // Serialize processing so bytes reach `muxer.receiveData` in arrival order. The guard\n // releases on scope exit even if processing throws, so a single bad message is logged\n // and dropped instead of stalling every message queued after it.\n using _guard = await this._receiveMutex.acquire();\n const bytes = await toUint8Array(data);\n this._recordBytes(0, bytes.byteLength);\n if (!this.isOpen) {\n return;\n }\n\n this._messagesReceived++;\n\n const message = this._ws?.protocol?.includes(EdgeWebsocketProtocol.V0)\n ? buf.fromBinary(MessageSchema, bytes)\n : muxer.receiveData(bytes);\n\n if (message) {\n log('received', { from: message.source, payload: protocol.getPayloadType(message) });\n this._callbacks.onMessage(message);\n }\n }\n\n protected override async _close(): Promise<void> {\n void this._inactivityTimeoutCtx?.dispose().catch(() => {});\n\n try {\n this._ws?.close();\n this._ws = undefined;\n this._wsMuxer?.destroy();\n this._wsMuxer = undefined;\n } catch (err) {\n if (err instanceof Error && err.message.includes('WebSocket is closed before the connection is established.')) {\n return;\n }\n log.warn('error closing websocket', { err });\n }\n }\n\n private _scheduleHeartbeats(): void {\n invariant(this._ws);\n scheduleTaskInterval(\n this._ctx,\n async () => {\n // TODO(mykola): use RFC6455 ping/pong once implemented in the browser?\n // Cloudflare's worker responds to this `without interrupting hibernation`. https://developers.cloudflare.com/durable-objects/api/websockets/#setwebsocketautoresponse\n this._sendPing();\n },\n SIGNAL_KEEPALIVE_INTERVAL,\n );\n this._sendPing();\n this._rescheduleHeartbeatTimeout();\n }\n\n private _sendPing(): void {\n if (!this._ws) {\n return;\n }\n this._pingTimestamp = Date.now();\n this._lastPingSentTimestamp = Date.now();\n this._ws.send('__ping__');\n }\n\n /**\n * Inactivity watchdog. Restarts the connection only after a fair trial: pings were actually\n * flowing (a recent send), the timer fired on schedule (the local event loop was alive to\n * process an answer), and still nothing was received for the full window. Wall-clock silence\n * alone is not evidence — sync compute can pin the event loop for seconds, during which the\n * ping sender does not run and arrived pongs are not processed; restarting a healthy\n * connection on that basis costs a re-handshake and fails in-flight sync rounds.\n */\n private _rescheduleHeartbeatTimeout(): void {\n if (!this.isOpen) {\n return;\n }\n void this._inactivityTimeoutCtx?.dispose();\n this._inactivityTimeoutCtx = new Context();\n const armedAt = Date.now();\n scheduleTask(\n this._inactivityTimeoutCtx,\n () => {\n if (!this.isOpen) {\n return;\n }\n const now = Date.now();\n const silenceMs = now - this._lastReceivedMessageTimestamp;\n if (silenceMs <= SIGNAL_KEEPALIVE_TIMEOUT) {\n this._rescheduleHeartbeatTimeout();\n return;\n }\n const pingAgeMs = this._lastPingSentTimestamp ? now - this._lastPingSentTimestamp : Number.POSITIVE_INFINITY;\n const firedLateByMs = now - armedAt - SIGNAL_KEEPALIVE_TIMEOUT;\n const pingsWereFlowing = pingAgeMs <= SIGNAL_KEEPALIVE_INTERVAL * 2;\n const loopWasLive = firedLateByMs < KEEPALIVE_WATCHDOG_LATE_TOLERANCE;\n if (pingsWereFlowing && loopWasLive) {\n log.warn('restart due to inactivity timeout', {\n silenceMs,\n pingAgeMs,\n lastReceivedMessageTimestamp: this._lastReceivedMessageTimestamp,\n });\n this._callbacks.onRestartRequired();\n return;\n }\n // The silence is self-inflicted (starved event loop stopped our pings and delayed this\n // timer). Probe immediately and give the connection a fresh full window to answer.\n log.verbose('keepalive starved by event loop; probing instead of restarting', {\n silenceMs,\n pingAgeMs,\n firedLateByMs,\n });\n this._sendPing();\n this._rescheduleHeartbeatTimeout();\n },\n SIGNAL_KEEPALIVE_TIMEOUT,\n );\n }\n\n private _recordBytes(sent: number, received: number): void {\n const now = Date.now();\n\n // Find if we have a sample for the current second.\n const currentSecond = Math.floor(now / 1000) * 1000;\n const existingSample = this._bytesSamples.find((s) => Math.floor(s.timestamp / 1000) * 1000 === currentSecond);\n\n if (existingSample) {\n existingSample.sent += sent;\n existingSample.received += received;\n } else {\n this._bytesSamples.push({ timestamp: now, sent, received });\n }\n }\n\n private _scheduleRateCalculation(): void {\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this._calculateRates();\n },\n this._rateUpdateInterval,\n );\n // Calculate initial rates.\n this._calculateRates();\n }\n\n private _calculateRates(): void {\n const now = Date.now();\n const cutoff = now - this._rateWindow;\n\n // Remove old samples.\n this._bytesSamples = this._bytesSamples.filter((s) => s.timestamp > cutoff);\n\n if (this._bytesSamples.length === 0) {\n this._uploadRate = 0;\n this._downloadRate = 0;\n return;\n }\n\n // Calculate total bytes and time span.\n let totalSent = 0;\n let totalReceived = 0;\n const oldestTimestamp = Math.min(...this._bytesSamples.map((s) => s.timestamp));\n const timeSpan = (now - oldestTimestamp) / 1000; // Convert to seconds.\n\n for (const sample of this._bytesSamples) {\n totalSent += sample.sent;\n totalReceived += sample.received;\n }\n\n // Calculate rates (bytes per second).\n this._uploadRate = timeSpan > 0 ? Math.round(totalSent / timeSpan) : 0;\n this._downloadRate = timeSpan > 0 ? Math.round(totalReceived / timeSpan) : 0;\n }\n}\n","//\n// Copyright 2024 DXOS.org\n//\n\nexport class EdgeConnectionClosedError extends Error {\n constructor() {\n super('Edge connection closed.');\n }\n}\n\nexport class EdgeIdentityChangedError extends Error {\n constructor() {\n super('Edge identity changed.');\n }\n}\n","//\n// Copyright 2024 DXOS.org\n//\n\nexport const getEdgeUrlWithProtocol = (baseUrl: string, protocol: 'http' | 'ws') => {\n const isSecure = baseUrl.startsWith('https') || baseUrl.startsWith('wss');\n const url = new URL(baseUrl);\n url.protocol = protocol + (isSecure ? 's' : '');\n return url.toString();\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as EffectContext from 'effect/Context';\n\nimport {\n Event,\n PersistentLifecycle,\n Trigger,\n TriggerState,\n scheduleMicroTask,\n scheduleTaskInterval,\n} from '@dxos/async';\nimport { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';\nimport { type Lifecycle, Resource } from '@dxos/context';\nimport { log, logInfo } from '@dxos/log';\nimport { type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\nimport { EdgeStatus } from '@dxos/protocols/proto/dxos/client/services';\n\nimport { protocol } from './defs';\nimport { type EdgeIdentity, handleAuthChallenge } from './edge-identity';\nimport { EdgeWsConnection } from './edge-ws-connection';\nimport { EdgeConnectionClosedError, EdgeIdentityChangedError } from './errors';\nimport { type Protocol } from './protocol';\nimport { getEdgeUrlWithProtocol } from './utils';\n\nconst DEFAULT_TIMEOUT = 10_000;\n\n// Refresh status every second: rtt, rate counters.\nconst STATUS_REFRESH_INTERVAL = 1000;\n\nexport type MessageListener = (message: Message) => void;\nexport type ReconnectListener = () => void;\n\nexport type MessengerConfig = {\n socketEndpoint: string;\n timeout?: number;\n protocol?: Protocol;\n disableAuth?: boolean;\n /** Sent as `X-DXOS-Client-Tag` on the WebSocket upgrade (Node/`ws` only; ignored in browsers). */\n clientTag?: string;\n};\n\nexport interface EdgeConnection extends Required<Lifecycle> {\n statusChanged: Event<EdgeStatus>;\n get info(): any;\n /** Identity DID (`did:halo:…`) of the connected identity. */\n get identityDid(): string;\n get peerKey(): string;\n get isOpen(): boolean;\n get status(): EdgeStatus;\n setIdentity(identity: EdgeIdentity): void;\n send(ctx: Context, message: Message): Promise<void>;\n onMessage(listener: MessageListener): () => void;\n /**\n * Subscribe to connection (re-)establishment.\n *\n * By default the listener also fires once immediately when the connection is already ready at\n * subscription time — a current-state notification for late subscribers. Pass\n * `emitCurrentState: false` for reconnect-reaction logic (e.g. restarting a session): reacting\n * to the subscribe-time firing there causes restart loops, since each restart re-subscribes.\n */\n onReconnected(listener: ReconnectListener, opts?: { emitCurrentState?: boolean }): () => void;\n}\n\n/**\n * Effect service tag for {@link EdgeConnection}.\n */\nexport class EdgeConnectionService extends EffectContext.Tag('@dxos/edge-client/EdgeConnection')<\n EdgeConnectionService,\n EdgeConnection\n>() {}\n\n/**\n * Messenger client for EDGE:\n * - While open, uses PersistentLifecycle to keep an open EdgeWsConnection, reconnecting on failures.\n * - Manages identity and re-create EdgeWsConnection when identity changes.\n * - Dispatches connection state and message notifications.\n */\nexport class EdgeClient extends Resource implements EdgeConnection {\n public readonly statusChanged = new Event<EdgeStatus>();\n\n private readonly _persistentLifecycle = new PersistentLifecycle<EdgeWsConnection>({\n start: async () => this._connect(),\n stop: async (state: EdgeWsConnection) => this._disconnect(state),\n });\n\n private readonly _messageListeners = new Set<MessageListener>();\n private readonly _reconnectListeners = new Set<ReconnectListener>();\n private readonly _baseWsUrl: string;\n private readonly _baseHttpUrl: string;\n private _currentConnection?: EdgeWsConnection = undefined;\n private _ready = new Trigger();\n\n constructor(\n private _identity: EdgeIdentity,\n private readonly _config: MessengerConfig,\n ) {\n super();\n this._baseWsUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, 'ws');\n this._baseHttpUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, 'http');\n }\n\n @logInfo\n public get info() {\n return {\n open: this.isOpen,\n status: this.status,\n identity: this._identity.identityDid,\n device: this._identity.peerKey,\n };\n }\n\n get status(): EdgeStatus {\n return {\n state:\n Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED\n ? EdgeStatus.ConnectionState.CONNECTED\n : EdgeStatus.ConnectionState.NOT_CONNECTED,\n uptime: this._currentConnection?.uptime ?? 0,\n rtt: this._currentConnection?.rtt ?? 0,\n rateBytesUp: this._currentConnection?.uploadRate ?? 0,\n rateBytesDown: this._currentConnection?.downloadRate ?? 0,\n messagesSent: this._currentConnection?.messagesSent ?? 0,\n messagesReceived: this._currentConnection?.messagesReceived ?? 0,\n };\n }\n\n get identityDid() {\n return this._identity.identityDid;\n }\n\n get peerKey() {\n return this._identity.peerKey;\n }\n\n setIdentity(identity: EdgeIdentity) {\n if (identity.identityDid !== this._identity.identityDid || identity.peerKey !== this._identity.peerKey) {\n log('Edge identity changed', { identity, oldIdentity: this._identity });\n this._identity = identity;\n this._closeCurrentConnection(new EdgeIdentityChangedError());\n void this._persistentLifecycle.scheduleRestart();\n }\n }\n\n /**\n * Send message.\n * NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.\n */\n public async send(ctx: Context, message: Message) {\n if (this._ready.state !== TriggerState.RESOLVED) {\n log('waiting for websocket');\n await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });\n }\n\n if (!this._currentConnection) {\n throw new EdgeConnectionClosedError();\n }\n\n // DX-1059: sources are DID-only; validate against identityDid.\n if (\n message.source &&\n (message.source.peerKey !== this._identity.peerKey || message.source.identityDid !== this.identityDid)\n ) {\n throw new EdgeIdentityChangedError();\n }\n\n const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE) as TraceContextData | undefined;\n if (traceCtx) {\n message.traceContext = {\n $typeName: 'dxos.edge.messenger.TraceContext',\n traceparent: traceCtx.traceparent,\n tracestate: traceCtx.tracestate,\n };\n }\n\n this._currentConnection.send(message);\n }\n\n public onMessage(listener: MessageListener) {\n this._messageListeners.add(listener);\n return () => this._messageListeners.delete(listener);\n }\n\n public onReconnected(listener: ReconnectListener, opts?: { emitCurrentState?: boolean }) {\n this._reconnectListeners.add(listener);\n if ((opts?.emitCurrentState ?? true) && this._ready.state === TriggerState.RESOLVED) {\n // Microtask so that listener is always called asynchronously, no matter the state of the ready trigger\n // at the moment of registration.\n scheduleMicroTask(this._ctx, () => {\n if (this._reconnectListeners.has(listener)) {\n try {\n listener();\n } catch (error) {\n log.catch(error);\n }\n }\n });\n }\n\n return () => this._reconnectListeners.delete(listener);\n }\n\n /**\n * Open connection to messaging service.\n */\n protected override async _open(): Promise<void> {\n log('opening...', { info: this.info });\n this._persistentLifecycle.open().catch((err) => {\n log.warn('Error while opening connection', { err });\n });\n\n // Notify about status changes (rtt, rate counters).\n scheduleTaskInterval(\n this._ctx,\n async () => {\n if (!this._currentConnection) {\n return;\n }\n this.statusChanged.emit(this.status);\n },\n STATUS_REFRESH_INTERVAL,\n );\n }\n\n /**\n * Close connection and free resources.\n */\n protected override async _close(): Promise<void> {\n log('closing...', { peerKey: this._identity.peerKey });\n this._closeCurrentConnection();\n await this._persistentLifecycle.close();\n }\n\n private async _connect(): Promise<EdgeWsConnection | undefined> {\n if (this._ctx.disposed) {\n return undefined;\n }\n\n const identity = this._identity;\n const path = `/ws/${identity.identityDid}/${identity.peerKey}`;\n const protocolHeader = this._config.disableAuth ? undefined : await this._createAuthHeader(path);\n if (this._identity !== identity) {\n log('identity changed during auth header request');\n return undefined;\n }\n\n const restartRequired = new Trigger();\n const url = new URL(path, this._baseWsUrl);\n log('Opening websocket', { url: url.toString(), protocolHeader });\n const connection = new EdgeWsConnection(\n identity,\n {\n url,\n protocolHeader,\n headers: this._config.clientTag ? { 'X-DXOS-Client-Tag': this._config.clientTag } : undefined,\n },\n {\n onConnected: () => {\n if (this._isActive(connection)) {\n this._ready.wake();\n this._notifyReconnected();\n } else {\n log.verbose('connected callback ignored, because connection is not active');\n }\n },\n onRestartRequired: () => {\n if (this._isActive(connection)) {\n this._closeCurrentConnection();\n void this._persistentLifecycle.scheduleRestart();\n } else {\n log.verbose('restart requested by inactive connection');\n }\n restartRequired.wake();\n },\n onMessage: (message) => {\n if (this._isActive(connection)) {\n this._notifyMessageReceived(message);\n } else {\n log.verbose('ignored a message on inactive connection', {\n from: message.source,\n type: message.payload?.typeUrl,\n });\n }\n },\n },\n );\n this._currentConnection = connection;\n\n await connection.open();\n\n // The connection is only a successful start once the socket becomes ready. A socket that\n // closes or errors before becoming ready (or never connects within the timeout) is a failed\n // start: throwing lets PersistentLifecycle apply its backoff. Returning here would mark the\n // attempt as successful and reset the backoff, degenerating reconnects into a hot loop when\n // the server accepts then immediately drops the socket.\n const becameReady = await Promise.race([\n this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT }).then(\n () => true,\n () => false,\n ),\n restartRequired.wait().then(() => false),\n ]);\n if (!becameReady) {\n throw new EdgeConnectionClosedError();\n }\n\n return connection;\n }\n\n private async _disconnect(state: EdgeWsConnection): Promise<void> {\n await state.close();\n this.statusChanged.emit(this.status);\n }\n\n private _closeCurrentConnection(error: Error = new EdgeConnectionClosedError()): void {\n this._currentConnection = undefined;\n this._ready.throw(error);\n this._ready.reset();\n this.statusChanged.emit(this.status);\n }\n\n private _notifyReconnected(): void {\n this.statusChanged.emit(this.status);\n for (const listener of this._reconnectListeners) {\n try {\n listener();\n } catch (err) {\n log.error('ws reconnect listener failed', { err });\n }\n }\n }\n\n private _notifyMessageReceived(message: Message): void {\n for (const listener of this._messageListeners) {\n try {\n listener(message);\n } catch (err) {\n log.error('ws incoming message processing failed', { err, payload: protocol.getPayloadType(message) });\n }\n }\n }\n\n private async _createAuthHeader(path: string): Promise<string | undefined> {\n const httpUrl = new URL(path, this._baseHttpUrl);\n httpUrl.protocol = getEdgeUrlWithProtocol(this._baseWsUrl.toString(), 'http');\n const response = await fetch(httpUrl, { method: 'GET' });\n if (response.status === 401) {\n return encodePresentationWsAuthHeader(await handleAuthChallenge(response, this._identity));\n } else {\n log.warn('no auth challenge from edge', { status: response.status, statusText: response.statusText });\n return undefined;\n }\n }\n\n private _isActive = (connection: EdgeWsConnection) => connection === this._currentConnection;\n}\n\nconst encodePresentationWsAuthHeader = (encodedPresentation: Uint8Array): string => {\n // '=' and '/' characters are not allowed in the WebSocket subprotocol header.\n const encodedToken = Buffer.from(encodedPresentation).toString('base64').replace(/=*$/, '').replaceAll('/', '|');\n return `base64url.bearer.authorization.dxos.org.${encodedToken}`;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type * as HttpClient from '@effect/platform/HttpClient';\nimport type * as HttpClientError from '@effect/platform/HttpClientError';\nimport type * as HttpClientResponse from '@effect/platform/HttpClientResponse';\nimport * as Context from 'effect/Context';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Schedule from 'effect/Schedule';\n\nimport { log } from '@dxos/log';\n\n// TODO(burdon): Factor out.\n\nexport type RetryOptions = {\n timeout: Duration.Duration;\n retryTimes: number;\n retryBaseDelay: Duration.Duration;\n};\n\n// Layer pattern.\nexport class HttpConfig extends Context.Tag('HttpConfig')<HttpConfig, RetryOptions>() {\n static default = Layer.succeed(HttpConfig, {\n timeout: Duration.millis(1_000),\n retryTimes: 3,\n retryBaseDelay: Duration.millis(1_000),\n });\n}\n\n// HOC pattern.\nexport const withRetry = (\n effect: Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError.HttpClientError, HttpClient.HttpClient>,\n {\n timeout = Duration.millis(1_000),\n retryBaseDelay = Duration.millis(1_000),\n retryTimes = 3,\n }: Partial<RetryOptions> = {},\n) => {\n return effect.pipe(\n Effect.flatMap((res) =>\n // Treat 500 errors as retryable?\n res.status === 500 ? Effect.fail(new Error(res.status.toString())) : res.json,\n ),\n Effect.timeout(timeout),\n Effect.retry({\n schedule: Schedule.exponential(retryBaseDelay).pipe(Schedule.jittered),\n times: retryTimes,\n }),\n );\n};\n\nexport const withRetryConfig = (\n effect: Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError.HttpClientError, HttpClient.HttpClient>,\n) =>\n Effect.gen(function* () {\n const config = yield* HttpConfig;\n return yield* withRetry(effect, config);\n });\n\nexport const withLogging = <A extends HttpClientResponse.HttpClientResponse, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(\n Effect.tap((res) => {\n log.info('response', { status: res.status });\n }),\n );\n\n/**\n *\n */\n// TODO(burdon): Document.\nexport const encodeAuthHeader = (challenge: Uint8Array) => {\n const encodedChallenge = Buffer.from(challenge).toString('base64');\n return `VerifiablePresentation pb;base64,${encodedChallenge}`;\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { sleep } from '@dxos/async';\nimport { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { EDGE_CLIENT_TAG_HEADER, EdgeAuthChallengeError, EdgeCallFailedError, type EdgeFailure } from '@dxos/protocols';\n\nimport { type EdgeIdentity, handleAuthChallenge } from './edge-identity';\nimport { encodeAuthHeader } from './http-client';\nimport { getEdgeUrlWithProtocol } from './utils';\n\nconst DEFAULT_RETRY_TIMEOUT = 1500;\nconst DEFAULT_RETRY_JITTER = 500;\nconst DEFAULT_MAX_RETRIES_COUNT = 3;\nconst WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB\n\nexport type RetryConfig = {\n /** Number of retries, not counting the initial request. */\n count: number;\n /** Delay before retries in ms. */\n timeout?: number;\n /** Random additional delay to spread retries. */\n jitter?: number;\n};\n\nexport type EdgeHttpCallArgs = {\n retry?: RetryConfig;\n /**\n * Force authentication by pre-fetching `/auth` to obtain the challenge before\n * sending the body. Use for requests with large bodies to avoid sending twice.\n * Not available on HubHttpClient (hub-service has no `/auth` endpoint).\n */\n auth?: boolean;\n};\n\nexport type BaseHttpClientOptions = {\n /**\n * Tag included in the {@link EDGE_CLIENT_TAG_HEADER} header on every request.\n * Used on Edge to classify traffic for metering (e.g. `ci-e2e`).\n */\n clientTag?: string;\n};\n\ntype HttpRequestArgs = {\n method: string;\n retry?: RetryConfig;\n body?: any;\n /** @default true */\n json?: boolean;\n auth?: boolean;\n};\n\nexport type RawHttpRequestArgs = {\n method: string;\n body?: BodyInit;\n headers?: Record<string, string>;\n retry?: RetryConfig;\n auth?: boolean;\n};\n\nexport abstract class BaseHttpClient {\n protected readonly _baseUrl: string;\n protected readonly _clientTag: string | undefined;\n protected _edgeIdentity: EdgeIdentity | undefined;\n /** Auth header cached until next 401. */\n protected _authHeader: string | undefined;\n\n constructor(baseUrl: string, options?: BaseHttpClientOptions) {\n this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');\n this._clientTag = options?.clientTag;\n log('created', { url: this._baseUrl });\n }\n\n get baseUrl() {\n return this._baseUrl;\n }\n\n setIdentity(identity: EdgeIdentity): void {\n if (this._edgeIdentity?.identityDid !== identity.identityDid || this._edgeIdentity?.peerKey !== identity.peerKey) {\n this._edgeIdentity = identity;\n this._authHeader = undefined;\n }\n }\n\n protected async _call<T>(ctx: Context, url: URL, args: HttpRequestArgs): Promise<T> {\n const shouldRetry = createRetryHandler(args);\n // Log presence/size only — never log raw body contents which may contain PII.\n log('fetch', {\n url,\n hasBody: args.body !== undefined,\n bodySize: typeof args.body === 'string' ? args.body.length : undefined,\n });\n\n const traceHeaders = getTraceHeaders(ctx);\n\n let handledAuth = false;\n const tryCount = 1;\n while (true) {\n let processingError: EdgeCallFailedError | undefined = undefined;\n try {\n if (!this._authHeader && args.auth) {\n const response = await fetch(new URL('/auth', this._baseUrl));\n if (response.status === 401) {\n this._authHeader = await this._handleUnauthorized(response);\n }\n }\n\n const request = createRequest(args, this._authHeader, traceHeaders, this._clientTag);\n log('call', { url, tryCount, authHeader: !!this._authHeader });\n const response = await fetch(url, request);\n\n if (response.ok) {\n const contentType = response.headers.get('Content-Type') ?? '';\n // No-content responses (204, empty body, non-JSON) — return undefined.\n if (\n response.status === 204 ||\n response.headers.get('Content-Length') === '0' ||\n !contentType.includes('application/json')\n ) {\n return undefined as T;\n }\n const body = await response.clone().json();\n if (typeof body !== 'object' || body === null) {\n return body;\n }\n if (!('success' in body)) {\n return body;\n }\n if (body.success) {\n return body.data;\n }\n } else if (response.status === 401 && response.headers.get('WWW-Authenticate') !== null && !handledAuth) {\n // Only retry edge auth when the 401 came from edge's own auth layer. Edge always sets\n // `WWW-Authenticate` on its own 401s; upstream-forwarded 401s lack it.\n this._authHeader = await this._handleUnauthorized(response);\n handledAuth = true;\n continue;\n }\n\n const contentType = response.headers.get('Content-Type') ?? '';\n const body: EdgeFailure = contentType.startsWith('application/json')\n ? await response.clone().json()\n : undefined;\n\n invariant(!body?.success, 'Expected body to not be a failure response or undefined.');\n\n if (body?.data?.type === 'auth_challenge' && typeof body?.data?.challenge === 'string') {\n processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);\n } else if (body?.success === false) {\n processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);\n } else {\n invariant(!response.ok, 'Expected response to not be ok.');\n processingError = await EdgeCallFailedError.fromHttpFailure(response);\n }\n } catch (error: any) {\n processingError = EdgeCallFailedError.fromProcessingFailureCause(error);\n }\n\n if (processingError?.isRetryable && (await shouldRetry(ctx, processingError.retryAfterMs))) {\n log.verbose('retrying request', { url, processingError });\n } else {\n throw processingError!;\n }\n }\n }\n\n /**\n * Like {@link _call} but returns the raw `Response` instead of parsing a JSON envelope — for\n * endpoints with binary or absent response bodies (e.g. blob storage). A 404 is returned to the\n * caller rather than thrown, since \"not found\" is an expected outcome for lookups; all other\n * non-ok, non-retryable statuses throw `EdgeCallFailedError`, mirroring `_call`.\n *\n * NOTE: Duplicates `_call`'s auth/retry loop rather than sharing it, to avoid touching `_call`'s\n * broadly-depended-on JSON-envelope behavior. `EdgeHttpClient.anthropicAiRequest`'s separate\n * duplicate loop is a follow-up candidate for consolidating onto this method.\n */\n protected async _callRaw(ctx: Context, url: URL, args: RawHttpRequestArgs): Promise<Response> {\n const shouldRetry = createRetryHandler(args);\n log('fetch', { url, hasBody: args.body !== undefined });\n\n const traceHeaders = getTraceHeaders(ctx);\n\n let handledAuth = false;\n while (true) {\n let processingError: EdgeCallFailedError | undefined;\n try {\n if (!this._authHeader && args.auth) {\n const response = await fetch(new URL('/auth', this._baseUrl));\n if (response.status === 401) {\n this._authHeader = await this._handleUnauthorized(response);\n }\n }\n\n const headers: Record<string, string> = { ...args.headers };\n if (this._authHeader) {\n headers['Authorization'] = this._authHeader;\n }\n if (traceHeaders) {\n Object.assign(headers, traceHeaders);\n }\n if (this._clientTag) {\n headers[EDGE_CLIENT_TAG_HEADER] = this._clientTag;\n }\n\n const response = await fetch(url, { method: args.method, body: args.body, headers });\n\n if (response.ok || response.status === 404) {\n return response;\n }\n\n if (response.status === 401 && response.headers.get('WWW-Authenticate') !== null && !handledAuth) {\n this._authHeader = await this._handleUnauthorized(response);\n handledAuth = true;\n continue;\n }\n\n processingError = await EdgeCallFailedError.fromHttpFailure(response);\n } catch (error: any) {\n processingError = EdgeCallFailedError.fromProcessingFailureCause(error);\n }\n\n if (processingError?.isRetryable && (await shouldRetry(ctx, processingError.retryAfterMs))) {\n log.verbose('retrying raw request', { url, processingError });\n } else {\n throw processingError!;\n }\n }\n }\n\n protected async _handleUnauthorized(response: Response): Promise<string> {\n if (!this._edgeIdentity) {\n log.warn('unauthorized response received before identity was set');\n throw await EdgeCallFailedError.fromHttpFailure(response);\n }\n const challenge = await handleAuthChallenge(response, this._edgeIdentity);\n return encodeAuthHeader(challenge);\n }\n}\n\nconst createRequest = (\n { method, body, json = true }: HttpRequestArgs,\n authHeader: string | undefined,\n traceHeaders?: Record<string, string>,\n clientTag?: string,\n): RequestInit => {\n let requestBody: BodyInit | undefined;\n const headers: HeadersInit = {};\n\n if (json) {\n requestBody = body === undefined ? undefined : JSON.stringify(body);\n headers['Content-Type'] = 'application/json';\n } else {\n requestBody = body;\n }\n\n if (typeof requestBody === 'string' && requestBody.length > WARNING_BODY_SIZE) {\n log.warn('Request with large body', { bodySize: requestBody.length });\n }\n\n if (authHeader) {\n headers['Authorization'] = authHeader;\n }\n\n if (traceHeaders) {\n Object.assign(headers, traceHeaders);\n }\n\n if (clientTag) {\n headers[EDGE_CLIENT_TAG_HEADER] = clientTag;\n }\n\n return { method, body: requestBody, headers };\n};\n\nconst getTraceHeaders = (ctx: Context): Record<string, string> | undefined => {\n const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE) as TraceContextData | undefined;\n if (!traceCtx) {\n return undefined;\n }\n const headers: Record<string, string> = { traceparent: traceCtx.traceparent };\n if (traceCtx.tracestate) {\n headers.tracestate = traceCtx.tracestate;\n }\n return headers;\n};\n\n/** @deprecated */\nconst createRetryHandler = ({ retry }: HttpRequestArgs) => {\n if (!retry || retry.count < 1) {\n return async () => false;\n }\n let retries = 0;\n const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;\n const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;\n const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;\n return async (ctx: Context, retryAfter?: number) => {\n if (++retries > maxRetries || ctx.disposed) {\n return false;\n }\n if (retryAfter) {\n await sleep(retryAfter);\n } else {\n const timeout = baseTimeout + Math.random() * jitter;\n await sleep(timeout);\n }\n return true;\n };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Headers from '@effect/platform/Headers';\nimport * as HttpClient from '@effect/platform/HttpClient';\nimport * as HttpClientError from '@effect/platform/HttpClientError';\nimport * as HttpClientResponse from '@effect/platform/HttpClientResponse';\nimport * as Effect from 'effect/Effect';\nimport * as FiberRef from 'effect/FiberRef';\nimport * as Layer from 'effect/Layer';\nimport * as Stream from 'effect/Stream';\n\nimport { BaseError, type BaseErrorOptions } from '@dxos/errors';\nimport { log } from '@dxos/log';\nimport { BYOK_HEADER } from '@dxos/protocols';\n\nimport { type EdgeHttpClient } from './edge-http-client';\n\nexport type GetEdgeHttpClient = () => EdgeHttpClient;\n\n/**\n * Thrown by {@link EdgeAiHttpClient} when an AI request carrying {@link BYOK_HEADER} is rejected\n * with 401/403 by the upstream provider — i.e. the user-supplied API key is invalid. Wrapped as\n * the `cause` of an `HttpClientError.ResponseError` so it flows through `@effect/ai`'s error\n * mapping; callers walk the cause chain (via {@link ByokError.is}) to render a useful message.\n */\nexport class ByokError extends BaseError.extend('ByokError', 'BYOK authentication failed') {\n constructor(options: { status: number; provider: string } & BaseErrorOptions) {\n super({ context: { status: options.status, provider: options.provider }, ...options });\n }\n}\n\n/**\n * Thrown by {@link EdgeAiHttpClient} when EDGE rejects an AI request with 429 because the\n * authenticated profile exceeded a metering limit. Wrapped as the `cause` of an\n * {@link HttpClientError.ResponseError} so it survives `@effect/ai`'s error mapping.\n */\nexport class UsageQuotaExceededError extends BaseError.extend('UsageQuotaExceededError', 'Usage quota exceeded') {}\n\n/**\n * Copy pasted from https://github.com/Effect-TS/effect/blob/main/packages/platform/src/internal/fetchHttpClient.ts\n */\nexport const requestInitTagKey = '@effect/platform/FetchHttpClient/FetchOptions';\n\ntype AnthropicMessagesPayload = {\n tools?: ReadonlyArray<Record<string, unknown>>;\n};\n\nconst isUserDefinedAnthropicTool = (tool: Record<string, unknown>): boolean =>\n tool.input_schema != null && typeof tool.input_schema === 'object';\n\n/**\n * Enables Anthropic fine-grained tool input streaming for client-defined tools.\n * Provider tools (bash, web_search, etc.) are left unchanged.\n */\nexport const patchAnthropicMessagesRequestBody = (body: BodyInit | undefined): BodyInit | undefined => {\n if (body == null) {\n return body;\n }\n\n const decodeBody = (): string | undefined => {\n if (typeof body === 'string') {\n return body;\n }\n if (body instanceof Uint8Array) {\n return new TextDecoder().decode(body);\n }\n return undefined;\n };\n\n const text = decodeBody();\n if (text == null) {\n return body;\n }\n\n try {\n const payload = JSON.parse(text) as AnthropicMessagesPayload;\n if (!Array.isArray(payload.tools)) {\n return body;\n }\n\n payload.tools = payload.tools.map((tool) =>\n isUserDefinedAnthropicTool(tool) ? { ...tool, eager_input_streaming: true } : tool,\n );\n\n const patched = JSON.stringify(payload);\n return typeof body === 'string' ? patched : new TextEncoder().encode(patched);\n } catch {\n return body;\n }\n};\n\nconst readStreamBody = (stream: ReadableStream<Uint8Array>): Effect.Effect<string | undefined> =>\n Effect.promise(async () => {\n const response = new Response(stream);\n return await response.text();\n });\n\n/**\n * An `@effect/platform` {@link HttpClient.HttpClient} that routes requests through the\n * authenticated EDGE AI endpoint via {@link EdgeHttpClient.anthropicAiRequest}, instead of\n * fetching the AI service directly.\n *\n * Provide this layer in place of `FetchHttpClient.layer` when constructing an Anthropic client,\n * e.g. `AnthropicClient.layer({ apiUrl: 'http://edge' }).pipe(Layer.provide(EdgeAiHttpClient.layer(() => edgeClient)))`.\n * The `apiUrl` host is a sentinel; only the request path is forwarded (see `anthropicAiRequest`).\n *\n * Modeled on `FunctionsAiHttpClient` in `@dxos/functions`.\n */\nexport class EdgeAiHttpClient {\n static make = (getClient: GetEdgeHttpClient) =>\n HttpClient.make((request, url, signal, fiber) => {\n const edgeClient = getClient();\n const context = fiber.getFiberRef(FiberRef.currentContext);\n const options: RequestInit = context.unsafeMap.get(requestInitTagKey) ?? {};\n const headers = options.headers\n ? Headers.merge(Headers.fromInput(options.headers), request.headers)\n : request.headers;\n\n const carriedByok = !!headers[BYOK_HEADER.toLowerCase()];\n\n const send = (body: BodyInit | undefined) =>\n Effect.tryPromise({\n try: () =>\n edgeClient.anthropicAiRequest(\n new Request(url, {\n ...options,\n method: request.method,\n headers,\n body: patchAnthropicMessagesRequestBody(body),\n signal,\n }),\n ),\n catch: (cause) => {\n log.error('Failed to fetch', { cause });\n return new HttpClientError.RequestError({\n request,\n reason: 'Transport',\n cause,\n });\n },\n }).pipe(\n Effect.flatMap((response) => {\n const httpResponse = HttpClientResponse.fromWeb(request, response);\n // A 401/403 on a BYOK-carrying request means the user's upstream key was rejected.\n // Wrap as a typed ResponseError with `cause: ByokError` so it survives AiError's\n // `fromRequestError` mapping; callers walk the cause chain to render a useful message.\n if (carriedByok && (response.status === 401 || response.status === 403)) {\n return Effect.tryPromise({\n try: () => response.clone().json() as Promise<{ error?: { message?: string } } | undefined>,\n catch: () => undefined,\n }).pipe(\n Effect.orElseSucceed(() => undefined),\n Effect.flatMap((body) =>\n Effect.fail(\n new HttpClientError.ResponseError({\n request,\n response: httpResponse,\n reason: 'StatusCode',\n cause: new ByokError({\n status: response.status,\n provider: 'anthropic.com',\n message: body?.error?.message ?? 'Authentication failed',\n }),\n }),\n ),\n ),\n );\n }\n // Platform quota (not BYOK): reserve/commit rejected the request before upstream AI.\n if (!carriedByok && response.status === 429) {\n return Effect.tryPromise({\n try: () => response.clone().json() as Promise<{ error?: { message?: string } } | undefined>,\n catch: () => undefined,\n }).pipe(\n Effect.orElseSucceed(() => undefined),\n Effect.flatMap((body) =>\n Effect.fail(\n new HttpClientError.ResponseError({\n request,\n response: httpResponse,\n reason: 'StatusCode',\n cause: new UsageQuotaExceededError({\n message: body?.error?.message,\n }),\n }),\n ),\n ),\n );\n }\n return Effect.succeed(httpResponse);\n }),\n );\n\n switch (request.body._tag) {\n case 'Raw':\n case 'Uint8Array':\n return send(request.body.body as any);\n case 'FormData':\n return send(request.body.formData);\n case 'Stream':\n return Stream.toReadableStreamEffect(request.body.stream).pipe(\n Effect.flatMap((readable) => readStreamBody(readable)),\n Effect.flatMap((text) => send(text)),\n );\n }\n\n return send(undefined);\n });\n\n static layer = (getClient: GetEdgeHttpClient) =>\n Layer.succeed(HttpClient.HttpClient, EdgeAiHttpClient.make(getClient));\n}\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport * as FetchHttpClient from '@effect/platform/FetchHttpClient';\nimport * as HttpClient from '@effect/platform/HttpClient';\nimport * as HttpClientRequest from '@effect/platform/HttpClientRequest';\nimport * as EffectContext from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Function from 'effect/Function';\n\nimport { type Context } from '@dxos/context';\nimport { EffectEx } from '@dxos/effect';\nimport { invariant } from '@dxos/invariant';\nimport { type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport {\n type CompleteOAuthRegistrationRequest,\n type CompleteOAuthRegistrationResponse,\n type CreateAgentRequestBody,\n type CreateAgentResponseBody,\n type CreateSpaceRequest,\n type CreateSpaceResponseBody,\n EDGE_CLIENT_TAG_HEADER,\n type EdgeStatus,\n type ExecuteWorkflowResponseBody,\n type ExportBundleRequest,\n type ExportBundleResponse,\n type FeedProtocol,\n type GetAccessTokenRequest,\n type GetAccessTokenResponseBody,\n type GetAgentStatusResponseBody,\n type GetNotarizationResponseBody,\n type GetPluginsResponseBody,\n type ImportBundleRequest,\n type InitiateOAuthFlowRequest,\n type InitiateOAuthFlowResponse,\n type JoinSpaceRequest,\n type JoinSpaceResponseBody,\n type ObjectId,\n type PostNotarizationRequestBody,\n type RecoverIdentityRequest,\n type RecoverIdentityResponseBody,\n type SerializedError,\n type UploadFunctionRequest,\n type UploadFunctionResponseBody,\n} from '@dxos/protocols';\nimport {\n type QueryRequest as QueryRequestProto,\n type QueryResponse as QueryResponseProto,\n} from '@dxos/protocols/proto/dxos/echo/query';\nimport { createUrl } from '@dxos/util';\n\nimport { BaseHttpClient, type BaseHttpClientOptions, type EdgeHttpCallArgs } from './base-http-client';\nimport { proxyFetchLegacy } from './cors-proxy';\nimport { HttpConfig, withLogging, withRetryConfig } from './http-client';\n\nexport type { EdgeHttpCallArgs, RetryConfig } from './base-http-client';\n\n/**\n * HTTP wire shape returned by `/queue/.../query`.\n */\nexport type EdgeQueryQueueResponse = {\n objects?: unknown[];\n nextCursor?: string;\n prevCursor?: string;\n};\n\nexport type UploadPluginBundleRequest = {\n slug: string;\n version: string;\n files: { path: string; content: string }[];\n};\n\nexport type TriggersDispatcherStatus = {\n isActive: boolean;\n nextCronTaskRunTimestamp?: number;\n registeredTriggers: string[];\n stopAfterTimestamp?: number;\n remainingMs?: number;\n nextAlarmTimestamp?: number;\n};\n\nexport type GetCronTriggersResponse = {\n cronIds: string[];\n};\n\n/**\n * Per-trigger runtime status reported by the EDGE dispatcher, keyed to the\n * trigger's ECHO object id so a client can correlate it with the replicated\n * `Trigger` object in its local database.\n *\n * TODO(edge): The backing endpoint (`GET /triggers/{spaceId}`, see\n * {@link EdgeHttpClient.getSpaceTriggers}) is a proposal and is not yet\n * implemented server-side.\n */\nexport type EdgeTriggerStatus = {\n /** ECHO object id of the trigger. */\n triggerId: ObjectId;\n /** Whether the EDGE dispatcher currently has this trigger registered. */\n registered: boolean;\n kind: 'timer' | 'subscription' | 'email' | 'webhook' | 'feed' | 'direct';\n /** Next scheduled cron execution (epoch ms). Set only for `timer` triggers. */\n nextExecutionTimestamp?: number;\n /** Cooldown expiry after a failure (epoch ms). */\n cooldownUntilTimestamp?: number;\n /** Outcome of the most recent invocation on the edge. */\n lastResult?: {\n status: 'success' | 'failure';\n /** Completion time (epoch ms). */\n timestamp: number;\n error?: SerializedError;\n };\n};\n\n/**\n * Response of the proposed `GET /triggers/{spaceId}` endpoint: the full list of\n * triggers registered on a space's EDGE dispatcher, with runtime status. Polled\n * by the remote trigger monitor to surface edge trigger state.\n */\nexport type GetSpaceTriggersResponse = {\n /** Whether the space's edge dispatcher is active. */\n isActive: boolean;\n triggers: EdgeTriggerStatus[];\n};\n\nexport type EdgeHttpClientOptions = BaseHttpClientOptions;\n\nexport class EdgeHttpClientService extends EffectContext.Tag('@dxos/edge-client/EdgeHttpClient')<\n EdgeHttpClientService,\n EdgeHttpClient\n>() {}\n\n/**\n * HTTP client for the edge worker API (spaces, queues, functions, agents, etc.).\n *\n * Hub-service API (accounts, invitations) lives in {@link HubHttpClient} — the two\n * services run at different URLs and are never both available from the same base URL.\n */\nexport class EdgeHttpClient extends BaseHttpClient {\n constructor(baseUrl: string, options?: EdgeHttpClientOptions) {\n super(baseUrl, options);\n log('created', { url: this.baseUrl });\n }\n\n //\n // Status\n //\n\n public async getStatus(ctx: Context, args?: EdgeHttpCallArgs): Promise<EdgeStatus> {\n return this._call(ctx, new URL('/status', this.baseUrl), { ...args, method: 'GET', auth: true });\n }\n\n //\n // Agents\n //\n\n public createAgent(\n ctx: Context,\n body: CreateAgentRequestBody,\n args?: EdgeHttpCallArgs,\n ): Promise<CreateAgentResponseBody> {\n return this._call(ctx, new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });\n }\n\n public getAgentStatus(\n ctx: Context,\n request: { ownerIdentityDid: string },\n args?: EdgeHttpCallArgs,\n ): Promise<GetAgentStatusResponseBody> {\n return this._call(ctx, new URL(`/users/${request.ownerIdentityDid}/agent/status`, this.baseUrl), {\n ...args,\n method: 'GET',\n });\n }\n\n //\n // Credentials\n //\n\n public getCredentialsForNotarization(\n ctx: Context,\n spaceId: SpaceId,\n args?: EdgeHttpCallArgs,\n ): Promise<GetNotarizationResponseBody> {\n return this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });\n }\n\n public async notarizeCredentials(\n ctx: Context,\n spaceId: SpaceId,\n body: PostNotarizationRequestBody,\n args?: EdgeHttpCallArgs,\n ): Promise<void> {\n await this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Identity\n //\n\n public async recoverIdentity(\n ctx: Context,\n body: RecoverIdentityRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<RecoverIdentityResponseBody> {\n return this._call(ctx, new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Invitations (space join)\n //\n\n public async joinSpaceByInvitation(\n ctx: Context,\n spaceId: SpaceId,\n body: JoinSpaceRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<JoinSpaceResponseBody> {\n return this._call(ctx, new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // OAuth\n //\n\n public async initiateOAuthFlow(\n ctx: Context,\n body: InitiateOAuthFlowRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<InitiateOAuthFlowResponse> {\n return this._call(ctx, new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n public async completeOAuthRegistration(\n ctx: Context,\n body: CompleteOAuthRegistrationRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<CompleteOAuthRegistrationResponse> {\n return this._call(ctx, new URL('/oauth/registration/complete', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n /**\n * Resolves the live access token behind a `MANAGED_ACCESS_TOKEN` placeholder. Authorized by the\n * caller's presentation: EDGE serves it only to members of the owning space.\n */\n public async getAccessToken(\n ctx: Context,\n body: GetAccessTokenRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<GetAccessTokenResponseBody> {\n return this._call(ctx, new URL('/oauth/token', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Spaces\n //\n\n async createSpace(ctx: Context, body: CreateSpaceRequest, args?: EdgeHttpCallArgs): Promise<CreateSpaceResponseBody> {\n return this._call(ctx, new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Queues\n //\n\n public async queryQueue(\n ctx: Context,\n subspaceTag: string,\n spaceId: SpaceId,\n query: FeedProtocol.FeedQuery,\n args?: EdgeHttpCallArgs,\n ): Promise<EdgeQueryQueueResponse> {\n const queueId = query.feedIds?.[0];\n invariant(queueId, 'queueId required');\n return this._call(\n ctx,\n createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {\n after: query.after,\n before: query.before,\n limit: query.limit,\n reverse: query.reverse,\n objectIds: query.objectIds?.join(','),\n }),\n { ...args, method: 'GET' },\n );\n }\n\n public async insertIntoQueue(\n ctx: Context,\n subspaceTag: string,\n spaceId: SpaceId,\n queueId: ObjectId,\n objects: unknown[],\n args?: EdgeHttpCallArgs,\n ): Promise<void> {\n return this._call(ctx, new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {\n ...args,\n body: { objects },\n method: 'POST',\n });\n }\n\n public async deleteFromQueue(\n ctx: Context,\n subspaceTag: string,\n spaceId: SpaceId,\n queueId: ObjectId,\n objectIds: ObjectId[],\n args?: EdgeHttpCallArgs,\n ): Promise<void> {\n return this._call(\n ctx,\n createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {\n ids: objectIds.join(','),\n }),\n { ...args, method: 'DELETE' },\n );\n }\n\n //\n // Blobs\n //\n\n /**\n * Builds the URL for the blob stored under `key`. `key` is URL-encoded for defense in depth —\n * callers pass a lowercase hex SHA-256 digest (extracted from an `ni:` URI by the edge backend).\n */\n public getBlobUrl(key: string): URL {\n return new URL(`/api/file/${encodeURIComponent(key)}`, this.baseUrl);\n }\n\n /**\n * Uploads bytes to the edge blob service, keyed by content hash. Pre-fetches `/auth` (`auth:\n * true`) so large bodies aren't sent twice on an auth challenge.\n */\n public async putBlob(\n ctx: Context,\n key: string,\n data: Uint8Array,\n args?: EdgeHttpCallArgs & { contentType?: string },\n ): Promise<void> {\n const headers: Record<string, string> = {};\n if (args?.contentType) {\n headers['Content-Type'] = args.contentType;\n }\n await this._callRaw(ctx, this.getBlobUrl(key), {\n retry: args?.retry,\n auth: args?.auth ?? true,\n method: 'POST',\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while DOM's\n // `BodyInit` only covers `ArrayBuffer`-backed views — a gap between the DOM lib types and\n // the TS standard lib, not fixable by typing `data` differently.\n body: data as BodyInit,\n headers,\n });\n }\n\n /**\n * Downloads bytes previously stored with {@link putBlob}. Returns `undefined` if `key` is not\n * found.\n */\n public async getBlob(ctx: Context, key: string, args?: EdgeHttpCallArgs): Promise<Uint8Array | undefined> {\n const response = await this._callRaw(ctx, this.getBlobUrl(key), { ...args, method: 'GET' });\n if (response.status === 404) {\n return undefined;\n }\n return new Uint8Array(await response.arrayBuffer());\n }\n\n /**\n * Checks whether bytes are stored under `key`, without downloading them.\n */\n public async hasBlob(ctx: Context, key: string, args?: EdgeHttpCallArgs): Promise<boolean> {\n const response = await this._callRaw(ctx, this.getBlobUrl(key), { ...args, method: 'HEAD' });\n return response.status !== 404;\n }\n\n /**\n * Deletes bytes stored under `key`. Not called by any core `Blob.remove` path in v1 (deletion is\n * deferred), provided for completeness.\n */\n public async deleteBlob(ctx: Context, key: string, args?: EdgeHttpCallArgs): Promise<void> {\n await this._callRaw(ctx, this.getBlobUrl(key), { ...args, method: 'DELETE' });\n }\n\n //\n // Functions\n //\n\n public async uploadFunction(\n ctx: Context,\n pathParts: { functionId?: string },\n body: UploadFunctionRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<UploadFunctionResponseBody> {\n const formData = new FormData();\n formData.append('name', body.name ?? '');\n formData.append('version', body.version);\n // The function owner is the authenticated identity (edge requires ownerUri === presenter DID).\n // Prefer the connected identity's DID; otherwise use the DID supplied on the request body.\n const ownerUri = this._edgeIdentity?.identityDid ?? body.ownerUri;\n formData.append('ownerUri', ownerUri);\n formData.append('entryPoint', body.entryPoint);\n body.runtime && formData.append('runtime', body.runtime);\n for (const [filename, content] of Object.entries(body.assets)) {\n formData.append(\n 'assets',\n new Blob([content as Uint8Array<ArrayBuffer>], { type: getFileMimeType(filename) }),\n filename,\n );\n }\n const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');\n return this._call(ctx, new URL(path, this.baseUrl), { ...args, body: formData, method: 'PUT', json: false });\n }\n\n public async listFunctions(ctx: Context, args?: EdgeHttpCallArgs): Promise<any> {\n return this._call(ctx, new URL('/functions', this.baseUrl), { ...args, method: 'GET' });\n }\n\n public async invokeFunction(\n ctx: Context,\n params: {\n functionId: string;\n version?: string;\n spaceId?: SpaceId;\n cpuTimeLimit?: number;\n subrequestsLimit?: number;\n },\n input: unknown,\n args?: EdgeHttpCallArgs,\n ): Promise<any> {\n const url = new URL(`/functions/${params.functionId}`, this.baseUrl);\n if (params.version) {\n url.searchParams.set('version', params.version);\n }\n if (params.spaceId) {\n url.searchParams.set('spaceId', params.spaceId.toString());\n }\n if (params.cpuTimeLimit) {\n url.searchParams.set('cpuTimeLimit', params.cpuTimeLimit.toString());\n }\n if (params.subrequestsLimit) {\n url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());\n }\n return this._call(ctx, url, { ...args, body: input, method: 'POST' });\n }\n\n //\n // Workflows\n //\n\n public async executeWorkflow(\n ctx: Context,\n spaceId: SpaceId,\n graphId: ObjectId,\n input: any,\n args?: EdgeHttpCallArgs,\n ): Promise<ExecuteWorkflowResponseBody> {\n return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {\n ...args,\n body: input,\n method: 'POST',\n });\n }\n\n //\n // Triggers\n //\n\n public async getCronTriggers(ctx: Context, spaceId: SpaceId): Promise<GetCronTriggersResponse> {\n return this._call<GetCronTriggersResponse>(ctx, new URL(`/functions/${spaceId}/triggers/crons`, this.baseUrl), {\n method: 'GET',\n });\n }\n\n public async getTriggersDispatcherStatus(\n ctx: Context,\n spaceId: SpaceId,\n args?: EdgeHttpCallArgs,\n ): Promise<TriggersDispatcherStatus> {\n return this._call<TriggersDispatcherStatus>(ctx, new URL(`/triggers/${spaceId}/status`, this.baseUrl), {\n ...args,\n method: 'GET',\n auth: true,\n });\n }\n\n public async forceRunCronTrigger(ctx: Context, spaceId: SpaceId, triggerId: ObjectId) {\n return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {\n method: 'POST',\n });\n }\n\n /**\n * Cancels the current run of a cron trigger on the EDGE dispatcher — its in-flight execution and\n * `runAgain` continuation chain. The trigger stays enabled, so its schedule keeps firing.\n */\n public async cancelTriggerRun(ctx: Context, spaceId: SpaceId, triggerId: ObjectId) {\n return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/cancel`, this.baseUrl), {\n method: 'POST',\n });\n }\n\n /**\n * Returns the full list of triggers registered on a space's EDGE dispatcher, with per-trigger\n * runtime status. Polled by the remote trigger monitor to surface edge trigger state.\n *\n * TODO(edge): Proposed endpoint; not yet implemented server-side.\n */\n public async getSpaceTriggers(\n ctx: Context,\n spaceId: SpaceId,\n args?: EdgeHttpCallArgs,\n ): Promise<GetSpaceTriggersResponse> {\n return this._call<GetSpaceTriggersResponse>(ctx, new URL(`/triggers/${spaceId}`, this.baseUrl), {\n ...args,\n method: 'GET',\n auth: true,\n });\n }\n\n //\n // Query\n //\n\n public async execQuery(\n ctx: Context,\n spaceId: SpaceId,\n body: QueryRequestProto,\n args?: EdgeHttpCallArgs,\n ): Promise<QueryResponseProto> {\n return this._call(ctx, new URL(`/spaces/${spaceId}/exec-query`, this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Registry\n //\n\n public async getRegistryPlugins(ctx: Context, args?: EdgeHttpCallArgs): Promise<GetPluginsResponseBody> {\n return this._call(ctx, new URL('/registry/plugins', this.baseUrl), { ...args, method: 'GET' });\n }\n\n /**\n * Uploads a built plugin bundle to the registry's R2-backed hosting. Authenticated\n * with the caller's hub identity (verifiable presentation) — `setIdentity` must\n * have been called. Returns the canonical `moduleUrl` (the hosted `manifest.json`).\n */\n public async uploadPluginBundle(\n ctx: Context,\n request: UploadPluginBundleRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<{ moduleUrl: string }> {\n return this._call(ctx, new URL('/registry/upload', this.baseUrl), {\n body: request,\n method: 'POST',\n auth: true,\n ...args,\n });\n }\n\n //\n // Import/Export\n //\n\n public async importBundle(\n ctx: Context,\n spaceId: SpaceId,\n body: ImportBundleRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<void> {\n return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });\n }\n\n public async exportBundle(\n ctx: Context,\n spaceId: SpaceId,\n body: ExportBundleRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<ExportBundleResponse> {\n return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Proxy\n //\n\n /**\n * Fetch through the edge proxy for third-party REST APIs.\n * TEMPORARY: currently routes through legacy open proxy. See https://github.com/dxos/edge/pull/576.\n */\n public async proxyFetch(target: URL, init: RequestInit = {}): Promise<Response> {\n return proxyFetchLegacy(target, init, this._clientTag);\n }\n\n //\n // AI service.\n //\n\n /**\n * Issue an authenticated request to the EDGE AI route (`/ai/generate/anthropic/*`), which\n * proxies to the AI service. Used as the backend HTTP client for the Anthropic AI provider\n * (see {@link EdgeAiHttpClient}).\n *\n * Returns the raw `Response` so streaming bodies are forwarded unchanged to `@effect/ai`.\n * Requires an identity to have been set via {@link setIdentity}.\n */\n // TODO(mykola): Merge into `BaseHttpClient._call` once it can return a streaming/raw `Response`;\n // the auth/retry loop below duplicates the one in `_call`.\n public async anthropicAiRequest(request: Request): Promise<Response> {\n const incoming = new URL(request.url);\n const base = this.baseUrl.replace(/\\/$/, '');\n const target = new URL(`${base}/ai/generate/anthropic${incoming.pathname}${incoming.search}`);\n\n const method = request.method;\n const body = method === 'GET' || method === 'HEAD' ? undefined : await request.arrayBuffer();\n\n let handledAuth = false;\n while (true) {\n if (!this._authHeader) {\n const authResponse = await fetch(new URL('/auth', this.baseUrl));\n if (authResponse.status === 401) {\n this._authHeader = await this._handleUnauthorized(authResponse);\n }\n }\n\n const headers = new Headers(request.headers);\n if (this._authHeader) {\n headers.set('Authorization', this._authHeader);\n }\n if (this._clientTag) {\n headers.set(EDGE_CLIENT_TAG_HEADER, this._clientTag);\n }\n\n const response = await fetch(target, { method, headers, body, signal: request.signal });\n // Only retry edge auth when the 401 came from edge's own auth layer. Edge always sets\n // `WWW-Authenticate` on its own 401s; upstream-forwarded 401s (e.g. invalid BYOK rejected\n // by Anthropic) lack it and must be surfaced verbatim.\n if (response.status === 401 && response.headers.get('WWW-Authenticate') !== null && !handledAuth) {\n this._authHeader = await this._handleUnauthorized(response);\n handledAuth = true;\n continue;\n }\n\n return response;\n }\n }\n\n //\n // Internal (Effect-based, used by tests)\n //\n\n public async _fetch<T>(url: URL, _args: { method: string }): Promise<T> {\n return Function.pipe(\n HttpClient.execute(HttpClientRequest.make(_args.method as any)(url.toString())),\n withLogging,\n withRetryConfig,\n Effect.provide(FetchHttpClient.layer),\n Effect.provide(HttpConfig.default),\n Effect.withSpan('EdgeHttpClient'),\n EffectEx.runAndForwardErrors,\n ) as T;\n }\n}\n\nconst getFileMimeType = (filename: string) =>\n ['.js', '.mjs'].some((ext) => filename.endsWith(ext))\n ? 'application/javascript+module'\n : filename.endsWith('.wasm')\n ? 'application/wasm'\n : 'application/octet-stream';\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Context } from '@dxos/context';\nimport {\n type CheckEmailExistsResponse,\n type GetAccountResponse,\n type GetProfileUsageResponse,\n type IssueInvitationResponse,\n type ListAccountInvitationsResponse,\n type LoginRequest,\n type LoginResponse,\n type RedeemInvitationCodeRequest,\n type RedeemInvitationCodeResponse,\n type RequestAccessRequest,\n type RequestAccessResponse,\n type ResendVerificationEmailResponse,\n type ValidateInvitationCodeResponse,\n} from '@dxos/protocols';\nimport { createUrl } from '@dxos/util';\n\nimport { BaseHttpClient, type BaseHttpClientOptions, type EdgeHttpCallArgs } from './base-http-client';\n\n/**\n * HTTP client for the hub-service API (accounts, invitations, email verification).\n *\n * Hub-service and the edge worker are separate Cloudflare Workers deployed at different\n * URLs (`DX_HUB_URL` vs `DX_EDGE_URL`). This client is never used to talk to the edge\n * worker, and vice versa — keep them separate.\n *\n * NOTE: Do NOT set `auth: true` on any call here. Hub-service has no `/auth` VP-challenge\n * endpoint (it has an admin login page that 302s and is not CORS-enabled). Auth is handled\n * via the regular request → 401 → WWW-Authenticate challenge → retry path.\n */\nexport class HubHttpClient extends BaseHttpClient {\n constructor(hubUrl: string, options?: BaseHttpClientOptions) {\n super(hubUrl, options);\n }\n\n //\n // Public (unauthenticated) endpoints\n //\n\n public async checkEmailExists(\n ctx: Context,\n body: { email: string },\n args?: EdgeHttpCallArgs,\n ): Promise<CheckEmailExistsResponse> {\n return this._call(ctx, new URL('/account/email/exists', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n public async validateInvitationCode(\n ctx: Context,\n body: { code: string },\n args?: EdgeHttpCallArgs,\n ): Promise<ValidateInvitationCodeResponse> {\n return this._call(ctx, new URL('/account/invitation-code/validate', this.baseUrl), {\n ...args,\n body,\n method: 'POST',\n });\n }\n\n public async redeemInvitationCode(\n ctx: Context,\n body: RedeemInvitationCodeRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<RedeemInvitationCodeResponse> {\n return this._call(ctx, new URL('/account/invitation-code/redeem', this.baseUrl), {\n ...args,\n body,\n method: 'POST',\n });\n }\n\n /**\n * Existing-account email login. Server inlines `token` for test emails; regular\n * emails are delivered out-of-band. Response is identical for unknown emails\n * (enumeration-safe).\n */\n public async login(ctx: Context, body: LoginRequest, args?: EdgeHttpCallArgs): Promise<LoginResponse> {\n return this._call(ctx, new URL('/account/login', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n public async requestAccess(\n ctx: Context,\n body: RequestAccessRequest,\n args?: EdgeHttpCallArgs,\n ): Promise<RequestAccessResponse> {\n return this._call(ctx, new URL('/account/request-access', this.baseUrl), { ...args, body, method: 'POST' });\n }\n\n //\n // Authenticated (VP) endpoints\n //\n\n public async getAccount(ctx: Context, args?: EdgeHttpCallArgs): Promise<GetAccountResponse> {\n return this._call(ctx, new URL('/account/me', this.baseUrl), { ...args, method: 'GET' });\n }\n\n public async deleteAccount(ctx: Context, args?: EdgeHttpCallArgs): Promise<{ deleted: boolean }> {\n return this._call(ctx, new URL('/account/me', this.baseUrl), { ...args, method: 'DELETE' });\n }\n\n public async listAccountInvitations(ctx: Context, args?: EdgeHttpCallArgs): Promise<ListAccountInvitationsResponse> {\n return this._call(ctx, new URL('/account/invitation', this.baseUrl), { ...args, method: 'GET' });\n }\n\n public async issueAccountInvitation(ctx: Context, args?: EdgeHttpCallArgs): Promise<IssueInvitationResponse> {\n return this._call(ctx, new URL('/account/invitation/issue', this.baseUrl), { ...args, method: 'POST' });\n }\n\n public async resendVerificationEmail(\n ctx: Context,\n args?: EdgeHttpCallArgs,\n ): Promise<ResendVerificationEmailResponse> {\n return this._call(ctx, new URL('/account/email/resend-verification', this.baseUrl), { ...args, method: 'POST' });\n }\n\n /**\n * Rolling-window usage and effective limits for the authenticated identity.\n * Served from the per-user metering DO; optional `windowSeconds` defaults to the largest limit window.\n */\n public async getProfileUsage(\n ctx: Context,\n query?: { windowSeconds?: number },\n args?: EdgeHttpCallArgs,\n ): Promise<GetProfileUsageResponse> {\n return this._call(\n ctx,\n createUrl(new URL('/api/metering/profile/usage', this.baseUrl), {\n windowSeconds: query?.windowSeconds,\n }),\n { ...args, method: 'GET' },\n );\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\n/** Request body for EDGE `/ai/browser-rendering/markdown` (ai-service Browser Run markdown quick action). */\nexport const MarkdownRequest = Schema.Struct({\n url: Schema.optional(Schema.String),\n html: Schema.optional(Schema.String),\n gotoOptions: Schema.optional(\n Schema.Struct({\n waitUntil: Schema.optional(Schema.Literal('load', 'domcontentloaded', 'networkidle0', 'networkidle2')),\n timeout: Schema.optional(Schema.Number),\n }),\n ),\n rejectRequestPattern: Schema.optional(Schema.Array(Schema.String)),\n userAgent: Schema.optional(Schema.String),\n waitForSelector: Schema.optional(Schema.String),\n});\n\nexport type MarkdownRequest = Schema.Schema.Type<typeof MarkdownRequest>;\n\n/** JSON body returned by Cloudflare Browser Run markdown quick action. */\nexport const MarkdownResponse = Schema.Struct({\n success: Schema.Boolean,\n result: Schema.String,\n});\n\nexport type MarkdownResponse = Schema.Schema.Type<typeof MarkdownResponse>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as Duration from "effect/Duration";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import * as Data from "effect/Data";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __exportAll = (all, no_symbols) => {
|
|
8
|
+
let target = {};
|
|
9
|
+
for (var name in all) __defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/service/edge-service.ts
|
|
18
|
+
var EDGE_CLIENT_TAG_HEADER = "X-DXOS-Client-Tag";
|
|
19
|
+
var DEFAULT_TIMEOUT = Duration.seconds(15);
|
|
20
|
+
/** Number of body characters retained for the error message on a non-2xx response. */
|
|
21
|
+
var ERROR_BODY_LIMIT = 512;
|
|
22
|
+
/** Single failure type for every transport, status, and decode error. */
|
|
23
|
+
var EdgeServiceError = class extends Data.TaggedError("EdgeServiceError") {};
|
|
24
|
+
var EdgeServiceClient = class {
|
|
25
|
+
#baseUrl;
|
|
26
|
+
#clientTag;
|
|
27
|
+
#timeout;
|
|
28
|
+
#fetch;
|
|
29
|
+
#authHeaders;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#baseUrl = options.baseUrl;
|
|
32
|
+
this.#clientTag = options.clientTag;
|
|
33
|
+
this.#timeout = Duration.decode(options.timeout ?? DEFAULT_TIMEOUT);
|
|
34
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
35
|
+
this.#fetch = fetchImpl.bind(globalThis);
|
|
36
|
+
this.#authHeaders = options.authHeaders;
|
|
37
|
+
}
|
|
38
|
+
get baseUrl() {
|
|
39
|
+
return this.#baseUrl;
|
|
40
|
+
}
|
|
41
|
+
/** POST a `FormData` (multipart) body and decode the JSON response. */
|
|
42
|
+
postForm(path, form, schema) {
|
|
43
|
+
return this.#request(path, schema, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
body: form
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** POST a JSON body and decode the JSON response. */
|
|
49
|
+
postJson(path, body, schema) {
|
|
50
|
+
return Effect.try({
|
|
51
|
+
try: () => JSON.stringify(body),
|
|
52
|
+
catch: (cause) => new EdgeServiceError({
|
|
53
|
+
message: "Failed to serialize JSON request body",
|
|
54
|
+
cause
|
|
55
|
+
})
|
|
56
|
+
}).pipe(Effect.flatMap((json) => this.#request(path, schema, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: json,
|
|
59
|
+
headers: { "Content-Type": "application/json" }
|
|
60
|
+
})));
|
|
61
|
+
}
|
|
62
|
+
#request(path, schema, init) {
|
|
63
|
+
return Effect.gen(this, function* () {
|
|
64
|
+
const url = new URL(path, this.#baseUrl);
|
|
65
|
+
const headers = new Headers(init.headers ?? void 0);
|
|
66
|
+
if (this.#clientTag) headers.set(EDGE_CLIENT_TAG_HEADER, this.#clientTag);
|
|
67
|
+
if (this.#authHeaders) {
|
|
68
|
+
const auth = yield* this.#authHeaders();
|
|
69
|
+
for (const [key, value] of Object.entries(auth)) headers.set(key, value);
|
|
70
|
+
}
|
|
71
|
+
const response = yield* Effect.tryPromise({
|
|
72
|
+
try: (signal) => this.#fetch(url, {
|
|
73
|
+
...init,
|
|
74
|
+
headers,
|
|
75
|
+
signal
|
|
76
|
+
}),
|
|
77
|
+
catch: (cause) => new EdgeServiceError({
|
|
78
|
+
message: `Request to ${url.pathname} failed`,
|
|
79
|
+
cause
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const detail = yield* Effect.promise(() => response.text().catch(() => "")).pipe(Effect.map((text) => text.slice(0, ERROR_BODY_LIMIT)));
|
|
84
|
+
return yield* new EdgeServiceError({
|
|
85
|
+
message: `Service responded ${response.status}${detail ? `: ${detail}` : ""}`,
|
|
86
|
+
status: response.status
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const json = yield* Effect.tryPromise({
|
|
90
|
+
try: () => response.json(),
|
|
91
|
+
catch: (cause) => new EdgeServiceError({
|
|
92
|
+
message: "Failed to parse JSON response",
|
|
93
|
+
status: response.status,
|
|
94
|
+
cause
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
return yield* Schema.decodeUnknown(schema)(json).pipe(Effect.mapError((cause) => new EdgeServiceError({
|
|
98
|
+
message: "Response did not match expected schema",
|
|
99
|
+
status: response.status,
|
|
100
|
+
cause
|
|
101
|
+
})));
|
|
102
|
+
}).pipe(Effect.timeoutFail({
|
|
103
|
+
duration: this.#timeout,
|
|
104
|
+
onTimeout: () => new EdgeServiceError({ message: `Request to ${path} timed out` })
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/service/Image.ts
|
|
110
|
+
var Image_exports = /* @__PURE__ */ __exportAll({
|
|
111
|
+
DEFAULT_IMAGE_SERVICE_URL: () => DEFAULT_IMAGE_SERVICE_URL,
|
|
112
|
+
Result: () => Result,
|
|
113
|
+
thumbnail: () => thumbnail,
|
|
114
|
+
upload: () => upload
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Default Composer image-service base URL — the Cloudflare Worker used in
|
|
118
|
+
* production. Override per-environment via the client's `baseUrl`.
|
|
119
|
+
*/
|
|
120
|
+
var DEFAULT_IMAGE_SERVICE_URL = "https://image-service-main.dxos.workers.dev";
|
|
121
|
+
/** Hosted image returned by the service: a CDN `url` and its storage `id`. */
|
|
122
|
+
var Result = Schema.Struct({
|
|
123
|
+
id: Schema.optional(Schema.String),
|
|
124
|
+
url: Schema.String
|
|
125
|
+
});
|
|
126
|
+
/** Store an image and return its hosted CDN URL (POST `/upload`). */
|
|
127
|
+
var upload = (client, blob, opts) => uploadToPath(client, blob, "/upload", opts);
|
|
128
|
+
/** Store an image and create a thumbnail, returning its hosted CDN URL (POST `/thumbnail`). */
|
|
129
|
+
var thumbnail = (client, blob, opts) => uploadToPath(client, blob, "/thumbnail", opts);
|
|
130
|
+
var uploadToPath = (client, blob, path, opts) => {
|
|
131
|
+
const form = new FormData();
|
|
132
|
+
form.append(opts?.field ?? "file", blob, opts?.filename ?? "image");
|
|
133
|
+
return client.postForm(path, form, Result);
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
export { EdgeServiceClient, EdgeServiceError, Image_exports as Image };
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=service.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../src/service/edge-service.ts","../../src/service/Image.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// Lightweight client for EDGE-hosted HTTP services — intentionally free of the\n// heavy transitive dependencies (`@dxos/context`, `@dxos/protocols`) that\n// `BaseHttpClient` pulls in, so it bundles cleanly into browser / workerd. The\n// only runtime dependencies are `effect` and `@dxos/log`.\n\nimport * as Data from 'effect/Data';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\n// Matches EDGE_CLIENT_TAG_HEADER from @dxos/protocols. Duplicated here (as in\n// `cors-proxy`) to avoid importing the heavy protocols bundle.\nconst EDGE_CLIENT_TAG_HEADER = 'X-DXOS-Client-Tag';\n\nconst DEFAULT_TIMEOUT = Duration.seconds(15);\n\n/** Number of body characters retained for the error message on a non-2xx response. */\nconst ERROR_BODY_LIMIT = 512;\n\nexport type EdgeServiceClientOptions = {\n /** Base URL the service is hosted at; request paths resolve against it. */\n baseUrl: string;\n /** Tag included in the {@link EDGE_CLIENT_TAG_HEADER} header for metering. */\n clientTag?: string;\n /** Per-request timeout. */\n timeout?: Duration.DurationInput;\n /** Injectable for deterministic tests; defaults to `globalThis.fetch`. */\n fetch?: typeof globalThis.fetch;\n /** Auth seam invoked per request; unused by the image service today. */\n authHeaders?: () => Effect.Effect<Record<string, string>>;\n};\n\n/** Single failure type for every transport, status, and decode error. */\nexport class EdgeServiceError extends Data.TaggedError('EdgeServiceError')<{\n message: string;\n status?: number;\n cause?: unknown;\n}> {}\n\nexport class EdgeServiceClient {\n readonly #baseUrl: string;\n readonly #clientTag: string | undefined;\n readonly #timeout: Duration.Duration;\n readonly #fetch: typeof globalThis.fetch;\n readonly #authHeaders: (() => Effect.Effect<Record<string, string>>) | undefined;\n\n constructor(options: EdgeServiceClientOptions) {\n this.#baseUrl = options.baseUrl;\n this.#clientTag = options.clientTag;\n this.#timeout = Duration.decode(options.timeout ?? DEFAULT_TIMEOUT);\n // Bind so `fetch` keeps its expected `this` when injected as a method reference.\n const fetchImpl = options.fetch ?? globalThis.fetch;\n this.#fetch = fetchImpl.bind(globalThis);\n this.#authHeaders = options.authHeaders;\n }\n\n get baseUrl(): string {\n return this.#baseUrl;\n }\n\n /** POST a `FormData` (multipart) body and decode the JSON response. */\n postForm<A>(path: string, form: FormData, schema: Schema.Schema<A>): Effect.Effect<A, EdgeServiceError> {\n // The runtime sets `Content-Type: multipart/form-data` with the boundary;\n // setting it manually here would omit the boundary and break parsing.\n return this.#request(path, schema, { method: 'POST', body: form });\n }\n\n /** POST a JSON body and decode the JSON response. */\n postJson<A>(path: string, body: unknown, schema: Schema.Schema<A>): Effect.Effect<A, EdgeServiceError> {\n // Serialize inside the Effect so a circular/non-serializable body surfaces as\n // EdgeServiceError rather than throwing synchronously from the call site.\n return Effect.try({\n try: () => JSON.stringify(body),\n catch: (cause) => new EdgeServiceError({ message: 'Failed to serialize JSON request body', cause }),\n }).pipe(\n Effect.flatMap((json) =>\n this.#request(path, schema, {\n method: 'POST',\n body: json,\n headers: { 'Content-Type': 'application/json' },\n }),\n ),\n );\n }\n\n #request<A>(path: string, schema: Schema.Schema<A>, init: RequestInit): Effect.Effect<A, EdgeServiceError> {\n return Effect.gen(this, function* () {\n const url = new URL(path, this.#baseUrl);\n const headers = new Headers(init.headers ?? undefined);\n if (this.#clientTag) {\n headers.set(EDGE_CLIENT_TAG_HEADER, this.#clientTag);\n }\n if (this.#authHeaders) {\n const auth = yield* this.#authHeaders();\n for (const [key, value] of Object.entries(auth)) {\n headers.set(key, value);\n }\n }\n\n const response = yield* Effect.tryPromise({\n try: (signal) => this.#fetch(url, { ...init, headers, signal }),\n catch: (cause) => new EdgeServiceError({ message: `Request to ${url.pathname} failed`, cause }),\n });\n\n if (!response.ok) {\n // Body text aids debugging but may be large; truncate and never throw on read.\n const detail = yield* Effect.promise(() => response.text().catch(() => '')).pipe(\n Effect.map((text) => text.slice(0, ERROR_BODY_LIMIT)),\n );\n return yield* new EdgeServiceError({\n message: `Service responded ${response.status}${detail ? `: ${detail}` : ''}`,\n status: response.status,\n });\n }\n\n const json = yield* Effect.tryPromise({\n try: () => response.json(),\n catch: (cause) =>\n new EdgeServiceError({ message: 'Failed to parse JSON response', status: response.status, cause }),\n });\n\n return yield* Schema.decodeUnknown(schema)(json).pipe(\n Effect.mapError(\n (cause) =>\n new EdgeServiceError({ message: 'Response did not match expected schema', status: response.status, cause }),\n ),\n );\n }).pipe(\n Effect.timeoutFail({\n duration: this.#timeout,\n onTimeout: () => new EdgeServiceError({ message: `Request to ${path} timed out` }),\n }),\n );\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\n// Image service helpers built on the generic {@link EdgeServiceClient}. The\n// worker exposes two contract-identical routes (`/upload` and `/thumbnail`,\n// both returning `{ id, url }`); we mirror them so callers stay on whichever\n// route they already use.\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { type EdgeServiceClient, type EdgeServiceError } from './edge-service';\n\n/**\n * Default Composer image-service base URL — the Cloudflare Worker used in\n * production. Override per-environment via the client's `baseUrl`.\n */\n// TODO(burdon): Get from config.\nexport const DEFAULT_IMAGE_SERVICE_URL = 'https://image-service-main.dxos.workers.dev';\n\n/** Hosted image returned by the service: a CDN `url` and its storage `id`. */\nexport const Result = Schema.Struct({\n id: Schema.optional(Schema.String),\n url: Schema.String,\n});\nexport type Result = typeof Result.Type;\n\nexport type UploadOptions = {\n /** Multipart field name. */\n field?: string;\n /** Upload filename. */\n filename?: string;\n};\n\n/** Store an image and return its hosted CDN URL (POST `/upload`). */\nexport const upload = (\n client: EdgeServiceClient,\n blob: Blob,\n opts?: UploadOptions,\n): Effect.Effect<Result, EdgeServiceError> => uploadToPath(client, blob, '/upload', opts);\n\n/** Store an image and create a thumbnail, returning its hosted CDN URL (POST `/thumbnail`). */\nexport const thumbnail = (\n client: EdgeServiceClient,\n blob: Blob,\n opts?: UploadOptions,\n): Effect.Effect<Result, EdgeServiceError> => uploadToPath(client, blob, '/thumbnail', opts);\n\nconst uploadToPath = (\n client: EdgeServiceClient,\n blob: Blob,\n path: string,\n opts?: UploadOptions,\n): Effect.Effect<Result, EdgeServiceError> => {\n const form = new FormData();\n form.append(opts?.field ?? 'file', blob, opts?.filename ?? 'image');\n return client.postForm(path, form, Result);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,IAAM,yBAAyB;AAE/B,IAAM,kBAAkB,SAAS,QAAQ,EAAE;;AAG3C,IAAM,mBAAmB;;AAgBzB,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAIvE,CAAC;AAEJ,IAAa,oBAAb,MAA+B;CAC7B;CACA;CACA;CACA;CACA;CAEA,YAAY,SAAmC;EAC7C,KAAK,WAAW,QAAQ;EACxB,KAAK,aAAa,QAAQ;EAC1B,KAAK,WAAW,SAAS,OAAO,QAAQ,WAAW,eAAe;EAElE,MAAM,YAAY,QAAQ,SAAS,WAAW;EAC9C,KAAK,SAAS,UAAU,KAAK,UAAU;EACvC,KAAK,eAAe,QAAQ;CAC9B;CAEA,IAAI,UAAkB;EACpB,OAAO,KAAK;CACd;;CAGA,SAAY,MAAc,MAAgB,QAA8D;EAGtG,OAAO,KAAK,SAAS,MAAM,QAAQ;GAAE,QAAQ;GAAQ,MAAM;EAAK,CAAC;CACnE;;CAGA,SAAY,MAAc,MAAe,QAA8D;EAGrG,OAAO,OAAO,IAAI;GAChB,WAAW,KAAK,UAAU,IAAI;GAC9B,QAAQ,UAAU,IAAI,iBAAiB;IAAE,SAAS;IAAyC;GAAM,CAAC;EACpG,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,SACd,KAAK,SAAS,MAAM,QAAQ;GAC1B,QAAQ;GACR,MAAM;GACN,SAAS,EAAE,gBAAgB,mBAAmB;EAChD,CAAC,CACH,CACF;CACF;CAEA,SAAY,MAAc,QAA0B,MAAuD;EACzG,OAAO,OAAO,IAAI,MAAM,aAAa;GACnC,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,QAAQ;GACvC,MAAM,UAAU,IAAI,QAAQ,KAAK,WAAW,KAAA,CAAS;GACrD,IAAI,KAAK,YACP,QAAQ,IAAI,wBAAwB,KAAK,UAAU;GAErD,IAAI,KAAK,cAAc;IACrB,MAAM,OAAO,OAAO,KAAK,aAAa;IACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,GAC5C,QAAQ,IAAI,KAAK,KAAK;GAE1B;GAEA,MAAM,WAAW,OAAO,OAAO,WAAW;IACxC,MAAM,WAAW,KAAK,OAAO,KAAK;KAAE,GAAG;KAAM;KAAS;IAAO,CAAC;IAC9D,QAAQ,UAAU,IAAI,iBAAiB;KAAE,SAAS,cAAc,IAAI,SAAS;KAAU;IAAM,CAAC;GAChG,CAAC;GAED,IAAI,CAAC,SAAS,IAAI;IAEhB,MAAM,SAAS,OAAO,OAAO,cAAc,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,KAC1E,OAAO,KAAK,SAAS,KAAK,MAAM,GAAG,gBAAgB,CAAC,CACtD;IACA,OAAO,OAAO,IAAI,iBAAiB;KACjC,SAAS,qBAAqB,SAAS,SAAS,SAAS,KAAK,WAAW;KACzE,QAAQ,SAAS;IACnB,CAAC;GACH;GAEA,MAAM,OAAO,OAAO,OAAO,WAAW;IACpC,WAAW,SAAS,KAAK;IACzB,QAAQ,UACN,IAAI,iBAAiB;KAAE,SAAS;KAAiC,QAAQ,SAAS;KAAQ;IAAM,CAAC;GACrG,CAAC;GAED,OAAO,OAAO,OAAO,cAAc,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,KAC/C,OAAO,UACJ,UACC,IAAI,iBAAiB;IAAE,SAAS;IAA0C,QAAQ,SAAS;IAAQ;GAAM,CAAC,CAC9G,CACF;EACF,CAAC,CAAC,CAAC,KACD,OAAO,YAAY;GACjB,UAAU,KAAK;GACf,iBAAiB,IAAI,iBAAiB,EAAE,SAAS,cAAc,KAAK,YAAY,CAAC;EACnF,CAAC,CACH;CACF;AACF;;;;;;;;;;;;;ACrHA,IAAa,4BAA4B;;AAGzC,IAAa,SAAS,OAAO,OAAO;CAClC,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,KAAK,OAAO;AACd,CAAC;;AAWD,IAAa,UACX,QACA,MACA,SAC4C,aAAa,QAAQ,MAAM,WAAW,IAAI;;AAGxF,IAAa,aACX,QACA,MACA,SAC4C,aAAa,QAAQ,MAAM,cAAc,IAAI;AAE3F,IAAM,gBACJ,QACA,MACA,MACA,SAC4C;CAC5C,MAAM,OAAO,IAAI,SAAS;CAC1B,KAAK,OAAO,MAAM,SAAS,QAAQ,MAAM,MAAM,YAAY,OAAO;CAClE,OAAO,OAAO,SAAS,MAAM,MAAM,MAAM;AAC3C"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { i as protocol, r as WebSocketMuxer, s as toUint8Array } from "./chunk-edge-ws-muxer.mjs";
|
|
2
|
+
import { Trigger } from "@dxos/async";
|
|
3
|
+
import { log } from "@dxos/log";
|
|
4
|
+
import { buf } from "@dxos/protocols/buf";
|
|
5
|
+
import { MessageSchema, TextMessageSchema } from "@dxos/protocols/buf/dxos/edge/messenger_pb";
|
|
6
|
+
import WebSocket from "isomorphic-ws";
|
|
7
|
+
import { EdgeWebsocketProtocol } from "@dxos/protocols";
|
|
8
|
+
import http from "node:http";
|
|
9
|
+
//#region src/testing/test-server.ts
|
|
10
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/testing/test-server.ts";
|
|
11
|
+
var createTestServer = (responseHandler) => {
|
|
12
|
+
const server = http.createServer(responseHandler);
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
server.listen(0, () => {
|
|
15
|
+
const address = server.address();
|
|
16
|
+
resolve({
|
|
17
|
+
url: `http://localhost:${typeof address === "object" && address ? address.port : 0}`,
|
|
18
|
+
close: () => server.close()
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var responseHandler = (cb) => {
|
|
24
|
+
let attempt = 0;
|
|
25
|
+
return (req, res) => {
|
|
26
|
+
const data = cb(++attempt) ?? {};
|
|
27
|
+
if (data === false) {
|
|
28
|
+
log("simulating failure", { attempt }, {
|
|
29
|
+
"~LogMeta": "~LogMeta",
|
|
30
|
+
F: __dxlog_file$1,
|
|
31
|
+
L: 36,
|
|
32
|
+
S: void 0
|
|
33
|
+
});
|
|
34
|
+
res.statusCode = 500;
|
|
35
|
+
res.statusMessage = "Simulating failure";
|
|
36
|
+
res.end("");
|
|
37
|
+
} else {
|
|
38
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
39
|
+
res.end(JSON.stringify({
|
|
40
|
+
success: true,
|
|
41
|
+
data
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/testing/test-utils.ts
|
|
48
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/testing/test-utils.ts";
|
|
49
|
+
var DEFAULT_PORT = 8080;
|
|
50
|
+
var createTestEdgeWsServer = async (port = DEFAULT_PORT, params) => {
|
|
51
|
+
const wsServer = new WebSocket.Server({
|
|
52
|
+
port,
|
|
53
|
+
verifyClient: createConnectionDelayHandler(params),
|
|
54
|
+
handleProtocols: () => EdgeWebsocketProtocol.V1
|
|
55
|
+
});
|
|
56
|
+
let connection;
|
|
57
|
+
const messageSink = [];
|
|
58
|
+
const messageSourceLog = [];
|
|
59
|
+
const closeTrigger = new Trigger();
|
|
60
|
+
const sendResponseMessage = createResponseSender(() => connection.muxer);
|
|
61
|
+
wsServer.on("connection", (ws) => {
|
|
62
|
+
const muxer = new WebSocketMuxer(ws);
|
|
63
|
+
connection = {
|
|
64
|
+
ws,
|
|
65
|
+
muxer
|
|
66
|
+
};
|
|
67
|
+
ws.on("error", (err) => log.catch(err, void 0, {
|
|
68
|
+
"~LogMeta": "~LogMeta",
|
|
69
|
+
F: __dxlog_file,
|
|
70
|
+
L: 42,
|
|
71
|
+
S: void 0
|
|
72
|
+
}));
|
|
73
|
+
ws.on("message", async (data) => {
|
|
74
|
+
if (String(data) === "__ping__") {
|
|
75
|
+
ws.send("__pong__");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const message = muxer.receiveData(await toUint8Array(data));
|
|
79
|
+
if (!message) return;
|
|
80
|
+
const { request, requestPayload } = await decodePayload(message, params);
|
|
81
|
+
messageSourceLog.push(request.source);
|
|
82
|
+
if (params?.messageHandler) {
|
|
83
|
+
const responsePayload = await params.messageHandler(requestPayload);
|
|
84
|
+
if (responsePayload && connection) sendResponseMessage(request, responsePayload);
|
|
85
|
+
}
|
|
86
|
+
log("message", { payload: requestPayload }, {
|
|
87
|
+
"~LogMeta": "~LogMeta",
|
|
88
|
+
F: __dxlog_file,
|
|
89
|
+
L: 60,
|
|
90
|
+
S: void 0
|
|
91
|
+
});
|
|
92
|
+
messageSink.push(requestPayload);
|
|
93
|
+
});
|
|
94
|
+
ws.on("close", () => {
|
|
95
|
+
if (connection?.ws === ws) connection = void 0;
|
|
96
|
+
closeTrigger.wake();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
server: wsServer,
|
|
101
|
+
messageSink,
|
|
102
|
+
messageSourceLog,
|
|
103
|
+
endpoint: `ws://127.0.0.1:${port}`,
|
|
104
|
+
cleanup: () => wsServer.close(),
|
|
105
|
+
currentConnection: () => connection,
|
|
106
|
+
sendResponseMessage,
|
|
107
|
+
sendMessage: (msg) => {
|
|
108
|
+
return connection.muxer.send(msg);
|
|
109
|
+
},
|
|
110
|
+
closeConnection: () => {
|
|
111
|
+
closeTrigger.reset();
|
|
112
|
+
connection.ws.close(1011);
|
|
113
|
+
return closeTrigger.wait();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
var createConnectionDelayHandler = (params) => {
|
|
118
|
+
return (_, callback) => {
|
|
119
|
+
if (params?.admitConnection) {
|
|
120
|
+
log("delaying edge connection admission", void 0, {
|
|
121
|
+
"~LogMeta": "~LogMeta",
|
|
122
|
+
F: __dxlog_file,
|
|
123
|
+
L: 96,
|
|
124
|
+
S: void 0
|
|
125
|
+
});
|
|
126
|
+
params.admitConnection.wait().then(() => {
|
|
127
|
+
callback(true);
|
|
128
|
+
log("edge connection admitted", void 0, {
|
|
129
|
+
"~LogMeta": "~LogMeta",
|
|
130
|
+
F: __dxlog_file,
|
|
131
|
+
L: 99,
|
|
132
|
+
S: void 0
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
} else callback(true);
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
var createResponseSender = (connection) => {
|
|
139
|
+
return (request, responsePayload) => {
|
|
140
|
+
const recipient = request.source;
|
|
141
|
+
connection().send(buf.create(MessageSchema, {
|
|
142
|
+
source: {
|
|
143
|
+
identityDid: recipient.identityDid,
|
|
144
|
+
peerKey: recipient.peerKey
|
|
145
|
+
},
|
|
146
|
+
serviceId: request.serviceId,
|
|
147
|
+
payload: { value: responsePayload }
|
|
148
|
+
}));
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
var decodePayload = async (request, params) => {
|
|
152
|
+
return {
|
|
153
|
+
request,
|
|
154
|
+
requestPayload: params?.payloadDecoder ? params.payloadDecoder(request.payload.value) : protocol.getPayload(request, TextMessageSchema)
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
export { DEFAULT_PORT, createTestEdgeWsServer, createTestServer, responseHandler };
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../../src/testing/test-server.ts","../../src/testing/test-utils.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport http from 'node:http';\n\nimport { log } from '@dxos/log';\n\nexport type TestServer = {\n url: string;\n close: () => void;\n};\n\nexport type ResponseHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void;\n\nexport const createTestServer = (responseHandler: ResponseHandler) => {\n const server = http.createServer(responseHandler);\n\n return new Promise<TestServer>((resolve) => {\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n resolve({\n url: `http://localhost:${port}`,\n close: () => server.close(),\n });\n });\n });\n};\n\nexport const responseHandler = (cb: (attempt: number) => false | object): ResponseHandler => {\n let attempt = 0;\n return (req, res) => {\n const data = cb(++attempt) ?? {};\n if (data === false) {\n log('simulating failure', { attempt });\n res.statusCode = 500;\n res.statusMessage = 'Simulating failure';\n res.end('');\n } else {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true, data }));\n }\n };\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport WebSocket from 'isomorphic-ws';\n\nimport { Trigger } from '@dxos/async';\nimport { log } from '@dxos/log';\nimport { EdgeWebsocketProtocol } from '@dxos/protocols';\nimport { buf } from '@dxos/protocols/buf';\nimport { type Message, MessageSchema, TextMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\n\nimport { protocol } from '../defs';\nimport { WebSocketMuxer } from '../edge-ws-muxer';\nimport { toUint8Array } from '../protocol';\n\nexport const DEFAULT_PORT = 8080;\n\ntype TestEdgeWsServerProps = {\n admitConnection?: Trigger;\n payloadDecoder?: (payload: Uint8Array) => any;\n messageHandler?: (payload: any) => Promise<Uint8Array | undefined>;\n};\n\nexport const createTestEdgeWsServer = async (port = DEFAULT_PORT, params?: TestEdgeWsServerProps) => {\n const wsServer = new WebSocket.Server({\n port,\n verifyClient: createConnectionDelayHandler(params),\n handleProtocols: () => EdgeWebsocketProtocol.V1,\n });\n\n let connection: { ws: WebSocket; muxer: WebSocketMuxer } | undefined;\n\n const messageSink: any[] = [];\n const messageSourceLog: any[] = [];\n const closeTrigger = new Trigger();\n const sendResponseMessage = createResponseSender(() => connection!.muxer);\n\n wsServer.on('connection', (ws: WebSocket) => {\n const muxer = new WebSocketMuxer(ws);\n connection = { ws, muxer };\n ws.on('error', (err: Error) => log.catch(err));\n ws.on('message', async (data: any) => {\n if (String(data) === '__ping__') {\n ws.send('__pong__');\n return;\n }\n const message = muxer.receiveData(await toUint8Array(data));\n if (!message) {\n return;\n }\n const { request, requestPayload } = await decodePayload(message, params);\n messageSourceLog.push(request.source);\n if (params?.messageHandler) {\n const responsePayload = await params.messageHandler(requestPayload);\n if (responsePayload && connection) {\n sendResponseMessage(request, responsePayload);\n }\n }\n log('message', { payload: requestPayload });\n messageSink.push(requestPayload);\n });\n\n ws.on('close', () => {\n // During a reconnect the new connection may be admitted before the old\n // socket's close event fires; only clear if this socket is still current.\n if (connection?.ws === ws) {\n connection = undefined;\n }\n closeTrigger.wake();\n });\n });\n\n return {\n server: wsServer,\n messageSink,\n messageSourceLog,\n endpoint: `ws://127.0.0.1:${port}`,\n cleanup: () => wsServer.close(),\n currentConnection: () => connection,\n sendResponseMessage,\n sendMessage: (msg: Message) => {\n return connection!.muxer.send(msg);\n },\n closeConnection: () => {\n closeTrigger.reset();\n connection!.ws.close(1011);\n return closeTrigger.wait();\n },\n };\n};\n\nconst createConnectionDelayHandler = (params: TestEdgeWsServerProps | undefined) => {\n return (_: any, callback: (admit: boolean) => void) => {\n if (params?.admitConnection) {\n log('delaying edge connection admission');\n void params.admitConnection.wait().then(() => {\n callback(true);\n log('edge connection admitted');\n });\n } else {\n callback(true);\n }\n };\n};\n\nconst createResponseSender = (connection: () => WebSocketMuxer) => {\n return (request: Message, responsePayload: Uint8Array) => {\n const recipient = request.source!;\n void connection().send(\n buf.create(MessageSchema, {\n source: {\n identityDid: recipient.identityDid,\n peerKey: recipient.peerKey,\n },\n serviceId: request.serviceId!,\n payload: { value: responsePayload },\n }),\n );\n };\n};\n\nconst decodePayload = async (request: Message, params: TestEdgeWsServerProps | undefined) => {\n const requestPayload = params?.payloadDecoder\n ? params.payloadDecoder(request.payload!.value!)\n : protocol.getPayload(request, TextMessageSchema);\n return { request, requestPayload };\n};\n"],"mappings":"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"}
|