@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/proxy/portStream.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE, PROXY_WINDOW_STREAM_END_MSG_TYPE, PROXY_WINDOW_STREAM_RESET_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
1
|
+
import { PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE, PROXY_WINDOW_STREAM_END_MSG_TYPE, PROXY_WINDOW_STREAM_RESET_MSG_TYPE, PROXY_WINDOW_STREAM_WRITE_ACK_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
2
2
|
function cloneChunk(chunk) {
|
|
3
3
|
const out = new Uint8Array(chunk.byteLength);
|
|
4
4
|
out.set(chunk);
|
|
5
5
|
return out.buffer;
|
|
6
6
|
}
|
|
7
|
-
export function createMessagePortBackedStream(port) {
|
|
7
|
+
export function createMessagePortBackedStream(port, opts = {}) {
|
|
8
8
|
let closed = false;
|
|
9
9
|
let error = null;
|
|
10
10
|
const queue = [];
|
|
11
11
|
const waiters = [];
|
|
12
|
+
const pendingWrites = new Map();
|
|
13
|
+
let nextWriteId = 1;
|
|
12
14
|
const resolveWaiter = (value) => {
|
|
13
15
|
const waiter = waiters.shift();
|
|
14
16
|
if (waiter) {
|
|
@@ -29,6 +31,9 @@ export function createMessagePortBackedStream(port) {
|
|
|
29
31
|
while (resolveWaiter(err)) {
|
|
30
32
|
// Drain waiters.
|
|
31
33
|
}
|
|
34
|
+
for (const pending of pendingWrites.values())
|
|
35
|
+
pending.reject(err);
|
|
36
|
+
pendingWrites.clear();
|
|
32
37
|
};
|
|
33
38
|
port.onmessage = (ev) => {
|
|
34
39
|
const data = ev.data;
|
|
@@ -46,8 +51,22 @@ export function createMessagePortBackedStream(port) {
|
|
|
46
51
|
case PROXY_WINDOW_STREAM_END_MSG_TYPE:
|
|
47
52
|
case PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE:
|
|
48
53
|
closed = true;
|
|
54
|
+
for (const pending of pendingWrites.values())
|
|
55
|
+
pending.reject(new Error("stream is closed"));
|
|
56
|
+
pendingWrites.clear();
|
|
49
57
|
pushValue(null);
|
|
50
58
|
return;
|
|
59
|
+
case PROXY_WINDOW_STREAM_WRITE_ACK_MSG_TYPE: {
|
|
60
|
+
const writeId = data.writeId;
|
|
61
|
+
if (!Number.isSafeInteger(writeId) || writeId <= 0)
|
|
62
|
+
return;
|
|
63
|
+
const pending = pendingWrites.get(writeId);
|
|
64
|
+
if (pending == null)
|
|
65
|
+
return;
|
|
66
|
+
pendingWrites.delete(writeId);
|
|
67
|
+
pending.resolve();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
51
70
|
case PROXY_WINDOW_STREAM_RESET_MSG_TYPE: {
|
|
52
71
|
closed = true;
|
|
53
72
|
const message = String(data.message ?? "stream reset");
|
|
@@ -87,12 +106,30 @@ export function createMessagePortBackedStream(port) {
|
|
|
87
106
|
if (closed)
|
|
88
107
|
throw new Error("stream is closed");
|
|
89
108
|
const ab = cloneChunk(chunk);
|
|
90
|
-
|
|
109
|
+
if (opts.writeAcknowledgements !== true) {
|
|
110
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, data: ab }, [ab]);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const writeId = nextWriteId++;
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
pendingWrites.set(writeId, { resolve, reject });
|
|
116
|
+
try {
|
|
117
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, data: ab, writeId }, [ab]);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
pendingWrites.delete(writeId);
|
|
121
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
91
124
|
},
|
|
92
125
|
async close() {
|
|
93
126
|
if (closed)
|
|
94
127
|
return;
|
|
95
128
|
closed = true;
|
|
129
|
+
const closeError = new Error("stream is closed");
|
|
130
|
+
for (const pending of pendingWrites.values())
|
|
131
|
+
pending.reject(closeError);
|
|
132
|
+
pendingWrites.clear();
|
|
96
133
|
try {
|
|
97
134
|
port.postMessage({ type: PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE });
|
|
98
135
|
}
|
package/dist/proxy/preset.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export type ResolvedProxyPreset = Readonly<{
|
|
|
24
24
|
}>;
|
|
25
25
|
}>;
|
|
26
26
|
export declare const DEFAULT_PROXY_PRESET_MANIFEST: ProxyPresetManifest;
|
|
27
|
-
export declare const CODESERVER_PROXY_PRESET_MANIFEST: ProxyPresetManifest;
|
|
28
27
|
export type ProxyPresetInput = ProxyPresetManifest | Partial<ProxyPresetLimits>;
|
|
29
28
|
export declare function assertProxyPresetManifest(value: unknown): ProxyPresetManifest;
|
|
30
29
|
export declare function resolveNamedProxyPreset(name: string): ProxyPresetManifest;
|
package/dist/proxy/preset.js
CHANGED
|
@@ -17,17 +17,6 @@ export const DEFAULT_PROXY_PRESET_MANIFEST = Object.freeze({
|
|
|
17
17
|
max_ws_frame_bytes: DEFAULT_MAX_WS_FRAME_BYTES,
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
|
-
export const CODESERVER_PROXY_PRESET_MANIFEST = Object.freeze({
|
|
21
|
-
v: 1,
|
|
22
|
-
preset_id: "codeserver",
|
|
23
|
-
deprecated: true,
|
|
24
|
-
limits: {
|
|
25
|
-
max_json_frame_bytes: DEFAULT_MAX_JSON_FRAME_BYTES,
|
|
26
|
-
max_chunk_bytes: DEFAULT_MAX_CHUNK_BYTES,
|
|
27
|
-
max_body_bytes: DEFAULT_MAX_BODY_BYTES,
|
|
28
|
-
max_ws_frame_bytes: 32 * 1024 * 1024,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
20
|
function isRecord(v) {
|
|
32
21
|
return typeof v === "object" && v != null && !Array.isArray(v);
|
|
33
22
|
}
|
|
@@ -91,8 +80,6 @@ export function resolveNamedProxyPreset(name) {
|
|
|
91
80
|
case "":
|
|
92
81
|
case "default":
|
|
93
82
|
return DEFAULT_PROXY_PRESET_MANIFEST;
|
|
94
|
-
case "codeserver":
|
|
95
|
-
return CODESERVER_PROXY_PRESET_MANIFEST;
|
|
96
83
|
default:
|
|
97
84
|
throw new Error(`unknown proxy preset: ${name}`);
|
|
98
85
|
}
|
package/dist/proxy/profiles.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type ProxyProfile = Readonly<{
|
|
|
6
6
|
maxWsFrameBytes: number;
|
|
7
7
|
timeoutMs: number;
|
|
8
8
|
}>;
|
|
9
|
-
export type ProxyProfileName = "default"
|
|
9
|
+
export type ProxyProfileName = "default";
|
|
10
10
|
export declare const PROXY_PROFILE_DEFAULT: Readonly<{
|
|
11
11
|
maxJsonFrameBytes: number;
|
|
12
12
|
maxChunkBytes: number;
|
|
@@ -14,12 +14,5 @@ export declare const PROXY_PROFILE_DEFAULT: Readonly<{
|
|
|
14
14
|
maxWsFrameBytes: number;
|
|
15
15
|
timeoutMs: number;
|
|
16
16
|
}>;
|
|
17
|
-
export declare const PROXY_PROFILE_CODESERVER: Readonly<{
|
|
18
|
-
maxJsonFrameBytes: number;
|
|
19
|
-
maxChunkBytes: number;
|
|
20
|
-
maxBodyBytes: number;
|
|
21
|
-
maxWsFrameBytes: number;
|
|
22
|
-
timeoutMs: number;
|
|
23
|
-
}>;
|
|
24
17
|
export declare function resolveProxyProfile(profile?: ProxyProfileName | Partial<ProxyProfile>): ProxyProfile;
|
|
25
18
|
export declare function profileToPresetManifest(profile?: ProxyProfileName | Partial<ProxyProfile>): ProxyPresetManifest;
|
package/dist/proxy/profiles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_PROXY_PRESET_MANIFEST, resolveProxyPreset, } from "./preset.js";
|
|
2
2
|
function toLegacyProfile(manifest) {
|
|
3
3
|
const resolved = resolveProxyPreset(manifest);
|
|
4
4
|
return Object.freeze({
|
|
@@ -10,9 +10,7 @@ function toLegacyProfile(manifest) {
|
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
const DEFAULT_PROFILE = toLegacyProfile(DEFAULT_PROXY_PRESET_MANIFEST);
|
|
13
|
-
const CODESERVER_PROFILE = toLegacyProfile(CODESERVER_PROXY_PRESET_MANIFEST);
|
|
14
13
|
export const PROXY_PROFILE_DEFAULT = DEFAULT_PROFILE;
|
|
15
|
-
export const PROXY_PROFILE_CODESERVER = CODESERVER_PROFILE;
|
|
16
14
|
function normalizeSafeInt(name, value) {
|
|
17
15
|
if (!Number.isFinite(value))
|
|
18
16
|
throw new Error(`${name} must be a finite number`);
|
|
@@ -27,8 +25,6 @@ function resolveNamedProfile(name) {
|
|
|
27
25
|
switch (name) {
|
|
28
26
|
case "default":
|
|
29
27
|
return DEFAULT_PROFILE;
|
|
30
|
-
case "codeserver":
|
|
31
|
-
return CODESERVER_PROFILE;
|
|
32
28
|
default:
|
|
33
29
|
throw new Error(`unknown proxy profile: ${name}`);
|
|
34
30
|
}
|
|
@@ -55,8 +51,6 @@ export function profileToPresetManifest(profile) {
|
|
|
55
51
|
switch (profile) {
|
|
56
52
|
case "default":
|
|
57
53
|
return DEFAULT_PROXY_PRESET_MANIFEST;
|
|
58
|
-
case "codeserver":
|
|
59
|
-
return CODESERVER_PROXY_PRESET_MANIFEST;
|
|
60
54
|
default:
|
|
61
55
|
throw new Error(`unknown proxy profile: ${profile}`);
|
|
62
56
|
}
|
package/dist/proxy/runtime.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ export type ProxyRuntimeLimits = Readonly<{
|
|
|
15
15
|
maxChunkBytes: number;
|
|
16
16
|
maxBodyBytes: number;
|
|
17
17
|
maxWsFrameBytes: number;
|
|
18
|
+
maxWsBufferedAmountBytes: number;
|
|
18
19
|
maxConcurrentHttpStreams: number;
|
|
19
20
|
maxQueuedHttpRequests: number;
|
|
21
|
+
maxQueuedHttpBodyBytes: number;
|
|
20
22
|
}>;
|
|
21
23
|
export type ProxyRuntime = Readonly<{
|
|
22
24
|
limits: ProxyRuntimeLimits;
|
|
@@ -42,8 +44,10 @@ export type ProxyRuntimeOptions = Readonly<{
|
|
|
42
44
|
maxChunkBytes?: number;
|
|
43
45
|
maxBodyBytes?: number;
|
|
44
46
|
maxWsFrameBytes?: number;
|
|
47
|
+
maxWsBufferedAmountBytes?: number;
|
|
45
48
|
maxConcurrentHttpStreams?: number;
|
|
46
49
|
maxQueuedHttpRequests?: number;
|
|
50
|
+
maxQueuedHttpBodyBytes?: number;
|
|
47
51
|
timeoutMs?: number;
|
|
48
52
|
extraRequestHeaders?: readonly string[];
|
|
49
53
|
extraResponseHeaders?: readonly string[];
|
package/dist/proxy/runtime.js
CHANGED
|
@@ -132,6 +132,8 @@ function normalizeMaxBytes(name, v, defaultValue) {
|
|
|
132
132
|
}
|
|
133
133
|
const DEFAULT_MAX_CONCURRENT_HTTP_STREAMS = 24;
|
|
134
134
|
const DEFAULT_MAX_QUEUED_HTTP_REQUESTS = 128;
|
|
135
|
+
const DEFAULT_MAX_QUEUED_HTTP_BODY_BYTES = 64 * (1 << 20);
|
|
136
|
+
const DEFAULT_MAX_WS_BUFFERED_AMOUNT_BYTES = 4 * (1 << 20);
|
|
135
137
|
function normalizePositiveLimit(name, value, defaultValue) {
|
|
136
138
|
if (value == null)
|
|
137
139
|
return defaultValue;
|
|
@@ -152,15 +154,18 @@ class HttpStreamAdmission {
|
|
|
152
154
|
path;
|
|
153
155
|
maxConcurrent;
|
|
154
156
|
maxQueued;
|
|
157
|
+
maxQueuedBodyBytes;
|
|
155
158
|
active = 0;
|
|
156
159
|
pending = [];
|
|
160
|
+
pendingBodyBytes = 0;
|
|
157
161
|
closed = false;
|
|
158
|
-
constructor(path, maxConcurrent, maxQueued) {
|
|
162
|
+
constructor(path, maxConcurrent, maxQueued, maxQueuedBodyBytes) {
|
|
159
163
|
this.path = path;
|
|
160
164
|
this.maxConcurrent = maxConcurrent;
|
|
161
165
|
this.maxQueued = maxQueued;
|
|
166
|
+
this.maxQueuedBodyBytes = maxQueuedBodyBytes;
|
|
162
167
|
}
|
|
163
|
-
acquire(signal) {
|
|
168
|
+
acquire(bodyBytes, signal) {
|
|
164
169
|
if (this.closed)
|
|
165
170
|
return Promise.reject(this.closedError());
|
|
166
171
|
if (signal?.aborted)
|
|
@@ -177,8 +182,17 @@ class HttpStreamAdmission {
|
|
|
177
182
|
message: "proxy runtime HTTP request queue is full",
|
|
178
183
|
}));
|
|
179
184
|
}
|
|
185
|
+
if (this.pendingBodyBytes + bodyBytes > this.maxQueuedBodyBytes) {
|
|
186
|
+
return Promise.reject(new FlowersecError({
|
|
187
|
+
path: this.path,
|
|
188
|
+
stage: "yamux",
|
|
189
|
+
code: "resource_exhausted",
|
|
190
|
+
message: "proxy runtime HTTP request body queue is full",
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
180
193
|
return new Promise((resolve, reject) => {
|
|
181
194
|
const waiter = {
|
|
195
|
+
bodyBytes,
|
|
182
196
|
resolve,
|
|
183
197
|
reject,
|
|
184
198
|
...(signal === undefined ? {} : { signal }),
|
|
@@ -188,11 +202,13 @@ class HttpStreamAdmission {
|
|
|
188
202
|
if (index < 0)
|
|
189
203
|
return;
|
|
190
204
|
this.pending.splice(index, 1);
|
|
205
|
+
this.pendingBodyBytes = Math.max(0, this.pendingBodyBytes - waiter.bodyBytes);
|
|
191
206
|
this.cleanupWaiter(waiter);
|
|
192
207
|
reject(this.abortedError());
|
|
193
208
|
};
|
|
194
209
|
signal?.addEventListener("abort", waiter.onAbort, { once: true });
|
|
195
210
|
this.pending.push(waiter);
|
|
211
|
+
this.pendingBodyBytes += bodyBytes;
|
|
196
212
|
});
|
|
197
213
|
}
|
|
198
214
|
close() {
|
|
@@ -200,6 +216,7 @@ class HttpStreamAdmission {
|
|
|
200
216
|
return;
|
|
201
217
|
this.closed = true;
|
|
202
218
|
for (const waiter of this.pending.splice(0)) {
|
|
219
|
+
this.pendingBodyBytes = Math.max(0, this.pendingBodyBytes - waiter.bodyBytes);
|
|
203
220
|
this.cleanupWaiter(waiter);
|
|
204
221
|
waiter.reject(this.closedError());
|
|
205
222
|
}
|
|
@@ -221,6 +238,7 @@ class HttpStreamAdmission {
|
|
|
221
238
|
drain() {
|
|
222
239
|
while (!this.closed && this.active < this.maxConcurrent && this.pending.length > 0) {
|
|
223
240
|
const waiter = this.pending.shift();
|
|
241
|
+
this.pendingBodyBytes = Math.max(0, this.pendingBodyBytes - waiter.bodyBytes);
|
|
224
242
|
this.cleanupWaiter(waiter);
|
|
225
243
|
if (waiter.signal?.aborted) {
|
|
226
244
|
waiter.reject(this.abortedError());
|
|
@@ -287,9 +305,11 @@ export function createProxyRuntime(opts) {
|
|
|
287
305
|
const maxChunkBytes = normalizeMaxBytes("maxChunkBytes", opts.maxChunkBytes, DEFAULT_MAX_CHUNK_BYTES);
|
|
288
306
|
const maxBodyBytes = normalizeMaxBytes("maxBodyBytes", opts.maxBodyBytes, DEFAULT_MAX_BODY_BYTES);
|
|
289
307
|
const maxWsFrameBytes = normalizeMaxBytes("maxWsFrameBytes", opts.maxWsFrameBytes, DEFAULT_MAX_WS_FRAME_BYTES);
|
|
308
|
+
const maxWsBufferedAmountBytes = normalizeMaxBytes("maxWsBufferedAmountBytes", opts.maxWsBufferedAmountBytes, DEFAULT_MAX_WS_BUFFERED_AMOUNT_BYTES);
|
|
290
309
|
const maxConcurrentHttpStreams = normalizePositiveLimit("maxConcurrentHttpStreams", opts.maxConcurrentHttpStreams, DEFAULT_MAX_CONCURRENT_HTTP_STREAMS);
|
|
291
310
|
const maxQueuedHttpRequests = normalizeNonNegativeLimit("maxQueuedHttpRequests", opts.maxQueuedHttpRequests, DEFAULT_MAX_QUEUED_HTTP_REQUESTS);
|
|
292
|
-
const
|
|
311
|
+
const maxQueuedHttpBodyBytes = normalizeNonNegativeLimit("maxQueuedHttpBodyBytes", opts.maxQueuedHttpBodyBytes, DEFAULT_MAX_QUEUED_HTTP_BODY_BYTES);
|
|
312
|
+
const httpStreamAdmission = new HttpStreamAdmission(client.path, maxConcurrentHttpStreams, maxQueuedHttpRequests, maxQueuedHttpBodyBytes);
|
|
293
313
|
const timeoutMs = normalizeTimeoutMs(opts.timeoutMs);
|
|
294
314
|
const extraRequestHeaders = opts.extraRequestHeaders ?? [];
|
|
295
315
|
const extraResponseHeaders = opts.extraResponseHeaders ?? [];
|
|
@@ -337,7 +357,10 @@ export function createProxyRuntime(opts) {
|
|
|
337
357
|
assertPathPolicyAllows("http", path, pathPolicy);
|
|
338
358
|
const requestID = req.id.trim() !== "" ? req.id : randomB64u(18);
|
|
339
359
|
const externalOrigin = externalOriginOverride ?? normalizeExternalOrigin(req.external_origin);
|
|
340
|
-
|
|
360
|
+
const body = req.body != null ? new Uint8Array(req.body) : new Uint8Array();
|
|
361
|
+
if (maxBodyBytes > 0 && body.length > maxBodyBytes)
|
|
362
|
+
throw new Error("request body too large");
|
|
363
|
+
releaseAdmission = await httpStreamAdmission.acquire(body.byteLength, ac.signal);
|
|
341
364
|
httpStreamAdmission.assertOpen();
|
|
342
365
|
stream = await client.openStream(PROXY_KIND_HTTP1, { signal: ac.signal });
|
|
343
366
|
const reader = createByteReader(stream, { signal: ac.signal });
|
|
@@ -353,7 +376,6 @@ export function createProxyRuntime(opts) {
|
|
|
353
376
|
...(externalOrigin === undefined ? {} : { external_origin: externalOrigin }),
|
|
354
377
|
timeout_ms: timeoutMs
|
|
355
378
|
});
|
|
356
|
-
const body = req.body != null ? new Uint8Array(req.body) : new Uint8Array();
|
|
357
379
|
await writeChunkFrames(stream, body, Math.min(64 * 1024, maxChunkBytes), maxBodyBytes);
|
|
358
380
|
const respMeta = (await readJsonFrame(reader, maxJsonFrameBytes));
|
|
359
381
|
if (respMeta.v !== PROXY_PROTOCOL_VERSION || respMeta.request_id !== requestID) {
|
|
@@ -451,8 +473,10 @@ export function createProxyRuntime(opts) {
|
|
|
451
473
|
maxChunkBytes,
|
|
452
474
|
maxBodyBytes,
|
|
453
475
|
maxWsFrameBytes,
|
|
476
|
+
maxWsBufferedAmountBytes,
|
|
454
477
|
maxConcurrentHttpStreams,
|
|
455
478
|
maxQueuedHttpRequests,
|
|
479
|
+
maxQueuedHttpBodyBytes,
|
|
456
480
|
},
|
|
457
481
|
dispatchFetch,
|
|
458
482
|
openWebSocketStream,
|
|
@@ -36,16 +36,20 @@ export declare function resolveRuntimeLimitsFromScope(scope: ProxyRuntimeScopeV1
|
|
|
36
36
|
maxChunkBytes?: number;
|
|
37
37
|
maxBodyBytes?: number;
|
|
38
38
|
maxWsFrameBytes?: number;
|
|
39
|
+
maxWsBufferedAmountBytes?: number;
|
|
39
40
|
maxConcurrentHttpStreams?: number;
|
|
40
41
|
maxQueuedHttpRequests?: number;
|
|
42
|
+
maxQueuedHttpBodyBytes?: number;
|
|
41
43
|
timeoutMs?: number;
|
|
42
44
|
}> | undefined): Readonly<{
|
|
43
45
|
maxJsonFrameBytes?: number;
|
|
44
46
|
maxChunkBytes?: number;
|
|
45
47
|
maxBodyBytes?: number;
|
|
46
48
|
maxWsFrameBytes?: number;
|
|
49
|
+
maxWsBufferedAmountBytes?: number;
|
|
47
50
|
maxConcurrentHttpStreams?: number;
|
|
48
51
|
maxQueuedHttpRequests?: number;
|
|
52
|
+
maxQueuedHttpBodyBytes?: number;
|
|
49
53
|
timeoutMs?: number;
|
|
50
54
|
}> | undefined;
|
|
51
55
|
export declare function resolvePresetInputFromScope(scope: ProxyRuntimeScopeV1, presetOverride: ProxyPresetInput | undefined): ProxyPresetInput | undefined;
|
|
@@ -3,8 +3,10 @@ export declare const PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE = "flowersec-proxy:wind
|
|
|
3
3
|
export declare const PROXY_WINDOW_FETCH_MSG_TYPE = "flowersec-proxy:fetch";
|
|
4
4
|
export declare const PROXY_WINDOW_WS_OPEN_MSG_TYPE = "flowersec-proxy:ws_open";
|
|
5
5
|
export declare const PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE = "flowersec-proxy:ws_open_ack";
|
|
6
|
+
export declare const PROXY_WINDOW_WS_WRITE_ACK_CAPABILITY = "stream_write_ack_v1";
|
|
6
7
|
export declare const PROXY_WINDOW_WS_ERROR_MSG_TYPE = "flowersec-proxy:ws_error";
|
|
7
8
|
export declare const PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE = "flowersec-proxy:stream_chunk";
|
|
9
|
+
export declare const PROXY_WINDOW_STREAM_WRITE_ACK_MSG_TYPE = "flowersec-proxy:stream_write_ack";
|
|
8
10
|
export declare const PROXY_WINDOW_STREAM_END_MSG_TYPE = "flowersec-proxy:stream_end";
|
|
9
11
|
export declare const PROXY_WINDOW_STREAM_RESET_MSG_TYPE = "flowersec-proxy:stream_reset";
|
|
10
12
|
export declare const PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE = "flowersec-proxy:stream_close";
|
|
@@ -34,6 +36,7 @@ export type ProxyWindowWsOpenMsg = Readonly<{
|
|
|
34
36
|
export type ProxyWindowWsOpenAckMsg = Readonly<{
|
|
35
37
|
type: typeof PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE;
|
|
36
38
|
protocol: string;
|
|
39
|
+
capabilities?: readonly string[];
|
|
37
40
|
}>;
|
|
38
41
|
export type ProxyWindowWsErrorMsg = Readonly<{
|
|
39
42
|
type: typeof PROXY_WINDOW_WS_ERROR_MSG_TYPE;
|
|
@@ -42,6 +45,11 @@ export type ProxyWindowWsErrorMsg = Readonly<{
|
|
|
42
45
|
export type ProxyWindowStreamChunkMsg = Readonly<{
|
|
43
46
|
type: typeof PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE;
|
|
44
47
|
data: ArrayBuffer;
|
|
48
|
+
writeId?: number;
|
|
49
|
+
}>;
|
|
50
|
+
export type ProxyWindowStreamWriteAckMsg = Readonly<{
|
|
51
|
+
type: typeof PROXY_WINDOW_STREAM_WRITE_ACK_MSG_TYPE;
|
|
52
|
+
writeId: number;
|
|
45
53
|
}>;
|
|
46
54
|
export type ProxyWindowStreamEndMsg = Readonly<{
|
|
47
55
|
type: typeof PROXY_WINDOW_STREAM_END_MSG_TYPE;
|
|
@@ -2,8 +2,10 @@ export const PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE = "flowersec-proxy:window_fetch
|
|
|
2
2
|
export const PROXY_WINDOW_FETCH_MSG_TYPE = "flowersec-proxy:fetch";
|
|
3
3
|
export const PROXY_WINDOW_WS_OPEN_MSG_TYPE = "flowersec-proxy:ws_open";
|
|
4
4
|
export const PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE = "flowersec-proxy:ws_open_ack";
|
|
5
|
+
export const PROXY_WINDOW_WS_WRITE_ACK_CAPABILITY = "stream_write_ack_v1";
|
|
5
6
|
export const PROXY_WINDOW_WS_ERROR_MSG_TYPE = "flowersec-proxy:ws_error";
|
|
6
7
|
export const PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE = "flowersec-proxy:stream_chunk";
|
|
8
|
+
export const PROXY_WINDOW_STREAM_WRITE_ACK_MSG_TYPE = "flowersec-proxy:stream_write_ack";
|
|
7
9
|
export const PROXY_WINDOW_STREAM_END_MSG_TYPE = "flowersec-proxy:stream_end";
|
|
8
10
|
export const PROXY_WINDOW_STREAM_RESET_MSG_TYPE = "flowersec-proxy:stream_reset";
|
|
9
11
|
export const PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE = "flowersec-proxy:stream_close";
|
package/dist/proxy/wsPatch.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type WebSocketPatchOptions = Readonly<{
|
|
|
13
13
|
}>;
|
|
14
14
|
shouldProxy?: (url: URL) => boolean;
|
|
15
15
|
maxWsFrameBytes?: number;
|
|
16
|
+
maxWsBufferedAmountBytes?: number;
|
|
16
17
|
}>;
|
|
17
18
|
export declare function installWebSocketPatch(opts: WebSocketPatchOptions): Readonly<{
|
|
18
19
|
uninstall: () => void;
|
package/dist/proxy/wsPatch.js
CHANGED
|
@@ -85,6 +85,22 @@ export function installWebSocketPatch(opts) {
|
|
|
85
85
|
if (maxWsFrameBytesFloor < 0)
|
|
86
86
|
throw new Error("maxWsFrameBytes must be >= 0");
|
|
87
87
|
const maxWsFrameBytes = maxWsFrameBytesFloor === 0 ? runtimeMaxWsFrameBytes : maxWsFrameBytesFloor;
|
|
88
|
+
const defaultMaxWsBufferedAmountBytes = 4 * (1 << 20);
|
|
89
|
+
const runtimeMaxWsBufferedAmountBytesRaw = runtime.limits?.maxWsBufferedAmountBytes;
|
|
90
|
+
if (runtimeMaxWsBufferedAmountBytesRaw !== undefined &&
|
|
91
|
+
(!Number.isSafeInteger(runtimeMaxWsBufferedAmountBytesRaw) || runtimeMaxWsBufferedAmountBytesRaw < 0)) {
|
|
92
|
+
throw new Error("runtime maxWsBufferedAmountBytes must be a non-negative safe integer");
|
|
93
|
+
}
|
|
94
|
+
const runtimeMaxWsBufferedAmountBytes = runtimeMaxWsBufferedAmountBytesRaw == null || runtimeMaxWsBufferedAmountBytesRaw === 0
|
|
95
|
+
? defaultMaxWsBufferedAmountBytes
|
|
96
|
+
: runtimeMaxWsBufferedAmountBytesRaw;
|
|
97
|
+
const maxWsBufferedAmountBytesRaw = opts.maxWsBufferedAmountBytes ?? runtimeMaxWsBufferedAmountBytes;
|
|
98
|
+
if (!Number.isSafeInteger(maxWsBufferedAmountBytesRaw) || maxWsBufferedAmountBytesRaw < 0) {
|
|
99
|
+
throw new Error("maxWsBufferedAmountBytes must be a non-negative safe integer");
|
|
100
|
+
}
|
|
101
|
+
const maxWsBufferedAmountBytes = maxWsBufferedAmountBytesRaw === 0
|
|
102
|
+
? runtimeMaxWsBufferedAmountBytes
|
|
103
|
+
: maxWsBufferedAmountBytesRaw;
|
|
88
104
|
class PatchedWebSocket {
|
|
89
105
|
static CONNECTING = 0;
|
|
90
106
|
static OPEN = 1;
|
|
@@ -119,36 +135,60 @@ export function installWebSocketPatch(opts) {
|
|
|
119
135
|
removeEventListener(type, listener) {
|
|
120
136
|
this.listeners.off(type, listener);
|
|
121
137
|
}
|
|
122
|
-
queueWriteFrame(stream, op, payload) {
|
|
138
|
+
queueWriteFrame(stream, op, payload, bufferedBytes = 0) {
|
|
123
139
|
this.writeChain = this.writeChain
|
|
124
|
-
.then(() =>
|
|
125
|
-
.
|
|
140
|
+
.then(async () => {
|
|
141
|
+
if (this.readyState === PatchedWebSocket.CLOSED)
|
|
142
|
+
return;
|
|
143
|
+
const resolved = typeof payload === "function" ? await payload() : payload;
|
|
144
|
+
await writeWSFrame(stream, op, resolved, maxWsFrameBytes);
|
|
145
|
+
})
|
|
146
|
+
.catch((e) => this.fail(e))
|
|
147
|
+
.finally(() => {
|
|
148
|
+
this.bufferedAmount = Math.max(0, this.bufferedAmount - bufferedBytes);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
reserveBufferedAmount(bytes) {
|
|
152
|
+
if (this.bufferedAmount + bytes > maxWsBufferedAmountBytes) {
|
|
153
|
+
this.fail(new Error("WebSocket bufferedAmount limit exceeded"));
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
this.bufferedAmount += bytes;
|
|
157
|
+
return true;
|
|
126
158
|
}
|
|
127
159
|
send(data) {
|
|
128
160
|
if (this.readyState !== PatchedWebSocket.OPEN || this.stream == null) {
|
|
129
161
|
throw new Error("WebSocket is not open");
|
|
130
162
|
}
|
|
131
163
|
const s = this.stream;
|
|
132
|
-
const sendBytes = (op,
|
|
133
|
-
this.
|
|
164
|
+
const sendBytes = (op, byteLength, copyPayload) => {
|
|
165
|
+
if (!this.reserveBufferedAmount(byteLength))
|
|
166
|
+
return;
|
|
167
|
+
try {
|
|
168
|
+
this.queueWriteFrame(s, op, copyPayload(), byteLength);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.bufferedAmount = Math.max(0, this.bufferedAmount - byteLength);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
134
174
|
};
|
|
135
175
|
if (typeof data === "string") {
|
|
136
|
-
|
|
176
|
+
const payload = te.encode(data);
|
|
177
|
+
sendBytes(1, payload.byteLength, () => payload);
|
|
137
178
|
return;
|
|
138
179
|
}
|
|
139
180
|
if (data instanceof ArrayBuffer) {
|
|
140
|
-
sendBytes(2, new Uint8Array(data));
|
|
181
|
+
sendBytes(2, data.byteLength, () => new Uint8Array(data).slice());
|
|
141
182
|
return;
|
|
142
183
|
}
|
|
143
184
|
if (ArrayBuffer.isView(data)) {
|
|
144
|
-
sendBytes(2, new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
185
|
+
sendBytes(2, data.byteLength, () => new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice());
|
|
145
186
|
return;
|
|
146
187
|
}
|
|
147
188
|
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.catch((e) => this.fail(e));
|
|
189
|
+
if (!this.reserveBufferedAmount(data.size))
|
|
190
|
+
return;
|
|
191
|
+
this.queueWriteFrame(s, 2, async () => new Uint8Array(await data.arrayBuffer()), data.size);
|
|
152
192
|
return;
|
|
153
193
|
}
|
|
154
194
|
throw new Error("unsupported WebSocket send payload");
|
|
@@ -260,10 +300,14 @@ export function installWebSocketPatch(opts) {
|
|
|
260
300
|
}
|
|
261
301
|
}
|
|
262
302
|
fail(e) {
|
|
303
|
+
if (this.readyState === PatchedWebSocket.CLOSED)
|
|
304
|
+
return;
|
|
263
305
|
this.readyState = PatchedWebSocket.CLOSED;
|
|
264
306
|
const msg = e instanceof Error ? e.message : String(e);
|
|
307
|
+
this.bufferedAmount = 0;
|
|
265
308
|
this.emit("error", { type: "error", message: msg });
|
|
266
309
|
this.emit("close", { type: "close", code: 1006, reason: msg, wasClean: false });
|
|
310
|
+
this.stream = null;
|
|
267
311
|
try {
|
|
268
312
|
this.ac.abort(msg);
|
|
269
313
|
}
|
package/dist/reconnect/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getClientTermination } from "../client-connect/termination.js";
|
|
1
2
|
import { emitObserverDiagnostic, withObserverContext } from "../observability/observer.js";
|
|
2
3
|
export { createArtifactResolver, createControlplaneArtifactSource } from "./artifactControlplane.js";
|
|
3
4
|
function normalizeAutoReconnect(cfg) {
|
|
@@ -213,6 +214,14 @@ export function createReconnectManager() {
|
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
216
|
setState({ status: "connected", client, error: null });
|
|
217
|
+
const termination = getClientTermination(client);
|
|
218
|
+
if (termination != null) {
|
|
219
|
+
void termination.then(({ error }) => {
|
|
220
|
+
if (s.client !== client)
|
|
221
|
+
return;
|
|
222
|
+
startReconnect(t, cfg, error);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
216
225
|
emitObserverDiagnostic(withObserverContext(cfg.observer, { attemptSeq }), {
|
|
217
226
|
path: "auto",
|
|
218
227
|
stage: "reconnect",
|
package/dist/rpc/client.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export declare class RpcClient {
|
|
|
8
8
|
private readonly notifyHandlers;
|
|
9
9
|
private closed;
|
|
10
10
|
private readonly observer;
|
|
11
|
+
private readonly onTerminal;
|
|
11
12
|
constructor(readExactly: (n: number) => Promise<Uint8Array>, write: (b: Uint8Array) => Promise<void>, opts?: Readonly<{
|
|
12
13
|
observer?: ClientObserverLike;
|
|
14
|
+
onTerminal?: (error: Error) => void;
|
|
13
15
|
}>);
|
|
14
16
|
call(typeId: number, payload: unknown, signal?: AbortSignal): Promise<{
|
|
15
17
|
payload: unknown;
|
package/dist/rpc/client.js
CHANGED
|
@@ -17,10 +17,12 @@ export class RpcClient {
|
|
|
17
17
|
closed = false;
|
|
18
18
|
// Observer for RPC events.
|
|
19
19
|
observer;
|
|
20
|
+
onTerminal;
|
|
20
21
|
constructor(readExactly, write, opts = {}) {
|
|
21
22
|
this.readExactly = readExactly;
|
|
22
23
|
this.write = write;
|
|
23
24
|
this.observer = normalizeObserver(opts.observer);
|
|
25
|
+
this.onTerminal = opts.onTerminal;
|
|
24
26
|
void this.readLoop();
|
|
25
27
|
}
|
|
26
28
|
// call sends a request and awaits a response or abort.
|
|
@@ -136,10 +138,19 @@ export class RpcClient {
|
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
catch (e) {
|
|
141
|
+
const unexpected = !this.closed;
|
|
139
142
|
this.closed = true;
|
|
140
143
|
for (const [, p] of this.pending)
|
|
141
144
|
p.reject(e);
|
|
142
145
|
this.pending.clear();
|
|
146
|
+
if (unexpected) {
|
|
147
|
+
try {
|
|
148
|
+
this.onTerminal?.(e instanceof Error ? e : new Error(String(e)));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Lifecycle callbacks must not escape the read loop.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
143
154
|
}
|
|
144
155
|
}
|
|
145
156
|
}
|
package/dist/yamux/session.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type YamuxSessionOptions = Readonly<{
|
|
|
33
33
|
limits?: Partial<YamuxLimits>;
|
|
34
34
|
/** Optional generic resource diagnostic callback. */
|
|
35
35
|
onDiagnostic?: (event: YamuxDiagnostic) => void;
|
|
36
|
+
/** Internal lifecycle callback for unexpected session termination. */
|
|
37
|
+
onTerminal?: (error: Error) => void;
|
|
36
38
|
}>;
|
|
37
39
|
export declare class YamuxSession {
|
|
38
40
|
private readonly conn;
|
|
@@ -41,6 +43,7 @@ export declare class YamuxSession {
|
|
|
41
43
|
private readonly onIncomingStream;
|
|
42
44
|
private readonly limits;
|
|
43
45
|
private readonly onDiagnostic;
|
|
46
|
+
private readonly onTerminal;
|
|
44
47
|
private readonly client;
|
|
45
48
|
private nextStreamId;
|
|
46
49
|
private closed;
|
|
@@ -51,7 +54,9 @@ export declare class YamuxSession {
|
|
|
51
54
|
private readonly pingWaiters;
|
|
52
55
|
private activeProbe;
|
|
53
56
|
constructor(conn: ByteDuplex, opts: YamuxSessionOptions);
|
|
54
|
-
openStream(
|
|
57
|
+
openStream(opts?: Readonly<{
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}>): Promise<YamuxStream>;
|
|
55
60
|
getStream(id: number): YamuxStream | undefined;
|
|
56
61
|
writeRaw(chunk: Uint8Array): Promise<void>;
|
|
57
62
|
outboundFrameBytes(): number;
|
|
@@ -64,6 +69,8 @@ export declare class YamuxSession {
|
|
|
64
69
|
onStreamEstablished(_streamId: number): void;
|
|
65
70
|
onStreamClosed(streamId: number): void;
|
|
66
71
|
close(): void;
|
|
72
|
+
private fail;
|
|
73
|
+
private closeInternal;
|
|
67
74
|
private wakeSendWindowWaiters;
|
|
68
75
|
private readLoop;
|
|
69
76
|
private handlePing;
|