@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.
- package/dist/lib/browser/{chunk-VHS3XEIX.mjs → chunk-IKP53CBQ.mjs} +51 -18
- package/dist/lib/browser/{chunk-VHS3XEIX.mjs.map → chunk-IKP53CBQ.mjs.map} +3 -3
- package/dist/lib/browser/edge-ws-muxer.mjs +1 -1
- package/dist/lib/browser/index.mjs +566 -350
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +61 -16
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/{chunk-HGQUUFIJ.mjs → chunk-DR5YNW5K.mjs} +51 -18
- package/dist/lib/node-esm/{chunk-HGQUUFIJ.mjs.map → chunk-DR5YNW5K.mjs.map} +3 -3
- package/dist/lib/node-esm/edge-ws-muxer.mjs +1 -1
- package/dist/lib/node-esm/index.mjs +566 -350
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +61 -16
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/edge-client.d.ts +15 -15
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +52 -30
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.test.d.ts +2 -0
- package/dist/types/src/edge-http-client.test.d.ts.map +1 -0
- package/dist/types/src/edge-ws-connection.d.ts +1 -0
- package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
- package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
- package/dist/types/src/http-client.d.ts +22 -0
- package/dist/types/src/http-client.d.ts.map +1 -0
- package/dist/types/src/http-client.test.d.ts +2 -0
- package/dist/types/src/http-client.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-server.d.ts +9 -0
- package/dist/types/src/testing/test-server.d.ts.map +1 -0
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -14
- package/src/edge-client.ts +40 -40
- package/src/edge-http-client.test.ts +22 -0
- package/src/edge-http-client.ts +330 -140
- package/src/edge-ws-connection.ts +11 -3
- package/src/edge-ws-muxer.ts +1 -1
- package/src/http-client.test.ts +55 -0
- package/src/http-client.ts +67 -0
- package/src/index.ts +4 -3
- package/src/testing/index.ts +1 -0
- package/src/testing/test-server.ts +45 -0
- package/src/testing/test-utils.ts +2 -2
- package/src/websocket.test.ts +1 -1
- package/dist/lib/node/chunk-XNHBUTNB.cjs +0 -317
- package/dist/lib/node/chunk-XNHBUTNB.cjs.map +0 -7
- package/dist/lib/node/edge-ws-muxer.cjs +0 -33
- package/dist/lib/node/edge-ws-muxer.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -1060
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -169
- package/dist/lib/node/testing/index.cjs.map +0 -7
package/src/edge-http-client.ts
CHANGED
|
@@ -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
|
|
26
|
+
type ImportBundleRequest,
|
|
27
|
+
type InitiateOAuthFlowRequest,
|
|
28
|
+
type InitiateOAuthFlowResponse,
|
|
14
29
|
type JoinSpaceRequest,
|
|
15
30
|
type JoinSpaceResponseBody,
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
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`,
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
164
|
+
//
|
|
165
|
+
// Invitations
|
|
166
|
+
//
|
|
167
|
+
|
|
168
|
+
public async joinSpaceByInvitation(
|
|
106
169
|
spaceId: SpaceId,
|
|
107
|
-
|
|
108
|
-
input: any,
|
|
170
|
+
body: JoinSpaceRequest,
|
|
109
171
|
args?: EdgeHttpGetArgs,
|
|
110
|
-
): Promise<
|
|
111
|
-
return this._call(`/
|
|
172
|
+
): Promise<JoinSpaceResponseBody> {
|
|
173
|
+
return this._call(new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
112
174
|
}
|
|
113
175
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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(
|
|
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
|
-
|
|
183
|
-
method: '
|
|
278
|
+
body: formData,
|
|
279
|
+
method: 'PUT',
|
|
280
|
+
json: false,
|
|
184
281
|
});
|
|
185
282
|
}
|
|
186
283
|
|
|
187
|
-
async
|
|
188
|
-
return this._call('/
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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,
|
|
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', {
|
|
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
|
-
|
|
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
|
|
242
|
-
log('retrying edge request', {
|
|
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('
|
|
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
|
-
|
|
256
|
-
log('auth header updated');
|
|
257
|
-
return this._authHeader;
|
|
447
|
+
return encodeAuthHeader(challenge);
|
|
258
448
|
}
|
|
259
449
|
}
|
|
260
450
|
|
|
261
|
-
const
|
|
262
|
-
|
|
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 =
|
|
267
|
-
const baseTimeout =
|
|
268
|
-
const 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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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 {
|
|
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
|
-
|
|
176
|
-
|
|
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,
|
package/src/edge-ws-muxer.ts
CHANGED
|
@@ -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 {
|
|
8
|
+
import { type Message, MessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
|
|
9
9
|
|
|
10
10
|
import { protocol } from './defs';
|
|
11
11
|
|