@floegence/flowersec-core 2.5.3 → 3.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 (119) hide show
  1. package/README.md +46 -18
  2. package/dist/browser/connectSessionV3.d.ts +13 -0
  3. package/dist/browser/connectSessionV3.js +75 -0
  4. package/dist/browser/index.d.ts +3 -2
  5. package/dist/browser/index.js +2 -1
  6. package/dist/browser/v2.d.ts +3 -0
  7. package/dist/browser/v2.js +2 -0
  8. package/dist/facade.d.ts +15 -8
  9. package/dist/facade.js +5 -4
  10. package/dist/framing/jsonframe.d.ts +1 -1
  11. package/dist/framing/jsonframe.js +3 -1
  12. package/dist/interop/proxyServerPeer.js +1 -1
  13. package/dist/interop/serverParityPeer.js +8 -1
  14. package/dist/node/acceptor.d.ts +10 -0
  15. package/dist/node/acceptor.js +53 -7
  16. package/dist/node/acceptorV3.d.ts +57 -0
  17. package/dist/node/acceptorV3.js +227 -0
  18. package/dist/node/connectSessionV3.d.ts +21 -0
  19. package/dist/node/connectSessionV3.js +73 -0
  20. package/dist/node/index.d.ts +15 -10
  21. package/dist/node/index.js +6 -5
  22. package/dist/node/nativeTransportAddon.d.ts +39 -1
  23. package/dist/node/nativeTransportAddon.js +29 -5
  24. package/dist/node/rawQuicAdapterV3.d.ts +10 -0
  25. package/dist/node/rawQuicAdapterV3.js +76 -0
  26. package/dist/node/rawQuicServerV3.d.ts +24 -0
  27. package/dist/node/rawQuicServerV3.js +34 -0
  28. package/dist/node/runtimeAuthorizationV3.d.ts +29 -0
  29. package/dist/node/runtimeAuthorizationV3.js +126 -0
  30. package/dist/node/tunnelRuntimeV3.d.ts +57 -0
  31. package/dist/node/tunnelRuntimeV3.js +857 -0
  32. package/dist/node/v2.d.ts +11 -0
  33. package/dist/node/v2.js +6 -0
  34. package/dist/node/webSocketServerV3.d.ts +27 -0
  35. package/dist/node/webSocketServerV3.js +219 -0
  36. package/dist/proxy/runtime.js +39 -6
  37. package/dist/proxy/serviceWorker.d.ts +2 -0
  38. package/dist/proxy/serviceWorker.js +63 -7
  39. package/dist/proxy/windowBridge.d.ts +1 -0
  40. package/dist/proxy/windowBridge.js +93 -33
  41. package/dist/proxy/wsPatch.d.ts +1 -0
  42. package/dist/proxy/wsPatch.js +39 -6
  43. package/dist/public/streamHandlers.d.ts +3 -0
  44. package/dist/public/streamHandlers.js +6 -1
  45. package/dist/rpc/client.js +9 -7
  46. package/dist/rpc/server.js +44 -20
  47. package/dist/rpc/validate.d.ts +1 -0
  48. package/dist/rpc/validate.js +18 -6
  49. package/dist/v2/index.d.ts +14 -0
  50. package/dist/v2/index.js +7 -0
  51. package/dist/v2/protocol.js +23 -32
  52. package/dist/v2/publicSession.js +4 -0
  53. package/dist/v2/session.d.ts +5 -0
  54. package/dist/v2/session.js +94 -35
  55. package/dist/v3/admissionError.d.ts +4 -0
  56. package/dist/v3/admissionError.js +8 -0
  57. package/dist/v3/artifact.d.ts +145 -0
  58. package/dist/v3/artifact.js +1162 -0
  59. package/dist/v3/artifactLease.d.ts +17 -0
  60. package/dist/v3/artifactLease.js +109 -0
  61. package/dist/v3/browserRuntime.d.ts +37 -0
  62. package/dist/v3/browserRuntime.js +197 -0
  63. package/dist/v3/browserSessionRuntime.d.ts +2 -0
  64. package/dist/v3/browserSessionRuntime.js +21 -0
  65. package/dist/v3/capability.d.ts +32 -0
  66. package/dist/v3/capability.js +345 -0
  67. package/dist/v3/carrier.d.ts +84 -0
  68. package/dist/v3/carrier.js +411 -0
  69. package/dist/v3/connectionController.d.ts +87 -0
  70. package/dist/v3/connectionController.js +805 -0
  71. package/dist/v3/contract.d.ts +65 -0
  72. package/dist/v3/contract.js +3 -0
  73. package/dist/v3/controller.d.ts +43 -0
  74. package/dist/v3/controller.js +303 -0
  75. package/dist/v3/handshake.d.ts +81 -0
  76. package/dist/v3/handshake.js +471 -0
  77. package/dist/v3/jcs.d.ts +6 -0
  78. package/dist/v3/jcs.js +69 -0
  79. package/dist/v3/jsonPreflight.d.ts +1 -0
  80. package/dist/v3/jsonPreflight.js +251 -0
  81. package/dist/v3/nodeRuntime.d.ts +40 -0
  82. package/dist/v3/nodeRuntime.js +315 -0
  83. package/dist/v3/nodeSessionRuntime.d.ts +2 -0
  84. package/dist/v3/nodeSessionRuntime.js +13 -0
  85. package/dist/v3/protocol.d.ts +99 -0
  86. package/dist/v3/protocol.js +773 -0
  87. package/dist/v3/publicApi.d.ts +12 -0
  88. package/dist/v3/publicApi.js +46 -0
  89. package/dist/v3/publicSession.d.ts +1 -0
  90. package/dist/v3/publicSession.js +281 -0
  91. package/dist/v3/retryDisposition.d.ts +16 -0
  92. package/dist/v3/retryDisposition.js +30 -0
  93. package/dist/v3/runtimeAdapters.d.ts +9 -0
  94. package/dist/v3/runtimeAdapters.js +109 -0
  95. package/dist/v3/security.d.ts +34 -0
  96. package/dist/v3/security.js +100 -0
  97. package/dist/v3/serverAdmission.d.ts +38 -0
  98. package/dist/v3/serverAdmission.js +223 -0
  99. package/dist/v3/session.d.ts +318 -0
  100. package/dist/v3/session.js +2289 -0
  101. package/dist/v3/sessionConnector.d.ts +37 -0
  102. package/dist/v3/sessionConnector.js +440 -0
  103. package/dist/v3/streamLifetimeLedger.d.ts +31 -0
  104. package/dist/v3/streamLifetimeLedger.js +131 -0
  105. package/dist/v3/streamMetadata.d.ts +1 -0
  106. package/dist/v3/streamMetadata.js +7 -0
  107. package/dist/v3/transportConstants.d.ts +56 -0
  108. package/dist/v3/transportConstants.js +59 -0
  109. package/dist/v3/unreliableMessage.d.ts +44 -0
  110. package/dist/v3/unreliableMessage.js +284 -0
  111. package/dist/v3/webSocketCarrier.d.ts +31 -0
  112. package/dist/v3/webSocketCarrier.js +370 -0
  113. package/dist/v3/webTransportCarrier.d.ts +27 -0
  114. package/dist/v3/webTransportCarrier.js +534 -0
  115. package/dist/yamux/stream.d.ts +3 -0
  116. package/dist/yamux/stream.js +17 -4
  117. package/package.json +4 -4
  118. package/sbom/cyclonedx.json +6 -6
  119. package/sbom/spdx.json +11 -11
package/README.md CHANGED
@@ -15,7 +15,7 @@ npm install @floegence/flowersec-core
15
15
 
16
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
17
  - `@floegence/flowersec-core/browser` adds `connect(...)`, `createConnectionController(...)`, and their options.
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
+ - `@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.
19
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.
20
20
 
21
21
  The root type exports are:
@@ -31,7 +31,7 @@ Retry ownership belongs to `ConnectionController`; applications do not classify
31
31
 
32
32
  `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.
33
33
 
34
- When connector options omit a connection timeout, browser and Node.js connectors use the shared ten-second default.
34
+ Browser and Node.js connector options accept `connectTimeoutMs`; omitting it uses the shared ten-second default.
35
35
 
36
36
  ### One-shot Node client
37
37
 
@@ -82,7 +82,10 @@ the Node entrypoint.
82
82
  For the complete durable `ArtifactLease` spend workflow, see the
83
83
  [TypeScript cookbook](../examples/ts/README.md). Node raw-QUIC-only artifacts
84
84
  may omit `origin`; providing an absolute HTTP(S) origin enables WebSocket
85
- candidates. Secure raw QUIC still requires an explicit `tls.ca` trust root.
85
+ candidates. CA candidates use platform or deployment-provided trust roots;
86
+ pin candidates use only the complete active leaf-certificate SHA-256 pin set
87
+ embedded in the opaque artifact. The connector never fetches pins or falls
88
+ back between CA and pin modes.
86
89
 
87
90
  ### Accepted Node server Session
88
91
 
@@ -96,15 +99,30 @@ handlers.handleStream("files/read", async (incoming) => serveFile(incoming));
96
99
  const acceptor = await createAcceptor({
97
100
  listeners,
98
101
  maxInboundStreams: 32,
99
- authorize,
102
+ admissionTimeoutMs: 10_000,
103
+ authorize: async (request, options) => ({
104
+ accepted: true,
105
+ artifact: await loadAuthorizedArtifact(request, options),
106
+ }),
100
107
  resolveHandlers: () => handlers,
101
108
  });
102
109
  const accepted = await acceptor.accept();
103
110
  await accepted.serve();
104
111
  ```
105
112
 
113
+ `loadAuthorizedArtifact(...)` returns the opaque `Artifact` produced by
114
+ `parseArtifact(...)`; authorization code never reconstructs or receives
115
+ package-private PSK, candidate, or pin fields. Tunnel authorization verifies
116
+ that artifact with `verifyTunnelAuthorizationGrant(...)` and returns only the
117
+ request-bound, secret-free grant consumed by the relay.
118
+ The admission deadline covers FSB3 receive, authorization, handler resolution,
119
+ FSA3 completion, and Session establishment. Tunnel allow decisions return the
120
+ secret-free grant; the trusted verifier performs the full FSB3 projection while
121
+ the relay remains unable to inspect the artifact or its E2EE session fields.
122
+
106
123
  `RPCHandlers` is available only from the Node entrypoint and cannot register
107
- application streams. `SessionHandlers` is accepted-server-only.
124
+ application streams. The default `SessionHandlers` is strict v3 and
125
+ accepted-server-only; the v2 registry remains under `node.v2.SessionHandlers`.
108
126
 
109
127
  ## Connection Lifecycle
110
128
 
@@ -112,7 +130,7 @@ The Browser and Node `connect(...)` operations are one-shot and never reconnect.
112
130
 
113
131
  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.
114
132
 
115
- `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 `flowersec.rpc.v2` for Flowersec RPC. Successful handlers half-close their stream. A rejected handler Promise resets only that stream; the accept loop and unrelated streams continue.
133
+ `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.
116
134
 
117
135
  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.
118
136
 
@@ -132,9 +150,10 @@ server sessions, and opaque `TunnelRuntime` relay legs. Raw QUIC uses the
132
150
  Flowersec-owned optional native addon wrapper and one of its supported
133
151
  prebuilt platform packages; it never loads from the browser entrypoint. The
134
152
  wrapper selects the matching optional package for macOS arm64/x64 or Linux
135
- arm64/x64 glibc. Windows and musl packages are not published. The same Node entrypoint provides the
136
- control plane and `ProxyServer`, and the relay never terminates an E2EE
137
- Session. WebTransport is an optional adapter profile and the Node.js runtime
153
+ arm64/x64 glibc. Windows and musl packages are not published. The V3 Node
154
+ entrypoint keeps the control plane and `ProxyServer` behind `node.v2`; the
155
+ relay never terminates an E2EE Session. WebTransport is an optional adapter
156
+ profile and the Node.js runtime
138
157
  does not currently expose a production adapter.
139
158
  The `/proxy` entrypoint adds browser bridges for applications that need to keep
140
159
  the session behind a Service Worker or another window.
@@ -143,6 +162,14 @@ the session behind a Service Worker or another window.
143
162
 
144
163
  `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.
145
164
 
165
+ A Node tunnel authorizer returns an allow decision only after calling
166
+ `verifyTunnelAuthorizationGrant(request, artifact, { leaseId, allowReplacement })`.
167
+ The verifier compares the complete observed FSB3 with the opaque authorization
168
+ artifact and mints a request-bound, secret-free `TunnelAuthorizationGrant`.
169
+ The relay runtime retains only that grant; a structurally similar ordinary
170
+ object is not an authorization and the relay never unwraps or retains the
171
+ artifact's E2EE key material.
172
+
146
173
  `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.
147
174
 
148
175
  Connection negotiation and cryptographic state are not package exports.
@@ -154,20 +181,21 @@ The proxy entrypoint accepts an opaque `ArtifactLease` or an already connected `
154
181
  ## Connection Notes
155
182
 
156
183
  Browser applications receive a ready `Session` from `connect(...)`. The browser
157
- connector supports WSS, restricted plaintext loopback WebSocket direct
158
- connections, and WebTransport when the browser exposes that API. WebTransport
159
- uses browser-owned HTTP/3 streams and is not available in the Node entrypoint.
184
+ connector supports CA-authenticated WSS and pinned WebTransport when the
185
+ browser exposes that API. WebTransport uses browser-owned HTTP/3 streams and
186
+ is not available in the Node entrypoint. Production v3 accepts no plaintext
187
+ carrier.
160
188
 
161
189
  Chromium does not support a WebTransport pooling option; each carrier creates an independent native WebTransport connection.
162
190
 
163
191
  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.
164
192
 
165
193
  Node.js applications receive the same `Session` contract from `connect(...)`.
166
- The Node connector supports WSS, restricted plaintext loopback WebSocket
167
- direct connections, and raw QUIC through the optional native package. WebSocket
168
- candidates require an absolute HTTP(S) `origin`; raw-QUIC-only artifacts may
169
- omit it. Custom certificate authorities can be supplied through `tls.ca`, and
170
- secure raw QUIC requires an explicit trust root.
194
+ The Node connector supports WSS and raw QUIC through the optional native
195
+ package. WebSocket candidates require an absolute HTTP(S) `origin`;
196
+ raw-QUIC-only artifacts may omit it. CA candidates use platform or configured
197
+ private roots. Pin candidates use their artifact-bound pin set and never
198
+ downgrade to CA after a verification failure.
171
199
 
172
200
  The connectors choose an eligible connection path from the invitation. They do
173
201
  not expose transport selectors, candidate lists, or native carrier objects to application code.
@@ -180,4 +208,4 @@ npm test
180
208
  npm run verify:package
181
209
  ```
182
210
 
183
- See the [API contract](../docs/API_CONTRACT.md), [Transport v2 architecture](../docs/TRANSPORT_V2_ARCHITECTURE.md), [threat model](../docs/THREAT_MODEL.md), and [error model](../docs/ERROR_MODEL.md).
211
+ 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).
@@ -0,0 +1,13 @@
1
+ import type { Session } from "../public/contract.js";
2
+ import type { ArtifactLeaseV3 } from "../v3/artifactLease.js";
3
+ import { type ArtifactSourceV3, type ConnectionControllerV3 } from "../v3/connectionController.js";
4
+ export type SessionOptionsV3 = Readonly<{
5
+ signal?: AbortSignal;
6
+ connectTimeoutMs?: number;
7
+ }>;
8
+ export type ConnectionControllerOptionsV3 = Readonly<{
9
+ maximumAttempts?: number;
10
+ connectTimeoutMs?: number;
11
+ }>;
12
+ export declare function connectV3(lease: ArtifactLeaseV3, options?: SessionOptionsV3): Promise<Session>;
13
+ export declare function createConnectionControllerV3(source: ArtifactSourceV3, options?: ConnectionControllerOptionsV3): Promise<ConnectionControllerV3<Session>>;
@@ -0,0 +1,75 @@
1
+ import { SDK_DEFAULTS } from "../defaults.js";
2
+ import { createConnectionControllerV3 as createCoreControllerV3, } from "../v3/connectionController.js";
3
+ import { BrowserRuntimeCapabilityRegistryV3, createBrowserWebTransportCarrierV3, } from "../v3/browserRuntime.js";
4
+ import { attemptClaimedArtifactLeaseV3, connectArtifactLeaseV3 } from "../v3/sessionConnector.js";
5
+ import { readyNativeAdmissionV3, readyWebSocketAdmissionV3 } from "../v3/runtimeAdapters.js";
6
+ import { TransportFailureV3, ConnectErrorV3 } from "../v3/security.js";
7
+ import { browserSessionRuntimeV3 } from "../v3/browserSessionRuntime.js";
8
+ export async function connectV3(lease, options = {}) {
9
+ const registry = await BrowserRuntimeCapabilityRegistryV3.create();
10
+ return await connectArtifactLeaseV3(lease, browserRuntime(registry, options.connectTimeoutMs), options.signal);
11
+ }
12
+ export async function createConnectionControllerV3(source, options = {}) {
13
+ const registry = await BrowserRuntimeCapabilityRegistryV3.create();
14
+ const runtime = browserRuntime(registry, options.connectTimeoutMs);
15
+ const coreOptions = {
16
+ capabilitySnapshot: runtime.capabilitySnapshot,
17
+ projectSessionFailure,
18
+ ...(options.maximumAttempts === undefined ? {} : { maximumAttempts: options.maximumAttempts }),
19
+ };
20
+ return createCoreControllerV3(source, async (context) => await attemptClaimedArtifactLeaseV3(context, runtime), coreOptions);
21
+ }
22
+ function browserRuntime(registry, connectTimeoutMs) {
23
+ const connectTimeoutMilliseconds = connectTimeoutMs ?? SDK_DEFAULTS.transport.connectTimeoutMs;
24
+ if (!Number.isSafeInteger(connectTimeoutMilliseconds) || connectTimeoutMilliseconds < 1) {
25
+ throw new ConnectErrorV3("artifact_invalid", { kind: "terminal" });
26
+ }
27
+ return {
28
+ capabilitySnapshot: () => registry.snapshot(),
29
+ connectTimeoutMilliseconds,
30
+ protocolRuntime: browserSessionRuntimeV3,
31
+ dial: async (candidate, artifact, attemptNow, capability, signal) => {
32
+ if (candidate.carrier === "webtransport") {
33
+ const carrier = await createBrowserWebTransportCarrierV3(candidate, attemptNow, capability, registry, artifact.session.max_inbound_streams + 2, signal);
34
+ return readyNativeAdmissionV3(candidate, carrier);
35
+ }
36
+ if (candidate.carrier !== "websocket" || candidate.tls.mode !== "ca") {
37
+ throw new TransportFailureV3("tls_unsupported");
38
+ }
39
+ validateBrowserWebSocketURL(candidate.normalized_url, artifact.path.kind);
40
+ const Constructor = globalThis.WebSocket;
41
+ if (Constructor === undefined)
42
+ throw new TransportFailureV3("tls_unsupported");
43
+ const protocol = artifact.path.kind === "direct" ? "flowersec.direct.v3" : "flowersec.tunnel.v3";
44
+ let socket;
45
+ try {
46
+ socket = new Constructor(candidate.normalized_url, protocol);
47
+ }
48
+ catch (error) {
49
+ throw new TransportFailureV3("connection_failed", undefined, error);
50
+ }
51
+ return await readyWebSocketAdmissionV3(candidate, artifact, socket, signal);
52
+ },
53
+ };
54
+ }
55
+ function validateBrowserWebSocketURL(raw, path) {
56
+ let parsed;
57
+ try {
58
+ parsed = new URL(raw);
59
+ }
60
+ catch {
61
+ throw new TransportFailureV3("invalid_artifact");
62
+ }
63
+ const expectedPath = path === "direct" ? "/flowersec/v3/direct" : "/flowersec/v3/tunnel";
64
+ if (parsed.href !== raw || parsed.protocol !== "wss:" || parsed.username !== "" || parsed.password !== "" ||
65
+ parsed.pathname !== expectedPath || parsed.search !== "" || parsed.hash !== "") {
66
+ throw new TransportFailureV3("invalid_artifact");
67
+ }
68
+ }
69
+ function projectSessionFailure(error) {
70
+ const code = error.code;
71
+ const retryable = new Set([
72
+ "closed", "going_away", "timeout", "resource_exhausted", "stream_reset", "rekey_failed", "liveness_failed",
73
+ ]).has(String(code));
74
+ return new ConnectErrorV3("connection_failed", { kind: retryable ? "retryable" : "terminal" });
75
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "../facade.js";
2
- export { connect, createConnectionController } from "./connectSession.js";
3
- export type { ConnectionControllerOptions, SessionOptions, } from "./connectSession.js";
2
+ export * as v2 from "./v2.js";
3
+ export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
4
+ export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, ConnectionControllerOptionsV3, SessionOptionsV3 as SessionOptions, SessionOptionsV3, } from "./connectSessionV3.js";
@@ -1,2 +1,3 @@
1
1
  export * from "../facade.js";
2
- export { connect, createConnectionController } from "./connectSession.js";
2
+ export * as v2 from "./v2.js";
3
+ export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
@@ -0,0 +1,3 @@
1
+ export * from "../v2/index.js";
2
+ export { connect, createConnectionController } from "./connectSession.js";
3
+ export type { ConnectionControllerOptions, SessionOptions, } from "./connectSession.js";
@@ -0,0 +1,2 @@
1
+ export * from "../v2/index.js";
2
+ export { connect, createConnectionController } from "./connectSession.js";
package/dist/facade.d.ts CHANGED
@@ -1,14 +1,21 @@
1
+ import type { Session as PublicSession } from "./public/contract.js";
2
+ import type { ConnectionControllerSnapshotV3 as CoreConnectionControllerSnapshotV3, ConnectionControllerV3 as CoreConnectionControllerV3 } from "./v3/connectionController.js";
3
+ export * as v2 from "./v2/index.js";
1
4
  export type { ByteStream, IncomingStream, JsonObject, JsonPrimitive, JsonValue, OperationOptions, RpcPeer, RpcResult, SessionErrorCode, StreamOpenOptions, UnreliableMessageChannel, UnreliableMessageSendOptions, UnreliableMessageSendResult, SessionTermination, Session, } from "./public/contract.js";
2
5
  export { SessionError, UnreliableMessageError } from "./public/contract.js";
3
6
  export { createStreamMetadata, StreamMetadataError } from "./public/streamMetadata.js";
4
7
  export type { StreamMetadata } from "./public/streamMetadata.js";
5
8
  export { HandlerRegistrationError, StreamHandlers, } from "./public/streamHandlers.js";
6
9
  export type { StreamHandler, StreamHandlerOptions, } from "./public/streamHandlers.js";
7
- export { ArtifactLeaseError, createArtifactLease, } from "./public/artifactLease.js";
8
- export type { ArtifactLease } from "./public/artifactLease.js";
9
- export { Artifact, ArtifactError, parseArtifact } from "./public/artifact.js";
10
- export type { ArtifactErrorCode } from "./public/artifact.js";
11
- export type { ConnectErrorCode } from "./public/connectError.js";
12
- export { ConnectError } from "./public/connectError.js";
13
- export { ConnectionControllerError } from "./connectionController.js";
14
- export type { ArtifactSource, ArtifactSourceResult, ConnectionController, ConnectionControllerFailure, ConnectionControllerOptions, ConnectionSnapshot, ConnectionState, RetryDisposition, } from "./connectionController.js";
10
+ export { ArtifactHandleV3 as Artifact, ArtifactHandleV3, ArtifactParseErrorV3 as ArtifactError, ArtifactParseErrorV3, createArtifactLeaseV3 as createArtifactLease, createArtifactLeaseV3, parseArtifactV3 as parseArtifact, parseArtifactV3, } from "./v3/publicApi.js";
11
+ export type { ArtifactParseErrorCodeV3 as ArtifactErrorCode, ArtifactParseErrorCodeV3, } from "./v3/publicApi.js";
12
+ export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3, ArtifactLeaseV3Error as ArtifactLeaseError, ArtifactLeaseV3Error, } from "./v3/artifactLease.js";
13
+ export type { ArtifactSourceResultV3 as ArtifactSourceResult, ArtifactSourceResultV3, ArtifactSourceV3 as ArtifactSource, ArtifactSourceV3, ConnectionControllerFailureV3 as ConnectionControllerFailure, ConnectionControllerFailureV3, ConnectionControllerSnapshotV3, ConnectionControllerStateV3 as ConnectionState, ConnectionControllerStateV3, ConnectionControllerV3, } from "./v3/connectionController.js";
14
+ export type ConnectionController = CoreConnectionControllerV3<PublicSession>;
15
+ export type ConnectionSnapshot = CoreConnectionControllerSnapshotV3<PublicSession>;
16
+ export type ConnectionControllerOptions = Readonly<{
17
+ maximumAttempts?: number;
18
+ }>;
19
+ export { ConnectionControllerV3Error as ConnectionControllerError, ConnectionControllerV3Error, } from "./v3/connectionController.js";
20
+ export { ConnectErrorV3 as ConnectError, ConnectErrorV3 } from "./v3/security.js";
21
+ export type { PublicConnectErrorCodeV3 as ConnectErrorCode, PublicConnectErrorCodeV3, RetryDispositionV3 as RetryDisposition, RetryDispositionV3, } from "./v3/security.js";
package/dist/facade.js CHANGED
@@ -1,7 +1,8 @@
1
+ export * as v2 from "./v2/index.js";
1
2
  export { SessionError, UnreliableMessageError } from "./public/contract.js";
2
3
  export { createStreamMetadata, StreamMetadataError } from "./public/streamMetadata.js";
3
4
  export { HandlerRegistrationError, StreamHandlers, } from "./public/streamHandlers.js";
4
- export { ArtifactLeaseError, createArtifactLease, } from "./public/artifactLease.js";
5
- export { Artifact, ArtifactError, parseArtifact } from "./public/artifact.js";
6
- export { ConnectError } from "./public/connectError.js";
7
- export { ConnectionControllerError } from "./connectionController.js";
5
+ export { ArtifactHandleV3 as Artifact, ArtifactHandleV3, ArtifactParseErrorV3 as ArtifactError, ArtifactParseErrorV3, createArtifactLeaseV3 as createArtifactLease, createArtifactLeaseV3, parseArtifactV3 as parseArtifact, parseArtifactV3, } from "./v3/publicApi.js";
6
+ export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3, ArtifactLeaseV3Error as ArtifactLeaseError, ArtifactLeaseV3Error, } from "./v3/artifactLease.js";
7
+ export { ConnectionControllerV3Error as ConnectionControllerError, ConnectionControllerV3Error, } from "./v3/connectionController.js";
8
+ export { ConnectErrorV3 as ConnectError, ConnectErrorV3 } from "./v3/security.js";
@@ -9,6 +9,6 @@ type ReadExactlyFn = (n: number) => Promise<Uint8Array>;
9
9
  type ReadExactlyLike = Readonly<{
10
10
  readExactly: (n: number) => Promise<Uint8Array>;
11
11
  }>;
12
- export declare function writeJsonFrame(write: WriteFn | WriteLike, v: unknown): Promise<void>;
12
+ export declare function writeJsonFrame(write: WriteFn | WriteLike, v: unknown, maxBytes?: number): Promise<void>;
13
13
  export declare function readJsonFrame(readExactly: ReadExactlyFn | ReadExactlyLike, maxBytes: number): Promise<unknown>;
14
14
  export {};
@@ -13,8 +13,10 @@ function normalizeReadExactly(readExactly) {
13
13
  return typeof readExactly === "function" ? readExactly : (n) => readExactly.readExactly(n);
14
14
  }
15
15
  // writeJsonFrame encodes a JSON payload with a 4-byte length prefix.
16
- export async function writeJsonFrame(write, v) {
16
+ export async function writeJsonFrame(write, v, maxBytes = 0) {
17
17
  const json = te.encode(JSON.stringify(v));
18
+ if (maxBytes > 0 && json.length > maxBytes)
19
+ throw new JsonFramingError("frame too large");
18
20
  const hdr = u32be(json.length);
19
21
  const out = new Uint8Array(4 + json.length);
20
22
  out.set(hdr, 0);
@@ -1,4 +1,4 @@
1
- import { authorizeRuntime, createAcceptor, createEndpointSet, Issuer, parseArtifact, ProxyServer, SessionHandlers, } from "../node/index.js";
1
+ import { authorizeRuntime, createAcceptor, createEndpointSet, Issuer, parseArtifact, ProxyServer, SessionHandlers, } from "../node/v2.js";
2
2
  const ORIGIN = "https://app.example";
3
3
  const args = process.argv.slice(2);
4
4
  if (args.length !== 2 || args[0] !== "--upstream" || args[1] === undefined) {
@@ -1,6 +1,13 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { createPrivateKey, createPublicKey, X509Certificate } from "node:crypto";
3
- import { createAcceptor, createArtifactLease, createEndpointSet, createStreamMetadata, createTunnelRuntime, connect, Issuer, parseArtifact, RPCHandlers, SessionError, SessionHandlers, } from "../node/index.js";
3
+ import { createStreamMetadata, } from "../public/streamMetadata.js";
4
+ import { createAcceptor, RPCHandlers, SessionHandlers } from "../node/acceptor.js";
5
+ import { createTunnelRuntime } from "../node/tunnelRuntime.js";
6
+ import { createArtifactLease } from "../public/artifactLease.js";
7
+ import { parseArtifact } from "../public/artifact.js";
8
+ import { connect } from "../node/connectSession.js";
9
+ import { Issuer, createEndpointSet } from "../node/controlplane.js";
10
+ import { SessionError } from "../public/contract.js";
4
11
  const RUNTIME = "node-typescript";
5
12
  const ORIGIN = process.env.FLOWERSEC_PARITY_ORIGIN ?? "https://client.example";
6
13
  const ECHO_RPC = 7001;
@@ -27,6 +27,16 @@ export declare class RPCHandlers {
27
27
  }
28
28
  export declare class SessionHandlers {
29
29
  private readonly streamHandlerRegistrarBrand;
30
+ private readonly sessionHandlersV2Brand;
31
+ constructor(options?: SessionHandlerOptions);
32
+ handleRPC(typeId: number, handler: RPCHandler): void;
33
+ handleNotification(typeId: number, handler: NotificationHandler): void;
34
+ handleStream(kind: string, handler: StreamHandler): void;
35
+ }
36
+ /** Strict-v3 accepted-session handlers. */
37
+ export declare class SessionHandlersV3 {
38
+ private readonly streamHandlerRegistrarBrand;
39
+ private readonly sessionHandlersV3Brand;
30
40
  constructor(options?: SessionHandlerOptions);
31
41
  handleRPC(typeId: number, handler: RPCHandler): void;
32
42
  handleNotification(typeId: number, handler: NotificationHandler): void;
@@ -1,5 +1,5 @@
1
1
  import { RpcRouter } from "../rpc/server.js";
2
- import { assertRpcError } from "../rpc/validate.js";
2
+ import { assertRpcError, assertRpcTypeId } from "../rpc/validate.js";
3
3
  import { acceptReceivedSessionV2, receiveSessionAdmissionV2, rejectSessionAdmissionV2, } from "../connector/sessionAcceptor.js";
4
4
  import { nodeSessionRuntimeV2 } from "./sessionRuntime.js";
5
5
  import { startNodeWebSocketServer, } from "./webSocketServer.js";
@@ -9,7 +9,7 @@ import { unwrapArtifact } from "../public/artifact.js";
9
9
  import { SessionError, } from "../public/contract.js";
10
10
  import { projectSessionV2 } from "../v2/publicSession.js";
11
11
  import { runtimeAuthorizationRequestFromDecoded, } from "./controlplane.js";
12
- import { HandlerRegistrationError, StreamHandlers, freezeStreamHandlers, registerStreamHandlersAtomically, serveFrozenStreamHandlers, } from "../public/streamHandlers.js";
12
+ import { HandlerRegistrationError, StreamHandlers, LegacyStreamHandlers, freezeStreamHandlers, registerStreamHandlersAtomically, serveFrozenStreamHandlers, } from "../public/streamHandlers.js";
13
13
  export { HandlerRegistrationError } from "../public/streamHandlers.js";
14
14
  const DEFAULT_CLEANUP_TIMEOUT_MS = 2_000;
15
15
  export class RPCHandlers {
@@ -24,6 +24,28 @@ export class RPCHandlers {
24
24
  }
25
25
  }
26
26
  export class SessionHandlers {
27
+ constructor(options = {}) {
28
+ sessionHandlerStates.set(this, {
29
+ rpc: createRPCHandlerState(),
30
+ streams: new LegacyStreamHandlers(options),
31
+ frozen: false,
32
+ });
33
+ }
34
+ handleRPC(typeId, handler) {
35
+ const state = mutableSessionHandlerState(this);
36
+ registerRPC(state.rpc, typeId, handler);
37
+ }
38
+ handleNotification(typeId, handler) {
39
+ const state = mutableSessionHandlerState(this);
40
+ registerNotification(state.rpc, typeId, handler);
41
+ }
42
+ handleStream(kind, handler) {
43
+ const state = mutableSessionHandlerState(this);
44
+ state.streams.handleStream(kind, handler);
45
+ }
46
+ }
47
+ /** Strict-v3 accepted-session handlers. */
48
+ export class SessionHandlersV3 {
27
49
  constructor(options = {}) {
28
50
  sessionHandlerStates.set(this, {
29
51
  rpc: createRPCHandlerState(),
@@ -85,10 +107,13 @@ function registerNotification(state, typeId, handler) {
85
107
  state.notifications.set(typeId, handler);
86
108
  }
87
109
  function validateRPCRegistration(typeId, handler) {
88
- if (!Number.isSafeInteger(typeId)
89
- || typeId < 1
90
- || typeId > 0xffff_ffff
91
- || typeof handler !== "function") {
110
+ try {
111
+ assertRpcTypeId(typeId);
112
+ }
113
+ catch {
114
+ throw new HandlerRegistrationError("invalid_handler");
115
+ }
116
+ if (typeof handler !== "function") {
92
117
  throw new HandlerRegistrationError("invalid_handler");
93
118
  }
94
119
  }
@@ -134,7 +159,28 @@ export function createRPCRouter(snapshot) {
134
159
  }
135
160
  return router;
136
161
  }
137
- function freezeSessionHandlers(handlers) {
162
+ /** @internal */
163
+ export function freezeSessionHandlers(handlers) {
164
+ if (!(handlers instanceof SessionHandlers)) {
165
+ throw new HandlerRegistrationError("invalid_handler");
166
+ }
167
+ const state = sessionHandlerStates.get(handlers);
168
+ if (state === undefined)
169
+ throw new HandlerRegistrationError("invalid_handler");
170
+ if (state.snapshot !== undefined)
171
+ return state.snapshot;
172
+ state.frozen = true;
173
+ state.snapshot = Object.freeze({
174
+ rpc: freezeRPCHandlerState(state.rpc),
175
+ streams: freezeStreamHandlers(state.streams),
176
+ });
177
+ return state.snapshot;
178
+ }
179
+ /** @internal */
180
+ export function freezeSessionHandlersV3(handlers) {
181
+ if (!(handlers instanceof SessionHandlersV3)) {
182
+ throw new HandlerRegistrationError("invalid_handler");
183
+ }
138
184
  const state = sessionHandlerStates.get(handlers);
139
185
  if (state === undefined)
140
186
  throw new HandlerRegistrationError("invalid_handler");
@@ -0,0 +1,57 @@
1
+ import type { OperationOptions, Session } from "../public/contract.js";
2
+ import { type ArtifactHandleV3 } from "../v3/publicApi.js";
3
+ import { SessionHandlersV3 } from "./acceptor.js";
4
+ import { type RuntimeAuthorizationRequestV3 } from "./runtimeAuthorizationV3.js";
5
+ export type AcceptorListenerV3 = Readonly<{
6
+ carrier: "websocket";
7
+ path: "direct";
8
+ host: string;
9
+ port: number;
10
+ tls: Readonly<{
11
+ certificate: string;
12
+ privateKey: string;
13
+ }>;
14
+ allowedOrigins: readonly string[];
15
+ }> | Readonly<{
16
+ carrier: "raw_quic";
17
+ path: "direct";
18
+ host: string;
19
+ port: number;
20
+ tls: Readonly<{
21
+ certificate: string | Uint8Array;
22
+ privateKey: string | Uint8Array;
23
+ }>;
24
+ }>;
25
+ export type AcceptorOptionsV3 = Readonly<{
26
+ listeners: readonly AcceptorListenerV3[];
27
+ maxInboundStreams: number;
28
+ admissionTimeoutMs?: number;
29
+ admissionReasons?: readonly string[];
30
+ authorize: AcceptorAuthorizerV3;
31
+ resolveHandlers?(request: RuntimeAuthorizationRequestV3, options: OperationOptions): Promise<SessionHandlersV3> | SessionHandlersV3;
32
+ }>;
33
+ export type AcceptorAuthorizationDecisionV3 = Readonly<{
34
+ accepted: true;
35
+ artifact: ArtifactHandleV3;
36
+ }> | Readonly<{
37
+ accepted: false;
38
+ retryable: boolean;
39
+ reason: string;
40
+ }>;
41
+ export type AcceptorAuthorizerV3 = (request: RuntimeAuthorizationRequestV3, options: OperationOptions) => Promise<AcceptorAuthorizationDecisionV3> | AcceptorAuthorizationDecisionV3;
42
+ export declare class AcceptedSessionV3 {
43
+ private constructor();
44
+ get session(): Session;
45
+ serve(options?: OperationOptions): Promise<void>;
46
+ close(): Promise<void>;
47
+ }
48
+ export declare class AcceptorV3 {
49
+ private constructor();
50
+ addresses(): readonly Readonly<{
51
+ host: string;
52
+ port: number;
53
+ }>[];
54
+ accept(options?: OperationOptions): Promise<AcceptedSessionV3>;
55
+ close(): Promise<void>;
56
+ }
57
+ export declare function createAcceptorV3(options: AcceptorOptionsV3): Promise<AcceptorV3>;