@floegence/flowersec-core 2.3.5 → 2.3.7
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 +33 -42
- package/THIRD_PARTY_NOTICES.md +4 -79
- package/dist/cli.js +31 -31
- package/dist/connectionController.d.ts +1 -0
- package/dist/connectionController.js +3 -0
- package/dist/connector/adapters/rawQuicCandidate.d.ts +5 -0
- package/dist/connector/adapters/rawQuicCandidate.js +71 -0
- package/dist/connector/sessionAcceptor.d.ts +22 -2
- package/dist/connector/sessionAcceptor.js +51 -21
- package/dist/connector/sessionConnector.d.ts +2 -0
- package/dist/connector/sessionConnector.js +4 -1
- package/dist/interop/proxyServerPeer.d.ts +1 -0
- package/dist/interop/proxyServerPeer.js +83 -0
- package/dist/interop/serverParityPeer.d.ts +1 -0
- package/dist/interop/serverParityPeer.js +608 -0
- package/dist/node/acceptor.d.ts +31 -19
- package/dist/node/acceptor.js +360 -70
- package/dist/node/connectSession.d.ts +2 -1
- package/dist/node/connectSession.js +22 -12
- package/dist/node/controlplane.d.ts +137 -0
- package/dist/node/controlplane.js +439 -0
- package/dist/node/index.d.ts +8 -2
- package/dist/node/index.js +4 -1
- package/dist/node/nativeTransportAddon.d.ts +94 -0
- package/dist/node/nativeTransportAddon.js +166 -0
- package/dist/node/proxyServer.d.ts +31 -0
- package/dist/node/proxyServer.js +572 -0
- package/dist/node/rawQuicAdapter.d.ts +9 -0
- package/dist/node/rawQuicAdapter.js +107 -0
- package/dist/node/rawQuicServer.d.ts +24 -0
- package/dist/node/rawQuicServer.js +37 -0
- package/dist/node/runtimeCapability.d.ts +8 -1
- package/dist/node/runtimeCapability.js +20 -6
- package/dist/node/tunnelRuntime.d.ts +42 -0
- package/dist/node/tunnelRuntime.js +552 -0
- package/dist/node/webSocketServer.d.ts +24 -0
- package/dist/node/webSocketServer.js +135 -0
- package/dist/public/contract.d.ts +4 -3
- package/dist/transport/webSocketAdapter.d.ts +7 -0
- package/dist/transport/webSocketAdapter.js +136 -0
- package/dist/v2/artifact.js +1 -1
- package/dist/v2/capability.js +1 -1
- package/dist/v2/carrier.d.ts +4 -1
- package/dist/v2/carrier.js +2 -2
- package/dist/v2/handshake.js +7 -7
- package/dist/v2/protocol.js +5 -5
- package/dist/v2/publicSession.js +25 -7
- package/dist/v2/retryDisposition.js +4 -4
- package/dist/v2/session.d.ts +2 -2
- package/dist/v2/session.js +3 -3
- package/dist/v2/unreliableMessage.js +4 -4
- package/dist/vendor/tr46.js +11 -6
- package/dist/ws-client/binaryTransport.d.ts +3 -0
- package/dist/ws-client/binaryTransport.js +14 -0
- package/package.json +19 -16
- package/sbom/cyclonedx.json +52 -2585
- package/sbom/spdx.json +56 -1826
- package/dist/node/webTransportClient.d.ts +0 -9
- package/dist/node/webTransportClient.js +0 -56
- package/dist/node/webTransportServer.d.ts +0 -22
- package/dist/node/webTransportServer.js +0 -100
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createServer as createHTTPServer } from "node:http";
|
|
2
|
+
import { createServer as createHTTPSServer } from "node:https";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { isIP } from "node:net";
|
|
5
|
+
import { createServerWebSocketCarrierSessionV2 } from "../transport/webSocketAdapter.js";
|
|
6
|
+
import { WebSocketBinaryTransport } from "../ws-client/binaryTransport.js";
|
|
7
|
+
import { defaultWsMaxPayload } from "./wsDefaults.js";
|
|
8
|
+
export async function startNodeWebSocketServer(options) {
|
|
9
|
+
validateOptions(options);
|
|
10
|
+
const protocol = options.path === "direct" ? "flowersec.direct.v2" : "flowersec.tunnel.v2";
|
|
11
|
+
const endpoint = "/flowersec/v2/" + options.path;
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const wsModule = require("ws");
|
|
14
|
+
const WebSocketServer = wsModule.WebSocketServer;
|
|
15
|
+
const server = options.tls === undefined
|
|
16
|
+
? createHTTPServer()
|
|
17
|
+
: createHTTPSServer({ key: options.tls.privateKey, cert: options.tls.certificate, minVersion: "TLSv1.3", maxVersion: "TLSv1.3" });
|
|
18
|
+
const sessions = new SessionQueue();
|
|
19
|
+
const sockets = new Set();
|
|
20
|
+
const wss = new WebSocketServer({
|
|
21
|
+
noServer: true,
|
|
22
|
+
perMessageDeflate: false,
|
|
23
|
+
maxPayload: defaultWsMaxPayload({}),
|
|
24
|
+
handleProtocols(protocols) { return protocols.has(protocol) ? protocol : false; },
|
|
25
|
+
});
|
|
26
|
+
server.on("upgrade", (request, socket, head) => {
|
|
27
|
+
const origin = request.headers.origin;
|
|
28
|
+
const requested = request.headers["sec-websocket-protocol"];
|
|
29
|
+
const remote = request.socket.remoteAddress;
|
|
30
|
+
const local = request.socket.localAddress;
|
|
31
|
+
const plaintextAllowed = options.tls === undefined && options.path === "direct" && isLoopback(remote) && isLoopback(local);
|
|
32
|
+
if (request.url !== endpoint || typeof origin !== "string" || !options.allowedOrigins.includes(origin) || requested !== protocol || (options.tls === undefined && !plaintextAllowed)) {
|
|
33
|
+
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
34
|
+
socket.destroy();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
wss.handleUpgrade(request, socket, head, (webSocket) => wss.emit("connection", webSocket));
|
|
38
|
+
});
|
|
39
|
+
wss.on("connection", (socket) => {
|
|
40
|
+
sockets.add(socket);
|
|
41
|
+
socket.once("close", () => sockets.delete(socket));
|
|
42
|
+
const transport = new WebSocketBinaryTransport(socket);
|
|
43
|
+
sessions.push(createServerWebSocketCarrierSessionV2(transport, {
|
|
44
|
+
path: options.path,
|
|
45
|
+
inboundBidirectionalStreamCapacity: options.inboundBidirectionalStreamCapacity,
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
await new Promise((resolve, reject) => {
|
|
49
|
+
server.once("error", reject);
|
|
50
|
+
server.listen(options.port, options.host, resolve);
|
|
51
|
+
});
|
|
52
|
+
let closed = false;
|
|
53
|
+
return {
|
|
54
|
+
address() {
|
|
55
|
+
const address = server.address();
|
|
56
|
+
if (address === null || typeof address === "string")
|
|
57
|
+
throw new Error("Node WebSocket server is not listening");
|
|
58
|
+
return Object.freeze({ host: address.address, port: address.port });
|
|
59
|
+
},
|
|
60
|
+
async accept(acceptOptions = {}) { return await sessions.shift(acceptOptions.signal); },
|
|
61
|
+
async close() {
|
|
62
|
+
if (closed)
|
|
63
|
+
return;
|
|
64
|
+
closed = true;
|
|
65
|
+
sessions.close();
|
|
66
|
+
for (const socket of sockets)
|
|
67
|
+
socket.close();
|
|
68
|
+
wss.close();
|
|
69
|
+
await new Promise((resolve, reject) => server.close((error) => error === undefined ? resolve() : reject(error)));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function validateOptions(options) {
|
|
74
|
+
if (!Number.isInteger(options.port) || options.port < 0 || options.port > 65_535 || options.allowedOrigins.length === 0) {
|
|
75
|
+
throw new TypeError("invalid Node WebSocket listener options");
|
|
76
|
+
}
|
|
77
|
+
if (options.tls === undefined && (options.path !== "direct" || !isLoopback(options.host))) {
|
|
78
|
+
throw new TypeError("plaintext WebSocket is restricted to direct loopback listeners");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isLoopback(address) {
|
|
82
|
+
if (address === undefined)
|
|
83
|
+
return false;
|
|
84
|
+
if (address === "::1")
|
|
85
|
+
return true;
|
|
86
|
+
if (isIP(address) === 4)
|
|
87
|
+
return address.startsWith("127.");
|
|
88
|
+
return address.toLowerCase().startsWith("::ffff:127.");
|
|
89
|
+
}
|
|
90
|
+
class SessionQueue {
|
|
91
|
+
values = [];
|
|
92
|
+
waiters = new Set();
|
|
93
|
+
closed = false;
|
|
94
|
+
push(value) {
|
|
95
|
+
if (this.closed) {
|
|
96
|
+
void value.close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const waiter = this.waiters.values().next().value;
|
|
100
|
+
if (waiter === undefined)
|
|
101
|
+
this.values.push(value);
|
|
102
|
+
else {
|
|
103
|
+
this.waiters.delete(waiter);
|
|
104
|
+
waiter.resolve(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async shift(signal) {
|
|
108
|
+
if (signal?.aborted === true)
|
|
109
|
+
throw new Error("accept canceled");
|
|
110
|
+
const value = this.values.shift();
|
|
111
|
+
if (value !== undefined)
|
|
112
|
+
return value;
|
|
113
|
+
if (this.closed)
|
|
114
|
+
throw new Error("WebSocket listener is closed");
|
|
115
|
+
return await new Promise((resolve, reject) => {
|
|
116
|
+
const waiter = { resolve: (session) => { cleanup(); resolve(session); }, reject };
|
|
117
|
+
const abort = () => { this.waiters.delete(waiter); reject(new Error("accept canceled")); };
|
|
118
|
+
const cleanup = () => signal?.removeEventListener("abort", abort);
|
|
119
|
+
this.waiters.add(waiter);
|
|
120
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
close() {
|
|
124
|
+
if (this.closed)
|
|
125
|
+
return;
|
|
126
|
+
this.closed = true;
|
|
127
|
+
const error = new Error("WebSocket listener is closed");
|
|
128
|
+
for (const waiter of this.waiters)
|
|
129
|
+
waiter.reject(error);
|
|
130
|
+
this.waiters.clear();
|
|
131
|
+
for (const value of this.values)
|
|
132
|
+
void value.close();
|
|
133
|
+
this.values.length = 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -14,7 +14,7 @@ export type UnreliableMessageSendOptions = OperationOptions & Readonly<{
|
|
|
14
14
|
}>;
|
|
15
15
|
export type UnreliableMessageSendResult = "accepted" | "dropped_budget" | "dropped_expired" | "dropped_carrier";
|
|
16
16
|
export interface UnreliableMessageChannel {
|
|
17
|
-
readonly maxMessageSize:
|
|
17
|
+
readonly maxMessageSize: number;
|
|
18
18
|
send(message: Uint8Array, options: UnreliableMessageSendOptions): Promise<UnreliableMessageSendResult>;
|
|
19
19
|
receive(options?: OperationOptions): Promise<Uint8Array>;
|
|
20
20
|
}
|
|
@@ -42,8 +42,8 @@ export type RpcResult<Response = unknown> = Readonly<{
|
|
|
42
42
|
}>;
|
|
43
43
|
}>;
|
|
44
44
|
export interface RpcPeer {
|
|
45
|
-
call<Request = unknown, Response = unknown>(typeId: number, payload: Request, decodeResponse: (payload: JsonValue) => Response,
|
|
46
|
-
notify<Payload = unknown>(typeId: number, payload: Payload): Promise<void>;
|
|
45
|
+
call<Request = unknown, Response = unknown>(typeId: number, payload: Request, decodeResponse: (payload: JsonValue) => Response, options?: OperationOptions): Promise<RpcResult<Response>>;
|
|
46
|
+
notify<Payload = unknown>(typeId: number, payload: Payload, options?: OperationOptions): Promise<void>;
|
|
47
47
|
onNotify<Payload = unknown>(typeId: number, handler: (payload: Payload) => void): () => void;
|
|
48
48
|
}
|
|
49
49
|
export interface ByteStream {
|
|
@@ -71,6 +71,7 @@ export interface Session {
|
|
|
71
71
|
rekey(options?: OperationOptions): Promise<void>;
|
|
72
72
|
probeLiveness(options?: OperationOptions): Promise<number>;
|
|
73
73
|
waitTermination(): Promise<SessionTermination>;
|
|
74
|
+
waitTermination(options: OperationOptions): Promise<SessionTermination>;
|
|
74
75
|
close(): Promise<void>;
|
|
75
76
|
}
|
|
76
77
|
export type RetryDisposition = Readonly<{
|
|
@@ -6,6 +6,7 @@ export type WebSocketBinaryTransportV2 = Readonly<{
|
|
|
6
6
|
timeoutMs?: number;
|
|
7
7
|
}>): Promise<Uint8Array>;
|
|
8
8
|
writeBinary(data: Uint8Array, options?: OperationOptionsV2): Promise<void>;
|
|
9
|
+
flush(options?: OperationOptionsV2): Promise<void>;
|
|
9
10
|
close(): void;
|
|
10
11
|
}>;
|
|
11
12
|
export type WebSocketResourcePolicyV2 = Readonly<{
|
|
@@ -22,3 +23,9 @@ export declare function createWebSocketCarrierSessionV2(transport: WebSocketBina
|
|
|
22
23
|
inboundBidirectionalStreamCapacity: number;
|
|
23
24
|
resourcePolicy?: WebSocketResourcePolicyV2;
|
|
24
25
|
}>): CarrierSessionV2;
|
|
26
|
+
export declare function createServerWebSocketCarrierSessionV2(transport: WebSocketBinaryTransportV2, options: Readonly<{
|
|
27
|
+
path: PathKind;
|
|
28
|
+
inboundBidirectionalStreamCapacity: number;
|
|
29
|
+
resourcePolicy?: WebSocketResourcePolicyV2;
|
|
30
|
+
}>): CarrierSessionV2;
|
|
31
|
+
export declare function configureServerWebSocketCarrierRoleV2(carrier: CarrierSessionV2, client: boolean): void;
|
|
@@ -3,7 +3,139 @@ import { CarrierError } from "../v2/carrier.js";
|
|
|
3
3
|
export function createWebSocketCarrierSessionV2(transport, options) {
|
|
4
4
|
return new WebSocketYamuxCarrierSession(transport, options);
|
|
5
5
|
}
|
|
6
|
+
export function createServerWebSocketCarrierSessionV2(transport, options) {
|
|
7
|
+
requireCapacity(options.inboundBidirectionalStreamCapacity);
|
|
8
|
+
const carrier = new ServerWebSocketAdmissionCarrier(transport, options);
|
|
9
|
+
serverAdmissionCarriers.set(carrier, carrier);
|
|
10
|
+
return carrier;
|
|
11
|
+
}
|
|
12
|
+
const serverAdmissionCarriers = new WeakMap();
|
|
13
|
+
export function configureServerWebSocketCarrierRoleV2(carrier, client) {
|
|
14
|
+
const server = serverAdmissionCarriers.get(carrier);
|
|
15
|
+
if (server === undefined)
|
|
16
|
+
throw new CarrierError("closed", "WebSocket carrier is not a server admission carrier");
|
|
17
|
+
server.configureRole(client);
|
|
18
|
+
}
|
|
19
|
+
class ServerWebSocketAdmissionCarrier {
|
|
20
|
+
transport;
|
|
21
|
+
options;
|
|
22
|
+
kind = "websocket";
|
|
23
|
+
path;
|
|
24
|
+
inboundBidirectionalStreamCapacity;
|
|
25
|
+
unreliableDatagrams = undefined;
|
|
26
|
+
admissionAvailable = true;
|
|
27
|
+
active;
|
|
28
|
+
closed = false;
|
|
29
|
+
client = false;
|
|
30
|
+
constructor(transport, options) {
|
|
31
|
+
this.transport = transport;
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.path = options.path;
|
|
34
|
+
this.inboundBidirectionalStreamCapacity = options.inboundBidirectionalStreamCapacity;
|
|
35
|
+
}
|
|
36
|
+
async openStream(options = {}) {
|
|
37
|
+
return await this.activate().openStream(options);
|
|
38
|
+
}
|
|
39
|
+
async acceptStream(options = {}) {
|
|
40
|
+
throwIfAborted(options.signal);
|
|
41
|
+
if (this.closed)
|
|
42
|
+
throw new CarrierError("closed", "WebSocket carrier is closed");
|
|
43
|
+
if (this.admissionAvailable) {
|
|
44
|
+
this.admissionAvailable = false;
|
|
45
|
+
return new WebSocketAdmissionStream(this.transport);
|
|
46
|
+
}
|
|
47
|
+
return await this.activate().acceptStream(options);
|
|
48
|
+
}
|
|
49
|
+
async close() {
|
|
50
|
+
if (this.closed)
|
|
51
|
+
return;
|
|
52
|
+
this.closed = true;
|
|
53
|
+
if (this.active === undefined)
|
|
54
|
+
this.transport.close();
|
|
55
|
+
else
|
|
56
|
+
await this.active.close();
|
|
57
|
+
}
|
|
58
|
+
abort() {
|
|
59
|
+
if (this.closed)
|
|
60
|
+
return;
|
|
61
|
+
this.closed = true;
|
|
62
|
+
if (this.active === undefined)
|
|
63
|
+
this.transport.close();
|
|
64
|
+
else
|
|
65
|
+
this.active.abort();
|
|
66
|
+
}
|
|
67
|
+
async waitTermination() {
|
|
68
|
+
if (this.closed && this.active === undefined)
|
|
69
|
+
return;
|
|
70
|
+
await this.activate().waitTermination();
|
|
71
|
+
}
|
|
72
|
+
configureRole(client) {
|
|
73
|
+
if (this.closed || this.active !== undefined)
|
|
74
|
+
throw new CarrierError("closed", "WebSocket carrier is already active");
|
|
75
|
+
this.client = client;
|
|
76
|
+
}
|
|
77
|
+
activate() {
|
|
78
|
+
if (this.closed)
|
|
79
|
+
throw new CarrierError("closed", "WebSocket carrier is closed");
|
|
80
|
+
if (this.admissionAvailable) {
|
|
81
|
+
throw new CarrierError("closed", "WebSocket admission has not completed");
|
|
82
|
+
}
|
|
83
|
+
this.active ??= createWebSocketCarrierSessionV2(this.transport, {
|
|
84
|
+
path: this.options.path,
|
|
85
|
+
client: this.client,
|
|
86
|
+
inboundBidirectionalStreamCapacity: this.options.inboundBidirectionalStreamCapacity,
|
|
87
|
+
...(this.options.resourcePolicy === undefined ? {} : { resourcePolicy: this.options.resourcePolicy }),
|
|
88
|
+
});
|
|
89
|
+
return this.active;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
class WebSocketAdmissionStream {
|
|
93
|
+
transport;
|
|
94
|
+
readComplete = false;
|
|
95
|
+
writeComplete = false;
|
|
96
|
+
outbound = [];
|
|
97
|
+
constructor(transport) {
|
|
98
|
+
this.transport = transport;
|
|
99
|
+
}
|
|
100
|
+
async read(options = {}) {
|
|
101
|
+
if (this.readComplete)
|
|
102
|
+
return null;
|
|
103
|
+
this.readComplete = true;
|
|
104
|
+
return await this.transport.readBinary(options);
|
|
105
|
+
}
|
|
106
|
+
async write(data, options = {}) {
|
|
107
|
+
throwIfAborted(options.signal);
|
|
108
|
+
if (this.writeComplete)
|
|
109
|
+
throw new CarrierError("write_closed", "admission response is closed");
|
|
110
|
+
this.outbound.push(data.slice());
|
|
111
|
+
return data.length;
|
|
112
|
+
}
|
|
113
|
+
async closeWrite() {
|
|
114
|
+
if (this.writeComplete)
|
|
115
|
+
return;
|
|
116
|
+
this.writeComplete = true;
|
|
117
|
+
const length = this.outbound.reduce((total, chunk) => total + chunk.length, 0);
|
|
118
|
+
const response = new Uint8Array(length);
|
|
119
|
+
let offset = 0;
|
|
120
|
+
for (const chunk of this.outbound) {
|
|
121
|
+
response.set(chunk, offset);
|
|
122
|
+
offset += chunk.length;
|
|
123
|
+
}
|
|
124
|
+
this.outbound.length = 0;
|
|
125
|
+
await this.transport.writeBinary(response);
|
|
126
|
+
}
|
|
127
|
+
async stopSending() {
|
|
128
|
+
this.transport.close();
|
|
129
|
+
}
|
|
130
|
+
async reset() {
|
|
131
|
+
this.transport.close();
|
|
132
|
+
}
|
|
133
|
+
abort() {
|
|
134
|
+
this.transport.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
6
137
|
class WebSocketYamuxCarrierSession {
|
|
138
|
+
transport;
|
|
7
139
|
kind = "websocket";
|
|
8
140
|
path;
|
|
9
141
|
inboundBidirectionalStreamCapacity;
|
|
@@ -14,6 +146,7 @@ class WebSocketYamuxCarrierSession {
|
|
|
14
146
|
terminalError;
|
|
15
147
|
closed = false;
|
|
16
148
|
constructor(transport, options) {
|
|
149
|
+
this.transport = transport;
|
|
17
150
|
requireCapacity(options.inboundBidirectionalStreamCapacity);
|
|
18
151
|
this.path = options.path;
|
|
19
152
|
this.inboundBidirectionalStreamCapacity = options.inboundBidirectionalStreamCapacity;
|
|
@@ -60,6 +193,9 @@ class WebSocketYamuxCarrierSession {
|
|
|
60
193
|
return await this.incoming.shift(options.signal);
|
|
61
194
|
}
|
|
62
195
|
async close() {
|
|
196
|
+
if (this.closed)
|
|
197
|
+
return;
|
|
198
|
+
await this.transport.flush().catch(() => undefined);
|
|
63
199
|
this.closeLocally();
|
|
64
200
|
}
|
|
65
201
|
abort() {
|
package/dist/v2/artifact.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sha256 } from "@noble/hashes/
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
2
2
|
import { base64urlDecode, base64urlEncode } from "../utils/base64url.js";
|
|
3
3
|
import { concatBytes, readU32be, u32be } from "../utils/bin.js";
|
|
4
4
|
import { toASCII } from "../vendor/tr46.js";
|
package/dist/v2/capability.js
CHANGED
package/dist/v2/carrier.d.ts
CHANGED
|
@@ -63,7 +63,10 @@ export type NativeCarrierSessionV2 = Readonly<{
|
|
|
63
63
|
openStream(options?: OperationOptionsV2): Promise<NativeCarrierStreamV2>;
|
|
64
64
|
acceptStream(options?: OperationOptionsV2): Promise<NativeCarrierStreamV2>;
|
|
65
65
|
waitTermination(): Promise<void>;
|
|
66
|
-
close(
|
|
66
|
+
close(error?: Readonly<{
|
|
67
|
+
code: number;
|
|
68
|
+
reason: string;
|
|
69
|
+
}>): Promise<void>;
|
|
67
70
|
/** See {@link CarrierSessionV2.abort}. */
|
|
68
71
|
abort(error?: Readonly<{
|
|
69
72
|
code: number;
|
package/dist/v2/carrier.js
CHANGED
|
@@ -335,8 +335,8 @@ class NativeCarrierSessionAdapter {
|
|
|
335
335
|
async waitTermination() {
|
|
336
336
|
await this.native.waitTermination();
|
|
337
337
|
}
|
|
338
|
-
async close() {
|
|
339
|
-
await this.native.close();
|
|
338
|
+
async close(error) {
|
|
339
|
+
await this.native.close(error);
|
|
340
340
|
}
|
|
341
341
|
abort(error) {
|
|
342
342
|
this.native.abort(error);
|
package/dist/v2/handshake.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { x25519 } from "@noble/curves/ed25519";
|
|
2
|
-
import { p256 } from "@noble/curves/
|
|
3
|
-
import { expand, extract } from "@noble/hashes/hkdf";
|
|
4
|
-
import { hmac } from "@noble/hashes/hmac";
|
|
5
|
-
import { sha256 } from "@noble/hashes/
|
|
1
|
+
import { x25519 } from "@noble/curves/ed25519.js";
|
|
2
|
+
import { p256 } from "@noble/curves/nist.js";
|
|
3
|
+
import { expand, extract } from "@noble/hashes/hkdf.js";
|
|
4
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
5
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
6
|
import { base64urlDecode, base64urlEncode } from "../utils/base64url.js";
|
|
7
7
|
import { concatBytes, readU32be, u32be } from "../utils/bin.js";
|
|
8
8
|
import { CipherSuiteV2, ProtocolV2Error } from "./protocol.js";
|
|
@@ -187,7 +187,7 @@ export function generateEphemeralKeyV2(suite, entropy) {
|
|
|
187
187
|
else if (suite === CipherSuiteV2.AES256GCM) {
|
|
188
188
|
do
|
|
189
189
|
privateKey = requireEntropy(entropy, 32);
|
|
190
|
-
while (!p256.utils.
|
|
190
|
+
while (!p256.utils.isValidSecretKey(privateKey));
|
|
191
191
|
}
|
|
192
192
|
else {
|
|
193
193
|
return invalidSuite();
|
|
@@ -353,7 +353,7 @@ function validateEphemeralPublic(suite, value) {
|
|
|
353
353
|
if (suite === CipherSuiteV2.AES256GCM) {
|
|
354
354
|
if (value.length !== 65 || value[0] !== 4)
|
|
355
355
|
throw new Error("invalid P-256 public key");
|
|
356
|
-
p256.
|
|
356
|
+
p256.Point.fromBytes(value);
|
|
357
357
|
return;
|
|
358
358
|
}
|
|
359
359
|
}
|
package/dist/v2/protocol.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { gcm } from "@noble/ciphers/aes";
|
|
2
|
-
import { chacha20poly1305 } from "@noble/ciphers/chacha";
|
|
3
|
-
import { expand } from "@noble/hashes/hkdf";
|
|
4
|
-
import { hmac } from "@noble/hashes/hmac";
|
|
5
|
-
import { sha256 } from "@noble/hashes/
|
|
1
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
2
|
+
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
3
|
+
import { expand } from "@noble/hashes/hkdf.js";
|
|
4
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
5
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
6
|
const encoder = new TextEncoder();
|
|
7
7
|
const MAX_UINT32 = 0xffffffff;
|
|
8
8
|
const MAX_UINT64 = (1n << 64n) - 1n;
|
package/dist/v2/publicSession.js
CHANGED
|
@@ -48,9 +48,14 @@ export function projectSessionV2(session) {
|
|
|
48
48
|
throw redactSessionError(error);
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
async waitTermination() {
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
async waitTermination(options) {
|
|
52
|
+
try {
|
|
53
|
+
const { error } = await raceWithSignal(session.waitTermination(), options?.signal);
|
|
54
|
+
return Object.freeze({ error: redactSessionError(error) });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw redactSessionError(error);
|
|
58
|
+
}
|
|
54
59
|
},
|
|
55
60
|
async close() {
|
|
56
61
|
try {
|
|
@@ -112,9 +117,9 @@ function projectByteStreamV2(stream) {
|
|
|
112
117
|
}
|
|
113
118
|
function projectRpcPeerV2(peer) {
|
|
114
119
|
return Object.freeze({
|
|
115
|
-
async call(typeId, payload, decodeResponse,
|
|
120
|
+
async call(typeId, payload, decodeResponse, options) {
|
|
116
121
|
try {
|
|
117
|
-
const result = await peer.call(typeId, payload, signal);
|
|
122
|
+
const result = await peer.call(typeId, payload, options?.signal);
|
|
118
123
|
if (result.error !== undefined) {
|
|
119
124
|
return Object.freeze({ ok: false, error: Object.freeze({ ...result.error }) });
|
|
120
125
|
}
|
|
@@ -124,9 +129,11 @@ function projectRpcPeerV2(peer) {
|
|
|
124
129
|
throw redactSessionError(error);
|
|
125
130
|
}
|
|
126
131
|
},
|
|
127
|
-
async notify(typeId, payload) {
|
|
132
|
+
async notify(typeId, payload, options) {
|
|
128
133
|
try {
|
|
129
|
-
|
|
134
|
+
if (options?.signal?.aborted)
|
|
135
|
+
throw options.signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
136
|
+
await raceWithSignal(peer.notify(typeId, payload), options?.signal);
|
|
130
137
|
}
|
|
131
138
|
catch (error) {
|
|
132
139
|
throw redactSessionError(error);
|
|
@@ -137,6 +144,17 @@ function projectRpcPeerV2(peer) {
|
|
|
137
144
|
},
|
|
138
145
|
});
|
|
139
146
|
}
|
|
147
|
+
async function raceWithSignal(operation, signal) {
|
|
148
|
+
if (signal === undefined)
|
|
149
|
+
return await operation;
|
|
150
|
+
if (signal.aborted)
|
|
151
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
152
|
+
return await new Promise((resolve, reject) => {
|
|
153
|
+
const abort = () => reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
|
|
154
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
155
|
+
void operation.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
140
158
|
function redactSessionError(error) {
|
|
141
159
|
if (error instanceof SessionError)
|
|
142
160
|
return error;
|
|
@@ -9,9 +9,9 @@ export function retryDispositionForConnectError(error) {
|
|
|
9
9
|
case "rpc_failed":
|
|
10
10
|
case "resource_exhausted":
|
|
11
11
|
case "not_connected":
|
|
12
|
-
return { kind: "retryable" };
|
|
12
|
+
return Object.freeze({ kind: "retryable" });
|
|
13
13
|
default:
|
|
14
|
-
return { kind: "terminal" };
|
|
14
|
+
return Object.freeze({ kind: "terminal" });
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
export function retryDispositionForSessionError(error) {
|
|
@@ -23,8 +23,8 @@ export function retryDispositionForSessionError(error) {
|
|
|
23
23
|
case "stream_reset":
|
|
24
24
|
case "rekey_failed":
|
|
25
25
|
case "liveness_failed":
|
|
26
|
-
return { kind: "retryable" };
|
|
26
|
+
return Object.freeze({ kind: "retryable" });
|
|
27
27
|
default:
|
|
28
|
-
return { kind: "terminal" };
|
|
28
|
+
return Object.freeze({ kind: "terminal" });
|
|
29
29
|
}
|
|
30
30
|
}
|
package/dist/v2/session.d.ts
CHANGED
|
@@ -46,8 +46,8 @@ export type SessionProtocolRuntimeV2 = Readonly<{
|
|
|
46
46
|
monotonicMilliseconds(): number;
|
|
47
47
|
}>;
|
|
48
48
|
export declare class SessionV2Error extends Error {
|
|
49
|
-
readonly code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "timeout";
|
|
50
|
-
constructor(code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "timeout", message: string);
|
|
49
|
+
readonly code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "stream_reset" | "timeout";
|
|
50
|
+
constructor(code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "stream_reset" | "timeout", message: string);
|
|
51
51
|
}
|
|
52
52
|
declare class SessionRpcPeerV2 implements InternalRpcPeerV2 {
|
|
53
53
|
private readonly outbound;
|
package/dist/v2/session.js
CHANGED
|
@@ -305,7 +305,6 @@ export class SessionV2 {
|
|
|
305
305
|
async localReset(stream, error) {
|
|
306
306
|
if (!stream.markTerminal(error))
|
|
307
307
|
return;
|
|
308
|
-
await stream.carrier.reset().catch(() => undefined);
|
|
309
308
|
try {
|
|
310
309
|
await this.sendControl(InnerTypeV2.StreamReset, idReason(stream.id, 6));
|
|
311
310
|
this.commitLocalReset(stream.id);
|
|
@@ -313,6 +312,7 @@ export class SessionV2 {
|
|
|
313
312
|
catch (cause) {
|
|
314
313
|
this.fail(asError(cause));
|
|
315
314
|
}
|
|
315
|
+
await stream.carrier.reset().catch(() => undefined);
|
|
316
316
|
this.releaseStream(stream);
|
|
317
317
|
}
|
|
318
318
|
releaseStream(stream) {
|
|
@@ -599,7 +599,7 @@ export class SessionV2 {
|
|
|
599
599
|
const { id, reason } = parseIDReason(record.payload);
|
|
600
600
|
if (id === 0n || reason === 0)
|
|
601
601
|
throw protocolError("invalid STREAM_RESET");
|
|
602
|
-
this.streams.get(id)?.peerReset(new SessionV2Error("
|
|
602
|
+
this.streams.get(id)?.peerReset(new SessionV2Error("stream_reset", "logical stream reset by peer"));
|
|
603
603
|
if (this.isLocalLogicalID(id)) {
|
|
604
604
|
this.outboundLedger.peerReset(id);
|
|
605
605
|
this.notifyOutboundFrontierChanged();
|
|
@@ -1086,7 +1086,7 @@ class EncryptedStreamV2 {
|
|
|
1086
1086
|
});
|
|
1087
1087
|
}
|
|
1088
1088
|
async reset() {
|
|
1089
|
-
await this.session.localReset(this, new SessionV2Error("
|
|
1089
|
+
await this.session.localReset(this, new SessionV2Error("stream_reset", "logical stream reset"));
|
|
1090
1090
|
}
|
|
1091
1091
|
async close() {
|
|
1092
1092
|
await this.reset();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { gcm } from "@noble/ciphers/aes";
|
|
2
|
-
import { chacha20poly1305 } from "@noble/ciphers/chacha";
|
|
3
|
-
import { expand } from "@noble/hashes/hkdf";
|
|
4
|
-
import { sha256 } from "@noble/hashes/
|
|
1
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
2
|
+
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
3
|
+
import { expand } from "@noble/hashes/hkdf.js";
|
|
4
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
5
|
import { CipherSuiteV2 } from "./protocol.js";
|
|
6
6
|
import { UnreliableMessageError } from "../public/contract.js";
|
|
7
7
|
export const UNRELIABLE_MESSAGES_FEATURE_V2 = 0x00000001;
|
package/dist/vendor/tr46.js
CHANGED
|
@@ -5,7 +5,14 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esmMin = (fn, res) => () =>
|
|
8
|
+
var __esmMin = (fn, res, err) => () => {
|
|
9
|
+
if (err) throw err[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = fn(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err = [e], e;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
9
16
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
10
17
|
var __exportAll = (all, no_symbols) => {
|
|
11
18
|
let target = {};
|
|
@@ -26,7 +33,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
33
|
}
|
|
27
34
|
return to;
|
|
28
35
|
};
|
|
29
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
36
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
30
37
|
value: mod,
|
|
31
38
|
enumerable: true
|
|
32
39
|
}) : target, mod));
|
|
@@ -135,7 +142,7 @@ var init_punycode_es6 = __esmMin((() => {
|
|
|
135
142
|
"not-basic": "Illegal input >= 0x80 (not a basic code point)",
|
|
136
143
|
"invalid-input": "Invalid input"
|
|
137
144
|
};
|
|
138
|
-
baseMinusTMin =
|
|
145
|
+
baseMinusTMin = 35;
|
|
139
146
|
floor = Math.floor;
|
|
140
147
|
stringFromCharCode = String.fromCharCode;
|
|
141
148
|
ucs2encode = (codePoints) => String.fromCodePoint(...codePoints);
|
|
@@ -348,9 +355,7 @@ var import_tr46 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((expor
|
|
|
348
355
|
if (transitionalProcessing) processed += mapping;
|
|
349
356
|
else processed += ch;
|
|
350
357
|
break;
|
|
351
|
-
case STATUS_MAPPING.valid:
|
|
352
|
-
processed += ch;
|
|
353
|
-
break;
|
|
358
|
+
case STATUS_MAPPING.valid: processed += ch;
|
|
354
359
|
}
|
|
355
360
|
}
|
|
356
361
|
return processed;
|
|
@@ -46,6 +46,9 @@ export declare class WebSocketBinaryTransport {
|
|
|
46
46
|
writeBinary(frame: Uint8Array, opts?: Readonly<{
|
|
47
47
|
signal?: AbortSignal;
|
|
48
48
|
}>): Promise<void>;
|
|
49
|
+
flush(opts?: Readonly<{
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
}>): Promise<void>;
|
|
49
52
|
close(): void;
|
|
50
53
|
private handleMessage;
|
|
51
54
|
private readonly onMessage;
|
|
@@ -143,6 +143,20 @@ export class WebSocketBinaryTransport {
|
|
|
143
143
|
this.writeChain = write.catch(() => { });
|
|
144
144
|
await write;
|
|
145
145
|
}
|
|
146
|
+
async flush(opts = {}) {
|
|
147
|
+
throwIfAborted(opts.signal, "flush aborted");
|
|
148
|
+
await this.writeChain;
|
|
149
|
+
const startedAt = Date.now();
|
|
150
|
+
while (this.ws.bufferedAmount > 0) {
|
|
151
|
+
throwIfAborted(opts.signal, "flush aborted");
|
|
152
|
+
if (this.error != null)
|
|
153
|
+
throw this.error;
|
|
154
|
+
if (Date.now() - startedAt >= this.limits.outboundDrainTimeoutMs) {
|
|
155
|
+
throw new TimeoutError("ws send buffer drain timeout");
|
|
156
|
+
}
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
146
160
|
// close tears down listeners and rejects pending readers.
|
|
147
161
|
close() {
|
|
148
162
|
if (!this.localCloseRequested) {
|