@floegence/flowersec-core 3.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -200
- package/THIRD_PARTY_NOTICES.md +11 -11
- package/dist/browser/index.d.ts +0 -5
- package/dist/browser/index.js +0 -3
- package/dist/cli.js +93 -64
- package/dist/facade.d.ts +0 -17
- package/dist/facade.js +0 -11
- package/dist/generated/unicode151.d.ts +2 -0
- package/dist/generated/unicode151.js +83 -0
- package/dist/interop/proxyServerPeer.js +34 -20
- package/dist/interop/serverParityPeer.js +11 -252
- package/dist/node/acceptor.d.ts +1 -57
- package/dist/node/acceptor.js +3 -420
- package/dist/node/acceptorV3.js +2 -2
- package/dist/node/connectSessionV3.js +2 -2
- package/dist/node/index.d.ts +2 -17
- package/dist/node/index.js +1 -9
- package/dist/node/nativeTransportAddon.d.ts +7 -36
- package/dist/node/nativeTransportAddon.js +7 -27
- package/dist/node/proxyServer.d.ts +2 -2
- package/dist/node/proxyServer.js +2 -2
- package/dist/node/rawQuicAdapterV3.d.ts +2 -2
- package/dist/node/rawQuicAdapterV3.js +1 -1
- package/dist/node/rawQuicServerV3.d.ts +2 -2
- package/dist/node/rawQuicServerV3.js +1 -1
- package/dist/node/rawQuicTls.d.ts +2 -0
- package/dist/node/{rawQuicAdapter.js → rawQuicTls.js} +30 -45
- package/dist/node/tunnelRuntimeV3.js +2 -2
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.js +1 -1
- package/dist/proxy/integration.d.ts +5 -5
- package/dist/proxy/integration.js +2 -2
- package/dist/proxy/runtime.js +11 -26
- package/dist/proxy/serviceWorkerRuntime.d.ts +7 -0
- package/dist/proxy/serviceWorkerRuntime.js +75 -0
- package/dist/proxy/types.d.ts +0 -1
- package/dist/proxy/windowBridge.d.ts +2 -0
- package/dist/proxy/windowBridge.js +53 -6
- package/dist/public/contract.d.ts +1 -3
- package/dist/public/streamHandlers.d.ts +0 -3
- package/dist/public/streamHandlers.js +2 -4
- package/dist/public/streamMetadata.js +2 -2
- package/dist/runtime/errors.d.ts +2 -2
- package/dist/testSupport/tlsFixture.d.ts +2 -0
- package/dist/testSupport/tlsFixture.js +8 -0
- package/dist/utils/errors.d.ts +0 -2
- package/dist/utils/errors.js +0 -80
- package/dist/v3/artifact.js +17 -3
- package/dist/v3/carrier.d.ts +2 -2
- package/dist/v3/controller.js +2 -6
- package/dist/v3/protocol.js +2 -103
- package/dist/v3/retryDisposition.d.ts +2 -2
- package/dist/v3/retryDisposition.js +0 -7
- package/dist/v3/security.d.ts +1 -10
- package/dist/v3/security.js +2 -9
- package/dist/vendor/tr46.d.ts +4 -0
- package/dist/vendor/tr46.js +37 -32
- package/package.json +19 -17
- package/sbom/cyclonedx.json +62 -62
- package/sbom/spdx.json +65 -65
- package/dist/browser/connectSession.d.ts +0 -13
- package/dist/browser/connectSession.js +0 -40
- package/dist/browser/runtimeCapability.d.ts +0 -13
- package/dist/browser/runtimeCapability.js +0 -27
- package/dist/browser/sessionRuntime.d.ts +0 -2
- package/dist/browser/sessionRuntime.js +0 -21
- package/dist/browser/v2.d.ts +0 -3
- package/dist/browser/v2.js +0 -2
- package/dist/browser/webTransportClient.d.ts +0 -8
- package/dist/browser/webTransportClient.js +0 -55
- package/dist/connectionController.d.ts +0 -55
- package/dist/connectionController.js +0 -441
- package/dist/connector/adapters/rawQuicCandidate.d.ts +0 -5
- package/dist/connector/adapters/rawQuicCandidate.js +0 -71
- package/dist/connector/adapters/webSocketCandidate.d.ts +0 -7
- package/dist/connector/adapters/webSocketCandidate.js +0 -116
- package/dist/connector/adapters/webTransportCandidate.d.ts +0 -5
- package/dist/connector/adapters/webTransportCandidate.js +0 -73
- package/dist/connector/admissionCommit.d.ts +0 -31
- package/dist/connector/admissionCommit.js +0 -239
- package/dist/connector/sessionAcceptor.d.ts +0 -38
- package/dist/connector/sessionAcceptor.js +0 -158
- package/dist/connector/sessionConfig.d.ts +0 -3
- package/dist/connector/sessionConfig.js +0 -30
- package/dist/connector/sessionConnector.d.ts +0 -47
- package/dist/connector/sessionConnector.js +0 -606
- package/dist/node/connectSession.d.ts +0 -23
- package/dist/node/connectSession.js +0 -93
- package/dist/node/controlplane.d.ts +0 -137
- package/dist/node/controlplane.js +0 -443
- package/dist/node/rawQuicAdapter.d.ts +0 -9
- package/dist/node/rawQuicServer.d.ts +0 -24
- package/dist/node/rawQuicServer.js +0 -37
- package/dist/node/runtimeCapability.d.ts +0 -14
- package/dist/node/runtimeCapability.js +0 -24
- package/dist/node/sessionRuntime.d.ts +0 -2
- package/dist/node/sessionRuntime.js +0 -13
- package/dist/node/tunnelRuntime.d.ts +0 -42
- package/dist/node/tunnelRuntime.js +0 -559
- package/dist/node/v2.d.ts +0 -11
- package/dist/node/v2.js +0 -6
- package/dist/node/webSocketServer.d.ts +0 -24
- package/dist/node/webSocketServer.js +0 -135
- package/dist/public/artifact.d.ts +0 -11
- package/dist/public/artifact.js +0 -44
- package/dist/public/artifactLease.d.ts +0 -10
- package/dist/public/artifactLease.js +0 -41
- package/dist/public/connectError.d.ts +0 -6
- package/dist/public/connectError.js +0 -9
- package/dist/transport/webSocketAdapter.d.ts +0 -31
- package/dist/transport/webSocketAdapter.js +0 -370
- package/dist/transport/webTransportAdapter.d.ts +0 -27
- package/dist/transport/webTransportAdapter.js +0 -534
- package/dist/v2/admissionError.d.ts +0 -4
- package/dist/v2/admissionError.js +0 -8
- package/dist/v2/artifact.d.ts +0 -128
- package/dist/v2/artifact.js +0 -1131
- package/dist/v2/artifactLease.d.ts +0 -1
- package/dist/v2/artifactLease.js +0 -7
- package/dist/v2/capability.d.ts +0 -29
- package/dist/v2/capability.js +0 -190
- package/dist/v2/carrier.d.ts +0 -84
- package/dist/v2/carrier.js +0 -411
- package/dist/v2/contract.d.ts +0 -46
- package/dist/v2/contract.js +0 -1
- package/dist/v2/handshake.d.ts +0 -80
- package/dist/v2/handshake.js +0 -470
- package/dist/v2/index.d.ts +0 -14
- package/dist/v2/index.js +0 -7
- package/dist/v2/opaqueArtifact.d.ts +0 -1
- package/dist/v2/opaqueArtifact.js +0 -2
- package/dist/v2/protocol.d.ts +0 -99
- package/dist/v2/protocol.js +0 -737
- package/dist/v2/publicSession.d.ts +0 -1
- package/dist/v2/publicSession.js +0 -281
- package/dist/v2/retryDisposition.d.ts +0 -16
- package/dist/v2/retryDisposition.js +0 -30
- package/dist/v2/session.d.ts +0 -310
- package/dist/v2/session.js +0 -2127
- package/dist/v2/streamLifetimeLedger.d.ts +0 -31
- package/dist/v2/streamLifetimeLedger.js +0 -131
- package/dist/v2/streamMetadata.d.ts +0 -1
- package/dist/v2/streamMetadata.js +0 -7
- package/dist/v2/unreliableMessage.d.ts +0 -43
- package/dist/v2/unreliableMessage.js +0 -254
|
@@ -1,606 +0,0 @@
|
|
|
1
|
-
import { buildFSB2RequestV2, encodeFSB2RequestV2, validateArtifactV2, } from "../v2/artifact.js";
|
|
2
|
-
import { AdmissionSessionV2Error } from "../v2/admissionError.js";
|
|
3
|
-
import { establishSessionV2, SessionV2Error, } from "../v2/session.js";
|
|
4
|
-
import { unwrapArtifact } from "../v2/opaqueArtifact.js";
|
|
5
|
-
import { artifactLeaseArtifact } from "../public/artifactLease.js";
|
|
6
|
-
import { ConnectError } from "../public/connectError.js";
|
|
7
|
-
import { AbortError, TimeoutError, connectErrorDetailsInternal, createConnectErrorInternal, } from "../utils/errors.js";
|
|
8
|
-
import { validateRuntimeCapabilityDescriptorV2, } from "../v2/capability.js";
|
|
9
|
-
import { SDK_DEFAULTS } from "../defaults.js";
|
|
10
|
-
import { sessionConfigFromArtifactV2 } from "./sessionConfig.js";
|
|
11
|
-
import { commitClientAdmissionV2, CredentialCommitError, } from "./admissionCommit.js";
|
|
12
|
-
export function composeCandidateAttemptFactoryV2(factories) {
|
|
13
|
-
return Object.freeze({
|
|
14
|
-
create(candidate, artifact) {
|
|
15
|
-
const factory = factories[candidate.carrier];
|
|
16
|
-
if (factory === undefined)
|
|
17
|
-
throw new Error(`runtime does not implement ${candidate.carrier}`);
|
|
18
|
-
return factory.create(candidate, artifact);
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
export class SessionConnectorV2 {
|
|
23
|
-
lease;
|
|
24
|
-
attemptFactory;
|
|
25
|
-
options;
|
|
26
|
-
state = "attempt";
|
|
27
|
-
claimed = false;
|
|
28
|
-
artifact;
|
|
29
|
-
constructor(lease, attemptFactory, options) {
|
|
30
|
-
this.lease = lease;
|
|
31
|
-
this.attemptFactory = attemptFactory;
|
|
32
|
-
this.options = options;
|
|
33
|
-
this.artifact = unwrapArtifact(artifactLeaseArtifact(lease));
|
|
34
|
-
}
|
|
35
|
-
async connect(options = {}) {
|
|
36
|
-
const path = connectorPath(this.artifact);
|
|
37
|
-
if (this.claimed) {
|
|
38
|
-
throw connectorError(path, "validate", "invalid_input", new Error("Flowersec v2 artifact is already claimed"));
|
|
39
|
-
}
|
|
40
|
-
this.claimed = true;
|
|
41
|
-
let stage = "validate";
|
|
42
|
-
let deadline;
|
|
43
|
-
let operation;
|
|
44
|
-
const diagnostics = [];
|
|
45
|
-
try {
|
|
46
|
-
let loserCloseTimeoutMs;
|
|
47
|
-
let connectTimeoutMs;
|
|
48
|
-
try {
|
|
49
|
-
loserCloseTimeoutMs = normalizeLoserCloseTimeout(this.options.loserCloseTimeoutMs);
|
|
50
|
-
connectTimeoutMs = normalizeConnectTimeout(this.options.connectTimeoutMs);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
throw connectorError(path, "validate", "invalid_option", error);
|
|
54
|
-
}
|
|
55
|
-
let canonical;
|
|
56
|
-
try {
|
|
57
|
-
canonical = validateArtifactV2(this.artifact);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
throw connectorError(path, "validate", "invalid_input", error);
|
|
61
|
-
}
|
|
62
|
-
let remaining;
|
|
63
|
-
try {
|
|
64
|
-
remaining = artifactRemainingMilliseconds(this.artifact, this.options.now);
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
throw connectorError(path, "validate", error instanceof ArtifactExpiredError ? "timeout" : "invalid_input", error);
|
|
68
|
-
}
|
|
69
|
-
const capability = this.options.capability;
|
|
70
|
-
if (capability === undefined) {
|
|
71
|
-
throw connectorError(path, "validate", "invalid_option", new TypeError("runtime capability is required"));
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
validateRuntimeCapabilityDescriptorV2(capability);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
throw connectorError(path, "validate", "invalid_option", error);
|
|
78
|
-
}
|
|
79
|
-
if (capability.language !== "typescript") {
|
|
80
|
-
throw connectorError(path, "validate", "invalid_option", new TypeError("SessionV2 connector requires a TypeScript runtime capability descriptor"));
|
|
81
|
-
}
|
|
82
|
-
const requiredRole = this.artifact.path.kind === "tunnel" && this.artifact.path.role === 2
|
|
83
|
-
? "server"
|
|
84
|
-
: "client";
|
|
85
|
-
const supported = new Set(capability.tuples
|
|
86
|
-
.filter(({ networkMode, sessionRole, path }) => networkMode === "dial" && sessionRole === requiredRole && path === this.artifact.path.kind)
|
|
87
|
-
.map(({ carrier }) => carrier));
|
|
88
|
-
const candidates = canonical.candidates.filter((candidate) => supported.has(candidate.carrier));
|
|
89
|
-
if (candidates.length === 0) {
|
|
90
|
-
throw connectorError(path, "validate", "unsupported_capability", new Error("no runtime-compatible Flowersec v2 candidate"));
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
deadline = createConnectorDeadline(Math.min(connectTimeoutMs, this.artifact.session.establish_timeout_seconds * 1_000, remaining), this.options.deadlineFactory);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
throw connectorError(path, "validate", "invalid_option", error);
|
|
97
|
-
}
|
|
98
|
-
operation = combineConnectorSignals(options.signal, deadline.signal);
|
|
99
|
-
const operationSignal = operation.signal;
|
|
100
|
-
const cancellationSignal = options.signal;
|
|
101
|
-
const abortSources = cancellationSignal === undefined
|
|
102
|
-
? { timeoutSignal: deadline.signal }
|
|
103
|
-
: { cancellationSignal, timeoutSignal: deadline.signal };
|
|
104
|
-
stage = "connect";
|
|
105
|
-
throwIfAborted(operationSignal);
|
|
106
|
-
if (this.attemptFactory == null) {
|
|
107
|
-
throw connectorError(path, "validate", "invalid_option", new TypeError("candidate attempt factory is required"));
|
|
108
|
-
}
|
|
109
|
-
const attempts = [];
|
|
110
|
-
const createErrors = [];
|
|
111
|
-
for (const candidate of candidates) {
|
|
112
|
-
try {
|
|
113
|
-
const attempt = this.attemptFactory.create(candidate, this.artifact);
|
|
114
|
-
if (attempt == null)
|
|
115
|
-
throw new TypeError("candidate factory returned no attempt");
|
|
116
|
-
attempts.push(attempt);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
const failure = candidateFailure(candidate, "connect", "dial_failed", error);
|
|
120
|
-
diagnostics.push(failure.diagnostic);
|
|
121
|
-
createErrors.push(failure.error);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (attempts.length === 0) {
|
|
125
|
-
throw connectorError(path, "connect", dominantDiagnosticCode(diagnostics, "dial_failed"), aggregateErrors("no Flowersec v2 candidate could be created", createErrors), diagnostics);
|
|
126
|
-
}
|
|
127
|
-
const barrier = deferred();
|
|
128
|
-
const results = new ResultQueue();
|
|
129
|
-
const attemptTasks = attempts.map(async (attempt) => {
|
|
130
|
-
await barrier.promise;
|
|
131
|
-
let result;
|
|
132
|
-
try {
|
|
133
|
-
result = { attempt, ready: await attempt.ready(operationSignal) };
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
result = { attempt, error };
|
|
137
|
-
}
|
|
138
|
-
results.push(result);
|
|
139
|
-
return result;
|
|
140
|
-
});
|
|
141
|
-
barrier.resolve();
|
|
142
|
-
let winner;
|
|
143
|
-
const errors = [...createErrors];
|
|
144
|
-
try {
|
|
145
|
-
for (let remaining = attempts.length; remaining > 0;) {
|
|
146
|
-
throwIfAborted(operationSignal);
|
|
147
|
-
const result = await raceAbort(results.shift(), operationSignal);
|
|
148
|
-
remaining--;
|
|
149
|
-
if (result.ready !== undefined) {
|
|
150
|
-
winner = result;
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
const failure = candidateFailure(result.attempt.candidate, "connect", "dial_failed", result.error);
|
|
154
|
-
diagnostics.push(failure.diagnostic);
|
|
155
|
-
errors.push(failure.error);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
const cleanupFailures = await closeCandidateLosers(attempts, attemptTasks, undefined, operationSignal, abortSources, loserCloseTimeoutMs);
|
|
160
|
-
diagnostics.push(...cleanupFailures.map(({ diagnostic }) => diagnostic));
|
|
161
|
-
throw connectorError(path, "connect", contextualErrorCode(error, "dial_failed", options.signal, deadline.signal), aggregateErrors("candidate race failed", [asError(error), ...cleanupFailures.map(({ error }) => error)]), diagnostics);
|
|
162
|
-
}
|
|
163
|
-
if (winner?.ready === undefined) {
|
|
164
|
-
const cleanupFailures = await closeCandidateLosers(attempts, attemptTasks, undefined, operationSignal, abortSources, loserCloseTimeoutMs);
|
|
165
|
-
diagnostics.push(...cleanupFailures.map(({ diagnostic }) => diagnostic));
|
|
166
|
-
const failureStage = cleanupFailures.length === 0 ? "connect" : "close";
|
|
167
|
-
throw connectorError(path, failureStage, failureStage === "connect"
|
|
168
|
-
? dominantDiagnosticCode(diagnostics, "dial_failed")
|
|
169
|
-
: dominantCandidateCode(cleanupFailures, "not_connected"), aggregateErrors(cleanupFailures.length === 0
|
|
170
|
-
? "no Flowersec v2 candidate became ready"
|
|
171
|
-
: "no Flowersec v2 candidate became ready; candidate cleanup failed", [...errors, ...cleanupFailures.map(({ error }) => error)]), diagnostics);
|
|
172
|
-
}
|
|
173
|
-
this.state = "ready";
|
|
174
|
-
const selected = { attempt: winner.attempt, ready: winner.ready };
|
|
175
|
-
this.state = "winner";
|
|
176
|
-
stage = "close";
|
|
177
|
-
const cleanupFailures = await closeCandidateLosers(attempts, attemptTasks, selected, operationSignal, abortSources, loserCloseTimeoutMs);
|
|
178
|
-
if (cleanupFailures.length !== 0) {
|
|
179
|
-
const winnerClose = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => selected.ready.close(), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
|
|
180
|
-
if (winnerClose !== undefined)
|
|
181
|
-
cleanupFailures.push(winnerClose);
|
|
182
|
-
diagnostics.push(...cleanupFailures.map(({ diagnostic }) => diagnostic));
|
|
183
|
-
throw connectorError(path, "close", dominantCandidateCode(cleanupFailures, "not_connected"), aggregateErrors("candidate cleanup failed", cleanupFailures.map(({ error }) => error)), diagnostics);
|
|
184
|
-
}
|
|
185
|
-
stage = "validate";
|
|
186
|
-
try {
|
|
187
|
-
artifactRemainingMilliseconds(this.artifact, this.options.now);
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
const closeFailure = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => selected.ready.close(), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
|
|
191
|
-
if (closeFailure !== undefined)
|
|
192
|
-
diagnostics.push(closeFailure.diagnostic);
|
|
193
|
-
throw connectorError(path, "validate", error instanceof ArtifactExpiredError ? "timeout" : "invalid_input", aggregateErrors("artifact validation failed after candidate selection", [
|
|
194
|
-
asError(error),
|
|
195
|
-
...(closeFailure === undefined ? [] : [closeFailure.error]),
|
|
196
|
-
]), diagnostics);
|
|
197
|
-
}
|
|
198
|
-
let rawFSB2;
|
|
199
|
-
let config;
|
|
200
|
-
try {
|
|
201
|
-
rawFSB2 = encodeFSB2RequestV2(buildFSB2RequestV2(this.artifact, selected.attempt.candidate.id));
|
|
202
|
-
const baseConfig = sessionConfigFromArtifactV2(this.artifact, rawFSB2, this.options.runtime, this.options.deadlineFactory);
|
|
203
|
-
config = this.options.rpcRouter === undefined
|
|
204
|
-
? baseConfig
|
|
205
|
-
: { ...baseConfig, rpcRouter: this.options.rpcRouter };
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
const closeFailure = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => selected.ready.close(), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
|
|
209
|
-
if (closeFailure !== undefined)
|
|
210
|
-
diagnostics.push(closeFailure.diagnostic);
|
|
211
|
-
throw connectorError(path, "validate", "invalid_input", aggregateErrors("failed to prepare Flowersec v2 admission request", [asError(error), ...(closeFailure === undefined ? [] : [closeFailure.error])]), diagnostics);
|
|
212
|
-
}
|
|
213
|
-
stage = "attach";
|
|
214
|
-
try {
|
|
215
|
-
throwIfAborted(operationSignal);
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
const closeFailure = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => selected.ready.close(), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
|
|
219
|
-
if (closeFailure !== undefined)
|
|
220
|
-
diagnostics.push(closeFailure.diagnostic);
|
|
221
|
-
throw connectorError(path, "attach", contextualErrorCode(error, "attach_failed", options.signal, deadline.signal), aggregateErrors("connection canceled before durable spend", [
|
|
222
|
-
asError(error),
|
|
223
|
-
...(closeFailure === undefined ? [] : [closeFailure.error]),
|
|
224
|
-
]), diagnostics);
|
|
225
|
-
}
|
|
226
|
-
let session;
|
|
227
|
-
stage = "attach";
|
|
228
|
-
try {
|
|
229
|
-
throwIfAborted(operationSignal);
|
|
230
|
-
const committed = await commitClientAdmissionV2(selected.ready, this.lease, () => {
|
|
231
|
-
artifactRemainingMilliseconds(this.artifact, this.options.now);
|
|
232
|
-
}, rawFSB2, config, operationSignal);
|
|
233
|
-
if (committed == null)
|
|
234
|
-
throw new TypeError("candidate commit returned no carrier session");
|
|
235
|
-
this.state = "admitted";
|
|
236
|
-
session = await establishSessionV2(committed, config, operationSignal === undefined ? {} : { signal: operationSignal });
|
|
237
|
-
throwIfAborted(operationSignal);
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
const failureStage = error instanceof ArtifactExpiredError
|
|
241
|
-
? "validate"
|
|
242
|
-
: error instanceof SessionV2Error || error instanceof CredentialCommitError
|
|
243
|
-
? "handshake"
|
|
244
|
-
: "attach";
|
|
245
|
-
const defaultCode = error instanceof ArtifactExpiredError
|
|
246
|
-
? "timeout"
|
|
247
|
-
: error instanceof CredentialCommitError
|
|
248
|
-
? "credential_commit_failed"
|
|
249
|
-
: error instanceof SessionV2Error
|
|
250
|
-
? "handshake_failed"
|
|
251
|
-
: "attach_failed";
|
|
252
|
-
diagnostics.push(candidateFailure(selected.attempt.candidate, failureStage, defaultCode, error).diagnostic);
|
|
253
|
-
const closeFailure = await captureCandidateCleanupFailure(selected.attempt.candidate, "close selected candidate", () => closeCommittedSession(session, selected.ready), loserCloseTimeoutMs, operationSignal, abortSources, selected.ready.abort);
|
|
254
|
-
if (closeFailure !== undefined)
|
|
255
|
-
diagnostics.push(closeFailure.diagnostic);
|
|
256
|
-
throw connectorError(path, failureStage, contextualErrorCode(error, defaultCode, options.signal, deadline.signal), aggregateErrors(error instanceof CredentialCommitError
|
|
257
|
-
? "durable credential spend failed"
|
|
258
|
-
: error instanceof AdmissionSessionV2Error
|
|
259
|
-
? "Flowersec v2 admission failed"
|
|
260
|
-
: failureStage === "handshake"
|
|
261
|
-
? "Flowersec v2 session handshake failed"
|
|
262
|
-
: "Flowersec v2 candidate commit failed", [asError(error), ...(closeFailure === undefined ? [] : [closeFailure.error])]), diagnostics);
|
|
263
|
-
}
|
|
264
|
-
this.state = "established";
|
|
265
|
-
void session.waitTermination().finally(() => {
|
|
266
|
-
this.state = "terminated";
|
|
267
|
-
});
|
|
268
|
-
return { candidate: selected.attempt.candidate, session };
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
this.state = "terminated";
|
|
272
|
-
throw normalizeConnectorError(error, path, stage, diagnostics, options.signal, deadline?.signal);
|
|
273
|
-
}
|
|
274
|
-
finally {
|
|
275
|
-
operation?.cancel();
|
|
276
|
-
deadline?.cancel();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
async function closeCommittedSession(session, ready) {
|
|
281
|
-
const operations = [() => ready.close()];
|
|
282
|
-
if (session !== undefined && typeof session.close === "function") {
|
|
283
|
-
operations.push(() => session.close());
|
|
284
|
-
}
|
|
285
|
-
const settled = await Promise.allSettled(operations.map(async (operation) => await operation()));
|
|
286
|
-
const failures = settled.flatMap((result) => result.status === "rejected" ? [asError(result.reason)] : []);
|
|
287
|
-
if (failures.length !== 0)
|
|
288
|
-
throw aggregateErrors("committed session cleanup failed", failures);
|
|
289
|
-
}
|
|
290
|
-
async function closeCandidateLosers(attempts, attemptTasks, winner, operationSignal, abortSources, timeoutMs) {
|
|
291
|
-
const deadline = new AbortController();
|
|
292
|
-
const timeoutError = new TimeoutError("candidate cleanup timeout");
|
|
293
|
-
const timer = setTimeout(() => deadline.abort(timeoutError), timeoutMs);
|
|
294
|
-
const signal = combineConnectorSignals(operationSignal, deadline.signal);
|
|
295
|
-
const failures = [];
|
|
296
|
-
const loserSet = new Set(attempts.filter((attempt) => winner === undefined || attempt !== winner.attempt));
|
|
297
|
-
try {
|
|
298
|
-
for (const attempt of loserSet) {
|
|
299
|
-
try {
|
|
300
|
-
attempt.abort();
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
failures.push(candidateFailure(attempt.candidate, "close", "not_connected", error));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
let results;
|
|
307
|
-
try {
|
|
308
|
-
results = await raceAbort(Promise.all(attemptTasks), signal.signal);
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
if (!signal.signal.aborted)
|
|
312
|
-
throw error;
|
|
313
|
-
const code = cleanupErrorCode(error, "not_connected", abortSources, deadline.signal);
|
|
314
|
-
for (const attempt of loserSet) {
|
|
315
|
-
failures.push(candidateFailure(attempt.candidate, "close", code, abortReason(signal.signal)));
|
|
316
|
-
}
|
|
317
|
-
return failures;
|
|
318
|
-
}
|
|
319
|
-
for (const result of results) {
|
|
320
|
-
if (result.ready === undefined || !loserSet.has(result.attempt))
|
|
321
|
-
continue;
|
|
322
|
-
const failure = await captureCandidateCleanupFailure(result.attempt.candidate, "close candidate loser", () => result.ready.close(), timeoutMs, signal.signal, abortSources, result.ready.abort);
|
|
323
|
-
if (failure !== undefined)
|
|
324
|
-
failures.push(failure);
|
|
325
|
-
}
|
|
326
|
-
return failures;
|
|
327
|
-
}
|
|
328
|
-
finally {
|
|
329
|
-
clearTimeout(timer);
|
|
330
|
-
signal.cancel();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
async function captureCandidateCleanupFailure(candidate, label, operation, timeoutMs, operationSignal, abortSources = {}, abort) {
|
|
334
|
-
const deadline = new AbortController();
|
|
335
|
-
const timer = setTimeout(() => deadline.abort(new TimeoutError(`${label} timeout`)), timeoutMs);
|
|
336
|
-
const signal = combineConnectorSignals(operationSignal, deadline.signal);
|
|
337
|
-
try {
|
|
338
|
-
let pending;
|
|
339
|
-
try {
|
|
340
|
-
pending = Promise.resolve(operation());
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
return candidateFailure(candidate, "close", "not_connected", new Error(`${label}: ${asError(error).message}`));
|
|
344
|
-
}
|
|
345
|
-
void pending.catch(() => undefined);
|
|
346
|
-
await raceAbort(pending, signal.signal);
|
|
347
|
-
return undefined;
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
const code = cleanupErrorCode(error, "not_connected", abortSources, deadline.signal);
|
|
351
|
-
const causes = [asError(error)];
|
|
352
|
-
if (signal.signal.aborted && abort !== undefined) {
|
|
353
|
-
try {
|
|
354
|
-
abort();
|
|
355
|
-
}
|
|
356
|
-
catch (abortError) {
|
|
357
|
-
causes.push(asError(abortError));
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return candidateFailure(candidate, "close", code, causes.length === 1
|
|
361
|
-
? new Error(`${label}: ${causes[0].message}`)
|
|
362
|
-
: aggregateErrors(`${label} failed`, causes));
|
|
363
|
-
}
|
|
364
|
-
finally {
|
|
365
|
-
clearTimeout(timer);
|
|
366
|
-
signal.cancel();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function normalizeLoserCloseTimeout(value) {
|
|
370
|
-
const timeout = value ?? 1_000;
|
|
371
|
-
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 60_000) {
|
|
372
|
-
throw new RangeError("loserCloseTimeoutMs must be an integer from 1 to 60000");
|
|
373
|
-
}
|
|
374
|
-
return timeout;
|
|
375
|
-
}
|
|
376
|
-
function normalizeConnectTimeout(value) {
|
|
377
|
-
const timeout = value ?? SDK_DEFAULTS.transport.connectTimeoutMs;
|
|
378
|
-
if (!Number.isSafeInteger(timeout) || timeout < 1) {
|
|
379
|
-
throw new RangeError("connectTimeoutMs must be a positive safe integer");
|
|
380
|
-
}
|
|
381
|
-
return timeout;
|
|
382
|
-
}
|
|
383
|
-
function artifactRemainingMilliseconds(artifact, now) {
|
|
384
|
-
const current = (now ?? Date.now)();
|
|
385
|
-
if (!Number.isFinite(current))
|
|
386
|
-
throw new TypeError("session V2 clock returned a non-finite value");
|
|
387
|
-
const remaining = artifact.session.init_expire_at_unix_s * 1_000 - current;
|
|
388
|
-
if (remaining <= 0)
|
|
389
|
-
throw new ArtifactExpiredError();
|
|
390
|
-
return remaining;
|
|
391
|
-
}
|
|
392
|
-
class ArtifactExpiredError extends Error {
|
|
393
|
-
constructor() {
|
|
394
|
-
super("Flowersec v2 artifact initiation deadline expired");
|
|
395
|
-
this.name = "ArtifactExpiredError";
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
class ResultQueue {
|
|
399
|
-
values = [];
|
|
400
|
-
waiters = [];
|
|
401
|
-
push(value) {
|
|
402
|
-
const waiter = this.waiters.shift();
|
|
403
|
-
if (waiter !== undefined)
|
|
404
|
-
waiter(value);
|
|
405
|
-
else
|
|
406
|
-
this.values.push(value);
|
|
407
|
-
}
|
|
408
|
-
async shift() {
|
|
409
|
-
const value = this.values.shift();
|
|
410
|
-
if (value !== undefined)
|
|
411
|
-
return value;
|
|
412
|
-
return await new Promise((resolve) => this.waiters.push(resolve));
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
function deferred() {
|
|
416
|
-
let resolve;
|
|
417
|
-
const promise = new Promise((resolvePromise) => { resolve = resolvePromise; });
|
|
418
|
-
return { promise, resolve };
|
|
419
|
-
}
|
|
420
|
-
async function raceAbort(promise, signal) {
|
|
421
|
-
if (signal === undefined)
|
|
422
|
-
return await promise;
|
|
423
|
-
throwIfAborted(signal);
|
|
424
|
-
return await new Promise((resolve, reject) => {
|
|
425
|
-
const abort = () => reject(abortReason(signal));
|
|
426
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
427
|
-
void promise.then((value) => { signal.removeEventListener("abort", abort); resolve(value); }, (error) => { signal.removeEventListener("abort", abort); reject(error); });
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
function throwIfAborted(signal) {
|
|
431
|
-
if (signal?.aborted === true)
|
|
432
|
-
throw abortReason(signal);
|
|
433
|
-
}
|
|
434
|
-
function abortReason(signal) {
|
|
435
|
-
return signal.reason instanceof Error ? signal.reason : new Error("session V2 connect aborted");
|
|
436
|
-
}
|
|
437
|
-
function asError(error) {
|
|
438
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
439
|
-
}
|
|
440
|
-
function connectorPath(artifact) {
|
|
441
|
-
if (artifact?.path?.kind === "tunnel")
|
|
442
|
-
return "tunnel";
|
|
443
|
-
if (artifact?.path?.kind === "direct")
|
|
444
|
-
return "direct";
|
|
445
|
-
return "auto";
|
|
446
|
-
}
|
|
447
|
-
function connectorError(path, stage, code, cause, diagnostics = []) {
|
|
448
|
-
return createConnectErrorInternal({
|
|
449
|
-
path,
|
|
450
|
-
stage,
|
|
451
|
-
code,
|
|
452
|
-
cause,
|
|
453
|
-
diagnostics,
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
function normalizeConnectorError(error, path, stage, diagnostics, cancellationSignal, timeoutSignal) {
|
|
457
|
-
if (error instanceof ConnectError) {
|
|
458
|
-
const details = connectErrorDetailsInternal(error);
|
|
459
|
-
const merged = mergeDiagnostics(details.diagnostics, diagnostics);
|
|
460
|
-
if (merged.length === details.diagnostics.length)
|
|
461
|
-
return error;
|
|
462
|
-
return createConnectErrorInternal({
|
|
463
|
-
path,
|
|
464
|
-
stage: details.stage,
|
|
465
|
-
code: details.code,
|
|
466
|
-
cause: details.cause ?? error,
|
|
467
|
-
diagnostics: merged,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
return connectorError(path, stage, contextualErrorCode(error, defaultCodeForStage(stage), cancellationSignal, timeoutSignal), error, diagnostics);
|
|
471
|
-
}
|
|
472
|
-
function defaultCodeForStage(stage) {
|
|
473
|
-
switch (stage) {
|
|
474
|
-
case "validate": return "invalid_input";
|
|
475
|
-
case "connect": return "dial_failed";
|
|
476
|
-
case "attach": return "attach_failed";
|
|
477
|
-
case "handshake": return "handshake_failed";
|
|
478
|
-
case "close": return "not_connected";
|
|
479
|
-
case "secure": return "handshake_failed";
|
|
480
|
-
case "yamux": return "mux_failed";
|
|
481
|
-
case "rpc": return "rpc_failed";
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
function contextualErrorCode(error, defaultCode, cancellationSignal, timeoutSignal) {
|
|
485
|
-
if (cancellationSignal?.aborted === true)
|
|
486
|
-
return "canceled";
|
|
487
|
-
if (timeoutSignal?.aborted === true)
|
|
488
|
-
return "timeout";
|
|
489
|
-
if (error instanceof TimeoutError || (error instanceof SessionV2Error && error.code === "timeout"))
|
|
490
|
-
return "timeout";
|
|
491
|
-
if (error instanceof AbortError ||
|
|
492
|
-
(error instanceof SessionV2Error && error.code === "aborted") ||
|
|
493
|
-
(error instanceof DOMException && error.name === "AbortError"))
|
|
494
|
-
return "canceled";
|
|
495
|
-
return defaultCode;
|
|
496
|
-
}
|
|
497
|
-
function cleanupErrorCode(error, defaultCode, abortSources, cleanupDeadline) {
|
|
498
|
-
return contextualErrorCode(error, defaultCode, abortSources.cancellationSignal, abortSources.timeoutSignal?.aborted === true ? abortSources.timeoutSignal : cleanupDeadline);
|
|
499
|
-
}
|
|
500
|
-
function candidateFailure(candidate, stage, defaultCode, error) {
|
|
501
|
-
const failure = asError(error);
|
|
502
|
-
const code = contextualErrorCode(error, defaultCode);
|
|
503
|
-
return {
|
|
504
|
-
error: failure,
|
|
505
|
-
diagnostic: {
|
|
506
|
-
candidateId: candidate.id,
|
|
507
|
-
carrier: candidate.carrier,
|
|
508
|
-
stage,
|
|
509
|
-
code,
|
|
510
|
-
message: boundedCandidateDiagnosticMessage(failure.message),
|
|
511
|
-
},
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
function dominantCandidateCode(failures, defaultCode) {
|
|
515
|
-
if (failures.some(({ diagnostic }) => diagnostic.code === "canceled"))
|
|
516
|
-
return "canceled";
|
|
517
|
-
if (failures.some(({ diagnostic }) => diagnostic.code === "timeout"))
|
|
518
|
-
return "timeout";
|
|
519
|
-
if (failures.some(({ diagnostic }) => diagnostic.code === "not_connected"))
|
|
520
|
-
return "not_connected";
|
|
521
|
-
return defaultCode;
|
|
522
|
-
}
|
|
523
|
-
function dominantDiagnosticCode(diagnostics, defaultCode) {
|
|
524
|
-
if (diagnostics.some(({ code }) => code === "canceled"))
|
|
525
|
-
return "canceled";
|
|
526
|
-
if (diagnostics.some(({ code }) => code === "timeout"))
|
|
527
|
-
return "timeout";
|
|
528
|
-
return defaultCode;
|
|
529
|
-
}
|
|
530
|
-
const MAX_CANDIDATE_DIAGNOSTIC_MESSAGE_BYTES = 1_024;
|
|
531
|
-
const candidateDiagnosticEncoder = new TextEncoder();
|
|
532
|
-
function boundedCandidateDiagnosticMessage(message) {
|
|
533
|
-
if (candidateDiagnosticEncoder.encode(message).length <= MAX_CANDIDATE_DIAGNOSTIC_MESSAGE_BYTES)
|
|
534
|
-
return message;
|
|
535
|
-
let result = "";
|
|
536
|
-
let bytes = 0;
|
|
537
|
-
for (const character of message) {
|
|
538
|
-
const characterBytes = candidateDiagnosticEncoder.encode(character).length;
|
|
539
|
-
if (bytes + characterBytes > MAX_CANDIDATE_DIAGNOSTIC_MESSAGE_BYTES)
|
|
540
|
-
break;
|
|
541
|
-
result += character;
|
|
542
|
-
bytes += characterBytes;
|
|
543
|
-
}
|
|
544
|
-
return result;
|
|
545
|
-
}
|
|
546
|
-
function mergeDiagnostics(left, right) {
|
|
547
|
-
const merged = [];
|
|
548
|
-
const seen = new Set();
|
|
549
|
-
for (const diagnostic of [...left, ...right]) {
|
|
550
|
-
const key = [
|
|
551
|
-
diagnostic.candidateId,
|
|
552
|
-
diagnostic.carrier,
|
|
553
|
-
diagnostic.stage,
|
|
554
|
-
diagnostic.code,
|
|
555
|
-
diagnostic.message,
|
|
556
|
-
].join("\u0000");
|
|
557
|
-
if (seen.has(key))
|
|
558
|
-
continue;
|
|
559
|
-
seen.add(key);
|
|
560
|
-
merged.push(diagnostic);
|
|
561
|
-
}
|
|
562
|
-
return merged;
|
|
563
|
-
}
|
|
564
|
-
function aggregateErrors(label, errors) {
|
|
565
|
-
if (errors.length === 0)
|
|
566
|
-
return new Error(label);
|
|
567
|
-
return new AggregateError(errors, `${label}: ${errors.map(({ message }) => message).join("; ")}`);
|
|
568
|
-
}
|
|
569
|
-
function createConnectorDeadline(timeoutMs, factory) {
|
|
570
|
-
if (factory !== undefined) {
|
|
571
|
-
const handle = factory(timeoutMs, "establish");
|
|
572
|
-
if (handle == null || !isAbortSignal(handle.signal) || typeof handle.cancel !== "function") {
|
|
573
|
-
throw new TypeError("deadlineFactory returned an invalid establish deadline handle");
|
|
574
|
-
}
|
|
575
|
-
return handle;
|
|
576
|
-
}
|
|
577
|
-
const controller = new AbortController();
|
|
578
|
-
const timer = setTimeout(() => controller.abort(new TimeoutError("session V2 establish deadline exceeded")), timeoutMs);
|
|
579
|
-
return { signal: controller.signal, cancel: () => clearTimeout(timer) };
|
|
580
|
-
}
|
|
581
|
-
function isAbortSignal(value) {
|
|
582
|
-
if (typeof value !== "object" || value === null)
|
|
583
|
-
return false;
|
|
584
|
-
const signal = value;
|
|
585
|
-
return typeof signal.aborted === "boolean" &&
|
|
586
|
-
typeof signal.addEventListener === "function" &&
|
|
587
|
-
typeof signal.removeEventListener === "function";
|
|
588
|
-
}
|
|
589
|
-
function combineConnectorSignals(...signals) {
|
|
590
|
-
const controller = new AbortController();
|
|
591
|
-
const cleanups = [];
|
|
592
|
-
for (const signal of signals) {
|
|
593
|
-
if (signal === undefined)
|
|
594
|
-
continue;
|
|
595
|
-
const abort = () => {
|
|
596
|
-
if (!controller.signal.aborted)
|
|
597
|
-
controller.abort(signal.reason instanceof Error ? signal.reason : new Error("session V2 connect aborted"));
|
|
598
|
-
};
|
|
599
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
600
|
-
cleanups.push(() => signal.removeEventListener("abort", abort));
|
|
601
|
-
if (signal.aborted)
|
|
602
|
-
abort();
|
|
603
|
-
}
|
|
604
|
-
return { signal: controller.signal, cancel: () => { for (const cleanup of cleanups.splice(0))
|
|
605
|
-
cleanup(); } };
|
|
606
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ArtifactLease } from "../public/artifactLease.js";
|
|
2
|
-
import type { Session } from "../public/contract.js";
|
|
3
|
-
import { type ArtifactSource, type ConnectionController } from "../connectionController.js";
|
|
4
|
-
import { type RPCHandlers } from "./acceptor.js";
|
|
5
|
-
export type SessionTLSOptions = Readonly<{
|
|
6
|
-
ca?: string | Uint8Array;
|
|
7
|
-
}>;
|
|
8
|
-
export type SessionOptions = Readonly<{
|
|
9
|
-
origin?: string;
|
|
10
|
-
signal?: AbortSignal;
|
|
11
|
-
connectTimeoutMs?: number;
|
|
12
|
-
tls?: SessionTLSOptions;
|
|
13
|
-
rpcHandlers?: RPCHandlers;
|
|
14
|
-
}>;
|
|
15
|
-
export type ConnectionControllerOptions = Readonly<{
|
|
16
|
-
origin?: string;
|
|
17
|
-
connectTimeoutMs?: number;
|
|
18
|
-
tls?: SessionTLSOptions;
|
|
19
|
-
maximumAttempts?: number;
|
|
20
|
-
rpcHandlers?: RPCHandlers;
|
|
21
|
-
}>;
|
|
22
|
-
export declare function createConnectionController(source: ArtifactSource, options: ConnectionControllerOptions): ConnectionController;
|
|
23
|
-
export declare function connect(lease: ArtifactLease, options: SessionOptions): Promise<Session>;
|