@dxos/edge-client 0.8.3 → 0.8.4-main.1c7ec43d41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/lib/{browser/chunk-VHS3XEIX.mjs → neutral/chunk-ZIQ5T3A7.mjs} +20 -50
  2. package/dist/lib/{browser/chunk-VHS3XEIX.mjs.map → neutral/chunk-ZIQ5T3A7.mjs.map} +3 -3
  3. package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
  4. package/dist/lib/neutral/index.mjs +1189 -0
  5. package/dist/lib/neutral/index.mjs.map +7 -0
  6. package/dist/lib/neutral/meta.json +1 -0
  7. package/dist/lib/{browser → neutral}/testing/index.mjs +53 -33
  8. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  9. package/dist/types/src/auth.d.ts.map +1 -1
  10. package/dist/types/src/edge-client.d.ts +18 -15
  11. package/dist/types/src/edge-client.d.ts.map +1 -1
  12. package/dist/types/src/edge-http-client.d.ts +98 -37
  13. package/dist/types/src/edge-http-client.d.ts.map +1 -1
  14. package/dist/types/src/edge-http-client.test.d.ts +2 -0
  15. package/dist/types/src/edge-http-client.test.d.ts.map +1 -0
  16. package/dist/types/src/edge-identity.d.ts.map +1 -1
  17. package/dist/types/src/edge-ws-connection.d.ts +21 -0
  18. package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
  19. package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
  20. package/dist/types/src/errors.d.ts.map +1 -1
  21. package/dist/types/src/http-client.d.ts +25 -0
  22. package/dist/types/src/http-client.d.ts.map +1 -0
  23. package/dist/types/src/http-client.test.d.ts +2 -0
  24. package/dist/types/src/http-client.test.d.ts.map +1 -0
  25. package/dist/types/src/index.d.ts +4 -3
  26. package/dist/types/src/index.d.ts.map +1 -1
  27. package/dist/types/src/protocol.d.ts +1 -1
  28. package/dist/types/src/protocol.d.ts.map +1 -1
  29. package/dist/types/src/testing/index.d.ts +1 -0
  30. package/dist/types/src/testing/index.d.ts.map +1 -1
  31. package/dist/types/src/testing/test-server.d.ts +9 -0
  32. package/dist/types/src/testing/test-server.d.ts.map +1 -0
  33. package/dist/types/src/testing/test-utils.d.ts +3 -3
  34. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  35. package/dist/types/src/utils.d.ts +1 -1
  36. package/dist/types/src/utils.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +33 -29
  39. package/src/edge-client.test.ts +20 -15
  40. package/src/edge-client.ts +90 -43
  41. package/src/edge-http-client.test.ts +23 -0
  42. package/src/edge-http-client.ts +502 -164
  43. package/src/edge-ws-connection.ts +131 -9
  44. package/src/edge-ws-muxer.ts +1 -1
  45. package/src/http-client.test.ts +58 -0
  46. package/src/http-client.ts +77 -0
  47. package/src/index.ts +4 -3
  48. package/src/testing/index.ts +1 -0
  49. package/src/testing/test-server.ts +45 -0
  50. package/src/testing/test-utils.ts +9 -9
  51. package/src/websocket.test.ts +1 -1
  52. package/dist/lib/browser/index.mjs +0 -1034
  53. package/dist/lib/browser/index.mjs.map +0 -7
  54. package/dist/lib/browser/meta.json +0 -1
  55. package/dist/lib/browser/testing/index.mjs.map +0 -7
  56. package/dist/lib/node/chunk-XNHBUTNB.cjs +0 -317
  57. package/dist/lib/node/chunk-XNHBUTNB.cjs.map +0 -7
  58. package/dist/lib/node/edge-ws-muxer.cjs +0 -33
  59. package/dist/lib/node/edge-ws-muxer.cjs.map +0 -7
  60. package/dist/lib/node/index.cjs +0 -1060
  61. package/dist/lib/node/index.cjs.map +0 -7
  62. package/dist/lib/node/meta.json +0 -1
  63. package/dist/lib/node/testing/index.cjs +0 -169
  64. package/dist/lib/node/testing/index.cjs.map +0 -7
  65. package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs +0 -299
  66. package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs.map +0 -7
  67. package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
  68. package/dist/lib/node-esm/edge-ws-muxer.mjs.map +0 -7
  69. package/dist/lib/node-esm/index.mjs +0 -1035
  70. package/dist/lib/node-esm/index.mjs.map +0 -7
  71. package/dist/lib/node-esm/meta.json +0 -1
  72. package/dist/lib/node-esm/testing/index.mjs +0 -141
  73. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  74. /package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs.map +0 -0
@@ -0,0 +1,1189 @@
1
+ import {
2
+ CLOUDFLARE_MESSAGE_MAX_BYTES,
3
+ CLOUDFLARE_RPC_MAX_BYTES,
4
+ Protocol,
5
+ WebSocketMuxer,
6
+ getTypename,
7
+ protocol,
8
+ toUint8Array
9
+ } from "./chunk-ZIQ5T3A7.mjs";
10
+
11
+ // src/index.ts
12
+ export * from "@dxos/protocols/buf/dxos/edge/messenger_pb";
13
+
14
+ // src/auth.ts
15
+ import { createCredential, signPresentation } from "@dxos/credentials";
16
+ import { invariant } from "@dxos/invariant";
17
+ import { Keyring } from "@dxos/keyring";
18
+ import { PublicKey } from "@dxos/keys";
19
+ var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/auth.ts";
20
+ var createDeviceEdgeIdentity = async (signer, key) => {
21
+ return {
22
+ identityKey: key.toHex(),
23
+ peerKey: key.toHex(),
24
+ presentCredentials: async ({ challenge }) => {
25
+ return signPresentation({
26
+ presentation: {
27
+ credentials: [
28
+ // Verifier requires at least one credential in the presentation to establish the subject.
29
+ await createCredential({
30
+ assertion: {
31
+ "@type": "dxos.halo.credentials.Auth"
32
+ },
33
+ issuer: key,
34
+ subject: key,
35
+ signer
36
+ })
37
+ ]
38
+ },
39
+ signer,
40
+ signerKey: key,
41
+ nonce: challenge
42
+ });
43
+ }
44
+ };
45
+ };
46
+ var createChainEdgeIdentity = async (signer, identityKey, peerKey, chain, credentials) => {
47
+ const credentialsToSign = credentials.length > 0 ? credentials : [
48
+ await createCredential({
49
+ assertion: {
50
+ "@type": "dxos.halo.credentials.Auth"
51
+ },
52
+ issuer: identityKey,
53
+ subject: identityKey,
54
+ signer,
55
+ chain,
56
+ signingKey: peerKey
57
+ })
58
+ ];
59
+ return {
60
+ identityKey: identityKey.toHex(),
61
+ peerKey: peerKey.toHex(),
62
+ presentCredentials: async ({ challenge }) => {
63
+ invariant(chain, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 56, S: void 0, A: ["chain", ""] });
64
+ return signPresentation({
65
+ presentation: {
66
+ credentials: credentialsToSign
67
+ },
68
+ signer,
69
+ nonce: challenge,
70
+ signerKey: peerKey,
71
+ chain
72
+ });
73
+ }
74
+ };
75
+ };
76
+ var createEphemeralEdgeIdentity = async () => {
77
+ const keyring = new Keyring();
78
+ const key = await keyring.createKey();
79
+ return createDeviceEdgeIdentity(keyring, key);
80
+ };
81
+ var createTestHaloEdgeIdentity = async (signer, identityKey, deviceKey) => {
82
+ const deviceAdmission = await createCredential({
83
+ assertion: {
84
+ "@type": "dxos.halo.credentials.AuthorizedDevice",
85
+ deviceKey,
86
+ identityKey
87
+ },
88
+ issuer: identityKey,
89
+ subject: deviceKey,
90
+ signer
91
+ });
92
+ return createChainEdgeIdentity(signer, identityKey, deviceKey, {
93
+ credential: deviceAdmission
94
+ }, [
95
+ await createCredential({
96
+ assertion: {
97
+ "@type": "dxos.halo.credentials.Auth"
98
+ },
99
+ issuer: identityKey,
100
+ subject: identityKey,
101
+ signer
102
+ })
103
+ ]);
104
+ };
105
+ var createStubEdgeIdentity = () => {
106
+ const identityKey = PublicKey.random();
107
+ const deviceKey = PublicKey.random();
108
+ return {
109
+ identityKey: identityKey.toHex(),
110
+ peerKey: deviceKey.toHex(),
111
+ presentCredentials: async () => {
112
+ throw new Error("Stub identity does not support authentication.");
113
+ }
114
+ };
115
+ };
116
+
117
+ // src/edge-client.ts
118
+ import { Event, PersistentLifecycle, Trigger, TriggerState, scheduleMicroTask, scheduleTaskInterval as scheduleTaskInterval2 } from "@dxos/async";
119
+ import { TRACE_SPAN_ATTRIBUTE } from "@dxos/context";
120
+ import { Resource as Resource2 } from "@dxos/context";
121
+ import { log as log2, logInfo as logInfo2 } from "@dxos/log";
122
+ import { EdgeStatus } from "@dxos/protocols/proto/dxos/client/services";
123
+
124
+ // src/edge-identity.ts
125
+ import { invariant as invariant2 } from "@dxos/invariant";
126
+ import { schema } from "@dxos/protocols/proto";
127
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-identity.ts";
128
+ var handleAuthChallenge = async (failedResponse, identity) => {
129
+ invariant2(failedResponse.status === 401, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 7, S: void 0, A: ["failedResponse.status === 401", ""] });
130
+ const headerValue = failedResponse.headers.get("Www-Authenticate");
131
+ invariant2(headerValue?.startsWith("VerifiablePresentation challenge="), void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 9, S: void 0, A: ["headerValue?.startsWith('VerifiablePresentation challenge=')", ""] });
132
+ const challenge = headerValue?.slice("VerifiablePresentation challenge=".length);
133
+ invariant2(challenge, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 11, S: void 0, A: ["challenge", ""] });
134
+ const presentation = await identity.presentCredentials({
135
+ challenge: Buffer.from(challenge, "base64")
136
+ });
137
+ return schema.getCodecForType("dxos.halo.credentials.Presentation").encode(presentation);
138
+ };
139
+
140
+ // src/edge-ws-connection.ts
141
+ import WebSocket from "isomorphic-ws";
142
+ import { scheduleTask, scheduleTaskInterval } from "@dxos/async";
143
+ import { Context, Resource } from "@dxos/context";
144
+ import { invariant as invariant3 } from "@dxos/invariant";
145
+ import { log, logInfo } from "@dxos/log";
146
+ import { EdgeWebsocketProtocol } from "@dxos/protocols";
147
+ import { buf } from "@dxos/protocols/buf";
148
+ import { MessageSchema } from "@dxos/protocols/buf/dxos/edge/messenger_pb";
149
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-ws-connection.ts";
150
+ function _ts_decorate(decorators, target, key, desc) {
151
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
152
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
153
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
154
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
155
+ }
156
+ var SIGNAL_KEEPALIVE_INTERVAL = 4e3;
157
+ var SIGNAL_KEEPALIVE_TIMEOUT = 12e3;
158
+ var EdgeWsConnection = class extends Resource {
159
+ _identity;
160
+ _connectionInfo;
161
+ _callbacks;
162
+ _inactivityTimeoutCtx;
163
+ _ws;
164
+ _wsMuxer;
165
+ _lastReceivedMessageTimestamp = Date.now();
166
+ _openTimestamp;
167
+ // Latency tracking.
168
+ _pingTimestamp;
169
+ _rtt = 0;
170
+ // Rate tracking with sliding window.
171
+ _uploadRate = 0;
172
+ _downloadRate = 0;
173
+ _rateWindow = 1e4;
174
+ _rateUpdateInterval = 1e3;
175
+ _bytesSamples = [];
176
+ _messagesSent = 0;
177
+ _messagesReceived = 0;
178
+ constructor(_identity, _connectionInfo, _callbacks) {
179
+ super(), this._identity = _identity, this._connectionInfo = _connectionInfo, this._callbacks = _callbacks;
180
+ }
181
+ get info() {
182
+ return {
183
+ open: this.isOpen,
184
+ identity: this._identity.identityKey,
185
+ device: this._identity.peerKey
186
+ };
187
+ }
188
+ get rtt() {
189
+ return this._rtt;
190
+ }
191
+ get uptime() {
192
+ return this._openTimestamp ? (Date.now() - this._openTimestamp) / 1e3 : 0;
193
+ }
194
+ get uploadRate() {
195
+ return this._uploadRate;
196
+ }
197
+ get downloadRate() {
198
+ return this._downloadRate;
199
+ }
200
+ get messagesSent() {
201
+ return this._messagesSent;
202
+ }
203
+ get messagesReceived() {
204
+ return this._messagesReceived;
205
+ }
206
+ send(message) {
207
+ invariant3(this._ws, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 72, S: this, A: ["this._ws", ""] });
208
+ invariant3(this._wsMuxer, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 73, S: this, A: ["this._wsMuxer", ""] });
209
+ log("sending...", {
210
+ peerKey: this._identity.peerKey,
211
+ payload: protocol.getPayloadType(message)
212
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 74, S: this });
213
+ this._messagesSent++;
214
+ if (this._ws?.protocol.includes(EdgeWebsocketProtocol.V0)) {
215
+ const binary = buf.toBinary(MessageSchema, message);
216
+ if (binary.length > CLOUDFLARE_MESSAGE_MAX_BYTES) {
217
+ log.error("Message dropped because it was too large (>1MB).", {
218
+ byteLength: binary.byteLength,
219
+ serviceId: message.serviceId,
220
+ payload: protocol.getPayloadType(message)
221
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 82, S: this });
222
+ return;
223
+ }
224
+ this._recordBytes(binary.byteLength, 0);
225
+ this._ws.send(binary);
226
+ } else {
227
+ const binary = buf.toBinary(MessageSchema, message);
228
+ this._recordBytes(binary.byteLength, 0);
229
+ this._wsMuxer.send(message).catch((e) => log.catch(e, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 95, S: this }));
230
+ }
231
+ }
232
+ async _open() {
233
+ const baseProtocols = [
234
+ ...Object.values(EdgeWebsocketProtocol)
235
+ ];
236
+ this._ws = new WebSocket(this._connectionInfo.url.toString(), this._connectionInfo.protocolHeader ? [
237
+ ...baseProtocols,
238
+ this._connectionInfo.protocolHeader
239
+ ] : [
240
+ ...baseProtocols
241
+ ], this._connectionInfo.headers ? {
242
+ headers: this._connectionInfo.headers
243
+ } : void 0);
244
+ const muxer = new WebSocketMuxer(this._ws);
245
+ this._wsMuxer = muxer;
246
+ this._ws.onopen = () => {
247
+ if (this.isOpen) {
248
+ log("connected", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 114, S: this });
249
+ this._openTimestamp = Date.now();
250
+ this._callbacks.onConnected();
251
+ this._scheduleHeartbeats();
252
+ this._scheduleRateCalculation();
253
+ } else {
254
+ log.verbose("connected after becoming inactive", {
255
+ currentIdentity: this._identity
256
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 120, S: this });
257
+ }
258
+ };
259
+ this._ws.onclose = (event) => {
260
+ if (this.isOpen) {
261
+ log.warn("server disconnected", {
262
+ code: event.code,
263
+ reason: event.reason
264
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 127, S: this });
265
+ this._callbacks.onRestartRequired();
266
+ muxer.destroy();
267
+ }
268
+ };
269
+ this._ws.onerror = (event) => {
270
+ if (this.isOpen) {
271
+ log.warn("edge connection socket error", {
272
+ error: event.error,
273
+ info: event.message
274
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 137, S: this });
275
+ this._callbacks.onRestartRequired();
276
+ } else {
277
+ log.verbose("error ignored on closed connection", {
278
+ error: event.error
279
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 143, S: this });
280
+ }
281
+ };
282
+ this._ws.onmessage = async (event) => {
283
+ if (!this.isOpen) {
284
+ log.verbose("message ignored on closed connection", {
285
+ event: event.type
286
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 152, S: this });
287
+ return;
288
+ }
289
+ this._lastReceivedMessageTimestamp = Date.now();
290
+ if (event.data === "__pong__") {
291
+ if (this._pingTimestamp) {
292
+ this._rtt = Date.now() - this._pingTimestamp;
293
+ this._pingTimestamp = void 0;
294
+ }
295
+ this._rescheduleHeartbeatTimeout();
296
+ return;
297
+ }
298
+ const bytes = await toUint8Array(event.data);
299
+ this._recordBytes(0, bytes.byteLength);
300
+ if (!this.isOpen) {
301
+ return;
302
+ }
303
+ this._messagesReceived++;
304
+ const message = this._ws?.protocol?.includes(EdgeWebsocketProtocol.V0) ? buf.fromBinary(MessageSchema, bytes) : muxer.receiveData(bytes);
305
+ if (message) {
306
+ log("received", {
307
+ from: message.source,
308
+ payload: protocol.getPayloadType(message)
309
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 175, S: this });
310
+ this._callbacks.onMessage(message);
311
+ }
312
+ };
313
+ }
314
+ async _close() {
315
+ void this._inactivityTimeoutCtx?.dispose().catch(() => {
316
+ });
317
+ try {
318
+ this._ws?.close();
319
+ this._ws = void 0;
320
+ this._wsMuxer?.destroy();
321
+ this._wsMuxer = void 0;
322
+ } catch (err) {
323
+ if (err instanceof Error && err.message.includes("WebSocket is closed before the connection is established.")) {
324
+ return;
325
+ }
326
+ log.warn("error closing websocket", {
327
+ err
328
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 194, S: this });
329
+ }
330
+ }
331
+ _scheduleHeartbeats() {
332
+ invariant3(this._ws, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 200, S: this, A: ["this._ws", ""] });
333
+ scheduleTaskInterval(this._ctx, async () => {
334
+ this._pingTimestamp = Date.now();
335
+ this._ws?.send("__ping__");
336
+ }, SIGNAL_KEEPALIVE_INTERVAL);
337
+ this._pingTimestamp = Date.now();
338
+ this._ws.send("__ping__");
339
+ this._rescheduleHeartbeatTimeout();
340
+ }
341
+ _rescheduleHeartbeatTimeout() {
342
+ if (!this.isOpen) {
343
+ return;
344
+ }
345
+ void this._inactivityTimeoutCtx?.dispose();
346
+ this._inactivityTimeoutCtx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 216 });
347
+ scheduleTask(this._inactivityTimeoutCtx, () => {
348
+ if (this.isOpen) {
349
+ if (Date.now() - this._lastReceivedMessageTimestamp > SIGNAL_KEEPALIVE_TIMEOUT) {
350
+ log.warn("restart due to inactivity timeout", {
351
+ lastReceivedMessageTimestamp: this._lastReceivedMessageTimestamp
352
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 220, S: this });
353
+ this._callbacks.onRestartRequired();
354
+ } else {
355
+ this._rescheduleHeartbeatTimeout();
356
+ }
357
+ }
358
+ }, SIGNAL_KEEPALIVE_TIMEOUT);
359
+ }
360
+ _recordBytes(sent, received) {
361
+ const now = Date.now();
362
+ const currentSecond = Math.floor(now / 1e3) * 1e3;
363
+ const existingSample = this._bytesSamples.find((s) => Math.floor(s.timestamp / 1e3) * 1e3 === currentSecond);
364
+ if (existingSample) {
365
+ existingSample.sent += sent;
366
+ existingSample.received += received;
367
+ } else {
368
+ this._bytesSamples.push({
369
+ timestamp: now,
370
+ sent,
371
+ received
372
+ });
373
+ }
374
+ }
375
+ _scheduleRateCalculation() {
376
+ scheduleTaskInterval(this._ctx, async () => {
377
+ this._calculateRates();
378
+ }, this._rateUpdateInterval);
379
+ this._calculateRates();
380
+ }
381
+ _calculateRates() {
382
+ const now = Date.now();
383
+ const cutoff = now - this._rateWindow;
384
+ this._bytesSamples = this._bytesSamples.filter((s) => s.timestamp > cutoff);
385
+ if (this._bytesSamples.length === 0) {
386
+ this._uploadRate = 0;
387
+ this._downloadRate = 0;
388
+ return;
389
+ }
390
+ let totalSent = 0;
391
+ let totalReceived = 0;
392
+ const oldestTimestamp = Math.min(...this._bytesSamples.map((s) => s.timestamp));
393
+ const timeSpan = (now - oldestTimestamp) / 1e3;
394
+ for (const sample of this._bytesSamples) {
395
+ totalSent += sample.sent;
396
+ totalReceived += sample.received;
397
+ }
398
+ this._uploadRate = timeSpan > 0 ? Math.round(totalSent / timeSpan) : 0;
399
+ this._downloadRate = timeSpan > 0 ? Math.round(totalReceived / timeSpan) : 0;
400
+ }
401
+ };
402
+ _ts_decorate([
403
+ logInfo
404
+ ], EdgeWsConnection.prototype, "info", null);
405
+
406
+ // src/errors.ts
407
+ var EdgeConnectionClosedError = class extends Error {
408
+ constructor() {
409
+ super("Edge connection closed.");
410
+ }
411
+ };
412
+ var EdgeIdentityChangedError = class extends Error {
413
+ constructor() {
414
+ super("Edge identity changed.");
415
+ }
416
+ };
417
+
418
+ // src/utils.ts
419
+ var getEdgeUrlWithProtocol = (baseUrl, protocol2) => {
420
+ const isSecure = baseUrl.startsWith("https") || baseUrl.startsWith("wss");
421
+ const url = new URL(baseUrl);
422
+ url.protocol = protocol2 + (isSecure ? "s" : "");
423
+ return url.toString();
424
+ };
425
+
426
+ // src/edge-client.ts
427
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-client.ts";
428
+ function _ts_decorate2(decorators, target, key, desc) {
429
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
430
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
431
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
432
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
433
+ }
434
+ var DEFAULT_TIMEOUT = 1e4;
435
+ var STATUS_REFRESH_INTERVAL = 1e3;
436
+ var EdgeClient = class extends Resource2 {
437
+ _identity;
438
+ _config;
439
+ statusChanged = new Event();
440
+ _persistentLifecycle = new PersistentLifecycle({
441
+ start: async () => this._connect(),
442
+ stop: async (state) => this._disconnect(state)
443
+ });
444
+ _messageListeners = /* @__PURE__ */ new Set();
445
+ _reconnectListeners = /* @__PURE__ */ new Set();
446
+ _baseWsUrl;
447
+ _baseHttpUrl;
448
+ _currentConnection = void 0;
449
+ _ready = new Trigger();
450
+ constructor(_identity, _config) {
451
+ super(), this._identity = _identity, this._config = _config;
452
+ this._baseWsUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, "ws");
453
+ this._baseHttpUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, "http");
454
+ }
455
+ get info() {
456
+ return {
457
+ open: this.isOpen,
458
+ status: this.status,
459
+ identity: this._identity.identityKey,
460
+ device: this._identity.peerKey
461
+ };
462
+ }
463
+ get status() {
464
+ return {
465
+ state: Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED ? EdgeStatus.ConnectionState.CONNECTED : EdgeStatus.ConnectionState.NOT_CONNECTED,
466
+ uptime: this._currentConnection?.uptime ?? 0,
467
+ rtt: this._currentConnection?.rtt ?? 0,
468
+ rateBytesUp: this._currentConnection?.uploadRate ?? 0,
469
+ rateBytesDown: this._currentConnection?.downloadRate ?? 0,
470
+ messagesSent: this._currentConnection?.messagesSent ?? 0,
471
+ messagesReceived: this._currentConnection?.messagesReceived ?? 0
472
+ };
473
+ }
474
+ get identityKey() {
475
+ return this._identity.identityKey;
476
+ }
477
+ get peerKey() {
478
+ return this._identity.peerKey;
479
+ }
480
+ setIdentity(identity) {
481
+ if (identity.identityKey !== this._identity.identityKey || identity.peerKey !== this._identity.peerKey) {
482
+ log2("Edge identity changed", {
483
+ identity,
484
+ oldIdentity: this._identity
485
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 74, S: this });
486
+ this._identity = identity;
487
+ this._closeCurrentConnection(new EdgeIdentityChangedError());
488
+ void this._persistentLifecycle.scheduleRestart();
489
+ }
490
+ }
491
+ /**
492
+ * Send message.
493
+ * NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
494
+ */
495
+ async send(ctx, message) {
496
+ if (this._ready.state !== TriggerState.RESOLVED) {
497
+ log2("waiting for websocket", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 88, S: this });
498
+ await this._ready.wait({
499
+ timeout: this._config.timeout ?? DEFAULT_TIMEOUT
500
+ });
501
+ }
502
+ if (!this._currentConnection) {
503
+ throw new EdgeConnectionClosedError();
504
+ }
505
+ if (message.source && (message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)) {
506
+ throw new EdgeIdentityChangedError();
507
+ }
508
+ const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
509
+ if (traceCtx) {
510
+ message.traceContext = {
511
+ $typeName: "dxos.edge.messenger.TraceContext",
512
+ traceparent: traceCtx.traceparent,
513
+ tracestate: traceCtx.tracestate
514
+ };
515
+ }
516
+ this._currentConnection.send(message);
517
+ }
518
+ onMessage(listener) {
519
+ this._messageListeners.add(listener);
520
+ return () => this._messageListeners.delete(listener);
521
+ }
522
+ onReconnected(listener) {
523
+ this._reconnectListeners.add(listener);
524
+ if (this._ready.state === TriggerState.RESOLVED) {
525
+ scheduleMicroTask(this._ctx, () => {
526
+ if (this._reconnectListeners.has(listener)) {
527
+ try {
528
+ listener();
529
+ } catch (error) {
530
+ log2.catch(error, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 123, S: this });
531
+ }
532
+ }
533
+ });
534
+ }
535
+ return () => this._reconnectListeners.delete(listener);
536
+ }
537
+ /**
538
+ * Open connection to messaging service.
539
+ */
540
+ async _open() {
541
+ log2("opening...", {
542
+ info: this.info
543
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 133, S: this });
544
+ this._persistentLifecycle.open().catch((err) => {
545
+ log2.warn("Error while opening connection", {
546
+ err
547
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 137, S: this });
548
+ });
549
+ scheduleTaskInterval2(this._ctx, async () => {
550
+ if (!this._currentConnection) {
551
+ return;
552
+ }
553
+ this.statusChanged.emit(this.status);
554
+ }, STATUS_REFRESH_INTERVAL);
555
+ }
556
+ /**
557
+ * Close connection and free resources.
558
+ */
559
+ async _close() {
560
+ log2("closing...", {
561
+ peerKey: this._identity.peerKey
562
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 152, S: this });
563
+ this._closeCurrentConnection();
564
+ await this._persistentLifecycle.close();
565
+ }
566
+ async _connect() {
567
+ if (this._ctx.disposed) {
568
+ return void 0;
569
+ }
570
+ const identity = this._identity;
571
+ const path = `/ws/${identity.identityKey}/${identity.peerKey}`;
572
+ const protocolHeader = this._config.disableAuth ? void 0 : await this._createAuthHeader(path);
573
+ if (this._identity !== identity) {
574
+ log2("identity changed during auth header request", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 166, S: this });
575
+ return void 0;
576
+ }
577
+ const restartRequired = new Trigger();
578
+ const url = new URL(path, this._baseWsUrl);
579
+ log2("Opening websocket", {
580
+ url: url.toString(),
581
+ protocolHeader
582
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 171, S: this });
583
+ const connection = new EdgeWsConnection(identity, {
584
+ url,
585
+ protocolHeader,
586
+ headers: this._config.clientTag ? {
587
+ "X-DXOS-Client-Tag": this._config.clientTag
588
+ } : void 0
589
+ }, {
590
+ onConnected: () => {
591
+ if (this._isActive(connection)) {
592
+ this._ready.wake();
593
+ this._notifyReconnected();
594
+ } else {
595
+ log2.verbose("connected callback ignored, because connection is not active", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 187, S: this });
596
+ }
597
+ },
598
+ onRestartRequired: () => {
599
+ if (this._isActive(connection)) {
600
+ this._closeCurrentConnection();
601
+ void this._persistentLifecycle.scheduleRestart();
602
+ } else {
603
+ log2.verbose("restart requested by inactive connection", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 195, S: this });
604
+ }
605
+ restartRequired.wake();
606
+ },
607
+ onMessage: (message) => {
608
+ if (this._isActive(connection)) {
609
+ this._notifyMessageReceived(message);
610
+ } else {
611
+ log2.verbose("ignored a message on inactive connection", {
612
+ from: message.source,
613
+ type: message.payload?.typeUrl
614
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 203, S: this });
615
+ }
616
+ }
617
+ });
618
+ this._currentConnection = connection;
619
+ await connection.open();
620
+ await Promise.race([
621
+ this._ready.wait({
622
+ timeout: this._config.timeout ?? DEFAULT_TIMEOUT
623
+ }),
624
+ restartRequired
625
+ ]);
626
+ return connection;
627
+ }
628
+ async _disconnect(state) {
629
+ await state.close();
630
+ this.statusChanged.emit(this.status);
631
+ }
632
+ _closeCurrentConnection(error = new EdgeConnectionClosedError()) {
633
+ this._currentConnection = void 0;
634
+ this._ready.throw(error);
635
+ this._ready.reset();
636
+ this.statusChanged.emit(this.status);
637
+ }
638
+ _notifyReconnected() {
639
+ this.statusChanged.emit(this.status);
640
+ for (const listener of this._reconnectListeners) {
641
+ try {
642
+ listener();
643
+ } catch (err) {
644
+ log2.error("ws reconnect listener failed", {
645
+ err
646
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 238, S: this });
647
+ }
648
+ }
649
+ }
650
+ _notifyMessageReceived(message) {
651
+ for (const listener of this._messageListeners) {
652
+ try {
653
+ listener(message);
654
+ } catch (err) {
655
+ log2.error("ws incoming message processing failed", {
656
+ err,
657
+ payload: protocol.getPayloadType(message)
658
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 249, S: this });
659
+ }
660
+ }
661
+ }
662
+ async _createAuthHeader(path) {
663
+ const httpUrl = new URL(path, this._baseHttpUrl);
664
+ httpUrl.protocol = getEdgeUrlWithProtocol(this._baseWsUrl.toString(), "http");
665
+ const response = await fetch(httpUrl, {
666
+ method: "GET"
667
+ });
668
+ if (response.status === 401) {
669
+ return encodePresentationWsAuthHeader(await handleAuthChallenge(response, this._identity));
670
+ } else {
671
+ log2.warn("no auth challenge from edge", {
672
+ status: response.status,
673
+ statusText: response.statusText
674
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 265, S: this });
675
+ return void 0;
676
+ }
677
+ }
678
+ _isActive = (connection) => connection === this._currentConnection;
679
+ };
680
+ _ts_decorate2([
681
+ logInfo2
682
+ ], EdgeClient.prototype, "info", null);
683
+ var encodePresentationWsAuthHeader = (encodedPresentation) => {
684
+ const encodedToken = Buffer.from(encodedPresentation).toString("base64").replace(/=*$/, "").replaceAll("/", "|");
685
+ return `base64url.bearer.authorization.dxos.org.${encodedToken}`;
686
+ };
687
+
688
+ // src/edge-http-client.ts
689
+ import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
690
+ import * as HttpClient from "@effect/platform/HttpClient";
691
+ import * as Effect2 from "effect/Effect";
692
+ import * as Function from "effect/Function";
693
+ import { sleep } from "@dxos/async";
694
+ import { TRACE_SPAN_ATTRIBUTE as TRACE_SPAN_ATTRIBUTE2 } from "@dxos/context";
695
+ import { runAndForwardErrors } from "@dxos/effect";
696
+ import { invariant as invariant4 } from "@dxos/invariant";
697
+ import { log as log4 } from "@dxos/log";
698
+ import { EDGE_CLIENT_TAG_HEADER, EdgeAuthChallengeError, EdgeCallFailedError } from "@dxos/protocols";
699
+ import { createUrl } from "@dxos/util";
700
+
701
+ // src/http-client.ts
702
+ import * as Context2 from "effect/Context";
703
+ import * as Duration from "effect/Duration";
704
+ import * as Effect from "effect/Effect";
705
+ import * as Layer from "effect/Layer";
706
+ import * as Schedule from "effect/Schedule";
707
+ import { log as log3 } from "@dxos/log";
708
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/http-client.ts";
709
+ var HttpConfig = class _HttpConfig extends Context2.Tag("HttpConfig")() {
710
+ static default = Layer.succeed(_HttpConfig, {
711
+ timeout: Duration.millis(1e3),
712
+ retryTimes: 3,
713
+ retryBaseDelay: Duration.millis(1e3)
714
+ });
715
+ };
716
+ var withRetry = (effect, { timeout: timeout2 = Duration.millis(1e3), retryBaseDelay = Duration.millis(1e3), retryTimes = 3 } = {}) => {
717
+ return effect.pipe(Effect.flatMap((res) => (
718
+ // Treat 500 errors as retryable?
719
+ res.status === 500 ? Effect.fail(new Error(res.status.toString())) : res.json
720
+ )), Effect.timeout(timeout2), Effect.retry({
721
+ schedule: Schedule.exponential(retryBaseDelay).pipe(Schedule.jittered),
722
+ times: retryTimes
723
+ }));
724
+ };
725
+ var withRetryConfig = (effect) => Effect.gen(function* () {
726
+ const config = yield* HttpConfig;
727
+ return yield* withRetry(effect, config);
728
+ });
729
+ var withLogging = (effect) => effect.pipe(Effect.tap((res) => {
730
+ log3.info("response", {
731
+ status: res.status
732
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 31, S: void 0 });
733
+ }));
734
+ var encodeAuthHeader = (challenge) => {
735
+ const encodedChallenge = Buffer.from(challenge).toString("base64");
736
+ return `VerifiablePresentation pb;base64,${encodedChallenge}`;
737
+ };
738
+
739
+ // src/edge-http-client.ts
740
+ var __dxlog_file6 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-http-client.ts";
741
+ var DEFAULT_RETRY_TIMEOUT = 1500;
742
+ var DEFAULT_RETRY_JITTER = 500;
743
+ var DEFAULT_MAX_RETRIES_COUNT = 3;
744
+ var WARNING_BODY_SIZE = 10 * 1024 * 1024;
745
+ var EdgeHttpClient = class {
746
+ _baseUrl;
747
+ _clientTag;
748
+ _edgeIdentity;
749
+ /**
750
+ * Auth header is cached until receiving the next 401 from EDGE, at which point it gets refreshed.
751
+ */
752
+ _authHeader;
753
+ constructor(baseUrl, options) {
754
+ this._baseUrl = getEdgeUrlWithProtocol(baseUrl, "http");
755
+ this._clientTag = options?.clientTag;
756
+ log4("created", {
757
+ url: this._baseUrl
758
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 32, S: this });
759
+ }
760
+ get baseUrl() {
761
+ return this._baseUrl;
762
+ }
763
+ setIdentity(identity) {
764
+ if (this._edgeIdentity?.identityKey !== identity.identityKey || this._edgeIdentity?.peerKey !== identity.peerKey) {
765
+ this._edgeIdentity = identity;
766
+ this._authHeader = void 0;
767
+ }
768
+ }
769
+ //
770
+ // Status
771
+ //
772
+ async getStatus(ctx, args) {
773
+ return this._call(ctx, new URL("/status", this.baseUrl), {
774
+ ...args,
775
+ method: "GET",
776
+ auth: true
777
+ });
778
+ }
779
+ //
780
+ // Agents
781
+ //
782
+ createAgent(ctx, body, args) {
783
+ return this._call(ctx, new URL("/agents/create", this.baseUrl), {
784
+ ...args,
785
+ method: "POST",
786
+ body
787
+ });
788
+ }
789
+ getAgentStatus(ctx, request, args) {
790
+ return this._call(ctx, new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
791
+ ...args,
792
+ method: "GET"
793
+ });
794
+ }
795
+ //
796
+ // Credentials
797
+ //
798
+ getCredentialsForNotarization(ctx, spaceId, args) {
799
+ return this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), {
800
+ ...args,
801
+ method: "GET"
802
+ });
803
+ }
804
+ async notarizeCredentials(ctx, spaceId, body, args) {
805
+ await this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), {
806
+ ...args,
807
+ body,
808
+ method: "POST"
809
+ });
810
+ }
811
+ //
812
+ // Identity
813
+ //
814
+ async recoverIdentity(ctx, body, args) {
815
+ return this._call(ctx, new URL("/identity/recover", this.baseUrl), {
816
+ ...args,
817
+ body,
818
+ method: "POST"
819
+ });
820
+ }
821
+ //
822
+ // Invitations
823
+ //
824
+ async joinSpaceByInvitation(ctx, spaceId, body, args) {
825
+ return this._call(ctx, new URL(`/spaces/${spaceId}/join`, this.baseUrl), {
826
+ ...args,
827
+ body,
828
+ method: "POST"
829
+ });
830
+ }
831
+ //
832
+ // OAuth and credentials
833
+ //
834
+ async initiateOAuthFlow(ctx, body, args) {
835
+ return this._call(ctx, new URL("/oauth/initiate", this.baseUrl), {
836
+ ...args,
837
+ body,
838
+ method: "POST"
839
+ });
840
+ }
841
+ //
842
+ // Spaces
843
+ //
844
+ async createSpace(ctx, body, args) {
845
+ return this._call(ctx, new URL("/spaces/create", this.baseUrl), {
846
+ ...args,
847
+ body,
848
+ method: "POST"
849
+ });
850
+ }
851
+ //
852
+ // Queues
853
+ //
854
+ async queryQueue(ctx, subspaceTag, spaceId, query, args) {
855
+ const queueId = query.queueIds?.[0];
856
+ invariant4(queueId, "queueId required", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 132, S: this, A: ["queueId", "'queueId required'"] });
857
+ return this._call(ctx, createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
858
+ after: query.after,
859
+ before: query.before,
860
+ limit: query.limit,
861
+ reverse: query.reverse,
862
+ objectIds: query.objectIds?.join(",")
863
+ }), {
864
+ ...args,
865
+ method: "GET"
866
+ });
867
+ }
868
+ async insertIntoQueue(ctx, subspaceTag, spaceId, queueId, objects, args) {
869
+ return this._call(ctx, new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
870
+ ...args,
871
+ body: {
872
+ objects
873
+ },
874
+ method: "POST"
875
+ });
876
+ }
877
+ async deleteFromQueue(ctx, subspaceTag, spaceId, queueId, objectIds, args) {
878
+ return this._call(ctx, createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
879
+ ids: objectIds.join(",")
880
+ }), {
881
+ ...args,
882
+ method: "DELETE"
883
+ });
884
+ }
885
+ //
886
+ // Functions
887
+ //
888
+ async uploadFunction(ctx, pathParts, body, args) {
889
+ const formData = new FormData();
890
+ formData.append("name", body.name ?? "");
891
+ formData.append("version", body.version);
892
+ formData.append("ownerPublicKey", body.ownerPublicKey);
893
+ formData.append("entryPoint", body.entryPoint);
894
+ body.runtime && formData.append("runtime", body.runtime);
895
+ for (const [filename, content] of Object.entries(body.assets)) {
896
+ formData.append("assets", new Blob([
897
+ content
898
+ ], {
899
+ type: getFileMimeType(filename)
900
+ }), filename);
901
+ }
902
+ const path = [
903
+ "functions",
904
+ ...pathParts.functionId ? [
905
+ pathParts.functionId
906
+ ] : []
907
+ ].join("/");
908
+ return this._call(ctx, new URL(path, this.baseUrl), {
909
+ ...args,
910
+ body: formData,
911
+ method: "PUT",
912
+ json: false
913
+ });
914
+ }
915
+ async listFunctions(ctx, args) {
916
+ return this._call(ctx, new URL("/functions", this.baseUrl), {
917
+ ...args,
918
+ method: "GET"
919
+ });
920
+ }
921
+ async invokeFunction(ctx, params, input, args) {
922
+ const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
923
+ if (params.version) {
924
+ url.searchParams.set("version", params.version);
925
+ }
926
+ if (params.spaceId) {
927
+ url.searchParams.set("spaceId", params.spaceId.toString());
928
+ }
929
+ if (params.cpuTimeLimit) {
930
+ url.searchParams.set("cpuTimeLimit", params.cpuTimeLimit.toString());
931
+ }
932
+ if (params.subrequestsLimit) {
933
+ url.searchParams.set("subrequestsLimit", params.subrequestsLimit.toString());
934
+ }
935
+ return this._call(ctx, url, {
936
+ ...args,
937
+ body: input,
938
+ method: "POST"
939
+ });
940
+ }
941
+ //
942
+ // Workflows
943
+ //
944
+ async executeWorkflow(ctx, spaceId, graphId, input, args) {
945
+ return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
946
+ ...args,
947
+ body: input,
948
+ method: "POST"
949
+ });
950
+ }
951
+ //
952
+ // Triggers
953
+ //
954
+ async getCronTriggers(ctx, spaceId) {
955
+ return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons`, this.baseUrl), {
956
+ method: "GET"
957
+ });
958
+ }
959
+ async forceRunCronTrigger(ctx, spaceId, triggerId) {
960
+ return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {
961
+ method: "POST"
962
+ });
963
+ }
964
+ //
965
+ // Query
966
+ //
967
+ /**
968
+ * Execute a QueryAST query against a space.
969
+ */
970
+ async execQuery(ctx, spaceId, body, args) {
971
+ return this._call(ctx, new URL(`/spaces/${spaceId}/exec-query`, this.baseUrl), {
972
+ ...args,
973
+ body,
974
+ method: "POST"
975
+ });
976
+ }
977
+ //
978
+ // Registry
979
+ //
980
+ /**
981
+ * Fetches the hydrated plugin directory from the Edge registry service.
982
+ * Unauthenticated; safe to call without an identity.
983
+ */
984
+ async getRegistryPlugins(ctx, args) {
985
+ return this._call(ctx, new URL("/registry/plugins", this.baseUrl), {
986
+ ...args,
987
+ method: "GET"
988
+ });
989
+ }
990
+ /**
991
+ * Fetches the available release versions for a given plugin repo. `repo` is the
992
+ * GitHub `owner/name` form; this method takes care of URL-encoding before issuing
993
+ * the request. Unauthenticated; same surface area as {@link getRegistryPlugins}.
994
+ *
995
+ * Versions are returned newest first, suitable for direct rendering in a picker.
996
+ */
997
+ async getRegistryPluginVersions(ctx, repo, args) {
998
+ return this._call(ctx, new URL(`/registry/plugins/${encodeURIComponent(repo)}/versions`, this.baseUrl), {
999
+ ...args,
1000
+ method: "GET"
1001
+ });
1002
+ }
1003
+ //
1004
+ // Import/Export space.
1005
+ //
1006
+ async importBundle(ctx, spaceId, body, args) {
1007
+ return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), {
1008
+ ...args,
1009
+ body,
1010
+ method: "PUT"
1011
+ });
1012
+ }
1013
+ async exportBundle(ctx, spaceId, body, args) {
1014
+ return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
1015
+ ...args,
1016
+ body,
1017
+ method: "POST"
1018
+ });
1019
+ }
1020
+ //
1021
+ // Internal
1022
+ //
1023
+ async _fetch(url, _args) {
1024
+ return Function.pipe(HttpClient.get(url), withLogging, withRetryConfig, Effect2.provide(FetchHttpClient.layer), Effect2.provide(HttpConfig.default), Effect2.withSpan("EdgeHttpClient"), runAndForwardErrors);
1025
+ }
1026
+ // TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
1027
+ async _call(ctx, url, args) {
1028
+ const shouldRetry = createRetryHandler(args);
1029
+ log4("fetch", {
1030
+ url,
1031
+ request: args.body
1032
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 302, S: this });
1033
+ const traceHeaders = getTraceHeaders(ctx);
1034
+ let handledAuth = false;
1035
+ const tryCount = 1;
1036
+ while (true) {
1037
+ let processingError = void 0;
1038
+ try {
1039
+ if (!this._authHeader && args.auth) {
1040
+ const response2 = await fetch(new URL(`/auth`, this.baseUrl));
1041
+ if (response2.status === 401) {
1042
+ this._authHeader = await this._handleUnauthorized(response2);
1043
+ }
1044
+ }
1045
+ const request = createRequest(args, this._authHeader, traceHeaders, this._clientTag);
1046
+ log4("call edge", {
1047
+ url,
1048
+ tryCount,
1049
+ authHeader: !!this._authHeader
1050
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 319, S: this });
1051
+ const response = await fetch(url, request);
1052
+ if (response.ok) {
1053
+ const body2 = await response.clone().json();
1054
+ invariant4(body2, "Expected body to be present", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 327, S: this, A: ["body", "'Expected body to be present'"] });
1055
+ if (!("success" in body2)) {
1056
+ return body2;
1057
+ }
1058
+ if (body2.success) {
1059
+ return body2.data;
1060
+ }
1061
+ } else if (response.status === 401 && !handledAuth) {
1062
+ this._authHeader = await this._handleUnauthorized(response);
1063
+ handledAuth = true;
1064
+ continue;
1065
+ }
1066
+ const body = response.headers.get("Content-Type") === "application/json" ? await response.clone().json() : void 0;
1067
+ invariant4(!body?.success, "Expected body to not be a failure response or undefined.", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 340, S: this, A: ["!body?.success", "'Expected body to not be a failure response or undefined.'"] });
1068
+ if (body?.data?.type === "auth_challenge" && typeof body?.data?.challenge === "string") {
1069
+ processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);
1070
+ } else if (body?.success === false) {
1071
+ processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
1072
+ } else {
1073
+ invariant4(!response.ok, "Expected response to not be ok.", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 346, S: this, A: ["!response.ok", "'Expected response to not be ok.'"] });
1074
+ processingError = await EdgeCallFailedError.fromHttpFailure(response);
1075
+ }
1076
+ } catch (error) {
1077
+ processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
1078
+ }
1079
+ if (processingError?.isRetryable && await shouldRetry(ctx, processingError.retryAfterMs)) {
1080
+ log4.verbose("retrying edge request", {
1081
+ url,
1082
+ processingError
1083
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 353, S: this });
1084
+ } else {
1085
+ throw processingError;
1086
+ }
1087
+ }
1088
+ }
1089
+ async _handleUnauthorized(response) {
1090
+ if (!this._edgeIdentity) {
1091
+ log4.warn("unauthorized response received before identity was set", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 364, S: this });
1092
+ throw await EdgeCallFailedError.fromHttpFailure(response);
1093
+ }
1094
+ const challenge = await handleAuthChallenge(response, this._edgeIdentity);
1095
+ return encodeAuthHeader(challenge);
1096
+ }
1097
+ };
1098
+ var createRequest = ({ method, body, json = true }, authHeader, traceHeaders, clientTag) => {
1099
+ let requestBody;
1100
+ const headers = {};
1101
+ if (json) {
1102
+ requestBody = body && JSON.stringify(body);
1103
+ headers["Content-Type"] = "application/json";
1104
+ } else {
1105
+ requestBody = body;
1106
+ }
1107
+ if (typeof requestBody === "string" && requestBody.length > WARNING_BODY_SIZE) {
1108
+ log4.warn("Request with large body", {
1109
+ bodySize: requestBody.length
1110
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 381, S: void 0 });
1111
+ }
1112
+ if (authHeader) {
1113
+ headers["Authorization"] = authHeader;
1114
+ }
1115
+ if (traceHeaders) {
1116
+ Object.assign(headers, traceHeaders);
1117
+ }
1118
+ if (clientTag) {
1119
+ headers[EDGE_CLIENT_TAG_HEADER] = clientTag;
1120
+ }
1121
+ return {
1122
+ method,
1123
+ body: requestBody,
1124
+ headers
1125
+ };
1126
+ };
1127
+ var getTraceHeaders = (ctx) => {
1128
+ const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE2);
1129
+ if (!traceCtx) {
1130
+ return void 0;
1131
+ }
1132
+ const headers = {
1133
+ traceparent: traceCtx.traceparent
1134
+ };
1135
+ if (traceCtx.tracestate) {
1136
+ headers.tracestate = traceCtx.tracestate;
1137
+ }
1138
+ return headers;
1139
+ };
1140
+ var createRetryHandler = ({ retry: retry2 }) => {
1141
+ if (!retry2 || retry2.count < 1) {
1142
+ return async () => false;
1143
+ }
1144
+ let retries = 0;
1145
+ const maxRetries = retry2.count ?? DEFAULT_MAX_RETRIES_COUNT;
1146
+ const baseTimeout = retry2.timeout ?? DEFAULT_RETRY_TIMEOUT;
1147
+ const jitter = retry2.jitter ?? DEFAULT_RETRY_JITTER;
1148
+ return async (ctx, retryAfter) => {
1149
+ if (++retries > maxRetries || ctx.disposed) {
1150
+ return false;
1151
+ }
1152
+ if (retryAfter) {
1153
+ await sleep(retryAfter);
1154
+ } else {
1155
+ const timeout2 = baseTimeout + Math.random() * jitter;
1156
+ await sleep(timeout2);
1157
+ }
1158
+ return true;
1159
+ };
1160
+ };
1161
+ var getFileMimeType = (filename) => [
1162
+ ".js",
1163
+ ".mjs"
1164
+ ].some((codeExtension) => filename.endsWith(codeExtension)) ? "application/javascript+module" : filename.endsWith(".wasm") ? "application/wasm" : "application/octet-stream";
1165
+ export {
1166
+ CLOUDFLARE_MESSAGE_MAX_BYTES,
1167
+ CLOUDFLARE_RPC_MAX_BYTES,
1168
+ EdgeClient,
1169
+ EdgeConnectionClosedError,
1170
+ EdgeHttpClient,
1171
+ EdgeIdentityChangedError,
1172
+ HttpConfig,
1173
+ Protocol,
1174
+ WebSocketMuxer,
1175
+ createChainEdgeIdentity,
1176
+ createDeviceEdgeIdentity,
1177
+ createEphemeralEdgeIdentity,
1178
+ createStubEdgeIdentity,
1179
+ createTestHaloEdgeIdentity,
1180
+ encodeAuthHeader,
1181
+ getTypename,
1182
+ handleAuthChallenge,
1183
+ protocol,
1184
+ toUint8Array,
1185
+ withLogging,
1186
+ withRetry,
1187
+ withRetryConfig
1188
+ };
1189
+ //# sourceMappingURL=index.mjs.map