@dxos/edge-client 0.8.3 → 0.8.4-main.28f8d3d

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 (56) hide show
  1. package/dist/lib/browser/{chunk-VHS3XEIX.mjs → chunk-SUXH7FH6.mjs} +15 -8
  2. package/dist/lib/browser/{chunk-VHS3XEIX.mjs.map → chunk-SUXH7FH6.mjs.map} +3 -3
  3. package/dist/lib/browser/edge-ws-muxer.mjs +1 -1
  4. package/dist/lib/browser/index.mjs +239 -149
  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-R6K4IIBW.mjs} +15 -8
  10. package/dist/lib/node-esm/{chunk-HGQUUFIJ.mjs.map → chunk-R6K4IIBW.mjs.map} +3 -3
  11. package/dist/lib/node-esm/edge-ws-muxer.mjs +1 -1
  12. package/dist/lib/node-esm/index.mjs +239 -149
  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 +1 -1
  18. package/dist/types/src/edge-client.d.ts.map +1 -1
  19. package/dist/types/src/edge-http-client.d.ts +33 -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/testing/index.d.ts +1 -0
  31. package/dist/types/src/testing/index.d.ts.map +1 -1
  32. package/dist/types/src/testing/test-server.d.ts +9 -0
  33. package/dist/types/src/testing/test-server.d.ts.map +1 -0
  34. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +21 -14
  37. package/src/edge-client.ts +2 -2
  38. package/src/edge-http-client.test.ts +22 -0
  39. package/src/edge-http-client.ts +192 -135
  40. package/src/edge-ws-connection.ts +11 -3
  41. package/src/edge-ws-muxer.ts +1 -1
  42. package/src/http-client.test.ts +54 -0
  43. package/src/http-client.ts +67 -0
  44. package/src/testing/index.ts +1 -0
  45. package/src/testing/test-server.ts +45 -0
  46. package/src/testing/test-utils.ts +2 -2
  47. package/src/websocket.test.ts +1 -1
  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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/edge-client",
3
- "version": "0.8.3",
3
+ "version": "0.8.4-main.28f8d3d",
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,23 +42,27 @@
39
42
  "README.md"
40
43
  ],
41
44
  "dependencies": {
45
+ "@effect/platform": "^0.90.2",
42
46
  "isomorphic-ws": "^5.0.0",
43
47
  "ws": "^8.14.2",
44
- "@dxos/async": "0.8.3",
45
- "@dxos/context": "0.8.3",
46
- "@dxos/credentials": "0.8.3",
47
- "@dxos/debug": "0.8.3",
48
- "@dxos/invariant": "0.8.3",
49
- "@dxos/keys": "0.8.3",
50
- "@dxos/keyring": "0.8.3",
51
- "@dxos/log": "0.8.3",
52
- "@dxos/crypto": "0.8.3",
53
- "@dxos/node-std": "0.8.3",
54
- "@dxos/protocols": "0.8.3",
55
- "@dxos/util": "0.8.3"
48
+ "@dxos/async": "0.8.4-main.28f8d3d",
49
+ "@dxos/context": "0.8.4-main.28f8d3d",
50
+ "@dxos/credentials": "0.8.4-main.28f8d3d",
51
+ "@dxos/crypto": "0.8.4-main.28f8d3d",
52
+ "@dxos/debug": "0.8.4-main.28f8d3d",
53
+ "@dxos/invariant": "0.8.4-main.28f8d3d",
54
+ "@dxos/keyring": "0.8.4-main.28f8d3d",
55
+ "@dxos/keys": "0.8.4-main.28f8d3d",
56
+ "@dxos/node-std": "0.8.4-main.28f8d3d",
57
+ "@dxos/log": "0.8.4-main.28f8d3d",
58
+ "@dxos/protocols": "0.8.4-main.28f8d3d",
59
+ "@dxos/util": "0.8.4-main.28f8d3d"
56
60
  },
57
61
  "devDependencies": {
58
- "@dxos/test-utils": "0.8.3"
62
+ "@dxos/test-utils": "0.8.4-main.28f8d3d"
63
+ },
64
+ "peerDependencies": {
65
+ "effect": "^3.13.3"
59
66
  },
60
67
  "publishConfig": {
61
68
  "access": "public"
@@ -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';
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, it } from 'vitest';
6
+
7
+ import { createEphemeralEdgeIdentity } from './auth';
8
+ import { EdgeHttpClient } from './edge-http-client';
9
+
10
+ // TODO(burdon): Factor out config.
11
+ const DEV_SERVER = 'https://edge.dxos.workers.dev';
12
+
13
+ describe.skipIf(process.env.CI)('EdgeHttpClient', () => {
14
+ it.only('should get status', async ({ expect }) => {
15
+ const client = new EdgeHttpClient(DEV_SERVER);
16
+ const identity = await createEphemeralEdgeIdentity();
17
+ client.setIdentity(identity);
18
+
19
+ const result = await client.getStatus();
20
+ expect(result).toBeDefined();
21
+ });
22
+ });
@@ -2,42 +2,73 @@
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 GetAgentStatusResponseBody,
12
23
  type GetNotarizationResponseBody,
13
- type PostNotarizationRequestBody,
24
+ type InitiateOAuthFlowRequest,
25
+ type InitiateOAuthFlowResponse,
14
26
  type JoinSpaceRequest,
15
27
  type JoinSpaceResponseBody,
16
- EdgeAuthChallengeError,
17
- type CreateAgentResponseBody,
18
- type CreateAgentRequestBody,
19
- type GetAgentStatusResponseBody,
28
+ type ObjectId,
29
+ type PostNotarizationRequestBody,
30
+ type QueryResult,
31
+ type QueueQuery,
20
32
  type RecoverIdentityRequest,
21
33
  type RecoverIdentityResponseBody,
22
34
  type UploadFunctionRequest,
23
35
  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
36
  } from '@dxos/protocols';
37
+ import { createUrl } from '@dxos/util';
33
38
 
34
39
  import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
40
+ import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
35
41
  import { getEdgeUrlWithProtocol } from './utils';
36
42
 
37
43
  const DEFAULT_RETRY_TIMEOUT = 1500;
38
44
  const DEFAULT_RETRY_JITTER = 500;
39
45
  const DEFAULT_MAX_RETRIES_COUNT = 3;
40
46
 
47
+ export type RetryConfig = {
48
+ /**
49
+ * A number of call retries, not counting the initial request.
50
+ */
51
+ count: number;
52
+ /**
53
+ * Delay before retries in ms.
54
+ */
55
+ timeout?: number;
56
+ /**
57
+ * A random amount of time before retrying to help prevent large bursts of requests.
58
+ */
59
+ jitter?: number;
60
+ };
61
+
62
+ type EdgeHttpRequestArgs = {
63
+ method: string;
64
+ context?: Context;
65
+ retry?: RetryConfig;
66
+ body?: any;
67
+ };
68
+
69
+ export type EdgeHttpGetArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry'>;
70
+ export type EdgeHttpPostArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry' | 'body'>;
71
+
41
72
  export class EdgeHttpClient {
42
73
  private readonly _baseUrl: string;
43
74
 
@@ -64,19 +95,38 @@ export class EdgeHttpClient {
64
95
  }
65
96
  }
66
97
 
98
+ //
99
+ // Status
100
+ //
101
+
102
+ public async getStatus(args?: EdgeHttpGetArgs): Promise<EdgeStatus> {
103
+ return this._call(new URL('/status', this.baseUrl), { ...args, method: 'GET' });
104
+ }
105
+
106
+ //
107
+ // Agents
108
+ //
109
+
67
110
  public createAgent(body: CreateAgentRequestBody, args?: EdgeHttpGetArgs): Promise<CreateAgentResponseBody> {
68
- return this._call('/agents/create', { ...args, method: 'POST', body });
111
+ return this._call(new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });
69
112
  }
70
113
 
71
114
  public getAgentStatus(
72
115
  request: { ownerIdentityKey: PublicKey },
73
116
  args?: EdgeHttpGetArgs,
74
117
  ): Promise<GetAgentStatusResponseBody> {
75
- return this._call(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, { ...args, method: 'GET' });
118
+ return this._call(new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
119
+ ...args,
120
+ method: 'GET',
121
+ });
76
122
  }
77
123
 
124
+ //
125
+ // Credentials
126
+ //
127
+
78
128
  public getCredentialsForNotarization(spaceId: SpaceId, args?: EdgeHttpGetArgs): Promise<GetNotarizationResponseBody> {
79
- return this._call(`/spaces/${spaceId}/notarization`, { ...args, method: 'GET' });
129
+ return this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });
80
130
  }
81
131
 
82
132
  public async notarizeCredentials(
@@ -84,49 +134,59 @@ export class EdgeHttpClient {
84
134
  body: PostNotarizationRequestBody,
85
135
  args?: EdgeHttpGetArgs,
86
136
  ): Promise<void> {
87
- await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
137
+ await this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
88
138
  }
89
139
 
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
- }
140
+ //
141
+ // Identity
142
+ //
97
143
 
98
144
  public async recoverIdentity(
99
145
  body: RecoverIdentityRequest,
100
146
  args?: EdgeHttpGetArgs,
101
147
  ): Promise<RecoverIdentityResponseBody> {
102
- return this._call('/identity/recover', { ...args, body, method: 'POST' });
148
+ return this._call(new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
103
149
  }
104
150
 
105
- public async executeWorkflow(
151
+ //
152
+ // Invitations
153
+ //
154
+
155
+ public async joinSpaceByInvitation(
106
156
  spaceId: SpaceId,
107
- graphId: ObjectId,
108
- input: any,
157
+ body: JoinSpaceRequest,
109
158
  args?: EdgeHttpGetArgs,
110
- ): Promise<ExecuteWorkflowResponseBody> {
111
- return this._call(`/workflows/${spaceId}/${graphId}`, { ...args, body: input, method: 'POST' });
159
+ ): Promise<JoinSpaceResponseBody> {
160
+ return this._call(new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
112
161
  }
113
162
 
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' });
163
+ //
164
+ // OAuth and credentials
165
+ //
166
+
167
+ public async listFunctions(args?: EdgeHttpGetArgs): Promise<any> {
168
+ return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
121
169
  }
122
170
 
123
171
  public async initiateOAuthFlow(
124
172
  body: InitiateOAuthFlowRequest,
125
173
  args?: EdgeHttpGetArgs,
126
174
  ): Promise<InitiateOAuthFlowResponse> {
127
- return this._call('/oauth/initiate', { ...args, body, method: 'POST' });
175
+ return this._call(new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
128
176
  }
129
177
 
178
+ //
179
+ // Spaces
180
+ //
181
+
182
+ async createSpace(body: CreateSpaceRequest, args?: EdgeHttpGetArgs): Promise<CreateSpaceResponseBody> {
183
+ return this._call(new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
184
+ }
185
+
186
+ //
187
+ // Queues
188
+ //
189
+
130
190
  public async queryQueue(
131
191
  subspaceTag: string,
132
192
  spaceId: SpaceId,
@@ -134,26 +194,19 @@ export class EdgeHttpClient {
134
194
  args?: EdgeHttpGetArgs,
135
195
  ): Promise<QueryResult> {
136
196
  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
- });
197
+ return this._call(
198
+ createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
199
+ after: query.after,
200
+ before: query.before,
201
+ limit: query.limit,
202
+ reverse: query.reverse,
203
+ objectIds: query.objectIds?.join(','),
204
+ }),
205
+ {
206
+ ...args,
207
+ method: 'GET',
208
+ },
209
+ );
157
210
  }
158
211
 
159
212
  public async insertIntoQueue(
@@ -163,72 +216,105 @@ export class EdgeHttpClient {
163
216
  objects: unknown[],
164
217
  args?: EdgeHttpGetArgs,
165
218
  ): Promise<void> {
166
- return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
219
+ return this._call(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
167
220
  ...args,
168
221
  body: { objects },
169
222
  method: 'POST',
170
223
  });
171
224
  }
172
225
 
173
- async deleteFromQueue(
226
+ public async deleteFromQueue(
174
227
  subspaceTag: string,
175
228
  spaceId: SpaceId,
176
229
  queueId: ObjectId,
177
230
  objectIds: ObjectId[],
178
231
  args?: EdgeHttpGetArgs,
179
232
  ): Promise<void> {
180
- return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
233
+ return this._call(
234
+ createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
235
+ ids: objectIds.join(','),
236
+ }),
237
+ {
238
+ ...args,
239
+ method: 'DELETE',
240
+ },
241
+ );
242
+ }
243
+
244
+ //
245
+ // Functions
246
+ //
247
+
248
+ public async uploadFunction(
249
+ pathParts: { functionId?: string },
250
+ body: UploadFunctionRequest,
251
+ args?: EdgeHttpGetArgs,
252
+ ): Promise<UploadFunctionResponseBody> {
253
+ const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
254
+ return this._call(new URL(path, this.baseUrl), { ...args, body, method: 'PUT' });
255
+ }
256
+
257
+ //
258
+ // Workflows
259
+ //
260
+
261
+ public async executeWorkflow(
262
+ spaceId: SpaceId,
263
+ graphId: ObjectId,
264
+ input: any,
265
+ args?: EdgeHttpGetArgs,
266
+ ): Promise<ExecuteWorkflowResponseBody> {
267
+ return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
181
268
  ...args,
182
- query: { ids: objectIds.join(',') },
183
- method: 'DELETE',
269
+ body: input,
270
+ method: 'POST',
184
271
  });
185
272
  }
186
273
 
187
- async createSpace(body: CreateSpaceRequest, args?: EdgeHttpGetArgs): Promise<CreateSpaceResponseBody> {
188
- return this._call('/spaces/create', { ...args, body, method: 'POST' });
274
+ //
275
+ // Internal
276
+ //
277
+
278
+ private async _fetch<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
279
+ return pipe(
280
+ HttpClient.get(url),
281
+ withLogging,
282
+ withRetryConfig,
283
+ Effect.provide(FetchHttpClient.layer),
284
+ Effect.provide(HttpConfig.default),
285
+ Effect.withSpan('EdgeHttpClient'),
286
+ Effect.runPromise,
287
+ ) as T;
189
288
  }
190
289
 
191
- private async _call<T>(path: string, args: EdgeHttpCallArgs): Promise<T> {
192
- const requestContext = args.context ?? new Context();
290
+ // TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
291
+ private async _call<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
193
292
  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()}`;
202
- }
203
-
204
- log('call', { method: args.method, path, request: args.body });
293
+ const requestContext = args.context ?? new Context();
294
+ log('fetch', { url, request: args.body });
205
295
 
206
296
  let handledAuth = false;
207
- let authHeader = this._authHeader;
208
297
  while (true) {
209
298
  let processingError: EdgeCallFailedError;
210
299
  let retryAfterHeaderValue: number = Number.NaN;
211
300
  try {
212
- const request = createRequest(args, authHeader);
301
+ const request = createRequest(args, this._authHeader);
213
302
  const response = await fetch(url, request);
214
-
215
303
  retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
216
-
217
304
  if (response.ok) {
218
305
  const body = (await response.json()) as EdgeHttpResponse<T>;
219
306
  if (body.success) {
220
307
  return body.data;
221
308
  }
222
309
 
223
- log('unsuccessful edge response', { path, body });
224
-
310
+ log.warn('unsuccessful edge response', { url, body });
225
311
  if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
226
312
  processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
227
313
  } else {
228
314
  processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
229
315
  }
230
316
  } else if (response.status === 401 && !handledAuth) {
231
- authHeader = await this._handleUnauthorized(response);
317
+ this._authHeader = await this._handleUnauthorized(response);
232
318
  handledAuth = true;
233
319
  continue;
234
320
  } else {
@@ -239,7 +325,7 @@ export class EdgeHttpClient {
239
325
  }
240
326
 
241
327
  if (processingError.isRetryable && (await shouldRetry(requestContext, retryAfterHeaderValue))) {
242
- log('retrying edge request', { path, processingError });
328
+ log('retrying edge request', { url, processingError });
243
329
  } else {
244
330
  throw processingError;
245
331
  }
@@ -248,24 +334,35 @@ export class EdgeHttpClient {
248
334
 
249
335
  private async _handleUnauthorized(response: Response): Promise<string> {
250
336
  if (!this._edgeIdentity) {
251
- log.warn('edge unauthorized response received before identity was set');
337
+ log.warn('unauthorized response received before identity was set');
252
338
  throw EdgeCallFailedError.fromHttpFailure(response);
253
339
  }
340
+
254
341
  const challenge = await handleAuthChallenge(response, this._edgeIdentity);
255
- this._authHeader = encodeAuthHeader(challenge);
256
- log('auth header updated');
257
- return this._authHeader;
342
+ return encodeAuthHeader(challenge);
258
343
  }
259
344
  }
260
345
 
261
- const createRetryHandler = (args: EdgeHttpCallArgs) => {
262
- if (!args.retry || args.retry.count < 1) {
346
+ const createRequest = ({ method, body }: EdgeHttpRequestArgs, authHeader: string | undefined): RequestInit => {
347
+ return {
348
+ method,
349
+ body: body && JSON.stringify(body),
350
+ headers: authHeader ? { Authorization: authHeader } : undefined,
351
+ };
352
+ };
353
+
354
+ /**
355
+ * @deprecated
356
+ */
357
+ const createRetryHandler = ({ retry }: EdgeHttpRequestArgs) => {
358
+ if (!retry || retry.count < 1) {
263
359
  return async () => false;
264
360
  }
361
+
265
362
  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;
363
+ const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
364
+ const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
365
+ const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;
269
366
  return async (ctx: Context, retryAfter: number) => {
270
367
  if (++retries > maxRetries || ctx.disposed) {
271
368
  return false;
@@ -281,43 +378,3 @@ const createRetryHandler = (args: EdgeHttpCallArgs) => {
281
378
  return true;
282
379
  };
283
380
  };
284
-
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
- };
@@ -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