@dxos/edge-client 0.8.3 → 0.8.4-main.1068cf700f

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