@floegence/flowersec-core 0.22.1 → 0.24.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 +17 -65
- package/YAMUX_ALIGNMENT.md +4 -4
- package/dist/client-connect/connectCore.d.ts +3 -3
- package/dist/client-connect/connectCore.js +120 -106
- package/dist/client.d.ts +2 -0
- package/dist/controlplane/issuer.d.ts +5 -2
- package/dist/controlplane/issuer.js +76 -16
- package/dist/controlplane/request.js +3 -1
- package/dist/defaults.d.ts +2 -0
- package/dist/defaults.js +2 -0
- package/dist/e2ee/handshake.d.ts +2 -2
- package/dist/e2ee/secureChannel.d.ts +2 -2
- package/dist/e2ee/secureChannel.js +22 -10
- package/dist/endpoint/index.d.ts +1 -0
- package/dist/endpoint/index.js +26 -5
- package/dist/proxy/controllerWindow.js +4 -5
- package/dist/proxy/runtime.js +10 -19
- package/dist/reconnect/index.js +141 -88
- package/dist/rpc/server.d.ts +1 -0
- package/dist/rpc/server.js +49 -20
- package/dist/streamio/index.js +3 -6
- package/dist/utils/errors.d.ts +1 -1
- package/dist/yamux/byteReader.d.ts +2 -0
- package/dist/yamux/byteReader.js +28 -24
- package/dist/yamux/errors.d.ts +8 -0
- package/dist/yamux/errors.js +18 -0
- package/dist/yamux/session.js +8 -5
- package/dist/yamux/stream.d.ts +1 -1
- package/dist/yamux/stream.js +3 -3
- package/package.json +4 -1
package/dist/reconnect/index.js
CHANGED
|
@@ -1,32 +1,59 @@
|
|
|
1
1
|
import { getClientTermination } from "../client-connect/termination.js";
|
|
2
2
|
import { emitObserverDiagnostic, withObserverContext } from "../observability/observer.js";
|
|
3
3
|
import { SDK_DEFAULTS } from "../defaults.js";
|
|
4
|
+
import { AbortError, FlowersecError } from "../utils/errors.js";
|
|
4
5
|
export { createArtifactResolver, createControlplaneArtifactSource } from "./artifactControlplane.js";
|
|
5
6
|
function normalizeAutoReconnect(cfg) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
jitterRatio: SDK_DEFAULTS.reconnect.jitterRatio,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
enabled: true,
|
|
18
|
-
maxAttempts: Math.max(1, cfg.maxAttempts ?? SDK_DEFAULTS.reconnect.maxAttempts),
|
|
19
|
-
initialDelayMs: Math.max(0, cfg.initialDelayMs ?? SDK_DEFAULTS.reconnect.initialDelayMs),
|
|
20
|
-
maxDelayMs: Math.max(0, cfg.maxDelayMs ?? SDK_DEFAULTS.reconnect.maxDelayMs),
|
|
21
|
-
factor: Math.max(1, cfg.factor ?? SDK_DEFAULTS.reconnect.factor),
|
|
22
|
-
jitterRatio: Math.max(0, cfg.jitterRatio ?? SDK_DEFAULTS.reconnect.jitterRatio),
|
|
7
|
+
const settings = {
|
|
8
|
+
enabled: cfg?.enabled === true,
|
|
9
|
+
maxAttempts: cfg?.enabled === true ? (cfg.maxAttempts ?? SDK_DEFAULTS.reconnect.maxAttempts) : 1,
|
|
10
|
+
initialDelayMs: cfg?.initialDelayMs ?? SDK_DEFAULTS.reconnect.initialDelayMs,
|
|
11
|
+
maxDelayMs: cfg?.maxDelayMs ?? SDK_DEFAULTS.reconnect.maxDelayMs,
|
|
12
|
+
factor: cfg?.factor ?? SDK_DEFAULTS.reconnect.factor,
|
|
13
|
+
jitterRatio: cfg?.jitterRatio ?? SDK_DEFAULTS.reconnect.jitterRatio,
|
|
23
14
|
};
|
|
15
|
+
if (!Number.isSafeInteger(settings.maxAttempts) || settings.maxAttempts < 1) {
|
|
16
|
+
throw new TypeError("autoReconnect.maxAttempts must be a positive integer");
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isFinite(settings.initialDelayMs) || settings.initialDelayMs < 0) {
|
|
19
|
+
throw new TypeError("autoReconnect.initialDelayMs must be a non-negative finite number");
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(settings.maxDelayMs) || settings.maxDelayMs < 0) {
|
|
22
|
+
throw new TypeError("autoReconnect.maxDelayMs must be a non-negative finite number");
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isFinite(settings.factor) || settings.factor < 1) {
|
|
25
|
+
throw new TypeError("autoReconnect.factor must be a finite number greater than or equal to one");
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isFinite(settings.jitterRatio) || settings.jitterRatio < 0 || settings.jitterRatio > 1) {
|
|
28
|
+
throw new TypeError("autoReconnect.jitterRatio must be a finite number between zero and one");
|
|
29
|
+
}
|
|
30
|
+
return settings;
|
|
24
31
|
}
|
|
25
32
|
function backoffDelayMs(attemptIndex, cfg) {
|
|
26
33
|
const base = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * Math.pow(cfg.factor, attemptIndex));
|
|
27
34
|
const jitter = cfg.jitterRatio <= 0 ? 0 : base * cfg.jitterRatio * (Math.random() * 2 - 1);
|
|
28
35
|
return Math.max(0, Math.round(base + jitter));
|
|
29
36
|
}
|
|
37
|
+
const TERMINAL_RECONNECT_CODES = new Set([
|
|
38
|
+
"invalid_input",
|
|
39
|
+
"invalid_option",
|
|
40
|
+
"role_mismatch",
|
|
41
|
+
"transport_policy_denied",
|
|
42
|
+
"invalid_psk",
|
|
43
|
+
"invalid_suite",
|
|
44
|
+
"missing_grant",
|
|
45
|
+
"missing_connect_info",
|
|
46
|
+
"missing_tunnel_url",
|
|
47
|
+
"missing_ws_url",
|
|
48
|
+
"missing_channel_id",
|
|
49
|
+
"missing_token",
|
|
50
|
+
"missing_init_exp",
|
|
51
|
+
]);
|
|
52
|
+
function isTerminalConnectError(error) {
|
|
53
|
+
if (error instanceof AbortError || error.name === "AbortError")
|
|
54
|
+
return true;
|
|
55
|
+
return error instanceof FlowersecError && (error.code === "canceled" || TERMINAL_RECONNECT_CODES.has(error.code));
|
|
56
|
+
}
|
|
30
57
|
function isSameConfig(a, b) {
|
|
31
58
|
if (a == null)
|
|
32
59
|
return false;
|
|
@@ -73,13 +100,19 @@ export function createReconnectManager() {
|
|
|
73
100
|
retryResolve = null;
|
|
74
101
|
};
|
|
75
102
|
const abortActiveAttempt = () => {
|
|
103
|
+
attemptAbort?.abort("canceled");
|
|
104
|
+
attemptAbort = null;
|
|
105
|
+
};
|
|
106
|
+
const closeClient = (value) => {
|
|
107
|
+
if (value == null)
|
|
108
|
+
return null;
|
|
76
109
|
try {
|
|
77
|
-
|
|
110
|
+
value.close();
|
|
111
|
+
return null;
|
|
78
112
|
}
|
|
79
|
-
catch {
|
|
80
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
return asError(error);
|
|
81
115
|
}
|
|
82
|
-
attemptAbort = null;
|
|
83
116
|
};
|
|
84
117
|
const sleep = (ms) => new Promise((resolve) => {
|
|
85
118
|
retryResolve = resolve;
|
|
@@ -89,6 +122,30 @@ export function createReconnectManager() {
|
|
|
89
122
|
resolve();
|
|
90
123
|
}, ms);
|
|
91
124
|
});
|
|
125
|
+
const finishWithError = (cfg, error, currentAttemptSeq) => {
|
|
126
|
+
setState({ status: "error", error, client: null });
|
|
127
|
+
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq: currentAttemptSeq }), {
|
|
128
|
+
path: "auto",
|
|
129
|
+
stage: "reconnect",
|
|
130
|
+
code_domain: "event",
|
|
131
|
+
code: "reconnect_exhausted",
|
|
132
|
+
result: "fail",
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const scheduleRetry = async (t, cfg, error, settings, failedAttemptIndex, currentAttemptSeq) => {
|
|
136
|
+
if (t !== token || active !== cfg)
|
|
137
|
+
return false;
|
|
138
|
+
setState({ status: "connecting", error, client: null });
|
|
139
|
+
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq: currentAttemptSeq }), {
|
|
140
|
+
path: "auto",
|
|
141
|
+
stage: "reconnect",
|
|
142
|
+
code_domain: "event",
|
|
143
|
+
code: "reconnect_scheduled",
|
|
144
|
+
result: "retry",
|
|
145
|
+
});
|
|
146
|
+
await sleep(backoffDelayMs(failedAttemptIndex, settings));
|
|
147
|
+
return t === token && active === cfg;
|
|
148
|
+
};
|
|
92
149
|
const disconnectInternal = () => {
|
|
93
150
|
cancelRetrySleep();
|
|
94
151
|
abortActiveAttempt();
|
|
@@ -96,13 +153,10 @@ export function createReconnectManager() {
|
|
|
96
153
|
activeConnectPromise = null;
|
|
97
154
|
token += 1;
|
|
98
155
|
attemptSeq = 0;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
catch {
|
|
104
|
-
// ignore
|
|
105
|
-
}
|
|
156
|
+
const closeError = closeClient(s.client);
|
|
157
|
+
if (closeError != null) {
|
|
158
|
+
setState({ status: "error", error: closeError, client: null });
|
|
159
|
+
throw closeError;
|
|
106
160
|
}
|
|
107
161
|
setState({ status: "disconnected", error: null, client: null });
|
|
108
162
|
};
|
|
@@ -115,32 +169,28 @@ export function createReconnectManager() {
|
|
|
115
169
|
return;
|
|
116
170
|
const ar = normalizeAutoReconnect(cfg.autoReconnect);
|
|
117
171
|
if (!ar.enabled) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// ignore
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
setState({ status: "error", error, client: null });
|
|
172
|
+
const closeError = closeClient(s.client);
|
|
173
|
+
const finalError = closeError == null
|
|
174
|
+
? error
|
|
175
|
+
: new AggregateError([error, closeError], "reconnect termination and client close failed");
|
|
176
|
+
setState({ status: "error", error: finalError, client: null });
|
|
127
177
|
return;
|
|
128
178
|
}
|
|
129
179
|
// Restart the connection loop in the background.
|
|
130
180
|
cancelRetrySleep();
|
|
131
181
|
abortActiveAttempt();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
182
|
+
const closeError = closeClient(s.client);
|
|
183
|
+
if (closeError != null) {
|
|
184
|
+
setState({
|
|
185
|
+
status: "error",
|
|
186
|
+
error: new AggregateError([error, closeError], "reconnect termination and client close failed"),
|
|
187
|
+
client: null,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
139
190
|
}
|
|
140
191
|
token += 1;
|
|
141
192
|
const nextToken = token;
|
|
142
|
-
const reconnectPromise = startConnectLoop(nextToken, cfg);
|
|
143
|
-
setState({ status: "connecting", error, client: null });
|
|
193
|
+
const reconnectPromise = startConnectLoop(nextToken, cfg, error);
|
|
144
194
|
void reconnectPromise.catch(() => {
|
|
145
195
|
// connectWithRetry updates state; keep errors observable via state().
|
|
146
196
|
});
|
|
@@ -178,9 +228,17 @@ export function createReconnectManager() {
|
|
|
178
228
|
attemptAbort = new AbortController();
|
|
179
229
|
return await cfg.connectOnce({ signal: attemptAbort.signal, observer: createObserver(t, cfg, currentAttemptSeq) ?? {} });
|
|
180
230
|
};
|
|
181
|
-
const connectWithRetry = async (t, cfg) => {
|
|
231
|
+
const connectWithRetry = async (t, cfg, initialFailure) => {
|
|
182
232
|
const ar = normalizeAutoReconnect(cfg.autoReconnect);
|
|
183
233
|
let attempts = 0;
|
|
234
|
+
if (initialFailure != null) {
|
|
235
|
+
if (isTerminalConnectError(initialFailure)) {
|
|
236
|
+
finishWithError(cfg, initialFailure, attemptSeq);
|
|
237
|
+
throw initialFailure;
|
|
238
|
+
}
|
|
239
|
+
if (!await scheduleRetry(t, cfg, initialFailure, ar, 0, attemptSeq))
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
184
242
|
for (;;) {
|
|
185
243
|
if (t !== token)
|
|
186
244
|
return;
|
|
@@ -198,21 +256,15 @@ export function createReconnectManager() {
|
|
|
198
256
|
try {
|
|
199
257
|
const client = await connectOnce(t, cfg, attemptSeq);
|
|
200
258
|
if (t !== token) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
catch {
|
|
205
|
-
// ignore
|
|
206
|
-
}
|
|
259
|
+
const closeError = closeClient(client);
|
|
260
|
+
if (closeError != null)
|
|
261
|
+
throw new ReconnectCleanupError(closeError);
|
|
207
262
|
return;
|
|
208
263
|
}
|
|
209
264
|
if (active !== cfg) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
catch {
|
|
214
|
-
// ignore
|
|
215
|
-
}
|
|
265
|
+
const closeError = closeClient(client);
|
|
266
|
+
if (closeError != null)
|
|
267
|
+
throw new ReconnectCleanupError(closeError);
|
|
216
268
|
return;
|
|
217
269
|
}
|
|
218
270
|
setState({ status: "connected", client, error: null });
|
|
@@ -222,6 +274,10 @@ export function createReconnectManager() {
|
|
|
222
274
|
if (s.client !== client)
|
|
223
275
|
return;
|
|
224
276
|
startReconnect(t, cfg, error);
|
|
277
|
+
}, (error) => {
|
|
278
|
+
if (s.client !== client)
|
|
279
|
+
return;
|
|
280
|
+
startReconnect(t, cfg, asError(error));
|
|
225
281
|
});
|
|
226
282
|
}
|
|
227
283
|
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq }), {
|
|
@@ -234,37 +290,24 @@ export function createReconnectManager() {
|
|
|
234
290
|
return;
|
|
235
291
|
}
|
|
236
292
|
catch (err) {
|
|
293
|
+
if (err instanceof ReconnectCleanupError)
|
|
294
|
+
throw err.cleanupError;
|
|
237
295
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
238
296
|
if (t !== token)
|
|
239
297
|
return;
|
|
240
298
|
if (active !== cfg)
|
|
241
299
|
return;
|
|
242
|
-
const canRetry = ar.enabled && attempts < ar.maxAttempts;
|
|
300
|
+
const canRetry = ar.enabled && attempts < ar.maxAttempts && !isTerminalConnectError(e);
|
|
243
301
|
if (!canRetry) {
|
|
244
|
-
|
|
245
|
-
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq }), {
|
|
246
|
-
path: "auto",
|
|
247
|
-
stage: "reconnect",
|
|
248
|
-
code_domain: "event",
|
|
249
|
-
code: "reconnect_exhausted",
|
|
250
|
-
result: "fail",
|
|
251
|
-
});
|
|
302
|
+
finishWithError(cfg, e, attemptSeq);
|
|
252
303
|
throw e;
|
|
253
304
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq }), {
|
|
257
|
-
path: "auto",
|
|
258
|
-
stage: "reconnect",
|
|
259
|
-
code_domain: "event",
|
|
260
|
-
code: "reconnect_scheduled",
|
|
261
|
-
result: "retry",
|
|
262
|
-
});
|
|
263
|
-
await sleep(delay);
|
|
305
|
+
if (!await scheduleRetry(t, cfg, e, ar, attempts - 1, attemptSeq))
|
|
306
|
+
return;
|
|
264
307
|
}
|
|
265
308
|
}
|
|
266
309
|
};
|
|
267
|
-
const startConnectLoop = (t, cfg) => {
|
|
310
|
+
const startConnectLoop = (t, cfg, initialFailure) => {
|
|
268
311
|
let resolveLoop;
|
|
269
312
|
let rejectLoop;
|
|
270
313
|
const loop = new Promise((resolve, reject) => {
|
|
@@ -277,25 +320,24 @@ export function createReconnectManager() {
|
|
|
277
320
|
activeConnectPromise = null;
|
|
278
321
|
});
|
|
279
322
|
activeConnectPromise = promise;
|
|
280
|
-
void connectWithRetry(t, cfg).then(resolveLoop, rejectLoop);
|
|
323
|
+
void connectWithRetry(t, cfg, initialFailure).then(resolveLoop, rejectLoop);
|
|
281
324
|
return promise;
|
|
282
325
|
};
|
|
283
326
|
const connect = async (cfg) => {
|
|
327
|
+
normalizeAutoReconnect(cfg.autoReconnect);
|
|
284
328
|
cancelRetrySleep();
|
|
285
329
|
abortActiveAttempt();
|
|
286
330
|
token += 1;
|
|
287
331
|
const t = token;
|
|
288
332
|
active = cfg;
|
|
289
333
|
attemptSeq = 0;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ignore
|
|
296
|
-
}
|
|
334
|
+
const closeError = closeClient(s.client);
|
|
335
|
+
if (closeError != null) {
|
|
336
|
+
active = null;
|
|
337
|
+
setState({ status: "error", error: closeError, client: null });
|
|
338
|
+
throw closeError;
|
|
297
339
|
}
|
|
298
|
-
const connectPromise = startConnectLoop(t, cfg);
|
|
340
|
+
const connectPromise = startConnectLoop(t, cfg, null);
|
|
299
341
|
setState({ status: "connecting", error: null, client: null });
|
|
300
342
|
await connectPromise;
|
|
301
343
|
};
|
|
@@ -333,3 +375,14 @@ export function createReconnectManager() {
|
|
|
333
375
|
disconnect,
|
|
334
376
|
};
|
|
335
377
|
}
|
|
378
|
+
class ReconnectCleanupError extends Error {
|
|
379
|
+
cleanupError;
|
|
380
|
+
constructor(cleanupError) {
|
|
381
|
+
super("reconnect cleanup failed", { cause: cleanupError });
|
|
382
|
+
this.cleanupError = cleanupError;
|
|
383
|
+
this.name = "ReconnectCleanupError";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function asError(value) {
|
|
387
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
388
|
+
}
|
package/dist/rpc/server.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export declare class RpcServer {
|
|
|
32
32
|
private readonly terminalSignal;
|
|
33
33
|
private signalTerminal;
|
|
34
34
|
private transportClosed;
|
|
35
|
+
private admittedRequests;
|
|
35
36
|
constructor(transport: RpcServerTransport, options?: RpcServerOptions, router?: RpcRouter);
|
|
36
37
|
register(typeId: number, h: RpcHandler): void;
|
|
37
38
|
notify(typeId: number, payload: unknown): Promise<void>;
|
package/dist/rpc/server.js
CHANGED
|
@@ -31,6 +31,7 @@ export class RpcServer {
|
|
|
31
31
|
terminalSignal;
|
|
32
32
|
signalTerminal;
|
|
33
33
|
transportClosed = false;
|
|
34
|
+
admittedRequests = 0;
|
|
34
35
|
constructor(transport, options = {}, router = new RpcRouter()) {
|
|
35
36
|
this.transport = transport;
|
|
36
37
|
this.router = router;
|
|
@@ -57,11 +58,15 @@ export class RpcServer {
|
|
|
57
58
|
}
|
|
58
59
|
// serve handles request/response frames until closed or aborted.
|
|
59
60
|
async serve(signal) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
const workers = Array.from({ length: this.options.maxConcurrentRequests }, () => this.requestWorker());
|
|
62
|
+
workers.push(this.notificationWorker());
|
|
63
|
+
const workerFailure = Promise.race(workers.map(async (worker) => {
|
|
64
|
+
await worker;
|
|
65
|
+
if (!this.closed)
|
|
66
|
+
throw new Error("rpc worker ended before server shutdown");
|
|
67
|
+
return await new Promise(() => undefined);
|
|
68
|
+
}));
|
|
69
|
+
let failure;
|
|
65
70
|
try {
|
|
66
71
|
while (!this.closed) {
|
|
67
72
|
if (signal?.aborted)
|
|
@@ -69,6 +74,7 @@ export class RpcServer {
|
|
|
69
74
|
const next = await Promise.race([
|
|
70
75
|
readJsonFrame(this.transport.readExactly, DEFAULT_MAX_JSON_FRAME_BYTES),
|
|
71
76
|
this.terminalSignal.then((error) => { throw error; }),
|
|
77
|
+
workerFailure,
|
|
72
78
|
]);
|
|
73
79
|
const v = assertRpcEnvelope(next);
|
|
74
80
|
if (v.response_to !== 0)
|
|
@@ -81,23 +87,38 @@ export class RpcServer {
|
|
|
81
87
|
this.wakeOne(this.notificationWaiters);
|
|
82
88
|
continue;
|
|
83
89
|
}
|
|
84
|
-
if (this.
|
|
90
|
+
if (this.admittedRequests >= this.options.maxConcurrentRequests + this.options.maxQueuedRequests) {
|
|
85
91
|
await this.writeResponse(v, { payload: null, error: { code: 429, message: "server overloaded" } });
|
|
86
92
|
continue;
|
|
87
93
|
}
|
|
94
|
+
this.admittedRequests += 1;
|
|
88
95
|
this.requests.push({ envelope: v });
|
|
89
96
|
this.wakeOne(this.requestWaiters);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
catch (err) {
|
|
100
|
+
failure = err;
|
|
93
101
|
this.terminalError = err;
|
|
94
|
-
this.close(err);
|
|
95
|
-
throw err;
|
|
96
102
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
let closeError;
|
|
104
|
+
try {
|
|
105
|
+
this.close(failure ?? this.terminalError ?? new Error("rpc server closed"));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
closeError = error;
|
|
100
109
|
}
|
|
110
|
+
const settled = await Promise.allSettled(workers);
|
|
111
|
+
const workerErrors = settled
|
|
112
|
+
.filter((result) => result.status === "rejected")
|
|
113
|
+
.map((result) => result.reason)
|
|
114
|
+
.filter((error) => error !== failure);
|
|
115
|
+
const errors = [failure, closeError, ...workerErrors].filter((error) => error !== undefined);
|
|
116
|
+
if (errors.length === 1)
|
|
117
|
+
throw errors[0];
|
|
118
|
+
if (errors.length > 1)
|
|
119
|
+
throw new AggregateError(errors, "rpc server and cleanup failed");
|
|
120
|
+
if (this.terminalError !== undefined)
|
|
121
|
+
throw this.terminalError;
|
|
101
122
|
}
|
|
102
123
|
// close stops the serve loop and closes the underlying RPC stream.
|
|
103
124
|
close(error = new Error("rpc server closed")) {
|
|
@@ -138,9 +159,14 @@ export class RpcServer {
|
|
|
138
159
|
out = { payload: null, error: { code: 500, message: "internal error" } };
|
|
139
160
|
}
|
|
140
161
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
try {
|
|
163
|
+
if (this.closed)
|
|
164
|
+
return;
|
|
165
|
+
await this.writeResponse(v, out);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
this.admittedRequests = Math.max(0, this.admittedRequests - 1);
|
|
169
|
+
}
|
|
144
170
|
}
|
|
145
171
|
}
|
|
146
172
|
async notificationWorker() {
|
|
@@ -152,10 +178,7 @@ export class RpcServer {
|
|
|
152
178
|
const h = this.router.handler(v.type_id);
|
|
153
179
|
if (h == null)
|
|
154
180
|
continue;
|
|
155
|
-
|
|
156
|
-
await h(v.payload);
|
|
157
|
-
}
|
|
158
|
-
catch { /* Notification failures are isolated. */ }
|
|
181
|
+
await h(v.payload);
|
|
159
182
|
}
|
|
160
183
|
}
|
|
161
184
|
async nextWork(queue, waiters) {
|
|
@@ -182,8 +205,14 @@ export class RpcServer {
|
|
|
182
205
|
}
|
|
183
206
|
async writeEnvelope(envelope) {
|
|
184
207
|
const write = this.writeChain.then(() => writeJsonFrame(this.transport.write, envelope));
|
|
185
|
-
this.writeChain = write
|
|
186
|
-
|
|
208
|
+
this.writeChain = write;
|
|
209
|
+
try {
|
|
210
|
+
await write;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
this.fail(error);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
187
216
|
}
|
|
188
217
|
}
|
|
189
218
|
function positiveInteger(value, name) {
|
package/dist/streamio/index.js
CHANGED
|
@@ -11,12 +11,9 @@ function abortReasonToError(signal) {
|
|
|
11
11
|
}
|
|
12
12
|
function bindAbortToStream(stream, signal) {
|
|
13
13
|
const onAbort = () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
// Best-effort cancel.
|
|
19
|
-
}
|
|
14
|
+
void Promise.resolve(stream.reset(abortReasonToError(signal))).catch(() => {
|
|
15
|
+
// The read/write operation reports the authoritative abort to its caller.
|
|
16
|
+
});
|
|
20
17
|
};
|
|
21
18
|
if (signal.aborted) {
|
|
22
19
|
onAbort();
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare class AbortError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export type FlowersecPath = "auto" | "tunnel" | "direct";
|
|
8
8
|
export type FlowersecStage = "validate" | "connect" | "attach" | "handshake" | "secure" | "yamux" | "rpc" | "close";
|
|
9
|
-
export type FlowersecErrorCode = "timeout" | "canceled" | "invalid_version" | "invalid_input" | "invalid_option" | "invalid_endpoint_instance_id" | "invalid_psk" | "invalid_suite" | "missing_grant" | "missing_connect_info" | "missing_conn" | "missing_handler" | "missing_stream_kind" | "role_mismatch" | "missing_tunnel_url" | "missing_ws_url" | "missing_origin" | "missing_channel_id" | "missing_token" | "missing_init_exp" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "auth_tag_mismatch" | "resolve_failed" | "transport_policy_denied" | "credential_commit_failed" | "random_failed" | "upgrade_failed" | "dial_failed" | "attach_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "tenant_mismatch" | "policy_denied" | "policy_error" | "replace_rate_limited" | "handshake_failed" | "ping_failed" | "mux_failed" | "accept_stream_failed" | "open_stream_failed" | "stream_hello_failed" | "rpc_failed" | "resource_exhausted" | "not_connected";
|
|
9
|
+
export type FlowersecErrorCode = "timeout" | "canceled" | "invalid_version" | "invalid_input" | "invalid_option" | "invalid_endpoint_instance_id" | "invalid_psk" | "invalid_suite" | "missing_grant" | "missing_connect_info" | "missing_conn" | "missing_handler" | "missing_stream_kind" | "role_mismatch" | "missing_tunnel_url" | "missing_ws_url" | "missing_origin" | "missing_channel_id" | "missing_token" | "missing_init_exp" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "auth_tag_mismatch" | "resolve_failed" | "transport_policy_denied" | "credential_commit_failed" | "random_failed" | "upgrade_failed" | "dial_failed" | "attach_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "tenant_mismatch" | "policy_denied" | "policy_error" | "replace_rate_limited" | "handshake_failed" | "ping_failed" | "rekey_failed" | "mux_failed" | "accept_stream_failed" | "open_stream_failed" | "stream_hello_failed" | "rpc_failed" | "resource_exhausted" | "not_connected";
|
|
10
10
|
export declare class FlowersecError extends Error {
|
|
11
11
|
readonly code: FlowersecErrorCode;
|
|
12
12
|
readonly stage: FlowersecStage;
|
package/dist/yamux/byteReader.js
CHANGED
|
@@ -23,25 +23,7 @@ export class ByteReader {
|
|
|
23
23
|
this.buffered += chunk.length;
|
|
24
24
|
}
|
|
25
25
|
const out = new Uint8Array(n);
|
|
26
|
-
|
|
27
|
-
while (outOff < n) {
|
|
28
|
-
const head = this.chunks[this.chunkHead];
|
|
29
|
-
const avail = head.length - this.headOff;
|
|
30
|
-
const need = n - outOff;
|
|
31
|
-
const take = Math.min(avail, need);
|
|
32
|
-
out.set(head.subarray(this.headOff, this.headOff + take), outOff);
|
|
33
|
-
outOff += take;
|
|
34
|
-
this.headOff += take;
|
|
35
|
-
this.buffered -= take;
|
|
36
|
-
if (this.headOff === head.length) {
|
|
37
|
-
this.chunkHead++;
|
|
38
|
-
this.headOff = 0;
|
|
39
|
-
if (this.chunkHead > 1024 && this.chunkHead * 2 > this.chunks.length) {
|
|
40
|
-
this.chunks.splice(0, this.chunkHead);
|
|
41
|
-
this.chunkHead = 0;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
26
|
+
this.consumeAvailable(n, out);
|
|
45
27
|
return out;
|
|
46
28
|
}
|
|
47
29
|
// discardExactly consumes bytes without allocating a contiguous output buffer.
|
|
@@ -59,19 +41,41 @@ export class ByteReader {
|
|
|
59
41
|
this.chunks.push(chunk);
|
|
60
42
|
this.buffered += chunk.length;
|
|
61
43
|
}
|
|
44
|
+
remaining -= this.consumeAvailable(remaining);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// bufferedBytes returns the number of bytes currently buffered.
|
|
48
|
+
bufferedBytes() {
|
|
49
|
+
return this.buffered;
|
|
50
|
+
}
|
|
51
|
+
consumeAvailable(maxBytes, output) {
|
|
52
|
+
let consumed = 0;
|
|
53
|
+
while (consumed < maxBytes && this.buffered > 0) {
|
|
62
54
|
const head = this.chunks[this.chunkHead];
|
|
63
|
-
const take = Math.min(
|
|
55
|
+
const take = Math.min(maxBytes - consumed, head.length - this.headOff);
|
|
56
|
+
if (output != null)
|
|
57
|
+
output.set(head.subarray(this.headOff, this.headOff + take), consumed);
|
|
64
58
|
this.headOff += take;
|
|
65
59
|
this.buffered -= take;
|
|
66
|
-
|
|
60
|
+
consumed += take;
|
|
67
61
|
if (this.headOff === head.length) {
|
|
68
62
|
this.chunkHead++;
|
|
69
63
|
this.headOff = 0;
|
|
70
64
|
}
|
|
71
65
|
}
|
|
66
|
+
this.compactConsumedChunks();
|
|
67
|
+
return consumed;
|
|
72
68
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
compactConsumedChunks() {
|
|
70
|
+
if (this.buffered === 0) {
|
|
71
|
+
this.chunks.length = 0;
|
|
72
|
+
this.chunkHead = 0;
|
|
73
|
+
this.headOff = 0;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (this.chunkHead > 1024 && this.chunkHead * 2 > this.chunks.length) {
|
|
77
|
+
this.chunks.splice(0, this.chunkHead);
|
|
78
|
+
this.chunkHead = 0;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
}
|
package/dist/yamux/errors.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ export declare class StreamEOFError extends Error {
|
|
|
2
2
|
constructor(message?: string);
|
|
3
3
|
}
|
|
4
4
|
export declare function isStreamEOFError(e: unknown): e is StreamEOFError;
|
|
5
|
+
export declare class YamuxStreamResetError extends Error {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
export declare function isYamuxStreamResetError(error: unknown): error is YamuxStreamResetError;
|
|
5
9
|
export declare class YamuxResourceExhaustedError extends Error {
|
|
6
10
|
readonly resource: string;
|
|
7
11
|
readonly current: number;
|
|
@@ -9,3 +13,7 @@ export declare class YamuxResourceExhaustedError extends Error {
|
|
|
9
13
|
constructor(resource: string, current: number, limit: number);
|
|
10
14
|
}
|
|
11
15
|
export declare function isYamuxResourceExhaustedError(error: unknown): error is YamuxResourceExhaustedError;
|
|
16
|
+
export declare class YamuxPingTimeoutError extends Error {
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
export declare function isYamuxPingTimeoutError(error: unknown): error is YamuxPingTimeoutError;
|
package/dist/yamux/errors.js
CHANGED
|
@@ -8,6 +8,15 @@ export class StreamEOFError extends Error {
|
|
|
8
8
|
export function isStreamEOFError(e) {
|
|
9
9
|
return e instanceof StreamEOFError;
|
|
10
10
|
}
|
|
11
|
+
export class YamuxStreamResetError extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("yamux stream reset");
|
|
14
|
+
this.name = "YamuxStreamResetError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function isYamuxStreamResetError(error) {
|
|
18
|
+
return error instanceof YamuxStreamResetError;
|
|
19
|
+
}
|
|
11
20
|
export class YamuxResourceExhaustedError extends Error {
|
|
12
21
|
resource;
|
|
13
22
|
current;
|
|
@@ -23,3 +32,12 @@ export class YamuxResourceExhaustedError extends Error {
|
|
|
23
32
|
export function isYamuxResourceExhaustedError(error) {
|
|
24
33
|
return error instanceof YamuxResourceExhaustedError;
|
|
25
34
|
}
|
|
35
|
+
export class YamuxPingTimeoutError extends Error {
|
|
36
|
+
constructor() {
|
|
37
|
+
super("yamux ping timeout");
|
|
38
|
+
this.name = "YamuxPingTimeoutError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function isYamuxPingTimeoutError(error) {
|
|
42
|
+
return error instanceof YamuxPingTimeoutError;
|
|
43
|
+
}
|