@floegence/flowersec-core 2.3.3 → 2.3.5

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 CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  `@floegence/flowersec-core` is the ESM-only Flowersec v2 SDK for browsers and Node.js. Its public package surface is limited to the root, `/browser`, `/node`, and `/proxy` entrypoints.
4
4
 
5
- Flowersec 2.3.3 is the coordinated TypeScript SDK patch release.
5
+ Flowersec 2.3.5 is the published TypeScript SDK release.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @floegence/flowersec-core@2.3.3
10
+ npm install @floegence/flowersec-core@2.3.5
11
11
  ```
12
12
 
13
13
  ## Public API
@@ -28,7 +28,7 @@ The root type exports are:
28
28
 
29
29
  Retry ownership belongs to `ConnectionController`; applications do not classify error text or run a parallel retry scheduler. Public failures remain redacted and reveal no carrier, candidate, URL, credential, stage, key, or diagnostic details.
30
30
 
31
- `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 are portable across SDKs, while `RpcPeer.onNotify(...)` is a TypeScript-specific subscription convenience.
31
+ `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 are portable across SDKs. TypeScript `RpcPeer.onNotify(...)` receives peer outbound notifications through the local Session's inbound reserved RPC stream.
32
32
 
33
33
  When connector options omit a connection timeout, browser and Node.js connectors use the shared ten-second default.
34
34
 
@@ -84,13 +84,13 @@ Raw QUIC and WebTransport preserve native FIN, RESET_STREAM, STOP_SENDING, flow
84
84
 
85
85
  WebSocket uses Yamux only inside its carrier adapter. Yamux has no independent STOP_SENDING primitive, so that operation is explicitly unavailable rather than emulated with a full stream reset.
86
86
 
87
- Browser applications receive a ready `Session` from `connect(...)`. The browser connector supports WSS and WebTransport production connections. WebTransport uses native HTTP/3 bidirectional streams and does not use Yamux.
87
+ Browser applications receive a ready `Session` from `connect(...)`. The browser connector supports WSS, restricted plaintext loopback WebSocket direct connections, and WebTransport. WebTransport uses native HTTP/3 bidirectional streams and does not use Yamux.
88
88
 
89
89
  Chromium does not support a WebTransport pooling option; each carrier creates an independent native WebTransport connection.
90
90
 
91
91
  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.
92
92
 
93
- Node.js applications receive the same `Session` contract from `connect(...)`. The Node.js connector supports WSS and WebTransport production connections for direct and tunnel artifacts. It requires an absolute HTTP(S) `origin`; a custom certificate authority can be supplied through `tls.ca`. Invalid origin and TLS options fail as `ConnectError` with `invalid_options` and are terminal to the optional connection controller.
93
+ Node.js applications receive the same `Session` contract from `connect(...)`. The Node.js connector supports WSS and WebTransport for direct and tunnel artifacts, plus restricted plaintext loopback WebSocket direct connections. It requires an absolute HTTP(S) `origin`; a custom certificate authority can be supplied through `tls.ca`. Invalid origin and TLS options fail as `ConnectError` with `invalid_options` and are terminal to the optional connection controller.
94
94
 
95
95
  The Node WebTransport direct listener/server is owned by the Node runtime and the `flowersec-ts-cli` server path. It uses the same native carrier acceptor and session engine as the client connector; it is not a second protocol implementation.
96
96
 
@@ -15,8 +15,11 @@ export type RpcServerTransport = Readonly<{
15
15
  }>;
16
16
  export declare class RpcRouter {
17
17
  private readonly handlers;
18
+ private readonly notifyHandlers;
18
19
  register(typeId: number, handler: RpcHandler): void;
19
20
  handler(typeId: number): RpcHandler | undefined;
21
+ onNotify(typeId: number, handler: (payload: unknown) => void): () => void;
22
+ dispatchNotification(typeId: number, payload: unknown): Promise<void>;
20
23
  }
21
24
  export declare class RpcServer {
22
25
  private readonly transport;
@@ -8,12 +8,38 @@ const DEFAULT_RPC_SERVER_OPTIONS = Object.freeze({
8
8
  });
9
9
  export class RpcRouter {
10
10
  handlers = new Map();
11
+ notifyHandlers = new Map();
11
12
  register(typeId, handler) {
12
13
  this.handlers.set(typeId >>> 0, handler);
13
14
  }
14
15
  handler(typeId) {
15
16
  return this.handlers.get(typeId >>> 0);
16
17
  }
18
+ onNotify(typeId, handler) {
19
+ const normalized = typeId >>> 0;
20
+ const handlers = this.notifyHandlers.get(normalized) ?? new Set();
21
+ handlers.add(handler);
22
+ this.notifyHandlers.set(normalized, handlers);
23
+ return () => {
24
+ handlers.delete(handler);
25
+ if (handlers.size === 0)
26
+ this.notifyHandlers.delete(normalized);
27
+ };
28
+ }
29
+ async dispatchNotification(typeId, payload) {
30
+ const normalized = typeId >>> 0;
31
+ const requestHandler = this.handlers.get(normalized);
32
+ if (requestHandler !== undefined)
33
+ await requestHandler(payload);
34
+ for (const handler of [...(this.notifyHandlers.get(normalized) ?? [])]) {
35
+ try {
36
+ handler(payload);
37
+ }
38
+ catch {
39
+ // Application subscribers cannot stop RPC serving.
40
+ }
41
+ }
42
+ }
17
43
  }
18
44
  // RpcServer dispatches request envelopes to registered handlers.
19
45
  export class RpcServer {
@@ -175,10 +201,7 @@ export class RpcServer {
175
201
  if (work == null)
176
202
  return;
177
203
  const v = work.envelope;
178
- const h = this.router.handler(v.type_id);
179
- if (h == null)
180
- continue;
181
- await h(v.payload);
204
+ await this.router.dispatchNotification(v.type_id, v.payload);
182
205
  }
183
206
  }
184
207
  async nextWork(queue, waiters) {
@@ -1,4 +1,4 @@
1
- import type { RpcClient } from "../rpc/client.js";
1
+ import type { RpcError } from "../rpc/wire.js";
2
2
  export interface InternalByteStreamV2 {
3
3
  readonly id: bigint;
4
4
  readonly kind: string;
@@ -18,7 +18,7 @@ export interface InternalIncomingStreamV2 {
18
18
  export interface InternalSessionV2 {
19
19
  readonly path: PathKind;
20
20
  readonly endpointInstanceId: string | undefined;
21
- readonly rpc: RpcClient;
21
+ readonly rpc: InternalRpcPeerV2;
22
22
  readonly termination: Promise<Readonly<{
23
23
  error: Error;
24
24
  }>>;
@@ -32,6 +32,15 @@ export interface InternalSessionV2 {
32
32
  }>>;
33
33
  close(): Promise<void>;
34
34
  }
35
+ export interface InternalRpcPeerV2 {
36
+ call(typeId: number, payload: unknown, signal?: AbortSignal): Promise<{
37
+ payload: unknown;
38
+ error?: RpcError;
39
+ }>;
40
+ notify(typeId: number, payload: unknown): Promise<void>;
41
+ onNotify(typeId: number, handler: (payload: unknown) => void): () => void;
42
+ close(): void;
43
+ }
35
44
  export type InternalStreamOpenOptionsV2 = OperationOptionsV2 & Readonly<{
36
45
  metadata?: JsonObjectV2;
37
46
  }>;
@@ -1,6 +1,6 @@
1
1
  import { RpcClient } from "../rpc/client.js";
2
2
  import { RpcRouter, type RpcServerOptions } from "../rpc/server.js";
3
- import type { InternalByteStreamV2 as ByteStreamV2, InternalIncomingStreamV2 as IncomingStreamV2, InternalSessionV2 as SessionV2Contract, OperationOptionsV2, PathKind, InternalStreamOpenOptionsV2, UnreliableMessageChannelV2 } from "./contract.js";
3
+ import type { InternalByteStreamV2 as ByteStreamV2, InternalIncomingStreamV2 as IncomingStreamV2, InternalRpcPeerV2, InternalSessionV2 as SessionV2Contract, OperationOptionsV2, PathKind, InternalStreamOpenOptionsV2, UnreliableMessageChannelV2 } from "./contract.js";
4
4
  import { type CarrierSessionV2, type CarrierStreamV2 } from "./carrier.js";
5
5
  import type { SessionContractV2 } from "./artifact.js";
6
6
  import { DirectionV2, InnerTypeV2, type EpochRootsV2, type RecordHeaderV2, type CipherSuiteV2 } from "./protocol.js";
@@ -49,6 +49,18 @@ export declare class SessionV2Error extends Error {
49
49
  readonly code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "timeout";
50
50
  constructor(code: "aborted" | "closed" | "going_away" | "handshake" | "open_rejected" | "protocol" | "resource_exhausted" | "timeout", message: string);
51
51
  }
52
+ declare class SessionRpcPeerV2 implements InternalRpcPeerV2 {
53
+ private readonly outbound;
54
+ private readonly inbound;
55
+ constructor(outbound: RpcClient, inbound: RpcRouter);
56
+ call(typeId: number, payload: unknown, signal?: AbortSignal): Promise<{
57
+ payload: unknown;
58
+ error?: import("../rpc/wire.js").RpcError;
59
+ }>;
60
+ notify(typeId: number, payload: unknown): Promise<void>;
61
+ onNotify(typeId: number, handler: (payload: unknown) => void): () => void;
62
+ close(): void;
63
+ }
52
64
  type HandshakeMaterial = Readonly<{
53
65
  h3: Uint8Array;
54
66
  sessionPRK: Uint8Array;
@@ -62,7 +74,7 @@ export declare class SessionV2 implements SessionV2Contract {
62
74
  private readonly config;
63
75
  readonly path: PathKind;
64
76
  readonly endpointInstanceId: string | undefined;
65
- readonly rpc: RpcClient;
77
+ readonly rpc: SessionRpcPeerV2;
66
78
  readonly termination: Promise<SessionTerminationV2>;
67
79
  readonly unreliableMessages: UnreliableMessageChannelV2 | undefined;
68
80
  terminalError: Error | undefined;
@@ -119,6 +131,7 @@ export declare class SessionV2 implements SessionV2Contract {
119
131
  private idleWatchdogStarted;
120
132
  private idleTimer;
121
133
  private readonly terminationState;
134
+ private readonly rpcRouter;
122
135
  constructor(carrier: CarrierSessionV2, control: CarrierStreamV2, controlReader: ExactReader, config: SessionConfigV2, material: HandshakeMaterial);
123
136
  openStream(kind: string, options?: InternalStreamOpenOptionsV2): Promise<ByteStreamV2>;
124
137
  acceptStream(options?: OperationOptionsV2): Promise<IncomingStreamV2>;
@@ -20,6 +20,26 @@ export class SessionV2Error extends Error {
20
20
  this.name = "SessionV2Error";
21
21
  }
22
22
  }
23
+ class SessionRpcPeerV2 {
24
+ outbound;
25
+ inbound;
26
+ constructor(outbound, inbound) {
27
+ this.outbound = outbound;
28
+ this.inbound = inbound;
29
+ }
30
+ call(typeId, payload, signal) {
31
+ return this.outbound.call(typeId, payload, signal);
32
+ }
33
+ notify(typeId, payload) {
34
+ return this.outbound.notify(typeId, payload);
35
+ }
36
+ onNotify(typeId, handler) {
37
+ return this.inbound.onNotify(typeId, handler);
38
+ }
39
+ close() {
40
+ this.outbound.close();
41
+ }
42
+ }
23
43
  export async function establishSessionV2(carrier, config, options = {}) {
24
44
  validateConfig(carrier, config);
25
45
  const establishDeadline = createSessionDeadline(config, "establish");
@@ -115,6 +135,7 @@ export class SessionV2 {
115
135
  idleWatchdogStarted = false;
116
136
  idleTimer;
117
137
  terminationState = deferred();
138
+ rpcRouter;
118
139
  constructor(carrier, control, controlReader, config, material) {
119
140
  this.carrier = carrier;
120
141
  this.control = control;
@@ -150,8 +171,9 @@ export class SessionV2 {
150
171
  : undefined;
151
172
  this.outboundPermits = new AsyncSemaphore(config.maxInboundStreams);
152
173
  this.inboundPermits = new AsyncSemaphore(config.maxInboundStreams);
174
+ this.rpcRouter = config.rpcRouter ?? new RpcRouter();
153
175
  const rpcReadState = { reader: undefined };
154
- this.rpc = new RpcClient(async (length) => {
176
+ const rpcClient = new RpcClient(async (length) => {
155
177
  await this.rpcActivation.promise;
156
178
  const stream = await this.ensureRPCStream();
157
179
  rpcReadState.reader ??= new ExactReader(stream);
@@ -161,6 +183,7 @@ export class SessionV2 {
161
183
  const stream = await this.ensureRPCStream();
162
184
  await stream.write(payload);
163
185
  }, { onTerminal: (error) => this.fail(error) });
186
+ this.rpc = new SessionRpcPeerV2(rpcClient, this.rpcRouter);
164
187
  }
165
188
  async openStream(kind, options = {}) {
166
189
  if (kind === RESERVED_RPC_KIND)
@@ -495,7 +518,7 @@ export class SessionV2 {
495
518
  readExactly: async (length) => await rpcReader.readExactly(length),
496
519
  write: async (payload) => { await stream.write(payload); },
497
520
  close: () => { void stream.reset(); },
498
- }, this.config.rpcServerOptions, this.config.rpcRouter ?? new RpcRouter());
521
+ }, this.config.rpcServerOptions, this.rpcRouter);
499
522
  void server.serve().catch((error) => {
500
523
  if (this.lifecycle !== "closed")
501
524
  this.fail(asError(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/flowersec-core",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "Flowersec core TypeScript library for carrier-neutral encrypted sessions and multiplexed streams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:c94053cc-a443-5cf2-8417-f0399a407a0f",
4
+ "serialNumber": "urn:uuid:3400c69b-8cbe-5509-8b3e-e52a56f2135d",
5
5
  "version": 1,
6
6
  "metadata": {
7
7
  "component": {
8
8
  "type": "library",
9
9
  "name": "@floegence/flowersec-core",
10
- "version": "2.3.3",
11
- "purl": "pkg:npm/%40floegence/flowersec-core@2.3.3",
12
- "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.3.3"
10
+ "version": "2.3.5",
11
+ "purl": "pkg:npm/%40floegence/flowersec-core@2.3.5",
12
+ "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.3.5"
13
13
  },
14
14
  "properties": [
15
15
  {
16
16
  "name": "flowersec:source-inventory-sha256",
17
- "value": "fcd6271d40c202e8e93bf5d7f79a228360688ae574e876f9c0616d6efe5ef8ca"
17
+ "value": "0ab98241dfe33a677cf1e6726296a7db34a9f0a77e1eb4c9d21784b4581fccd1"
18
18
  }
19
19
  ]
20
20
  },
@@ -2290,7 +2290,7 @@
2290
2290
  ],
2291
2291
  "dependencies": [
2292
2292
  {
2293
- "ref": "pkg:npm/%40floegence/flowersec-core@2.3.3",
2293
+ "ref": "pkg:npm/%40floegence/flowersec-core@2.3.5",
2294
2294
  "dependsOn": [
2295
2295
  "pkg:npm/%40fails-components/webtransport-transport-http3-quiche@1.6.7",
2296
2296
  "pkg:npm/%40fails-components/webtransport@1.6.7",
package/sbom/spdx.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
5
  "name": "flowersec-ts",
6
- "documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/fcd6271d40c202e8e93bf5d7f79a228360688ae574e876f9c0616d6efe5ef8ca",
6
+ "documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/0ab98241dfe33a677cf1e6726296a7db34a9f0a77e1eb4c9d21784b4581fccd1",
7
7
  "creationInfo": {
8
8
  "created": "1970-01-01T00:00:00Z",
9
9
  "creators": [
@@ -13,19 +13,19 @@
13
13
  "packages": [
14
14
  {
15
15
  "name": "@floegence/flowersec-core",
16
- "SPDXID": "SPDXRef-Package-17254d46a5f845d3133b",
17
- "versionInfo": "2.3.3",
16
+ "SPDXID": "SPDXRef-Package-4c55f673934ca9f855ad",
17
+ "versionInfo": "2.3.5",
18
18
  "downloadLocation": "NOASSERTION",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "NOASSERTION",
21
21
  "licenseDeclared": "NOASSERTION",
22
22
  "copyrightText": "NOASSERTION",
23
- "comment": "Flowersec source inventory SHA-256: fcd6271d40c202e8e93bf5d7f79a228360688ae574e876f9c0616d6efe5ef8ca",
23
+ "comment": "Flowersec source inventory SHA-256: 0ab98241dfe33a677cf1e6726296a7db34a9f0a77e1eb4c9d21784b4581fccd1",
24
24
  "externalRefs": [
25
25
  {
26
26
  "referenceCategory": "PACKAGE-MANAGER",
27
27
  "referenceType": "purl",
28
- "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.3.3"
28
+ "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.3.5"
29
29
  }
30
30
  ]
31
31
  },
@@ -1411,7 +1411,7 @@
1411
1411
  {
1412
1412
  "spdxElementId": "SPDXRef-DOCUMENT",
1413
1413
  "relationshipType": "DESCRIBES",
1414
- "relatedSpdxElement": "SPDXRef-Package-17254d46a5f845d3133b"
1414
+ "relatedSpdxElement": "SPDXRef-Package-4c55f673934ca9f855ad"
1415
1415
  },
1416
1416
  {
1417
1417
  "spdxElementId": "SPDXRef-Package-789c0f508771e385c5e8",
@@ -1459,37 +1459,37 @@
1459
1459
  "relatedSpdxElement": "SPDXRef-Package-1a7f123f21a1eaf4fd69"
1460
1460
  },
1461
1461
  {
1462
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1462
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1463
1463
  "relationshipType": "DEPENDS_ON",
1464
1464
  "relatedSpdxElement": "SPDXRef-Package-789c0f508771e385c5e8"
1465
1465
  },
1466
1466
  {
1467
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1467
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1468
1468
  "relationshipType": "DEPENDS_ON",
1469
1469
  "relatedSpdxElement": "SPDXRef-Package-4b35e4821b43dc47a168"
1470
1470
  },
1471
1471
  {
1472
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1472
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1473
1473
  "relationshipType": "DEPENDS_ON",
1474
1474
  "relatedSpdxElement": "SPDXRef-Package-24f128c8779cacb70e46"
1475
1475
  },
1476
1476
  {
1477
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1477
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1478
1478
  "relationshipType": "DEPENDS_ON",
1479
1479
  "relatedSpdxElement": "SPDXRef-Package-6a42d288421aaee354ff"
1480
1480
  },
1481
1481
  {
1482
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1482
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1483
1483
  "relationshipType": "DEPENDS_ON",
1484
1484
  "relatedSpdxElement": "SPDXRef-Package-d976986d5d79eddc7789"
1485
1485
  },
1486
1486
  {
1487
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1487
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1488
1488
  "relationshipType": "DEPENDS_ON",
1489
1489
  "relatedSpdxElement": "SPDXRef-Package-f8b45a289df643042b94"
1490
1490
  },
1491
1491
  {
1492
- "spdxElementId": "SPDXRef-Package-17254d46a5f845d3133b",
1492
+ "spdxElementId": "SPDXRef-Package-4c55f673934ca9f855ad",
1493
1493
  "relationshipType": "DEPENDS_ON",
1494
1494
  "relatedSpdxElement": "SPDXRef-Package-38fe7b92bdac2a86fb61"
1495
1495
  },