@dxos/edge-client 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8
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/{node-esm/chunk-R6K4IIBW.mjs → neutral/chunk-L5ZHLJ4B.mjs} +57 -53
- package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs.map +7 -0
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs +30 -0
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs.map +7 -0
- package/dist/lib/neutral/cors-proxy.mjs +7 -0
- package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
- package/dist/lib/neutral/index.mjs +1448 -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 +6 -31
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/auth.d.ts.map +1 -1
- package/dist/types/src/base-http-client.d.ts +48 -0
- package/dist/types/src/base-http-client.d.ts.map +1 -0
- package/dist/types/src/cors-proxy.d.ts +6 -0
- package/dist/types/src/cors-proxy.d.ts.map +1 -0
- package/dist/types/src/edge-ai-http-client.d.ts +65 -0
- package/dist/types/src/edge-ai-http-client.d.ts.map +1 -0
- package/dist/types/src/edge-client.d.ts +17 -14
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +81 -54
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-identity.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.d.ts +20 -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 +10 -7
- package/dist/types/src/http-client.d.ts.map +1 -1
- package/dist/types/src/hub-http-client.d.ts +39 -0
- package/dist/types/src/hub-http-client.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +8 -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/test-server.d.ts.map +1 -1
- 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 +33 -32
- package/src/base-http-client.ts +243 -0
- package/src/cors-proxy.ts +38 -0
- package/src/edge-ai-http-client.ts +129 -0
- package/src/edge-client.test.ts +20 -15
- package/src/edge-client.ts +105 -44
- package/src/edge-http-client.test.ts +37 -3
- package/src/edge-http-client.ts +312 -190
- package/src/edge-ws-connection.ts +120 -6
- package/src/edge-ws-muxer.ts +49 -5
- package/src/http-client.test.ts +11 -7
- package/src/http-client.ts +18 -8
- package/src/hub-http-client.ts +118 -0
- package/src/index.ts +8 -3
- package/src/testing/test-utils.ts +7 -7
- package/dist/lib/browser/chunk-SUXH7FH6.mjs +0 -304
- package/dist/lib/browser/chunk-SUXH7FH6.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -1124
- 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-esm/chunk-R6K4IIBW.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/index.mjs +0 -1125
- 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 -186
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- /package/dist/lib/{browser/edge-ws-muxer.mjs.map → neutral/cors-proxy.mjs.map} +0 -0
- /package/dist/lib/{node-esm → neutral}/edge-ws-muxer.mjs.map +0 -0
|
@@ -0,0 +1,1448 @@
|
|
|
1
|
+
import {
|
|
2
|
+
proxyFetchLegacy
|
|
3
|
+
} from "./chunk-WQKMEZJR.mjs";
|
|
4
|
+
import {
|
|
5
|
+
CLOUDFLARE_MESSAGE_MAX_BYTES,
|
|
6
|
+
CLOUDFLARE_RPC_MAX_BYTES,
|
|
7
|
+
Protocol,
|
|
8
|
+
WebSocketMuxer,
|
|
9
|
+
getTypename,
|
|
10
|
+
protocol,
|
|
11
|
+
toUint8Array
|
|
12
|
+
} from "./chunk-L5ZHLJ4B.mjs";
|
|
13
|
+
|
|
14
|
+
// src/index.ts
|
|
15
|
+
export * from "@dxos/protocols/buf/dxos/edge/messenger_pb";
|
|
16
|
+
|
|
17
|
+
// src/auth.ts
|
|
18
|
+
import { createCredential, signPresentation } from "@dxos/credentials";
|
|
19
|
+
import { invariant } from "@dxos/invariant";
|
|
20
|
+
import { Keyring } from "@dxos/keyring";
|
|
21
|
+
import { PublicKey } from "@dxos/keys";
|
|
22
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/auth.ts";
|
|
23
|
+
var createDeviceEdgeIdentity = async (signer, key) => {
|
|
24
|
+
return {
|
|
25
|
+
identityKey: key.toHex(),
|
|
26
|
+
peerKey: key.toHex(),
|
|
27
|
+
presentCredentials: async ({ challenge }) => {
|
|
28
|
+
return signPresentation({
|
|
29
|
+
presentation: {
|
|
30
|
+
credentials: [
|
|
31
|
+
// Verifier requires at least one credential in the presentation to establish the subject.
|
|
32
|
+
await createCredential({
|
|
33
|
+
assertion: {
|
|
34
|
+
"@type": "dxos.halo.credentials.Auth"
|
|
35
|
+
},
|
|
36
|
+
issuer: key,
|
|
37
|
+
subject: key,
|
|
38
|
+
signer
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
signer,
|
|
43
|
+
signerKey: key,
|
|
44
|
+
nonce: challenge
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
var createChainEdgeIdentity = async (signer, identityKey, peerKey, chain, credentials) => {
|
|
50
|
+
const credentialsToSign = credentials.length > 0 ? credentials : [
|
|
51
|
+
await createCredential({
|
|
52
|
+
assertion: {
|
|
53
|
+
"@type": "dxos.halo.credentials.Auth"
|
|
54
|
+
},
|
|
55
|
+
issuer: identityKey,
|
|
56
|
+
subject: identityKey,
|
|
57
|
+
signer,
|
|
58
|
+
chain,
|
|
59
|
+
signingKey: peerKey
|
|
60
|
+
})
|
|
61
|
+
];
|
|
62
|
+
return {
|
|
63
|
+
identityKey: identityKey.toHex(),
|
|
64
|
+
peerKey: peerKey.toHex(),
|
|
65
|
+
presentCredentials: async ({ challenge }) => {
|
|
66
|
+
invariant(chain, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 56, S: void 0, A: ["chain", ""] });
|
|
67
|
+
return signPresentation({
|
|
68
|
+
presentation: {
|
|
69
|
+
credentials: credentialsToSign
|
|
70
|
+
},
|
|
71
|
+
signer,
|
|
72
|
+
nonce: challenge,
|
|
73
|
+
signerKey: peerKey,
|
|
74
|
+
chain
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
var createEphemeralEdgeIdentity = async () => {
|
|
80
|
+
const keyring = new Keyring();
|
|
81
|
+
const key = await keyring.createKey();
|
|
82
|
+
return createDeviceEdgeIdentity(keyring, key);
|
|
83
|
+
};
|
|
84
|
+
var createTestHaloEdgeIdentity = async (signer, identityKey, deviceKey) => {
|
|
85
|
+
const deviceAdmission = await createCredential({
|
|
86
|
+
assertion: {
|
|
87
|
+
"@type": "dxos.halo.credentials.AuthorizedDevice",
|
|
88
|
+
deviceKey,
|
|
89
|
+
identityKey
|
|
90
|
+
},
|
|
91
|
+
issuer: identityKey,
|
|
92
|
+
subject: deviceKey,
|
|
93
|
+
signer
|
|
94
|
+
});
|
|
95
|
+
return createChainEdgeIdentity(signer, identityKey, deviceKey, {
|
|
96
|
+
credential: deviceAdmission
|
|
97
|
+
}, [
|
|
98
|
+
await createCredential({
|
|
99
|
+
assertion: {
|
|
100
|
+
"@type": "dxos.halo.credentials.Auth"
|
|
101
|
+
},
|
|
102
|
+
issuer: identityKey,
|
|
103
|
+
subject: identityKey,
|
|
104
|
+
signer
|
|
105
|
+
})
|
|
106
|
+
]);
|
|
107
|
+
};
|
|
108
|
+
var createStubEdgeIdentity = () => {
|
|
109
|
+
const identityKey = PublicKey.random();
|
|
110
|
+
const deviceKey = PublicKey.random();
|
|
111
|
+
return {
|
|
112
|
+
identityKey: identityKey.toHex(),
|
|
113
|
+
peerKey: deviceKey.toHex(),
|
|
114
|
+
presentCredentials: async () => {
|
|
115
|
+
throw new Error("Stub identity does not support authentication.");
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/edge-client.ts
|
|
121
|
+
import { Event, PersistentLifecycle, Trigger, TriggerState, scheduleMicroTask, scheduleTaskInterval as scheduleTaskInterval2 } from "@dxos/async";
|
|
122
|
+
import { TRACE_SPAN_ATTRIBUTE } from "@dxos/context";
|
|
123
|
+
import { Resource as Resource2 } from "@dxos/context";
|
|
124
|
+
import { log as log2, logInfo as logInfo2 } from "@dxos/log";
|
|
125
|
+
import { EdgeStatus } from "@dxos/protocols/proto/dxos/client/services";
|
|
126
|
+
|
|
127
|
+
// src/edge-identity.ts
|
|
128
|
+
import { invariant as invariant2 } from "@dxos/invariant";
|
|
129
|
+
import { schema } from "@dxos/protocols/proto";
|
|
130
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-identity.ts";
|
|
131
|
+
var handleAuthChallenge = async (failedResponse, identity) => {
|
|
132
|
+
invariant2(failedResponse.status === 401, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 7, S: void 0, A: ["failedResponse.status === 401", ""] });
|
|
133
|
+
const headerValue = failedResponse.headers.get("Www-Authenticate");
|
|
134
|
+
invariant2(headerValue?.startsWith("VerifiablePresentation challenge="), void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 9, S: void 0, A: ["headerValue?.startsWith('VerifiablePresentation challenge=')", ""] });
|
|
135
|
+
const challenge = headerValue?.slice("VerifiablePresentation challenge=".length);
|
|
136
|
+
invariant2(challenge, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 11, S: void 0, A: ["challenge", ""] });
|
|
137
|
+
const presentation = await identity.presentCredentials({
|
|
138
|
+
challenge: Buffer.from(challenge, "base64")
|
|
139
|
+
});
|
|
140
|
+
return schema.getCodecForType("dxos.halo.credentials.Presentation").encode(presentation);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/edge-ws-connection.ts
|
|
144
|
+
import WebSocket from "isomorphic-ws";
|
|
145
|
+
import { scheduleTask, scheduleTaskInterval } from "@dxos/async";
|
|
146
|
+
import { Context, Resource } from "@dxos/context";
|
|
147
|
+
import { invariant as invariant3 } from "@dxos/invariant";
|
|
148
|
+
import { log, logInfo } from "@dxos/log";
|
|
149
|
+
import { EdgeWebsocketProtocol } from "@dxos/protocols";
|
|
150
|
+
import { buf } from "@dxos/protocols/buf";
|
|
151
|
+
import { MessageSchema } from "@dxos/protocols/buf/dxos/edge/messenger_pb";
|
|
152
|
+
var __dxlog_file3 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-ws-connection.ts";
|
|
153
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
154
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
155
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
156
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
157
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
158
|
+
}
|
|
159
|
+
var SIGNAL_KEEPALIVE_INTERVAL = 4e3;
|
|
160
|
+
var SIGNAL_KEEPALIVE_TIMEOUT = 12e3;
|
|
161
|
+
var EdgeWsConnection = class extends Resource {
|
|
162
|
+
_identity;
|
|
163
|
+
_connectionInfo;
|
|
164
|
+
_callbacks;
|
|
165
|
+
_inactivityTimeoutCtx;
|
|
166
|
+
_ws;
|
|
167
|
+
_wsMuxer;
|
|
168
|
+
_lastReceivedMessageTimestamp = Date.now();
|
|
169
|
+
_openTimestamp;
|
|
170
|
+
// Latency tracking.
|
|
171
|
+
_pingTimestamp;
|
|
172
|
+
_rtt = 0;
|
|
173
|
+
// Rate tracking with sliding window.
|
|
174
|
+
_uploadRate = 0;
|
|
175
|
+
_downloadRate = 0;
|
|
176
|
+
_rateWindow = 1e4;
|
|
177
|
+
_rateUpdateInterval = 1e3;
|
|
178
|
+
_bytesSamples = [];
|
|
179
|
+
_messagesSent = 0;
|
|
180
|
+
_messagesReceived = 0;
|
|
181
|
+
constructor(_identity, _connectionInfo, _callbacks) {
|
|
182
|
+
super(), this._identity = _identity, this._connectionInfo = _connectionInfo, this._callbacks = _callbacks;
|
|
183
|
+
}
|
|
184
|
+
get info() {
|
|
185
|
+
return {
|
|
186
|
+
open: this.isOpen,
|
|
187
|
+
identity: this._identity.identityKey,
|
|
188
|
+
device: this._identity.peerKey
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
get rtt() {
|
|
192
|
+
return this._rtt;
|
|
193
|
+
}
|
|
194
|
+
get uptime() {
|
|
195
|
+
return this._openTimestamp ? (Date.now() - this._openTimestamp) / 1e3 : 0;
|
|
196
|
+
}
|
|
197
|
+
get uploadRate() {
|
|
198
|
+
return this._uploadRate;
|
|
199
|
+
}
|
|
200
|
+
get downloadRate() {
|
|
201
|
+
return this._downloadRate;
|
|
202
|
+
}
|
|
203
|
+
get messagesSent() {
|
|
204
|
+
return this._messagesSent;
|
|
205
|
+
}
|
|
206
|
+
get messagesReceived() {
|
|
207
|
+
return this._messagesReceived;
|
|
208
|
+
}
|
|
209
|
+
send(message) {
|
|
210
|
+
invariant3(this._ws, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 72, S: this, A: ["this._ws", ""] });
|
|
211
|
+
invariant3(this._wsMuxer, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 73, S: this, A: ["this._wsMuxer", ""] });
|
|
212
|
+
log("sending...", {
|
|
213
|
+
peerKey: this._identity.peerKey,
|
|
214
|
+
payload: protocol.getPayloadType(message)
|
|
215
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 74, S: this });
|
|
216
|
+
this._messagesSent++;
|
|
217
|
+
if (this._ws?.protocol.includes(EdgeWebsocketProtocol.V0)) {
|
|
218
|
+
const binary = buf.toBinary(MessageSchema, message);
|
|
219
|
+
if (binary.length > CLOUDFLARE_MESSAGE_MAX_BYTES) {
|
|
220
|
+
log.error("Message dropped because it was too large (>1MB).", {
|
|
221
|
+
byteLength: binary.byteLength,
|
|
222
|
+
serviceId: message.serviceId,
|
|
223
|
+
payload: protocol.getPayloadType(message)
|
|
224
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 82, S: this });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this._recordBytes(binary.byteLength, 0);
|
|
228
|
+
this._ws.send(binary);
|
|
229
|
+
} else {
|
|
230
|
+
const binary = buf.toBinary(MessageSchema, message);
|
|
231
|
+
this._recordBytes(binary.byteLength, 0);
|
|
232
|
+
this._wsMuxer.send(message).catch((e) => log.catch(e, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 95, S: this }));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async _open() {
|
|
236
|
+
const baseProtocols = [
|
|
237
|
+
...Object.values(EdgeWebsocketProtocol)
|
|
238
|
+
];
|
|
239
|
+
this._ws = new WebSocket(this._connectionInfo.url.toString(), this._connectionInfo.protocolHeader ? [
|
|
240
|
+
...baseProtocols,
|
|
241
|
+
this._connectionInfo.protocolHeader
|
|
242
|
+
] : [
|
|
243
|
+
...baseProtocols
|
|
244
|
+
], this._connectionInfo.headers ? {
|
|
245
|
+
headers: this._connectionInfo.headers
|
|
246
|
+
} : void 0);
|
|
247
|
+
const muxer = new WebSocketMuxer(this._ws);
|
|
248
|
+
this._wsMuxer = muxer;
|
|
249
|
+
this._ws.onopen = () => {
|
|
250
|
+
if (this.isOpen) {
|
|
251
|
+
log("connected", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 114, S: this });
|
|
252
|
+
this._openTimestamp = Date.now();
|
|
253
|
+
this._callbacks.onConnected();
|
|
254
|
+
this._scheduleHeartbeats();
|
|
255
|
+
this._scheduleRateCalculation();
|
|
256
|
+
} else {
|
|
257
|
+
log.verbose("connected after becoming inactive", {
|
|
258
|
+
currentIdentity: this._identity
|
|
259
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 120, S: this });
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
this._ws.onclose = (event) => {
|
|
263
|
+
if (this.isOpen) {
|
|
264
|
+
log.warn("server disconnected", {
|
|
265
|
+
code: event.code,
|
|
266
|
+
reason: event.reason
|
|
267
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 127, S: this });
|
|
268
|
+
this._callbacks.onRestartRequired();
|
|
269
|
+
muxer.destroy();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
this._ws.onerror = (event) => {
|
|
273
|
+
if (this.isOpen) {
|
|
274
|
+
log.warn("edge connection socket error", {
|
|
275
|
+
error: event.error,
|
|
276
|
+
info: event.message
|
|
277
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 137, S: this });
|
|
278
|
+
this._callbacks.onRestartRequired();
|
|
279
|
+
} else {
|
|
280
|
+
log.verbose("error ignored on closed connection", {
|
|
281
|
+
error: event.error
|
|
282
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 143, S: this });
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
this._ws.onmessage = async (event) => {
|
|
286
|
+
if (!this.isOpen) {
|
|
287
|
+
log.verbose("message ignored on closed connection", {
|
|
288
|
+
event: event.type
|
|
289
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 152, S: this });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this._lastReceivedMessageTimestamp = Date.now();
|
|
293
|
+
if (event.data === "__pong__") {
|
|
294
|
+
if (this._pingTimestamp) {
|
|
295
|
+
this._rtt = Date.now() - this._pingTimestamp;
|
|
296
|
+
this._pingTimestamp = void 0;
|
|
297
|
+
}
|
|
298
|
+
this._rescheduleHeartbeatTimeout();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const bytes = await toUint8Array(event.data);
|
|
302
|
+
this._recordBytes(0, bytes.byteLength);
|
|
303
|
+
if (!this.isOpen) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this._messagesReceived++;
|
|
307
|
+
const message = this._ws?.protocol?.includes(EdgeWebsocketProtocol.V0) ? buf.fromBinary(MessageSchema, bytes) : muxer.receiveData(bytes);
|
|
308
|
+
if (message) {
|
|
309
|
+
log("received", {
|
|
310
|
+
from: message.source,
|
|
311
|
+
payload: protocol.getPayloadType(message)
|
|
312
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 175, S: this });
|
|
313
|
+
this._callbacks.onMessage(message);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async _close() {
|
|
318
|
+
void this._inactivityTimeoutCtx?.dispose().catch(() => {
|
|
319
|
+
});
|
|
320
|
+
try {
|
|
321
|
+
this._ws?.close();
|
|
322
|
+
this._ws = void 0;
|
|
323
|
+
this._wsMuxer?.destroy();
|
|
324
|
+
this._wsMuxer = void 0;
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (err instanceof Error && err.message.includes("WebSocket is closed before the connection is established.")) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
log.warn("error closing websocket", {
|
|
330
|
+
err
|
|
331
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 194, S: this });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
_scheduleHeartbeats() {
|
|
335
|
+
invariant3(this._ws, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 200, S: this, A: ["this._ws", ""] });
|
|
336
|
+
scheduleTaskInterval(this._ctx, async () => {
|
|
337
|
+
this._pingTimestamp = Date.now();
|
|
338
|
+
this._ws?.send("__ping__");
|
|
339
|
+
}, SIGNAL_KEEPALIVE_INTERVAL);
|
|
340
|
+
this._pingTimestamp = Date.now();
|
|
341
|
+
this._ws.send("__ping__");
|
|
342
|
+
this._rescheduleHeartbeatTimeout();
|
|
343
|
+
}
|
|
344
|
+
_rescheduleHeartbeatTimeout() {
|
|
345
|
+
if (!this.isOpen) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
void this._inactivityTimeoutCtx?.dispose();
|
|
349
|
+
this._inactivityTimeoutCtx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 216 });
|
|
350
|
+
scheduleTask(this._inactivityTimeoutCtx, () => {
|
|
351
|
+
if (this.isOpen) {
|
|
352
|
+
if (Date.now() - this._lastReceivedMessageTimestamp > SIGNAL_KEEPALIVE_TIMEOUT) {
|
|
353
|
+
log.warn("restart due to inactivity timeout", {
|
|
354
|
+
lastReceivedMessageTimestamp: this._lastReceivedMessageTimestamp
|
|
355
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 220, S: this });
|
|
356
|
+
this._callbacks.onRestartRequired();
|
|
357
|
+
} else {
|
|
358
|
+
this._rescheduleHeartbeatTimeout();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}, SIGNAL_KEEPALIVE_TIMEOUT);
|
|
362
|
+
}
|
|
363
|
+
_recordBytes(sent, received) {
|
|
364
|
+
const now = Date.now();
|
|
365
|
+
const currentSecond = Math.floor(now / 1e3) * 1e3;
|
|
366
|
+
const existingSample = this._bytesSamples.find((s) => Math.floor(s.timestamp / 1e3) * 1e3 === currentSecond);
|
|
367
|
+
if (existingSample) {
|
|
368
|
+
existingSample.sent += sent;
|
|
369
|
+
existingSample.received += received;
|
|
370
|
+
} else {
|
|
371
|
+
this._bytesSamples.push({
|
|
372
|
+
timestamp: now,
|
|
373
|
+
sent,
|
|
374
|
+
received
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
_scheduleRateCalculation() {
|
|
379
|
+
scheduleTaskInterval(this._ctx, async () => {
|
|
380
|
+
this._calculateRates();
|
|
381
|
+
}, this._rateUpdateInterval);
|
|
382
|
+
this._calculateRates();
|
|
383
|
+
}
|
|
384
|
+
_calculateRates() {
|
|
385
|
+
const now = Date.now();
|
|
386
|
+
const cutoff = now - this._rateWindow;
|
|
387
|
+
this._bytesSamples = this._bytesSamples.filter((s) => s.timestamp > cutoff);
|
|
388
|
+
if (this._bytesSamples.length === 0) {
|
|
389
|
+
this._uploadRate = 0;
|
|
390
|
+
this._downloadRate = 0;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let totalSent = 0;
|
|
394
|
+
let totalReceived = 0;
|
|
395
|
+
const oldestTimestamp = Math.min(...this._bytesSamples.map((s) => s.timestamp));
|
|
396
|
+
const timeSpan = (now - oldestTimestamp) / 1e3;
|
|
397
|
+
for (const sample of this._bytesSamples) {
|
|
398
|
+
totalSent += sample.sent;
|
|
399
|
+
totalReceived += sample.received;
|
|
400
|
+
}
|
|
401
|
+
this._uploadRate = timeSpan > 0 ? Math.round(totalSent / timeSpan) : 0;
|
|
402
|
+
this._downloadRate = timeSpan > 0 ? Math.round(totalReceived / timeSpan) : 0;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
_ts_decorate([
|
|
406
|
+
logInfo
|
|
407
|
+
], EdgeWsConnection.prototype, "info", null);
|
|
408
|
+
|
|
409
|
+
// src/errors.ts
|
|
410
|
+
var EdgeConnectionClosedError = class extends Error {
|
|
411
|
+
constructor() {
|
|
412
|
+
super("Edge connection closed.");
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var EdgeIdentityChangedError = class extends Error {
|
|
416
|
+
constructor() {
|
|
417
|
+
super("Edge identity changed.");
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/utils.ts
|
|
422
|
+
var getEdgeUrlWithProtocol = (baseUrl, protocol2) => {
|
|
423
|
+
const isSecure = baseUrl.startsWith("https") || baseUrl.startsWith("wss");
|
|
424
|
+
const url = new URL(baseUrl);
|
|
425
|
+
url.protocol = protocol2 + (isSecure ? "s" : "");
|
|
426
|
+
return url.toString();
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/edge-client.ts
|
|
430
|
+
var __dxlog_file4 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-client.ts";
|
|
431
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
432
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
433
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
434
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
435
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
436
|
+
}
|
|
437
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
438
|
+
var STATUS_REFRESH_INTERVAL = 1e3;
|
|
439
|
+
var EdgeClient = class extends Resource2 {
|
|
440
|
+
_identity;
|
|
441
|
+
_config;
|
|
442
|
+
statusChanged = new Event();
|
|
443
|
+
_persistentLifecycle = new PersistentLifecycle({
|
|
444
|
+
start: async () => this._connect(),
|
|
445
|
+
stop: async (state) => this._disconnect(state)
|
|
446
|
+
});
|
|
447
|
+
_messageListeners = /* @__PURE__ */ new Set();
|
|
448
|
+
_reconnectListeners = /* @__PURE__ */ new Set();
|
|
449
|
+
_baseWsUrl;
|
|
450
|
+
_baseHttpUrl;
|
|
451
|
+
_currentConnection = void 0;
|
|
452
|
+
_ready = new Trigger();
|
|
453
|
+
constructor(_identity, _config) {
|
|
454
|
+
super(), this._identity = _identity, this._config = _config;
|
|
455
|
+
this._baseWsUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, "ws");
|
|
456
|
+
this._baseHttpUrl = getEdgeUrlWithProtocol(_config.socketEndpoint, "http");
|
|
457
|
+
}
|
|
458
|
+
get info() {
|
|
459
|
+
return {
|
|
460
|
+
open: this.isOpen,
|
|
461
|
+
status: this.status,
|
|
462
|
+
identity: this._identity.identityKey,
|
|
463
|
+
device: this._identity.peerKey
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
get status() {
|
|
467
|
+
return {
|
|
468
|
+
state: Boolean(this._currentConnection) && this._ready.state === TriggerState.RESOLVED ? EdgeStatus.ConnectionState.CONNECTED : EdgeStatus.ConnectionState.NOT_CONNECTED,
|
|
469
|
+
uptime: this._currentConnection?.uptime ?? 0,
|
|
470
|
+
rtt: this._currentConnection?.rtt ?? 0,
|
|
471
|
+
rateBytesUp: this._currentConnection?.uploadRate ?? 0,
|
|
472
|
+
rateBytesDown: this._currentConnection?.downloadRate ?? 0,
|
|
473
|
+
messagesSent: this._currentConnection?.messagesSent ?? 0,
|
|
474
|
+
messagesReceived: this._currentConnection?.messagesReceived ?? 0
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
get identityKey() {
|
|
478
|
+
return this._identity.identityKey;
|
|
479
|
+
}
|
|
480
|
+
get peerKey() {
|
|
481
|
+
return this._identity.peerKey;
|
|
482
|
+
}
|
|
483
|
+
setIdentity(identity) {
|
|
484
|
+
if (identity.identityKey !== this._identity.identityKey || identity.peerKey !== this._identity.peerKey) {
|
|
485
|
+
log2("Edge identity changed", {
|
|
486
|
+
identity,
|
|
487
|
+
oldIdentity: this._identity
|
|
488
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 74, S: this });
|
|
489
|
+
this._identity = identity;
|
|
490
|
+
this._closeCurrentConnection(new EdgeIdentityChangedError());
|
|
491
|
+
void this._persistentLifecycle.scheduleRestart();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Send message.
|
|
496
|
+
* NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
|
|
497
|
+
*/
|
|
498
|
+
async send(ctx, message) {
|
|
499
|
+
if (this._ready.state !== TriggerState.RESOLVED) {
|
|
500
|
+
log2("waiting for websocket", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 88, S: this });
|
|
501
|
+
await this._ready.wait({
|
|
502
|
+
timeout: this._config.timeout ?? DEFAULT_TIMEOUT
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
if (!this._currentConnection) {
|
|
506
|
+
throw new EdgeConnectionClosedError();
|
|
507
|
+
}
|
|
508
|
+
if (message.source && (message.source.peerKey !== this._identity.peerKey || message.source.identityKey !== this.identityKey)) {
|
|
509
|
+
throw new EdgeIdentityChangedError();
|
|
510
|
+
}
|
|
511
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
512
|
+
if (traceCtx) {
|
|
513
|
+
message.traceContext = {
|
|
514
|
+
$typeName: "dxos.edge.messenger.TraceContext",
|
|
515
|
+
traceparent: traceCtx.traceparent,
|
|
516
|
+
tracestate: traceCtx.tracestate
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
this._currentConnection.send(message);
|
|
520
|
+
}
|
|
521
|
+
onMessage(listener) {
|
|
522
|
+
this._messageListeners.add(listener);
|
|
523
|
+
return () => this._messageListeners.delete(listener);
|
|
524
|
+
}
|
|
525
|
+
onReconnected(listener) {
|
|
526
|
+
this._reconnectListeners.add(listener);
|
|
527
|
+
if (this._ready.state === TriggerState.RESOLVED) {
|
|
528
|
+
scheduleMicroTask(this._ctx, () => {
|
|
529
|
+
if (this._reconnectListeners.has(listener)) {
|
|
530
|
+
try {
|
|
531
|
+
listener();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
log2.catch(error, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 123, S: this });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return () => this._reconnectListeners.delete(listener);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Open connection to messaging service.
|
|
542
|
+
*/
|
|
543
|
+
async _open() {
|
|
544
|
+
log2("opening...", {
|
|
545
|
+
info: this.info
|
|
546
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 133, S: this });
|
|
547
|
+
this._persistentLifecycle.open().catch((err) => {
|
|
548
|
+
log2.warn("Error while opening connection", {
|
|
549
|
+
err
|
|
550
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 137, S: this });
|
|
551
|
+
});
|
|
552
|
+
scheduleTaskInterval2(this._ctx, async () => {
|
|
553
|
+
if (!this._currentConnection) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.statusChanged.emit(this.status);
|
|
557
|
+
}, STATUS_REFRESH_INTERVAL);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Close connection and free resources.
|
|
561
|
+
*/
|
|
562
|
+
async _close() {
|
|
563
|
+
log2("closing...", {
|
|
564
|
+
peerKey: this._identity.peerKey
|
|
565
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 152, S: this });
|
|
566
|
+
this._closeCurrentConnection();
|
|
567
|
+
await this._persistentLifecycle.close();
|
|
568
|
+
}
|
|
569
|
+
async _connect() {
|
|
570
|
+
if (this._ctx.disposed) {
|
|
571
|
+
return void 0;
|
|
572
|
+
}
|
|
573
|
+
const identity = this._identity;
|
|
574
|
+
const path = `/ws/${identity.identityKey}/${identity.peerKey}`;
|
|
575
|
+
const protocolHeader = this._config.disableAuth ? void 0 : await this._createAuthHeader(path);
|
|
576
|
+
if (this._identity !== identity) {
|
|
577
|
+
log2("identity changed during auth header request", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 166, S: this });
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
const restartRequired = new Trigger();
|
|
581
|
+
const url = new URL(path, this._baseWsUrl);
|
|
582
|
+
log2("Opening websocket", {
|
|
583
|
+
url: url.toString(),
|
|
584
|
+
protocolHeader
|
|
585
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 171, S: this });
|
|
586
|
+
const connection = new EdgeWsConnection(identity, {
|
|
587
|
+
url,
|
|
588
|
+
protocolHeader,
|
|
589
|
+
headers: this._config.clientTag ? {
|
|
590
|
+
"X-DXOS-Client-Tag": this._config.clientTag
|
|
591
|
+
} : void 0
|
|
592
|
+
}, {
|
|
593
|
+
onConnected: () => {
|
|
594
|
+
if (this._isActive(connection)) {
|
|
595
|
+
this._ready.wake();
|
|
596
|
+
this._notifyReconnected();
|
|
597
|
+
} else {
|
|
598
|
+
log2.verbose("connected callback ignored, because connection is not active", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 187, S: this });
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
onRestartRequired: () => {
|
|
602
|
+
if (this._isActive(connection)) {
|
|
603
|
+
this._closeCurrentConnection();
|
|
604
|
+
void this._persistentLifecycle.scheduleRestart();
|
|
605
|
+
} else {
|
|
606
|
+
log2.verbose("restart requested by inactive connection", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 195, S: this });
|
|
607
|
+
}
|
|
608
|
+
restartRequired.wake();
|
|
609
|
+
},
|
|
610
|
+
onMessage: (message) => {
|
|
611
|
+
if (this._isActive(connection)) {
|
|
612
|
+
this._notifyMessageReceived(message);
|
|
613
|
+
} else {
|
|
614
|
+
log2.verbose("ignored a message on inactive connection", {
|
|
615
|
+
from: message.source,
|
|
616
|
+
type: message.payload?.typeUrl
|
|
617
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 203, S: this });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
this._currentConnection = connection;
|
|
622
|
+
await connection.open();
|
|
623
|
+
const becameReady = await Promise.race([
|
|
624
|
+
this._ready.wait({
|
|
625
|
+
timeout: this._config.timeout ?? DEFAULT_TIMEOUT
|
|
626
|
+
}).then(() => true, () => false),
|
|
627
|
+
restartRequired.wait().then(() => false)
|
|
628
|
+
]);
|
|
629
|
+
if (!becameReady) {
|
|
630
|
+
throw new EdgeConnectionClosedError();
|
|
631
|
+
}
|
|
632
|
+
return connection;
|
|
633
|
+
}
|
|
634
|
+
async _disconnect(state) {
|
|
635
|
+
await state.close();
|
|
636
|
+
this.statusChanged.emit(this.status);
|
|
637
|
+
}
|
|
638
|
+
_closeCurrentConnection(error = new EdgeConnectionClosedError()) {
|
|
639
|
+
this._currentConnection = void 0;
|
|
640
|
+
this._ready.throw(error);
|
|
641
|
+
this._ready.reset();
|
|
642
|
+
this.statusChanged.emit(this.status);
|
|
643
|
+
}
|
|
644
|
+
_notifyReconnected() {
|
|
645
|
+
this.statusChanged.emit(this.status);
|
|
646
|
+
for (const listener of this._reconnectListeners) {
|
|
647
|
+
try {
|
|
648
|
+
listener();
|
|
649
|
+
} catch (err) {
|
|
650
|
+
log2.error("ws reconnect listener failed", {
|
|
651
|
+
err
|
|
652
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 244, S: this });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
_notifyMessageReceived(message) {
|
|
657
|
+
for (const listener of this._messageListeners) {
|
|
658
|
+
try {
|
|
659
|
+
listener(message);
|
|
660
|
+
} catch (err) {
|
|
661
|
+
log2.error("ws incoming message processing failed", {
|
|
662
|
+
err,
|
|
663
|
+
payload: protocol.getPayloadType(message)
|
|
664
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 255, S: this });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async _createAuthHeader(path) {
|
|
669
|
+
const httpUrl = new URL(path, this._baseHttpUrl);
|
|
670
|
+
httpUrl.protocol = getEdgeUrlWithProtocol(this._baseWsUrl.toString(), "http");
|
|
671
|
+
const response = await fetch(httpUrl, {
|
|
672
|
+
method: "GET"
|
|
673
|
+
});
|
|
674
|
+
if (response.status === 401) {
|
|
675
|
+
return encodePresentationWsAuthHeader(await handleAuthChallenge(response, this._identity));
|
|
676
|
+
} else {
|
|
677
|
+
log2.warn("no auth challenge from edge", {
|
|
678
|
+
status: response.status,
|
|
679
|
+
statusText: response.statusText
|
|
680
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 271, S: this });
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
_isActive = (connection) => connection === this._currentConnection;
|
|
685
|
+
};
|
|
686
|
+
_ts_decorate2([
|
|
687
|
+
logInfo2
|
|
688
|
+
], EdgeClient.prototype, "info", null);
|
|
689
|
+
var encodePresentationWsAuthHeader = (encodedPresentation) => {
|
|
690
|
+
const encodedToken = Buffer.from(encodedPresentation).toString("base64").replace(/=*$/, "").replaceAll("/", "|");
|
|
691
|
+
return `base64url.bearer.authorization.dxos.org.${encodedToken}`;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// src/base-http-client.ts
|
|
695
|
+
import { sleep } from "@dxos/async";
|
|
696
|
+
import { TRACE_SPAN_ATTRIBUTE as TRACE_SPAN_ATTRIBUTE2 } from "@dxos/context";
|
|
697
|
+
import { invariant as invariant4 } from "@dxos/invariant";
|
|
698
|
+
import { log as log4 } from "@dxos/log";
|
|
699
|
+
import { EDGE_CLIENT_TAG_HEADER, EdgeAuthChallengeError, EdgeCallFailedError } from "@dxos/protocols";
|
|
700
|
+
|
|
701
|
+
// src/http-client.ts
|
|
702
|
+
import * as Context2 from "effect/Context";
|
|
703
|
+
import * as Duration from "effect/Duration";
|
|
704
|
+
import * as Effect from "effect/Effect";
|
|
705
|
+
import * as Layer from "effect/Layer";
|
|
706
|
+
import * as Schedule from "effect/Schedule";
|
|
707
|
+
import { log as log3 } from "@dxos/log";
|
|
708
|
+
var __dxlog_file5 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/http-client.ts";
|
|
709
|
+
var HttpConfig = class _HttpConfig extends Context2.Tag("HttpConfig")() {
|
|
710
|
+
static default = Layer.succeed(_HttpConfig, {
|
|
711
|
+
timeout: Duration.millis(1e3),
|
|
712
|
+
retryTimes: 3,
|
|
713
|
+
retryBaseDelay: Duration.millis(1e3)
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
var withRetry = (effect, { timeout: timeout2 = Duration.millis(1e3), retryBaseDelay = Duration.millis(1e3), retryTimes = 3 } = {}) => {
|
|
717
|
+
return effect.pipe(Effect.flatMap((res) => (
|
|
718
|
+
// Treat 500 errors as retryable?
|
|
719
|
+
res.status === 500 ? Effect.fail(new Error(res.status.toString())) : res.json
|
|
720
|
+
)), Effect.timeout(timeout2), Effect.retry({
|
|
721
|
+
schedule: Schedule.exponential(retryBaseDelay).pipe(Schedule.jittered),
|
|
722
|
+
times: retryTimes
|
|
723
|
+
}));
|
|
724
|
+
};
|
|
725
|
+
var withRetryConfig = (effect) => Effect.gen(function* () {
|
|
726
|
+
const config = yield* HttpConfig;
|
|
727
|
+
return yield* withRetry(effect, config);
|
|
728
|
+
});
|
|
729
|
+
var withLogging = (effect) => effect.pipe(Effect.tap((res) => {
|
|
730
|
+
log3.info("response", {
|
|
731
|
+
status: res.status
|
|
732
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 31, S: void 0 });
|
|
733
|
+
}));
|
|
734
|
+
var encodeAuthHeader = (challenge) => {
|
|
735
|
+
const encodedChallenge = Buffer.from(challenge).toString("base64");
|
|
736
|
+
return `VerifiablePresentation pb;base64,${encodedChallenge}`;
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/base-http-client.ts
|
|
740
|
+
var __dxlog_file6 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/base-http-client.ts";
|
|
741
|
+
var DEFAULT_RETRY_TIMEOUT = 1500;
|
|
742
|
+
var DEFAULT_RETRY_JITTER = 500;
|
|
743
|
+
var DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
744
|
+
var WARNING_BODY_SIZE = 10 * 1024 * 1024;
|
|
745
|
+
var BaseHttpClient = class {
|
|
746
|
+
_baseUrl;
|
|
747
|
+
_clientTag;
|
|
748
|
+
_edgeIdentity;
|
|
749
|
+
/** Auth header cached until next 401. */
|
|
750
|
+
_authHeader;
|
|
751
|
+
constructor(baseUrl, options) {
|
|
752
|
+
this._baseUrl = getEdgeUrlWithProtocol(baseUrl, "http");
|
|
753
|
+
this._clientTag = options?.clientTag;
|
|
754
|
+
log4("created", {
|
|
755
|
+
url: this._baseUrl
|
|
756
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 24, S: this });
|
|
757
|
+
}
|
|
758
|
+
get baseUrl() {
|
|
759
|
+
return this._baseUrl;
|
|
760
|
+
}
|
|
761
|
+
setIdentity(identity) {
|
|
762
|
+
if (this._edgeIdentity?.identityKey !== identity.identityKey || this._edgeIdentity?.peerKey !== identity.peerKey) {
|
|
763
|
+
this._edgeIdentity = identity;
|
|
764
|
+
this._authHeader = void 0;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// TODO(mykola): Extend `_call` to support streaming/raw `Response` returns so
|
|
768
|
+
// `EdgeHttpClient.anthropicAiRequest` can be absorbed here and the auth/retry loop
|
|
769
|
+
// stops being duplicated across the two paths.
|
|
770
|
+
async _call(ctx, url, args) {
|
|
771
|
+
const shouldRetry = createRetryHandler(args);
|
|
772
|
+
log4("fetch", {
|
|
773
|
+
url,
|
|
774
|
+
hasBody: args.body !== void 0,
|
|
775
|
+
bodySize: typeof args.body === "string" ? args.body.length : void 0
|
|
776
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 43, S: this });
|
|
777
|
+
const traceHeaders = getTraceHeaders(ctx);
|
|
778
|
+
let handledAuth = false;
|
|
779
|
+
const tryCount = 1;
|
|
780
|
+
while (true) {
|
|
781
|
+
let processingError = void 0;
|
|
782
|
+
try {
|
|
783
|
+
if (!this._authHeader && args.auth) {
|
|
784
|
+
const response2 = await fetch(new URL("/auth", this._baseUrl));
|
|
785
|
+
if (response2.status === 401) {
|
|
786
|
+
this._authHeader = await this._handleUnauthorized(response2);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const request = createRequest(args, this._authHeader, traceHeaders, this._clientTag);
|
|
790
|
+
log4("call", {
|
|
791
|
+
url,
|
|
792
|
+
tryCount,
|
|
793
|
+
authHeader: !!this._authHeader
|
|
794
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 61, S: this });
|
|
795
|
+
const response = await fetch(url, request);
|
|
796
|
+
if (response.ok) {
|
|
797
|
+
const contentType2 = response.headers.get("Content-Type") ?? "";
|
|
798
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0" || !contentType2.includes("application/json")) {
|
|
799
|
+
return void 0;
|
|
800
|
+
}
|
|
801
|
+
const body2 = await response.clone().json();
|
|
802
|
+
if (typeof body2 !== "object" || body2 === null) {
|
|
803
|
+
return body2;
|
|
804
|
+
}
|
|
805
|
+
if (!("success" in body2)) {
|
|
806
|
+
return body2;
|
|
807
|
+
}
|
|
808
|
+
if (body2.success) {
|
|
809
|
+
return body2.data;
|
|
810
|
+
}
|
|
811
|
+
} else if (response.status === 401 && response.headers.get("WWW-Authenticate") !== null && !handledAuth) {
|
|
812
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
813
|
+
handledAuth = true;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
817
|
+
const body = contentType.startsWith("application/json") ? await response.clone().json() : void 0;
|
|
818
|
+
invariant4(!body?.success, "Expected body to not be a failure response or undefined.", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 92, S: this, A: ["!body?.success", "'Expected body to not be a failure response or undefined.'"] });
|
|
819
|
+
if (body?.data?.type === "auth_challenge" && typeof body?.data?.challenge === "string") {
|
|
820
|
+
processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);
|
|
821
|
+
} else if (body?.success === false) {
|
|
822
|
+
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
823
|
+
} else {
|
|
824
|
+
invariant4(!response.ok, "Expected response to not be ok.", { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 98, S: this, A: ["!response.ok", "'Expected response to not be ok.'"] });
|
|
825
|
+
processingError = await EdgeCallFailedError.fromHttpFailure(response);
|
|
826
|
+
}
|
|
827
|
+
} catch (error) {
|
|
828
|
+
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
829
|
+
}
|
|
830
|
+
if (processingError?.isRetryable && await shouldRetry(ctx, processingError.retryAfterMs)) {
|
|
831
|
+
log4.verbose("retrying request", {
|
|
832
|
+
url,
|
|
833
|
+
processingError
|
|
834
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 105, S: this });
|
|
835
|
+
} else {
|
|
836
|
+
throw processingError;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async _handleUnauthorized(response) {
|
|
841
|
+
if (!this._edgeIdentity) {
|
|
842
|
+
log4.warn("unauthorized response received before identity was set", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 116, S: this });
|
|
843
|
+
throw await EdgeCallFailedError.fromHttpFailure(response);
|
|
844
|
+
}
|
|
845
|
+
const challenge = await handleAuthChallenge(response, this._edgeIdentity);
|
|
846
|
+
return encodeAuthHeader(challenge);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
var createRequest = ({ method, body, json = true }, authHeader, traceHeaders, clientTag) => {
|
|
850
|
+
let requestBody;
|
|
851
|
+
const headers = {};
|
|
852
|
+
if (json) {
|
|
853
|
+
requestBody = body === void 0 ? void 0 : JSON.stringify(body);
|
|
854
|
+
headers["Content-Type"] = "application/json";
|
|
855
|
+
} else {
|
|
856
|
+
requestBody = body;
|
|
857
|
+
}
|
|
858
|
+
if (typeof requestBody === "string" && requestBody.length > WARNING_BODY_SIZE) {
|
|
859
|
+
log4.warn("Request with large body", {
|
|
860
|
+
bodySize: requestBody.length
|
|
861
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 133, S: void 0 });
|
|
862
|
+
}
|
|
863
|
+
if (authHeader) {
|
|
864
|
+
headers["Authorization"] = authHeader;
|
|
865
|
+
}
|
|
866
|
+
if (traceHeaders) {
|
|
867
|
+
Object.assign(headers, traceHeaders);
|
|
868
|
+
}
|
|
869
|
+
if (clientTag) {
|
|
870
|
+
headers[EDGE_CLIENT_TAG_HEADER] = clientTag;
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
method,
|
|
874
|
+
body: requestBody,
|
|
875
|
+
headers
|
|
876
|
+
};
|
|
877
|
+
};
|
|
878
|
+
var getTraceHeaders = (ctx) => {
|
|
879
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE2);
|
|
880
|
+
if (!traceCtx) {
|
|
881
|
+
return void 0;
|
|
882
|
+
}
|
|
883
|
+
const headers = {
|
|
884
|
+
traceparent: traceCtx.traceparent
|
|
885
|
+
};
|
|
886
|
+
if (traceCtx.tracestate) {
|
|
887
|
+
headers.tracestate = traceCtx.tracestate;
|
|
888
|
+
}
|
|
889
|
+
return headers;
|
|
890
|
+
};
|
|
891
|
+
var createRetryHandler = ({ retry: retry2 }) => {
|
|
892
|
+
if (!retry2 || retry2.count < 1) {
|
|
893
|
+
return async () => false;
|
|
894
|
+
}
|
|
895
|
+
let retries = 0;
|
|
896
|
+
const maxRetries = retry2.count ?? DEFAULT_MAX_RETRIES_COUNT;
|
|
897
|
+
const baseTimeout = retry2.timeout ?? DEFAULT_RETRY_TIMEOUT;
|
|
898
|
+
const jitter = retry2.jitter ?? DEFAULT_RETRY_JITTER;
|
|
899
|
+
return async (ctx, retryAfter) => {
|
|
900
|
+
if (++retries > maxRetries || ctx.disposed) {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
if (retryAfter) {
|
|
904
|
+
await sleep(retryAfter);
|
|
905
|
+
} else {
|
|
906
|
+
const timeout2 = baseTimeout + Math.random() * jitter;
|
|
907
|
+
await sleep(timeout2);
|
|
908
|
+
}
|
|
909
|
+
return true;
|
|
910
|
+
};
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// src/edge-ai-http-client.ts
|
|
914
|
+
import * as Headers2 from "@effect/platform/Headers";
|
|
915
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
916
|
+
import * as HttpClientError from "@effect/platform/HttpClientError";
|
|
917
|
+
import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
|
|
918
|
+
import * as Effect2 from "effect/Effect";
|
|
919
|
+
import * as FiberRef from "effect/FiberRef";
|
|
920
|
+
import * as Layer2 from "effect/Layer";
|
|
921
|
+
import * as Stream from "effect/Stream";
|
|
922
|
+
import { BaseError } from "@dxos/errors";
|
|
923
|
+
import { log as log5 } from "@dxos/log";
|
|
924
|
+
import { BYOK_HEADER } from "@dxos/protocols";
|
|
925
|
+
var __dxlog_file7 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-ai-http-client.ts";
|
|
926
|
+
var ByokError = class extends BaseError.extend("ByokError", "BYOK authentication failed") {
|
|
927
|
+
constructor(options) {
|
|
928
|
+
super({
|
|
929
|
+
context: {
|
|
930
|
+
status: options.status,
|
|
931
|
+
provider: options.provider
|
|
932
|
+
},
|
|
933
|
+
...options
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
var requestInitTagKey = "@effect/platform/FetchHttpClient/FetchOptions";
|
|
938
|
+
var EdgeAiHttpClient = class _EdgeAiHttpClient {
|
|
939
|
+
static make = (getClient) => HttpClient.make((request, url, signal, fiber) => {
|
|
940
|
+
const edgeClient = getClient();
|
|
941
|
+
const context = fiber.getFiberRef(FiberRef.currentContext);
|
|
942
|
+
const options = context.unsafeMap.get(requestInitTagKey) ?? {};
|
|
943
|
+
const headers = options.headers ? Headers2.merge(Headers2.fromInput(options.headers), request.headers) : request.headers;
|
|
944
|
+
const carriedByok = !!headers[BYOK_HEADER.toLowerCase()];
|
|
945
|
+
const send = (body) => Effect2.tryPromise({
|
|
946
|
+
try: () => edgeClient.anthropicAiRequest(new Request(url, {
|
|
947
|
+
...options,
|
|
948
|
+
method: request.method,
|
|
949
|
+
headers,
|
|
950
|
+
body,
|
|
951
|
+
signal
|
|
952
|
+
})),
|
|
953
|
+
catch: (cause) => {
|
|
954
|
+
log5.error("Failed to fetch", {
|
|
955
|
+
cause
|
|
956
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 60, S: this });
|
|
957
|
+
return new HttpClientError.RequestError({
|
|
958
|
+
request,
|
|
959
|
+
reason: "Transport",
|
|
960
|
+
cause
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}).pipe(Effect2.flatMap((response) => {
|
|
964
|
+
const httpResponse = HttpClientResponse.fromWeb(request, response);
|
|
965
|
+
if (carriedByok && (response.status === 401 || response.status === 403)) {
|
|
966
|
+
return Effect2.tryPromise({
|
|
967
|
+
try: () => response.clone().json(),
|
|
968
|
+
catch: () => void 0
|
|
969
|
+
}).pipe(Effect2.orElseSucceed(() => void 0), Effect2.flatMap((body2) => Effect2.fail(new HttpClientError.ResponseError({
|
|
970
|
+
request,
|
|
971
|
+
response: httpResponse,
|
|
972
|
+
reason: "StatusCode",
|
|
973
|
+
cause: new ByokError({
|
|
974
|
+
status: response.status,
|
|
975
|
+
provider: "anthropic.com",
|
|
976
|
+
message: body2?.error?.message ?? "Authentication failed"
|
|
977
|
+
})
|
|
978
|
+
}))));
|
|
979
|
+
}
|
|
980
|
+
return Effect2.succeed(httpResponse);
|
|
981
|
+
}));
|
|
982
|
+
switch (request.body._tag) {
|
|
983
|
+
case "Raw":
|
|
984
|
+
case "Uint8Array":
|
|
985
|
+
return send(request.body.body);
|
|
986
|
+
case "FormData":
|
|
987
|
+
return send(request.body.formData);
|
|
988
|
+
case "Stream":
|
|
989
|
+
return Stream.toReadableStreamEffect(request.body.stream).pipe(Effect2.flatMap(send));
|
|
990
|
+
}
|
|
991
|
+
return send(void 0);
|
|
992
|
+
});
|
|
993
|
+
static layer = (getClient) => Layer2.succeed(HttpClient.HttpClient, _EdgeAiHttpClient.make(getClient));
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/edge-http-client.ts
|
|
997
|
+
import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
|
|
998
|
+
import * as HttpClient3 from "@effect/platform/HttpClient";
|
|
999
|
+
import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
|
|
1000
|
+
import * as Effect3 from "effect/Effect";
|
|
1001
|
+
import * as Function from "effect/Function";
|
|
1002
|
+
import { EffectEx } from "@dxos/effect";
|
|
1003
|
+
import { invariant as invariant5 } from "@dxos/invariant";
|
|
1004
|
+
import { log as log6 } from "@dxos/log";
|
|
1005
|
+
import { EDGE_CLIENT_TAG_HEADER as EDGE_CLIENT_TAG_HEADER2 } from "@dxos/protocols";
|
|
1006
|
+
import { createUrl } from "@dxos/util";
|
|
1007
|
+
var __dxlog_file8 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-http-client.ts";
|
|
1008
|
+
var EdgeHttpClient = class extends BaseHttpClient {
|
|
1009
|
+
constructor(baseUrl, options) {
|
|
1010
|
+
super(baseUrl, options);
|
|
1011
|
+
log6("created", {
|
|
1012
|
+
url: this.baseUrl
|
|
1013
|
+
}, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 25, S: this });
|
|
1014
|
+
}
|
|
1015
|
+
//
|
|
1016
|
+
// Status
|
|
1017
|
+
//
|
|
1018
|
+
async getStatus(ctx, args) {
|
|
1019
|
+
return this._call(ctx, new URL("/status", this.baseUrl), {
|
|
1020
|
+
...args,
|
|
1021
|
+
method: "GET",
|
|
1022
|
+
auth: true
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
//
|
|
1026
|
+
// Agents
|
|
1027
|
+
//
|
|
1028
|
+
createAgent(ctx, body, args) {
|
|
1029
|
+
return this._call(ctx, new URL("/agents/create", this.baseUrl), {
|
|
1030
|
+
...args,
|
|
1031
|
+
method: "POST",
|
|
1032
|
+
body
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
getAgentStatus(ctx, request, args) {
|
|
1036
|
+
return this._call(ctx, new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
|
|
1037
|
+
...args,
|
|
1038
|
+
method: "GET"
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
//
|
|
1042
|
+
// Credentials
|
|
1043
|
+
//
|
|
1044
|
+
getCredentialsForNotarization(ctx, spaceId, args) {
|
|
1045
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), {
|
|
1046
|
+
...args,
|
|
1047
|
+
method: "GET"
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
async notarizeCredentials(ctx, spaceId, body, args) {
|
|
1051
|
+
await this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), {
|
|
1052
|
+
...args,
|
|
1053
|
+
body,
|
|
1054
|
+
method: "POST"
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
//
|
|
1058
|
+
// Identity
|
|
1059
|
+
//
|
|
1060
|
+
async recoverIdentity(ctx, body, args) {
|
|
1061
|
+
return this._call(ctx, new URL("/identity/recover", this.baseUrl), {
|
|
1062
|
+
...args,
|
|
1063
|
+
body,
|
|
1064
|
+
method: "POST"
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
//
|
|
1068
|
+
// Invitations (space join)
|
|
1069
|
+
//
|
|
1070
|
+
async joinSpaceByInvitation(ctx, spaceId, body, args) {
|
|
1071
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/join`, this.baseUrl), {
|
|
1072
|
+
...args,
|
|
1073
|
+
body,
|
|
1074
|
+
method: "POST"
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
//
|
|
1078
|
+
// OAuth
|
|
1079
|
+
//
|
|
1080
|
+
async initiateOAuthFlow(ctx, body, args) {
|
|
1081
|
+
return this._call(ctx, new URL("/oauth/initiate", this.baseUrl), {
|
|
1082
|
+
...args,
|
|
1083
|
+
body,
|
|
1084
|
+
method: "POST"
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
async completeOAuthRegistration(ctx, body, args) {
|
|
1088
|
+
return this._call(ctx, new URL("/oauth/registration/complete", this.baseUrl), {
|
|
1089
|
+
...args,
|
|
1090
|
+
body,
|
|
1091
|
+
method: "POST"
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
//
|
|
1095
|
+
// Spaces
|
|
1096
|
+
//
|
|
1097
|
+
async createSpace(ctx, body, args) {
|
|
1098
|
+
return this._call(ctx, new URL("/spaces/create", this.baseUrl), {
|
|
1099
|
+
...args,
|
|
1100
|
+
body,
|
|
1101
|
+
method: "POST"
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
//
|
|
1105
|
+
// Queues
|
|
1106
|
+
//
|
|
1107
|
+
async queryQueue(ctx, subspaceTag, spaceId, query, args) {
|
|
1108
|
+
const queueId = query.queueIds?.[0];
|
|
1109
|
+
invariant5(queueId, "queueId required", { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 123, S: this, A: ["queueId", "'queueId required'"] });
|
|
1110
|
+
return this._call(ctx, createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
1111
|
+
after: query.after,
|
|
1112
|
+
before: query.before,
|
|
1113
|
+
limit: query.limit,
|
|
1114
|
+
reverse: query.reverse,
|
|
1115
|
+
objectIds: query.objectIds?.join(",")
|
|
1116
|
+
}), {
|
|
1117
|
+
...args,
|
|
1118
|
+
method: "GET"
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
async insertIntoQueue(ctx, subspaceTag, spaceId, queueId, objects, args) {
|
|
1122
|
+
return this._call(ctx, new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
1123
|
+
...args,
|
|
1124
|
+
body: {
|
|
1125
|
+
objects
|
|
1126
|
+
},
|
|
1127
|
+
method: "POST"
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
async deleteFromQueue(ctx, subspaceTag, spaceId, queueId, objectIds, args) {
|
|
1131
|
+
return this._call(ctx, createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
1132
|
+
ids: objectIds.join(",")
|
|
1133
|
+
}), {
|
|
1134
|
+
...args,
|
|
1135
|
+
method: "DELETE"
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
//
|
|
1139
|
+
// Functions
|
|
1140
|
+
//
|
|
1141
|
+
async uploadFunction(ctx, pathParts, body, args) {
|
|
1142
|
+
const formData = new FormData();
|
|
1143
|
+
formData.append("name", body.name ?? "");
|
|
1144
|
+
formData.append("version", body.version);
|
|
1145
|
+
formData.append("ownerPublicKey", body.ownerPublicKey);
|
|
1146
|
+
formData.append("entryPoint", body.entryPoint);
|
|
1147
|
+
body.runtime && formData.append("runtime", body.runtime);
|
|
1148
|
+
for (const [filename, content] of Object.entries(body.assets)) {
|
|
1149
|
+
formData.append("assets", new Blob([
|
|
1150
|
+
content
|
|
1151
|
+
], {
|
|
1152
|
+
type: getFileMimeType(filename)
|
|
1153
|
+
}), filename);
|
|
1154
|
+
}
|
|
1155
|
+
const path = [
|
|
1156
|
+
"functions",
|
|
1157
|
+
...pathParts.functionId ? [
|
|
1158
|
+
pathParts.functionId
|
|
1159
|
+
] : []
|
|
1160
|
+
].join("/");
|
|
1161
|
+
return this._call(ctx, new URL(path, this.baseUrl), {
|
|
1162
|
+
...args,
|
|
1163
|
+
body: formData,
|
|
1164
|
+
method: "PUT",
|
|
1165
|
+
json: false
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async listFunctions(ctx, args) {
|
|
1169
|
+
return this._call(ctx, new URL("/functions", this.baseUrl), {
|
|
1170
|
+
...args,
|
|
1171
|
+
method: "GET"
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
async invokeFunction(ctx, params, input, args) {
|
|
1175
|
+
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
1176
|
+
if (params.version) {
|
|
1177
|
+
url.searchParams.set("version", params.version);
|
|
1178
|
+
}
|
|
1179
|
+
if (params.spaceId) {
|
|
1180
|
+
url.searchParams.set("spaceId", params.spaceId.toString());
|
|
1181
|
+
}
|
|
1182
|
+
if (params.cpuTimeLimit) {
|
|
1183
|
+
url.searchParams.set("cpuTimeLimit", params.cpuTimeLimit.toString());
|
|
1184
|
+
}
|
|
1185
|
+
if (params.subrequestsLimit) {
|
|
1186
|
+
url.searchParams.set("subrequestsLimit", params.subrequestsLimit.toString());
|
|
1187
|
+
}
|
|
1188
|
+
return this._call(ctx, url, {
|
|
1189
|
+
...args,
|
|
1190
|
+
body: input,
|
|
1191
|
+
method: "POST"
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
//
|
|
1195
|
+
// Workflows
|
|
1196
|
+
//
|
|
1197
|
+
async executeWorkflow(ctx, spaceId, graphId, input, args) {
|
|
1198
|
+
return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
1199
|
+
...args,
|
|
1200
|
+
body: input,
|
|
1201
|
+
method: "POST"
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
//
|
|
1205
|
+
// Triggers
|
|
1206
|
+
//
|
|
1207
|
+
async getCronTriggers(ctx, spaceId) {
|
|
1208
|
+
return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons`, this.baseUrl), {
|
|
1209
|
+
method: "GET"
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
async getTriggersDispatcherStatus(ctx, spaceId, args) {
|
|
1213
|
+
return this._call(ctx, new URL(`/triggers/${spaceId}/status`, this.baseUrl), {
|
|
1214
|
+
...args,
|
|
1215
|
+
method: "GET",
|
|
1216
|
+
auth: true
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
async forceRunCronTrigger(ctx, spaceId, triggerId) {
|
|
1220
|
+
return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {
|
|
1221
|
+
method: "POST"
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
//
|
|
1225
|
+
// Query
|
|
1226
|
+
//
|
|
1227
|
+
async execQuery(ctx, spaceId, body, args) {
|
|
1228
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/exec-query`, this.baseUrl), {
|
|
1229
|
+
...args,
|
|
1230
|
+
body,
|
|
1231
|
+
method: "POST"
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
//
|
|
1235
|
+
// Registry
|
|
1236
|
+
//
|
|
1237
|
+
async getRegistryPlugins(ctx, args) {
|
|
1238
|
+
return this._call(ctx, new URL("/registry/plugins", this.baseUrl), {
|
|
1239
|
+
...args,
|
|
1240
|
+
method: "GET"
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
async getRegistryPluginVersions(ctx, repo, args) {
|
|
1244
|
+
return this._call(ctx, new URL(`/registry/plugins/${encodeURIComponent(repo)}/versions`, this.baseUrl), {
|
|
1245
|
+
...args,
|
|
1246
|
+
method: "GET"
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
//
|
|
1250
|
+
// Import/Export
|
|
1251
|
+
//
|
|
1252
|
+
async importBundle(ctx, spaceId, body, args) {
|
|
1253
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), {
|
|
1254
|
+
...args,
|
|
1255
|
+
body,
|
|
1256
|
+
method: "PUT"
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
async exportBundle(ctx, spaceId, body, args) {
|
|
1260
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
1261
|
+
...args,
|
|
1262
|
+
body,
|
|
1263
|
+
method: "POST"
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
//
|
|
1267
|
+
// Proxy
|
|
1268
|
+
//
|
|
1269
|
+
/**
|
|
1270
|
+
* Fetch through the edge proxy for third-party REST APIs.
|
|
1271
|
+
* TEMPORARY: currently routes through legacy open proxy. See https://github.com/dxos/edge/pull/576.
|
|
1272
|
+
*/
|
|
1273
|
+
async proxyFetch(target, init = {}) {
|
|
1274
|
+
return proxyFetchLegacy(target, init, this._clientTag);
|
|
1275
|
+
}
|
|
1276
|
+
//
|
|
1277
|
+
// AI service.
|
|
1278
|
+
//
|
|
1279
|
+
/**
|
|
1280
|
+
* Issue an authenticated request to the EDGE AI route (`/ai/generate/anthropic/*`), which
|
|
1281
|
+
* proxies to the AI service. Used as the backend HTTP client for the Anthropic AI provider
|
|
1282
|
+
* (see {@link EdgeAiHttpClient}).
|
|
1283
|
+
*
|
|
1284
|
+
* Returns the raw `Response` so streaming bodies are forwarded unchanged to `@effect/ai`.
|
|
1285
|
+
* Requires an identity to have been set via {@link setIdentity}.
|
|
1286
|
+
*/
|
|
1287
|
+
// TODO(mykola): Merge into `BaseHttpClient._call` once it can return a streaming/raw `Response`;
|
|
1288
|
+
// the auth/retry loop below duplicates the one in `_call`.
|
|
1289
|
+
async anthropicAiRequest(request) {
|
|
1290
|
+
const incoming = new URL(request.url);
|
|
1291
|
+
const base = this.baseUrl.replace(/\/$/, "");
|
|
1292
|
+
const target = new URL(`${base}/ai/generate/anthropic${incoming.pathname}${incoming.search}`);
|
|
1293
|
+
const method = request.method;
|
|
1294
|
+
const body = method === "GET" || method === "HEAD" ? void 0 : await request.arrayBuffer();
|
|
1295
|
+
let handledAuth = false;
|
|
1296
|
+
while (true) {
|
|
1297
|
+
if (!this._authHeader) {
|
|
1298
|
+
const authResponse = await fetch(new URL("/auth", this.baseUrl));
|
|
1299
|
+
if (authResponse.status === 401) {
|
|
1300
|
+
this._authHeader = await this._handleUnauthorized(authResponse);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const headers = new Headers(request.headers);
|
|
1304
|
+
if (this._authHeader) {
|
|
1305
|
+
headers.set("Authorization", this._authHeader);
|
|
1306
|
+
}
|
|
1307
|
+
if (this._clientTag) {
|
|
1308
|
+
headers.set(EDGE_CLIENT_TAG_HEADER2, this._clientTag);
|
|
1309
|
+
}
|
|
1310
|
+
const response = await fetch(target, {
|
|
1311
|
+
method,
|
|
1312
|
+
headers,
|
|
1313
|
+
body,
|
|
1314
|
+
signal: request.signal
|
|
1315
|
+
});
|
|
1316
|
+
if (response.status === 401 && response.headers.get("WWW-Authenticate") !== null && !handledAuth) {
|
|
1317
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
1318
|
+
handledAuth = true;
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
return response;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
//
|
|
1325
|
+
// Internal (Effect-based, used by tests)
|
|
1326
|
+
//
|
|
1327
|
+
async _fetch(url, _args) {
|
|
1328
|
+
return Function.pipe(HttpClient3.execute(HttpClientRequest.make(_args.method)(url.toString())), withLogging, withRetryConfig, Effect3.provide(FetchHttpClient.layer), Effect3.provide(HttpConfig.default), Effect3.withSpan("EdgeHttpClient"), EffectEx.runAndForwardErrors);
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
var getFileMimeType = (filename) => [
|
|
1332
|
+
".js",
|
|
1333
|
+
".mjs"
|
|
1334
|
+
].some((ext) => filename.endsWith(ext)) ? "application/javascript+module" : filename.endsWith(".wasm") ? "application/wasm" : "application/octet-stream";
|
|
1335
|
+
|
|
1336
|
+
// src/hub-http-client.ts
|
|
1337
|
+
var HubHttpClient = class extends BaseHttpClient {
|
|
1338
|
+
constructor(hubUrl, options) {
|
|
1339
|
+
super(hubUrl, options);
|
|
1340
|
+
}
|
|
1341
|
+
//
|
|
1342
|
+
// Public (unauthenticated) endpoints
|
|
1343
|
+
//
|
|
1344
|
+
async checkEmailExists(ctx, body, args) {
|
|
1345
|
+
return this._call(ctx, new URL("/account/email/exists", this.baseUrl), {
|
|
1346
|
+
...args,
|
|
1347
|
+
body,
|
|
1348
|
+
method: "POST"
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
async validateInvitationCode(ctx, body, args) {
|
|
1352
|
+
return this._call(ctx, new URL("/account/invitation-code/validate", this.baseUrl), {
|
|
1353
|
+
...args,
|
|
1354
|
+
body,
|
|
1355
|
+
method: "POST"
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
async redeemInvitationCode(ctx, body, args) {
|
|
1359
|
+
return this._call(ctx, new URL("/account/invitation-code/redeem", this.baseUrl), {
|
|
1360
|
+
...args,
|
|
1361
|
+
body,
|
|
1362
|
+
method: "POST"
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Existing-account email login. Server inlines `token` for test emails; regular
|
|
1367
|
+
* emails are delivered out-of-band. Response is identical for unknown emails
|
|
1368
|
+
* (enumeration-safe).
|
|
1369
|
+
*/
|
|
1370
|
+
async login(ctx, body, args) {
|
|
1371
|
+
return this._call(ctx, new URL("/account/login", this.baseUrl), {
|
|
1372
|
+
...args,
|
|
1373
|
+
body,
|
|
1374
|
+
method: "POST"
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
async requestAccess(ctx, body, args) {
|
|
1378
|
+
return this._call(ctx, new URL("/account/request-access", this.baseUrl), {
|
|
1379
|
+
...args,
|
|
1380
|
+
body,
|
|
1381
|
+
method: "POST"
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
//
|
|
1385
|
+
// Authenticated (VP) endpoints
|
|
1386
|
+
//
|
|
1387
|
+
async getAccount(ctx, args) {
|
|
1388
|
+
return this._call(ctx, new URL("/account/me", this.baseUrl), {
|
|
1389
|
+
...args,
|
|
1390
|
+
method: "GET"
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
async deleteAccount(ctx, args) {
|
|
1394
|
+
return this._call(ctx, new URL("/account/me", this.baseUrl), {
|
|
1395
|
+
...args,
|
|
1396
|
+
method: "DELETE"
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
async listAccountInvitations(ctx, args) {
|
|
1400
|
+
return this._call(ctx, new URL("/account/invitation", this.baseUrl), {
|
|
1401
|
+
...args,
|
|
1402
|
+
method: "GET"
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
async issueAccountInvitation(ctx, args) {
|
|
1406
|
+
return this._call(ctx, new URL("/account/invitation/issue", this.baseUrl), {
|
|
1407
|
+
...args,
|
|
1408
|
+
method: "POST"
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
async resendVerificationEmail(ctx, args) {
|
|
1412
|
+
return this._call(ctx, new URL("/account/email/resend-verification", this.baseUrl), {
|
|
1413
|
+
...args,
|
|
1414
|
+
method: "POST"
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
export {
|
|
1419
|
+
BaseHttpClient,
|
|
1420
|
+
ByokError,
|
|
1421
|
+
CLOUDFLARE_MESSAGE_MAX_BYTES,
|
|
1422
|
+
CLOUDFLARE_RPC_MAX_BYTES,
|
|
1423
|
+
EdgeAiHttpClient,
|
|
1424
|
+
EdgeClient,
|
|
1425
|
+
EdgeConnectionClosedError,
|
|
1426
|
+
EdgeHttpClient,
|
|
1427
|
+
EdgeIdentityChangedError,
|
|
1428
|
+
HttpConfig,
|
|
1429
|
+
HubHttpClient,
|
|
1430
|
+
Protocol,
|
|
1431
|
+
WebSocketMuxer,
|
|
1432
|
+
createChainEdgeIdentity,
|
|
1433
|
+
createDeviceEdgeIdentity,
|
|
1434
|
+
createEphemeralEdgeIdentity,
|
|
1435
|
+
createStubEdgeIdentity,
|
|
1436
|
+
createTestHaloEdgeIdentity,
|
|
1437
|
+
encodeAuthHeader,
|
|
1438
|
+
getTypename,
|
|
1439
|
+
handleAuthChallenge,
|
|
1440
|
+
protocol,
|
|
1441
|
+
proxyFetchLegacy,
|
|
1442
|
+
requestInitTagKey,
|
|
1443
|
+
toUint8Array,
|
|
1444
|
+
withLogging,
|
|
1445
|
+
withRetry,
|
|
1446
|
+
withRetryConfig
|
|
1447
|
+
};
|
|
1448
|
+
//# sourceMappingURL=index.mjs.map
|