@floegence/flowersec-core 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +57 -200
  2. package/THIRD_PARTY_NOTICES.md +11 -11
  3. package/dist/browser/index.d.ts +0 -5
  4. package/dist/browser/index.js +0 -3
  5. package/dist/cli.js +93 -64
  6. package/dist/facade.d.ts +0 -17
  7. package/dist/facade.js +0 -11
  8. package/dist/generated/unicode151.d.ts +2 -0
  9. package/dist/generated/unicode151.js +83 -0
  10. package/dist/interop/proxyServerPeer.js +34 -20
  11. package/dist/interop/serverParityPeer.js +11 -252
  12. package/dist/node/acceptor.d.ts +1 -57
  13. package/dist/node/acceptor.js +3 -420
  14. package/dist/node/acceptorV3.js +2 -2
  15. package/dist/node/connectSessionV3.js +2 -2
  16. package/dist/node/index.d.ts +2 -17
  17. package/dist/node/index.js +1 -9
  18. package/dist/node/nativeTransportAddon.d.ts +7 -36
  19. package/dist/node/nativeTransportAddon.js +7 -27
  20. package/dist/node/proxyServer.d.ts +2 -2
  21. package/dist/node/proxyServer.js +2 -2
  22. package/dist/node/rawQuicAdapterV3.d.ts +2 -2
  23. package/dist/node/rawQuicAdapterV3.js +1 -1
  24. package/dist/node/rawQuicServerV3.d.ts +2 -2
  25. package/dist/node/rawQuicServerV3.js +1 -1
  26. package/dist/node/rawQuicTls.d.ts +2 -0
  27. package/dist/node/{rawQuicAdapter.js → rawQuicTls.js} +30 -45
  28. package/dist/node/tunnelRuntimeV3.js +2 -2
  29. package/dist/proxy/integration.d.ts +5 -5
  30. package/dist/proxy/integration.js +2 -2
  31. package/dist/public/streamHandlers.d.ts +0 -3
  32. package/dist/public/streamHandlers.js +2 -4
  33. package/dist/public/streamMetadata.js +2 -2
  34. package/dist/runtime/errors.d.ts +2 -2
  35. package/dist/testSupport/tlsFixture.d.ts +2 -0
  36. package/dist/testSupport/tlsFixture.js +8 -0
  37. package/dist/utils/errors.d.ts +0 -2
  38. package/dist/utils/errors.js +0 -80
  39. package/dist/v3/artifact.js +17 -3
  40. package/dist/v3/carrier.d.ts +2 -2
  41. package/dist/v3/protocol.js +2 -103
  42. package/dist/v3/retryDisposition.d.ts +2 -2
  43. package/dist/v3/retryDisposition.js +0 -7
  44. package/dist/vendor/tr46.d.ts +4 -0
  45. package/dist/vendor/tr46.js +37 -32
  46. package/package.json +19 -17
  47. package/sbom/cyclonedx.json +62 -62
  48. package/sbom/spdx.json +65 -65
  49. package/dist/browser/connectSession.d.ts +0 -13
  50. package/dist/browser/connectSession.js +0 -40
  51. package/dist/browser/runtimeCapability.d.ts +0 -13
  52. package/dist/browser/runtimeCapability.js +0 -27
  53. package/dist/browser/sessionRuntime.d.ts +0 -2
  54. package/dist/browser/sessionRuntime.js +0 -21
  55. package/dist/browser/v2.d.ts +0 -3
  56. package/dist/browser/v2.js +0 -2
  57. package/dist/browser/webTransportClient.d.ts +0 -8
  58. package/dist/browser/webTransportClient.js +0 -55
  59. package/dist/connectionController.d.ts +0 -55
  60. package/dist/connectionController.js +0 -441
  61. package/dist/connector/adapters/rawQuicCandidate.d.ts +0 -5
  62. package/dist/connector/adapters/rawQuicCandidate.js +0 -71
  63. package/dist/connector/adapters/webSocketCandidate.d.ts +0 -7
  64. package/dist/connector/adapters/webSocketCandidate.js +0 -116
  65. package/dist/connector/adapters/webTransportCandidate.d.ts +0 -5
  66. package/dist/connector/adapters/webTransportCandidate.js +0 -73
  67. package/dist/connector/admissionCommit.d.ts +0 -31
  68. package/dist/connector/admissionCommit.js +0 -239
  69. package/dist/connector/sessionAcceptor.d.ts +0 -38
  70. package/dist/connector/sessionAcceptor.js +0 -158
  71. package/dist/connector/sessionConfig.d.ts +0 -3
  72. package/dist/connector/sessionConfig.js +0 -30
  73. package/dist/connector/sessionConnector.d.ts +0 -47
  74. package/dist/connector/sessionConnector.js +0 -606
  75. package/dist/node/connectSession.d.ts +0 -23
  76. package/dist/node/connectSession.js +0 -93
  77. package/dist/node/controlplane.d.ts +0 -137
  78. package/dist/node/controlplane.js +0 -443
  79. package/dist/node/rawQuicAdapter.d.ts +0 -9
  80. package/dist/node/rawQuicServer.d.ts +0 -24
  81. package/dist/node/rawQuicServer.js +0 -37
  82. package/dist/node/runtimeCapability.d.ts +0 -14
  83. package/dist/node/runtimeCapability.js +0 -24
  84. package/dist/node/sessionRuntime.d.ts +0 -2
  85. package/dist/node/sessionRuntime.js +0 -13
  86. package/dist/node/tunnelRuntime.d.ts +0 -42
  87. package/dist/node/tunnelRuntime.js +0 -559
  88. package/dist/node/v2.d.ts +0 -11
  89. package/dist/node/v2.js +0 -6
  90. package/dist/node/webSocketServer.d.ts +0 -24
  91. package/dist/node/webSocketServer.js +0 -135
  92. package/dist/public/artifact.d.ts +0 -11
  93. package/dist/public/artifact.js +0 -44
  94. package/dist/public/artifactLease.d.ts +0 -10
  95. package/dist/public/artifactLease.js +0 -41
  96. package/dist/public/connectError.d.ts +0 -6
  97. package/dist/public/connectError.js +0 -9
  98. package/dist/transport/webSocketAdapter.d.ts +0 -31
  99. package/dist/transport/webSocketAdapter.js +0 -370
  100. package/dist/transport/webTransportAdapter.d.ts +0 -27
  101. package/dist/transport/webTransportAdapter.js +0 -534
  102. package/dist/v2/admissionError.d.ts +0 -4
  103. package/dist/v2/admissionError.js +0 -8
  104. package/dist/v2/artifact.d.ts +0 -128
  105. package/dist/v2/artifact.js +0 -1131
  106. package/dist/v2/artifactLease.d.ts +0 -1
  107. package/dist/v2/artifactLease.js +0 -7
  108. package/dist/v2/capability.d.ts +0 -29
  109. package/dist/v2/capability.js +0 -190
  110. package/dist/v2/carrier.d.ts +0 -84
  111. package/dist/v2/carrier.js +0 -411
  112. package/dist/v2/contract.d.ts +0 -46
  113. package/dist/v2/contract.js +0 -1
  114. package/dist/v2/handshake.d.ts +0 -80
  115. package/dist/v2/handshake.js +0 -470
  116. package/dist/v2/index.d.ts +0 -14
  117. package/dist/v2/index.js +0 -7
  118. package/dist/v2/opaqueArtifact.d.ts +0 -1
  119. package/dist/v2/opaqueArtifact.js +0 -2
  120. package/dist/v2/protocol.d.ts +0 -99
  121. package/dist/v2/protocol.js +0 -737
  122. package/dist/v2/publicSession.d.ts +0 -1
  123. package/dist/v2/publicSession.js +0 -281
  124. package/dist/v2/retryDisposition.d.ts +0 -16
  125. package/dist/v2/retryDisposition.js +0 -30
  126. package/dist/v2/session.d.ts +0 -310
  127. package/dist/v2/session.js +0 -2127
  128. package/dist/v2/streamLifetimeLedger.d.ts +0 -31
  129. package/dist/v2/streamLifetimeLedger.js +0 -131
  130. package/dist/v2/streamMetadata.d.ts +0 -1
  131. package/dist/v2/streamMetadata.js +0 -7
  132. package/dist/v2/unreliableMessage.d.ts +0 -43
  133. package/dist/v2/unreliableMessage.js +0 -254
@@ -1,559 +0,0 @@
1
- import { receiveSessionAdmissionV2, rejectSessionAdmissionV2, } from "../connector/sessionAcceptor.js";
2
- import { configureServerWebSocketCarrierRoleV2 } from "../transport/webSocketAdapter.js";
3
- import { AdmissionStatusV2, encodeFSA2ResponseV2, } from "../v2/artifact.js";
4
- import { runtimeAuthorizationRequestFromDecoded, TunnelAuthorizationResponse, } from "./controlplane.js";
5
- import { startNodeWebSocketServer } from "./webSocketServer.js";
6
- import { createNativeRawQuicDriver } from "./nativeTransportAddon.js";
7
- import { startNodeRawQuicServer } from "./rawQuicServer.js";
8
- const DEFAULT_PAIR_TIMEOUT_MS = 10_000;
9
- const DEFAULT_CLEANUP_TIMEOUT_MS = 2_000;
10
- const DEFAULT_MAX_PENDING_LEGS = 1_024;
11
- const DEFAULT_MAX_ACTIVE_PAIRS = 1_024;
12
- const DEFAULT_MAX_CONCURRENT_STREAMS = 128;
13
- const ID = /^[A-Za-z0-9._~-]{1,128}$/u;
14
- const REASON = /^[A-Za-z0-9._~-]{1,64}$/u;
15
- export class TunnelRuntime {
16
- constructor() { }
17
- async start() {
18
- await startTunnelRuntime(tunnelRuntimeState(this));
19
- }
20
- addresses() {
21
- return tunnelRuntimeState(this).listeners.map((listener) => listener.address());
22
- }
23
- async close() {
24
- const state = tunnelRuntimeState(this);
25
- if (state.abort.signal.aborted)
26
- return;
27
- state.abort.abort(new Error("Flowersec tunnel runtime closed"));
28
- for (const generation of [...state.generations.values()])
29
- finishGeneration(state, generation);
30
- const starting = starts.get(state);
31
- if (starting !== undefined)
32
- await Promise.allSettled([starting]);
33
- await Promise.allSettled(state.listeners.map(async (listener) => await listener.close()));
34
- await boundedCleanup(state.tasks, state.limits.cleanupTimeoutMs);
35
- state.tasks.clear();
36
- }
37
- }
38
- const tunnelRuntimeStates = new WeakMap();
39
- function tunnelRuntimeState(runtime) {
40
- const state = tunnelRuntimeStates.get(runtime);
41
- if (state === undefined)
42
- throw new Error("invalid Flowersec TunnelRuntime");
43
- return state;
44
- }
45
- export function createTunnelRuntime(options) {
46
- const limits = validateOptions(options);
47
- const runtime = new TunnelRuntime();
48
- const state = {
49
- listeners: [],
50
- options,
51
- limits,
52
- abort: new AbortController(),
53
- generations: new Map(),
54
- usedCredentials: new Map(),
55
- legGenerations: new WeakMap(),
56
- tasks: new Set(),
57
- counts: { pendingLegs: 0, activePairs: 0 },
58
- };
59
- tunnelRuntimeStates.set(runtime, state);
60
- return Object.freeze(runtime);
61
- }
62
- const starts = new WeakMap();
63
- async function startTunnelRuntime(state) {
64
- if (state.abort.signal.aborted)
65
- throw new Error("Flowersec tunnel runtime is closed");
66
- let started = starts.get(state);
67
- if (started === undefined) {
68
- started = (async () => {
69
- let rawQuicDriver;
70
- try {
71
- for (const listener of state.options.listeners) {
72
- if (state.abort.signal.aborted)
73
- throw new Error("Flowersec tunnel runtime is closed");
74
- const running = listener.carrier === "websocket"
75
- ? await startNodeWebSocketServer({
76
- ...listener,
77
- path: "tunnel",
78
- inboundBidirectionalStreamCapacity: state.options.maxInboundStreams + 2,
79
- })
80
- : await startNodeRawQuicServer(rawQuicDriver ??= createNativeRawQuicDriver(), {
81
- ...listener,
82
- path: "tunnel",
83
- inboundBidirectionalStreamCapacity: state.options.maxInboundStreams + 2,
84
- });
85
- if (state.abort.signal.aborted) {
86
- await running.close().catch(() => undefined);
87
- throw new Error("Flowersec tunnel runtime is closed");
88
- }
89
- state.listeners.push(running);
90
- }
91
- }
92
- catch (error) {
93
- await Promise.allSettled(state.listeners.splice(0).map(async (listener) => await listener.close()));
94
- throw error;
95
- }
96
- for (const listener of state.listeners)
97
- track(state, runListener(state, listener));
98
- })();
99
- starts.set(state, started);
100
- }
101
- await started;
102
- }
103
- async function runListener(state, listener) {
104
- while (!state.abort.signal.aborted) {
105
- let carrier;
106
- try {
107
- carrier = await listener.accept({ signal: state.abort.signal });
108
- }
109
- catch {
110
- return;
111
- }
112
- track(state, processCarrier(state, carrier));
113
- }
114
- }
115
- async function processCarrier(state, carrier) {
116
- try {
117
- const received = await receiveSessionAdmissionV2(carrier, state.abort.signal);
118
- const decoded = received.decoded;
119
- if (carrier.path !== "tunnel" || decoded.request.pathKind !== "tunnel" ||
120
- decoded.request.candidates.find((candidate) => candidate.id === decoded.request.chosen_candidate_id)?.carrier !== carrier.kind) {
121
- await reject(received, "invalid_credential", false, state.abort.signal);
122
- return;
123
- }
124
- const authorization = await abortableCallback(state.options.authorize(runtimeAuthorizationRequestFromDecoded(decoded), { signal: state.abort.signal }), state.abort.signal);
125
- const decision = authorization instanceof TunnelAuthorizationResponse
126
- ? authorization.asDecision()
127
- : authorization;
128
- if (decision.decision !== "allow") {
129
- await reject(received, decision.reason, decision.decision === "retry", state.abort.signal);
130
- return;
131
- }
132
- const leg = validatedLeg(state, received, decoded.request, decision);
133
- if (leg === undefined) {
134
- releaseDecision(state, decision);
135
- await reject(received, "invalid_credential", false, state.abort.signal);
136
- return;
137
- }
138
- configureRole(leg);
139
- registerLeg(state, leg);
140
- }
141
- catch {
142
- await carrier.close().catch(() => undefined);
143
- }
144
- }
145
- function validatedLeg(state, received, request, authorization) {
146
- const now = Math.floor(Date.now() / 1_000);
147
- if (!ID.test(authorization.credentialId) || authorization.credentialId !== runtimeAuthorizationRequestFromDecoded(received.decoded).lookupKey() ||
148
- !ID.test(authorization.leaseId) || !Number.isSafeInteger(authorization.expiresAtUnixSeconds) ||
149
- authorization.expiresAtUnixSeconds <= now || !ID.test(authorization.expectedPeerEndpointInstanceId) ||
150
- authorization.expectedPeerEndpointInstanceId === request.endpoint_instance_id)
151
- return undefined;
152
- let released = false;
153
- return {
154
- received,
155
- request,
156
- authorization,
157
- release() {
158
- if (released)
159
- return;
160
- released = true;
161
- trackRelease(state, authorization.leaseId);
162
- },
163
- };
164
- }
165
- function registerLeg(state, leg) {
166
- pruneCredentials(state);
167
- if (state.usedCredentials.has(leg.authorization.credentialId)) {
168
- leg.release();
169
- track(state, reject(leg.received, "credential_replay", false, state.abort.signal));
170
- return;
171
- }
172
- state.usedCredentials.set(leg.authorization.credentialId, leg.authorization.expiresAtUnixSeconds);
173
- const key = pairKey(leg.request);
174
- let generation = state.generations.get(key);
175
- if (generation !== undefined && !generation.finished &&
176
- (generation.active || generation.roles.has(leg.request.role))) {
177
- if (leg.authorization.allowReplacement !== true) {
178
- leg.release();
179
- track(state, reject(leg.received, "replacement_denied", false, state.abort.signal));
180
- return;
181
- }
182
- rejectGeneration(state, generation, "replaced", false);
183
- generation = undefined;
184
- }
185
- if (generation === undefined || generation.finished) {
186
- if (state.counts.pendingLegs >= state.limits.maxPendingLegs) {
187
- leg.release();
188
- track(state, reject(leg.received, "capacity", true, state.abort.signal));
189
- return;
190
- }
191
- generation = {
192
- key,
193
- abort: new AbortController(),
194
- roles: new Map(),
195
- active: false,
196
- finished: false,
197
- };
198
- state.generations.set(key, generation);
199
- }
200
- const peer = generation.roles.get(leg.request.role === 1 ? 2 : 1);
201
- if (peer !== undefined && !mirrored(peer, leg)) {
202
- leg.release();
203
- track(state, reject(leg.received, "pair_mismatch", false, state.abort.signal));
204
- return;
205
- }
206
- generation.roles.set(leg.request.role, leg);
207
- state.legGenerations.set(leg, generation);
208
- track(state, watchTermination(state, leg));
209
- if (generation.roles.size === 1) {
210
- state.counts.pendingLegs += 1;
211
- const timeout = Math.min(state.limits.pairTimeoutMs, Math.max(1, leg.authorization.expiresAtUnixSeconds * 1_000 - Date.now()));
212
- generation.timer = setTimeout(() => rejectGeneration(state, generation, "pair_timeout", true), timeout);
213
- return;
214
- }
215
- if (state.counts.activePairs >= state.limits.maxActivePairs) {
216
- rejectGeneration(state, generation, "capacity", true);
217
- return;
218
- }
219
- if (generation.timer !== undefined)
220
- clearTimeout(generation.timer);
221
- state.counts.pendingLegs -= 1;
222
- state.counts.activePairs += 1;
223
- generation.active = true;
224
- track(state, activatePair(state, generation));
225
- }
226
- async function activatePair(state, generation) {
227
- const first = generation.roles.get(1);
228
- const second = generation.roles.get(2);
229
- if (first === undefined || second === undefined) {
230
- finishGeneration(state, generation);
231
- return;
232
- }
233
- try {
234
- await Promise.all([
235
- acceptAdmission(first.received, generation.abort.signal),
236
- acceptAdmission(second.received, generation.abort.signal),
237
- ]);
238
- await bridgePair(state, first.received.carrier, second.received.carrier, generation.abort.signal);
239
- }
240
- catch {
241
- // Pair teardown below is the observable failure boundary for both endpoints.
242
- }
243
- finally {
244
- finishGeneration(state, generation);
245
- }
246
- }
247
- async function watchTermination(state, leg) {
248
- try {
249
- await leg.received.carrier.waitTermination();
250
- }
251
- catch {
252
- // Carrier termination has the same generation cleanup below.
253
- }
254
- const generation = state.legGenerations.get(leg);
255
- if (generation !== undefined && !generation.finished)
256
- finishGeneration(state, generation);
257
- }
258
- function configureRole(leg) {
259
- if (leg.received.carrier.kind === "websocket") {
260
- configureServerWebSocketCarrierRoleV2(leg.received.carrier, leg.request.role === 2);
261
- }
262
- }
263
- async function bridgePair(state, first, second, signal) {
264
- const controller = new AbortController();
265
- const cancel = () => controller.abort(signal.reason);
266
- signal.addEventListener("abort", cancel, { once: true });
267
- const tasks = new Set();
268
- const slots = new StreamSlots(state.limits.maxConcurrentStreams);
269
- const start = (task, fatal = true) => {
270
- tasks.add(task);
271
- void task.catch(() => { if (fatal)
272
- controller.abort(); }).finally(() => tasks.delete(task));
273
- };
274
- try {
275
- const controlIn = await first.acceptStream({ signal: controller.signal });
276
- const controlOut = await second.openStream({ signal: controller.signal });
277
- start(spliceStreams(controlIn, controlOut, controller.signal, true));
278
- start(bridgeStreams(first, second, slots, controller.signal, start));
279
- start(bridgeStreams(second, first, slots, controller.signal, start));
280
- if (first.unreliableDatagrams !== undefined && second.unreliableDatagrams !== undefined) {
281
- start(bridgeDatagrams(first.unreliableDatagrams, second.unreliableDatagrams, controller.signal));
282
- start(bridgeDatagrams(second.unreliableDatagrams, first.unreliableDatagrams, controller.signal));
283
- }
284
- await aborted(controller.signal);
285
- }
286
- finally {
287
- signal.removeEventListener("abort", cancel);
288
- controller.abort();
289
- first.abort({ code: 1, reason: "tunnel bridge closed" });
290
- second.abort({ code: 1, reason: "tunnel bridge closed" });
291
- await boundedCleanup(tasks, state.limits.cleanupTimeoutMs);
292
- }
293
- }
294
- async function bridgeStreams(source, target, slots, signal, start) {
295
- while (!signal.aborted) {
296
- const incoming = await source.acceptStream({ signal });
297
- const release = await slots.acquire(signal);
298
- let outgoing;
299
- try {
300
- outgoing = await target.openStream({ signal });
301
- }
302
- catch (error) {
303
- release();
304
- if (signal.aborted) {
305
- incoming.abort(asError(error));
306
- throw error;
307
- }
308
- await incoming.reset().catch(() => undefined);
309
- continue;
310
- }
311
- start(spliceStreams(incoming, outgoing, signal, false).finally(release), false);
312
- }
313
- }
314
- async function spliceStreams(left, right, signal, closePair) {
315
- const abort = () => {
316
- left.abort(asError(signal.reason));
317
- right.abort(asError(signal.reason));
318
- };
319
- signal.addEventListener("abort", abort, { once: true });
320
- try {
321
- await Promise.all([copyStream(left, right, signal), copyStream(right, left, signal)]);
322
- if (closePair)
323
- throw new Error("Flowersec tunnel control stream closed");
324
- }
325
- catch (error) {
326
- if (closePair || signal.aborted) {
327
- left.abort(asError(error));
328
- right.abort(asError(error));
329
- }
330
- else {
331
- await Promise.allSettled([left.reset(), right.reset()]);
332
- }
333
- throw error;
334
- }
335
- finally {
336
- signal.removeEventListener("abort", abort);
337
- }
338
- }
339
- async function copyStream(source, target, signal) {
340
- while (true) {
341
- const chunk = await source.read({ signal });
342
- if (chunk === null) {
343
- await target.closeWrite();
344
- return;
345
- }
346
- let offset = 0;
347
- while (offset < chunk.length) {
348
- const written = await target.write(chunk.subarray(offset), { signal });
349
- if (written < 1 || written > chunk.length - offset)
350
- throw new Error("invalid tunnel stream write");
351
- offset += written;
352
- }
353
- }
354
- }
355
- async function bridgeDatagrams(source, target, signal) {
356
- while (!signal.aborted) {
357
- const message = await source.receive({ signal });
358
- if (message.length <= target.maxDatagramSize)
359
- await target.send(message, { signal });
360
- }
361
- }
362
- function mirrored(first, second) {
363
- const a = first.request;
364
- const b = second.request;
365
- return a.role !== b.role &&
366
- a.channel_id === b.channel_id &&
367
- a.profile === b.profile &&
368
- a.rendezvous_group_id === b.rendezvous_group_id &&
369
- a.session_contract_hash_b64u === b.session_contract_hash_b64u &&
370
- a.candidate_set_hash_b64u === b.candidate_set_hash_b64u &&
371
- a.listener_audience === b.listener_audience &&
372
- first.authorization.expectedPeerEndpointInstanceId === b.endpoint_instance_id &&
373
- second.authorization.expectedPeerEndpointInstanceId === a.endpoint_instance_id;
374
- }
375
- function pairKey(request) {
376
- return JSON.stringify([
377
- request.profile,
378
- request.channel_id,
379
- request.rendezvous_group_id,
380
- request.listener_audience,
381
- ]);
382
- }
383
- function rejectGeneration(state, generation, reason, retryable) {
384
- if (generation.finished)
385
- return;
386
- detachGeneration(state, generation);
387
- track(state, (async () => {
388
- await Promise.allSettled([...generation.roles.values()].map(async (leg) => await reject(leg.received, reason, retryable, state.abort.signal)));
389
- generation.abort.abort(new Error("Flowersec tunnel pair rejected"));
390
- for (const leg of generation.roles.values())
391
- leg.release();
392
- })());
393
- }
394
- function finishGeneration(state, generation) {
395
- if (generation.finished)
396
- return;
397
- detachGeneration(state, generation);
398
- generation.abort.abort(new Error("Flowersec tunnel pair closed"));
399
- for (const leg of generation.roles.values()) {
400
- leg.received.carrier.abort({ code: 1, reason: "tunnel pair closed" });
401
- leg.release();
402
- }
403
- }
404
- function detachGeneration(state, generation) {
405
- generation.finished = true;
406
- if (generation.timer !== undefined)
407
- clearTimeout(generation.timer);
408
- if (generation.active)
409
- state.counts.activePairs -= 1;
410
- else if (generation.roles.size > 0)
411
- state.counts.pendingLegs -= 1;
412
- if (state.generations.get(generation.key) === generation)
413
- state.generations.delete(generation.key);
414
- }
415
- async function acceptAdmission(received, signal) {
416
- const response = encodeFSA2ResponseV2({ status: AdmissionStatusV2.Success, reason: "" });
417
- let offset = 0;
418
- while (offset < response.length) {
419
- const written = await received.stream.write(response.subarray(offset), { signal });
420
- if (written < 1 || written > response.length - offset)
421
- throw new Error("invalid admission write");
422
- offset += written;
423
- }
424
- await received.stream.closeWrite();
425
- }
426
- async function reject(received, reason, retryable, signal) {
427
- const boundedReason = REASON.test(reason) ? reason : "invalid_credential";
428
- try {
429
- await rejectSessionAdmissionV2(received, {
430
- accepted: false,
431
- status: retryable ? AdmissionStatusV2.Retryable : AdmissionStatusV2.Reject,
432
- reason: boundedReason,
433
- }, signal);
434
- }
435
- catch {
436
- // Rejection intentionally closes the carrier and reports through FSA2.
437
- }
438
- }
439
- function validateOptions(options) {
440
- const limits = {
441
- maxPendingLegs: options.maxPendingLegs ?? DEFAULT_MAX_PENDING_LEGS,
442
- maxActivePairs: options.maxActivePairs ?? DEFAULT_MAX_ACTIVE_PAIRS,
443
- maxConcurrentStreams: options.maxConcurrentStreams ?? DEFAULT_MAX_CONCURRENT_STREAMS,
444
- pairTimeoutMs: options.pairTimeoutMs ?? DEFAULT_PAIR_TIMEOUT_MS,
445
- cleanupTimeoutMs: options.cleanupTimeoutMs ?? DEFAULT_CLEANUP_TIMEOUT_MS,
446
- };
447
- if (options.listeners.length === 0 || !Number.isSafeInteger(options.maxInboundStreams) ||
448
- options.maxInboundStreams < 1 || options.maxInboundStreams > 128 || typeof options.authorize !== "function" ||
449
- !boundedInteger(limits.maxPendingLegs, 1, 65_536) || !boundedInteger(limits.maxActivePairs, 1, 65_536) ||
450
- !boundedInteger(limits.maxConcurrentStreams, 1, 128) || !boundedInteger(limits.pairTimeoutMs, 1, 60_000) ||
451
- !boundedInteger(limits.cleanupTimeoutMs, 1, 60_000)) {
452
- throw new TypeError("invalid Flowersec TunnelRuntime options");
453
- }
454
- return limits;
455
- }
456
- function boundedInteger(value, minimum, maximum) {
457
- return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
458
- }
459
- function pruneCredentials(state) {
460
- const now = Math.floor(Date.now() / 1_000);
461
- for (const [credential, expiry] of state.usedCredentials) {
462
- if (expiry <= now)
463
- state.usedCredentials.delete(credential);
464
- }
465
- }
466
- function releaseDecision(state, decision) {
467
- if (!ID.test(decision.leaseId))
468
- return;
469
- trackRelease(state, decision.leaseId);
470
- }
471
- function trackRelease(state, leaseId) {
472
- if (state.options.release === undefined)
473
- return;
474
- track(state, Promise.resolve()
475
- .then(async () => await state.options.release(leaseId))
476
- .then(() => undefined));
477
- }
478
- function track(state, task) {
479
- state.tasks.add(task);
480
- void task.catch(() => undefined).finally(() => state.tasks.delete(task));
481
- }
482
- async function boundedCleanup(tasks, timeoutMs) {
483
- await Promise.race([
484
- Promise.allSettled([...tasks]).then(() => undefined),
485
- new Promise((resolve) => setTimeout(resolve, timeoutMs)),
486
- ]);
487
- }
488
- async function abortableCallback(promise, signal) {
489
- if (signal.aborted)
490
- throw asError(signal.reason);
491
- return await new Promise((resolve, reject) => {
492
- let settled = false;
493
- const cleanup = () => signal.removeEventListener("abort", abort);
494
- const abort = () => {
495
- if (settled)
496
- return;
497
- settled = true;
498
- cleanup();
499
- reject(asError(signal.reason));
500
- };
501
- signal.addEventListener("abort", abort, { once: true });
502
- void promise.then((value) => {
503
- if (settled)
504
- return;
505
- settled = true;
506
- cleanup();
507
- resolve(value);
508
- }, (error) => {
509
- if (settled)
510
- return;
511
- settled = true;
512
- cleanup();
513
- reject(error);
514
- });
515
- });
516
- }
517
- function aborted(signal) {
518
- if (signal.aborted)
519
- return Promise.resolve();
520
- return new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
521
- }
522
- function asError(error) {
523
- return error instanceof Error ? error : new Error(String(error));
524
- }
525
- class StreamSlots {
526
- maximum;
527
- active = 0;
528
- waiters = [];
529
- constructor(maximum) {
530
- this.maximum = maximum;
531
- }
532
- async acquire(signal) {
533
- if (signal.aborted)
534
- throw asError(signal.reason);
535
- if (this.active >= this.maximum) {
536
- await new Promise((resolve, reject) => {
537
- const ready = () => { cleanup(); resolve(); };
538
- const abort = () => { cleanup(); reject(asError(signal.reason)); };
539
- const cleanup = () => {
540
- const index = this.waiters.indexOf(ready);
541
- if (index >= 0)
542
- this.waiters.splice(index, 1);
543
- signal.removeEventListener("abort", abort);
544
- };
545
- this.waiters.push(ready);
546
- signal.addEventListener("abort", abort, { once: true });
547
- });
548
- }
549
- this.active += 1;
550
- let released = false;
551
- return () => {
552
- if (released)
553
- return;
554
- released = true;
555
- this.active -= 1;
556
- this.waiters.shift()?.();
557
- };
558
- }
559
- }
package/dist/node/v2.d.ts DELETED
@@ -1,11 +0,0 @@
1
- export * from "../v2/index.js";
2
- export { connect, createConnectionController } from "./connectSession.js";
3
- export type { ConnectionControllerOptions, SessionOptions, SessionTLSOptions, } from "./connectSession.js";
4
- export { AcceptedSession, Acceptor, HandlerRegistrationError, RPCHandlers, SessionHandlers, createAcceptor, } from "./acceptor.js";
5
- export type { AcceptorListener, AcceptorOptions, AuthorizationDecision, RPCHandler, RPCHandlerResult, NotificationHandler, SessionHandlerOptions, } from "./acceptor.js";
6
- export { TunnelRuntime, createTunnelRuntime } from "./tunnelRuntime.js";
7
- export type { TunnelAuthorizationDecision, TunnelRuntimeListener, TunnelRuntimeOptions, } from "./tunnelRuntime.js";
8
- export { AuthorizationRecord, AuthorizationResponse, TunnelAuthorizationResponse, ControlPlaneError, EndpointSet, IssuedArtifact, Issuer, RuntimeAuthorizationRequest, authorizeRuntime, authorizeTunnelRuntime, createEndpointSet, parseAuthorizationRecord, parseRuntimeAuthorizationRequest, rejectRuntime, rejectTunnelRuntime, } from "./controlplane.js";
9
- export type { ArtifactMetadata, ControlPlaneErrorCode, DirectIssueOptions, IssuedTunnelPair, Scope, SessionOptions as ControlPlaneSessionOptions, TunnelIssueOptions, } from "./controlplane.js";
10
- export { ProxyServer, ProxyServerError } from "./proxyServer.js";
11
- export type { ProxyServerOptions, StreamHandlerRegistrar } from "./proxyServer.js";
package/dist/node/v2.js DELETED
@@ -1,6 +0,0 @@
1
- export * from "../v2/index.js";
2
- export { connect, createConnectionController } from "./connectSession.js";
3
- export { AcceptedSession, Acceptor, HandlerRegistrationError, RPCHandlers, SessionHandlers, createAcceptor, } from "./acceptor.js";
4
- export { TunnelRuntime, createTunnelRuntime } from "./tunnelRuntime.js";
5
- export { AuthorizationRecord, AuthorizationResponse, TunnelAuthorizationResponse, ControlPlaneError, EndpointSet, IssuedArtifact, Issuer, RuntimeAuthorizationRequest, authorizeRuntime, authorizeTunnelRuntime, createEndpointSet, parseAuthorizationRecord, parseRuntimeAuthorizationRequest, rejectRuntime, rejectTunnelRuntime, } from "./controlplane.js";
6
- export { ProxyServer, ProxyServerError } from "./proxyServer.js";
@@ -1,24 +0,0 @@
1
- import type { CarrierSessionV2 } from "../v2/carrier.js";
2
- import type { PathKind } from "../v2/contract.js";
3
- export type NodeWebSocketServerOptions = Readonly<{
4
- host: string;
5
- port: number;
6
- path: PathKind;
7
- tls?: Readonly<{
8
- certificate: string;
9
- privateKey: string;
10
- }>;
11
- allowedOrigins: readonly string[];
12
- inboundBidirectionalStreamCapacity: number;
13
- }>;
14
- export type NodeWebSocketServer = Readonly<{
15
- address(): Readonly<{
16
- host: string;
17
- port: number;
18
- }>;
19
- accept(options?: Readonly<{
20
- signal?: AbortSignal;
21
- }>): Promise<CarrierSessionV2>;
22
- close(): Promise<void>;
23
- }>;
24
- export declare function startNodeWebSocketServer(options: NodeWebSocketServerOptions): Promise<NodeWebSocketServer>;