@floegence/flowersec-core 2.5.3 → 3.0.0
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/README.md +46 -18
- package/dist/browser/connectSessionV3.d.ts +13 -0
- package/dist/browser/connectSessionV3.js +75 -0
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +2 -1
- package/dist/browser/v2.d.ts +3 -0
- package/dist/browser/v2.js +2 -0
- package/dist/facade.d.ts +15 -8
- package/dist/facade.js +5 -4
- package/dist/framing/jsonframe.d.ts +1 -1
- package/dist/framing/jsonframe.js +3 -1
- package/dist/interop/proxyServerPeer.js +1 -1
- package/dist/interop/serverParityPeer.js +8 -1
- package/dist/node/acceptor.d.ts +10 -0
- package/dist/node/acceptor.js +53 -7
- package/dist/node/acceptorV3.d.ts +57 -0
- package/dist/node/acceptorV3.js +227 -0
- package/dist/node/connectSessionV3.d.ts +21 -0
- package/dist/node/connectSessionV3.js +73 -0
- package/dist/node/index.d.ts +15 -10
- package/dist/node/index.js +6 -5
- package/dist/node/nativeTransportAddon.d.ts +39 -1
- package/dist/node/nativeTransportAddon.js +29 -5
- package/dist/node/rawQuicAdapterV3.d.ts +10 -0
- package/dist/node/rawQuicAdapterV3.js +76 -0
- package/dist/node/rawQuicServerV3.d.ts +24 -0
- package/dist/node/rawQuicServerV3.js +34 -0
- package/dist/node/runtimeAuthorizationV3.d.ts +29 -0
- package/dist/node/runtimeAuthorizationV3.js +126 -0
- package/dist/node/tunnelRuntimeV3.d.ts +57 -0
- package/dist/node/tunnelRuntimeV3.js +857 -0
- package/dist/node/v2.d.ts +11 -0
- package/dist/node/v2.js +6 -0
- package/dist/node/webSocketServerV3.d.ts +27 -0
- package/dist/node/webSocketServerV3.js +219 -0
- package/dist/proxy/runtime.js +39 -6
- package/dist/proxy/serviceWorker.d.ts +2 -0
- package/dist/proxy/serviceWorker.js +63 -7
- package/dist/proxy/windowBridge.d.ts +1 -0
- package/dist/proxy/windowBridge.js +93 -33
- package/dist/proxy/wsPatch.d.ts +1 -0
- package/dist/proxy/wsPatch.js +39 -6
- package/dist/public/streamHandlers.d.ts +3 -0
- package/dist/public/streamHandlers.js +6 -1
- package/dist/rpc/client.js +9 -7
- package/dist/rpc/server.js +44 -20
- package/dist/rpc/validate.d.ts +1 -0
- package/dist/rpc/validate.js +18 -6
- package/dist/v2/index.d.ts +14 -0
- package/dist/v2/index.js +7 -0
- package/dist/v2/protocol.js +23 -32
- package/dist/v2/publicSession.js +4 -0
- package/dist/v2/session.d.ts +5 -0
- package/dist/v2/session.js +94 -35
- package/dist/v3/admissionError.d.ts +4 -0
- package/dist/v3/admissionError.js +8 -0
- package/dist/v3/artifact.d.ts +145 -0
- package/dist/v3/artifact.js +1162 -0
- package/dist/v3/artifactLease.d.ts +17 -0
- package/dist/v3/artifactLease.js +109 -0
- package/dist/v3/browserRuntime.d.ts +37 -0
- package/dist/v3/browserRuntime.js +197 -0
- package/dist/v3/browserSessionRuntime.d.ts +2 -0
- package/dist/v3/browserSessionRuntime.js +21 -0
- package/dist/v3/capability.d.ts +32 -0
- package/dist/v3/capability.js +345 -0
- package/dist/v3/carrier.d.ts +84 -0
- package/dist/v3/carrier.js +411 -0
- package/dist/v3/connectionController.d.ts +87 -0
- package/dist/v3/connectionController.js +805 -0
- package/dist/v3/contract.d.ts +65 -0
- package/dist/v3/contract.js +3 -0
- package/dist/v3/controller.d.ts +43 -0
- package/dist/v3/controller.js +303 -0
- package/dist/v3/handshake.d.ts +81 -0
- package/dist/v3/handshake.js +471 -0
- package/dist/v3/jcs.d.ts +6 -0
- package/dist/v3/jcs.js +69 -0
- package/dist/v3/jsonPreflight.d.ts +1 -0
- package/dist/v3/jsonPreflight.js +251 -0
- package/dist/v3/nodeRuntime.d.ts +40 -0
- package/dist/v3/nodeRuntime.js +315 -0
- package/dist/v3/nodeSessionRuntime.d.ts +2 -0
- package/dist/v3/nodeSessionRuntime.js +13 -0
- package/dist/v3/protocol.d.ts +99 -0
- package/dist/v3/protocol.js +773 -0
- package/dist/v3/publicApi.d.ts +12 -0
- package/dist/v3/publicApi.js +46 -0
- package/dist/v3/publicSession.d.ts +1 -0
- package/dist/v3/publicSession.js +281 -0
- package/dist/v3/retryDisposition.d.ts +16 -0
- package/dist/v3/retryDisposition.js +30 -0
- package/dist/v3/runtimeAdapters.d.ts +9 -0
- package/dist/v3/runtimeAdapters.js +109 -0
- package/dist/v3/security.d.ts +34 -0
- package/dist/v3/security.js +100 -0
- package/dist/v3/serverAdmission.d.ts +38 -0
- package/dist/v3/serverAdmission.js +223 -0
- package/dist/v3/session.d.ts +318 -0
- package/dist/v3/session.js +2289 -0
- package/dist/v3/sessionConnector.d.ts +37 -0
- package/dist/v3/sessionConnector.js +440 -0
- package/dist/v3/streamLifetimeLedger.d.ts +31 -0
- package/dist/v3/streamLifetimeLedger.js +131 -0
- package/dist/v3/streamMetadata.d.ts +1 -0
- package/dist/v3/streamMetadata.js +7 -0
- package/dist/v3/transportConstants.d.ts +56 -0
- package/dist/v3/transportConstants.js +59 -0
- package/dist/v3/unreliableMessage.d.ts +44 -0
- package/dist/v3/unreliableMessage.js +284 -0
- package/dist/v3/webSocketCarrier.d.ts +31 -0
- package/dist/v3/webSocketCarrier.js +370 -0
- package/dist/v3/webTransportCarrier.d.ts +27 -0
- package/dist/v3/webTransportCarrier.js +534 -0
- package/dist/yamux/stream.d.ts +3 -0
- package/dist/yamux/stream.js +17 -4
- package/package.json +4 -4
- package/sbom/cyclonedx.json +6 -6
- package/sbom/spdx.json +11 -11
|
@@ -39,12 +39,17 @@ export class MessagePortByteStream {
|
|
|
39
39
|
if (this.terminalError !== undefined)
|
|
40
40
|
throw this.terminalError;
|
|
41
41
|
return await new Promise((resolve, reject) => {
|
|
42
|
-
const
|
|
42
|
+
const cleanup = () => options.signal?.removeEventListener("abort", onAbort);
|
|
43
|
+
const waiter = {
|
|
44
|
+
resolve: (value) => { cleanup(); resolve(value); },
|
|
45
|
+
reject: (error) => { cleanup(); reject(error); },
|
|
46
|
+
};
|
|
43
47
|
const onAbort = () => {
|
|
44
48
|
const index = this.readWaiters.indexOf(waiter);
|
|
45
|
-
if (index
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
if (index < 0)
|
|
50
|
+
return;
|
|
51
|
+
this.readWaiters.splice(index, 1);
|
|
52
|
+
waiter.reject(new SessionError("canceled"));
|
|
48
53
|
};
|
|
49
54
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
50
55
|
this.readWaiters.push(waiter);
|
|
@@ -66,17 +71,19 @@ export class MessagePortByteStream {
|
|
|
66
71
|
const copy = data.slice();
|
|
67
72
|
this.buffered += copy.length;
|
|
68
73
|
await new Promise((resolve, reject) => {
|
|
74
|
+
const onAbort = () => this.settleWrite(id, new SessionError("canceled"));
|
|
69
75
|
this.writeWaiters.set(id, {
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
bytes: copy.length,
|
|
77
|
+
...(options.signal === undefined ? {} : { signal: options.signal, onAbort }),
|
|
78
|
+
resolve,
|
|
79
|
+
reject,
|
|
72
80
|
});
|
|
81
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
73
82
|
try {
|
|
74
83
|
this.port.postMessage({ type: "chunk", id, data: copy.buffer }, [copy.buffer]);
|
|
75
84
|
}
|
|
76
85
|
catch {
|
|
77
|
-
this.
|
|
78
|
-
this.buffered -= copy.length;
|
|
79
|
-
reject(new SessionError("operation_failed"));
|
|
86
|
+
this.settleWrite(id, new SessionError("operation_failed"));
|
|
80
87
|
}
|
|
81
88
|
});
|
|
82
89
|
return data.length;
|
|
@@ -130,11 +137,7 @@ export class MessagePortByteStream {
|
|
|
130
137
|
return;
|
|
131
138
|
}
|
|
132
139
|
if (message.type === "ack" && Number.isSafeInteger(message.id)) {
|
|
133
|
-
|
|
134
|
-
if (waiter !== undefined) {
|
|
135
|
-
this.writeWaiters.delete(message.id);
|
|
136
|
-
waiter.resolve();
|
|
137
|
-
}
|
|
140
|
+
this.settleWrite(message.id);
|
|
138
141
|
return;
|
|
139
142
|
}
|
|
140
143
|
if (message.type === "end") {
|
|
@@ -159,9 +162,8 @@ export class MessagePortByteStream {
|
|
|
159
162
|
this.readBuffered = 0;
|
|
160
163
|
for (const waiter of this.readWaiters.splice(0))
|
|
161
164
|
waiter.resolve(null);
|
|
162
|
-
for (const
|
|
163
|
-
|
|
164
|
-
this.writeWaiters.clear();
|
|
165
|
+
for (const id of [...this.writeWaiters.keys()])
|
|
166
|
+
this.settleWrite(id, new SessionError("closed"));
|
|
165
167
|
this.port.close();
|
|
166
168
|
}
|
|
167
169
|
fail(error) {
|
|
@@ -173,11 +175,22 @@ export class MessagePortByteStream {
|
|
|
173
175
|
this.readBuffered = 0;
|
|
174
176
|
for (const waiter of this.readWaiters.splice(0))
|
|
175
177
|
waiter.reject(error);
|
|
176
|
-
for (const
|
|
177
|
-
|
|
178
|
-
this.writeWaiters.clear();
|
|
178
|
+
for (const id of [...this.writeWaiters.keys()])
|
|
179
|
+
this.settleWrite(id, error);
|
|
179
180
|
this.port.close();
|
|
180
181
|
}
|
|
182
|
+
settleWrite(id, error) {
|
|
183
|
+
const waiter = this.writeWaiters.get(id);
|
|
184
|
+
if (waiter === undefined)
|
|
185
|
+
return;
|
|
186
|
+
this.writeWaiters.delete(id);
|
|
187
|
+
waiter.signal?.removeEventListener("abort", waiter.onAbort);
|
|
188
|
+
this.buffered = Math.max(0, this.buffered - waiter.bytes);
|
|
189
|
+
if (error === undefined)
|
|
190
|
+
waiter.resolve();
|
|
191
|
+
else
|
|
192
|
+
waiter.reject(error);
|
|
193
|
+
}
|
|
181
194
|
}
|
|
182
195
|
function bridgeLimits(maxWsFrameBytes, maxWsBufferedAmountBytes) {
|
|
183
196
|
const wsFrame = maxWsFrameBytes ?? SDK_DEFAULTS.proxy.maxWsFrameBytes;
|
|
@@ -279,33 +292,65 @@ export function registerProxyAppWindow(options) {
|
|
|
279
292
|
dispose: () => { disposed = true; },
|
|
280
293
|
});
|
|
281
294
|
}
|
|
282
|
-
async function bridgeStreams(runtimeStream, port) {
|
|
295
|
+
async function bridgeStreams(runtimeStream, port, signal) {
|
|
283
296
|
const bridge = new MessagePortByteStream(port);
|
|
297
|
+
const controller = new AbortController();
|
|
298
|
+
let resetTask;
|
|
299
|
+
const resetBoth = () => {
|
|
300
|
+
resetTask ??= Promise.allSettled([
|
|
301
|
+
Promise.resolve().then(async () => await runtimeStream.reset()),
|
|
302
|
+
Promise.resolve().then(async () => await bridge.reset()),
|
|
303
|
+
]);
|
|
304
|
+
return resetTask;
|
|
305
|
+
};
|
|
306
|
+
const abort = () => {
|
|
307
|
+
controller.abort(signal?.reason ?? new SessionError("canceled"));
|
|
308
|
+
void resetBoth();
|
|
309
|
+
};
|
|
310
|
+
if (signal?.aborted === true)
|
|
311
|
+
abort();
|
|
312
|
+
else
|
|
313
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
284
314
|
const left = (async () => {
|
|
285
315
|
while (true) {
|
|
286
|
-
const chunk = await runtimeStream.read();
|
|
316
|
+
const chunk = await runtimeStream.read({ signal: controller.signal });
|
|
287
317
|
if (chunk === null) {
|
|
288
318
|
await bridge.closeWrite();
|
|
289
319
|
return;
|
|
290
320
|
}
|
|
291
|
-
await bridge.write(chunk);
|
|
321
|
+
await bridge.write(chunk, { signal: controller.signal });
|
|
292
322
|
}
|
|
293
|
-
})();
|
|
323
|
+
})().catch((error) => { controller.abort(error); void resetBoth(); throw error; });
|
|
294
324
|
const right = (async () => {
|
|
295
325
|
while (true) {
|
|
296
|
-
const chunk = await bridge.read();
|
|
326
|
+
const chunk = await bridge.read({ signal: controller.signal });
|
|
297
327
|
if (chunk === null) {
|
|
298
328
|
await runtimeStream.closeWrite();
|
|
299
329
|
return;
|
|
300
330
|
}
|
|
301
331
|
let offset = 0;
|
|
302
332
|
while (offset < chunk.length)
|
|
303
|
-
offset += await runtimeStream.write(chunk.subarray(offset));
|
|
333
|
+
offset += await runtimeStream.write(chunk.subarray(offset), { signal: controller.signal });
|
|
304
334
|
}
|
|
305
|
-
})();
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
})().catch((error) => { controller.abort(error); void resetBoth(); throw error; });
|
|
336
|
+
try {
|
|
337
|
+
const settled = await Promise.allSettled([left, right]);
|
|
338
|
+
const failure = settled.find((result) => result.status === "rejected");
|
|
339
|
+
if (failure !== undefined) {
|
|
340
|
+
await resetBoth();
|
|
341
|
+
throw failure.reason;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
await Promise.all([runtimeStream.close(), bridge.close()]);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
await resetBoth();
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
signal?.removeEventListener("abort", abort);
|
|
353
|
+
}
|
|
309
354
|
}
|
|
310
355
|
export function registerProxyControllerWindow(options) {
|
|
311
356
|
const target = options.targetWindow ?? globalThis.window;
|
|
@@ -315,6 +360,7 @@ export function registerProxyControllerWindow(options) {
|
|
|
315
360
|
}
|
|
316
361
|
const nonce = capability(options.capabilityNonce);
|
|
317
362
|
let disposed = false;
|
|
363
|
+
const active = new Set();
|
|
318
364
|
const onMessage = (event) => {
|
|
319
365
|
if (disposed || !allowed.has(event.origin) || (options.expectedSource !== undefined && event.source !== options.expectedSource))
|
|
320
366
|
return;
|
|
@@ -331,6 +377,7 @@ export function registerProxyControllerWindow(options) {
|
|
|
331
377
|
void (async () => {
|
|
332
378
|
let canceled = false;
|
|
333
379
|
const openController = new AbortController();
|
|
380
|
+
active.add(openController);
|
|
334
381
|
port.onmessage = (message) => {
|
|
335
382
|
if (message.data?.type === "reset" ||
|
|
336
383
|
message.data?.type === "close") {
|
|
@@ -349,17 +396,30 @@ export function registerProxyControllerWindow(options) {
|
|
|
349
396
|
return;
|
|
350
397
|
}
|
|
351
398
|
port.postMessage({ type: WEBSOCKET_ACK_MESSAGE, ok: true, protocol: opened.protocol });
|
|
352
|
-
await bridgeStreams(opened.stream, port);
|
|
399
|
+
await bridgeStreams(opened.stream, port, openController.signal);
|
|
353
400
|
}
|
|
354
401
|
catch {
|
|
355
|
-
|
|
402
|
+
try {
|
|
403
|
+
port.postMessage({ type: WEBSOCKET_ACK_MESSAGE, ok: false });
|
|
404
|
+
}
|
|
405
|
+
catch { /* Port is already closed. */ }
|
|
356
406
|
port.close();
|
|
357
407
|
}
|
|
408
|
+
finally {
|
|
409
|
+
active.delete(openController);
|
|
410
|
+
}
|
|
358
411
|
})();
|
|
359
412
|
}
|
|
360
413
|
};
|
|
361
414
|
target.addEventListener("message", onMessage);
|
|
362
|
-
return Object.freeze({
|
|
415
|
+
return Object.freeze({
|
|
416
|
+
dispose: () => {
|
|
417
|
+
disposed = true;
|
|
418
|
+
target.removeEventListener("message", onMessage);
|
|
419
|
+
for (const controller of active)
|
|
420
|
+
controller.abort(new SessionError("closed"));
|
|
421
|
+
},
|
|
422
|
+
});
|
|
363
423
|
}
|
|
364
424
|
export async function registerProxyAppWindowWithServiceWorkerControl(options) {
|
|
365
425
|
await registerServiceWorkerAndEnsureControl({
|
package/dist/proxy/wsPatch.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type WebSocketPatchOptions = Readonly<{
|
|
|
4
4
|
shouldProxy?: (url: URL) => boolean;
|
|
5
5
|
maxWsFrameBytes?: number;
|
|
6
6
|
maxWsBufferedAmountBytes?: number;
|
|
7
|
+
closeHandshakeTimeoutMs?: number;
|
|
7
8
|
}>;
|
|
8
9
|
export declare function installWebSocketPatch(options: WebSocketPatchOptions): Readonly<{
|
|
9
10
|
uninstall(): void;
|
package/dist/proxy/wsPatch.js
CHANGED
|
@@ -64,6 +64,7 @@ export function installWebSocketPatch(options) {
|
|
|
64
64
|
const runtimeLimits = options.runtime.limits;
|
|
65
65
|
const maxFrameBytes = limit("maxWsFrameBytes", options.maxWsFrameBytes, runtimeLimits.maxWsFrameBytes ?? 1024 * 1024);
|
|
66
66
|
const maxBufferedBytes = limit("maxWsBufferedAmountBytes", options.maxWsBufferedAmountBytes, runtimeLimits.maxWsBufferedAmountBytes ?? 4 * 1024 * 1024);
|
|
67
|
+
const closeHandshakeTimeoutMs = limit("closeHandshakeTimeoutMs", options.closeHandshakeTimeoutMs, 5_000);
|
|
67
68
|
const shouldProxy = options.shouldProxy ?? ((url) => {
|
|
68
69
|
const location = globalThis.location;
|
|
69
70
|
if (location?.hostname === undefined || location.hostname === "")
|
|
@@ -95,6 +96,7 @@ export function installWebSocketPatch(options) {
|
|
|
95
96
|
abort = new AbortController();
|
|
96
97
|
stream;
|
|
97
98
|
writes = Promise.resolve();
|
|
99
|
+
closeTimer;
|
|
98
100
|
constructor(input, protocols) {
|
|
99
101
|
const url = new URL(String(input), globalThis.location?.href);
|
|
100
102
|
if (!shouldProxy(url))
|
|
@@ -152,12 +154,17 @@ export function installWebSocketPatch(options) {
|
|
|
152
154
|
const reasonBytes = encoder.encode(reason);
|
|
153
155
|
if (reasonBytes.length > 123)
|
|
154
156
|
throw new DOMException("WebSocket close reason is too long", "SyntaxError");
|
|
157
|
+
if (this.readyState === ProxyWebSocket.CONNECTING || this.stream === undefined) {
|
|
158
|
+
this.fail();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
155
161
|
this.readyState = ProxyWebSocket.CLOSING;
|
|
156
162
|
const payload = code === undefined ? new Uint8Array() : new Uint8Array([...u16be(code), ...reasonBytes]);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
const stream = this.stream;
|
|
164
|
+
this.closeTimer = setTimeout(() => this.fail(), closeHandshakeTimeoutMs);
|
|
165
|
+
this.writes = this.writes
|
|
166
|
+
.then(async () => await writeFrame(stream, 8, payload, maxFrameBytes))
|
|
167
|
+
.catch(() => this.fail());
|
|
161
168
|
}
|
|
162
169
|
async connect(url, protocols) {
|
|
163
170
|
try {
|
|
@@ -183,10 +190,19 @@ export function installWebSocketPatch(options) {
|
|
|
183
190
|
this.writes = this.writes.then(async () => await writeFrame(stream, 10, frame.payload, maxFrameBytes)).catch(() => this.fail());
|
|
184
191
|
}
|
|
185
192
|
else if (frame.opcode === 8) {
|
|
193
|
+
if (frame.payload.length === 1)
|
|
194
|
+
throw new Error("invalid WebSocket close frame");
|
|
186
195
|
const code = frame.payload.length >= 2 ? readU16(frame.payload) : 1000;
|
|
187
196
|
const reason = frame.payload.length > 2 ? decoder.decode(frame.payload.subarray(2)) : "";
|
|
188
|
-
this.readyState
|
|
189
|
-
this.
|
|
197
|
+
const peerInitiated = this.readyState === ProxyWebSocket.OPEN;
|
|
198
|
+
this.readyState = ProxyWebSocket.CLOSING;
|
|
199
|
+
if (this.closeTimer !== undefined)
|
|
200
|
+
clearTimeout(this.closeTimer);
|
|
201
|
+
if (peerInitiated) {
|
|
202
|
+
this.writes = this.writes.then(async () => await writeFrame(stream, 8, frame.payload, maxFrameBytes));
|
|
203
|
+
}
|
|
204
|
+
await this.writes;
|
|
205
|
+
this.completeClose(code, reason);
|
|
190
206
|
return;
|
|
191
207
|
}
|
|
192
208
|
else if (frame.opcode === 1) {
|
|
@@ -211,12 +227,29 @@ export function installWebSocketPatch(options) {
|
|
|
211
227
|
fail() {
|
|
212
228
|
if (this.readyState === ProxyWebSocket.CLOSED)
|
|
213
229
|
return;
|
|
230
|
+
if (this.closeTimer !== undefined)
|
|
231
|
+
clearTimeout(this.closeTimer);
|
|
214
232
|
this.readyState = ProxyWebSocket.CLOSED;
|
|
215
233
|
this.bufferedAmount = 0;
|
|
216
234
|
this.emit("error", new Event("error"));
|
|
217
235
|
this.emit("close", new CloseEvent("close", { code: 1006, reason: "proxy WebSocket failed", wasClean: false }));
|
|
236
|
+
const stream = this.stream;
|
|
237
|
+
this.stream = undefined;
|
|
238
|
+
this.abort.abort();
|
|
239
|
+
void stream?.reset().catch(() => undefined);
|
|
240
|
+
}
|
|
241
|
+
completeClose(code, reason) {
|
|
242
|
+
if (this.readyState === ProxyWebSocket.CLOSED)
|
|
243
|
+
return;
|
|
244
|
+
if (this.closeTimer !== undefined)
|
|
245
|
+
clearTimeout(this.closeTimer);
|
|
246
|
+
this.readyState = ProxyWebSocket.CLOSED;
|
|
247
|
+
this.bufferedAmount = 0;
|
|
248
|
+
this.emit("close", new CloseEvent("close", { code, reason, wasClean: true }));
|
|
249
|
+
const stream = this.stream;
|
|
218
250
|
this.stream = undefined;
|
|
219
251
|
this.abort.abort();
|
|
252
|
+
void stream?.close().catch(() => stream.reset().catch(() => undefined));
|
|
220
253
|
}
|
|
221
254
|
}
|
|
222
255
|
globalThis.WebSocket = ProxyWebSocket;
|
|
@@ -18,3 +18,6 @@ export declare class StreamHandlers {
|
|
|
18
18
|
handleStream(kind: string, handler: StreamHandler): void;
|
|
19
19
|
serve(session: Session, options?: OperationOptions): Promise<void>;
|
|
20
20
|
}
|
|
21
|
+
/** Explicit-compatibility application-stream handlers. */
|
|
22
|
+
export declare class LegacyStreamHandlers extends StreamHandlers {
|
|
23
|
+
}
|
|
@@ -11,6 +11,7 @@ export class HandlerRegistrationError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
const streamHandlerStates = new WeakMap();
|
|
14
|
+
const RESERVED_RPC_KINDS = new Set(["flowersec.rpc.v2", "flowersec.rpc.v3"]);
|
|
14
15
|
/** Carrier-neutral application-stream handlers for any established Session. */
|
|
15
16
|
export class StreamHandlers {
|
|
16
17
|
constructor(options = {}) {
|
|
@@ -22,6 +23,7 @@ export class StreamHandlers {
|
|
|
22
23
|
}
|
|
23
24
|
streamHandlerStates.set(this, {
|
|
24
25
|
maxConcurrentStreams: maximum,
|
|
26
|
+
reservedRPCKinds: RESERVED_RPC_KINDS,
|
|
25
27
|
streams: new Map(),
|
|
26
28
|
frozen: false,
|
|
27
29
|
});
|
|
@@ -36,6 +38,9 @@ export class StreamHandlers {
|
|
|
36
38
|
await serveFrozenStreamHandlers(freezeStreamHandlers(this), session, options, async () => await session.close());
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
/** Explicit-compatibility application-stream handlers. */
|
|
42
|
+
export class LegacyStreamHandlers extends StreamHandlers {
|
|
43
|
+
}
|
|
39
44
|
function mutableStreamHandlerState(handlers) {
|
|
40
45
|
const state = streamHandlerStates.get(handlers);
|
|
41
46
|
if (state === undefined)
|
|
@@ -50,7 +55,7 @@ function registerIntoState(state, entries) {
|
|
|
50
55
|
const pending = new Set();
|
|
51
56
|
for (const [kind, handler] of entries) {
|
|
52
57
|
if (!validApplicationStreamKind(kind) ||
|
|
53
|
-
|
|
58
|
+
state.reservedRPCKinds.has(kind) ||
|
|
54
59
|
typeof handler !== "function") {
|
|
55
60
|
throw new HandlerRegistrationError("invalid_handler");
|
|
56
61
|
}
|
package/dist/rpc/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_MAX_JSON_FRAME_BYTES, readJsonFrame, writeJsonFrame } from "../framing/jsonframe.js";
|
|
2
|
-
import { assertRpcEnvelope } from "./validate.js";
|
|
2
|
+
import { assertRpcEnvelope, assertRpcTypeId } from "./validate.js";
|
|
3
3
|
// Guard against precision loss when encoding request IDs as numbers.
|
|
4
4
|
const MAX_SAFE_REQUEST_ID = BigInt(Number.MAX_SAFE_INTEGER);
|
|
5
5
|
// RpcClient sends request/response envelopes and dispatches notifications.
|
|
@@ -25,12 +25,13 @@ export class RpcClient {
|
|
|
25
25
|
async call(typeId, payload, signal) {
|
|
26
26
|
if (this.closed)
|
|
27
27
|
throw new Error("rpc client closed");
|
|
28
|
+
const validatedTypeId = assertRpcTypeId(typeId);
|
|
28
29
|
if (this.nextId > MAX_SAFE_REQUEST_ID)
|
|
29
30
|
throw new Error("request id overflow");
|
|
30
31
|
const requestId = this.nextId;
|
|
31
32
|
this.nextId += 1n;
|
|
32
33
|
const env = {
|
|
33
|
-
type_id:
|
|
34
|
+
type_id: validatedTypeId,
|
|
34
35
|
request_id: Number(requestId),
|
|
35
36
|
response_to: 0,
|
|
36
37
|
payload
|
|
@@ -39,7 +40,7 @@ export class RpcClient {
|
|
|
39
40
|
this.pending.set(requestId, { resolve, reject });
|
|
40
41
|
});
|
|
41
42
|
try {
|
|
42
|
-
await writeJsonFrame(this.write, env);
|
|
43
|
+
await writeJsonFrame(this.write, env, DEFAULT_MAX_JSON_FRAME_BYTES);
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
46
|
this.pending.delete(requestId);
|
|
@@ -71,7 +72,7 @@ export class RpcClient {
|
|
|
71
72
|
}
|
|
72
73
|
// onNotify registers a handler for incoming notifications.
|
|
73
74
|
onNotify(typeId, handler) {
|
|
74
|
-
const tid = typeId
|
|
75
|
+
const tid = assertRpcTypeId(typeId);
|
|
75
76
|
const set = this.notifyHandlers.get(tid) ?? new Set();
|
|
76
77
|
set.add(handler);
|
|
77
78
|
this.notifyHandlers.set(tid, set);
|
|
@@ -86,13 +87,14 @@ export class RpcClient {
|
|
|
86
87
|
async notify(typeId, payload) {
|
|
87
88
|
if (this.closed)
|
|
88
89
|
throw new Error("rpc client closed");
|
|
90
|
+
const validatedTypeId = assertRpcTypeId(typeId);
|
|
89
91
|
const env = {
|
|
90
|
-
type_id:
|
|
92
|
+
type_id: validatedTypeId,
|
|
91
93
|
request_id: 0,
|
|
92
94
|
response_to: 0,
|
|
93
95
|
payload
|
|
94
96
|
};
|
|
95
|
-
await writeJsonFrame(this.write, env);
|
|
97
|
+
await writeJsonFrame(this.write, env, DEFAULT_MAX_JSON_FRAME_BYTES);
|
|
96
98
|
}
|
|
97
99
|
async readLoop() {
|
|
98
100
|
try {
|
|
@@ -101,7 +103,7 @@ export class RpcClient {
|
|
|
101
103
|
if (v.response_to === 0) {
|
|
102
104
|
// Notification: response_to=0 and request_id=0.
|
|
103
105
|
if (v.request_id === 0) {
|
|
104
|
-
const set = this.notifyHandlers.get(v.type_id
|
|
106
|
+
const set = this.notifyHandlers.get(v.type_id);
|
|
105
107
|
if (set != null) {
|
|
106
108
|
for (const h of set) {
|
|
107
109
|
try {
|
package/dist/rpc/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_MAX_JSON_FRAME_BYTES, readJsonFrame, writeJsonFrame } from "../framing/jsonframe.js";
|
|
2
|
-
import { assertRpcEnvelope, assertRpcError } from "./validate.js";
|
|
2
|
+
import { assertRpcEnvelope, assertRpcError, assertRpcTypeId } from "./validate.js";
|
|
3
3
|
import { SDK_DEFAULTS } from "../defaults.js";
|
|
4
4
|
const DEFAULT_RPC_SERVER_OPTIONS = Object.freeze({
|
|
5
5
|
maxConcurrentRequests: SDK_DEFAULTS.rpc.maxConcurrentRequests,
|
|
@@ -10,13 +10,13 @@ export class RpcRouter {
|
|
|
10
10
|
handlers = new Map();
|
|
11
11
|
notifyHandlers = new Map();
|
|
12
12
|
register(typeId, handler) {
|
|
13
|
-
this.handlers.set(typeId
|
|
13
|
+
this.handlers.set(assertRpcTypeId(typeId), handler);
|
|
14
14
|
}
|
|
15
15
|
handler(typeId) {
|
|
16
|
-
return this.handlers.get(typeId
|
|
16
|
+
return this.handlers.get(assertRpcTypeId(typeId));
|
|
17
17
|
}
|
|
18
18
|
onNotify(typeId, handler) {
|
|
19
|
-
const normalized = typeId
|
|
19
|
+
const normalized = assertRpcTypeId(typeId);
|
|
20
20
|
const handlers = this.notifyHandlers.get(normalized) ?? new Set();
|
|
21
21
|
handlers.add(handler);
|
|
22
22
|
this.notifyHandlers.set(normalized, handlers);
|
|
@@ -27,7 +27,7 @@ export class RpcRouter {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
async dispatchNotification(typeId, payload) {
|
|
30
|
-
const normalized = typeId
|
|
30
|
+
const normalized = assertRpcTypeId(typeId);
|
|
31
31
|
const requestHandler = this.handlers.get(normalized);
|
|
32
32
|
if (requestHandler !== undefined)
|
|
33
33
|
await requestHandler(payload);
|
|
@@ -76,7 +76,7 @@ export class RpcServer {
|
|
|
76
76
|
if (this.closed)
|
|
77
77
|
throw new Error("rpc server closed");
|
|
78
78
|
await this.writeEnvelope({
|
|
79
|
-
type_id: typeId
|
|
79
|
+
type_id: assertRpcTypeId(typeId),
|
|
80
80
|
request_id: 0,
|
|
81
81
|
response_to: 0,
|
|
82
82
|
payload,
|
|
@@ -93,6 +93,7 @@ export class RpcServer {
|
|
|
93
93
|
return await new Promise(() => undefined);
|
|
94
94
|
}));
|
|
95
95
|
let failure;
|
|
96
|
+
const aborted = abortPromise(signal);
|
|
96
97
|
try {
|
|
97
98
|
while (!this.closed) {
|
|
98
99
|
if (signal?.aborted)
|
|
@@ -101,6 +102,7 @@ export class RpcServer {
|
|
|
101
102
|
readJsonFrame(this.transport.readExactly, DEFAULT_MAX_JSON_FRAME_BYTES),
|
|
102
103
|
this.terminalSignal.then((error) => { throw error; }),
|
|
103
104
|
workerFailure,
|
|
105
|
+
...(aborted === undefined ? [] : [aborted.promise]),
|
|
104
106
|
]);
|
|
105
107
|
const v = assertRpcEnvelope(next);
|
|
106
108
|
if (v.response_to !== 0)
|
|
@@ -126,6 +128,9 @@ export class RpcServer {
|
|
|
126
128
|
failure = err;
|
|
127
129
|
this.terminalError = err;
|
|
128
130
|
}
|
|
131
|
+
finally {
|
|
132
|
+
aborted?.cleanup();
|
|
133
|
+
}
|
|
129
134
|
let closeError;
|
|
130
135
|
try {
|
|
131
136
|
this.close(failure ?? this.terminalError ?? new Error("rpc server closed"));
|
|
@@ -173,19 +178,23 @@ export class RpcServer {
|
|
|
173
178
|
if (work == null)
|
|
174
179
|
return;
|
|
175
180
|
const v = work.envelope;
|
|
176
|
-
const h = this.router.handler(v.type_id);
|
|
177
|
-
let out;
|
|
178
|
-
if (h == null)
|
|
179
|
-
out = { payload: null, error: { code: 404, message: "handler not found" } };
|
|
180
|
-
else {
|
|
181
|
-
try {
|
|
182
|
-
out = await h(v.payload);
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
out = { payload: null, error: { code: 500, message: "internal error" } };
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
181
|
try {
|
|
182
|
+
const h = this.router.handler(v.type_id);
|
|
183
|
+
let out;
|
|
184
|
+
if (h == null)
|
|
185
|
+
out = { payload: null, error: { code: 404, message: "handler not found" } };
|
|
186
|
+
else {
|
|
187
|
+
const outcome = await Promise.race([
|
|
188
|
+
Promise.resolve().then(() => h(v.payload)).then((value) => ({ kind: "completed", value }), () => ({
|
|
189
|
+
kind: "completed",
|
|
190
|
+
value: { payload: null, error: { code: 500, message: "internal error" } },
|
|
191
|
+
})),
|
|
192
|
+
this.terminalSignal.then(() => ({ kind: "terminated" })),
|
|
193
|
+
]);
|
|
194
|
+
if (outcome.kind === "terminated")
|
|
195
|
+
return;
|
|
196
|
+
out = outcome.value;
|
|
197
|
+
}
|
|
189
198
|
if (this.closed)
|
|
190
199
|
return;
|
|
191
200
|
await this.writeResponse(v, out);
|
|
@@ -201,7 +210,12 @@ export class RpcServer {
|
|
|
201
210
|
if (work == null)
|
|
202
211
|
return;
|
|
203
212
|
const v = work.envelope;
|
|
204
|
-
await
|
|
213
|
+
const completed = await Promise.race([
|
|
214
|
+
Promise.resolve().then(() => this.router.dispatchNotification(v.type_id, v.payload)).then(() => true),
|
|
215
|
+
this.terminalSignal.then(() => false),
|
|
216
|
+
]);
|
|
217
|
+
if (!completed)
|
|
218
|
+
return;
|
|
205
219
|
}
|
|
206
220
|
}
|
|
207
221
|
async nextWork(queue, waiters) {
|
|
@@ -236,7 +250,7 @@ export class RpcServer {
|
|
|
236
250
|
await this.writeEnvelope(resp);
|
|
237
251
|
}
|
|
238
252
|
async writeEnvelope(envelope) {
|
|
239
|
-
const write = this.writeChain.then(() => writeJsonFrame(this.transport.write, envelope));
|
|
253
|
+
const write = this.writeChain.then(() => writeJsonFrame(this.transport.write, envelope, DEFAULT_MAX_JSON_FRAME_BYTES));
|
|
240
254
|
this.writeChain = write;
|
|
241
255
|
try {
|
|
242
256
|
await write;
|
|
@@ -247,6 +261,16 @@ export class RpcServer {
|
|
|
247
261
|
}
|
|
248
262
|
}
|
|
249
263
|
}
|
|
264
|
+
function abortPromise(signal) {
|
|
265
|
+
if (signal === undefined || signal.aborted)
|
|
266
|
+
return undefined;
|
|
267
|
+
let onAbort;
|
|
268
|
+
const promise = new Promise((_resolve, reject) => {
|
|
269
|
+
onAbort = () => reject(signal.reason ?? new Error("aborted"));
|
|
270
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
271
|
+
});
|
|
272
|
+
return { promise, cleanup: () => signal.removeEventListener("abort", onAbort) };
|
|
273
|
+
}
|
|
250
274
|
function positiveInteger(value, name) {
|
|
251
275
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
252
276
|
throw new RangeError(`${name} must be a positive integer`);
|
package/dist/rpc/validate.d.ts
CHANGED
package/dist/rpc/validate.js
CHANGED
|
@@ -6,21 +6,33 @@ const strictDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
|
6
6
|
// The wire format is JSON, so JS numbers are used. For u64 we enforce the safe integer range
|
|
7
7
|
// to avoid silent precision loss on request/response correlation.
|
|
8
8
|
export function assertRpcEnvelope(v) {
|
|
9
|
-
if (typeof v !== "object" || v == null)
|
|
9
|
+
if (typeof v !== "object" || v == null || Array.isArray(v))
|
|
10
10
|
throw new Error("bad rpc envelope");
|
|
11
11
|
const o = v;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const keys = Object.keys(o);
|
|
13
|
+
if (keys.some((key) => key !== "type_id" && key !== "request_id" && key !== "response_to" && key !== "payload" && key !== "error")) {
|
|
14
|
+
throw new Error("bad rpc envelope: shape");
|
|
15
|
+
}
|
|
16
|
+
if (!Object.prototype.hasOwnProperty.call(o, "payload"))
|
|
17
|
+
throw new Error("bad rpc envelope: payload");
|
|
18
|
+
assertRpcTypeId(o.type_id);
|
|
14
19
|
if (!isSafeU64Number(o.request_id))
|
|
15
20
|
throw new Error("bad rpc envelope: request_id");
|
|
16
21
|
if (!isSafeU64Number(o.response_to))
|
|
17
22
|
throw new Error("bad rpc envelope: response_to");
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
if (o.request_id !== 0 && o.response_to !== 0)
|
|
24
|
+
throw new Error("bad rpc envelope: request/response shape");
|
|
25
|
+
if (o.error != null && o.response_to === 0)
|
|
26
|
+
throw new Error("bad rpc envelope: error shape");
|
|
27
|
+
if (o.error != null)
|
|
20
28
|
assertRpcError(o.error);
|
|
21
|
-
}
|
|
22
29
|
return o;
|
|
23
30
|
}
|
|
31
|
+
export function assertRpcTypeId(value) {
|
|
32
|
+
if (!isSafeU32Number(value) || value === 0)
|
|
33
|
+
throw new RangeError("RPC typeId must be a non-zero u32 integer");
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
24
36
|
export function assertRpcError(value) {
|
|
25
37
|
if (typeof value !== "object" || value == null)
|
|
26
38
|
throw new Error("bad rpc envelope: error");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type { ByteStream, IncomingStream, JsonObject, JsonPrimitive, JsonValue, OperationOptions, RpcPeer, RpcResult, SessionErrorCode, StreamOpenOptions, UnreliableMessageChannel, UnreliableMessageSendOptions, UnreliableMessageSendResult, SessionTermination, Session, } from "../public/contract.js";
|
|
2
|
+
export { SessionError, UnreliableMessageError } from "../public/contract.js";
|
|
3
|
+
export { createStreamMetadata, StreamMetadataError } from "../public/streamMetadata.js";
|
|
4
|
+
export type { StreamMetadata } from "../public/streamMetadata.js";
|
|
5
|
+
export { HandlerRegistrationError, LegacyStreamHandlers as StreamHandlers, } from "../public/streamHandlers.js";
|
|
6
|
+
export type { StreamHandler, StreamHandlerOptions, } from "../public/streamHandlers.js";
|
|
7
|
+
export { ArtifactLeaseError, createArtifactLease, } from "../public/artifactLease.js";
|
|
8
|
+
export type { ArtifactLease } from "../public/artifactLease.js";
|
|
9
|
+
export { Artifact, ArtifactError, parseArtifact } from "../public/artifact.js";
|
|
10
|
+
export type { ArtifactErrorCode } from "../public/artifact.js";
|
|
11
|
+
export type { ConnectErrorCode } from "../public/connectError.js";
|
|
12
|
+
export { ConnectError } from "../public/connectError.js";
|
|
13
|
+
export { ConnectionControllerError } from "../connectionController.js";
|
|
14
|
+
export type { ArtifactSource, ArtifactSourceResult, ConnectionController, ConnectionControllerFailure, ConnectionControllerOptions, ConnectionSnapshot, ConnectionState, RetryDisposition, } from "../connectionController.js";
|
package/dist/v2/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { SessionError, UnreliableMessageError } from "../public/contract.js";
|
|
2
|
+
export { createStreamMetadata, StreamMetadataError } from "../public/streamMetadata.js";
|
|
3
|
+
export { HandlerRegistrationError, LegacyStreamHandlers as StreamHandlers, } from "../public/streamHandlers.js";
|
|
4
|
+
export { ArtifactLeaseError, createArtifactLease, } from "../public/artifactLease.js";
|
|
5
|
+
export { Artifact, ArtifactError, parseArtifact } from "../public/artifact.js";
|
|
6
|
+
export { ConnectError } from "../public/connectError.js";
|
|
7
|
+
export { ConnectionControllerError } from "../connectionController.js";
|