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

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 (59) hide show
  1. package/dist/lib/browser/{chunk-VHS3XEIX.mjs → chunk-IKP53CBQ.mjs} +51 -18
  2. package/dist/lib/browser/{chunk-VHS3XEIX.mjs.map → chunk-IKP53CBQ.mjs.map} +3 -3
  3. package/dist/lib/browser/edge-ws-muxer.mjs +1 -1
  4. package/dist/lib/browser/index.mjs +566 -350
  5. package/dist/lib/browser/index.mjs.map +4 -4
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/testing/index.mjs +61 -16
  8. package/dist/lib/browser/testing/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/{chunk-HGQUUFIJ.mjs → chunk-DR5YNW5K.mjs} +51 -18
  10. package/dist/lib/node-esm/{chunk-HGQUUFIJ.mjs.map → chunk-DR5YNW5K.mjs.map} +3 -3
  11. package/dist/lib/node-esm/edge-ws-muxer.mjs +1 -1
  12. package/dist/lib/node-esm/index.mjs +566 -350
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/testing/index.mjs +61 -16
  16. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  17. package/dist/types/src/edge-client.d.ts +15 -15
  18. package/dist/types/src/edge-client.d.ts.map +1 -1
  19. package/dist/types/src/edge-http-client.d.ts +52 -30
  20. package/dist/types/src/edge-http-client.d.ts.map +1 -1
  21. package/dist/types/src/edge-http-client.test.d.ts +2 -0
  22. package/dist/types/src/edge-http-client.test.d.ts.map +1 -0
  23. package/dist/types/src/edge-ws-connection.d.ts +1 -0
  24. package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
  25. package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
  26. package/dist/types/src/http-client.d.ts +22 -0
  27. package/dist/types/src/http-client.d.ts.map +1 -0
  28. package/dist/types/src/http-client.test.d.ts +2 -0
  29. package/dist/types/src/http-client.test.d.ts.map +1 -0
  30. package/dist/types/src/index.d.ts +4 -3
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/testing/index.d.ts +1 -0
  33. package/dist/types/src/testing/index.d.ts.map +1 -1
  34. package/dist/types/src/testing/test-server.d.ts +9 -0
  35. package/dist/types/src/testing/test-server.d.ts.map +1 -0
  36. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +21 -14
  39. package/src/edge-client.ts +40 -40
  40. package/src/edge-http-client.test.ts +22 -0
  41. package/src/edge-http-client.ts +330 -140
  42. package/src/edge-ws-connection.ts +11 -3
  43. package/src/edge-ws-muxer.ts +1 -1
  44. package/src/http-client.test.ts +55 -0
  45. package/src/http-client.ts +67 -0
  46. package/src/index.ts +4 -3
  47. package/src/testing/index.ts +1 -0
  48. package/src/testing/test-server.ts +45 -0
  49. package/src/testing/test-utils.ts +2 -2
  50. package/src/websocket.test.ts +1 -1
  51. package/dist/lib/node/chunk-XNHBUTNB.cjs +0 -317
  52. package/dist/lib/node/chunk-XNHBUTNB.cjs.map +0 -7
  53. package/dist/lib/node/edge-ws-muxer.cjs +0 -33
  54. package/dist/lib/node/edge-ws-muxer.cjs.map +0 -7
  55. package/dist/lib/node/index.cjs +0 -1060
  56. package/dist/lib/node/index.cjs.map +0 -7
  57. package/dist/lib/node/meta.json +0 -1
  58. package/dist/lib/node/testing/index.cjs +0 -169
  59. package/dist/lib/node/testing/index.cjs.map +0 -7
@@ -2,41 +2,85 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { FetchHttpClient, HttpClient } from '@effect/platform';
6
+ import { Effect, pipe } from 'effect';
7
+
5
8
  import { sleep } from '@dxos/async';
6
9
  import { Context } from '@dxos/context';
7
10
  import { type PublicKey, type SpaceId } from '@dxos/keys';
8
11
  import { log } from '@dxos/log';
9
12
  import {
13
+ type CreateAgentRequestBody,
14
+ type CreateAgentResponseBody,
15
+ type CreateSpaceRequest,
16
+ type CreateSpaceResponseBody,
17
+ EdgeAuthChallengeError,
10
18
  EdgeCallFailedError,
11
19
  type EdgeHttpResponse,
20
+ type EdgeStatus,
21
+ type ExecuteWorkflowResponseBody,
22
+ type ExportBundleRequest,
23
+ type ExportBundleResponse,
24
+ type GetAgentStatusResponseBody,
12
25
  type GetNotarizationResponseBody,
13
- type PostNotarizationRequestBody,
26
+ type ImportBundleRequest,
27
+ type InitiateOAuthFlowRequest,
28
+ type InitiateOAuthFlowResponse,
14
29
  type JoinSpaceRequest,
15
30
  type JoinSpaceResponseBody,
16
- EdgeAuthChallengeError,
17
- type CreateAgentResponseBody,
18
- type CreateAgentRequestBody,
19
- type GetAgentStatusResponseBody,
31
+ type ObjectId,
32
+ type PostNotarizationRequestBody,
33
+ type QueryResult,
34
+ type QueueQuery,
20
35
  type RecoverIdentityRequest,
21
36
  type RecoverIdentityResponseBody,
22
37
  type UploadFunctionRequest,
23
38
  type UploadFunctionResponseBody,
24
- type ObjectId,
25
- type ExecuteWorkflowResponseBody,
26
- type QueueQuery,
27
- type QueryResult,
28
- type InitiateOAuthFlowRequest,
29
- type InitiateOAuthFlowResponse,
30
- type CreateSpaceRequest,
31
- type CreateSpaceResponseBody,
32
39
  } from '@dxos/protocols';
40
+ import { createUrl } from '@dxos/util';
33
41
 
34
42
  import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
43
+ import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
35
44
  import { getEdgeUrlWithProtocol } from './utils';
36
45
 
37
46
  const DEFAULT_RETRY_TIMEOUT = 1500;
38
47
  const DEFAULT_RETRY_JITTER = 500;
39
48
  const DEFAULT_MAX_RETRIES_COUNT = 3;
49
+ const WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB
50
+
51
+ export type RetryConfig = {
52
+ /**
53
+ * A number of call retries, not counting the initial request.
54
+ */
55
+ count: number;
56
+ /**
57
+ * Delay before retries in ms.
58
+ */
59
+ timeout?: number;
60
+ /**
61
+ * A random amount of time before retrying to help prevent large bursts of requests.
62
+ */
63
+ jitter?: number;
64
+ };
65
+
66
+ type EdgeHttpRequestArgs = {
67
+ method: string;
68
+ context?: Context;
69
+ retry?: RetryConfig;
70
+ body?: any;
71
+ /**
72
+ * @default true
73
+ */
74
+ json?: boolean;
75
+
76
+ /**
77
+ * Do not expect a standard EDGE JSON response with a `success` field.
78
+ */
79
+ rawResponse?: boolean;
80
+ };
81
+
82
+ export type EdgeHttpGetArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry'>;
83
+ export type EdgeHttpPostArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry' | 'body'>;
40
84
 
41
85
  export class EdgeHttpClient {
42
86
  private readonly _baseUrl: string;
@@ -64,19 +108,38 @@ export class EdgeHttpClient {
64
108
  }
65
109
  }
66
110
 
111
+ //
112
+ // Status
113
+ //
114
+
115
+ public async getStatus(args?: EdgeHttpGetArgs): Promise<EdgeStatus> {
116
+ return this._call(new URL('/status', this.baseUrl), { ...args, method: 'GET' });
117
+ }
118
+
119
+ //
120
+ // Agents
121
+ //
122
+
67
123
  public createAgent(body: CreateAgentRequestBody, args?: EdgeHttpGetArgs): Promise<CreateAgentResponseBody> {
68
- return this._call('/agents/create', { ...args, method: 'POST', body });
124
+ return this._call(new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });
69
125
  }
70
126
 
71
127
  public getAgentStatus(
72
128
  request: { ownerIdentityKey: PublicKey },
73
129
  args?: EdgeHttpGetArgs,
74
130
  ): Promise<GetAgentStatusResponseBody> {
75
- return this._call(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, { ...args, method: 'GET' });
131
+ return this._call(new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
132
+ ...args,
133
+ method: 'GET',
134
+ });
76
135
  }
77
136
 
137
+ //
138
+ // Credentials
139
+ //
140
+
78
141
  public getCredentialsForNotarization(spaceId: SpaceId, args?: EdgeHttpGetArgs): Promise<GetNotarizationResponseBody> {
79
- return this._call(`/spaces/${spaceId}/notarization`, { ...args, method: 'GET' });
142
+ return this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });
80
143
  }
81
144
 
82
145
  public async notarizeCredentials(
@@ -84,49 +147,55 @@ export class EdgeHttpClient {
84
147
  body: PostNotarizationRequestBody,
85
148
  args?: EdgeHttpGetArgs,
86
149
  ): Promise<void> {
87
- await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
150
+ await this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
88
151
  }
89
152
 
90
- public async joinSpaceByInvitation(
91
- spaceId: SpaceId,
92
- body: JoinSpaceRequest,
93
- args?: EdgeHttpGetArgs,
94
- ): Promise<JoinSpaceResponseBody> {
95
- return this._call(`/spaces/${spaceId}/join`, { ...args, body, method: 'POST' });
96
- }
153
+ //
154
+ // Identity
155
+ //
97
156
 
98
157
  public async recoverIdentity(
99
158
  body: RecoverIdentityRequest,
100
159
  args?: EdgeHttpGetArgs,
101
160
  ): Promise<RecoverIdentityResponseBody> {
102
- return this._call('/identity/recover', { ...args, body, method: 'POST' });
161
+ return this._call(new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
103
162
  }
104
163
 
105
- public async executeWorkflow(
164
+ //
165
+ // Invitations
166
+ //
167
+
168
+ public async joinSpaceByInvitation(
106
169
  spaceId: SpaceId,
107
- graphId: ObjectId,
108
- input: any,
170
+ body: JoinSpaceRequest,
109
171
  args?: EdgeHttpGetArgs,
110
- ): Promise<ExecuteWorkflowResponseBody> {
111
- return this._call(`/workflows/${spaceId}/${graphId}`, { ...args, body: input, method: 'POST' });
172
+ ): Promise<JoinSpaceResponseBody> {
173
+ return this._call(new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
112
174
  }
113
175
 
114
- public async uploadFunction(
115
- pathParts: { functionId?: string },
116
- body: UploadFunctionRequest,
117
- args?: EdgeHttpGetArgs,
118
- ): Promise<UploadFunctionResponseBody> {
119
- const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
120
- return this._call(path, { ...args, body, method: 'PUT' });
121
- }
176
+ //
177
+ // OAuth and credentials
178
+ //
122
179
 
123
180
  public async initiateOAuthFlow(
124
181
  body: InitiateOAuthFlowRequest,
125
182
  args?: EdgeHttpGetArgs,
126
183
  ): Promise<InitiateOAuthFlowResponse> {
127
- return this._call('/oauth/initiate', { ...args, body, method: 'POST' });
184
+ return this._call(new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
185
+ }
186
+
187
+ //
188
+ // Spaces
189
+ //
190
+
191
+ async createSpace(body: CreateSpaceRequest, args?: EdgeHttpGetArgs): Promise<CreateSpaceResponseBody> {
192
+ return this._call(new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
128
193
  }
129
194
 
195
+ //
196
+ // Queues
197
+ //
198
+
130
199
  public async queryQueue(
131
200
  subspaceTag: string,
132
201
  spaceId: SpaceId,
@@ -134,26 +203,19 @@ export class EdgeHttpClient {
134
203
  args?: EdgeHttpGetArgs,
135
204
  ): Promise<QueryResult> {
136
205
  const { queueId } = query;
137
- const queryParams = new URLSearchParams();
138
- if (query.after != null) {
139
- queryParams.set('after', query.after);
140
- }
141
- if (query.before != null) {
142
- queryParams.set('before', query.before);
143
- }
144
- if (query.limit != null) {
145
- queryParams.set('limit', query.limit.toString());
146
- }
147
- if (query.reverse != null) {
148
- queryParams.set('reverse', query.reverse.toString());
149
- }
150
- if (query.objectIds != null) {
151
- queryParams.set('objectIds', query.objectIds.join(','));
152
- }
153
- return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query?${queryParams.toString()}`, {
154
- ...args,
155
- method: 'GET',
156
- });
206
+ return this._call(
207
+ createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
208
+ after: query.after,
209
+ before: query.before,
210
+ limit: query.limit,
211
+ reverse: query.reverse,
212
+ objectIds: query.objectIds?.join(','),
213
+ }),
214
+ {
215
+ ...args,
216
+ method: 'GET',
217
+ },
218
+ );
157
219
  }
158
220
 
159
221
  public async insertIntoQueue(
@@ -163,109 +225,270 @@ export class EdgeHttpClient {
163
225
  objects: unknown[],
164
226
  args?: EdgeHttpGetArgs,
165
227
  ): Promise<void> {
166
- return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
228
+ return this._call(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
167
229
  ...args,
168
230
  body: { objects },
169
231
  method: 'POST',
170
232
  });
171
233
  }
172
234
 
173
- async deleteFromQueue(
235
+ public async deleteFromQueue(
174
236
  subspaceTag: string,
175
237
  spaceId: SpaceId,
176
238
  queueId: ObjectId,
177
239
  objectIds: ObjectId[],
178
240
  args?: EdgeHttpGetArgs,
179
241
  ): Promise<void> {
180
- return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
242
+ return this._call(
243
+ createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
244
+ ids: objectIds.join(','),
245
+ }),
246
+ {
247
+ ...args,
248
+ method: 'DELETE',
249
+ },
250
+ );
251
+ }
252
+
253
+ //
254
+ // Functions
255
+ //
256
+
257
+ public async uploadFunction(
258
+ pathParts: { functionId?: string },
259
+ body: UploadFunctionRequest,
260
+ args?: EdgeHttpGetArgs,
261
+ ): Promise<UploadFunctionResponseBody> {
262
+ const formData = new FormData();
263
+ formData.append('name', body.name ?? '');
264
+ formData.append('version', body.version);
265
+ formData.append('ownerPublicKey', body.ownerPublicKey);
266
+ formData.append('entryPoint', body.entryPoint);
267
+ for (const [filename, content] of Object.entries(body.assets)) {
268
+ formData.append(
269
+ 'assets',
270
+ new Blob([content as Uint8Array<ArrayBuffer>], { type: getFileMimeType(filename) }),
271
+ filename,
272
+ );
273
+ }
274
+
275
+ const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
276
+ return this._call(new URL(path, this.baseUrl), {
181
277
  ...args,
182
- query: { ids: objectIds.join(',') },
183
- method: 'DELETE',
278
+ body: formData,
279
+ method: 'PUT',
280
+ json: false,
184
281
  });
185
282
  }
186
283
 
187
- async createSpace(body: CreateSpaceRequest, args?: EdgeHttpGetArgs): Promise<CreateSpaceResponseBody> {
188
- return this._call('/spaces/create', { ...args, body, method: 'POST' });
284
+ public async listFunctions(args?: EdgeHttpGetArgs): Promise<any> {
285
+ return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
189
286
  }
190
287
 
191
- private async _call<T>(path: string, args: EdgeHttpCallArgs): Promise<T> {
192
- const requestContext = args.context ?? new Context();
193
- const shouldRetry = createRetryHandler(args);
194
- let url = `${this._baseUrl}${path.startsWith('/') ? path.slice(1) : path}`;
195
-
196
- if (args.query) {
197
- const queryParams = new URLSearchParams();
198
- for (const [key, value] of Object.entries(args.query)) {
199
- queryParams.set(key, value.toString());
200
- }
201
- url += `?${queryParams.toString()}`;
288
+ public async invokeFunction(
289
+ params: {
290
+ functionId: string;
291
+ version?: string;
292
+ spaceId?: SpaceId;
293
+ cpuTimeLimit?: number;
294
+ subrequestsLimit?: number;
295
+ },
296
+ input: unknown,
297
+ args?: EdgeHttpGetArgs,
298
+ ): Promise<any> {
299
+ const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
300
+ if (params.version) {
301
+ url.searchParams.set('version', params.version);
202
302
  }
303
+ if (params.spaceId) {
304
+ url.searchParams.set('spaceId', params.spaceId.toString());
305
+ }
306
+ if (params.cpuTimeLimit) {
307
+ url.searchParams.set('cpuTimeLimit', params.cpuTimeLimit.toString());
308
+ }
309
+ if (params.subrequestsLimit) {
310
+ url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
311
+ }
312
+
313
+ return this._call(url, {
314
+ ...args,
315
+ body: input,
316
+ method: 'POST',
317
+ rawResponse: true,
318
+ });
319
+ }
320
+
321
+ //
322
+ // Workflows
323
+ //
324
+
325
+ public async executeWorkflow(
326
+ spaceId: SpaceId,
327
+ graphId: ObjectId,
328
+ input: any,
329
+ args?: EdgeHttpGetArgs,
330
+ ): Promise<ExecuteWorkflowResponseBody> {
331
+ return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
332
+ ...args,
333
+ body: input,
334
+ method: 'POST',
335
+ });
336
+ }
337
+
338
+ //
339
+ // Triggers
340
+ //
341
+
342
+ public async getCronTriggers(spaceId: SpaceId) {
343
+ return this._call(new URL(`/test/functions/${spaceId}/triggers/crons`, this.baseUrl), { method: 'GET' });
344
+ }
345
+
346
+ //
347
+ // Import/Export space.
348
+ //
349
+
350
+ public async importBundle(
351
+ spaceId: SpaceId, //
352
+ body: ImportBundleRequest,
353
+ args?: EdgeHttpGetArgs,
354
+ ): Promise<void> {
355
+ return this._call(new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
356
+ }
357
+
358
+ public async exportBundle(
359
+ spaceId: SpaceId,
360
+ body: ExportBundleRequest,
361
+ args?: EdgeHttpGetArgs,
362
+ ): Promise<ExportBundleResponse> {
363
+ return this._call(new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
364
+ ...args,
365
+ body,
366
+ method: 'POST',
367
+ });
368
+ }
369
+
370
+ //
371
+ // Internal
372
+ //
373
+
374
+ private async _fetch<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
375
+ return pipe(
376
+ HttpClient.get(url),
377
+ withLogging,
378
+ withRetryConfig,
379
+ Effect.provide(FetchHttpClient.layer),
380
+ Effect.provide(HttpConfig.default),
381
+ Effect.withSpan('EdgeHttpClient'),
382
+ Effect.runPromise,
383
+ ) as T;
384
+ }
203
385
 
204
- log('call', { method: args.method, path, request: args.body });
386
+ // TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
387
+ private async _call<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
388
+ const shouldRetry = createRetryHandler(args);
389
+ const requestContext = args.context ?? new Context();
390
+ log('fetch', { url, request: args.body });
205
391
 
206
392
  let handledAuth = false;
207
- let authHeader = this._authHeader;
208
393
  while (true) {
209
- let processingError: EdgeCallFailedError;
394
+ let processingError: EdgeCallFailedError | undefined = undefined;
210
395
  let retryAfterHeaderValue: number = Number.NaN;
211
396
  try {
212
- const request = createRequest(args, authHeader);
397
+ const request = createRequest(args, this._authHeader);
213
398
  const response = await fetch(url, request);
214
-
215
399
  retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
216
-
217
400
  if (response.ok) {
218
401
  const body = (await response.json()) as EdgeHttpResponse<T>;
402
+
403
+ if (args.rawResponse) {
404
+ return body as any;
405
+ }
406
+
407
+ if (!('success' in body)) {
408
+ return body;
409
+ }
410
+
219
411
  if (body.success) {
220
412
  return body.data;
221
413
  }
222
414
 
223
- log('unsuccessful edge response', { path, body });
224
-
415
+ log.warn('unsuccessful edge response', { url, body });
225
416
  if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
226
417
  processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
227
- } else {
418
+ } else if (body.errorData) {
228
419
  processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
229
420
  }
230
421
  } else if (response.status === 401 && !handledAuth) {
231
- authHeader = await this._handleUnauthorized(response);
422
+ this._authHeader = await this._handleUnauthorized(response);
232
423
  handledAuth = true;
233
424
  continue;
234
425
  } else {
235
- processingError = EdgeCallFailedError.fromHttpFailure(response);
426
+ processingError = await EdgeCallFailedError.fromHttpFailure(response);
236
427
  }
237
428
  } catch (error: any) {
238
429
  processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
239
430
  }
240
431
 
241
- if (processingError.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
242
- log('retrying edge request', { path, processingError });
432
+ if (processingError?.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
433
+ log('retrying edge request', { url, processingError });
243
434
  } else {
244
- throw processingError;
435
+ throw processingError!;
245
436
  }
246
437
  }
247
438
  }
248
439
 
249
440
  private async _handleUnauthorized(response: Response): Promise<string> {
250
441
  if (!this._edgeIdentity) {
251
- log.warn('edge unauthorized response received before identity was set');
252
- throw EdgeCallFailedError.fromHttpFailure(response);
442
+ log.warn('unauthorized response received before identity was set');
443
+ throw await EdgeCallFailedError.fromHttpFailure(response);
253
444
  }
445
+
254
446
  const challenge = await handleAuthChallenge(response, this._edgeIdentity);
255
- this._authHeader = encodeAuthHeader(challenge);
256
- log('auth header updated');
257
- return this._authHeader;
447
+ return encodeAuthHeader(challenge);
258
448
  }
259
449
  }
260
450
 
261
- const createRetryHandler = (args: EdgeHttpCallArgs) => {
262
- if (!args.retry || args.retry.count < 1) {
451
+ const createRequest = (
452
+ { method, body, json = true }: EdgeHttpRequestArgs,
453
+ authHeader: string | undefined,
454
+ ): RequestInit => {
455
+ let requestBody: BodyInit | undefined;
456
+ const headers: HeadersInit = {};
457
+
458
+ if (json) {
459
+ requestBody = body && JSON.stringify(body);
460
+ headers['Content-Type'] = 'application/json';
461
+ } else {
462
+ requestBody = body;
463
+ }
464
+
465
+ if (typeof requestBody === 'string' && requestBody.length > WARNING_BODY_SIZE) {
466
+ log.warn('Request with large body', { bodySize: requestBody.length });
467
+ }
468
+
469
+ if (authHeader) {
470
+ headers['Authorization'] = authHeader;
471
+ }
472
+
473
+ return {
474
+ method,
475
+ body: requestBody,
476
+ headers,
477
+ };
478
+ };
479
+
480
+ /**
481
+ * @deprecated
482
+ */
483
+ const createRetryHandler = ({ retry }: EdgeHttpRequestArgs) => {
484
+ if (!retry || retry.count < 1) {
263
485
  return async () => false;
264
486
  }
487
+
265
488
  let retries = 0;
266
- const maxRetries = args.retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
267
- const baseTimeout = args.retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
268
- const jitter = args.retry.jitter ?? DEFAULT_RETRY_JITTER;
489
+ const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
490
+ const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
491
+ const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;
269
492
  return async (ctx: Context, retryAfter: number) => {
270
493
  if (++retries > maxRetries || ctx.disposed) {
271
494
  return false;
@@ -282,42 +505,9 @@ const createRetryHandler = (args: EdgeHttpCallArgs) => {
282
505
  };
283
506
  };
284
507
 
285
- export type RetryConfig = {
286
- /**
287
- * A number of call retries, not counting the initial request.
288
- */
289
- count: number;
290
- /**
291
- * Delay before retries in ms.
292
- */
293
- timeout?: number;
294
- /**
295
- * A random amount of time before retrying to help prevent large bursts of requests.
296
- */
297
- jitter?: number;
298
- };
299
-
300
- export type EdgeHttpGetArgs = { context?: Context; retry?: RetryConfig };
301
-
302
- export type EdgeHttpPostArgs = { context?: Context; body?: any; retry?: RetryConfig };
303
-
304
- type EdgeHttpCallArgs = {
305
- method: string;
306
- body?: any;
307
- context?: Context;
308
- retry?: RetryConfig;
309
- query?: Record<string, string>;
310
- };
311
-
312
- const createRequest = (args: EdgeHttpCallArgs, authHeader: string | undefined): RequestInit => {
313
- return {
314
- method: args.method,
315
- body: args.body && JSON.stringify(args.body),
316
- headers: authHeader ? { Authorization: authHeader } : undefined,
317
- };
318
- };
319
-
320
- const encodeAuthHeader = (challenge: Uint8Array) => {
321
- const encodedChallenge = Buffer.from(challenge).toString('base64');
322
- return `VerifiablePresentation pb;base64,${encodedChallenge}`;
323
- };
508
+ const getFileMimeType = (filename: string) =>
509
+ ['.js', '.mjs'].some((codeExtension) => filename.endsWith(codeExtension))
510
+ ? 'application/javascript+module'
511
+ : filename.endsWith('.wasm')
512
+ ? 'application/wasm'
513
+ : 'application/octet-stream';
@@ -10,7 +10,7 @@ import { invariant } from '@dxos/invariant';
10
10
  import { log, logInfo } from '@dxos/log';
11
11
  import { EdgeWebsocketProtocol } from '@dxos/protocols';
12
12
  import { buf } from '@dxos/protocols/buf';
13
- import { MessageSchema, type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
13
+ import { type Message, MessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
14
14
 
15
15
  import { protocol } from './defs';
16
16
  import { type EdgeIdentity } from './edge-identity';
@@ -30,6 +30,7 @@ export class EdgeWsConnection extends Resource {
30
30
  private _inactivityTimeoutCtx: Context | undefined;
31
31
  private _ws: WebSocket | undefined;
32
32
  private _wsMuxer: WebSocketMuxer | undefined;
33
+ private _lastReceivedMessageTimestamp = Date.now();
33
34
 
34
35
  constructor(
35
36
  private readonly _identity: EdgeIdentity,
@@ -111,6 +112,7 @@ export class EdgeWsConnection extends Resource {
111
112
  log.verbose('message ignored on closed connection', { event: event.type });
112
113
  return;
113
114
  }
115
+ this._lastReceivedMessageTimestamp = Date.now();
114
116
  if (event.data === '__pong__') {
115
117
  this._rescheduleHeartbeatTimeout();
116
118
  return;
@@ -172,8 +174,14 @@ export class EdgeWsConnection extends Resource {
172
174
  this._inactivityTimeoutCtx,
173
175
  () => {
174
176
  if (this.isOpen) {
175
- log.warn('restart due to inactivity timeout');
176
- this._callbacks.onRestartRequired();
177
+ if (Date.now() - this._lastReceivedMessageTimestamp > SIGNAL_KEEPALIVE_TIMEOUT) {
178
+ log.warn('restart due to inactivity timeout', {
179
+ lastReceivedMessageTimestamp: this._lastReceivedMessageTimestamp,
180
+ });
181
+ this._callbacks.onRestartRequired();
182
+ } else {
183
+ this._rescheduleHeartbeatTimeout();
184
+ }
177
185
  }
178
186
  },
179
187
  SIGNAL_KEEPALIVE_TIMEOUT,
@@ -5,7 +5,7 @@
5
5
  import { Trigger } from '@dxos/async';
6
6
  import { log } from '@dxos/log';
7
7
  import { buf } from '@dxos/protocols/buf';
8
- import { MessageSchema, type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
8
+ import { type Message, MessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
9
9
 
10
10
  import { protocol } from './defs';
11
11