@floegence/flowersec-core 2.3.10 → 2.4.1
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 +64 -4
- package/dist/interop/serverParityPeer.js +43 -38
- package/dist/node/acceptor.d.ts +10 -1
- package/dist/node/acceptor.js +106 -51
- package/dist/node/connectSession.d.ts +5 -4
- package/dist/node/connectSession.js +42 -21
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/public/contract.d.ts +3 -3
- package/dist/v2/publicSession.js +84 -4
- package/package.json +3 -3
- package/sbom/cyclonedx.json +6 -6
- package/sbom/spdx.json +11 -11
package/README.md
CHANGED
|
@@ -29,10 +29,69 @@ The root type exports are:
|
|
|
29
29
|
|
|
30
30
|
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.
|
|
31
31
|
|
|
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
|
|
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
|
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.
|
|
95
|
-
|
|
96
|
-
supplied through `tls.ca`, and
|
|
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.
|
|
@@ -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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|
package/dist/node/acceptor.d.ts
CHANGED
|
@@ -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
|
|
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";
|
package/dist/node/acceptor.js
CHANGED
|
@@ -12,12 +12,23 @@ const DEFAULT_MAX_CONCURRENT_STREAMS = 64;
|
|
|
12
12
|
const MAX_CONCURRENT_STREAMS = 128;
|
|
13
13
|
const DEFAULT_CLEANUP_TIMEOUT_MS = 2_000;
|
|
14
14
|
const encoder = new TextEncoder();
|
|
15
|
-
export class
|
|
15
|
+
export class HandlerRegistrationError extends Error {
|
|
16
16
|
code;
|
|
17
17
|
constructor(code) {
|
|
18
|
-
super(`Flowersec
|
|
18
|
+
super(`Flowersec handler registration failed (code=${code})`);
|
|
19
19
|
this.code = code;
|
|
20
|
-
this.name = "
|
|
20
|
+
this.name = "HandlerRegistrationError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class RPCHandlers {
|
|
24
|
+
constructor() {
|
|
25
|
+
rpcHandlerStates.set(this, createRPCHandlerState());
|
|
26
|
+
}
|
|
27
|
+
handleRPC(typeId, handler) {
|
|
28
|
+
registerRPC(mutableRPCHandlerState(this), typeId, handler);
|
|
29
|
+
}
|
|
30
|
+
handleNotification(typeId, handler) {
|
|
31
|
+
registerNotification(mutableRPCHandlerState(this), typeId, handler);
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
export class SessionHandlers {
|
|
@@ -26,68 +37,107 @@ export class SessionHandlers {
|
|
|
26
37
|
if (!Number.isSafeInteger(maximum) ||
|
|
27
38
|
maximum < 1 ||
|
|
28
39
|
maximum > MAX_CONCURRENT_STREAMS) {
|
|
29
|
-
throw new
|
|
40
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
30
41
|
}
|
|
31
42
|
sessionHandlerStates.set(this, {
|
|
32
43
|
maxConcurrentStreams: maximum,
|
|
33
|
-
rpc:
|
|
34
|
-
notifications: new Map(),
|
|
44
|
+
rpc: createRPCHandlerState(),
|
|
35
45
|
streams: new Map(),
|
|
36
46
|
frozen: false,
|
|
37
47
|
});
|
|
38
48
|
}
|
|
39
49
|
handleRPC(typeId, handler) {
|
|
40
|
-
const state =
|
|
41
|
-
|
|
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);
|
|
50
|
+
const state = mutableSessionHandlerState(this);
|
|
51
|
+
registerRPC(state.rpc, typeId, handler);
|
|
52
52
|
}
|
|
53
53
|
handleNotification(typeId, handler) {
|
|
54
|
-
const state =
|
|
55
|
-
|
|
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);
|
|
54
|
+
const state = mutableSessionHandlerState(this);
|
|
55
|
+
registerNotification(state.rpc, typeId, handler);
|
|
64
56
|
}
|
|
65
57
|
handleStream(kind, handler) {
|
|
66
|
-
const state =
|
|
58
|
+
const state = mutableSessionHandlerState(this);
|
|
67
59
|
if (kind.length < 1 ||
|
|
68
60
|
encoder.encode(kind).length > 255 ||
|
|
69
61
|
kind === "flowersec.rpc.v2" ||
|
|
70
62
|
typeof handler !== "function") {
|
|
71
|
-
throw new
|
|
63
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
72
64
|
}
|
|
73
65
|
if (state.streams.has(kind))
|
|
74
|
-
throw new
|
|
66
|
+
throw new HandlerRegistrationError("already_registered");
|
|
75
67
|
state.streams.set(kind, handler);
|
|
76
68
|
}
|
|
77
69
|
}
|
|
70
|
+
const rpcHandlerStates = new WeakMap();
|
|
78
71
|
const sessionHandlerStates = new WeakMap();
|
|
79
|
-
function
|
|
72
|
+
function createRPCHandlerState() {
|
|
73
|
+
return { requests: new Map(), notifications: new Map(), frozen: false };
|
|
74
|
+
}
|
|
75
|
+
function mutableRPCHandlerState(handlers) {
|
|
76
|
+
const state = rpcHandlerStates.get(handlers);
|
|
77
|
+
if (state === undefined)
|
|
78
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
79
|
+
if (state.frozen)
|
|
80
|
+
throw new HandlerRegistrationError("frozen");
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
function mutableSessionHandlerState(handlers) {
|
|
80
84
|
const state = sessionHandlerStates.get(handlers);
|
|
81
85
|
if (state === undefined)
|
|
82
|
-
throw new
|
|
86
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
83
87
|
if (state.frozen)
|
|
84
|
-
throw new
|
|
88
|
+
throw new HandlerRegistrationError("frozen");
|
|
85
89
|
return state;
|
|
86
90
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
91
|
+
function registerRPC(state, typeId, handler) {
|
|
92
|
+
validateRPCRegistration(typeId, handler);
|
|
93
|
+
if (state.requests.has(typeId) || state.notifications.has(typeId)) {
|
|
94
|
+
throw new HandlerRegistrationError("already_registered");
|
|
95
|
+
}
|
|
96
|
+
state.requests.set(typeId, handler);
|
|
97
|
+
}
|
|
98
|
+
function registerNotification(state, typeId, handler) {
|
|
99
|
+
validateRPCRegistration(typeId, handler);
|
|
100
|
+
if (state.requests.has(typeId) || state.notifications.has(typeId)) {
|
|
101
|
+
throw new HandlerRegistrationError("already_registered");
|
|
102
|
+
}
|
|
103
|
+
state.notifications.set(typeId, handler);
|
|
104
|
+
}
|
|
105
|
+
function validateRPCRegistration(typeId, handler) {
|
|
106
|
+
if (!Number.isSafeInteger(typeId)
|
|
107
|
+
|| typeId < 1
|
|
108
|
+
|| typeId > 0xffff_ffff
|
|
109
|
+
|| typeof handler !== "function") {
|
|
110
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** @internal */
|
|
114
|
+
export function freezeRPCHandlers(handlers) {
|
|
115
|
+
const state = rpcHandlerStates.get(handlers);
|
|
116
|
+
if (state === undefined)
|
|
117
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
118
|
+
if (state.snapshot !== undefined)
|
|
119
|
+
return state.snapshot;
|
|
120
|
+
state.frozen = true;
|
|
121
|
+
state.snapshot = Object.freeze({
|
|
122
|
+
requests: new Map(state.requests),
|
|
123
|
+
notifications: new Map(state.notifications),
|
|
124
|
+
});
|
|
125
|
+
return state.snapshot;
|
|
126
|
+
}
|
|
127
|
+
function freezeRPCHandlerState(state) {
|
|
128
|
+
if (state.snapshot !== undefined)
|
|
129
|
+
return state.snapshot;
|
|
89
130
|
state.frozen = true;
|
|
90
|
-
|
|
131
|
+
state.snapshot = Object.freeze({
|
|
132
|
+
requests: new Map(state.requests),
|
|
133
|
+
notifications: new Map(state.notifications),
|
|
134
|
+
});
|
|
135
|
+
return state.snapshot;
|
|
136
|
+
}
|
|
137
|
+
/** @internal */
|
|
138
|
+
export function createRPCRouter(snapshot) {
|
|
139
|
+
const router = new RpcRouter();
|
|
140
|
+
for (const [typeId, handler] of snapshot.requests) {
|
|
91
141
|
router.register(typeId, async (payload) => {
|
|
92
142
|
const result = await handler(payload, Object.freeze({ typeId }));
|
|
93
143
|
if ("error" in result)
|
|
@@ -95,34 +145,39 @@ function freezeHandlers(handlers, router) {
|
|
|
95
145
|
return { payload: result.payload };
|
|
96
146
|
});
|
|
97
147
|
}
|
|
98
|
-
for (const [typeId, handler] of
|
|
148
|
+
for (const [typeId, handler] of snapshot.notifications) {
|
|
99
149
|
router.onNotify(typeId, (payload) => {
|
|
100
150
|
void Promise.resolve(handler(payload, Object.freeze({ typeId }))).catch(() => undefined);
|
|
101
151
|
});
|
|
102
152
|
}
|
|
103
|
-
return
|
|
153
|
+
return router;
|
|
154
|
+
}
|
|
155
|
+
function freezeSessionHandlers(handlers) {
|
|
156
|
+
const state = sessionHandlerStates.get(handlers);
|
|
157
|
+
if (state === undefined)
|
|
158
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
159
|
+
if (state.snapshot !== undefined)
|
|
160
|
+
return state.snapshot;
|
|
161
|
+
state.frozen = true;
|
|
162
|
+
state.snapshot = Object.freeze({
|
|
163
|
+
rpc: freezeRPCHandlerState(state.rpc),
|
|
104
164
|
maxConcurrentStreams: state.maxConcurrentStreams,
|
|
105
165
|
streams: new Map(state.streams),
|
|
106
166
|
});
|
|
107
|
-
|
|
108
|
-
/** @internal */
|
|
109
|
-
export function freezeSessionHandlersForConnector(handlers) {
|
|
110
|
-
const router = new RpcRouter();
|
|
111
|
-
freezeHandlers(handlers, router);
|
|
112
|
-
return router;
|
|
167
|
+
return state.snapshot;
|
|
113
168
|
}
|
|
114
169
|
/** @internal */
|
|
115
170
|
export function registerSessionStreamsAtomically(handlers, entries) {
|
|
116
|
-
const state =
|
|
171
|
+
const state = mutableSessionHandlerState(handlers);
|
|
117
172
|
const pending = new Set();
|
|
118
173
|
for (const [kind, handler] of entries) {
|
|
119
174
|
if (kind.length < 1 ||
|
|
120
175
|
encoder.encode(kind).length > 255 ||
|
|
121
176
|
kind === "flowersec.rpc.v2" ||
|
|
122
177
|
typeof handler !== "function")
|
|
123
|
-
throw new
|
|
178
|
+
throw new HandlerRegistrationError("invalid_handler");
|
|
124
179
|
if (state.streams.has(kind) || pending.has(kind))
|
|
125
|
-
throw new
|
|
180
|
+
throw new HandlerRegistrationError("already_registered");
|
|
126
181
|
pending.add(kind);
|
|
127
182
|
}
|
|
128
183
|
for (const [kind, handler] of entries)
|
|
@@ -237,17 +292,17 @@ async function authorizeCarrier(state, carrier) {
|
|
|
237
292
|
? decision.leaseId
|
|
238
293
|
: undefined;
|
|
239
294
|
try {
|
|
240
|
-
const router = new RpcRouter();
|
|
241
295
|
const registry = state.options.resolveHandlers === undefined
|
|
242
296
|
? new SessionHandlers()
|
|
243
297
|
: await abortableCallback(Promise.resolve(state.options.resolveHandlers(request, {
|
|
244
298
|
signal: state.abort.signal,
|
|
245
299
|
})), state.abort.signal);
|
|
300
|
+
const handlers = freezeSessionHandlers(registry);
|
|
246
301
|
const leg = {
|
|
247
302
|
received,
|
|
248
303
|
artifact: unwrapArtifact(decision.artifact),
|
|
249
|
-
handlers
|
|
250
|
-
router,
|
|
304
|
+
handlers,
|
|
305
|
+
router: createRPCRouter(handlers.rpc),
|
|
251
306
|
};
|
|
252
307
|
if (leaseId !== undefined)
|
|
253
308
|
leg.leaseId = leaseId;
|
|
@@ -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
|
|
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
|
|
9
|
+
origin?: string;
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
connectTimeoutMs?: number;
|
|
12
12
|
tls?: SessionTLSOptions;
|
|
13
|
-
|
|
13
|
+
rpcHandlers?: RPCHandlers;
|
|
14
14
|
}>;
|
|
15
15
|
export type ConnectionControllerOptions = Readonly<{
|
|
16
|
-
origin
|
|
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 {
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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(
|
|
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);
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { connect, createConnectionController } from "./connectSession.js";
|
|
2
|
-
export { AcceptedSession, Acceptor,
|
|
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";
|
package/dist/node/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { connect, createConnectionController } from "./connectSession.js";
|
|
2
|
-
export { AcceptedSession, Acceptor,
|
|
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";
|
|
@@ -42,9 +42,9 @@ export type RpcResult<Response = unknown> = Readonly<{
|
|
|
42
42
|
}>;
|
|
43
43
|
}>;
|
|
44
44
|
export interface RpcPeer {
|
|
45
|
-
call<Request =
|
|
46
|
-
notify<Payload =
|
|
47
|
-
onNotify<Payload
|
|
45
|
+
call<Request extends JsonValue = JsonValue, Response = unknown>(typeId: number, payload: Request, decodeResponse: (payload: JsonValue) => Response, options?: OperationOptions): Promise<RpcResult<Response>>;
|
|
46
|
+
notify<Payload extends JsonValue = JsonValue>(typeId: number, payload: Payload, options?: OperationOptions): Promise<void>;
|
|
47
|
+
onNotify<Payload>(typeId: number, decodePayload: (payload: JsonValue) => Payload, handler: (payload: Payload) => void | Promise<void>): () => void;
|
|
48
48
|
}
|
|
49
49
|
export interface ByteStream {
|
|
50
50
|
readonly kind: string;
|
package/dist/v2/publicSession.js
CHANGED
|
@@ -2,8 +2,18 @@ import { SessionError } from "./contract.js";
|
|
|
2
2
|
import { createStreamMetadataV2, streamMetadataValuesV2 } from "./streamMetadata.js";
|
|
3
3
|
/** @internal */
|
|
4
4
|
export function projectSessionV2(session) {
|
|
5
|
-
const
|
|
5
|
+
const notificationOwner = {
|
|
6
|
+
subscriptions: new Set(),
|
|
7
|
+
closed: false,
|
|
8
|
+
};
|
|
9
|
+
const clearNotificationSubscriptions = () => {
|
|
10
|
+
notificationOwner.closed = true;
|
|
11
|
+
for (const unsubscribe of [...notificationOwner.subscriptions])
|
|
12
|
+
unsubscribe();
|
|
13
|
+
};
|
|
14
|
+
const rpc = projectRpcPeerV2(session.rpc, notificationOwner);
|
|
6
15
|
const unreliable = session.unreliableMessages;
|
|
16
|
+
void session.termination.then(clearNotificationSubscriptions, clearNotificationSubscriptions);
|
|
7
17
|
return Object.freeze({
|
|
8
18
|
rpc,
|
|
9
19
|
...(unreliable === undefined ? {} : { unreliableMessages: unreliable }),
|
|
@@ -64,6 +74,9 @@ export function projectSessionV2(session) {
|
|
|
64
74
|
catch (error) {
|
|
65
75
|
throw redactSessionError(error);
|
|
66
76
|
}
|
|
77
|
+
finally {
|
|
78
|
+
clearNotificationSubscriptions();
|
|
79
|
+
}
|
|
67
80
|
},
|
|
68
81
|
});
|
|
69
82
|
}
|
|
@@ -115,14 +128,16 @@ function projectByteStreamV2(stream) {
|
|
|
115
128
|
},
|
|
116
129
|
});
|
|
117
130
|
}
|
|
118
|
-
function projectRpcPeerV2(peer) {
|
|
131
|
+
function projectRpcPeerV2(peer, notificationOwner) {
|
|
119
132
|
return Object.freeze({
|
|
120
133
|
async call(typeId, payload, decodeResponse, options) {
|
|
121
134
|
try {
|
|
135
|
+
assertJsonValue(payload);
|
|
122
136
|
const result = await peer.call(typeId, payload, options?.signal);
|
|
123
137
|
if (result.error !== undefined) {
|
|
124
138
|
return Object.freeze({ ok: false, error: Object.freeze({ ...result.error }) });
|
|
125
139
|
}
|
|
140
|
+
assertJsonValue(result.payload);
|
|
126
141
|
return Object.freeze({ ok: true, payload: decodeResponse(result.payload) });
|
|
127
142
|
}
|
|
128
143
|
catch (error) {
|
|
@@ -131,6 +146,7 @@ function projectRpcPeerV2(peer) {
|
|
|
131
146
|
},
|
|
132
147
|
async notify(typeId, payload, options) {
|
|
133
148
|
try {
|
|
149
|
+
assertJsonValue(payload);
|
|
134
150
|
if (options?.signal?.aborted)
|
|
135
151
|
throw options.signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
136
152
|
await raceWithSignal(peer.notify(typeId, payload), options?.signal);
|
|
@@ -139,11 +155,75 @@ function projectRpcPeerV2(peer) {
|
|
|
139
155
|
throw redactSessionError(error);
|
|
140
156
|
}
|
|
141
157
|
},
|
|
142
|
-
onNotify(typeId, handler) {
|
|
143
|
-
|
|
158
|
+
onNotify(typeId, decodePayload, handler) {
|
|
159
|
+
if (notificationOwner.closed)
|
|
160
|
+
return () => undefined;
|
|
161
|
+
const unsubscribe = peer.onNotify(typeId, (payload) => {
|
|
162
|
+
let decoded;
|
|
163
|
+
try {
|
|
164
|
+
assertJsonValue(payload);
|
|
165
|
+
decoded = decodePayload(payload);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
void Promise.resolve(handler(decoded)).catch(() => undefined);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Notification handlers are isolated from RPC serving.
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
let subscribed = true;
|
|
178
|
+
const cancel = () => {
|
|
179
|
+
if (!subscribed)
|
|
180
|
+
return;
|
|
181
|
+
subscribed = false;
|
|
182
|
+
notificationOwner.subscriptions.delete(cancel);
|
|
183
|
+
unsubscribe();
|
|
184
|
+
};
|
|
185
|
+
notificationOwner.subscriptions.add(cancel);
|
|
186
|
+
return cancel;
|
|
144
187
|
},
|
|
145
188
|
});
|
|
146
189
|
}
|
|
190
|
+
function assertJsonValue(value) {
|
|
191
|
+
validateJsonValue(value, new Set());
|
|
192
|
+
}
|
|
193
|
+
function validateJsonValue(value, ancestors) {
|
|
194
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
195
|
+
return;
|
|
196
|
+
if (typeof value === "number") {
|
|
197
|
+
if (Number.isFinite(value))
|
|
198
|
+
return;
|
|
199
|
+
throw new TypeError("RPC payload contains a non-finite number");
|
|
200
|
+
}
|
|
201
|
+
if (typeof value !== "object")
|
|
202
|
+
throw new TypeError("RPC payload is not a JSON value");
|
|
203
|
+
if (ancestors.has(value))
|
|
204
|
+
throw new TypeError("RPC payload contains a cycle");
|
|
205
|
+
ancestors.add(value);
|
|
206
|
+
try {
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
209
|
+
if (!(index in value))
|
|
210
|
+
throw new TypeError("RPC payload contains a sparse array");
|
|
211
|
+
validateJsonValue(value[index], ancestors);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const prototype = Object.getPrototypeOf(value);
|
|
216
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
217
|
+
throw new TypeError("RPC payload contains a non-JSON object");
|
|
218
|
+
}
|
|
219
|
+
for (const key of Object.keys(value)) {
|
|
220
|
+
validateJsonValue(value[key], ancestors);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
ancestors.delete(value);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
147
227
|
async function raceWithSignal(operation, signal) {
|
|
148
228
|
if (signal === undefined)
|
|
149
229
|
return await operation;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floegence/flowersec-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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.
|
|
80
|
+
"@floegence/flowersec-node-native": "2.4.1"
|
|
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": "
|
|
98
|
+
"flowersecSourceCommit": "3605cb0c32551ec5bc2f799cd2e086c5c3102177"
|
|
99
99
|
}
|
package/sbom/cyclonedx.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:1b2d0f50-da64-544b-8761-aaf9dd5a7c79",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "@floegence/flowersec-core",
|
|
10
|
-
"version": "2.
|
|
11
|
-
"purl": "pkg:npm/%40floegence/flowersec-core@2.
|
|
12
|
-
"bom-ref": "pkg:npm/%40floegence/flowersec-core@2.
|
|
10
|
+
"version": "2.4.1",
|
|
11
|
+
"purl": "pkg:npm/%40floegence/flowersec-core@2.4.1",
|
|
12
|
+
"bom-ref": "pkg:npm/%40floegence/flowersec-core@2.4.1"
|
|
13
13
|
},
|
|
14
14
|
"properties": [
|
|
15
15
|
{
|
|
16
16
|
"name": "flowersec:source-inventory-sha256",
|
|
17
|
-
"value": "
|
|
17
|
+
"value": "661d332941b82dbf168619a507636c87c3d13befebe1b70ddec45674655e1941"
|
|
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.
|
|
193
|
+
"ref": "pkg:npm/%40floegence/flowersec-core@2.4.1",
|
|
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/
|
|
6
|
+
"documentNamespace": "https://github.com/floegence/flowersec/sbom/flowersec-ts/661d332941b82dbf168619a507636c87c3d13befebe1b70ddec45674655e1941",
|
|
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-
|
|
17
|
-
"versionInfo": "2.
|
|
16
|
+
"SPDXID": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
17
|
+
"versionInfo": "2.4.1",
|
|
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:
|
|
23
|
+
"comment": "Flowersec source inventory SHA-256: 661d332941b82dbf168619a507636c87c3d13befebe1b70ddec45674655e1941",
|
|
24
24
|
"externalRefs": [
|
|
25
25
|
{
|
|
26
26
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
27
27
|
"referenceType": "purl",
|
|
28
|
-
"referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.
|
|
28
|
+
"referenceLocator": "pkg:npm/%40floegence/flowersec-core@2.4.1"
|
|
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-
|
|
139
|
+
"relatedSpdxElement": "SPDXRef-Package-4eeba6fa52c374d21b02"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
|
-
"spdxElementId": "SPDXRef-Package-
|
|
142
|
+
"spdxElementId": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
143
143
|
"relationshipType": "DEPENDS_ON",
|
|
144
144
|
"relatedSpdxElement": "SPDXRef-Package-5ce913a03239b02770fb"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
|
-
"spdxElementId": "SPDXRef-Package-
|
|
147
|
+
"spdxElementId": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
148
148
|
"relationshipType": "DEPENDS_ON",
|
|
149
149
|
"relatedSpdxElement": "SPDXRef-Package-01009adf60db02c13634"
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
|
-
"spdxElementId": "SPDXRef-Package-
|
|
152
|
+
"spdxElementId": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
153
153
|
"relationshipType": "DEPENDS_ON",
|
|
154
154
|
"relatedSpdxElement": "SPDXRef-Package-8815b117c8a1d5f7eeb0"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
|
-
"spdxElementId": "SPDXRef-Package-
|
|
157
|
+
"spdxElementId": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
158
158
|
"relationshipType": "DEPENDS_ON",
|
|
159
159
|
"relatedSpdxElement": "SPDXRef-Package-f8b45a289df643042b94"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
|
-
"spdxElementId": "SPDXRef-Package-
|
|
162
|
+
"spdxElementId": "SPDXRef-Package-4eeba6fa52c374d21b02",
|
|
163
163
|
"relationshipType": "DEPENDS_ON",
|
|
164
164
|
"relatedSpdxElement": "SPDXRef-Package-b779412f685822663496"
|
|
165
165
|
},
|