@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.
Files changed (133) hide show
  1. package/README.md +57 -200
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/dist/browser/index.d.ts +0 -5
  4. package/dist/browser/index.js +0 -3
  5. package/dist/cli.js +93 -64
  6. package/dist/facade.d.ts +0 -17
  7. package/dist/facade.js +0 -11
  8. package/dist/generated/unicode151.d.ts +2 -0
  9. package/dist/generated/unicode151.js +83 -0
  10. package/dist/interop/proxyServerPeer.js +34 -20
  11. package/dist/interop/serverParityPeer.js +11 -252
  12. package/dist/node/acceptor.d.ts +1 -57
  13. package/dist/node/acceptor.js +3 -420
  14. package/dist/node/acceptorV3.js +2 -2
  15. package/dist/node/connectSessionV3.js +2 -2
  16. package/dist/node/index.d.ts +2 -17
  17. package/dist/node/index.js +1 -9
  18. package/dist/node/nativeTransportAddon.d.ts +7 -36
  19. package/dist/node/nativeTransportAddon.js +7 -27
  20. package/dist/node/proxyServer.d.ts +2 -2
  21. package/dist/node/proxyServer.js +2 -2
  22. package/dist/node/rawQuicAdapterV3.d.ts +2 -2
  23. package/dist/node/rawQuicAdapterV3.js +1 -1
  24. package/dist/node/rawQuicServerV3.d.ts +2 -2
  25. package/dist/node/rawQuicServerV3.js +1 -1
  26. package/dist/node/rawQuicTls.d.ts +2 -0
  27. package/dist/node/{rawQuicAdapter.js → rawQuicTls.js} +30 -45
  28. package/dist/node/tunnelRuntimeV3.js +2 -2
  29. package/dist/proxy/integration.d.ts +5 -5
  30. package/dist/proxy/integration.js +2 -2
  31. package/dist/public/streamHandlers.d.ts +0 -3
  32. package/dist/public/streamHandlers.js +2 -4
  33. package/dist/public/streamMetadata.js +2 -2
  34. package/dist/runtime/errors.d.ts +2 -2
  35. package/dist/testSupport/tlsFixture.d.ts +2 -0
  36. package/dist/testSupport/tlsFixture.js +8 -0
  37. package/dist/utils/errors.d.ts +0 -2
  38. package/dist/utils/errors.js +0 -80
  39. package/dist/v3/artifact.js +17 -3
  40. package/dist/v3/carrier.d.ts +2 -2
  41. package/dist/v3/protocol.js +2 -103
  42. package/dist/v3/retryDisposition.d.ts +2 -2
  43. package/dist/v3/retryDisposition.js +0 -7
  44. package/dist/vendor/tr46.d.ts +4 -0
  45. package/dist/vendor/tr46.js +37 -32
  46. package/package.json +21 -19
  47. package/sbom/cyclonedx.json +62 -62
  48. package/sbom/spdx.json +65 -65
  49. package/dist/browser/connectSession.d.ts +0 -13
  50. package/dist/browser/connectSession.js +0 -40
  51. package/dist/browser/runtimeCapability.d.ts +0 -13
  52. package/dist/browser/runtimeCapability.js +0 -27
  53. package/dist/browser/sessionRuntime.d.ts +0 -2
  54. package/dist/browser/sessionRuntime.js +0 -21
  55. package/dist/browser/v2.d.ts +0 -3
  56. package/dist/browser/v2.js +0 -2
  57. package/dist/browser/webTransportClient.d.ts +0 -8
  58. package/dist/browser/webTransportClient.js +0 -55
  59. package/dist/connectionController.d.ts +0 -55
  60. package/dist/connectionController.js +0 -441
  61. package/dist/connector/adapters/rawQuicCandidate.d.ts +0 -5
  62. package/dist/connector/adapters/rawQuicCandidate.js +0 -71
  63. package/dist/connector/adapters/webSocketCandidate.d.ts +0 -7
  64. package/dist/connector/adapters/webSocketCandidate.js +0 -116
  65. package/dist/connector/adapters/webTransportCandidate.d.ts +0 -5
  66. package/dist/connector/adapters/webTransportCandidate.js +0 -73
  67. package/dist/connector/admissionCommit.d.ts +0 -31
  68. package/dist/connector/admissionCommit.js +0 -239
  69. package/dist/connector/sessionAcceptor.d.ts +0 -38
  70. package/dist/connector/sessionAcceptor.js +0 -158
  71. package/dist/connector/sessionConfig.d.ts +0 -3
  72. package/dist/connector/sessionConfig.js +0 -30
  73. package/dist/connector/sessionConnector.d.ts +0 -47
  74. package/dist/connector/sessionConnector.js +0 -606
  75. package/dist/node/connectSession.d.ts +0 -23
  76. package/dist/node/connectSession.js +0 -93
  77. package/dist/node/controlplane.d.ts +0 -137
  78. package/dist/node/controlplane.js +0 -443
  79. package/dist/node/rawQuicAdapter.d.ts +0 -9
  80. package/dist/node/rawQuicServer.d.ts +0 -24
  81. package/dist/node/rawQuicServer.js +0 -37
  82. package/dist/node/runtimeCapability.d.ts +0 -14
  83. package/dist/node/runtimeCapability.js +0 -24
  84. package/dist/node/sessionRuntime.d.ts +0 -2
  85. package/dist/node/sessionRuntime.js +0 -13
  86. package/dist/node/tunnelRuntime.d.ts +0 -42
  87. package/dist/node/tunnelRuntime.js +0 -559
  88. package/dist/node/v2.d.ts +0 -11
  89. package/dist/node/v2.js +0 -6
  90. package/dist/node/webSocketServer.d.ts +0 -24
  91. package/dist/node/webSocketServer.js +0 -135
  92. package/dist/public/artifact.d.ts +0 -11
  93. package/dist/public/artifact.js +0 -44
  94. package/dist/public/artifactLease.d.ts +0 -10
  95. package/dist/public/artifactLease.js +0 -41
  96. package/dist/public/connectError.d.ts +0 -6
  97. package/dist/public/connectError.js +0 -9
  98. package/dist/transport/webSocketAdapter.d.ts +0 -31
  99. package/dist/transport/webSocketAdapter.js +0 -370
  100. package/dist/transport/webTransportAdapter.d.ts +0 -27
  101. package/dist/transport/webTransportAdapter.js +0 -534
  102. package/dist/v2/admissionError.d.ts +0 -4
  103. package/dist/v2/admissionError.js +0 -8
  104. package/dist/v2/artifact.d.ts +0 -128
  105. package/dist/v2/artifact.js +0 -1131
  106. package/dist/v2/artifactLease.d.ts +0 -1
  107. package/dist/v2/artifactLease.js +0 -7
  108. package/dist/v2/capability.d.ts +0 -29
  109. package/dist/v2/capability.js +0 -190
  110. package/dist/v2/carrier.d.ts +0 -84
  111. package/dist/v2/carrier.js +0 -411
  112. package/dist/v2/contract.d.ts +0 -46
  113. package/dist/v2/contract.js +0 -1
  114. package/dist/v2/handshake.d.ts +0 -80
  115. package/dist/v2/handshake.js +0 -470
  116. package/dist/v2/index.d.ts +0 -14
  117. package/dist/v2/index.js +0 -7
  118. package/dist/v2/opaqueArtifact.d.ts +0 -1
  119. package/dist/v2/opaqueArtifact.js +0 -2
  120. package/dist/v2/protocol.d.ts +0 -99
  121. package/dist/v2/protocol.js +0 -737
  122. package/dist/v2/publicSession.d.ts +0 -1
  123. package/dist/v2/publicSession.js +0 -281
  124. package/dist/v2/retryDisposition.d.ts +0 -16
  125. package/dist/v2/retryDisposition.js +0 -30
  126. package/dist/v2/session.d.ts +0 -310
  127. package/dist/v2/session.js +0 -2127
  128. package/dist/v2/streamLifetimeLedger.d.ts +0 -31
  129. package/dist/v2/streamLifetimeLedger.js +0 -131
  130. package/dist/v2/streamMetadata.d.ts +0 -1
  131. package/dist/v2/streamMetadata.js +0 -7
  132. package/dist/v2/unreliableMessage.d.ts +0 -43
  133. package/dist/v2/unreliableMessage.js +0 -254
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # Flowersec for TypeScript
2
2
 
3
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.
4
+ Node.js. It provides encrypted sessions, RPC, notifications, reliable byte
5
+ streams, connection recovery, server runtimes, and browser proxy integration.
7
6
 
8
7
  ## Install
9
8
 
@@ -11,221 +10,75 @@ entrypoints.
11
10
  npm install @floegence/flowersec-core
12
11
  ```
13
12
 
14
- ## Public API
13
+ Node.js 24.20.0 or newer is required.
15
14
 
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.
17
- - `@floegence/flowersec-core/browser` adds `connect(...)`, `createConnectionController(...)`, and their options.
18
- - `@floegence/flowersec-core/node` adds the V3 `connect(...)`, `createConnectionController(...)`, direct-only `createAcceptor(...)`, opaque `createTunnelRuntime(...)`, `AcceptedSession`, `SessionHandlers`, and `RPCHandlers` APIs. Legacy V2 server, control-plane, and `ProxyServer` APIs are available only under the explicit `node.v2` namespace; the Go control-plane remains the only v3 invitation issuer.
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.
15
+ ## Entrypoints
20
16
 
21
- The browser entrypoint also exposes the isolated
22
- `flowersec-private-loopback/1` parser, lease, one-shot connector, and
23
- connection controller. Only these dedicated APIs accept the profile, and they
24
- require the exact numeric-loopback HTTP origin. Ordinary `connect(...)`
25
- continues to accept only standard `flowersec/3` artifacts and WSS/WebTransport
26
- security capabilities.
17
+ - `@floegence/flowersec-core` exports the portable artifact, lease, Session,
18
+ RPC, stream, metadata, error, and connection-controller contracts.
19
+ - `@floegence/flowersec-core/browser` adds browser `connect(...)`,
20
+ `createConnectionController(...)`, WSS, optional WebTransport, and the
21
+ isolated private-loopback profile.
22
+ - `@floegence/flowersec-core/node` adds Node `connect(...)`,
23
+ `createConnectionController(...)`, `createAcceptor(...)`,
24
+ `createTunnelRuntime(...)`, `ProxyServer`, `SessionHandlers`, and
25
+ `RPCHandlers`.
26
+ - `@floegence/flowersec-core/proxy` provides the browser HTTP/WebSocket proxy
27
+ runtime, Service Worker integration, and exact-origin window bridges.
27
28
 
28
- The complete boundary is documented in the
29
- [private loopback profile](../docs/PRIVATE_LOOPBACK_V1.md).
29
+ ## Client Sessions
30
30
 
31
- The root type exports are:
32
-
33
- - Artifact lifecycle: `Artifact`, closed `ArtifactError` parse failures, and `ArtifactLease`.
34
- - Sessions: `Session`, `SessionTermination`, `RpcPeer`, `RpcResult<Response>`, `ByteStream`, `IncomingStream`, `StreamMetadata`, `OperationOptions`, and `StreamOpenOptions`. Create metadata with `createStreamMetadata(...)`; invalid values throw `StreamMetadataError` before opening a stream.
35
- - Unreliable messages: `UnreliableMessageChannel`, `UnreliableMessageSendOptions`, `UnreliableMessageSendResult`, and the closed `UnreliableMessageErrorCode` set.
36
- - JSON values: `JsonPrimitive`, `JsonValue`, and `JsonObject`.
37
- - Errors: `ConnectErrorCode`, `SessionErrorCode`, and structured `RetryDisposition`.
38
- - Connection lifecycle: `ArtifactSource`, `ArtifactSourceResult`, `ConnectionController`, `ConnectionState`, `ConnectionSnapshot`, `ConnectionDiagnostic`, `ConnectionControllerFailure`, `ConnectionControllerError`, and `RetryDisposition`.
39
-
40
- Unversioned names are the recommended strict-v3 API. Explicit `V3` exports are
41
- deprecated aliases. `ConnectError.retryDisposition` and
42
- `retry_after.notBeforeUnixMilliseconds` are canonical; deprecated
43
- `disposition` and `absoluteUnixMilliseconds` properties remain readable through
44
- their aliases. Retry ownership belongs to `ConnectionController`;
45
- applications do not classify error text or run a parallel retry scheduler.
46
- Public failures remain redacted and reveal no carrier, candidate, URL,
47
- credential, stage, key, or implementation details.
48
-
49
- `RpcResult<Response>` is a discriminated union. `RpcPeer.call(...)` requires a decoder for successful payloads, so the typed success value has passed application validation before it is returned. Check `result.ok` before reading either the typed success `payload` or bounded application `error`; a result cannot contain both. RPC call and notify accept only `JsonValue` payloads and reject values that cannot be represented on the wire before sending. TypeScript `RpcPeer.onNotify(typeId, decoder, handler)` receives peer outbound notifications through the local Session's inbound reserved RPC stream. A notification reaches the handler only after its decoder succeeds; decoder and handler failures are isolated from RPC serving.
50
-
51
- Browser and Node.js connector options accept `connectTimeoutMs`; omitting it uses the shared ten-second default.
52
-
53
- ### One-shot Node client
54
-
55
- ```ts
56
- import { RPCHandlers, connect } from "@floegence/flowersec-core/node";
57
-
58
- const rpcHandlers = new RPCHandlers();
59
- rpcHandlers.handleRPC(7, async (payload) => ({ payload }));
60
- rpcHandlers.handleNotification(8, (payload) => onNotice(payload));
61
- const session = await connect(lease, {
62
- origin: "https://app.example",
63
- rpcHandlers,
64
- });
65
- ```
66
-
67
- ### Long-lived Node client
68
-
69
- ```ts
70
- import {
71
- RPCHandlers,
72
- connectionDiagnostic,
73
- createConnectionController,
74
- } from "@floegence/flowersec-core/node";
75
-
76
- const rpcHandlers = new RPCHandlers();
77
- rpcHandlers.handleRPC(7, async (payload) => ({ payload }));
78
- const controller = createConnectionController(source, {
79
- origin: "https://app.example",
80
- rpcHandlers,
81
- });
82
- controller.start();
83
- const session = await controller.waitForSession();
84
- const unsubscribe = controller.subscribe((snapshot) => {
85
- monitor(connectionDiagnostic(snapshot));
86
- });
87
- ```
88
-
89
- The immutable callback definition applies to every generation, while each
90
- Session gets a fresh router. `waitForSession()` never starts the controller.
91
- Diagnostics contain only state, attempt, failure phase/code, and retry
92
- disposition. Terminated Session work is never replayed.
93
-
94
- ### Application streams on any Session
31
+ Parse an opaque artifact, bind its durable spend callback, and connect:
95
32
 
96
33
  ```ts
97
- import { StreamHandlers } from "@floegence/flowersec-core";
34
+ import { createArtifactLease, parseArtifact } from "@floegence/flowersec-core";
35
+ import { connect } from "@floegence/flowersec-core/node";
98
36
 
99
- const streamHandlers = new StreamHandlers({ maxConcurrentStreams: 32 });
100
- streamHandlers.handleStream("files/read", async (incoming) => serveFile(incoming));
101
- await streamHandlers.serve(session);
37
+ const artifact = parseArtifact(serializedArtifact);
38
+ const lease = createArtifactLease(artifact, persistSpendExactlyOnce);
39
+ const session = await connect(lease, { origin: "https://app.example" });
102
40
  ```
103
41
 
104
- The portable root, browser, and Node entrypoints share this dispatcher. The
105
- sealed registrar used by Node `ProxyServer.register(...)` is exported only from
106
- the Node entrypoint.
107
-
108
- For the complete durable `ArtifactLease` spend workflow, see the
109
- [TypeScript cookbook](../examples/ts/README.md). Node raw-QUIC-only artifacts
110
- may omit `origin`; providing an absolute HTTP(S) origin enables WebSocket
111
- candidates. CA candidates use platform or deployment-provided trust roots;
112
- pin candidates use only the complete active leaf-certificate SHA-256 pin set
113
- embedded in the opaque artifact. The connector never fetches pins or falls
114
- back between CA and pin modes.
115
-
116
- ### Accepted Node server Session
117
-
118
- ```ts
119
- import { SessionHandlers, createAcceptor } from "@floegence/flowersec-core/node";
120
-
121
- const handlers = new SessionHandlers({ maxConcurrentStreams: 32 });
122
- handlers.handleRPC(7, async (payload) => ({ payload }));
123
- handlers.handleNotification(8, (payload) => onNotice(payload));
124
- handlers.handleStream("files/read", async (incoming) => serveFile(incoming));
125
- const acceptor = await createAcceptor({
126
- listeners,
127
- maxInboundStreams: 32,
128
- admissionTimeoutMs: 10_000,
129
- authorize: async (request, options) => ({
130
- accepted: true,
131
- artifact: await loadAuthorizedArtifact(request, options),
132
- }),
133
- resolveHandlers: () => handlers,
134
- });
135
- const accepted = await acceptor.accept();
136
- await accepted.serve();
137
- ```
138
-
139
- `loadAuthorizedArtifact(...)` returns the opaque `Artifact` produced by
140
- `parseArtifact(...)`; authorization code never reconstructs or receives
141
- package-private PSK, candidate, or pin fields. Tunnel authorization verifies
142
- that artifact with `verifyTunnelAuthorizationGrant(...)` and returns only the
143
- request-bound, secret-free grant consumed by the relay.
144
- The admission deadline covers FSB3 receive, authorization, handler resolution,
145
- FSA3 completion, and Session establishment. Tunnel allow decisions return the
146
- secret-free grant; the trusted verifier performs the full FSB3 projection while
147
- the relay remains unable to inspect the artifact or its E2EE session fields.
148
-
149
- `RPCHandlers` is available only from the Node entrypoint and cannot register
150
- application streams. The default `SessionHandlers` is strict v3 and
151
- accepted-server-only; the v2 registry remains under `node.v2.SessionHandlers`.
152
-
153
- ## Connection Lifecycle
154
-
155
- The Browser and Node `connect(...)` operations are one-shot and never reconnect. Long-lived applications can create the runtime-specific `ConnectionController` with a refreshable `ArtifactSource`. Every attempt must return a fresh `ArtifactLease`; a one-time artifact or lease is not a controller source.
156
-
157
- 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.
158
-
159
- `StreamHandlers` and Node `SessionHandlers` accept application stream kinds containing 1 through 128 canonical UTF-8 bytes, reject leading or trailing Unicode whitespace, controls, and unassigned scalars, and reserve the package-owned `flowersec.rpc.v2` and `flowersec.rpc.v3` names for Flowersec RPC. Successful handlers half-close their stream. A rejected handler Promise resets only that stream; the accept loop and unrelated streams continue.
42
+ `Artifact` hides credentials and candidate selection. `ArtifactLease` exposes no
43
+ public spend method. `Session` exposes RPC, streams, unreliable messages when
44
+ negotiated, liveness, rekeying, termination, and close without revealing its
45
+ carrier.
160
46
 
161
- Reliable streams apply bounded per-stream receive backpressure instead of buffering application data without limit. A slow consumer pauses carrier progress until reads release capacity; records retain carrier order, so a rekey behind backpressured DATA completes after the consumer resumes. `closeWrite()` sends the graceful FIN and keeps reads available. `reset()` and `close()` abort both directions. If a write is canceled or fails after its wire commit may have started, only that stream becomes terminal and cannot be reused.
47
+ `StreamHandlers` serves bounded application handlers on any Session.
48
+ `ConnectionController` is the only reconnect scheduler and obtains a fresh
49
+ lease for each attempt. Work from a terminated Session is never migrated or
50
+ replayed.
162
51
 
163
- 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.
52
+ ## Node Servers and ProxyServer
164
53
 
165
- A newly established session replaces `currentSession` atomically. The controller never migrates or replays streams, RPC calls, or writes from a terminated session; callers start new application operations on the new session.
54
+ `createAcceptor(...)` accepts direct application Sessions. `SessionHandlers`
55
+ binds accepted RPC, notification, and stream handlers before establishment.
56
+ `createTunnelRuntime(...)` pairs and forwards opaque relay legs without
57
+ terminating the end-to-end Session.
166
58
 
167
- 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.
59
+ `ProxyServer` registers the bounded HTTP and WebSocket application protocol on
60
+ `StreamHandlers` or `SessionHandlers`. It enforces fixed upstream hosts,
61
+ origins, header and cookie policy, body/frame limits, timeouts, cancellation,
62
+ and a close barrier. The `/proxy` browser runtime uses the same application
63
+ wire through a Service Worker or exact-origin window bridge.
168
64
 
169
65
  ## Supported Connections
170
66
 
171
- Browsers support WebSocket. Browser WebTransport is capability-dependent on
172
- the browser's WebTransport API and is an optional browser adapter, not a
173
- required native-server carrier. Through
174
- `/node`, Node.js supports WebSocket and raw QUIC client connections, direct
175
- server sessions, and opaque `TunnelRuntime` relay legs. Raw QUIC uses the
176
- Flowersec-owned optional native addon wrapper and one of its supported
177
- prebuilt platform packages; it never loads from the browser entrypoint. The
178
- wrapper selects the matching optional package for macOS arm64/x64 or Linux
179
- arm64/x64 glibc. Windows and musl packages are not published. The V3 Node
180
- entrypoint keeps the legacy control plane and `ProxyServer` behind `node.v2`; it
181
- does not issue v3 invitations, which are produced by the Go control-plane. The
182
- relay never terminates an E2EE Session. WebTransport is an optional adapter
183
- profile and the Node.js runtime
184
- does not currently expose a production adapter.
185
- The `/proxy` entrypoint adds browser bridges for applications that need to keep
186
- the session behind a Service Worker or another window.
187
-
188
- ## Opaque Boundaries
189
-
190
- `Artifact` is an opaque handle. Applications cannot inspect its connection data or serialize it back to protocol JSON. `ArtifactLease` exposes no spend operation; only the connector may invoke the durable callback. `Session` exposes RPC, stream operations, liveness, rekeying, `waitTermination()`, and closure without revealing the selected transport or peer endpoint identity. Public streams expose their kind and terminal state, but no protocol stream identifier.
191
-
192
- A Node tunnel authorizer returns an allow decision only after calling
193
- `verifyTunnelAuthorizationGrant(request, artifact, { leaseId, allowReplacement })`.
194
- The verifier compares the complete observed FSB3 with the opaque authorization
195
- artifact and mints a request-bound, secret-free `TunnelAuthorizationGrant`.
196
- The relay runtime retains only that grant; a structurally similar ordinary
197
- object is not an authorization and the relay never unwraps or retains the
198
- artifact's E2EE key material.
199
-
200
- `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.
201
-
202
- Connection negotiation and cryptographic state are not package exports.
203
-
204
- 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.
205
-
206
- 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.
207
-
208
- ## Connection Notes
209
-
210
- Browser applications receive a ready `Session` from `connect(...)`. The browser
211
- connector supports CA-authenticated WSS and pinned WebTransport when the
212
- browser exposes that API. WebTransport uses browser-owned HTTP/3 streams and
213
- is not available in the Node entrypoint. Production v3 accepts no plaintext
214
- carrier.
215
-
216
- Chromium does not support a WebTransport pooling option; each carrier creates an independent native WebTransport connection.
67
+ Browsers support WSS and optional browser-owned WebTransport. Node.js supports
68
+ WSS and raw QUIC client, direct-server, and tunnel-runtime roles. Raw QUIC uses
69
+ the optional Flowersec native package for macOS or glibc Linux on arm64/x64.
70
+ Node.js does not expose WebTransport or an artifact issuer; use an application
71
+ control plane such as the Go control-plane package.
217
72
 
218
- 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.
73
+ CA candidates use platform or configured private roots. Pin candidates verify
74
+ only the complete artifact-bound active pin set and never fall back to CA.
75
+ Public errors remain closed and redacted.
219
76
 
220
- Node.js applications receive the same `Session` contract from `connect(...)`.
221
- The Node connector supports WSS and raw QUIC through the optional native
222
- package. WebSocket candidates require an absolute HTTP(S) `origin`;
223
- raw-QUIC-only artifacts may omit it. CA candidates use platform or configured
224
- private roots. Pin candidates use their artifact-bound pin set and never
225
- downgrade to CA after a verification failure.
77
+ ## CLI
226
78
 
227
- The connectors choose an eligible connection path from the invitation. They do
228
- not expose transport selectors, candidate lists, or native carrier objects to application code.
79
+ The package installs `flowersec-ts-cli`. Its client and server commands accept
80
+ only Transport v3 artifacts. The server requires a TLS certificate and private
81
+ key for its WebSocket listener.
229
82
 
230
83
  ## Verify
231
84
 
@@ -235,4 +88,8 @@ npm test
235
88
  npm run verify:package
236
89
  ```
237
90
 
238
- See the [API contract](../docs/API_CONTRACT.md), [Transport v3 architecture](../docs/TRANSPORT_V3_ARCHITECTURE.md), [v3 wire contract](../docs/TRANSPORT_V3_WIRE.md), [threat model](../docs/THREAT_MODEL.md), and [error model](../docs/ERROR_MODEL.md).
91
+ See the [TypeScript cookbook](../examples/ts/README.md),
92
+ [API contract](../docs/API_CONTRACT.md),
93
+ [Transport v3 architecture](../docs/TRANSPORT_V3_ARCHITECTURE.md),
94
+ [wire contract](../docs/TRANSPORT_V3_WIRE.md), and
95
+ [error model](../docs/ERROR_MODEL.md).
@@ -3,17 +3,17 @@
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
- - @floegence/flowersec-node-native 3.2.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
7
- - @floegence/flowersec-node-native-darwin-arm64 3.2.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
8
- - @floegence/flowersec-node-native-darwin-x64 3.2.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
9
- - @floegence/flowersec-node-native-linux-arm64-gnu 3.2.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
10
- - @floegence/flowersec-node-native-linux-x64-gnu 3.2.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
11
- - @noble/ciphers 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz)
12
- - @noble/curves 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz)
13
- - @noble/hashes 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz)
6
+ - @floegence/flowersec-node-native 4.0.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
7
+ - @floegence/flowersec-node-native-darwin-arm64 4.0.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
8
+ - @floegence/flowersec-node-native-darwin-x64 4.0.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
9
+ - @floegence/flowersec-node-native-linux-arm64-gnu 4.0.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
10
+ - @floegence/flowersec-node-native-linux-x64-gnu 4.0.0 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
11
+ - @noble/ciphers 2.4.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz)
12
+ - @noble/curves 2.4.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz)
13
+ - @noble/hashes 2.4.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz)
14
14
  - punycode 2.3.1 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz)
15
- - tr46 5.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz)
16
- - ws 8.21.3 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ws/-/ws-8.21.3.tgz)
15
+ - tr46 6.0.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz)
16
+ - ws 8.21.2 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/ws/-/ws-8.21.2.tgz)
17
17
  ## Distributed Dependency License Materials
18
18
 
19
19
  The following dependencies are incorporated into distributed Flowersec files or binaries.
@@ -44,7 +44,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
44
44
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45
45
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46
46
 
47
- ### tr46 5.0.0
47
+ ### tr46 6.0.0
48
48
 
49
49
  #### Reviewed bundled license text
50
50
 
@@ -1,11 +1,6 @@
1
1
  export * from "../facade.js";
2
- export * as v2 from "./v2.js";
3
2
  export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, connectPrivateLoopbackV1, createPrivateLoopbackConnectionControllerV1, } from "./connectSessionV3.js";
4
3
  export { PRIVATE_LOOPBACK_PROFILE_V1, PrivateLoopbackArtifactErrorV1, createPrivateLoopbackArtifactLeaseV1, parsePrivateLoopbackArtifactV1, } from "./privateLoopbackV1.js";
5
- /** @deprecated Use connect and createConnectionController. */
6
- export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
7
4
  export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, SessionOptionsV3 as SessionOptions, } from "./connectSessionV3.js";
8
5
  export type { PrivateLoopbackConnectionControllerOptionsV1, PrivateLoopbackSessionOptionsV1, } from "./connectSessionV3.js";
9
6
  export type { PrivateLoopbackArtifactLeaseV1, PrivateLoopbackArtifactSourceResultV1, PrivateLoopbackArtifactSourceV1, PrivateLoopbackArtifactV1, } from "./privateLoopbackV1.js";
10
- /** @deprecated Use ConnectionControllerOptions and SessionOptions. */
11
- export type { ConnectionControllerOptionsV3, SessionOptionsV3 } from "./connectSessionV3.js";
@@ -1,6 +1,3 @@
1
1
  export * from "../facade.js";
2
- export * as v2 from "./v2.js";
3
2
  export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, connectPrivateLoopbackV1, createPrivateLoopbackConnectionControllerV1, } from "./connectSessionV3.js";
4
3
  export { PRIVATE_LOOPBACK_PROFILE_V1, PrivateLoopbackArtifactErrorV1, createPrivateLoopbackArtifactLeaseV1, parsePrivateLoopbackArtifactV1, } from "./privateLoopbackV1.js";
5
- /** @deprecated Use connect and createConnectionController. */
6
- export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
package/dist/cli.js CHANGED
@@ -1,126 +1,155 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
- import { acceptNativeSessionV2 } from "./connector/sessionAcceptor.js";
4
3
  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";
8
- import { createArtifactLeaseV2 } from "./v2/artifactLease.js";
9
- import { buildFSB2RequestV2, encodeFSB2RequestV2 } from "./v2/artifact.js";
10
- import { parseArtifact, unwrapArtifact } from "./v2/opaqueArtifact.js";
11
- const args = parseArguments(process.argv.slice(2));
12
- if (args.mode === "client")
13
- await runClient(args.values);
14
- else
15
- await runServer(args.values);
4
+ import { createArtifactLease, parseArtifact } from "./facade.js";
5
+ import { connect, createAcceptor, } from "./node/index.js";
6
+ await main();
7
+ async function main() {
8
+ try {
9
+ const args = parseArguments(process.argv.slice(2));
10
+ if (args.mode === "client")
11
+ await runClient(args.values);
12
+ else
13
+ await runServer(args.values);
14
+ }
15
+ catch (error) {
16
+ const code = errorCode(error);
17
+ process.stderr.write(`${code}\n`);
18
+ process.exitCode = 1;
19
+ }
20
+ }
16
21
  async function runClient(values) {
17
22
  requireWebSocket(values);
18
23
  const artifact = parseArtifact(readFileSync(required(values, "artifact")));
19
- const lease = createArtifactLeaseV2(artifact, async () => claimSpendMarker(required(values, "spend-marker")));
24
+ const lease = createArtifactLease(artifact, async () => claimSpendMarker(required(values, "spend-marker")));
20
25
  let session;
21
26
  try {
22
27
  session = await connect(lease, {
23
28
  origin: required(values, "origin"),
24
- ...(values.ca === undefined ? {} : { tls: { ca: readFileSync(values.ca, "utf8") } }),
29
+ ...(values.ca === undefined
30
+ ? {}
31
+ : { roots: readFileSync(values.ca, "utf8") }),
25
32
  });
26
33
  const stream = await session.openStream("cli");
27
34
  await stream.write(new TextEncoder().encode("flowersec-ts-cli"));
28
35
  await stream.closeWrite();
29
36
  const response = await stream.read();
30
37
  if (response === null || new TextDecoder().decode(response) !== "flowersec-ts-cli") {
31
- throw new Error("CLI server returned an invalid stream response");
38
+ throw new Error("invalid_response");
32
39
  }
33
40
  process.stdout.write("GREEN\n");
34
41
  }
35
42
  finally {
36
- await session?.close();
43
+ await session?.close().catch(() => undefined);
37
44
  }
38
45
  }
39
46
  async function runServer(values) {
40
47
  requireWebSocket(values);
41
- const artifact = unwrapArtifact(parseArtifact(readFileSync(required(values, "artifact"))));
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({
50
- host: values.host ?? "127.0.0.1",
51
- port: Number.parseInt(required(values, "port"), 10),
52
- path: "direct",
53
- allowedOrigins: [required(values, "origin")],
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
- }),
48
+ const artifact = parseArtifact(readFileSync(required(values, "artifact")));
49
+ const certificate = readFileSync(required(values, "certificate"), "utf8");
50
+ const privateKey = readFileSync(required(values, "private-key"), "utf8");
51
+ const acceptor = await createAcceptor({
52
+ listeners: [{
53
+ carrier: "websocket",
54
+ path: "direct",
55
+ host: values.host ?? "127.0.0.1",
56
+ port: parsePort(required(values, "port")),
57
+ tls: { certificate, privateKey },
58
+ allowedOrigins: [required(values, "origin")],
59
+ }],
60
+ maxInboundStreams: parseMaxInboundStreams(required(values, "max-inbound-streams")),
61
+ authorize: async () => ({ accepted: true, artifact }),
61
62
  });
62
- process.stdout.write(JSON.stringify(server.address()) + "\n");
63
+ process.stdout.write(`${JSON.stringify(acceptor.addresses()[0])}\n`);
63
64
  const abort = new AbortController();
64
- const stop = () => abort.abort(new Error("CLI server stopped"));
65
+ let stopping = false;
66
+ let accepted;
67
+ const stop = () => {
68
+ stopping = true;
69
+ abort.abort(new Error("canceled"));
70
+ void accepted?.close().catch(() => undefined);
71
+ };
65
72
  process.once("SIGINT", stop);
66
73
  process.once("SIGTERM", stop);
67
74
  try {
68
- const session = await acceptNativeSessionV2(await server.accept({ signal: abort.signal }), async (request) => {
69
- const chosen = artifact.path.candidates.find(({ id }) => id === request.request.chosen_candidate_id);
70
- if (request.request.pathKind !== "direct" || chosen?.carrier !== "websocket") {
71
- throw new Error("CLI server rejected an unexpected artifact candidate");
72
- }
73
- const expected = encodeFSB2RequestV2(buildFSB2RequestV2(artifact, request.request.chosen_candidate_id));
74
- if (!bytesEqual(expected, request.raw))
75
- throw new Error("CLI server rejected an unbound artifact");
76
- return { accepted: true, artifact };
77
- }, { runtime: nodeSessionRuntimeV2, signal: abort.signal });
78
- await echoOnce(session);
79
- await session.close();
75
+ accepted = await acceptor.accept({ signal: abort.signal });
76
+ await echoOnce(accepted.session);
77
+ }
78
+ catch (error) {
79
+ if (!stopping)
80
+ throw error;
80
81
  }
81
82
  finally {
82
83
  process.removeListener("SIGINT", stop);
83
84
  process.removeListener("SIGTERM", stop);
84
- await server.close();
85
+ await accepted?.close().catch(() => undefined);
86
+ await acceptor.close().catch(() => undefined);
85
87
  }
86
88
  }
87
89
  async function echoOnce(session) {
88
90
  const incoming = await session.acceptStream();
91
+ if (incoming.kind !== "cli")
92
+ throw new Error("invalid_stream_kind");
89
93
  const payload = await incoming.stream.read();
90
94
  if (payload === null)
91
- throw new Error("CLI stream closed before payload");
95
+ throw new Error("missing_payload");
92
96
  await incoming.stream.write(payload);
93
97
  await incoming.stream.closeWrite();
94
98
  }
95
99
  function parseArguments(raw) {
96
100
  const [mode, ...rest] = raw;
97
- if (mode !== "client" && mode !== "server")
101
+ if (mode !== "client" && mode !== "server") {
98
102
  throw new Error("usage: flowersec-ts-cli <client|server> options");
103
+ }
99
104
  const values = {};
100
105
  for (let index = 0; index < rest.length; index++) {
101
106
  const token = rest[index];
102
107
  if (!token.startsWith("--") || index + 1 >= rest.length || rest[index + 1].startsWith("--")) {
103
- throw new Error(`invalid CLI option ${token}`);
108
+ throw new Error(`invalid_option:${token}`);
104
109
  }
105
- values[token.slice(2)] = rest[++index];
110
+ const name = token.slice(2);
111
+ if (Object.hasOwn(values, name))
112
+ throw new Error(`duplicate_option:${token}`);
113
+ values[name] = rest[++index];
106
114
  }
107
115
  return { mode, values };
108
116
  }
109
117
  function required(values, name) {
110
118
  const value = values[name];
111
119
  if (value === undefined || value === "")
112
- throw new Error(`missing --${name}`);
120
+ throw new Error(`missing_option:${name}`);
113
121
  return value;
114
122
  }
115
123
  function requireWebSocket(values) {
116
124
  if (values.transport !== "websocket")
117
- throw new Error("--transport websocket is required");
125
+ throw new Error("websocket_required");
126
+ }
127
+ function parsePort(value) {
128
+ if (!/^\d{1,5}$/u.test(value))
129
+ throw new Error("invalid_port");
130
+ const port = Number(value);
131
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) {
132
+ throw new Error("invalid_port");
133
+ }
134
+ return port;
118
135
  }
119
- function bytesEqual(left, right) {
120
- if (left.length !== right.length)
121
- return false;
122
- let different = 0;
123
- for (let index = 0; index < left.length; index++)
124
- different |= left[index] ^ right[index];
125
- return different === 0;
136
+ function parseMaxInboundStreams(value) {
137
+ if (!/^\d{1,3}$/u.test(value))
138
+ throw new Error("invalid_max_inbound_streams");
139
+ const maximum = Number(value);
140
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 128) {
141
+ throw new Error("invalid_max_inbound_streams");
142
+ }
143
+ return maximum;
144
+ }
145
+ function errorCode(error) {
146
+ if (typeof error === "object" && error !== null && "code" in error) {
147
+ const code = error.code;
148
+ if (typeof code === "string" && /^[a-z][a-z0-9_]*$/u.test(code))
149
+ return code;
150
+ }
151
+ if (error instanceof Error && /^[a-z][a-z0-9_:<> -]*$/u.test(error.message)) {
152
+ return error.message;
153
+ }
154
+ return "operation_failed";
126
155
  }
package/dist/facade.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Session as PublicSession } from "./public/contract.js";
2
2
  import type { ConnectionControllerSnapshotV3 as CoreConnectionControllerSnapshotV3, ConnectionControllerV3 as CoreConnectionControllerV3 } from "./v3/connectionController.js";
3
- export * as v2 from "./v2/index.js";
4
3
  export type { ByteStream, IncomingStream, JsonObject, JsonPrimitive, JsonValue, OperationOptions, RpcPeer, RpcResult, SessionErrorCode, UnreliableMessageErrorCode, StreamOpenOptions, UnreliableMessageChannel, UnreliableMessageSendOptions, UnreliableMessageSendResult, SessionTermination, Session, } from "./public/contract.js";
5
4
  export { SessionError, UnreliableMessageError } from "./public/contract.js";
6
5
  export { createStreamMetadata, StreamMetadataError } from "./public/streamMetadata.js";
@@ -9,30 +8,14 @@ export { HandlerRegistrationError, StreamHandlers, } from "./public/streamHandle
9
8
  export type { StreamHandler, StreamHandlerOptions, } from "./public/streamHandlers.js";
10
9
  export { ArtifactHandleV3 as Artifact, ArtifactParseErrorV3 as ArtifactError, createArtifactLeaseV3 as createArtifactLease, parseArtifactV3 as parseArtifact, } from "./v3/publicApi.js";
11
10
  export type { ArtifactParseErrorCodeV3 as ArtifactErrorCode, } from "./v3/publicApi.js";
12
- /** @deprecated Use Artifact, ArtifactError, createArtifactLease, and parseArtifact. */
13
- export { ArtifactHandleV3, ArtifactParseErrorV3, createArtifactLeaseV3, parseArtifactV3, } from "./v3/publicApi.js";
14
- /** @deprecated Use ArtifactErrorCode. */
15
- export type { ArtifactParseErrorCodeV3 } from "./v3/publicApi.js";
16
11
  export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3Error as ArtifactLeaseError, } from "./v3/artifactLease.js";
17
- /** @deprecated Use ArtifactLease and ArtifactLeaseError. */
18
- export { ArtifactLeaseV3, ArtifactLeaseV3Error } from "./v3/artifactLease.js";
19
12
  export type { ArtifactSourceResultV3 as ArtifactSourceResult, ArtifactSourceV3 as ArtifactSource, ConnectionDiagnosticV3 as ConnectionDiagnostic, ConnectionControllerFailureV3 as ConnectionControllerFailure, ConnectionControllerStateV3 as ConnectionState, } from "./v3/connectionController.js";
20
- /** @deprecated Use the corresponding unversioned controller types. */
21
- export type { ArtifactSourceResultV3, ArtifactSourceV3, ConnectionDiagnosticV3, ConnectionControllerFailureV3, ConnectionControllerSnapshotV3, ConnectionControllerStateV3, ConnectionControllerV3, } from "./v3/connectionController.js";
22
13
  export { connectionDiagnosticV3 as connectionDiagnostic } from "./v3/connectionController.js";
23
- /** @deprecated Use connectionDiagnostic. */
24
- export { connectionDiagnosticV3 } from "./v3/connectionController.js";
25
14
  export type ConnectionController = CoreConnectionControllerV3<PublicSession>;
26
15
  export type ConnectionSnapshot = CoreConnectionControllerSnapshotV3<PublicSession>;
27
16
  export type ConnectionControllerOptions = Readonly<{
28
17
  maximumAttempts?: number;
29
18
  }>;
30
19
  export { ConnectionControllerV3Error as ConnectionControllerError, } from "./v3/connectionController.js";
31
- /** @deprecated Use ConnectionControllerError. */
32
- export { ConnectionControllerV3Error } from "./v3/connectionController.js";
33
20
  export { ConnectErrorV3 as ConnectError } from "./v3/security.js";
34
- /** @deprecated Use ConnectError. */
35
- export { ConnectErrorV3 } from "./v3/security.js";
36
21
  export type { PublicConnectErrorCodeV3 as ConnectErrorCode, RetryDispositionV3 as RetryDisposition, } from "./v3/security.js";
37
- /** @deprecated Use ConnectErrorCode and RetryDisposition. */
38
- export type { PublicConnectErrorCodeV3, RetryDispositionV3 } from "./v3/security.js";