@floegence/flowersec-core 2.3.6 → 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.
Files changed (58) hide show
  1. package/README.md +31 -42
  2. package/THIRD_PARTY_NOTICES.md +4 -79
  3. package/dist/cli.js +31 -31
  4. package/dist/connector/adapters/rawQuicCandidate.d.ts +5 -0
  5. package/dist/connector/adapters/rawQuicCandidate.js +71 -0
  6. package/dist/connector/sessionAcceptor.d.ts +22 -2
  7. package/dist/connector/sessionAcceptor.js +51 -21
  8. package/dist/connector/sessionConnector.d.ts +2 -0
  9. package/dist/connector/sessionConnector.js +4 -1
  10. package/dist/interop/proxyServerPeer.d.ts +1 -0
  11. package/dist/interop/proxyServerPeer.js +83 -0
  12. package/dist/interop/serverParityPeer.d.ts +1 -0
  13. package/dist/interop/serverParityPeer.js +608 -0
  14. package/dist/node/acceptor.d.ts +31 -19
  15. package/dist/node/acceptor.js +353 -72
  16. package/dist/node/connectSession.d.ts +2 -1
  17. package/dist/node/connectSession.js +22 -12
  18. package/dist/node/controlplane.d.ts +137 -0
  19. package/dist/node/controlplane.js +439 -0
  20. package/dist/node/index.d.ts +8 -2
  21. package/dist/node/index.js +4 -1
  22. package/dist/node/nativeTransportAddon.d.ts +94 -0
  23. package/dist/node/nativeTransportAddon.js +166 -0
  24. package/dist/node/proxyServer.d.ts +31 -0
  25. package/dist/node/proxyServer.js +572 -0
  26. package/dist/node/rawQuicAdapter.d.ts +9 -0
  27. package/dist/node/rawQuicAdapter.js +107 -0
  28. package/dist/node/rawQuicServer.d.ts +24 -0
  29. package/dist/node/rawQuicServer.js +37 -0
  30. package/dist/node/runtimeCapability.d.ts +8 -1
  31. package/dist/node/runtimeCapability.js +20 -6
  32. package/dist/node/tunnelRuntime.d.ts +42 -0
  33. package/dist/node/tunnelRuntime.js +552 -0
  34. package/dist/node/webSocketServer.d.ts +24 -0
  35. package/dist/node/webSocketServer.js +135 -0
  36. package/dist/public/contract.d.ts +4 -3
  37. package/dist/transport/webSocketAdapter.d.ts +7 -0
  38. package/dist/transport/webSocketAdapter.js +136 -0
  39. package/dist/v2/artifact.js +1 -1
  40. package/dist/v2/capability.js +1 -1
  41. package/dist/v2/carrier.d.ts +4 -1
  42. package/dist/v2/carrier.js +2 -2
  43. package/dist/v2/handshake.js +7 -7
  44. package/dist/v2/protocol.js +5 -5
  45. package/dist/v2/publicSession.js +25 -7
  46. package/dist/v2/session.d.ts +2 -2
  47. package/dist/v2/session.js +3 -3
  48. package/dist/v2/unreliableMessage.js +4 -4
  49. package/dist/vendor/tr46.js +11 -6
  50. package/dist/ws-client/binaryTransport.d.ts +3 -0
  51. package/dist/ws-client/binaryTransport.js +14 -0
  52. package/package.json +19 -16
  53. package/sbom/cyclonedx.json +52 -2585
  54. package/sbom/spdx.json +56 -1826
  55. package/dist/node/webTransportClient.d.ts +0 -9
  56. package/dist/node/webTransportClient.js +0 -56
  57. package/dist/node/webTransportServer.d.ts +0 -22
  58. 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: 976;
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, signal?: AbortSignal): Promise<RpcResult<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() {
@@ -1,4 +1,4 @@
1
- import { sha256 } from "@noble/hashes/sha256";
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";
@@ -1,4 +1,4 @@
1
- import { sha256 } from "@noble/hashes/sha256";
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
2
  const encoder = new TextEncoder();
3
3
  const carriers = Object.freeze(["raw_quic", "websocket", "webtransport"]);
4
4
  const registryToken = /^[a-z][a-z0-9_]{0,127}$/;
@@ -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(): Promise<void>;
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;
@@ -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);
@@ -1,8 +1,8 @@
1
- import { x25519 } from "@noble/curves/ed25519";
2
- import { p256 } from "@noble/curves/p256";
3
- import { expand, extract } from "@noble/hashes/hkdf";
4
- import { hmac } from "@noble/hashes/hmac";
5
- import { sha256 } from "@noble/hashes/sha256";
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.isValidPrivateKey(privateKey));
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.ProjectivePoint.fromHex(value);
356
+ p256.Point.fromBytes(value);
357
357
  return;
358
358
  }
359
359
  }
@@ -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/sha256";
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;
@@ -48,9 +48,14 @@ export function projectSessionV2(session) {
48
48
  throw redactSessionError(error);
49
49
  }
50
50
  },
51
- async waitTermination() {
52
- const { error } = await session.waitTermination();
53
- return Object.freeze({ error: redactSessionError(error) });
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, signal) {
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
- await peer.notify(typeId, payload);
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;
@@ -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;
@@ -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("closed", "logical stream reset by peer"));
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("closed", "logical stream reset"));
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/sha256";
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;
@@ -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) => () => (fn && (res = fn(fn = 0)), 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 = base - tMin;
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) {