@dxos/edge-client 0.8.4-main.84f28bd → 0.8.4-main.a4bbb77

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 (36) hide show
  1. package/dist/lib/browser/{chunk-LMP5TVOP.mjs → chunk-IKP53CBQ.mjs} +44 -11
  2. package/dist/lib/browser/{chunk-LMP5TVOP.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 +420 -259
  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 +1 -1
  8. package/dist/lib/browser/testing/index.mjs.map +2 -2
  9. package/dist/lib/node-esm/{chunk-X7J46ISZ.mjs → chunk-DR5YNW5K.mjs} +44 -11
  10. package/dist/lib/node-esm/{chunk-X7J46ISZ.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 +420 -259
  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 +1 -1
  16. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  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 +21 -1
  20. package/dist/types/src/edge-http-client.d.ts.map +1 -1
  21. package/dist/types/src/edge-ws-connection.d.ts +1 -0
  22. package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
  23. package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
  24. package/dist/types/src/index.d.ts +4 -3
  25. package/dist/types/src/index.d.ts.map +1 -1
  26. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  27. package/dist/types/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +18 -15
  29. package/src/edge-client.ts +40 -40
  30. package/src/edge-http-client.ts +153 -16
  31. package/src/edge-ws-connection.ts +11 -3
  32. package/src/edge-ws-muxer.ts +1 -1
  33. package/src/http-client.test.ts +2 -2
  34. package/src/index.ts +4 -3
  35. package/src/testing/test-utils.ts +1 -1
  36. package/src/websocket.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/edge-client",
3
- "version": "0.8.4-main.84f28bd",
3
+ "version": "0.8.4-main.a4bbb77",
4
4
  "description": "EDGE Client",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,16 +10,19 @@
10
10
  "type": "module",
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
13
14
  "types": "./dist/types/src/index.d.ts",
14
15
  "browser": "./dist/lib/browser/index.mjs",
15
16
  "node": "./dist/lib/node-esm/index.mjs"
16
17
  },
17
18
  "./muxer": {
19
+ "source": "./src/edge-ws-muxer.ts",
18
20
  "types": "./dist/types/src/edge-ws-muxer.d.ts",
19
21
  "browser": "./dist/lib/browser/edge-ws-muxer.mjs",
20
22
  "node": "./dist/lib/node-esm/edge-ws-muxer.mjs"
21
23
  },
22
24
  "./testing": {
25
+ "source": "./src/testing/index.ts",
23
26
  "types": "./dist/types/src/testing/index.d.ts",
24
27
  "browser": "./dist/lib/browser/testing/index.mjs",
25
28
  "node": "./dist/lib/node-esm/testing/index.mjs"
@@ -39,24 +42,24 @@
39
42
  "README.md"
40
43
  ],
41
44
  "dependencies": {
42
- "@effect/platform": "^0.87.12",
45
+ "@effect/platform": "^0.92.1",
43
46
  "isomorphic-ws": "^5.0.0",
44
47
  "ws": "^8.14.2",
45
- "@dxos/async": "0.8.4-main.84f28bd",
46
- "@dxos/context": "0.8.4-main.84f28bd",
47
- "@dxos/credentials": "0.8.4-main.84f28bd",
48
- "@dxos/debug": "0.8.4-main.84f28bd",
49
- "@dxos/crypto": "0.8.4-main.84f28bd",
50
- "@dxos/invariant": "0.8.4-main.84f28bd",
51
- "@dxos/keyring": "0.8.4-main.84f28bd",
52
- "@dxos/keys": "0.8.4-main.84f28bd",
53
- "@dxos/log": "0.8.4-main.84f28bd",
54
- "@dxos/node-std": "0.8.4-main.84f28bd",
55
- "@dxos/protocols": "0.8.4-main.84f28bd",
56
- "@dxos/util": "0.8.4-main.84f28bd"
48
+ "@dxos/async": "0.8.4-main.a4bbb77",
49
+ "@dxos/context": "0.8.4-main.a4bbb77",
50
+ "@dxos/credentials": "0.8.4-main.a4bbb77",
51
+ "@dxos/crypto": "0.8.4-main.a4bbb77",
52
+ "@dxos/debug": "0.8.4-main.a4bbb77",
53
+ "@dxos/invariant": "0.8.4-main.a4bbb77",
54
+ "@dxos/log": "0.8.4-main.a4bbb77",
55
+ "@dxos/keyring": "0.8.4-main.a4bbb77",
56
+ "@dxos/keys": "0.8.4-main.a4bbb77",
57
+ "@dxos/node-std": "0.8.4-main.a4bbb77",
58
+ "@dxos/protocols": "0.8.4-main.a4bbb77",
59
+ "@dxos/util": "0.8.4-main.a4bbb77"
57
60
  },
58
61
  "devDependencies": {
59
- "@dxos/test-utils": "0.8.4-main.84f28bd"
62
+ "@dxos/test-utils": "0.8.4-main.a4bbb77"
60
63
  },
61
64
  "peerDependencies": {
62
65
  "effect": "^3.13.3"
@@ -2,8 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Trigger, scheduleMicroTask, TriggerState, PersistentLifecycle, Event } from '@dxos/async';
6
- import { Resource, type Lifecycle } from '@dxos/context';
5
+ import { Event, PersistentLifecycle, Trigger, TriggerState, scheduleMicroTask } from '@dxos/async';
6
+ import { type Lifecycle, Resource } from '@dxos/context';
7
7
  import { log, logInfo } from '@dxos/log';
8
8
  import { type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
9
9
  import { EdgeStatus } from '@dxos/protocols/proto/dxos/client/services';
@@ -20,6 +20,13 @@ const DEFAULT_TIMEOUT = 10_000;
20
20
  export type MessageListener = (message: Message) => void;
21
21
  export type ReconnectListener = () => void;
22
22
 
23
+ export type MessengerConfig = {
24
+ socketEndpoint: string;
25
+ timeout?: number;
26
+ protocol?: Protocol;
27
+ disableAuth?: boolean;
28
+ };
29
+
23
30
  export interface EdgeConnection extends Required<Lifecycle> {
24
31
  statusChanged: Event<EdgeStatus>;
25
32
  get info(): any;
@@ -28,18 +35,11 @@ export interface EdgeConnection extends Required<Lifecycle> {
28
35
  get isOpen(): boolean;
29
36
  get status(): EdgeStatus;
30
37
  setIdentity(identity: EdgeIdentity): void;
38
+ send(message: Message): Promise<void>;
31
39
  onMessage(listener: MessageListener): () => void;
32
40
  onReconnected(listener: ReconnectListener): () => void;
33
- send(message: Message): Promise<void>;
34
41
  }
35
42
 
36
- export type MessengerConfig = {
37
- socketEndpoint: string;
38
- timeout?: number;
39
- protocol?: Protocol;
40
- disableAuth?: boolean;
41
- };
42
-
43
43
  /**
44
44
  * Messenger client for EDGE:
45
45
  * - While open, uses PersistentLifecycle to keep an open EdgeWsConnection, reconnecting on failures.
@@ -80,7 +80,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
80
80
  };
81
81
  }
82
82
 
83
- get status(): EdgeStatus {
83
+ get status() {
84
84
  return Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED
85
85
  ? EdgeStatus.CONNECTED
86
86
  : EdgeStatus.NOT_CONNECTED;
@@ -94,7 +94,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
94
94
  return this._identity.peerKey;
95
95
  }
96
96
 
97
- setIdentity(identity: EdgeIdentity): void {
97
+ setIdentity(identity: EdgeIdentity) {
98
98
  if (identity.identityKey !== this._identity.identityKey || identity.peerKey !== this._identity.peerKey) {
99
99
  log('Edge identity changed', { identity, oldIdentity: this._identity });
100
100
  this._identity = identity;
@@ -103,12 +103,36 @@ export class EdgeClient extends Resource implements EdgeConnection {
103
103
  }
104
104
  }
105
105
 
106
- public onMessage(listener: MessageListener): () => void {
106
+ /**
107
+ * Send message.
108
+ * NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
109
+ */
110
+ public async send(message: Message) {
111
+ if (this._ready.state !== TriggerState.RESOLVED) {
112
+ log('waiting for websocket');
113
+ await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
114
+ }
115
+
116
+ if (!this._currentConnection) {
117
+ throw new EdgeConnectionClosedError();
118
+ }
119
+
120
+ if (
121
+ message.source &&
122
+ (message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)
123
+ ) {
124
+ throw new EdgeIdentityChangedError();
125
+ }
126
+
127
+ this._currentConnection.send(message);
128
+ }
129
+
130
+ public onMessage(listener: MessageListener) {
107
131
  this._messageListeners.add(listener);
108
132
  return () => this._messageListeners.delete(listener);
109
133
  }
110
134
 
111
- public onReconnected(listener: () => void): () => void {
135
+ public onReconnected(listener: () => void) {
112
136
  this._reconnectListeners.add(listener);
113
137
  if (this._ready.state === TriggerState.RESOLVED) {
114
138
  // Microtask so that listener is always called asynchronously, no matter the state of the ready trigger
@@ -123,6 +147,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
123
147
  }
124
148
  });
125
149
  }
150
+
126
151
  return () => this._reconnectListeners.delete(listener);
127
152
  }
128
153
 
@@ -200,7 +225,6 @@ export class EdgeClient extends Resource implements EdgeConnection {
200
225
  // Race with restartRequired so that restart is not blocked by _connect execution.
201
226
  // Wait on ready to attempt a reconnect if it times out.
202
227
  await Promise.race([this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT }), restartRequired]);
203
-
204
228
  return connection;
205
229
  }
206
230
 
@@ -237,30 +261,6 @@ export class EdgeClient extends Resource implements EdgeConnection {
237
261
  }
238
262
  }
239
263
 
240
- /**
241
- * Send message.
242
- * NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
243
- */
244
- public async send(message: Message): Promise<void> {
245
- if (this._ready.state !== TriggerState.RESOLVED) {
246
- log('waiting for websocket to become ready');
247
- await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
248
- }
249
-
250
- if (!this._currentConnection) {
251
- throw new EdgeConnectionClosedError();
252
- }
253
-
254
- if (
255
- message.source &&
256
- (message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)
257
- ) {
258
- throw new EdgeIdentityChangedError();
259
- }
260
-
261
- this._currentConnection.send(message);
262
- }
263
-
264
264
  private async _createAuthHeader(path: string): Promise<string | undefined> {
265
265
  const httpUrl = new URL(path, this._baseHttpUrl);
266
266
  httpUrl.protocol = getEdgeUrlWithProtocol(this._baseWsUrl.toString(), 'http');
@@ -277,7 +277,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
277
277
  }
278
278
 
279
279
  const encodePresentationWsAuthHeader = (encodedPresentation: Uint8Array): string => {
280
- // = and / characters are not allowed in the WebSocket subprotocol header.
280
+ // '=' and '/' characters are not allowed in the WebSocket subprotocol header.
281
281
  const encodedToken = Buffer.from(encodedPresentation).toString('base64').replace(/=*$/, '').replaceAll('/', '|');
282
282
  return `base64url.bearer.authorization.dxos.org.${encodedToken}`;
283
283
  };
@@ -10,39 +10,43 @@ import { Context } from '@dxos/context';
10
10
  import { type PublicKey, type SpaceId } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
12
  import {
13
- type CreateAgentResponseBody,
14
13
  type CreateAgentRequestBody,
14
+ type CreateAgentResponseBody,
15
15
  type CreateSpaceRequest,
16
16
  type CreateSpaceResponseBody,
17
17
  EdgeAuthChallengeError,
18
18
  EdgeCallFailedError,
19
19
  type EdgeHttpResponse,
20
+ type EdgeStatus,
20
21
  type ExecuteWorkflowResponseBody,
22
+ type ExportBundleRequest,
23
+ type ExportBundleResponse,
21
24
  type GetAgentStatusResponseBody,
22
25
  type GetNotarizationResponseBody,
26
+ type ImportBundleRequest,
23
27
  type InitiateOAuthFlowRequest,
24
28
  type InitiateOAuthFlowResponse,
25
29
  type JoinSpaceRequest,
26
30
  type JoinSpaceResponseBody,
27
- type RecoverIdentityRequest,
28
- type RecoverIdentityResponseBody,
29
31
  type ObjectId,
30
32
  type PostNotarizationRequestBody,
31
- type QueueQuery,
32
33
  type QueryResult,
34
+ type QueueQuery,
35
+ type RecoverIdentityRequest,
36
+ type RecoverIdentityResponseBody,
33
37
  type UploadFunctionRequest,
34
38
  type UploadFunctionResponseBody,
35
- type EdgeStatus,
36
39
  } from '@dxos/protocols';
37
40
  import { createUrl } from '@dxos/util';
38
41
 
39
42
  import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
40
- import { encodeAuthHeader, HttpConfig, withLogging, withRetryConfig } from './http-client';
43
+ import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
41
44
  import { getEdgeUrlWithProtocol } from './utils';
42
45
 
43
46
  const DEFAULT_RETRY_TIMEOUT = 1500;
44
47
  const DEFAULT_RETRY_JITTER = 500;
45
48
  const DEFAULT_MAX_RETRIES_COUNT = 3;
49
+ const WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB
46
50
 
47
51
  export type RetryConfig = {
48
52
  /**
@@ -64,6 +68,15 @@ type EdgeHttpRequestArgs = {
64
68
  context?: Context;
65
69
  retry?: RetryConfig;
66
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;
67
80
  };
68
81
 
69
82
  export type EdgeHttpGetArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry'>;
@@ -246,8 +259,63 @@ export class EdgeHttpClient {
246
259
  body: UploadFunctionRequest,
247
260
  args?: EdgeHttpGetArgs,
248
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
+
249
275
  const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
250
- return this._call(new URL(path, this.baseUrl), { ...args, body, method: 'PUT' });
276
+ return this._call(new URL(path, this.baseUrl), {
277
+ ...args,
278
+ body: formData,
279
+ method: 'PUT',
280
+ json: false,
281
+ });
282
+ }
283
+
284
+ public async listFunctions(args?: EdgeHttpGetArgs): Promise<any> {
285
+ return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
286
+ }
287
+
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);
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
+ });
251
319
  }
252
320
 
253
321
  //
@@ -267,6 +335,38 @@ export class EdgeHttpClient {
267
335
  });
268
336
  }
269
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
+
270
370
  //
271
371
  // Internal
272
372
  //
@@ -291,7 +391,7 @@ export class EdgeHttpClient {
291
391
 
292
392
  let handledAuth = false;
293
393
  while (true) {
294
- let processingError: EdgeCallFailedError;
394
+ let processingError: EdgeCallFailedError | undefined = undefined;
295
395
  let retryAfterHeaderValue: number = Number.NaN;
296
396
  try {
297
397
  const request = createRequest(args, this._authHeader);
@@ -299,6 +399,15 @@ export class EdgeHttpClient {
299
399
  retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
300
400
  if (response.ok) {
301
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
+
302
411
  if (body.success) {
303
412
  return body.data;
304
413
  }
@@ -306,7 +415,7 @@ export class EdgeHttpClient {
306
415
  log.warn('unsuccessful edge response', { url, body });
307
416
  if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
308
417
  processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
309
- } else {
418
+ } else if (body.errorData) {
310
419
  processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
311
420
  }
312
421
  } else if (response.status === 401 && !handledAuth) {
@@ -314,16 +423,16 @@ export class EdgeHttpClient {
314
423
  handledAuth = true;
315
424
  continue;
316
425
  } else {
317
- processingError = EdgeCallFailedError.fromHttpFailure(response);
426
+ processingError = await EdgeCallFailedError.fromHttpFailure(response);
318
427
  }
319
428
  } catch (error: any) {
320
429
  processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
321
430
  }
322
431
 
323
- if (processingError.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
432
+ if (processingError?.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
324
433
  log('retrying edge request', { url, processingError });
325
434
  } else {
326
- throw processingError;
435
+ throw processingError!;
327
436
  }
328
437
  }
329
438
  }
@@ -331,7 +440,7 @@ export class EdgeHttpClient {
331
440
  private async _handleUnauthorized(response: Response): Promise<string> {
332
441
  if (!this._edgeIdentity) {
333
442
  log.warn('unauthorized response received before identity was set');
334
- throw EdgeCallFailedError.fromHttpFailure(response);
443
+ throw await EdgeCallFailedError.fromHttpFailure(response);
335
444
  }
336
445
 
337
446
  const challenge = await handleAuthChallenge(response, this._edgeIdentity);
@@ -339,11 +448,32 @@ export class EdgeHttpClient {
339
448
  }
340
449
  }
341
450
 
342
- const createRequest = ({ method, body }: EdgeHttpRequestArgs, authHeader: string | undefined): RequestInit => {
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
+
343
473
  return {
344
474
  method,
345
- body: body && JSON.stringify(body),
346
- headers: authHeader ? { Authorization: authHeader } : undefined,
475
+ body: requestBody,
476
+ headers,
347
477
  };
348
478
  };
349
479
 
@@ -374,3 +504,10 @@ const createRetryHandler = ({ retry }: EdgeHttpRequestArgs) => {
374
504
  return true;
375
505
  };
376
506
  };
507
+
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
 
@@ -18,14 +18,14 @@ describe('HttpClient', () => {
18
18
  server = await createTestServer(responseHandler((attempt) => (attempt > 2 ? { value: 100 } : false)));
19
19
  });
20
20
 
21
- // eslint-disable-next-line mocha/no-top-level-hooks
22
21
  afterEach(() => {
23
22
  server?.close();
24
23
  server = undefined;
25
24
  });
26
25
 
27
- // TODO(burdon): Auth headers.
26
+ // TODO(burdon): Auth headers/API key for admin.
28
27
  // TODO(burdon): Add request/response schema type checking.
28
+ // TODO(burdon): Test swarm.
29
29
  it.skipIf(process.env.CI)('should retry', async ({ expect }) => {
30
30
  invariant(server);
31
31
 
package/src/index.ts CHANGED
@@ -4,11 +4,12 @@
4
4
 
5
5
  export * from '@dxos/protocols/buf/dxos/edge/messenger_pb';
6
6
 
7
- export * from './edge-client';
7
+ export * from './auth';
8
8
  export * from './defs';
9
- export * from './protocol';
9
+ export * from './edge-client';
10
10
  export * from './errors';
11
- export * from './auth';
11
+ export * from './protocol';
12
12
  export * from './edge-http-client';
13
13
  export * from './edge-identity';
14
14
  export * from './edge-ws-muxer';
15
+ export * from './http-client';
@@ -8,7 +8,7 @@ import { Trigger } from '@dxos/async';
8
8
  import { log } from '@dxos/log';
9
9
  import { EdgeWebsocketProtocol } from '@dxos/protocols';
10
10
  import { buf } from '@dxos/protocols/buf';
11
- import { MessageSchema, TextMessageSchema, type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
11
+ import { type Message, MessageSchema, TextMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
12
12
 
13
13
  import { protocol } from '../defs';
14
14
  import { WebSocketMuxer } from '../edge-ws-muxer';
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import WebSocket from 'isomorphic-ws';
6
- import { describe, expect, test, onTestFinished } from 'vitest';
6
+ import { describe, expect, onTestFinished, test } from 'vitest';
7
7
 
8
8
  import { Trigger, TriggerState } from '@dxos/async';
9
9