@floegence/flowersec-core 2.3.6 → 2.3.8

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 +37 -43
  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
package/README.md CHANGED
@@ -1,20 +1,21 @@
1
1
  # Flowersec for TypeScript
2
2
 
3
- `@floegence/flowersec-core` is the ESM-only Flowersec v2 SDK for browsers and Node.js. Its public package surface is limited to the root, `/browser`, `/node`, and `/proxy` entrypoints.
4
-
5
- Flowersec 2.3.6 is the published TypeScript SDK release.
3
+ `@floegence/flowersec-core` is the ESM-only Flowersec SDK for browsers and
4
+ Node.js. It gives both runtimes the same encrypted session, RPC, notification,
5
+ and byte-stream API through the root, `/browser`, `/node`, and `/proxy`
6
+ entrypoints.
6
7
 
7
8
  ## Install
8
9
 
9
10
  ```bash
10
- npm install @floegence/flowersec-core@2.3.6
11
+ npm install @floegence/flowersec-core
11
12
  ```
12
13
 
13
14
  ## Public API
14
15
 
15
16
  - `@floegence/flowersec-core` exports the portable artifact, lease, session, stream, RPC, stream-metadata, and connection-controller API, plus profile-owned unreliable messages when negotiated.
16
17
  - `@floegence/flowersec-core/browser` adds `connect(...)`, `createConnectionController(...)`, and their options.
17
- - `@floegence/flowersec-core/node` adds `connect(...)`, `createConnectionController(...)`, `createAcceptor(...)`, `SessionHandlers`, and `AcceptedSession`.
18
+ - `@floegence/flowersec-core/node` adds `connect(...)`, `createConnectionController(...)`, direct-only `createAcceptor(...)`, opaque `createTunnelRuntime(...)`, `SessionHandlers`, `AcceptedSession`, `Issuer`, authorization record/request/response types, `authorizeRuntime(...)`, `authorizeTunnelRuntime(...)`, and `ProxyServer`.
18
19
  - `@floegence/flowersec-core/proxy` adds the `Session`-based HTTP/WebSocket runtime, Service Worker and controller/app-window bridges, strict `proxy.runtime@2` validation, and `connectProxyBrowser(...)` composition.
19
20
 
20
21
  The root type exports are:
@@ -38,7 +39,7 @@ The Browser and Node `connect(...)` operations are one-shot and never reconnect.
38
39
 
39
40
  The controller has one scheduler and one in-flight attempt. Its states are `idle`, `connecting`, `connected`, `waiting`, `failed`, and `closed`; immutable snapshots expose `ConnectionSnapshot.retryDisposition` while the corresponding retry decision applies and clear it before a new attempt, after connection, and on close. Call `start()` once, observe snapshots with `subscribe(...)`, await an established session with `waitForSession(...)`, and use `retryNow()` only to wake a `waiting` controller. `close()` cancels acquisition, connection, and waiting before closing the current session.
40
41
 
41
- Node `SessionHandlers` accept application stream kinds whose UTF-8 encoding is 1 through 255 bytes and reserve `flowersec.rpc.v2` for Flowersec RPC. `AcceptedSession.serve(...)` closes successfully handled streams. A rejected handler Promise resets then closes only that stream; the accept loop and unrelated streams continue.
42
+ Node `SessionHandlers` accept application stream kinds whose UTF-8 encoding is 1 through 255 bytes and reserve `flowersec.rpc.v2` for Flowersec RPC. `AcceptedSession.serve(...)` half-closes successfully handled streams. A rejected handler Promise resets only that stream; the accept loop and unrelated streams continue.
42
43
 
43
44
  Source failures return a structured `terminal`, `retryable`, or `retry_after` disposition. Thrown or malformed source failures are terminal. Retry delay is deterministic exponential backoff from 250 ms, doubling to a 30-second maximum with no jitter; `retry_after` is never attempted before its specified Unix-millisecond boundary. Attempts are unlimited unless `maximumAttempts` is explicitly set.
44
45
 
@@ -46,25 +47,22 @@ A newly established session replaces `currentSession` atomically. The controller
46
47
 
47
48
  A negotiated `Session.unreliableMessages` channel sends defensively copied `Uint8Array` values and returns `accepted`, `dropped_expired`, `dropped_budget`, or `dropped_carrier`. `receive(...)` also returns a fresh `Uint8Array`. Invalid payloads, unavailable channels, cancellation, closure, and internal failures remain redacted public operation errors.
48
49
 
49
- ## Capability Layers
50
-
51
- The portable core is the same application model used by every Flowersec SDK:
52
- opaque artifact parsing, a durable single-use lease, one-shot connection,
53
- carrier-neutral sessions, RPC, reliable streams, redacted public errors, and
54
- the optional single-owner `ConnectionController`. Callers should not compare
55
- raw TypeScript error-code strings with other SDKs.
56
-
57
- Portable core, connection control, session/RPC/stream lifecycle, accepted-session
58
- workflows, and published consumer workflows align across every applicable SDK.
59
- Platform-limited profiles are unsupported only with an explicit alternative
60
- boundary and executable test ID in `stability/language_capabilities.json`.
61
-
62
- The TypeScript SDK profile is split by entrypoint: browsers own WebSocket and
63
- WebTransport dialing, while Node.js owns WebSocket and WebTransport dialing through
64
- the native `@fails-components/webtransport` adapter. Its language convenience
65
- includes static generic RPC result typing, notification subscriptions, and
66
- proxy composition. These conveniences do not expand the
67
- portable core required from Go, Swift, or Rust.
50
+ ## Supported Connections
51
+
52
+ Browsers support WebSocket. Browser WebTransport is capability-dependent on
53
+ the browser's WebTransport API and is an optional browser adapter, not a
54
+ required native-server carrier. Through
55
+ `/node`, Node.js supports WebSocket and raw QUIC client connections, direct
56
+ server sessions, and opaque `TunnelRuntime` relay legs. Raw QUIC uses the
57
+ Flowersec-owned optional native addon wrapper and one of its supported
58
+ prebuilt platform packages; it never loads from the browser entrypoint. The
59
+ wrapper selects the matching optional package for macOS arm64/x64 or Linux
60
+ arm64/x64 glibc. Windows and musl packages are not published. The same Node entrypoint provides the
61
+ control plane and `ProxyServer`, and the relay never terminates an E2EE
62
+ Session. WebTransport is an optional adapter profile and the Node.js runtime
63
+ does not currently expose a production adapter.
64
+ The `/proxy` entrypoint adds browser bridges for applications that need to keep
65
+ the session behind a Service Worker or another window.
68
66
 
69
67
  ## Opaque Boundaries
70
68
 
@@ -72,35 +70,31 @@ portable core required from Go, Swift, or Rust.
72
70
 
73
71
  `ConnectError` and `SessionError` expose only a closed `code`. They do not retain raw causes, credentials, URLs, candidate diagnostics, transport objects, peer details, or internal routing and handshake state.
74
72
 
75
- Candidate lists, runtime capability descriptors, transport factories, admission state, Yamux, wire messages, cryptographic state, keys, and spend-ledger internals are not package exports.
73
+ Connection negotiation and cryptographic state are not package exports.
76
74
 
77
75
  Admission rejection reasons are server-authorized bounded protocol tokens. Clients validate their wire form without carrying a deployment-specific reason registry; the public error boundary remains closed and redacted.
78
76
 
79
- The proxy entrypoint accepts an opaque `ArtifactLease` or an already connected `Session`. Its public runtime never exposes the selected carrier, a Yamux stream, raw artifact fields, or proxy wire frames. Cross-window bridges require an exact allowed origin and may require a bounded capability nonce; runtime failures are mapped to closed status/code values before reaching Service Worker or Window messages.
80
-
81
- ## Transport v2 Support
77
+ The proxy entrypoint accepts an opaque `ArtifactLease` or an already connected `Session`. Cross-window bridges require an exact allowed origin and may require a bounded capability nonce; runtime failures are mapped to closed status/code values before reaching Service Worker or Window messages.
82
78
 
83
- WebSocket, raw QUIC, and WebTransport are equal carrier candidates. Browser and Node.js support below describes this SDK's profiles, not the portable API.
79
+ ## Connection Notes
84
80
 
85
- Raw QUIC and WebTransport preserve native FIN, RESET_STREAM, STOP_SENDING, flow control, and migration behavior. The TypeScript SDK support below is narrower than the protocol carrier set.
86
-
87
- WebSocket uses Yamux only inside its carrier adapter. Yamux has no independent STOP_SENDING primitive, so that operation is explicitly unavailable rather than emulated with a full stream reset.
88
-
89
- Browser applications receive a ready `Session` from `connect(...)`. The browser connector supports WSS, restricted plaintext loopback WebSocket direct connections, and WebTransport. WebTransport uses native HTTP/3 bidirectional streams and does not use Yamux.
81
+ Browser applications receive a ready `Session` from `connect(...)`. The browser
82
+ connector supports WSS, restricted plaintext loopback WebSocket direct
83
+ connections, and WebTransport when the browser exposes that API. WebTransport
84
+ uses browser-owned HTTP/3 streams and is not available in the Node entrypoint.
90
85
 
91
86
  Chromium does not support a WebTransport pooling option; each carrier creates an independent native WebTransport connection.
92
87
 
93
88
  Cold-connection diagnostics require every independent carrier to meet the declared deadline. A `dial_failed` result remains a test failure and is not hidden by pooling, retry, or timeout relaxation.
94
89
 
95
- Node.js applications receive the same `Session` contract from `connect(...)`. The Node.js connector supports WSS and WebTransport for direct and tunnel artifacts, plus restricted plaintext loopback WebSocket direct connections. It requires an absolute HTTP(S) `origin`; a custom certificate authority can be supplied through `tls.ca`. Invalid origin and TLS options fail as `ConnectError` with `invalid_options` and are terminal to the optional connection controller.
96
-
97
- The Node WebTransport direct listener/server is owned by the Node runtime and the `flowersec-ts-cli` server path. It uses the same native carrier acceptor and session engine as the client connector; it is not a second protocol implementation.
98
-
99
- The connectors select an eligible transport from the opaque artifact internally. They do not accept a public transport selector, capability manifest, candidate factory, or admission-reason override. Unsupported artifacts fail closed. The TypeScript package does not expose raw QUIC dialing.
100
-
101
- Transport v2 production carrier support: browsers support WebSocket and WebTransport; Node.js supports WebSocket and WebTransport dialing for direct clients and both tunnel roles. Node WebTransport uses the pinned native quiche adapter and is covered by a real admission/session integration test without Playwright.
90
+ Node.js applications receive the same `Session` contract from `connect(...)`.
91
+ The Node connector supports WSS, restricted plaintext loopback WebSocket
92
+ direct connections, and raw QUIC through the optional native package. It
93
+ requires an absolute HTTP(S) `origin`; custom certificate authorities can be
94
+ supplied through `tls.ca`, and raw QUIC requires explicit trust roots.
102
95
 
103
- Flowersec disables application 0-RTT. Reliable streams never use QUIC DATAGRAM; runtimes with negotiated native DATAGRAM expose it only through carrier-neutral unreliable messages.
96
+ The connectors choose an eligible connection path from the invitation. They do
97
+ not expose transport selectors, candidate lists, or native carrier objects to application code.
104
98
 
105
99
  ## Verify
106
100
 
@@ -3,87 +3,12 @@
3
3
  This file is generated from the canonical Flowersec source dependency inventory for flowersec-ts.
4
4
  Do not edit it manually. License decisions are reviewed by the repository source license policy.
5
5
 
6
- - @fails-components/webtransport 1.6.7 (Declared: BSD-3-Clause; selected: BSD-3-Clause; source: https://registry.npmjs.org/@fails-components/webtransport/-/webtransport-1.6.7.tgz)
7
- - @fails-components/webtransport-transport-http3-quiche 1.6.7 (Declared: BSD-3-Clause; selected: BSD-3-Clause; source: https://registry.npmjs.org/@fails-components/webtransport-transport-http3-quiche/-/webtransport-transport-http3-quiche-1.6.7.tgz)
8
- - @isaacs/fs-minipass 4.0.1 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz)
9
- - @noble/ciphers 0.6.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz)
10
- - @noble/curves 1.9.7 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz)
11
- - @noble/hashes 1.8.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz)
12
- - @types/debug 4.1.13 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz)
13
- - @types/ms 2.1.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz)
14
- - ansi-regex 5.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz)
15
- - ansi-styles 4.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz)
16
- - base64-js 1.5.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz)
17
- - bindings 1.5.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz)
18
- - bl 4.1.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/bl/-/bl-4.1.0.tgz)
19
- - buffer 5.7.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz)
20
- - chownr 1.1.4 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz)
21
- - chownr 3.0.0 (Declared: BlueOak-1.0.0; selected: BlueOak-1.0.0; source: https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz)
22
- - cliui 8.0.1 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz)
23
- - cmake-js 8.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/cmake-js/-/cmake-js-8.0.0.tgz)
24
- - color-convert 2.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz)
25
- - color-name 1.1.4 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz)
26
- - debug 4.4.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/debug/-/debug-4.4.3.tgz)
27
- - decompress-response 6.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz)
28
- - deep-extend 0.6.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz)
29
- - detect-libc 2.1.2 (Declared: Apache-2.0; selected: Apache-2.0; source: https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz)
30
- - emoji-regex 8.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz)
31
- - end-of-stream 1.4.5 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz)
32
- - escalade 3.2.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz)
33
- - expand-template 2.0.3 (Declared: MIT OR WTFPL; selected: MIT; source: https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz)
34
- - file-uri-to-path 1.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz)
35
- - fs-constants 1.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz)
36
- - fs-extra 11.4.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz)
37
- - get-caller-file 2.0.5 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz)
38
- - github-from-package 0.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz)
39
- - graceful-fs 4.2.11 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz)
40
- - ieee754 1.2.1 (Declared: BSD-3-Clause; selected: BSD-3-Clause; source: https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz)
41
- - inherits 2.0.4 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz)
42
- - ini 1.3.8 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/ini/-/ini-1.3.8.tgz)
43
- - is-fullwidth-code-point 3.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz)
44
- - isexe 4.0.0 (Declared: BlueOak-1.0.0; selected: BlueOak-1.0.0; source: https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz)
45
- - jsonfile 6.2.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz)
46
- - mimic-response 3.1.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz)
47
- - minimist 1.2.8 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz)
48
- - minipass 7.1.3 (Declared: BlueOak-1.0.0; selected: BlueOak-1.0.0; source: https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz)
49
- - minizlib 3.1.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz)
50
- - mkdirp-classic 0.5.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz)
51
- - ms 2.1.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ms/-/ms-2.1.3.tgz)
52
- - napi-build-utils 2.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz)
53
- - node-abi 3.94.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz)
54
- - node-addon-api 7.1.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz)
55
- - node-api-headers 1.9.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.9.0.tgz)
56
- - once 1.4.0 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/once/-/once-1.4.0.tgz)
57
- - prebuild-install 7.1.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz)
58
- - pump 3.0.4 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/pump/-/pump-3.0.4.tgz)
6
+ - @noble/ciphers 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz)
7
+ - @noble/curves 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz)
8
+ - @noble/hashes 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz)
59
9
  - punycode 2.3.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz)
60
- - rc 1.2.8 (Declared: BSD-2-Clause OR MIT OR Apache-2.0; selected: MIT; source: https://registry.npmjs.org/rc/-/rc-1.2.8.tgz)
61
- - readable-stream 3.6.2 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz)
62
- - require-directory 2.1.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz)
63
- - safe-buffer 5.2.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz)
64
- - semver 7.7.3 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz)
65
- - simple-concat 1.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz)
66
- - simple-get 4.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz)
67
- - string_decoder 1.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz)
68
- - string-width 4.2.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz)
69
- - strip-ansi 6.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz)
70
- - strip-json-comments 2.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz)
71
- - tar 7.5.22 (Declared: BlueOak-1.0.0; selected: BlueOak-1.0.0; source: https://registry.npmjs.org/tar/-/tar-7.5.22.tgz)
72
- - tar-fs 2.1.5 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz)
73
- - tar-stream 2.2.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz)
74
10
  - tr46 5.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz)
75
- - tunnel-agent 0.6.0 (Declared: Apache-2.0; selected: Apache-2.0; source: https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz)
76
- - universalify 2.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz)
77
- - url-join 4.0.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz)
78
- - util-deprecate 1.0.2 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz)
79
- - which 6.0.1 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/which/-/which-6.0.1.tgz)
80
- - wrap-ansi 7.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz)
81
- - wrappy 1.0.2 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz)
82
- - ws 8.21.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ws/-/ws-8.21.0.tgz)
83
- - y18n 5.0.8 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz)
84
- - yallist 5.0.0 (Declared: BlueOak-1.0.0; selected: BlueOak-1.0.0; source: https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz)
85
- - yargs 17.7.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz)
86
- - yargs-parser 21.1.1 (Declared: ISC; selected: ISC; source: https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz)
11
+ - ws 8.21.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ws/-/ws-8.21.3.tgz)
87
12
  ## Distributed Dependency License Materials
88
13
 
89
14
  The following dependencies are incorporated into distributed Flowersec files or binaries.
package/dist/cli.js CHANGED
@@ -1,32 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
+ import { acceptNativeSessionV2 } from "./connector/sessionAcceptor.js";
4
+ import { claimSpendMarker } from "./cliSpendMarker.js";
5
+ import { connect } from "./node/connectSession.js";
6
+ import { nodeSessionRuntimeV2 } from "./node/sessionRuntime.js";
7
+ import { startNodeWebSocketServer } from "./node/webSocketServer.js";
3
8
  import { createArtifactLeaseV2 } from "./v2/artifactLease.js";
4
9
  import { buildFSB2RequestV2, encodeFSB2RequestV2 } from "./v2/artifact.js";
5
10
  import { parseArtifact, unwrapArtifact } from "./v2/opaqueArtifact.js";
6
- import { connect } from "./node/connectSession.js";
7
- import { startNodeWebTransportServerV2 } from "./node/webTransportServer.js";
8
- import { acceptNativeSessionV2 } from "./connector/sessionAcceptor.js";
9
- import { nodeSessionRuntimeV2 } from "./node/sessionRuntime.js";
10
- import { claimSpendMarker } from "./cliSpendMarker.js";
11
11
  const args = parseArguments(process.argv.slice(2));
12
12
  if (args.mode === "client")
13
13
  await runClient(args.values);
14
14
  else
15
15
  await runServer(args.values);
16
16
  async function runClient(values) {
17
- const artifactPath = required(values, "artifact");
18
- const origin = required(values, "origin");
19
- const spendMarker = required(values, "spend-marker");
20
- requireWebTransport(values);
21
- const artifact = parseArtifact(readFileSync(artifactPath));
17
+ requireWebSocket(values);
18
+ const artifact = parseArtifact(readFileSync(required(values, "artifact")));
19
+ const lease = createArtifactLeaseV2(artifact, async () => claimSpendMarker(required(values, "spend-marker")));
22
20
  let session;
23
- const lease = createArtifactLeaseV2(artifact, async () => claimSpendMarker(spendMarker));
24
21
  try {
25
22
  session = await connect(lease, {
26
- origin,
27
- ...(values["certificate-hash"] === undefined ? {} : {
28
- tls: { serverCertificateHash: decodeHash(values["certificate-hash"]) },
29
- }),
23
+ origin: required(values, "origin"),
24
+ ...(values.ca === undefined ? {} : { tls: { ca: readFileSync(values.ca, "utf8") } }),
30
25
  });
31
26
  const stream = await session.openStream("cli");
32
27
  await stream.write(new TextEncoder().encode("flowersec-ts-cli"));
@@ -42,16 +37,27 @@ async function runClient(values) {
42
37
  }
43
38
  }
44
39
  async function runServer(values) {
40
+ requireWebSocket(values);
45
41
  const artifact = unwrapArtifact(parseArtifact(readFileSync(required(values, "artifact"))));
46
- requireWebTransport(values);
47
- const server = await startNodeWebTransportServerV2({
42
+ if (artifact.path.kind !== "direct")
43
+ throw new Error("CLI server requires a direct artifact");
44
+ const certificatePath = values.certificate;
45
+ const privateKeyPath = values["private-key"];
46
+ if ((certificatePath === undefined) !== (privateKeyPath === undefined)) {
47
+ throw new Error("--certificate and --private-key must be provided together");
48
+ }
49
+ const server = await startNodeWebSocketServer({
48
50
  host: values.host ?? "127.0.0.1",
49
51
  port: Number.parseInt(required(values, "port"), 10),
50
- path: values.path ?? "/flowersec/webtransport/v2/direct",
51
- certificate: readFileSync(required(values, "certificate"), "utf8"),
52
- privateKey: readFileSync(required(values, "private-key"), "utf8"),
53
- carrierPath: artifact.path.kind,
52
+ path: "direct",
53
+ allowedOrigins: [required(values, "origin")],
54
54
  inboundBidirectionalStreamCapacity: artifact.session.max_inbound_streams + 2,
55
+ ...(certificatePath === undefined || privateKeyPath === undefined ? {} : {
56
+ tls: {
57
+ certificate: readFileSync(certificatePath, "utf8"),
58
+ privateKey: readFileSync(privateKeyPath, "utf8"),
59
+ },
60
+ }),
55
61
  });
56
62
  process.stdout.write(JSON.stringify(server.address()) + "\n");
57
63
  const abort = new AbortController();
@@ -61,7 +67,7 @@ async function runServer(values) {
61
67
  try {
62
68
  const session = await acceptNativeSessionV2(await server.accept({ signal: abort.signal }), async (request) => {
63
69
  const chosen = artifact.path.candidates.find(({ id }) => id === request.request.chosen_candidate_id);
64
- if (request.request.pathKind !== artifact.path.kind || chosen?.carrier !== "webtransport") {
70
+ if (request.request.pathKind !== "direct" || chosen?.carrier !== "websocket") {
65
71
  throw new Error("CLI server rejected an unexpected artifact candidate");
66
72
  }
67
73
  const expected = encodeFSB2RequestV2(buildFSB2RequestV2(artifact, request.request.chosen_candidate_id));
@@ -106,15 +112,9 @@ function required(values, name) {
106
112
  throw new Error(`missing --${name}`);
107
113
  return value;
108
114
  }
109
- function decodeHash(value) {
110
- if (!/^[0-9a-fA-F]{64}$/.test(value))
111
- throw new Error("--certificate-hash must be 32-byte SHA-256 hex");
112
- return Uint8Array.from(Buffer.from(value, "hex"));
113
- }
114
- function requireWebTransport(values) {
115
- if (values.transport !== "webtransport") {
116
- throw new Error("--transport webtransport is required");
117
- }
115
+ function requireWebSocket(values) {
116
+ if (values.transport !== "websocket")
117
+ throw new Error("--transport websocket is required");
118
118
  }
119
119
  function bytesEqual(left, right) {
120
120
  if (left.length !== right.length)
@@ -0,0 +1,5 @@
1
+ import type { ArtifactV2, CanonicalArtifactCandidateV2 } from "../../v2/artifact.js";
2
+ import { type NativeCarrierSessionV2 } from "../../v2/carrier.js";
3
+ import type { CandidateAttemptFactoryV2 } from "../sessionConnector.js";
4
+ export type RawQuicClientFactoryV2 = (candidate: CanonicalArtifactCandidateV2, artifact: ArtifactV2, signal: AbortSignal) => Promise<NativeCarrierSessionV2>;
5
+ export declare function createRawQuicCandidateFactoryV2(factory: RawQuicClientFactoryV2): CandidateAttemptFactoryV2;
@@ -0,0 +1,71 @@
1
+ import { adaptNativeCarrierSessionV2, } from "../../v2/carrier.js";
2
+ export function createRawQuicCandidateFactoryV2(factory) {
3
+ return {
4
+ create(candidate, artifact) {
5
+ if (candidate.carrier !== "raw_quic") {
6
+ throw new Error(`unsupported raw QUIC carrier ${candidate.carrier}`);
7
+ }
8
+ return new RawQuicCandidateAttempt(candidate, artifact, factory);
9
+ },
10
+ };
11
+ }
12
+ class RawQuicCandidateAttempt {
13
+ candidate;
14
+ artifact;
15
+ factory;
16
+ controller = new AbortController();
17
+ carrier;
18
+ finalized = false;
19
+ constructor(candidate, artifact, factory) {
20
+ this.candidate = candidate;
21
+ this.artifact = artifact;
22
+ this.factory = factory;
23
+ }
24
+ async ready(signal) {
25
+ const unlink = linkAbort(signal, this.controller);
26
+ try {
27
+ const carrier = await this.factory(this.candidate, this.artifact, this.controller.signal);
28
+ this.carrier = carrier;
29
+ return {
30
+ candidate: this.candidate,
31
+ kind: carrier.kind,
32
+ path: carrier.path,
33
+ inboundBidirectionalStreamCapacity: carrier.inboundBidirectionalStreamCapacity,
34
+ openAdmissionChannel: async (channelSignal) => {
35
+ const options = channelSignal === undefined ? {} : { signal: channelSignal };
36
+ const stream = await carrier.openStream(options);
37
+ return {
38
+ framing: "stream",
39
+ stream,
40
+ abort: (error) => stream.abort(error),
41
+ };
42
+ },
43
+ finalize: () => {
44
+ if (this.finalized)
45
+ throw new Error("raw QUIC candidate already finalized");
46
+ this.finalized = true;
47
+ return adaptNativeCarrierSessionV2(carrier);
48
+ },
49
+ close: async () => await carrier.close(),
50
+ abort: () => carrier.abort({ code: 6, reason: "candidate aborted" }),
51
+ };
52
+ }
53
+ finally {
54
+ unlink();
55
+ }
56
+ }
57
+ abort() {
58
+ this.controller.abort(new Error("raw QUIC candidate aborted"));
59
+ this.carrier?.abort({ code: 6, reason: "candidate aborted" });
60
+ }
61
+ }
62
+ function linkAbort(parent, child) {
63
+ if (parent === undefined)
64
+ return () => undefined;
65
+ const abort = () => child.abort(parent.reason);
66
+ if (parent.aborted)
67
+ abort();
68
+ else
69
+ parent.addEventListener("abort", abort, { once: true });
70
+ return () => parent.removeEventListener("abort", abort);
71
+ }
@@ -1,5 +1,5 @@
1
1
  import { AdmissionStatusV2, type ArtifactV2, type DecodedFSB2RequestV2 } from "../v2/artifact.js";
2
- import { type NativeCarrierSessionV2 } from "../v2/carrier.js";
2
+ import type { CarrierSessionV2, CarrierStreamV2 } from "../v2/carrier.js";
3
3
  import type { SessionProtocolRuntimeV2, SessionV2 as InternalSessionV2 } from "../v2/session.js";
4
4
  import type { RpcRouter } from "../rpc/server.js";
5
5
  export type AdmissionDecisionV2 = Readonly<{
@@ -11,7 +11,27 @@ export type AdmissionDecisionV2 = Readonly<{
11
11
  reason: string;
12
12
  }>;
13
13
  export type AdmissionAuthorizerV2 = (request: DecodedFSB2RequestV2, signal?: AbortSignal) => Promise<AdmissionDecisionV2>;
14
- export declare function acceptNativeSessionV2(carrier: NativeCarrierSessionV2, authorize: AdmissionAuthorizerV2, options: Readonly<{
14
+ export type ReceivedSessionAdmissionV2 = Readonly<{
15
+ carrier: CarrierSessionV2;
16
+ stream: CarrierStreamV2;
17
+ rawFSB2: Uint8Array;
18
+ decoded: DecodedFSB2RequestV2;
19
+ }>;
20
+ export declare function receiveSessionAdmissionV2(carrier: CarrierSessionV2, signal?: AbortSignal): Promise<ReceivedSessionAdmissionV2>;
21
+ export declare function rejectSessionAdmissionV2(received: ReceivedSessionAdmissionV2, decision: Extract<AdmissionDecisionV2, Readonly<{
22
+ accepted: false;
23
+ }>>, signal?: AbortSignal): Promise<never>;
24
+ export declare function acceptReceivedSessionV2(received: ReceivedSessionAdmissionV2, artifact: ArtifactV2, options: Readonly<{
25
+ runtime: SessionProtocolRuntimeV2;
26
+ rpcRouter?: RpcRouter;
27
+ signal?: AbortSignal;
28
+ role?: "client" | "server";
29
+ localAdmissionBinding?: Uint8Array;
30
+ peerAdmissionBinding?: Uint8Array;
31
+ localEndpointInstanceID?: string;
32
+ expectedPeerEndpointInstanceID?: string;
33
+ }>): Promise<InternalSessionV2>;
34
+ export declare function acceptNativeSessionV2(carrier: CarrierSessionV2, authorize: AdmissionAuthorizerV2, options: Readonly<{
15
35
  runtime: SessionProtocolRuntimeV2;
16
36
  rpcRouter?: RpcRouter;
17
37
  signal?: AbortSignal;
@@ -1,38 +1,68 @@
1
1
  import { AdmissionStatusV2, buildFSB2RequestV2, decodeFSB2RequestV2, encodeFSB2RequestV2, encodeFSA2ResponseV2, } from "../v2/artifact.js";
2
- import { adaptNativeCarrierSessionV2 } from "../v2/carrier.js";
3
2
  import { establishSessionV2 } from "../v2/session.js";
4
3
  import { AdmissionSessionV2Error } from "../v2/admissionError.js";
5
4
  import { sessionConfigFromArtifactV2 } from "./sessionConfig.js";
5
+ export async function receiveSessionAdmissionV2(carrier, signal) {
6
+ const stream = carrier.kind === "webtransport"
7
+ ? await carrier.openStream(signalOptions(signal))
8
+ : await carrier.acceptStream(signalOptions(signal));
9
+ try {
10
+ const rawFSB2 = await readFSB2(stream, signal);
11
+ return Object.freeze({ carrier, stream, rawFSB2, decoded: decodeFSB2RequestV2(rawFSB2) });
12
+ }
13
+ catch (error) {
14
+ stream.abort(asError(error));
15
+ carrier.abort({ code: 6, reason: "admission failed" });
16
+ throw error;
17
+ }
18
+ }
19
+ export async function rejectSessionAdmissionV2(received, decision, signal) {
20
+ await respondAdmission(received.stream, { status: decision.status, reason: decision.reason }, signal);
21
+ received.carrier.abort({ code: 6, reason: "admission rejected" });
22
+ throw new AdmissionSessionV2Error(decision.reason, `Flowersec v2 admission rejected: ${decision.reason}`);
23
+ }
24
+ export async function acceptReceivedSessionV2(received, artifact, options) {
25
+ try {
26
+ const expected = encodeFSB2RequestV2(buildFSB2RequestV2(artifact, received.decoded.request.chosen_candidate_id));
27
+ if (!equalBytes(expected, received.rawFSB2))
28
+ throw new Error("authorized artifact does not match admission");
29
+ await respondAdmission(received.stream, { status: AdmissionStatusV2.Success, reason: "" }, options.signal);
30
+ const base = sessionConfigFromArtifactV2(artifact, received.rawFSB2, options.runtime, undefined, options.role);
31
+ const config = {
32
+ ...base,
33
+ ...(options.rpcRouter === undefined ? {} : { rpcRouter: options.rpcRouter }),
34
+ ...(options.localAdmissionBinding === undefined ? {} : { localAdmissionBinding: options.localAdmissionBinding }),
35
+ ...(options.peerAdmissionBinding === undefined ? {} : { peerAdmissionBinding: options.peerAdmissionBinding }),
36
+ ...(options.localEndpointInstanceID === undefined ? {} : { localEndpointInstanceID: options.localEndpointInstanceID }),
37
+ ...(options.expectedPeerEndpointInstanceID === undefined ? {} : { expectedPeerEndpointInstanceID: options.expectedPeerEndpointInstanceID }),
38
+ };
39
+ return await establishSessionV2(received.carrier, config, signalOptions(options.signal));
40
+ }
41
+ catch (error) {
42
+ received.stream.abort(asError(error));
43
+ received.carrier.abort({ code: 6, reason: "admission failed" });
44
+ throw error;
45
+ }
46
+ }
6
47
  export async function acceptNativeSessionV2(carrier, authorize, options) {
7
- const admission = carrier.kind === "webtransport"
8
- ? await carrier.openStream(signalOptions(options.signal))
9
- : await carrier.acceptStream(signalOptions(options.signal));
48
+ const received = await receiveSessionAdmissionV2(carrier, options.signal);
10
49
  try {
11
- const rawFSB2 = await readFSB2(admission, options.signal);
12
- const decoded = decodeFSB2RequestV2(rawFSB2);
13
- const decision = await authorize(decoded, options.signal);
14
- if (decision.accepted) {
15
- const expected = encodeFSB2RequestV2(buildFSB2RequestV2(decision.artifact, decoded.request.chosen_candidate_id));
16
- if (!equalBytes(expected, rawFSB2))
17
- throw new Error("authorized artifact does not match admission");
18
- }
19
- const response = decision.accepted
20
- ? { status: AdmissionStatusV2.Success, reason: "" }
21
- : { status: decision.status, reason: decision.reason };
22
- await writeAll(admission, encodeFSA2ResponseV2(response), options.signal);
23
- await raceAbort(admission.closeWrite(), options.signal);
50
+ const decision = await authorize(received.decoded, options.signal);
24
51
  if (!decision.accepted) {
25
- throw new AdmissionSessionV2Error(decision.reason, `Flowersec v2 admission rejected: ${decision.reason}`);
52
+ return await rejectSessionAdmissionV2(received, decision, options.signal);
26
53
  }
27
- const config = sessionConfigFromArtifactV2(decision.artifact, rawFSB2, options.runtime, undefined, "server");
28
- return await establishSessionV2(adaptNativeCarrierSessionV2(carrier), options.rpcRouter === undefined ? config : { ...config, rpcRouter: options.rpcRouter }, signalOptions(options.signal));
54
+ return await acceptReceivedSessionV2(received, decision.artifact, { ...options, role: "server" });
29
55
  }
30
56
  catch (error) {
31
- admission.abort(asError(error));
57
+ received.stream.abort(asError(error));
32
58
  carrier.abort({ code: 6, reason: "admission failed" });
33
59
  throw error;
34
60
  }
35
61
  }
62
+ async function respondAdmission(stream, response, signal) {
63
+ await writeAll(stream, encodeFSA2ResponseV2(response), signal);
64
+ await raceAbort(stream.closeWrite(), signal);
65
+ }
36
66
  function equalBytes(left, right) {
37
67
  if (left.length !== right.length)
38
68
  return false;
@@ -2,6 +2,7 @@ import { type ArtifactV2, type CanonicalArtifactCandidateV2 } from "../v2/artifa
2
2
  import { type SessionDeadlineFactoryV2, type SessionProtocolRuntimeV2, type SessionV2 as InternalSessionV2 } from "../v2/session.js";
3
3
  import type { ArtifactLeaseV2 } from "../v2/artifactLease.js";
4
4
  import { type RuntimeCapabilityDescriptorV2 } from "../v2/capability.js";
5
+ import type { RpcRouter } from "../rpc/server.js";
5
6
  import { type ReadyAdmissionTransportV2 } from "./admissionCommit.js";
6
7
  export type SessionConnectorStateV2 = "attempt" | "ready" | "winner" | "admitted" | "established" | "terminated";
7
8
  export type ConnectorArtifactLeaseV2 = ArtifactLeaseV2;
@@ -20,6 +21,7 @@ export type SessionConnectorOptionsV2 = Readonly<{
20
21
  }>;
21
22
  type SessionConnectorInternalOptionsV2 = Readonly<{
22
23
  runtime: SessionProtocolRuntimeV2;
24
+ rpcRouter?: RpcRouter;
23
25
  connectTimeoutMs?: number;
24
26
  deadlineFactory?: SessionDeadlineFactoryV2;
25
27
  loserCloseTimeoutMs?: number;
@@ -199,7 +199,10 @@ export class SessionConnectorV2 {
199
199
  let config;
200
200
  try {
201
201
  rawFSB2 = encodeFSB2RequestV2(buildFSB2RequestV2(this.artifact, selected.attempt.candidate.id));
202
- config = sessionConfigFromArtifactV2(this.artifact, rawFSB2, this.options.runtime, this.options.deadlineFactory);
202
+ const baseConfig = sessionConfigFromArtifactV2(this.artifact, rawFSB2, this.options.runtime, this.options.deadlineFactory);
203
+ config = this.options.rpcRouter === undefined
204
+ ? baseConfig
205
+ : { ...baseConfig, rpcRouter: this.options.rpcRouter };
203
206
  }
204
207
  catch (error) {
205
208
  const closeFailure = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => selected.ready.close(), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
@@ -0,0 +1 @@
1
+ export {};