@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.
- package/README.md +46 -18
- package/dist/browser/connectSessionV3.d.ts +13 -0
- package/dist/browser/connectSessionV3.js +75 -0
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +2 -1
- package/dist/browser/v2.d.ts +3 -0
- package/dist/browser/v2.js +2 -0
- package/dist/facade.d.ts +15 -8
- package/dist/facade.js +5 -4
- package/dist/framing/jsonframe.d.ts +1 -1
- package/dist/framing/jsonframe.js +3 -1
- package/dist/interop/proxyServerPeer.js +1 -1
- package/dist/interop/serverParityPeer.js +8 -1
- package/dist/node/acceptor.d.ts +10 -0
- package/dist/node/acceptor.js +53 -7
- package/dist/node/acceptorV3.d.ts +57 -0
- package/dist/node/acceptorV3.js +227 -0
- package/dist/node/connectSessionV3.d.ts +21 -0
- package/dist/node/connectSessionV3.js +73 -0
- package/dist/node/index.d.ts +15 -10
- package/dist/node/index.js +6 -5
- package/dist/node/nativeTransportAddon.d.ts +39 -1
- package/dist/node/nativeTransportAddon.js +29 -5
- package/dist/node/rawQuicAdapterV3.d.ts +10 -0
- package/dist/node/rawQuicAdapterV3.js +76 -0
- package/dist/node/rawQuicServerV3.d.ts +24 -0
- package/dist/node/rawQuicServerV3.js +34 -0
- package/dist/node/runtimeAuthorizationV3.d.ts +29 -0
- package/dist/node/runtimeAuthorizationV3.js +126 -0
- package/dist/node/tunnelRuntimeV3.d.ts +57 -0
- package/dist/node/tunnelRuntimeV3.js +857 -0
- package/dist/node/v2.d.ts +11 -0
- package/dist/node/v2.js +6 -0
- package/dist/node/webSocketServerV3.d.ts +27 -0
- package/dist/node/webSocketServerV3.js +219 -0
- package/dist/proxy/runtime.js +39 -6
- package/dist/proxy/serviceWorker.d.ts +2 -0
- package/dist/proxy/serviceWorker.js +63 -7
- package/dist/proxy/windowBridge.d.ts +1 -0
- package/dist/proxy/windowBridge.js +93 -33
- package/dist/proxy/wsPatch.d.ts +1 -0
- package/dist/proxy/wsPatch.js +39 -6
- package/dist/public/streamHandlers.d.ts +3 -0
- package/dist/public/streamHandlers.js +6 -1
- package/dist/rpc/client.js +9 -7
- package/dist/rpc/server.js +44 -20
- package/dist/rpc/validate.d.ts +1 -0
- package/dist/rpc/validate.js +18 -6
- package/dist/v2/index.d.ts +14 -0
- package/dist/v2/index.js +7 -0
- package/dist/v2/protocol.js +23 -32
- package/dist/v2/publicSession.js +4 -0
- package/dist/v2/session.d.ts +5 -0
- package/dist/v2/session.js +94 -35
- package/dist/v3/admissionError.d.ts +4 -0
- package/dist/v3/admissionError.js +8 -0
- package/dist/v3/artifact.d.ts +145 -0
- package/dist/v3/artifact.js +1162 -0
- package/dist/v3/artifactLease.d.ts +17 -0
- package/dist/v3/artifactLease.js +109 -0
- package/dist/v3/browserRuntime.d.ts +37 -0
- package/dist/v3/browserRuntime.js +197 -0
- package/dist/v3/browserSessionRuntime.d.ts +2 -0
- package/dist/v3/browserSessionRuntime.js +21 -0
- package/dist/v3/capability.d.ts +32 -0
- package/dist/v3/capability.js +345 -0
- package/dist/v3/carrier.d.ts +84 -0
- package/dist/v3/carrier.js +411 -0
- package/dist/v3/connectionController.d.ts +87 -0
- package/dist/v3/connectionController.js +805 -0
- package/dist/v3/contract.d.ts +65 -0
- package/dist/v3/contract.js +3 -0
- package/dist/v3/controller.d.ts +43 -0
- package/dist/v3/controller.js +303 -0
- package/dist/v3/handshake.d.ts +81 -0
- package/dist/v3/handshake.js +471 -0
- package/dist/v3/jcs.d.ts +6 -0
- package/dist/v3/jcs.js +69 -0
- package/dist/v3/jsonPreflight.d.ts +1 -0
- package/dist/v3/jsonPreflight.js +251 -0
- package/dist/v3/nodeRuntime.d.ts +40 -0
- package/dist/v3/nodeRuntime.js +315 -0
- package/dist/v3/nodeSessionRuntime.d.ts +2 -0
- package/dist/v3/nodeSessionRuntime.js +13 -0
- package/dist/v3/protocol.d.ts +99 -0
- package/dist/v3/protocol.js +773 -0
- package/dist/v3/publicApi.d.ts +12 -0
- package/dist/v3/publicApi.js +46 -0
- package/dist/v3/publicSession.d.ts +1 -0
- package/dist/v3/publicSession.js +281 -0
- package/dist/v3/retryDisposition.d.ts +16 -0
- package/dist/v3/retryDisposition.js +30 -0
- package/dist/v3/runtimeAdapters.d.ts +9 -0
- package/dist/v3/runtimeAdapters.js +109 -0
- package/dist/v3/security.d.ts +34 -0
- package/dist/v3/security.js +100 -0
- package/dist/v3/serverAdmission.d.ts +38 -0
- package/dist/v3/serverAdmission.js +223 -0
- package/dist/v3/session.d.ts +318 -0
- package/dist/v3/session.js +2289 -0
- package/dist/v3/sessionConnector.d.ts +37 -0
- package/dist/v3/sessionConnector.js +440 -0
- package/dist/v3/streamLifetimeLedger.d.ts +31 -0
- package/dist/v3/streamLifetimeLedger.js +131 -0
- package/dist/v3/streamMetadata.d.ts +1 -0
- package/dist/v3/streamMetadata.js +7 -0
- package/dist/v3/transportConstants.d.ts +56 -0
- package/dist/v3/transportConstants.js +59 -0
- package/dist/v3/unreliableMessage.d.ts +44 -0
- package/dist/v3/unreliableMessage.js +284 -0
- package/dist/v3/webSocketCarrier.d.ts +31 -0
- package/dist/v3/webSocketCarrier.js +370 -0
- package/dist/v3/webTransportCarrier.d.ts +27 -0
- package/dist/v3/webTransportCarrier.js +534 -0
- package/dist/yamux/stream.d.ts +3 -0
- package/dist/yamux/stream.js +17 -4
- package/package.json +4 -4
- package/sbom/cyclonedx.json +6 -6
- 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(...)`, `
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
136
|
-
control plane and `ProxyServer
|
|
137
|
-
Session. WebTransport is an optional adapter
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export * from "../facade.js";
|
|
2
|
-
export
|
|
3
|
-
export
|
|
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";
|
package/dist/browser/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export * from "../facade.js";
|
|
2
|
-
export
|
|
2
|
+
export * as v2 from "./v2.js";
|
|
3
|
+
export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.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 {
|
|
8
|
-
export type {
|
|
9
|
-
export {
|
|
10
|
-
export type {
|
|
11
|
-
export type
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
|
|
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 {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
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/
|
|
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 {
|
|
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;
|
package/dist/node/acceptor.d.ts
CHANGED
|
@@ -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;
|
package/dist/node/acceptor.js
CHANGED
|
@@ -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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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>;
|