@dxos/edge-client 0.8.3 → 0.8.4-main.05e74ebcff
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/LICENSE +102 -5
- package/dist/lib/{browser/chunk-VHS3XEIX.mjs → neutral/chunk-ZIQ5T3A7.mjs} +20 -50
- package/dist/lib/{browser/chunk-VHS3XEIX.mjs.map → neutral/chunk-ZIQ5T3A7.mjs.map} +3 -3
- package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
- package/dist/lib/neutral/index.mjs +1238 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +53 -33
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/auth.d.ts.map +1 -1
- package/dist/types/src/edge-client.d.ts +18 -15
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +132 -37
- 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-identity.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.d.ts +21 -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/errors.d.ts.map +1 -1
- package/dist/types/src/http-client.d.ts +25 -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/protocol.d.ts +1 -1
- package/dist/types/src/protocol.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 +3 -3
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +1 -1
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -30
- package/src/edge-client.test.ts +20 -15
- package/src/edge-client.ts +90 -43
- package/src/edge-http-client.test.ts +23 -0
- package/src/edge-http-client.ts +606 -161
- package/src/edge-ws-connection.ts +131 -9
- package/src/edge-ws-muxer.ts +1 -1
- package/src/http-client.test.ts +58 -0
- package/src/http-client.ts +77 -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 +9 -9
- package/src/websocket.test.ts +1 -1
- package/dist/lib/browser/index.mjs +0 -1034
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- 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/dist/lib/node-esm/chunk-HGQUUFIJ.mjs +0 -299
- package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/edge-ws-muxer.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1035
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -141
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- /package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs.map +0 -0
package/src/edge-http-client.ts
CHANGED
|
@@ -2,44 +2,128 @@
|
|
|
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
|
-
import { Context } from '@dxos/context';
|
|
11
|
+
import { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } 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
|
+
EDGE_CLIENT_TAG_HEADER,
|
|
22
|
+
EdgeAuthChallengeError,
|
|
10
23
|
EdgeCallFailedError,
|
|
11
|
-
type
|
|
24
|
+
type EdgeFailure,
|
|
25
|
+
type EdgeStatus,
|
|
26
|
+
type ExecuteWorkflowResponseBody,
|
|
27
|
+
type ExportBundleRequest,
|
|
28
|
+
type ExportBundleResponse,
|
|
29
|
+
type FeedProtocol,
|
|
30
|
+
type GetAgentStatusResponseBody,
|
|
31
|
+
type GetPluginVersionsResponseBody,
|
|
32
|
+
type GetPluginsResponseBody,
|
|
12
33
|
type GetNotarizationResponseBody,
|
|
13
|
-
type
|
|
34
|
+
type ImportBundleRequest,
|
|
35
|
+
type InitiateOAuthFlowRequest,
|
|
36
|
+
type InitiateOAuthFlowResponse,
|
|
14
37
|
type JoinSpaceRequest,
|
|
15
38
|
type JoinSpaceResponseBody,
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type CreateAgentRequestBody,
|
|
19
|
-
type GetAgentStatusResponseBody,
|
|
39
|
+
type ObjectId,
|
|
40
|
+
type PostNotarizationRequestBody,
|
|
20
41
|
type RecoverIdentityRequest,
|
|
21
42
|
type RecoverIdentityResponseBody,
|
|
22
43
|
type UploadFunctionRequest,
|
|
23
44
|
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
45
|
} from '@dxos/protocols';
|
|
46
|
+
import {
|
|
47
|
+
type QueryRequest as QueryRequestProto,
|
|
48
|
+
type QueryResponse as QueryResponseProto,
|
|
49
|
+
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
50
|
+
import { createUrl } from '@dxos/util';
|
|
33
51
|
|
|
34
52
|
import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
|
|
53
|
+
import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
|
|
35
54
|
import { getEdgeUrlWithProtocol } from './utils';
|
|
36
55
|
|
|
56
|
+
/**
|
|
57
|
+
* HTTP wire shape returned by `/queue/.../query`. Unlike `FeedProtocol.QueryResult`
|
|
58
|
+
* (the RPC proto type, which transports object payloads as JSON strings), the edge
|
|
59
|
+
* HTTP endpoint embeds each object directly in the response JSON.
|
|
60
|
+
*/
|
|
61
|
+
export type EdgeQueryQueueResponse = {
|
|
62
|
+
objects?: unknown[];
|
|
63
|
+
nextCursor?: string;
|
|
64
|
+
prevCursor?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
37
67
|
const DEFAULT_RETRY_TIMEOUT = 1500;
|
|
38
68
|
const DEFAULT_RETRY_JITTER = 500;
|
|
39
69
|
const DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
70
|
+
const WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
|
71
|
+
|
|
72
|
+
// TEMPORARY: legacy standalone CORS proxy used by `proxyFetch` until the
|
|
73
|
+
// authenticated `/proxy/*` route on the main edge worker ships
|
|
74
|
+
// (https://github.com/dxos/edge/pull/576). Delete this constant when the
|
|
75
|
+
// commented-out authenticated branch in `proxyFetch` is restored.
|
|
76
|
+
const LEGACY_CORS_PROXY_URL = 'https://cors-proxy.dxos.workers.dev';
|
|
77
|
+
|
|
78
|
+
export type RetryConfig = {
|
|
79
|
+
/**
|
|
80
|
+
* A number of call retries, not counting the initial request.
|
|
81
|
+
*/
|
|
82
|
+
count: number;
|
|
83
|
+
/**
|
|
84
|
+
* Delay before retries in ms.
|
|
85
|
+
*/
|
|
86
|
+
timeout?: number;
|
|
87
|
+
/**
|
|
88
|
+
* A random amount of time before retrying to help prevent large bursts of requests.
|
|
89
|
+
*/
|
|
90
|
+
jitter?: number;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type EdgeHttpRequestArgs = {
|
|
94
|
+
method: string;
|
|
95
|
+
retry?: RetryConfig;
|
|
96
|
+
body?: any;
|
|
97
|
+
/**
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
json?: boolean;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Force authentication.
|
|
104
|
+
* This should be used for requests with large bodies to avoid sending the body twice.
|
|
105
|
+
* The client will call /auth endpoint to generate the auth header.
|
|
106
|
+
*/
|
|
107
|
+
auth?: boolean;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type EdgeHttpCallArgs = Pick<EdgeHttpRequestArgs, 'retry' | 'auth'>;
|
|
111
|
+
|
|
112
|
+
export type GetCronTriggersResponse = {
|
|
113
|
+
cronIds: string[];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type EdgeHttpClientOptions = {
|
|
117
|
+
/**
|
|
118
|
+
* Tag included in the {@link EDGE_CLIENT_TAG_HEADER} header on every request.
|
|
119
|
+
* Used on Edge to classify traffic for metering (e.g. `ci-e2e`).
|
|
120
|
+
*/
|
|
121
|
+
clientTag?: string;
|
|
122
|
+
};
|
|
40
123
|
|
|
41
124
|
export class EdgeHttpClient {
|
|
42
125
|
private readonly _baseUrl: string;
|
|
126
|
+
private readonly _clientTag: string | undefined;
|
|
43
127
|
|
|
44
128
|
private _edgeIdentity: EdgeIdentity | undefined;
|
|
45
129
|
|
|
@@ -48,8 +132,9 @@ export class EdgeHttpClient {
|
|
|
48
132
|
*/
|
|
49
133
|
private _authHeader: string | undefined;
|
|
50
134
|
|
|
51
|
-
constructor(baseUrl: string) {
|
|
135
|
+
constructor(baseUrl: string, options?: EdgeHttpClientOptions) {
|
|
52
136
|
this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');
|
|
137
|
+
this._clientTag = options?.clientTag;
|
|
53
138
|
log('created', { url: this._baseUrl });
|
|
54
139
|
}
|
|
55
140
|
|
|
@@ -64,209 +149,560 @@ export class EdgeHttpClient {
|
|
|
64
149
|
}
|
|
65
150
|
}
|
|
66
151
|
|
|
67
|
-
|
|
68
|
-
|
|
152
|
+
//
|
|
153
|
+
// Status
|
|
154
|
+
//
|
|
155
|
+
|
|
156
|
+
public async getStatus(ctx: Context, args?: EdgeHttpCallArgs): Promise<EdgeStatus> {
|
|
157
|
+
return this._call(ctx, new URL('/status', this.baseUrl), { ...args, method: 'GET', auth: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//
|
|
161
|
+
// Agents
|
|
162
|
+
//
|
|
163
|
+
|
|
164
|
+
public createAgent(
|
|
165
|
+
ctx: Context,
|
|
166
|
+
body: CreateAgentRequestBody,
|
|
167
|
+
args?: EdgeHttpCallArgs,
|
|
168
|
+
): Promise<CreateAgentResponseBody> {
|
|
169
|
+
return this._call(ctx, new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });
|
|
69
170
|
}
|
|
70
171
|
|
|
71
172
|
public getAgentStatus(
|
|
173
|
+
ctx: Context,
|
|
72
174
|
request: { ownerIdentityKey: PublicKey },
|
|
73
|
-
args?:
|
|
175
|
+
args?: EdgeHttpCallArgs,
|
|
74
176
|
): Promise<GetAgentStatusResponseBody> {
|
|
75
|
-
return this._call(`/users/${request.ownerIdentityKey.toHex()}/agent/status`,
|
|
177
|
+
return this._call(ctx, new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
|
|
178
|
+
...args,
|
|
179
|
+
method: 'GET',
|
|
180
|
+
});
|
|
76
181
|
}
|
|
77
182
|
|
|
78
|
-
|
|
79
|
-
|
|
183
|
+
//
|
|
184
|
+
// Credentials
|
|
185
|
+
//
|
|
186
|
+
|
|
187
|
+
public getCredentialsForNotarization(
|
|
188
|
+
ctx: Context,
|
|
189
|
+
spaceId: SpaceId,
|
|
190
|
+
args?: EdgeHttpCallArgs,
|
|
191
|
+
): Promise<GetNotarizationResponseBody> {
|
|
192
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });
|
|
80
193
|
}
|
|
81
194
|
|
|
82
195
|
public async notarizeCredentials(
|
|
196
|
+
ctx: Context,
|
|
83
197
|
spaceId: SpaceId,
|
|
84
198
|
body: PostNotarizationRequestBody,
|
|
85
|
-
args?:
|
|
199
|
+
args?: EdgeHttpCallArgs,
|
|
86
200
|
): Promise<void> {
|
|
87
|
-
await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
|
|
201
|
+
await this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
88
202
|
}
|
|
89
203
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
args?: EdgeHttpGetArgs,
|
|
94
|
-
): Promise<JoinSpaceResponseBody> {
|
|
95
|
-
return this._call(`/spaces/${spaceId}/join`, { ...args, body, method: 'POST' });
|
|
96
|
-
}
|
|
204
|
+
//
|
|
205
|
+
// Identity
|
|
206
|
+
//
|
|
97
207
|
|
|
98
208
|
public async recoverIdentity(
|
|
209
|
+
ctx: Context,
|
|
99
210
|
body: RecoverIdentityRequest,
|
|
100
|
-
args?:
|
|
211
|
+
args?: EdgeHttpCallArgs,
|
|
101
212
|
): Promise<RecoverIdentityResponseBody> {
|
|
102
|
-
return this._call('/identity/recover', { ...args, body, method: 'POST' });
|
|
213
|
+
return this._call(ctx, new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
|
|
103
214
|
}
|
|
104
215
|
|
|
105
|
-
|
|
216
|
+
//
|
|
217
|
+
// Invitations
|
|
218
|
+
//
|
|
219
|
+
|
|
220
|
+
public async joinSpaceByInvitation(
|
|
221
|
+
ctx: Context,
|
|
106
222
|
spaceId: SpaceId,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return this._call(`/workflows/${spaceId}/${graphId}`, { ...args, body: input, method: 'POST' });
|
|
223
|
+
body: JoinSpaceRequest,
|
|
224
|
+
args?: EdgeHttpCallArgs,
|
|
225
|
+
): Promise<JoinSpaceResponseBody> {
|
|
226
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
112
227
|
}
|
|
113
228
|
|
|
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
|
-
}
|
|
229
|
+
//
|
|
230
|
+
// OAuth and credentials
|
|
231
|
+
//
|
|
122
232
|
|
|
123
233
|
public async initiateOAuthFlow(
|
|
234
|
+
ctx: Context,
|
|
124
235
|
body: InitiateOAuthFlowRequest,
|
|
125
|
-
args?:
|
|
236
|
+
args?: EdgeHttpCallArgs,
|
|
126
237
|
): Promise<InitiateOAuthFlowResponse> {
|
|
127
|
-
return this._call('/oauth/initiate', { ...args, body, method: 'POST' });
|
|
238
|
+
return this._call(ctx, new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//
|
|
242
|
+
// Spaces
|
|
243
|
+
//
|
|
244
|
+
|
|
245
|
+
async createSpace(ctx: Context, body: CreateSpaceRequest, args?: EdgeHttpCallArgs): Promise<CreateSpaceResponseBody> {
|
|
246
|
+
return this._call(ctx, new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
|
|
128
247
|
}
|
|
129
248
|
|
|
249
|
+
//
|
|
250
|
+
// Queues
|
|
251
|
+
//
|
|
252
|
+
|
|
130
253
|
public async queryQueue(
|
|
254
|
+
ctx: Context,
|
|
131
255
|
subspaceTag: string,
|
|
132
256
|
spaceId: SpaceId,
|
|
133
|
-
query: QueueQuery,
|
|
134
|
-
args?:
|
|
135
|
-
): Promise<
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query?${queryParams.toString()}`, {
|
|
154
|
-
...args,
|
|
155
|
-
method: 'GET',
|
|
156
|
-
});
|
|
257
|
+
query: FeedProtocol.QueueQuery,
|
|
258
|
+
args?: EdgeHttpCallArgs,
|
|
259
|
+
): Promise<EdgeQueryQueueResponse> {
|
|
260
|
+
const queueId = query.queueIds?.[0];
|
|
261
|
+
invariant(queueId, 'queueId required');
|
|
262
|
+
return this._call(
|
|
263
|
+
ctx,
|
|
264
|
+
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
265
|
+
after: query.after,
|
|
266
|
+
before: query.before,
|
|
267
|
+
limit: query.limit,
|
|
268
|
+
reverse: query.reverse,
|
|
269
|
+
objectIds: query.objectIds?.join(','),
|
|
270
|
+
}),
|
|
271
|
+
{
|
|
272
|
+
...args,
|
|
273
|
+
method: 'GET',
|
|
274
|
+
},
|
|
275
|
+
);
|
|
157
276
|
}
|
|
158
277
|
|
|
159
278
|
public async insertIntoQueue(
|
|
279
|
+
ctx: Context,
|
|
160
280
|
subspaceTag: string,
|
|
161
281
|
spaceId: SpaceId,
|
|
162
282
|
queueId: ObjectId,
|
|
163
283
|
objects: unknown[],
|
|
164
|
-
args?:
|
|
284
|
+
args?: EdgeHttpCallArgs,
|
|
165
285
|
): Promise<void> {
|
|
166
|
-
return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
|
|
286
|
+
return this._call(ctx, new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
167
287
|
...args,
|
|
168
288
|
body: { objects },
|
|
169
289
|
method: 'POST',
|
|
170
290
|
});
|
|
171
291
|
}
|
|
172
292
|
|
|
173
|
-
async deleteFromQueue(
|
|
293
|
+
public async deleteFromQueue(
|
|
294
|
+
ctx: Context,
|
|
174
295
|
subspaceTag: string,
|
|
175
296
|
spaceId: SpaceId,
|
|
176
297
|
queueId: ObjectId,
|
|
177
298
|
objectIds: ObjectId[],
|
|
178
|
-
args?:
|
|
299
|
+
args?: EdgeHttpCallArgs,
|
|
179
300
|
): Promise<void> {
|
|
180
|
-
return this._call(
|
|
301
|
+
return this._call(
|
|
302
|
+
ctx,
|
|
303
|
+
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
304
|
+
ids: objectIds.join(','),
|
|
305
|
+
}),
|
|
306
|
+
{
|
|
307
|
+
...args,
|
|
308
|
+
method: 'DELETE',
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
//
|
|
314
|
+
// Functions
|
|
315
|
+
//
|
|
316
|
+
|
|
317
|
+
public async uploadFunction(
|
|
318
|
+
ctx: Context,
|
|
319
|
+
pathParts: { functionId?: string },
|
|
320
|
+
body: UploadFunctionRequest,
|
|
321
|
+
args?: EdgeHttpCallArgs,
|
|
322
|
+
): Promise<UploadFunctionResponseBody> {
|
|
323
|
+
const formData = new FormData();
|
|
324
|
+
formData.append('name', body.name ?? '');
|
|
325
|
+
formData.append('version', body.version);
|
|
326
|
+
formData.append('ownerPublicKey', body.ownerPublicKey);
|
|
327
|
+
formData.append('entryPoint', body.entryPoint);
|
|
328
|
+
body.runtime && formData.append('runtime', body.runtime);
|
|
329
|
+
for (const [filename, content] of Object.entries(body.assets)) {
|
|
330
|
+
formData.append(
|
|
331
|
+
'assets',
|
|
332
|
+
new Blob([content as Uint8Array<ArrayBuffer>], { type: getFileMimeType(filename) }),
|
|
333
|
+
filename,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
338
|
+
return this._call(ctx, new URL(path, this.baseUrl), {
|
|
181
339
|
...args,
|
|
182
|
-
|
|
183
|
-
method: '
|
|
340
|
+
body: formData,
|
|
341
|
+
method: 'PUT',
|
|
342
|
+
json: false,
|
|
184
343
|
});
|
|
185
344
|
}
|
|
186
345
|
|
|
187
|
-
async
|
|
188
|
-
return this._call('/
|
|
346
|
+
public async listFunctions(ctx: Context, args?: EdgeHttpCallArgs): Promise<any> {
|
|
347
|
+
return this._call(ctx, new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
189
348
|
}
|
|
190
349
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
350
|
+
public async invokeFunction(
|
|
351
|
+
ctx: Context,
|
|
352
|
+
params: {
|
|
353
|
+
functionId: string;
|
|
354
|
+
version?: string;
|
|
355
|
+
spaceId?: SpaceId;
|
|
356
|
+
cpuTimeLimit?: number;
|
|
357
|
+
subrequestsLimit?: number;
|
|
358
|
+
},
|
|
359
|
+
input: unknown,
|
|
360
|
+
args?: EdgeHttpCallArgs,
|
|
361
|
+
): Promise<any> {
|
|
362
|
+
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
363
|
+
if (params.version) {
|
|
364
|
+
url.searchParams.set('version', params.version);
|
|
202
365
|
}
|
|
366
|
+
if (params.spaceId) {
|
|
367
|
+
url.searchParams.set('spaceId', params.spaceId.toString());
|
|
368
|
+
}
|
|
369
|
+
if (params.cpuTimeLimit) {
|
|
370
|
+
url.searchParams.set('cpuTimeLimit', params.cpuTimeLimit.toString());
|
|
371
|
+
}
|
|
372
|
+
if (params.subrequestsLimit) {
|
|
373
|
+
url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return this._call(ctx, url, {
|
|
377
|
+
...args,
|
|
378
|
+
body: input,
|
|
379
|
+
method: 'POST',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
//
|
|
384
|
+
// Workflows
|
|
385
|
+
//
|
|
386
|
+
|
|
387
|
+
public async executeWorkflow(
|
|
388
|
+
ctx: Context,
|
|
389
|
+
spaceId: SpaceId,
|
|
390
|
+
graphId: ObjectId,
|
|
391
|
+
input: any,
|
|
392
|
+
args?: EdgeHttpCallArgs,
|
|
393
|
+
): Promise<ExecuteWorkflowResponseBody> {
|
|
394
|
+
return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
395
|
+
...args,
|
|
396
|
+
body: input,
|
|
397
|
+
method: 'POST',
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
//
|
|
402
|
+
// Triggers
|
|
403
|
+
//
|
|
404
|
+
|
|
405
|
+
public async getCronTriggers(ctx: Context, spaceId: SpaceId): Promise<GetCronTriggersResponse> {
|
|
406
|
+
return this._call<GetCronTriggersResponse>(ctx, new URL(`/functions/${spaceId}/triggers/crons`, this.baseUrl), {
|
|
407
|
+
method: 'GET',
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
public async forceRunCronTrigger(ctx: Context, spaceId: SpaceId, triggerId: ObjectId) {
|
|
412
|
+
return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//
|
|
418
|
+
// Query
|
|
419
|
+
//
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Execute a QueryAST query against a space.
|
|
423
|
+
*/
|
|
424
|
+
public async execQuery(
|
|
425
|
+
ctx: Context,
|
|
426
|
+
spaceId: SpaceId,
|
|
427
|
+
body: QueryRequestProto,
|
|
428
|
+
args?: EdgeHttpCallArgs,
|
|
429
|
+
): Promise<QueryResponseProto> {
|
|
430
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/exec-query`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//
|
|
434
|
+
// Registry
|
|
435
|
+
//
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Fetches the hydrated plugin directory from the Edge registry service.
|
|
439
|
+
* Unauthenticated; safe to call without an identity.
|
|
440
|
+
*/
|
|
441
|
+
public async getRegistryPlugins(ctx: Context, args?: EdgeHttpCallArgs): Promise<GetPluginsResponseBody> {
|
|
442
|
+
return this._call(ctx, new URL('/registry/plugins', this.baseUrl), { ...args, method: 'GET' });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Fetches the available release versions for a given plugin repo. `repo` is the
|
|
447
|
+
* GitHub `owner/name` form; this method takes care of URL-encoding before issuing
|
|
448
|
+
* the request. Unauthenticated; same surface area as {@link getRegistryPlugins}.
|
|
449
|
+
*
|
|
450
|
+
* Versions are returned newest first, suitable for direct rendering in a picker.
|
|
451
|
+
*/
|
|
452
|
+
public async getRegistryPluginVersions(
|
|
453
|
+
ctx: Context,
|
|
454
|
+
repo: string,
|
|
455
|
+
args?: EdgeHttpCallArgs,
|
|
456
|
+
): Promise<GetPluginVersionsResponseBody> {
|
|
457
|
+
return this._call(ctx, new URL(`/registry/plugins/${encodeURIComponent(repo)}/versions`, this.baseUrl), {
|
|
458
|
+
...args,
|
|
459
|
+
method: 'GET',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
//
|
|
464
|
+
// Import/Export space.
|
|
465
|
+
//
|
|
466
|
+
|
|
467
|
+
public async importBundle(
|
|
468
|
+
ctx: Context,
|
|
469
|
+
spaceId: SpaceId,
|
|
470
|
+
body: ImportBundleRequest,
|
|
471
|
+
args?: EdgeHttpCallArgs,
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
public async exportBundle(
|
|
477
|
+
ctx: Context,
|
|
478
|
+
spaceId: SpaceId,
|
|
479
|
+
body: ExportBundleRequest,
|
|
480
|
+
args?: EdgeHttpCallArgs,
|
|
481
|
+
): Promise<ExportBundleResponse> {
|
|
482
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
483
|
+
...args,
|
|
484
|
+
body,
|
|
485
|
+
method: 'POST',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
203
488
|
|
|
204
|
-
|
|
489
|
+
//
|
|
490
|
+
// Integration proxy.
|
|
491
|
+
//
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Fetch through the edge proxy, used by integration plugins (Discord, ...)
|
|
495
|
+
* to call third-party REST APIs that don't set permissive CORS headers.
|
|
496
|
+
*
|
|
497
|
+
* `init.headers.Authorization` (caller-supplied) is preserved by prefixing
|
|
498
|
+
* with `X-Cors-Proxy-Authorization`, since the proxy strips `Authorization`
|
|
499
|
+
* on forwarding to avoid leaking the DXOS presentation upstream — the
|
|
500
|
+
* prefix carries the upstream's bot token / token through.
|
|
501
|
+
*
|
|
502
|
+
* TEMPORARY: routed through the legacy standalone proxy at
|
|
503
|
+
* `cors-proxy.dxos.workers.dev` (open, unauthenticated, path
|
|
504
|
+
* `/<host>/<path>`) so that integration plugins can be tested before the
|
|
505
|
+
* authenticated `/proxy/*` route on the main edge worker ships
|
|
506
|
+
* (https://github.com/dxos/edge/pull/576). When that PR deploys, restore
|
|
507
|
+
* the commented-out block below — it rewrites the target under
|
|
508
|
+
* `${this.baseUrl}/proxy/...` and signs the request with the cached
|
|
509
|
+
* verifiable presentation. The header-remap and `x-cors-proxy-*` override
|
|
510
|
+
* conventions are unchanged between the two paths.
|
|
511
|
+
*/
|
|
512
|
+
public async proxyFetch(target: URL, init: RequestInit = {}): Promise<Response> {
|
|
513
|
+
return proxyFetchLegacy(target, init, this._clientTag);
|
|
514
|
+
|
|
515
|
+
//
|
|
516
|
+
// Restore once the authenticated route on the main edge worker is deployed:
|
|
517
|
+
//
|
|
518
|
+
// const proxyUrl = new URL(`/proxy/${target.host}${target.pathname}${target.search}`, this.baseUrl);
|
|
519
|
+
// if (target.protocol === 'http:') {
|
|
520
|
+
// proxyUrl.searchParams.set('scheme', 'http');
|
|
521
|
+
// }
|
|
522
|
+
// const headers = remapAuthorizationForProxy(new Headers(init.headers ?? undefined));
|
|
523
|
+
// let handledAuth = false;
|
|
524
|
+
// while (true) {
|
|
525
|
+
// if (!this._authHeader) {
|
|
526
|
+
// const authResponse = await fetch(new URL('/auth', this.baseUrl));
|
|
527
|
+
// if (authResponse.status === 401) {
|
|
528
|
+
// this._authHeader = await this._handleUnauthorized(authResponse);
|
|
529
|
+
// }
|
|
530
|
+
// }
|
|
531
|
+
// const requestHeaders = new Headers(headers);
|
|
532
|
+
// if (this._authHeader) {
|
|
533
|
+
// requestHeaders.set('Authorization', this._authHeader);
|
|
534
|
+
// }
|
|
535
|
+
// if (this._clientTag) {
|
|
536
|
+
// requestHeaders.set(EDGE_CLIENT_TAG_HEADER, this._clientTag);
|
|
537
|
+
// }
|
|
538
|
+
// const response = await fetch(proxyUrl, { ...init, headers: requestHeaders });
|
|
539
|
+
// if (response.status === 401 && !handledAuth) {
|
|
540
|
+
// this._authHeader = await this._handleUnauthorized(response);
|
|
541
|
+
// handledAuth = true;
|
|
542
|
+
// continue;
|
|
543
|
+
// }
|
|
544
|
+
// return response;
|
|
545
|
+
// }
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
//
|
|
549
|
+
// Internal
|
|
550
|
+
//
|
|
551
|
+
|
|
552
|
+
private async _fetch<T>(url: URL, _args: EdgeHttpRequestArgs): Promise<T> {
|
|
553
|
+
return Function.pipe(
|
|
554
|
+
HttpClient.get(url),
|
|
555
|
+
withLogging,
|
|
556
|
+
withRetryConfig,
|
|
557
|
+
Effect.provide(FetchHttpClient.layer),
|
|
558
|
+
Effect.provide(HttpConfig.default),
|
|
559
|
+
Effect.withSpan('EdgeHttpClient'),
|
|
560
|
+
runAndForwardErrors,
|
|
561
|
+
) as T;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
|
|
565
|
+
private async _call<T>(ctx: Context, url: URL, args: EdgeHttpRequestArgs): Promise<T> {
|
|
566
|
+
const shouldRetry = createRetryHandler(args);
|
|
567
|
+
log('fetch', { url, request: args.body });
|
|
568
|
+
|
|
569
|
+
const traceHeaders = getTraceHeaders(ctx);
|
|
205
570
|
|
|
206
571
|
let handledAuth = false;
|
|
207
|
-
|
|
572
|
+
const tryCount = 1;
|
|
208
573
|
while (true) {
|
|
209
|
-
let processingError: EdgeCallFailedError;
|
|
210
|
-
let retryAfterHeaderValue: number = Number.NaN;
|
|
574
|
+
let processingError: EdgeCallFailedError | undefined = undefined;
|
|
211
575
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
576
|
+
if (!this._authHeader && args.auth) {
|
|
577
|
+
const response = await fetch(new URL(`/auth`, this.baseUrl));
|
|
578
|
+
if (response.status === 401) {
|
|
579
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
214
582
|
|
|
215
|
-
|
|
583
|
+
const request = createRequest(args, this._authHeader, traceHeaders, this._clientTag);
|
|
584
|
+
log('call edge', { url, tryCount, authHeader: !!this._authHeader });
|
|
585
|
+
const response = await fetch(url, request);
|
|
216
586
|
|
|
217
587
|
if (response.ok) {
|
|
218
|
-
const body =
|
|
588
|
+
const body = await response.clone().json();
|
|
589
|
+
invariant(body, 'Expected body to be present');
|
|
590
|
+
if (!('success' in body)) {
|
|
591
|
+
return body;
|
|
592
|
+
}
|
|
219
593
|
if (body.success) {
|
|
220
594
|
return body.data;
|
|
221
595
|
}
|
|
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
596
|
} else if (response.status === 401 && !handledAuth) {
|
|
231
|
-
|
|
597
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
232
598
|
handledAuth = true;
|
|
233
599
|
continue;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const body: EdgeFailure =
|
|
603
|
+
response.headers.get('Content-Type') === 'application/json' ? await response.clone().json() : undefined;
|
|
604
|
+
|
|
605
|
+
invariant(!body?.success, 'Expected body to not be a failure response or undefined.');
|
|
606
|
+
|
|
607
|
+
if (body?.data?.type === 'auth_challenge' && typeof body?.data?.challenge === 'string') {
|
|
608
|
+
processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);
|
|
609
|
+
} else if (body?.success === false) {
|
|
610
|
+
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
234
611
|
} else {
|
|
235
|
-
|
|
612
|
+
invariant(!response.ok, 'Expected response to not be ok.');
|
|
613
|
+
processingError = await EdgeCallFailedError.fromHttpFailure(response);
|
|
236
614
|
}
|
|
237
615
|
} catch (error: any) {
|
|
238
616
|
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
239
617
|
}
|
|
240
618
|
|
|
241
|
-
if (processingError
|
|
242
|
-
log('retrying edge request', {
|
|
619
|
+
if (processingError?.isRetryable && (await shouldRetry(ctx, processingError.retryAfterMs))) {
|
|
620
|
+
log.verbose('retrying edge request', { url, processingError });
|
|
243
621
|
} else {
|
|
244
|
-
throw processingError
|
|
622
|
+
throw processingError!;
|
|
245
623
|
}
|
|
246
624
|
}
|
|
247
625
|
}
|
|
248
626
|
|
|
249
627
|
private async _handleUnauthorized(response: Response): Promise<string> {
|
|
250
628
|
if (!this._edgeIdentity) {
|
|
251
|
-
log.warn('
|
|
252
|
-
throw EdgeCallFailedError.fromHttpFailure(response);
|
|
629
|
+
log.warn('unauthorized response received before identity was set');
|
|
630
|
+
throw await EdgeCallFailedError.fromHttpFailure(response);
|
|
253
631
|
}
|
|
632
|
+
|
|
254
633
|
const challenge = await handleAuthChallenge(response, this._edgeIdentity);
|
|
255
|
-
|
|
256
|
-
log('auth header updated');
|
|
257
|
-
return this._authHeader;
|
|
634
|
+
return encodeAuthHeader(challenge);
|
|
258
635
|
}
|
|
259
636
|
}
|
|
260
637
|
|
|
261
|
-
const
|
|
262
|
-
|
|
638
|
+
const createRequest = (
|
|
639
|
+
{ method, body, json = true }: EdgeHttpRequestArgs,
|
|
640
|
+
authHeader: string | undefined,
|
|
641
|
+
traceHeaders?: Record<string, string>,
|
|
642
|
+
clientTag?: string,
|
|
643
|
+
): RequestInit => {
|
|
644
|
+
let requestBody: BodyInit | undefined;
|
|
645
|
+
const headers: HeadersInit = {};
|
|
646
|
+
|
|
647
|
+
if (json) {
|
|
648
|
+
requestBody = body && JSON.stringify(body);
|
|
649
|
+
headers['Content-Type'] = 'application/json';
|
|
650
|
+
} else {
|
|
651
|
+
requestBody = body;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (typeof requestBody === 'string' && requestBody.length > WARNING_BODY_SIZE) {
|
|
655
|
+
log.warn('Request with large body', { bodySize: requestBody.length });
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (authHeader) {
|
|
659
|
+
headers['Authorization'] = authHeader;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (traceHeaders) {
|
|
663
|
+
Object.assign(headers, traceHeaders);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (clientTag) {
|
|
667
|
+
headers[EDGE_CLIENT_TAG_HEADER] = clientTag;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
method,
|
|
672
|
+
body: requestBody,
|
|
673
|
+
headers,
|
|
674
|
+
};
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Extract W3C Trace Context headers (traceparent/tracestate) from a DXOS Context.
|
|
679
|
+
*/
|
|
680
|
+
const getTraceHeaders = (ctx: Context): Record<string, string> | undefined => {
|
|
681
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE) as TraceContextData | undefined;
|
|
682
|
+
if (!traceCtx) {
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const headers: Record<string, string> = { traceparent: traceCtx.traceparent };
|
|
687
|
+
if (traceCtx.tracestate) {
|
|
688
|
+
headers.tracestate = traceCtx.tracestate;
|
|
689
|
+
}
|
|
690
|
+
return headers;
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* @deprecated
|
|
695
|
+
*/
|
|
696
|
+
const createRetryHandler = ({ retry }: EdgeHttpRequestArgs) => {
|
|
697
|
+
if (!retry || retry.count < 1) {
|
|
263
698
|
return async () => false;
|
|
264
699
|
}
|
|
700
|
+
|
|
265
701
|
let retries = 0;
|
|
266
|
-
const maxRetries =
|
|
267
|
-
const baseTimeout =
|
|
268
|
-
const jitter =
|
|
269
|
-
return async (ctx: Context, retryAfter
|
|
702
|
+
const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
|
|
703
|
+
const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
|
|
704
|
+
const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;
|
|
705
|
+
return async (ctx: Context, retryAfter?: number) => {
|
|
270
706
|
if (++retries > maxRetries || ctx.disposed) {
|
|
271
707
|
return false;
|
|
272
708
|
}
|
|
@@ -282,42 +718,51 @@ const createRetryHandler = (args: EdgeHttpCallArgs) => {
|
|
|
282
718
|
};
|
|
283
719
|
};
|
|
284
720
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
};
|
|
721
|
+
const getFileMimeType = (filename: string) =>
|
|
722
|
+
['.js', '.mjs'].some((codeExtension) => filename.endsWith(codeExtension))
|
|
723
|
+
? 'application/javascript+module'
|
|
724
|
+
: filename.endsWith('.wasm')
|
|
725
|
+
? 'application/wasm'
|
|
726
|
+
: 'application/octet-stream';
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Move any caller-supplied `Authorization` header to `X-Cors-Proxy-Authorization`
|
|
730
|
+
* so it survives the proxy hop. The edge proxy strips the top-level
|
|
731
|
+
* `Authorization` (it carries the DXOS presentation, never to be leaked
|
|
732
|
+
* upstream) and applies any `x-cors-proxy-*` override prefix as the actual
|
|
733
|
+
* upstream header — which is exactly the channel we want for forwarding bot
|
|
734
|
+
* tokens, OAuth tokens, etc.
|
|
735
|
+
*/
|
|
736
|
+
const remapAuthorizationForProxy = (headers: Headers): Headers => {
|
|
737
|
+
const callerAuth = headers.get('Authorization');
|
|
738
|
+
if (callerAuth !== null) {
|
|
739
|
+
headers.delete('Authorization');
|
|
740
|
+
headers.set('X-Cors-Proxy-Authorization', callerAuth);
|
|
741
|
+
}
|
|
742
|
+
return headers;
|
|
318
743
|
};
|
|
319
744
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
745
|
+
/**
|
|
746
|
+
* Fetch through the legacy standalone open proxy at `cors-proxy.dxos.workers.dev`.
|
|
747
|
+
*
|
|
748
|
+
* No DXOS auth, no `EdgeHttpClient` instance required — pure URL rewrite +
|
|
749
|
+
* header remap + `fetch`. Used by integration plugins from contexts that
|
|
750
|
+
* don't have an `EdgeHttpClient` in scope (e.g. plugin-integration's
|
|
751
|
+
* `credentialForm.onSubmit` and `onTokenCreated`, which run inside the
|
|
752
|
+
* coordinator's runtime that does not provide `Capability.Service`).
|
|
753
|
+
*
|
|
754
|
+
* TEMPORARY — see `LEGACY_CORS_PROXY_URL`. When the authenticated `/proxy/*`
|
|
755
|
+
* route on edge ships (https://github.com/dxos/edge/pull/576), delete this
|
|
756
|
+
* function and route everything through `EdgeHttpClient.proxyFetch` again.
|
|
757
|
+
*/
|
|
758
|
+
export const proxyFetchLegacy = (target: URL, init: RequestInit = {}, clientTag?: string): Promise<Response> => {
|
|
759
|
+
const proxyUrl = new URL(`/${target.host}${target.pathname}${target.search}`, LEGACY_CORS_PROXY_URL);
|
|
760
|
+
if (target.protocol === 'http:') {
|
|
761
|
+
proxyUrl.searchParams.set('scheme', 'http');
|
|
762
|
+
}
|
|
763
|
+
const requestHeaders = remapAuthorizationForProxy(new Headers(init.headers ?? undefined));
|
|
764
|
+
if (clientTag) {
|
|
765
|
+
requestHeaders.set(EDGE_CLIENT_TAG_HEADER, clientTag);
|
|
766
|
+
}
|
|
767
|
+
return fetch(proxyUrl, { ...init, headers: requestHeaders });
|
|
323
768
|
};
|