@floegence/flowersec-core 2.4.0 → 2.4.2

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
@@ -33,6 +33,65 @@ Retry ownership belongs to `ConnectionController`; applications do not classify
33
33
 
34
34
  When connector options omit a connection timeout, browser and Node.js connectors use the shared ten-second default.
35
35
 
36
+ ### One-shot Node client
37
+
38
+ ```ts
39
+ import { RPCHandlers, connect } from "@floegence/flowersec-core/node";
40
+
41
+ const rpcHandlers = new RPCHandlers();
42
+ rpcHandlers.handleRPC(7, async (payload) => ({ payload }));
43
+ rpcHandlers.handleNotification(8, (payload) => onNotice(payload));
44
+ const session = await connect(lease, {
45
+ origin: "https://app.example",
46
+ rpcHandlers,
47
+ });
48
+ ```
49
+
50
+ ### Long-lived Node client
51
+
52
+ ```ts
53
+ import { RPCHandlers, createConnectionController } from "@floegence/flowersec-core/node";
54
+
55
+ const rpcHandlers = new RPCHandlers();
56
+ rpcHandlers.handleRPC(7, async (payload) => ({ payload }));
57
+ const controller = createConnectionController(source, {
58
+ origin: "https://app.example",
59
+ rpcHandlers,
60
+ });
61
+ controller.start();
62
+ const session = await controller.waitForSession();
63
+ ```
64
+
65
+ The immutable callback definition applies to every generation, while each
66
+ Session gets a fresh router. Terminated Session work is never replayed.
67
+
68
+ For the complete durable `ArtifactLease` spend workflow, see the
69
+ [TypeScript cookbook](../examples/ts/README.md). Node raw-QUIC-only artifacts
70
+ may omit `origin`; providing an absolute HTTP(S) origin enables WebSocket
71
+ candidates. Secure raw QUIC still requires an explicit `tls.ca` trust root.
72
+
73
+ ### Accepted Node server Session
74
+
75
+ ```ts
76
+ import { SessionHandlers, createAcceptor } from "@floegence/flowersec-core/node";
77
+
78
+ const handlers = new SessionHandlers({ maxConcurrentStreams: 32 });
79
+ handlers.handleRPC(7, async (payload) => ({ payload }));
80
+ handlers.handleNotification(8, (payload) => onNotice(payload));
81
+ handlers.handleStream("files/read", async (incoming) => serveFile(incoming));
82
+ const acceptor = await createAcceptor({
83
+ listeners,
84
+ maxInboundStreams: 32,
85
+ authorize,
86
+ resolveHandlers: () => handlers,
87
+ });
88
+ const accepted = await acceptor.accept();
89
+ await accepted.serve();
90
+ ```
91
+
92
+ `RPCHandlers` is available only from the Node entrypoint and cannot register
93
+ application streams. `SessionHandlers` is accepted-server-only.
94
+
36
95
  ## Connection Lifecycle
37
96
 
38
97
  The Browser and Node `connect(...)` operations are one-shot and never reconnect. Long-lived applications can create the runtime-specific `ConnectionController` with a refreshable `ArtifactSource`. Every attempt must return a fresh `ArtifactLease`; a one-time artifact or lease is not a controller source.
@@ -91,9 +150,10 @@ Cold-connection diagnostics require every independent carrier to meet the declar
91
150
 
92
151
  Node.js applications receive the same `Session` contract from `connect(...)`.
93
152
  The Node connector supports WSS, restricted plaintext loopback WebSocket
94
- direct connections, and raw QUIC through the optional native package. It
95
- requires an absolute HTTP(S) `origin`; custom certificate authorities can be
96
- supplied through `tls.ca`, and raw QUIC requires explicit trust roots.
153
+ direct connections, and raw QUIC through the optional native package. WebSocket
154
+ candidates require an absolute HTTP(S) `origin`; raw-QUIC-only artifacts may
155
+ omit it. Custom certificate authorities can be supplied through `tls.ca`, and
156
+ secure raw QUIC requires an explicit trust root.
97
157
 
98
158
  The connectors choose an eligible connection path from the invitation. They do
99
159
  not expose transport selectors, candidate lists, or native carrier objects to application code.
@@ -2,7 +2,7 @@ import { readU32be, u32be } from "../utils/bin.js";
2
2
  import { SDK_DEFAULTS } from "../defaults.js";
3
3
  export const DEFAULT_MAX_JSON_FRAME_BYTES = SDK_DEFAULTS.rpc.maxJsonFrameBytes;
4
4
  const te = new TextEncoder();
5
- const td = new TextDecoder();
5
+ const td = new TextDecoder("utf-8", { fatal: true });
6
6
  // JsonFramingError marks malformed or oversized frames.
7
7
  export class JsonFramingError extends Error {
8
8
  }
@@ -29,5 +29,12 @@ export async function readJsonFrame(readExactly, maxBytes) {
29
29
  if (maxBytes > 0 && n > maxBytes)
30
30
  throw new JsonFramingError("frame too large");
31
31
  const payload = await read(n);
32
- return JSON.parse(td.decode(payload));
32
+ let text;
33
+ try {
34
+ text = td.decode(payload);
35
+ }
36
+ catch {
37
+ throw new JsonFramingError("invalid UTF-8");
38
+ }
39
+ return JSON.parse(text);
33
40
  }
@@ -1,6 +1,6 @@
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, SessionError, SessionHandlers, } from "../node/index.js";
3
+ import { createAcceptor, createArtifactLease, createEndpointSet, createStreamMetadata, createTunnelRuntime, connect, Issuer, parseArtifact, RPCHandlers, SessionError, SessionHandlers, } from "../node/index.js";
4
4
  const RUNTIME = "node-typescript";
5
5
  const ORIGIN = process.env.FLOWERSEC_PARITY_ORIGIN ?? "https://client.example";
6
6
  const ECHO_RPC = 7001;
@@ -56,38 +56,43 @@ class ExecutedCases {
56
56
  }
57
57
  }
58
58
  function createHandlers(path) {
59
- const handlers = new SessionHandlers({ maxConcurrentStreams: 16 });
59
+ const rpcHandlers = new RPCHandlers();
60
+ const sessionHandlers = new SessionHandlers({ maxConcurrentStreams: 16 });
60
61
  const notifications = new SignalQueue();
61
62
  const activeStreams = { value: 0 };
62
63
  const executed = new ExecutedCases();
63
- handlers.handleRPC(ECHO_RPC, async (payload) => {
64
- if (!validValuePayload(payload, "ping"))
65
- return { error: { code: 400, message: "invalid echo payload" } };
66
- executed.record("rpc");
67
- return { payload };
68
- });
69
- handlers.handleRPC(COMPLETE_RPC, async (payload) => {
70
- if (!validValuePayload(payload, "complete"))
71
- return { error: { code: 400, message: "invalid completion payload" } };
72
- executed.record("rekey", "liveness");
73
- notifications.push();
74
- return { payload };
75
- });
76
- handlers.handleRPC(DATAGRAM_READY_RPC, async (payload) => {
77
- if (!validValuePayload(payload, "datagram-ready"))
78
- return {
79
- error: { code: 400, message: "invalid datagram barrier payload" },
80
- };
81
- notifications.push();
82
- return { payload };
83
- });
84
- handlers.handleNotification(NOTIFY_RPC, (payload) => {
85
- if (!validValuePayload(payload, "notify"))
86
- throw new Error("invalid notification payload");
87
- executed.record("notification");
88
- notifications.push();
89
- });
90
- handlers.handleStream(ECHO_KIND, async (incoming) => {
64
+ const registerRPC = (handlers) => {
65
+ handlers.handleRPC(ECHO_RPC, async (payload) => {
66
+ if (!validValuePayload(payload, "ping"))
67
+ return { error: { code: 400, message: "invalid echo payload" } };
68
+ executed.record("rpc");
69
+ return { payload };
70
+ });
71
+ handlers.handleRPC(COMPLETE_RPC, async (payload) => {
72
+ if (!validValuePayload(payload, "complete"))
73
+ return { error: { code: 400, message: "invalid completion payload" } };
74
+ executed.record("rekey", "liveness");
75
+ notifications.push();
76
+ return { payload };
77
+ });
78
+ handlers.handleRPC(DATAGRAM_READY_RPC, async (payload) => {
79
+ if (!validValuePayload(payload, "datagram-ready"))
80
+ return {
81
+ error: { code: 400, message: "invalid datagram barrier payload" },
82
+ };
83
+ notifications.push();
84
+ return { payload };
85
+ });
86
+ handlers.handleNotification(NOTIFY_RPC, (payload) => {
87
+ if (!validValuePayload(payload, "notify"))
88
+ throw new Error("invalid notification payload");
89
+ executed.record("notification");
90
+ notifications.push();
91
+ });
92
+ };
93
+ registerRPC(rpcHandlers);
94
+ registerRPC(sessionHandlers);
95
+ sessionHandlers.handleStream(ECHO_KIND, async (incoming) => {
91
96
  activeStreams.value++;
92
97
  try {
93
98
  if (incoming.metadata.values.cell !== path)
@@ -102,13 +107,13 @@ function createHandlers(path) {
102
107
  activeStreams.value--;
103
108
  }
104
109
  });
105
- handlers.handleStream(RESET_KIND, async (incoming) => {
110
+ sessionHandlers.handleStream(RESET_KIND, async (incoming) => {
106
111
  if (decoder.decode(await readAll(incoming.stream)) !== "reset")
107
112
  throw new Error("invalid reset stream payload");
108
113
  executed.record("stream-reset");
109
114
  throw new Error("intentional parity reset");
110
115
  });
111
- return { handlers, notifications, activeStreams, executed };
116
+ return { rpcHandlers, sessionHandlers, notifications, activeStreams, executed };
112
117
  }
113
118
  function validValuePayload(payload, expected) {
114
119
  if (payload === null || typeof payload !== "object" || Array.isArray(payload))
@@ -315,11 +320,11 @@ function pem(label, encoded) {
315
320
  throw new Error("invalid embedded TLS fixture");
316
321
  return `-----BEGIN ${label}-----\n${lines.join("\n")}\n-----END ${label}-----\n`;
317
322
  }
318
- async function connectArtifact(artifactJSON, relay, handlers) {
323
+ async function connectArtifact(artifactJSON, relay, rpcHandlers) {
319
324
  return await connect(createArtifactLease(parseArtifact(artifactJSON), async () => undefined), {
320
325
  origin: relay.origin,
321
326
  tls: { ca: relay.trust_pem },
322
- handlers,
327
+ rpcHandlers,
323
328
  });
324
329
  }
325
330
  async function runServer(tls, carrier) {
@@ -334,7 +339,7 @@ async function runServer(tls, carrier) {
334
339
  ? { decision: "reject", reason: "invalid_credential" }
335
340
  : { decision: "allow", artifact };
336
341
  },
337
- resolveHandlers: () => state.handlers,
342
+ resolveHandlers: () => state.sessionHandlers,
338
343
  });
339
344
  try {
340
345
  const address = acceptor.addresses()[0];
@@ -387,7 +392,7 @@ async function runClient(input, carrier) {
387
392
  ready.artifact_json === "")
388
393
  throw new Error("invalid direct ready message");
389
394
  const state = createHandlers("direct");
390
- const session = await connectArtifact(ready.artifact_json, ready, state.handlers);
395
+ const session = await connectArtifact(ready.artifact_json, ready, state.rpcHandlers);
391
396
  state.executed.record("admission");
392
397
  await exerciseClient(session, state, "direct", carrier);
393
398
  await session.close().catch((error) => {
@@ -512,7 +517,7 @@ async function runTunnelEndpointB(input, carrier) {
512
517
  if (command.type !== "connect")
513
518
  throw new Error("endpoint B did not receive connect command");
514
519
  const state = createHandlers("tunnel");
515
- const session = await connectArtifact(secondJSON, relay, state.handlers);
520
+ const session = await connectArtifact(secondJSON, relay, state.rpcHandlers);
516
521
  state.executed.record("admission");
517
522
  if (process.env.FLOWERSEC_PARITY_CLIENT_PROFILE !== undefined) {
518
523
  await externalServer(session, state);
@@ -541,7 +546,7 @@ async function runTunnelEndpointA(input, carrier) {
541
546
  ready.endpoint_a_artifact_json === "")
542
547
  throw new Error("invalid endpoint B ready message");
543
548
  const state = createHandlers("tunnel");
544
- const session = await connectArtifact(ready.endpoint_a_artifact_json, ready.relay, state.handlers);
549
+ const session = await connectArtifact(ready.endpoint_a_artifact_json, ready.relay, state.rpcHandlers);
545
550
  state.executed.record("admission");
546
551
  await exerciseClient(session, state, "tunnel", carrier);
547
552
  await session.close().catch(() => undefined);
@@ -20,16 +20,25 @@ export type StreamHandler = (incoming: IncomingStream, options: OperationOptions
20
20
  export type SessionHandlerOptions = Readonly<{
21
21
  maxConcurrentStreams?: number;
22
22
  }>;
23
- export declare class SessionHandlersError extends Error {
23
+ export declare class HandlerRegistrationError extends Error {
24
24
  readonly code: "invalid_handler" | "already_registered" | "frozen";
25
25
  constructor(code: "invalid_handler" | "already_registered" | "frozen");
26
26
  }
27
+ export declare class RPCHandlers {
28
+ constructor();
29
+ handleRPC(typeId: number, handler: RPCHandler): void;
30
+ handleNotification(typeId: number, handler: NotificationHandler): void;
31
+ }
27
32
  export declare class SessionHandlers {
28
33
  constructor(options?: SessionHandlerOptions);
29
34
  handleRPC(typeId: number, handler: RPCHandler): void;
30
35
  handleNotification(typeId: number, handler: NotificationHandler): void;
31
36
  handleStream(kind: string, handler: StreamHandler): void;
32
37
  }
38
+ export type FrozenRPCHandlers = Readonly<{
39
+ requests: ReadonlyMap<number, RPCHandler>;
40
+ notifications: ReadonlyMap<number, NotificationHandler>;
41
+ }>;
33
42
  export type AcceptorListener = Readonly<{
34
43
  carrier: "websocket";
35
44
  path: "direct";
@@ -1,4 +1,5 @@
1
1
  import { RpcRouter } from "../rpc/server.js";
2
+ import { assertRpcError } from "../rpc/validate.js";
2
3
  import { acceptReceivedSessionV2, receiveSessionAdmissionV2, rejectSessionAdmissionV2, } from "../connector/sessionAcceptor.js";
3
4
  import { nodeSessionRuntimeV2 } from "./sessionRuntime.js";
4
5
  import { startNodeWebSocketServer, } from "./webSocketServer.js";
@@ -12,12 +13,23 @@ const DEFAULT_MAX_CONCURRENT_STREAMS = 64;
12
13
  const MAX_CONCURRENT_STREAMS = 128;
13
14
  const DEFAULT_CLEANUP_TIMEOUT_MS = 2_000;
14
15
  const encoder = new TextEncoder();
15
- export class SessionHandlersError extends Error {
16
+ export class HandlerRegistrationError extends Error {
16
17
  code;
17
18
  constructor(code) {
18
- super(`Flowersec session handler registration failed (code=${code})`);
19
+ super(`Flowersec handler registration failed (code=${code})`);
19
20
  this.code = code;
20
- this.name = "SessionHandlersError";
21
+ this.name = "HandlerRegistrationError";
22
+ }
23
+ }
24
+ export class RPCHandlers {
25
+ constructor() {
26
+ rpcHandlerStates.set(this, createRPCHandlerState());
27
+ }
28
+ handleRPC(typeId, handler) {
29
+ registerRPC(mutableRPCHandlerState(this), typeId, handler);
30
+ }
31
+ handleNotification(typeId, handler) {
32
+ registerNotification(mutableRPCHandlerState(this), typeId, handler);
21
33
  }
22
34
  }
23
35
  export class SessionHandlers {
@@ -26,68 +38,107 @@ export class SessionHandlers {
26
38
  if (!Number.isSafeInteger(maximum) ||
27
39
  maximum < 1 ||
28
40
  maximum > MAX_CONCURRENT_STREAMS) {
29
- throw new SessionHandlersError("invalid_handler");
41
+ throw new HandlerRegistrationError("invalid_handler");
30
42
  }
31
43
  sessionHandlerStates.set(this, {
32
44
  maxConcurrentStreams: maximum,
33
- rpc: new Map(),
34
- notifications: new Map(),
45
+ rpc: createRPCHandlerState(),
35
46
  streams: new Map(),
36
47
  frozen: false,
37
48
  });
38
49
  }
39
50
  handleRPC(typeId, handler) {
40
- const state = mutableHandlerState(this);
41
- if (!Number.isSafeInteger(typeId) ||
42
- typeId < 1 ||
43
- typeId > 0xffff_ffff ||
44
- typeof handler !== "function") {
45
- throw new SessionHandlersError("invalid_handler");
46
- }
47
- if (state.rpc.has(typeId))
48
- throw new SessionHandlersError("already_registered");
49
- if (state.notifications.has(typeId))
50
- throw new SessionHandlersError("already_registered");
51
- state.rpc.set(typeId, handler);
51
+ const state = mutableSessionHandlerState(this);
52
+ registerRPC(state.rpc, typeId, handler);
52
53
  }
53
54
  handleNotification(typeId, handler) {
54
- const state = mutableHandlerState(this);
55
- if (!Number.isSafeInteger(typeId) ||
56
- typeId < 1 ||
57
- typeId > 0xffff_ffff ||
58
- typeof handler !== "function") {
59
- throw new SessionHandlersError("invalid_handler");
60
- }
61
- if (state.rpc.has(typeId) || state.notifications.has(typeId))
62
- throw new SessionHandlersError("already_registered");
63
- state.notifications.set(typeId, handler);
55
+ const state = mutableSessionHandlerState(this);
56
+ registerNotification(state.rpc, typeId, handler);
64
57
  }
65
58
  handleStream(kind, handler) {
66
- const state = mutableHandlerState(this);
59
+ const state = mutableSessionHandlerState(this);
67
60
  if (kind.length < 1 ||
68
61
  encoder.encode(kind).length > 255 ||
69
62
  kind === "flowersec.rpc.v2" ||
70
63
  typeof handler !== "function") {
71
- throw new SessionHandlersError("invalid_handler");
64
+ throw new HandlerRegistrationError("invalid_handler");
72
65
  }
73
66
  if (state.streams.has(kind))
74
- throw new SessionHandlersError("already_registered");
67
+ throw new HandlerRegistrationError("already_registered");
75
68
  state.streams.set(kind, handler);
76
69
  }
77
70
  }
71
+ const rpcHandlerStates = new WeakMap();
78
72
  const sessionHandlerStates = new WeakMap();
79
- function mutableHandlerState(handlers) {
73
+ function createRPCHandlerState() {
74
+ return { requests: new Map(), notifications: new Map(), frozen: false };
75
+ }
76
+ function mutableRPCHandlerState(handlers) {
77
+ const state = rpcHandlerStates.get(handlers);
78
+ if (state === undefined)
79
+ throw new HandlerRegistrationError("invalid_handler");
80
+ if (state.frozen)
81
+ throw new HandlerRegistrationError("frozen");
82
+ return state;
83
+ }
84
+ function mutableSessionHandlerState(handlers) {
80
85
  const state = sessionHandlerStates.get(handlers);
81
86
  if (state === undefined)
82
- throw new SessionHandlersError("invalid_handler");
87
+ throw new HandlerRegistrationError("invalid_handler");
83
88
  if (state.frozen)
84
- throw new SessionHandlersError("frozen");
89
+ throw new HandlerRegistrationError("frozen");
85
90
  return state;
86
91
  }
87
- function freezeHandlers(handlers, router) {
88
- const state = mutableHandlerState(handlers);
92
+ function registerRPC(state, typeId, handler) {
93
+ validateRPCRegistration(typeId, handler);
94
+ if (state.requests.has(typeId) || state.notifications.has(typeId)) {
95
+ throw new HandlerRegistrationError("already_registered");
96
+ }
97
+ state.requests.set(typeId, handler);
98
+ }
99
+ function registerNotification(state, typeId, handler) {
100
+ validateRPCRegistration(typeId, handler);
101
+ if (state.requests.has(typeId) || state.notifications.has(typeId)) {
102
+ throw new HandlerRegistrationError("already_registered");
103
+ }
104
+ state.notifications.set(typeId, handler);
105
+ }
106
+ function validateRPCRegistration(typeId, handler) {
107
+ if (!Number.isSafeInteger(typeId)
108
+ || typeId < 1
109
+ || typeId > 0xffff_ffff
110
+ || typeof handler !== "function") {
111
+ throw new HandlerRegistrationError("invalid_handler");
112
+ }
113
+ }
114
+ /** @internal */
115
+ export function freezeRPCHandlers(handlers) {
116
+ const state = rpcHandlerStates.get(handlers);
117
+ if (state === undefined)
118
+ throw new HandlerRegistrationError("invalid_handler");
119
+ if (state.snapshot !== undefined)
120
+ return state.snapshot;
121
+ state.frozen = true;
122
+ state.snapshot = Object.freeze({
123
+ requests: new Map(state.requests),
124
+ notifications: new Map(state.notifications),
125
+ });
126
+ return state.snapshot;
127
+ }
128
+ function freezeRPCHandlerState(state) {
129
+ if (state.snapshot !== undefined)
130
+ return state.snapshot;
89
131
  state.frozen = true;
90
- for (const [typeId, handler] of state.rpc) {
132
+ state.snapshot = Object.freeze({
133
+ requests: new Map(state.requests),
134
+ notifications: new Map(state.notifications),
135
+ });
136
+ return state.snapshot;
137
+ }
138
+ /** @internal */
139
+ export function createRPCRouter(snapshot) {
140
+ const router = new RpcRouter();
141
+ for (const [typeId, handler] of snapshot.requests) {
91
142
  router.register(typeId, async (payload) => {
92
143
  const result = await handler(payload, Object.freeze({ typeId }));
93
144
  if ("error" in result)
@@ -95,34 +146,39 @@ function freezeHandlers(handlers, router) {
95
146
  return { payload: result.payload };
96
147
  });
97
148
  }
98
- for (const [typeId, handler] of state.notifications) {
149
+ for (const [typeId, handler] of snapshot.notifications) {
99
150
  router.onNotify(typeId, (payload) => {
100
151
  void Promise.resolve(handler(payload, Object.freeze({ typeId }))).catch(() => undefined);
101
152
  });
102
153
  }
103
- return Object.freeze({
154
+ return router;
155
+ }
156
+ function freezeSessionHandlers(handlers) {
157
+ const state = sessionHandlerStates.get(handlers);
158
+ if (state === undefined)
159
+ throw new HandlerRegistrationError("invalid_handler");
160
+ if (state.snapshot !== undefined)
161
+ return state.snapshot;
162
+ state.frozen = true;
163
+ state.snapshot = Object.freeze({
164
+ rpc: freezeRPCHandlerState(state.rpc),
104
165
  maxConcurrentStreams: state.maxConcurrentStreams,
105
166
  streams: new Map(state.streams),
106
167
  });
107
- }
108
- /** @internal */
109
- export function freezeSessionHandlersForConnector(handlers) {
110
- const router = new RpcRouter();
111
- freezeHandlers(handlers, router);
112
- return router;
168
+ return state.snapshot;
113
169
  }
114
170
  /** @internal */
115
171
  export function registerSessionStreamsAtomically(handlers, entries) {
116
- const state = mutableHandlerState(handlers);
172
+ const state = mutableSessionHandlerState(handlers);
117
173
  const pending = new Set();
118
174
  for (const [kind, handler] of entries) {
119
175
  if (kind.length < 1 ||
120
176
  encoder.encode(kind).length > 255 ||
121
177
  kind === "flowersec.rpc.v2" ||
122
178
  typeof handler !== "function")
123
- throw new SessionHandlersError("invalid_handler");
179
+ throw new HandlerRegistrationError("invalid_handler");
124
180
  if (state.streams.has(kind) || pending.has(kind))
125
- throw new SessionHandlersError("already_registered");
181
+ throw new HandlerRegistrationError("already_registered");
126
182
  pending.add(kind);
127
183
  }
128
184
  for (const [kind, handler] of entries)
@@ -209,11 +265,27 @@ export class Acceptor {
209
265
  }
210
266
  async close() {
211
267
  const state = acceptorState(this);
212
- state.abort.abort();
213
- state.accepted.close();
214
- await Promise.all(state.listeners.map(async (listener) => await listener.close()));
215
- await boundedCleanup(state.tasks, state.cleanupTimeoutMs);
216
- state.tasks.clear();
268
+ if (state.lifecycle.completion === undefined) {
269
+ state.lifecycle.completion = closeAcceptor(state);
270
+ }
271
+ await withCleanupTimeout(state.lifecycle.completion, state.cleanupTimeoutMs);
272
+ }
273
+ }
274
+ const MAX_CONCURRENT_ADMISSIONS = 64;
275
+ const MAX_PENDING_ACCEPTED_SESSIONS = 64;
276
+ async function closeAcceptor(state) {
277
+ state.abort.abort();
278
+ const queued = state.accepted.close();
279
+ const cleanup = await Promise.allSettled([
280
+ ...state.listeners.map(async (listener) => await listener.close()),
281
+ ...queued.map(async (accepted) => await accepted.close()),
282
+ ]);
283
+ while (state.tasks.size > 0) {
284
+ await Promise.allSettled([...state.tasks]);
285
+ }
286
+ if (cleanup.some((result) => result.status === "rejected") ||
287
+ state.lifecycle.cleanupFailed) {
288
+ throw new SessionError("operation_failed");
217
289
  }
218
290
  }
219
291
  async function releaseLease(state, leaseId) {
@@ -225,7 +297,7 @@ async function authorizeCarrier(state, carrier) {
225
297
  const received = await receiveSessionAdmissionV2(carrier, state.abort.signal);
226
298
  const decoded = received.decoded;
227
299
  const request = runtimeAuthorizationRequestFromDecoded(decoded);
228
- const decision = await abortableCallback(state.options.authorize(request, { signal: state.abort.signal }), state.abort.signal);
300
+ const decision = await state.options.authorize(request, { signal: state.abort.signal });
229
301
  if (decision.decision !== "allow") {
230
302
  return await rejectSessionAdmissionV2(received, {
231
303
  accepted: false,
@@ -237,17 +309,15 @@ async function authorizeCarrier(state, carrier) {
237
309
  ? decision.leaseId
238
310
  : undefined;
239
311
  try {
240
- const router = new RpcRouter();
241
312
  const registry = state.options.resolveHandlers === undefined
242
313
  ? new SessionHandlers()
243
- : await abortableCallback(Promise.resolve(state.options.resolveHandlers(request, {
244
- signal: state.abort.signal,
245
- })), state.abort.signal);
314
+ : await state.options.resolveHandlers(request, { signal: state.abort.signal });
315
+ const handlers = freezeSessionHandlers(registry);
246
316
  const leg = {
247
317
  received,
248
318
  artifact: unwrapArtifact(decision.artifact),
249
- handlers: freezeHandlers(registry, router),
250
- router,
319
+ handlers,
320
+ router: createRPCRouter(handlers.rpc),
251
321
  };
252
322
  if (leaseId !== undefined)
253
323
  leg.leaseId = leaseId;
@@ -271,28 +341,50 @@ async function processCarrier(state, carrier) {
271
341
  const leg = await authorizeCarrier(state, carrier);
272
342
  if (leg === undefined)
273
343
  return;
344
+ let releaseOwnedByAcceptedSession = false;
274
345
  try {
275
346
  if (leg.received.decoded.request.pathKind !== "direct" ||
276
347
  leg.artifact.path.kind !== "direct") {
277
348
  await leg.received.carrier.close().catch(() => undefined);
278
349
  throw new SessionError("operation_failed");
279
350
  }
280
- state.accepted.push(await establishDirect(leg, state.abort.signal, leg.leaseId === undefined ? undefined : () => releaseLease(state, leg.leaseId)));
351
+ const accepted = await establishDirect(leg, state.abort.signal, leg.leaseId === undefined ? undefined : () => releaseLease(state, leg.leaseId));
352
+ releaseOwnedByAcceptedSession = true;
353
+ if (state.accepted.push(accepted) === "rejected") {
354
+ try {
355
+ await accepted.close();
356
+ }
357
+ catch {
358
+ state.lifecycle.cleanupFailed = true;
359
+ throw new SessionError("operation_failed");
360
+ }
361
+ }
281
362
  }
282
363
  catch (error) {
283
- await releaseLease(state, leg.leaseId).catch(() => undefined);
364
+ if (!releaseOwnedByAcceptedSession) {
365
+ await releaseLease(state, leg.leaseId).catch(() => undefined);
366
+ }
284
367
  throw error;
285
368
  }
286
369
  }
287
370
  async function runAcceptLoop(state) {
288
371
  while (!state.abort.signal.aborted) {
289
372
  try {
373
+ while (state.admissions.size >= MAX_CONCURRENT_ADMISSIONS) {
374
+ await Promise.race(state.admissions);
375
+ if (state.abort.signal.aborted)
376
+ return;
377
+ }
290
378
  const carrier = await state.accept({ signal: state.abort.signal });
291
379
  const task = processCarrier(state, carrier)
292
380
  .catch(async () => {
293
381
  await carrier.close().catch(() => undefined);
294
382
  })
295
- .finally(() => state.tasks.delete(task));
383
+ .finally(() => {
384
+ state.admissions.delete(task);
385
+ state.tasks.delete(task);
386
+ });
387
+ state.admissions.add(task);
296
388
  state.tasks.add(task);
297
389
  }
298
390
  catch (error) {
@@ -307,16 +399,19 @@ class AcceptedQueue {
307
399
  waiters = new Set();
308
400
  failure;
309
401
  push(value) {
310
- if (this.failure !== undefined) {
311
- void value.close();
312
- return;
313
- }
402
+ if (this.failure !== undefined)
403
+ return "rejected";
314
404
  const waiter = this.waiters.values().next().value;
315
- if (waiter === undefined)
405
+ if (waiter === undefined) {
406
+ if (this.values.length >= MAX_PENDING_ACCEPTED_SESSIONS)
407
+ return "rejected";
316
408
  this.values.push(value);
409
+ return "queued";
410
+ }
317
411
  else {
318
412
  this.waiters.delete(waiter);
319
413
  waiter.resolve(value);
414
+ return "delivered";
320
415
  }
321
416
  }
322
417
  async shift(signal) {
@@ -357,6 +452,7 @@ class AcceptedQueue {
357
452
  }
358
453
  close() {
359
454
  this.fail(new SessionError("closed"));
455
+ return this.values.splice(0);
360
456
  }
361
457
  }
362
458
  const acceptorStates = new WeakMap();
@@ -427,6 +523,7 @@ export async function createAcceptor(options) {
427
523
  const accepted = new AcceptedQueue();
428
524
  const abort = new AbortController();
429
525
  const tasks = new Set();
526
+ const admissions = new Set();
430
527
  let started = false;
431
528
  const state = {
432
529
  listeners,
@@ -435,7 +532,9 @@ export async function createAcceptor(options) {
435
532
  accepted,
436
533
  abort,
437
534
  tasks,
535
+ admissions,
438
536
  cleanupTimeoutMs,
537
+ lifecycle: { cleanupFailed: false },
439
538
  start() {
440
539
  if (started)
441
540
  return;
@@ -447,52 +546,26 @@ export async function createAcceptor(options) {
447
546
  acceptorStates.set(acceptor, state);
448
547
  return Object.freeze(acceptor);
449
548
  }
450
- async function boundedCleanup(tasks, timeoutMs) {
451
- await Promise.race([
452
- Promise.allSettled([...tasks]).then(() => undefined),
453
- new Promise((resolve) => setTimeout(resolve, timeoutMs)),
454
- ]);
455
- }
456
- async function abortableCallback(promise, signal) {
457
- if (signal.aborted)
458
- throw signal.reason instanceof Error ? signal.reason : new SessionError("closed");
459
- return await new Promise((resolve, reject) => {
460
- let settled = false;
461
- const cleanup = () => signal.removeEventListener("abort", abort);
462
- const abort = () => {
463
- if (settled)
464
- return;
465
- settled = true;
466
- cleanup();
467
- reject(signal.reason instanceof Error ? signal.reason : new SessionError("closed"));
468
- };
469
- signal.addEventListener("abort", abort, { once: true });
470
- void promise.then((value) => {
471
- if (settled)
472
- return;
473
- settled = true;
474
- cleanup();
475
- resolve(value);
476
- }, (error) => {
477
- if (settled)
478
- return;
479
- settled = true;
480
- cleanup();
481
- reject(error);
482
- });
483
- });
549
+ async function withCleanupTimeout(completion, timeoutMs) {
550
+ let timer;
551
+ try {
552
+ await Promise.race([
553
+ completion,
554
+ new Promise((_, reject) => {
555
+ timer = setTimeout(() => reject(new SessionError("timeout")), timeoutMs);
556
+ }),
557
+ ]);
558
+ }
559
+ finally {
560
+ if (timer !== undefined)
561
+ clearTimeout(timer);
562
+ }
484
563
  }
485
564
  function validRPCError(error) {
486
- if (!Number.isSafeInteger(error.code) ||
487
- error.code < 1 ||
488
- error.code > 0xffff_ffff) {
489
- return { code: 500, message: "handler failed" };
565
+ try {
566
+ return assertRpcError(error);
490
567
  }
491
- if (error.message !== undefined &&
492
- encoder.encode(error.message).length > 1024) {
568
+ catch {
493
569
  return { code: 500, message: "handler failed" };
494
570
  }
495
- return error.message === undefined
496
- ? { code: error.code }
497
- : { code: error.code, message: error.message };
498
571
  }
@@ -1,22 +1,23 @@
1
1
  import type { ArtifactLease } from "../public/artifactLease.js";
2
2
  import type { Session } from "../public/contract.js";
3
3
  import { type ArtifactSource, type ConnectionController } from "../connectionController.js";
4
- import { type SessionHandlers } from "./acceptor.js";
4
+ import { type RPCHandlers } from "./acceptor.js";
5
5
  export type SessionTLSOptions = Readonly<{
6
6
  ca?: string | Uint8Array;
7
7
  }>;
8
8
  export type SessionOptions = Readonly<{
9
- origin: string;
9
+ origin?: string;
10
10
  signal?: AbortSignal;
11
11
  connectTimeoutMs?: number;
12
12
  tls?: SessionTLSOptions;
13
- handlers?: SessionHandlers;
13
+ rpcHandlers?: RPCHandlers;
14
14
  }>;
15
15
  export type ConnectionControllerOptions = Readonly<{
16
- origin: string;
16
+ origin?: string;
17
17
  connectTimeoutMs?: number;
18
18
  tls?: SessionTLSOptions;
19
19
  maximumAttempts?: number;
20
+ rpcHandlers?: RPCHandlers;
20
21
  }>;
21
22
  export declare function createConnectionController(source: ArtifactSource, options: ConnectionControllerOptions): ConnectionController;
22
23
  export declare function connect(lease: ArtifactLease, options: SessionOptions): Promise<Session>;
@@ -7,33 +7,24 @@ import { projectSessionV2 } from "../v2/publicSession.js";
7
7
  import { ConnectError } from "../public/connectError.js";
8
8
  import { nodeSessionRuntimeV2 } from "./sessionRuntime.js";
9
9
  import { createConnectionControllerV2, } from "../connectionController.js";
10
- import { freezeSessionHandlersForConnector, } from "./acceptor.js";
10
+ import { createRPCRouter, freezeRPCHandlers, } from "./acceptor.js";
11
11
  import { createNativeRawQuicDriver, NativeTransportUnavailableError, tryLoadNativeTransportAddon, } from "./nativeTransportAddon.js";
12
- import { createNodeRawQuicClientV2 } from "./rawQuicAdapter.js";
12
+ import { createNodeRawQuicClientV2, normalizeCertificateChain } from "./rawQuicAdapter.js";
13
13
  export function createConnectionController(source, options) {
14
+ const normalized = normalizeNodeOptions(options);
14
15
  const controllerOptions = options.maximumAttempts === undefined
15
16
  ? {}
16
17
  : { maximumAttempts: options.maximumAttempts };
17
- return createConnectionControllerV2(source, async (lease, signal) => await connect(lease, {
18
- origin: options.origin,
19
- signal,
20
- ...(options.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: options.connectTimeoutMs }),
21
- ...(options.tls === undefined ? {} : { tls: options.tls }),
22
- }), controllerOptions);
18
+ return createConnectionControllerV2(source, async (lease, signal) => await connectWithRPCSnapshot(lease, normalized, signal), controllerOptions);
23
19
  }
24
20
  export async function connect(lease, options) {
25
- let origin;
26
- let wsFactory;
27
- try {
28
- origin = normalizeOrigin(options.origin);
29
- wsFactory = createNodeWsFactory(options.tls);
30
- }
31
- catch {
32
- throw new ConnectError("invalid_options");
33
- }
34
- const rpcRouter = options.handlers === undefined
21
+ const normalized = normalizeNodeOptions(options);
22
+ return await connectWithRPCSnapshot(lease, normalized, options.signal);
23
+ }
24
+ async function connectWithRPCSnapshot(lease, options, signal) {
25
+ const rpcRouter = options.rpcSnapshot === undefined
35
26
  ? undefined
36
- : freezeSessionHandlersForConnector(options.handlers);
27
+ : createRPCRouter(options.rpcSnapshot);
37
28
  // WebSocket sessions do not require the optional native raw QUIC addon. Load
38
29
  // it only when a raw QUIC candidate is actually selected by the connector.
39
30
  const nativeAddon = tryLoadNativeTransportAddon();
@@ -45,8 +36,14 @@ export async function connect(lease, options) {
45
36
  }
46
37
  return await createNodeRawQuicClientV2(createNativeRawQuicDriver(nativeAddon), candidate, artifact, { ca: options.tls.ca }, signal, options.connectTimeoutMs);
47
38
  });
39
+ const origin = options.origin;
40
+ let websocketFactory;
41
+ if (origin !== undefined) {
42
+ const wsFactory = createNodeWsFactory(options.tls);
43
+ websocketFactory = createWebSocketCandidateFactoryV2((url, subprotocol) => wsFactory(url, origin, subprotocol));
44
+ }
48
45
  const connector = new SessionConnectorV2(lease, composeCandidateAttemptFactoryV2({
49
- websocket: createWebSocketCandidateFactoryV2((url, subprotocol) => wsFactory(url, origin, subprotocol)),
46
+ ...(websocketFactory === undefined ? {} : { websocket: websocketFactory }),
50
47
  raw_quic: rawQuicFactory,
51
48
  }), {
52
49
  capability: detectNodeRuntimeCapabilityV2(nativeAddon !== undefined),
@@ -54,10 +51,34 @@ export async function connect(lease, options) {
54
51
  ...(rpcRouter === undefined ? {} : { rpcRouter }),
55
52
  ...(options.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: options.connectTimeoutMs }),
56
53
  });
57
- const result = await connector.connect(options.signal === undefined ? {} : { signal: options.signal });
54
+ const result = await connector.connect(signal === undefined ? {} : { signal });
58
55
  return projectSessionV2(result.session);
59
56
  }
57
+ function normalizeNodeOptions(options) {
58
+ try {
59
+ const origin = normalizeOrigin(options.origin);
60
+ if (options.connectTimeoutMs !== undefined &&
61
+ (!Number.isSafeInteger(options.connectTimeoutMs) || options.connectTimeoutMs < 1)) {
62
+ throw new RangeError("connectTimeoutMs must be a positive safe integer");
63
+ }
64
+ if (origin !== undefined)
65
+ createNodeWsFactory(options.tls);
66
+ if (options.tls?.ca !== undefined)
67
+ normalizeCertificateChain(options.tls.ca);
68
+ return Object.freeze({
69
+ ...(origin === undefined ? {} : { origin }),
70
+ ...(options.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: options.connectTimeoutMs }),
71
+ ...(options.tls === undefined ? {} : { tls: options.tls }),
72
+ ...(options.rpcHandlers === undefined ? {} : { rpcSnapshot: freezeRPCHandlers(options.rpcHandlers) }),
73
+ });
74
+ }
75
+ catch {
76
+ throw new ConnectError("invalid_options");
77
+ }
78
+ }
60
79
  function normalizeOrigin(input) {
80
+ if (input === undefined)
81
+ return undefined;
61
82
  let parsed;
62
83
  try {
63
84
  parsed = new URL(input);
@@ -1,4 +1,4 @@
1
- import { randomBytes as nodeRandomBytes } from "node:crypto";
1
+ import { randomBytes as nodeRandomBytes, timingSafeEqual } from "node:crypto";
2
2
  import { buildFSB2RequestV2, canonicalizeCandidatesV2, computeSessionContractHashV2, decodeArtifactV2JSON, decodeFSB2RequestV2, encodeArtifactV2JSON, encodeFSB2RequestV2, } from "../v2/artifact.js";
3
3
  import { wrapArtifact } from "../public/artifact.js";
4
4
  import { base64urlDecode, base64urlEncode } from "../utils/base64url.js";
@@ -413,7 +413,11 @@ function validTCPAddress(value) {
413
413
  return port >= 1 && port <= 65_535;
414
414
  }
415
415
  function validObservedText(value) { return value.length > 0 && value.length <= 512 && !/[\u0000-\u001f\u007f]/u.test(value); }
416
- function bytesEqual(a, b) { return a.length === b.length && a.every((value, index) => value === b[index]); }
416
+ function bytesEqual(a, b) {
417
+ if (a.length !== b.length)
418
+ return false;
419
+ return timingSafeEqual(a, b);
420
+ }
417
421
  function strictObject(bytes) {
418
422
  let value;
419
423
  try {
@@ -1,5 +1,5 @@
1
1
  export { connect, createConnectionController } from "./connectSession.js";
2
- export { AcceptedSession, Acceptor, SessionHandlers, SessionHandlersError, createAcceptor, } from "./acceptor.js";
2
+ export { AcceptedSession, Acceptor, HandlerRegistrationError, RPCHandlers, SessionHandlers, createAcceptor, } from "./acceptor.js";
3
3
  export type { AcceptorListener, AcceptorOptions, AuthorizationDecision, RPCHandler, RPCHandlerResult, NotificationHandler, SessionHandlerOptions, StreamHandler, } from "./acceptor.js";
4
4
  export { TunnelRuntime, createTunnelRuntime } from "./tunnelRuntime.js";
5
5
  export type { TunnelAuthorizationDecision, TunnelRuntimeListener, TunnelRuntimeOptions, } from "./tunnelRuntime.js";
@@ -1,5 +1,5 @@
1
1
  export { connect, createConnectionController } from "./connectSession.js";
2
- export { AcceptedSession, Acceptor, SessionHandlers, SessionHandlersError, createAcceptor, } from "./acceptor.js";
2
+ export { AcceptedSession, Acceptor, HandlerRegistrationError, RPCHandlers, SessionHandlers, createAcceptor, } from "./acceptor.js";
3
3
  export { TunnelRuntime, createTunnelRuntime } from "./tunnelRuntime.js";
4
4
  export { AuthorizationRecord, AuthorizationResponse, TunnelAuthorizationResponse, ControlPlaneError, EndpointSet, IssuedArtifact, Issuer, RuntimeAuthorizationRequest, authorizeRuntime, authorizeTunnelRuntime, createEndpointSet, parseAuthorizationRecord, parseRuntimeAuthorizationRequest, rejectRuntime, rejectTunnelRuntime, } from "./controlplane.js";
5
5
  export { ProxyServer, ProxyServerError } from "./proxyServer.js";
@@ -36,13 +36,6 @@ export async function commitArtifactLeaseSpend(lease, signal) {
36
36
  const leaseState = artifactLeaseStates.get(lease);
37
37
  if (leaseState === undefined || leaseState.state !== "idle")
38
38
  throw new ArtifactLeaseError();
39
- leaseState.state = "spending";
40
- try {
41
- await leaseState.commitSpend(signal);
42
- leaseState.state = "consumed";
43
- }
44
- catch (error) {
45
- leaseState.state = "idle";
46
- throw error;
47
- }
39
+ leaseState.state = "consumed";
40
+ await leaseState.commitSpend(signal);
48
41
  }
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_MAX_JSON_FRAME_BYTES, readJsonFrame, writeJsonFrame } from "../framing/jsonframe.js";
2
- import { assertRpcEnvelope } from "./validate.js";
2
+ import { assertRpcEnvelope, assertRpcError } from "./validate.js";
3
3
  import { SDK_DEFAULTS } from "../defaults.js";
4
4
  const DEFAULT_RPC_SERVER_OPTIONS = Object.freeze({
5
5
  maxConcurrentRequests: SDK_DEFAULTS.rpc.maxConcurrentRequests,
@@ -217,12 +217,21 @@ export class RpcServer {
217
217
  waiters.shift()?.();
218
218
  }
219
219
  async writeResponse(request, out) {
220
+ let error;
221
+ if (out.error != null) {
222
+ try {
223
+ error = assertRpcError(out.error);
224
+ }
225
+ catch {
226
+ error = { code: 500, message: "internal error" };
227
+ }
228
+ }
220
229
  const resp = {
221
230
  type_id: request.type_id,
222
231
  request_id: 0,
223
232
  response_to: request.request_id,
224
233
  payload: out.payload,
225
- ...(out.error != null ? { error: out.error } : {}),
234
+ ...(error != null ? { error } : {}),
226
235
  };
227
236
  await this.writeEnvelope(resp);
228
237
  }
@@ -1,2 +1,3 @@
1
- import type { RpcEnvelope } from "./wire.js";
1
+ import type { RpcEnvelope, RpcError } from "./wire.js";
2
2
  export declare function assertRpcEnvelope(v: unknown): RpcEnvelope;
3
+ export declare function assertRpcError(value: unknown): RpcError;
@@ -1,4 +1,6 @@
1
1
  import { isSafeU32Number, isSafeU64Number } from "../utils/number.js";
2
+ const encoder = new TextEncoder();
3
+ const strictDecoder = new TextDecoder("utf-8", { fatal: true });
2
4
  // assertRpcEnvelope validates numeric fields that are u32/u64 in the IDL.
3
5
  //
4
6
  // The wire format is JSON, so JS numbers are used. For u64 we enforce the safe integer range
@@ -15,13 +17,29 @@ export function assertRpcEnvelope(v) {
15
17
  throw new Error("bad rpc envelope: response_to");
16
18
  // payload: unknown (JSON)
17
19
  if (o.error != null) {
18
- if (typeof o.error !== "object" || o.error == null)
19
- throw new Error("bad rpc envelope: error");
20
- if (!isSafeU32Number(o.error.code))
21
- throw new Error("bad rpc envelope: error.code");
22
- const msg = o.error.message;
23
- if (msg !== undefined && typeof msg !== "string")
24
- throw new Error("bad rpc envelope: error.message");
20
+ assertRpcError(o.error);
25
21
  }
26
22
  return o;
27
23
  }
24
+ export function assertRpcError(value) {
25
+ if (typeof value !== "object" || value == null)
26
+ throw new Error("bad rpc envelope: error");
27
+ const error = value;
28
+ if (Object.keys(error).some((key) => key !== "code" && key !== "message")) {
29
+ throw new Error("bad rpc envelope: error shape");
30
+ }
31
+ if (!isSafeU32Number(error.code) || error.code === 0) {
32
+ throw new Error("bad rpc envelope: error.code");
33
+ }
34
+ const message = error.message;
35
+ if (message !== undefined && typeof message !== "string") {
36
+ throw new Error("bad rpc envelope: error.message");
37
+ }
38
+ if (typeof message === "string") {
39
+ const encoded = encoder.encode(message);
40
+ if (encoded.byteLength > 1_024 || strictDecoder.decode(encoded) !== message) {
41
+ throw new Error("bad rpc envelope: error.message");
42
+ }
43
+ }
44
+ return message === undefined ? { code: error.code } : { code: error.code, message };
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/flowersec-core",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Flowersec core TypeScript library for carrier-neutral encrypted sessions and multiplexed streams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -77,7 +77,7 @@
77
77
  "ws": "^8.21.2"
78
78
  },
79
79
  "optionalDependencies": {
80
- "@floegence/flowersec-node-native": "2.4.0"
80
+ "@floegence/flowersec-node-native": "2.4.2"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@playwright/test": "1.62.1",
@@ -95,5 +95,5 @@
95
95
  "vite": "^8.2.1",
96
96
  "vitest": "4.1.10"
97
97
  },
98
- "flowersecSourceCommit": "0849589d3516564cc2f32ce51f15a1bba4aa3aa6"
98
+ "flowersecSourceCommit": "1393acdb891a6c935a435bee9068f65b68268795"
99
99
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:c502284d-2e30-5f97-8204-2cdd20d4483a",
4
+ "serialNumber": "urn:uuid:a7cb7385-1123-5452-8424-bbcb7904bbbb",
5
5
  "version": 1,
6
6
  "metadata": {
7
7
  "component": {
8
8
  "type": "library",
9
9
  "name": "@floegence/flowersec-core",
10
- "version": "2.4.0",
11
- "purl": "pkg:npm/%40floegence/flowersec-core@2.4.0",
12
- "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.4.0"
10
+ "version": "2.4.2",
11
+ "purl": "pkg:npm/%40floegence/flowersec-core@2.4.2",
12
+ "bom-ref": "pkg:npm/%40floegence/flowersec-core@2.4.2"
13
13
  },
14
14
  "properties": [
15
15
  {
16
16
  "name": "flowersec:source-inventory-sha256",
17
- "value": "1c377fd1fb5ea77eadd418a3990b5ae9585edf1a740a95901287df743cc2ec9c"
17
+ "value": "59d233df0e12f2c371ab542c83edf97a2968a75c7d17c057e453b97f530a90e4"
18
18
  }
19
19
  ]
20
20
  },
@@ -190,7 +190,7 @@
190
190
  ],
191
191
  "dependencies": [
192
192
  {
193
- "ref": "pkg:npm/%40floegence/flowersec-core@2.4.0",
193
+ "ref": "pkg:npm/%40floegence/flowersec-core@2.4.2",
194
194
  "dependsOn": [
195
195
  "pkg:npm/%40noble/ciphers@2.3.0",
196
196
  "pkg:npm/%40noble/curves@2.3.0",
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/1c377fd1fb5ea77eadd418a3990b5ae9585edf1a740a95901287df743cc2ec9c",
6
+ "documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/59d233df0e12f2c371ab542c83edf97a2968a75c7d17c057e453b97f530a90e4",
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-d5c0c7bb3c27968de167",
17
- "versionInfo": "2.4.0",
16
+ "SPDXID": "SPDXRef-Package-9eec732cc29715c69e1e",
17
+ "versionInfo": "2.4.2",
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: 1c377fd1fb5ea77eadd418a3990b5ae9585edf1a740a95901287df743cc2ec9c",
23
+ "comment": "Flowersec source inventory SHA-256: 59d233df0e12f2c371ab542c83edf97a2968a75c7d17c057e453b97f530a90e4",
24
24
  "externalRefs": [
25
25
  {
26
26
  "referenceCategory": "PACKAGE-MANAGER",
27
27
  "referenceType": "purl",
28
- "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.4.0"
28
+ "referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.4.2"
29
29
  }
30
30
  ]
31
31
  },
@@ -136,30 +136,30 @@
136
136
  {
137
137
  "spdxElementId": "SPDXRef-DOCUMENT",
138
138
  "relationshipType": "DESCRIBES",
139
- "relatedSpdxElement": "SPDXRef-Package-d5c0c7bb3c27968de167"
139
+ "relatedSpdxElement": "SPDXRef-Package-9eec732cc29715c69e1e"
140
140
  },
141
141
  {
142
- "spdxElementId": "SPDXRef-Package-d5c0c7bb3c27968de167",
142
+ "spdxElementId": "SPDXRef-Package-9eec732cc29715c69e1e",
143
143
  "relationshipType": "DEPENDS_ON",
144
144
  "relatedSpdxElement": "SPDXRef-Package-5ce913a03239b02770fb"
145
145
  },
146
146
  {
147
- "spdxElementId": "SPDXRef-Package-d5c0c7bb3c27968de167",
147
+ "spdxElementId": "SPDXRef-Package-9eec732cc29715c69e1e",
148
148
  "relationshipType": "DEPENDS_ON",
149
149
  "relatedSpdxElement": "SPDXRef-Package-01009adf60db02c13634"
150
150
  },
151
151
  {
152
- "spdxElementId": "SPDXRef-Package-d5c0c7bb3c27968de167",
152
+ "spdxElementId": "SPDXRef-Package-9eec732cc29715c69e1e",
153
153
  "relationshipType": "DEPENDS_ON",
154
154
  "relatedSpdxElement": "SPDXRef-Package-8815b117c8a1d5f7eeb0"
155
155
  },
156
156
  {
157
- "spdxElementId": "SPDXRef-Package-d5c0c7bb3c27968de167",
157
+ "spdxElementId": "SPDXRef-Package-9eec732cc29715c69e1e",
158
158
  "relationshipType": "DEPENDS_ON",
159
159
  "relatedSpdxElement": "SPDXRef-Package-f8b45a289df643042b94"
160
160
  },
161
161
  {
162
- "spdxElementId": "SPDXRef-Package-d5c0c7bb3c27968de167",
162
+ "spdxElementId": "SPDXRef-Package-9eec732cc29715c69e1e",
163
163
  "relationshipType": "DEPENDS_ON",
164
164
  "relatedSpdxElement": "SPDXRef-Package-b779412f685822663496"
165
165
  },