@floegence/flowersec-core 3.2.0 → 4.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 +57 -200
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/dist/browser/index.d.ts +0 -5
- package/dist/browser/index.js +0 -3
- package/dist/cli.js +93 -64
- package/dist/facade.d.ts +0 -17
- package/dist/facade.js +0 -11
- package/dist/generated/unicode151.d.ts +2 -0
- package/dist/generated/unicode151.js +83 -0
- package/dist/interop/proxyServerPeer.js +34 -20
- package/dist/interop/serverParityPeer.js +11 -252
- package/dist/node/acceptor.d.ts +1 -57
- package/dist/node/acceptor.js +3 -420
- package/dist/node/acceptorV3.js +2 -2
- package/dist/node/connectSessionV3.js +2 -2
- package/dist/node/index.d.ts +2 -17
- package/dist/node/index.js +1 -9
- package/dist/node/nativeTransportAddon.d.ts +7 -36
- package/dist/node/nativeTransportAddon.js +7 -27
- package/dist/node/proxyServer.d.ts +2 -2
- package/dist/node/proxyServer.js +2 -2
- package/dist/node/rawQuicAdapterV3.d.ts +2 -2
- package/dist/node/rawQuicAdapterV3.js +1 -1
- package/dist/node/rawQuicServerV3.d.ts +2 -2
- package/dist/node/rawQuicServerV3.js +1 -1
- package/dist/node/rawQuicTls.d.ts +2 -0
- package/dist/node/{rawQuicAdapter.js → rawQuicTls.js} +30 -45
- package/dist/node/tunnelRuntimeV3.js +2 -2
- package/dist/proxy/integration.d.ts +5 -5
- package/dist/proxy/integration.js +2 -2
- package/dist/public/streamHandlers.d.ts +0 -3
- package/dist/public/streamHandlers.js +2 -4
- package/dist/public/streamMetadata.js +2 -2
- package/dist/runtime/errors.d.ts +2 -2
- package/dist/testSupport/tlsFixture.d.ts +2 -0
- package/dist/testSupport/tlsFixture.js +8 -0
- package/dist/utils/errors.d.ts +0 -2
- package/dist/utils/errors.js +0 -80
- package/dist/v3/artifact.js +17 -3
- package/dist/v3/carrier.d.ts +2 -2
- package/dist/v3/protocol.js +2 -103
- package/dist/v3/retryDisposition.d.ts +2 -2
- package/dist/v3/retryDisposition.js +0 -7
- package/dist/vendor/tr46.d.ts +4 -0
- package/dist/vendor/tr46.js +37 -32
- package/package.json +21 -19
- package/sbom/cyclonedx.json +62 -62
- package/sbom/spdx.json +65 -65
- package/dist/browser/connectSession.d.ts +0 -13
- package/dist/browser/connectSession.js +0 -40
- package/dist/browser/runtimeCapability.d.ts +0 -13
- package/dist/browser/runtimeCapability.js +0 -27
- package/dist/browser/sessionRuntime.d.ts +0 -2
- package/dist/browser/sessionRuntime.js +0 -21
- package/dist/browser/v2.d.ts +0 -3
- package/dist/browser/v2.js +0 -2
- package/dist/browser/webTransportClient.d.ts +0 -8
- package/dist/browser/webTransportClient.js +0 -55
- package/dist/connectionController.d.ts +0 -55
- package/dist/connectionController.js +0 -441
- package/dist/connector/adapters/rawQuicCandidate.d.ts +0 -5
- package/dist/connector/adapters/rawQuicCandidate.js +0 -71
- package/dist/connector/adapters/webSocketCandidate.d.ts +0 -7
- package/dist/connector/adapters/webSocketCandidate.js +0 -116
- package/dist/connector/adapters/webTransportCandidate.d.ts +0 -5
- package/dist/connector/adapters/webTransportCandidate.js +0 -73
- package/dist/connector/admissionCommit.d.ts +0 -31
- package/dist/connector/admissionCommit.js +0 -239
- package/dist/connector/sessionAcceptor.d.ts +0 -38
- package/dist/connector/sessionAcceptor.js +0 -158
- package/dist/connector/sessionConfig.d.ts +0 -3
- package/dist/connector/sessionConfig.js +0 -30
- package/dist/connector/sessionConnector.d.ts +0 -47
- package/dist/connector/sessionConnector.js +0 -606
- package/dist/node/connectSession.d.ts +0 -23
- package/dist/node/connectSession.js +0 -93
- package/dist/node/controlplane.d.ts +0 -137
- package/dist/node/controlplane.js +0 -443
- package/dist/node/rawQuicAdapter.d.ts +0 -9
- package/dist/node/rawQuicServer.d.ts +0 -24
- package/dist/node/rawQuicServer.js +0 -37
- package/dist/node/runtimeCapability.d.ts +0 -14
- package/dist/node/runtimeCapability.js +0 -24
- package/dist/node/sessionRuntime.d.ts +0 -2
- package/dist/node/sessionRuntime.js +0 -13
- package/dist/node/tunnelRuntime.d.ts +0 -42
- package/dist/node/tunnelRuntime.js +0 -559
- package/dist/node/v2.d.ts +0 -11
- package/dist/node/v2.js +0 -6
- package/dist/node/webSocketServer.d.ts +0 -24
- package/dist/node/webSocketServer.js +0 -135
- package/dist/public/artifact.d.ts +0 -11
- package/dist/public/artifact.js +0 -44
- package/dist/public/artifactLease.d.ts +0 -10
- package/dist/public/artifactLease.js +0 -41
- package/dist/public/connectError.d.ts +0 -6
- package/dist/public/connectError.js +0 -9
- package/dist/transport/webSocketAdapter.d.ts +0 -31
- package/dist/transport/webSocketAdapter.js +0 -370
- package/dist/transport/webTransportAdapter.d.ts +0 -27
- package/dist/transport/webTransportAdapter.js +0 -534
- package/dist/v2/admissionError.d.ts +0 -4
- package/dist/v2/admissionError.js +0 -8
- package/dist/v2/artifact.d.ts +0 -128
- package/dist/v2/artifact.js +0 -1131
- package/dist/v2/artifactLease.d.ts +0 -1
- package/dist/v2/artifactLease.js +0 -7
- package/dist/v2/capability.d.ts +0 -29
- package/dist/v2/capability.js +0 -190
- package/dist/v2/carrier.d.ts +0 -84
- package/dist/v2/carrier.js +0 -411
- package/dist/v2/contract.d.ts +0 -46
- package/dist/v2/contract.js +0 -1
- package/dist/v2/handshake.d.ts +0 -80
- package/dist/v2/handshake.js +0 -470
- package/dist/v2/index.d.ts +0 -14
- package/dist/v2/index.js +0 -7
- package/dist/v2/opaqueArtifact.d.ts +0 -1
- package/dist/v2/opaqueArtifact.js +0 -2
- package/dist/v2/protocol.d.ts +0 -99
- package/dist/v2/protocol.js +0 -737
- package/dist/v2/publicSession.d.ts +0 -1
- package/dist/v2/publicSession.js +0 -281
- package/dist/v2/retryDisposition.d.ts +0 -16
- package/dist/v2/retryDisposition.js +0 -30
- package/dist/v2/session.d.ts +0 -310
- package/dist/v2/session.js +0 -2127
- package/dist/v2/streamLifetimeLedger.d.ts +0 -31
- package/dist/v2/streamLifetimeLedger.js +0 -131
- package/dist/v2/streamMetadata.d.ts +0 -1
- package/dist/v2/streamMetadata.js +0 -7
- package/dist/v2/unreliableMessage.d.ts +0 -43
- package/dist/v2/unreliableMessage.js +0 -254
|
@@ -1,135 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export declare class Artifact {
|
|
2
|
-
private readonly artifactBrand;
|
|
3
|
-
private constructor();
|
|
4
|
-
}
|
|
5
|
-
export type ArtifactErrorCode = "artifact_too_large" | "invalid_artifact" | "invalid_candidate";
|
|
6
|
-
/** A stable, redacted failure returned while parsing an opaque artifact. */
|
|
7
|
-
export declare class ArtifactError extends Error {
|
|
8
|
-
readonly code: ArtifactErrorCode;
|
|
9
|
-
constructor(code: ArtifactErrorCode);
|
|
10
|
-
}
|
|
11
|
-
export declare function parseArtifact(input: string | Uint8Array): Artifact;
|
package/dist/public/artifact.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ArtifactV2Error, decodeArtifactV2JSON } from "../v2/artifact.js";
|
|
2
|
-
const artifactValues = new WeakMap();
|
|
3
|
-
export class Artifact {
|
|
4
|
-
constructor() { }
|
|
5
|
-
}
|
|
6
|
-
/** A stable, redacted failure returned while parsing an opaque artifact. */
|
|
7
|
-
export class ArtifactError extends Error {
|
|
8
|
-
code;
|
|
9
|
-
constructor(code) {
|
|
10
|
-
super(`Flowersec artifact parsing failed (code=${code})`);
|
|
11
|
-
this.code = code;
|
|
12
|
-
this.name = "ArtifactError";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export function parseArtifact(input) {
|
|
16
|
-
try {
|
|
17
|
-
return wrapArtifact(decodeArtifactV2JSON(input));
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
if (error instanceof ArtifactV2Error) {
|
|
21
|
-
switch (error.code) {
|
|
22
|
-
case "artifact_too_large":
|
|
23
|
-
case "invalid_candidate":
|
|
24
|
-
throw new ArtifactError(error.code);
|
|
25
|
-
default:
|
|
26
|
-
throw new ArtifactError("invalid_artifact");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
throw new ArtifactError("invalid_artifact");
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/** @internal */
|
|
33
|
-
export function wrapArtifact(value) {
|
|
34
|
-
const artifact = new Artifact();
|
|
35
|
-
artifactValues.set(artifact, value);
|
|
36
|
-
return Object.freeze(artifact);
|
|
37
|
-
}
|
|
38
|
-
/** @internal */
|
|
39
|
-
export function unwrapArtifact(artifact) {
|
|
40
|
-
const value = artifactValues.get(artifact);
|
|
41
|
-
if (value === undefined)
|
|
42
|
-
throw new TypeError("invalid Flowersec artifact handle");
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type Artifact } from "./artifact.js";
|
|
2
|
-
export declare class ArtifactLeaseError extends Error {
|
|
3
|
-
readonly code = "already_consumed";
|
|
4
|
-
constructor();
|
|
5
|
-
}
|
|
6
|
-
export declare class ArtifactLease {
|
|
7
|
-
#private;
|
|
8
|
-
private constructor();
|
|
9
|
-
}
|
|
10
|
-
export declare function createArtifactLease(artifact: Artifact, commitSpend: (signal?: AbortSignal) => Promise<void>): ArtifactLease;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { validateArtifactV2 } from "../v2/artifact.js";
|
|
2
|
-
import { unwrapArtifact } from "./artifact.js";
|
|
3
|
-
export class ArtifactLeaseError extends Error {
|
|
4
|
-
code = "already_consumed";
|
|
5
|
-
constructor() {
|
|
6
|
-
super("Flowersec artifact lease has already been consumed");
|
|
7
|
-
this.name = "ArtifactLeaseError";
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export class ArtifactLease {
|
|
11
|
-
#artifactLeaseBrand = undefined;
|
|
12
|
-
constructor() {
|
|
13
|
-
Object.freeze(this);
|
|
14
|
-
}
|
|
15
|
-
/** @internal */
|
|
16
|
-
static create() {
|
|
17
|
-
return new ArtifactLease();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const artifactLeaseStates = new WeakMap();
|
|
21
|
-
export function createArtifactLease(artifact, commitSpend) {
|
|
22
|
-
validateArtifactV2(unwrapArtifact(artifact));
|
|
23
|
-
const lease = ArtifactLease.create();
|
|
24
|
-
artifactLeaseStates.set(lease, { artifact, state: "idle", commitSpend });
|
|
25
|
-
return lease;
|
|
26
|
-
}
|
|
27
|
-
/** @internal Connector-owned access to validated lease material. */
|
|
28
|
-
export function artifactLeaseArtifact(lease) {
|
|
29
|
-
const leaseState = artifactLeaseStates.get(lease);
|
|
30
|
-
if (leaseState === undefined)
|
|
31
|
-
throw new ArtifactLeaseError();
|
|
32
|
-
return leaseState.artifact;
|
|
33
|
-
}
|
|
34
|
-
/** @internal Connector-owned durable spend transition. */
|
|
35
|
-
export async function commitArtifactLeaseSpend(lease, signal) {
|
|
36
|
-
const leaseState = artifactLeaseStates.get(lease);
|
|
37
|
-
if (leaseState === undefined || leaseState.state !== "idle")
|
|
38
|
-
throw new ArtifactLeaseError();
|
|
39
|
-
leaseState.state = "consumed";
|
|
40
|
-
await leaseState.commitSpend(signal);
|
|
41
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export type ConnectErrorCode = "invalid_input" | "invalid_options" | "expired_artifact" | "resolve_failed" | "credential_spend_failed" | "connection_failed" | "timeout" | "canceled" | "handshake_failed" | "rpc_failed" | "resource_exhausted" | "not_connected";
|
|
2
|
-
/** A stable connection failure that retains no carrier or credential details. */
|
|
3
|
-
export declare class ConnectError extends Error {
|
|
4
|
-
readonly code: ConnectErrorCode;
|
|
5
|
-
constructor(code: ConnectErrorCode);
|
|
6
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/** A stable connection failure that retains no carrier or credential details. */
|
|
2
|
-
export class ConnectError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
constructor(code) {
|
|
5
|
-
super(`Flowersec connection failed (code=${code})`);
|
|
6
|
-
this.code = code;
|
|
7
|
-
this.name = "ConnectError";
|
|
8
|
-
}
|
|
9
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { type CarrierSessionV2 } from "../v2/carrier.js";
|
|
2
|
-
import type { OperationOptionsV2, PathKind } from "../v2/contract.js";
|
|
3
|
-
export type WebSocketBinaryTransportV2 = Readonly<{
|
|
4
|
-
readBinary(options?: Readonly<{
|
|
5
|
-
signal?: AbortSignal;
|
|
6
|
-
timeoutMs?: number;
|
|
7
|
-
}>): Promise<Uint8Array>;
|
|
8
|
-
writeBinary(data: Uint8Array, options?: OperationOptionsV2): Promise<void>;
|
|
9
|
-
flush(options?: OperationOptionsV2): Promise<void>;
|
|
10
|
-
close(): void;
|
|
11
|
-
}>;
|
|
12
|
-
export type WebSocketResourcePolicyV2 = Readonly<{
|
|
13
|
-
maxConcurrentStreams?: number;
|
|
14
|
-
maxFrameBytes?: number;
|
|
15
|
-
preferredWriteBytes?: number;
|
|
16
|
-
maxStreamWriteQueueBytes?: number;
|
|
17
|
-
maxStreamReceiveBytes?: number;
|
|
18
|
-
maxSessionReceiveBytes?: number;
|
|
19
|
-
}>;
|
|
20
|
-
export declare function createWebSocketCarrierSessionV2(transport: WebSocketBinaryTransportV2, options: Readonly<{
|
|
21
|
-
path: PathKind;
|
|
22
|
-
client: boolean;
|
|
23
|
-
inboundBidirectionalStreamCapacity: number;
|
|
24
|
-
resourcePolicy?: WebSocketResourcePolicyV2;
|
|
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;
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import { YamuxSession } from "../yamux/session.js";
|
|
2
|
-
import { CarrierError } from "../v2/carrier.js";
|
|
3
|
-
export function createWebSocketCarrierSessionV2(transport, options) {
|
|
4
|
-
return new WebSocketYamuxCarrierSession(transport, options);
|
|
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
|
-
}
|
|
137
|
-
class WebSocketYamuxCarrierSession {
|
|
138
|
-
transport;
|
|
139
|
-
kind = "websocket";
|
|
140
|
-
path;
|
|
141
|
-
inboundBidirectionalStreamCapacity;
|
|
142
|
-
unreliableDatagrams = undefined;
|
|
143
|
-
incoming = new IncomingStreamQueue();
|
|
144
|
-
yamux;
|
|
145
|
-
termination = deferred();
|
|
146
|
-
terminalError;
|
|
147
|
-
closed = false;
|
|
148
|
-
constructor(transport, options) {
|
|
149
|
-
this.transport = transport;
|
|
150
|
-
requireCapacity(options.inboundBidirectionalStreamCapacity);
|
|
151
|
-
this.path = options.path;
|
|
152
|
-
this.inboundBidirectionalStreamCapacity = options.inboundBidirectionalStreamCapacity;
|
|
153
|
-
const duplex = {
|
|
154
|
-
read: async () => await transport.readBinary(),
|
|
155
|
-
write: async (chunk) => await transport.writeBinary(chunk),
|
|
156
|
-
close: () => transport.close(),
|
|
157
|
-
};
|
|
158
|
-
const policy = options.resourcePolicy ?? {};
|
|
159
|
-
this.yamux = new YamuxSession(duplex, {
|
|
160
|
-
client: options.client,
|
|
161
|
-
limits: {
|
|
162
|
-
maxActiveStreams: Math.max(policy.maxConcurrentStreams ?? this.inboundBidirectionalStreamCapacity, this.inboundBidirectionalStreamCapacity),
|
|
163
|
-
maxInboundStreams: this.inboundBidirectionalStreamCapacity,
|
|
164
|
-
...(policy.maxFrameBytes === undefined ? {} : { maxFrameBytes: policy.maxFrameBytes }),
|
|
165
|
-
...(policy.preferredWriteBytes === undefined ? {} : { preferredOutboundFrameBytes: policy.preferredWriteBytes }),
|
|
166
|
-
...(policy.maxStreamWriteQueueBytes === undefined ? {} : { maxStreamWriteQueueBytes: policy.maxStreamWriteQueueBytes }),
|
|
167
|
-
...(policy.maxStreamReceiveBytes === undefined ? {} : { maxStreamReceiveBytes: policy.maxStreamReceiveBytes }),
|
|
168
|
-
...(policy.maxSessionReceiveBytes === undefined ? {} : { maxSessionReceiveBytes: policy.maxSessionReceiveBytes }),
|
|
169
|
-
},
|
|
170
|
-
onIncomingStream: (stream) => this.incoming.push(new YamuxCarrierStreamAdapter(stream)),
|
|
171
|
-
onTerminal: (error) => {
|
|
172
|
-
const failure = error instanceof CarrierError
|
|
173
|
-
? error
|
|
174
|
-
: new CarrierError("closed", "WebSocket carrier terminated", error);
|
|
175
|
-
this.closed = true;
|
|
176
|
-
this.terminalError = failure;
|
|
177
|
-
this.incoming.fail(failure);
|
|
178
|
-
this.termination.resolve();
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
async openStream(options = {}) {
|
|
183
|
-
this.assertOpen();
|
|
184
|
-
try {
|
|
185
|
-
return new YamuxCarrierStreamAdapter(await this.yamux.openStream(options));
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
throw error instanceof CarrierError ? error : new CarrierError("closed", "WebSocket stream open failed", error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async acceptStream(options = {}) {
|
|
192
|
-
this.assertOpen();
|
|
193
|
-
return await this.incoming.shift(options.signal);
|
|
194
|
-
}
|
|
195
|
-
async close() {
|
|
196
|
-
if (this.closed)
|
|
197
|
-
return;
|
|
198
|
-
await this.transport.flush().catch(() => undefined);
|
|
199
|
-
this.closeLocally();
|
|
200
|
-
}
|
|
201
|
-
abort() {
|
|
202
|
-
this.closeLocally();
|
|
203
|
-
}
|
|
204
|
-
async waitTermination() {
|
|
205
|
-
await this.termination.promise;
|
|
206
|
-
if (this.terminalError !== undefined)
|
|
207
|
-
throw this.terminalError;
|
|
208
|
-
}
|
|
209
|
-
assertOpen() {
|
|
210
|
-
if (this.terminalError !== undefined)
|
|
211
|
-
throw this.terminalError;
|
|
212
|
-
if (this.closed)
|
|
213
|
-
throw new CarrierError("closed", "WebSocket carrier is closed");
|
|
214
|
-
}
|
|
215
|
-
closeLocally() {
|
|
216
|
-
if (this.closed)
|
|
217
|
-
return;
|
|
218
|
-
this.closed = true;
|
|
219
|
-
const failure = new CarrierError("closed", "WebSocket carrier is closed");
|
|
220
|
-
this.incoming.fail(failure);
|
|
221
|
-
this.yamux.close();
|
|
222
|
-
this.termination.resolve();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
class YamuxCarrierStreamAdapter {
|
|
226
|
-
stream;
|
|
227
|
-
constructor(stream) {
|
|
228
|
-
this.stream = stream;
|
|
229
|
-
}
|
|
230
|
-
async read(options = {}) {
|
|
231
|
-
try {
|
|
232
|
-
return await abortable(this.stream.read(), options.signal, () => this.stream.abort());
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
throw error instanceof CarrierError ? error : new CarrierError("reset", "WebSocket stream read failed", error);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async write(data, options = {}) {
|
|
239
|
-
throwIfAborted(options.signal);
|
|
240
|
-
try {
|
|
241
|
-
await abortable(this.stream.write(data), options.signal, () => this.stream.abort());
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
throw error instanceof CarrierError ? error : new CarrierError("reset", "WebSocket stream write failed", error);
|
|
245
|
-
}
|
|
246
|
-
return data.length;
|
|
247
|
-
}
|
|
248
|
-
async closeWrite() {
|
|
249
|
-
await this.stream.close();
|
|
250
|
-
}
|
|
251
|
-
async reset() {
|
|
252
|
-
await this.stream.reset();
|
|
253
|
-
}
|
|
254
|
-
async stopSending() {
|
|
255
|
-
throw new CarrierError("stop_sending_unavailable", "WebSocket/Yamux cannot stop only the receive side of a bidirectional stream");
|
|
256
|
-
}
|
|
257
|
-
abort(error) {
|
|
258
|
-
this.stream.abort(error);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
class IncomingStreamQueue {
|
|
262
|
-
values = [];
|
|
263
|
-
waiters = new Set();
|
|
264
|
-
terminalError;
|
|
265
|
-
push(value) {
|
|
266
|
-
if (this.terminalError !== undefined) {
|
|
267
|
-
value.abort(this.terminalError);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const waiter = this.waiters.values().next().value;
|
|
271
|
-
if (waiter === undefined)
|
|
272
|
-
this.values.push(value);
|
|
273
|
-
else {
|
|
274
|
-
this.waiters.delete(waiter);
|
|
275
|
-
waiter.deliver(value);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
shift(signal) {
|
|
279
|
-
throwIfAborted(signal);
|
|
280
|
-
if (this.terminalError !== undefined)
|
|
281
|
-
return Promise.reject(this.terminalError);
|
|
282
|
-
const value = this.values.shift();
|
|
283
|
-
if (value !== undefined)
|
|
284
|
-
return Promise.resolve(value);
|
|
285
|
-
return new Promise((resolve, reject) => {
|
|
286
|
-
let settled = false;
|
|
287
|
-
const cleanup = () => {
|
|
288
|
-
this.waiters.delete(waiter);
|
|
289
|
-
signal?.removeEventListener("abort", abort);
|
|
290
|
-
};
|
|
291
|
-
const waiter = {
|
|
292
|
-
deliver: (stream) => {
|
|
293
|
-
if (settled)
|
|
294
|
-
return;
|
|
295
|
-
settled = true;
|
|
296
|
-
cleanup();
|
|
297
|
-
resolve(stream);
|
|
298
|
-
},
|
|
299
|
-
fail: (error) => {
|
|
300
|
-
if (settled)
|
|
301
|
-
return;
|
|
302
|
-
settled = true;
|
|
303
|
-
cleanup();
|
|
304
|
-
reject(error);
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
const abort = () => waiter.fail(new CarrierError("aborted", "carrier operation aborted"));
|
|
308
|
-
this.waiters.add(waiter);
|
|
309
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
310
|
-
if (signal?.aborted === true)
|
|
311
|
-
abort();
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
fail(error) {
|
|
315
|
-
if (this.terminalError !== undefined)
|
|
316
|
-
return;
|
|
317
|
-
this.terminalError = error;
|
|
318
|
-
for (const stream of this.values)
|
|
319
|
-
stream.abort(error);
|
|
320
|
-
this.values.length = 0;
|
|
321
|
-
for (const waiter of this.waiters)
|
|
322
|
-
waiter.fail(error);
|
|
323
|
-
this.waiters.clear();
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
function deferred() {
|
|
327
|
-
let resolve;
|
|
328
|
-
const promise = new Promise((resolvePromise) => { resolve = resolvePromise; });
|
|
329
|
-
return { promise, resolve };
|
|
330
|
-
}
|
|
331
|
-
function requireCapacity(value) {
|
|
332
|
-
if (!Number.isInteger(value) || value < 3 || value > 130) {
|
|
333
|
-
throw new RangeError("inboundBidirectionalStreamCapacity must be an integer from 3 to 130");
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function throwIfAborted(signal) {
|
|
337
|
-
if (signal?.aborted === true)
|
|
338
|
-
throw new CarrierError("aborted", "carrier operation aborted");
|
|
339
|
-
}
|
|
340
|
-
async function abortable(promise, signal, onAbort) {
|
|
341
|
-
if (signal === undefined)
|
|
342
|
-
return await promise;
|
|
343
|
-
throwIfAborted(signal);
|
|
344
|
-
return await new Promise((resolve, reject) => {
|
|
345
|
-
let settled = false;
|
|
346
|
-
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
347
|
-
const abort = () => {
|
|
348
|
-
if (settled)
|
|
349
|
-
return;
|
|
350
|
-
settled = true;
|
|
351
|
-
cleanup();
|
|
352
|
-
onAbort();
|
|
353
|
-
reject(new CarrierError("aborted", "carrier operation aborted"));
|
|
354
|
-
};
|
|
355
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
356
|
-
void promise.then((value) => {
|
|
357
|
-
if (settled)
|
|
358
|
-
return;
|
|
359
|
-
settled = true;
|
|
360
|
-
cleanup();
|
|
361
|
-
resolve(value);
|
|
362
|
-
}, (error) => {
|
|
363
|
-
if (settled)
|
|
364
|
-
return;
|
|
365
|
-
settled = true;
|
|
366
|
-
cleanup();
|
|
367
|
-
reject(error);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
}
|