@floegence/flowersec-core 0.20.1 → 0.21.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/dist/client-connect/connectCore.d.ts +2 -0
- package/dist/client-connect/connectCore.js +45 -5
- package/dist/client-connect/termination.d.ts +6 -0
- package/dist/client-connect/termination.js +7 -0
- package/dist/e2ee/handshake.d.ts +4 -0
- package/dist/e2ee/handshake.js +14 -1
- package/dist/e2ee/secureChannel.d.ts +6 -1
- package/dist/e2ee/secureChannel.js +37 -12
- package/dist/proxy/appWindow.d.ts +1 -0
- package/dist/proxy/appWindow.js +17 -5
- package/dist/proxy/controllerWindow.js +87 -14
- package/dist/proxy/headerPolicy.js +10 -1
- package/dist/proxy/integration.d.ts +2 -0
- package/dist/proxy/integration.js +6 -0
- package/dist/proxy/portStream.d.ts +3 -1
- package/dist/proxy/portStream.js +40 -3
- package/dist/proxy/preset.d.ts +0 -1
- package/dist/proxy/preset.js +0 -13
- package/dist/proxy/profiles.d.ts +1 -8
- package/dist/proxy/profiles.js +1 -7
- package/dist/proxy/runtime.d.ts +4 -0
- package/dist/proxy/runtime.js +29 -5
- package/dist/proxy/runtimeScope.d.ts +4 -0
- package/dist/proxy/windowBridgeProtocol.d.ts +8 -0
- package/dist/proxy/windowBridgeProtocol.js +2 -0
- package/dist/proxy/wsPatch.d.ts +1 -0
- package/dist/proxy/wsPatch.js +56 -12
- package/dist/reconnect/index.js +9 -0
- package/dist/rpc/client.d.ts +2 -0
- package/dist/rpc/client.js +11 -0
- package/dist/yamux/session.d.ts +8 -1
- package/dist/yamux/session.js +77 -26
- package/package.json +1 -1
package/dist/yamux/session.js
CHANGED
|
@@ -24,6 +24,7 @@ export class YamuxSession {
|
|
|
24
24
|
// Maximum allowed DATA frame length.
|
|
25
25
|
limits;
|
|
26
26
|
onDiagnostic;
|
|
27
|
+
onTerminal;
|
|
27
28
|
// True when this side is the yamux client (odd local stream IDs).
|
|
28
29
|
client;
|
|
29
30
|
// Next stream ID to allocate (odd/even based on role).
|
|
@@ -39,25 +40,21 @@ export class YamuxSession {
|
|
|
39
40
|
activeProbe;
|
|
40
41
|
constructor(conn, opts) {
|
|
41
42
|
this.conn = conn;
|
|
42
|
-
this.reader = new ByteReader(
|
|
43
|
-
try {
|
|
44
|
-
return await this.conn.read();
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
43
|
+
this.reader = new ByteReader(() => this.conn.read());
|
|
50
44
|
this.onIncomingStream = opts.onIncomingStream;
|
|
51
45
|
this.limits = normalizeYamuxLimits({ ...opts.limits, ...(opts.maxFrameBytes === undefined ? {} : { maxFrameBytes: opts.maxFrameBytes }) });
|
|
52
46
|
this.onDiagnostic = opts.onDiagnostic;
|
|
47
|
+
this.onTerminal = opts.onTerminal;
|
|
53
48
|
this.client = opts.client;
|
|
54
49
|
this.nextStreamId = opts.client ? 1 : 2;
|
|
55
50
|
void this.readLoop();
|
|
56
51
|
}
|
|
57
52
|
// openStream allocates a new stream and performs the SYN handshake.
|
|
58
|
-
async openStream() {
|
|
53
|
+
async openStream(opts = {}) {
|
|
59
54
|
if (this.closed)
|
|
60
55
|
throw new Error("session closed");
|
|
56
|
+
if (opts.signal?.aborted)
|
|
57
|
+
throw abortError(opts.signal);
|
|
61
58
|
if (this.streams.size >= this.limits.maxActiveStreams) {
|
|
62
59
|
const error = new YamuxResourceExhaustedError("active_streams", this.streams.size, this.limits.maxActiveStreams);
|
|
63
60
|
this.diagnostic({ code: "resource_limit_reached", resource: error.resource, current: error.current, limit: error.limit });
|
|
@@ -68,7 +65,9 @@ export class YamuxSession {
|
|
|
68
65
|
const s = new YamuxStream(this, id, "init");
|
|
69
66
|
this.streams.set(id, s);
|
|
70
67
|
try {
|
|
71
|
-
await s.open()
|
|
68
|
+
await raceAbort(s.open(), opts.signal, () => {
|
|
69
|
+
void s.reset(abortError(opts.signal));
|
|
70
|
+
});
|
|
72
71
|
return s;
|
|
73
72
|
}
|
|
74
73
|
catch (error) {
|
|
@@ -82,7 +81,13 @@ export class YamuxSession {
|
|
|
82
81
|
}
|
|
83
82
|
// writeRaw writes a raw yamux frame to the underlying connection.
|
|
84
83
|
async writeRaw(chunk) {
|
|
85
|
-
|
|
84
|
+
try {
|
|
85
|
+
await this.conn.write(chunk);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
this.fail(error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
86
91
|
}
|
|
87
92
|
outboundFrameBytes() {
|
|
88
93
|
return this.limits.preferredOutboundFrameBytes;
|
|
@@ -119,8 +124,9 @@ export class YamuxSession {
|
|
|
119
124
|
return await new Promise((resolve, reject) => {
|
|
120
125
|
const timer = setTimeout(() => {
|
|
121
126
|
this.pingWaiters.delete(opaque);
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
const error = new Error("yamux ping timeout");
|
|
128
|
+
reject(error);
|
|
129
|
+
this.fail(error);
|
|
124
130
|
}, timeoutMs);
|
|
125
131
|
timer?.unref?.();
|
|
126
132
|
this.pingWaiters.set(opaque, { startedAt, resolve, reject, timer });
|
|
@@ -132,14 +138,15 @@ export class YamuxSession {
|
|
|
132
138
|
this.pingWaiters.delete(opaque);
|
|
133
139
|
clearTimeout(waiter.timer);
|
|
134
140
|
reject(err);
|
|
141
|
+
this.fail(err);
|
|
135
142
|
});
|
|
136
143
|
});
|
|
137
144
|
}
|
|
138
145
|
// sendRst sends a reset frame and removes the stream.
|
|
139
146
|
async sendRst(id) {
|
|
140
147
|
const hdr = encodeHeader({ type: TYPE_WINDOW_UPDATE, flags: FLAG_RST, streamId: id, length: 0 });
|
|
148
|
+
this.onStreamClosed(id);
|
|
141
149
|
if (this.closed) {
|
|
142
|
-
this.onStreamClosed(id);
|
|
143
150
|
return;
|
|
144
151
|
}
|
|
145
152
|
try {
|
|
@@ -148,7 +155,6 @@ export class YamuxSession {
|
|
|
148
155
|
catch {
|
|
149
156
|
// Best-effort reset; ignore errors when the session is closing.
|
|
150
157
|
}
|
|
151
|
-
this.onStreamClosed(id);
|
|
152
158
|
}
|
|
153
159
|
// notifySendWindow wakes any writers waiting on window credit.
|
|
154
160
|
notifySendWindow(streamId) {
|
|
@@ -191,6 +197,21 @@ export class YamuxSession {
|
|
|
191
197
|
}
|
|
192
198
|
// close terminates the session and resets all streams.
|
|
193
199
|
close() {
|
|
200
|
+
this.closeInternal();
|
|
201
|
+
}
|
|
202
|
+
fail(error) {
|
|
203
|
+
if (this.closed)
|
|
204
|
+
return;
|
|
205
|
+
const terminalError = error instanceof Error ? error : new Error(String(error));
|
|
206
|
+
this.closeInternal();
|
|
207
|
+
try {
|
|
208
|
+
this.onTerminal?.(terminalError);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Lifecycle callbacks must not affect session shutdown.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
closeInternal() {
|
|
194
215
|
if (this.closed)
|
|
195
216
|
return;
|
|
196
217
|
this.closed = true;
|
|
@@ -219,16 +240,16 @@ export class YamuxSession {
|
|
|
219
240
|
const hdrBytes = await this.reader.readExactly(HEADER_LEN);
|
|
220
241
|
const h = decodeHeader(hdrBytes, 0);
|
|
221
242
|
if (h.version !== YAMUX_VERSION) {
|
|
222
|
-
this.
|
|
243
|
+
this.fail(new Error("unsupported yamux version"));
|
|
223
244
|
return;
|
|
224
245
|
}
|
|
225
246
|
if (h.type === TYPE_DATA) {
|
|
226
247
|
if (h.length > this.limits.maxFrameBytes) {
|
|
227
|
-
this.
|
|
248
|
+
this.fail(new Error("yamux frame exceeds limit"));
|
|
228
249
|
return;
|
|
229
250
|
}
|
|
230
251
|
if (h.streamId === 0) {
|
|
231
|
-
this.
|
|
252
|
+
this.fail(new Error("yamux data frame has stream id zero"));
|
|
232
253
|
return;
|
|
233
254
|
}
|
|
234
255
|
const existing = this.streams.get(h.streamId);
|
|
@@ -255,7 +276,7 @@ export class YamuxSession {
|
|
|
255
276
|
}
|
|
256
277
|
if (this.sessionReceiveBytes + h.length > this.limits.maxSessionReceiveBytes) {
|
|
257
278
|
this.diagnostic({ code: "resource_limit_reached", resource: "session_receive_bytes", current: this.sessionReceiveBytes + h.length, limit: this.limits.maxSessionReceiveBytes });
|
|
258
|
-
this.
|
|
279
|
+
this.fail(new Error("yamux session receive limit reached"));
|
|
259
280
|
return;
|
|
260
281
|
}
|
|
261
282
|
const data = h.length > 0 ? await this.reader.readExactly(h.length) : new Uint8Array();
|
|
@@ -275,15 +296,15 @@ export class YamuxSession {
|
|
|
275
296
|
continue;
|
|
276
297
|
}
|
|
277
298
|
if (h.type === TYPE_GO_AWAY) {
|
|
278
|
-
this.
|
|
299
|
+
this.fail(new Error("yamux peer sent go away"));
|
|
279
300
|
return;
|
|
280
301
|
}
|
|
281
|
-
this.
|
|
302
|
+
this.fail(new Error("unsupported yamux frame type"));
|
|
282
303
|
return;
|
|
283
304
|
}
|
|
284
305
|
}
|
|
285
|
-
catch {
|
|
286
|
-
this.
|
|
306
|
+
catch (error) {
|
|
307
|
+
this.fail(error);
|
|
287
308
|
}
|
|
288
309
|
}
|
|
289
310
|
async handlePing(flags, opaque) {
|
|
@@ -303,7 +324,7 @@ export class YamuxSession {
|
|
|
303
324
|
}
|
|
304
325
|
async handleDataFrame(streamId, flags, data) {
|
|
305
326
|
if (streamId === 0) {
|
|
306
|
-
this.
|
|
327
|
+
this.fail(new Error("yamux data frame has stream id zero"));
|
|
307
328
|
return false;
|
|
308
329
|
}
|
|
309
330
|
let s = this.streams.get(streamId);
|
|
@@ -332,7 +353,7 @@ export class YamuxSession {
|
|
|
332
353
|
}
|
|
333
354
|
async handleWindowUpdateFrame(streamId, flags, delta) {
|
|
334
355
|
if (streamId === 0) {
|
|
335
|
-
this.
|
|
356
|
+
this.fail(new Error("yamux window update has stream id zero"));
|
|
336
357
|
return;
|
|
337
358
|
}
|
|
338
359
|
let s = this.streams.get(streamId);
|
|
@@ -358,7 +379,7 @@ export class YamuxSession {
|
|
|
358
379
|
}
|
|
359
380
|
}
|
|
360
381
|
if (!s.onWindowUpdate(delta, flags)) {
|
|
361
|
-
this.
|
|
382
|
+
this.fail(new Error("yamux send window overflow"));
|
|
362
383
|
}
|
|
363
384
|
}
|
|
364
385
|
isInboundStreamIdValid(streamId) {
|
|
@@ -376,6 +397,36 @@ export class YamuxSession {
|
|
|
376
397
|
catch { /* Diagnostics cannot affect transport behavior. */ }
|
|
377
398
|
}
|
|
378
399
|
}
|
|
400
|
+
function abortError(signal) {
|
|
401
|
+
const reason = signal.reason;
|
|
402
|
+
if (reason instanceof Error)
|
|
403
|
+
return reason;
|
|
404
|
+
if (typeof reason === "string" && reason !== "")
|
|
405
|
+
return new Error(reason);
|
|
406
|
+
return new Error("yamux open stream aborted");
|
|
407
|
+
}
|
|
408
|
+
async function raceAbort(promise, signal, onAbort) {
|
|
409
|
+
if (signal == null)
|
|
410
|
+
return await promise;
|
|
411
|
+
if (signal.aborted) {
|
|
412
|
+
onAbort();
|
|
413
|
+
throw abortError(signal);
|
|
414
|
+
}
|
|
415
|
+
return await new Promise((resolve, reject) => {
|
|
416
|
+
const abort = () => {
|
|
417
|
+
onAbort();
|
|
418
|
+
reject(abortError(signal));
|
|
419
|
+
};
|
|
420
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
421
|
+
void promise.then((value) => {
|
|
422
|
+
signal.removeEventListener("abort", abort);
|
|
423
|
+
resolve(value);
|
|
424
|
+
}, (error) => {
|
|
425
|
+
signal.removeEventListener("abort", abort);
|
|
426
|
+
reject(error);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
379
430
|
function normalizeYamuxLimits(input) {
|
|
380
431
|
const maxFrameBytes = input?.maxFrameBytes ?? DEFAULT_YAMUX_LIMITS.maxFrameBytes;
|
|
381
432
|
const limits = {
|