@floegence/flowersec-core 0.21.0 → 0.22.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 +9 -1
- package/dist/client-connect/connectCore.d.ts +1 -1
- package/dist/client-connect/connectCore.js +5 -4
- package/dist/controlplane/channelInit.d.ts +27 -0
- package/dist/controlplane/channelInit.js +116 -0
- package/dist/controlplane/http.d.ts +16 -0
- package/dist/controlplane/http.js +59 -0
- package/dist/controlplane/index.d.ts +4 -0
- package/dist/controlplane/index.js +4 -0
- package/dist/controlplane/issuer.d.ts +15 -0
- package/dist/controlplane/issuer.js +53 -0
- package/dist/controlplane/request.js +2 -1
- package/dist/controlplane/token.d.ts +33 -0
- package/dist/controlplane/token.js +119 -0
- package/dist/defaults.d.ts +46 -0
- package/dist/defaults.js +46 -0
- package/dist/endpoint/index.d.ts +83 -0
- package/dist/endpoint/index.js +384 -0
- package/dist/endpoint/node.d.ts +9 -0
- package/dist/endpoint/node.js +51 -0
- package/dist/framing/jsonframe.js +2 -1
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.js +3 -0
- package/dist/proxy/constants.js +4 -3
- package/dist/proxy/controllerWindow.js +23 -3
- package/dist/proxy/headerPolicy.js +1 -0
- package/dist/proxy/runtime.d.ts +1 -0
- package/dist/proxy/runtime.js +38 -1
- package/dist/proxy/server.d.ts +27 -0
- package/dist/proxy/server.js +480 -0
- package/dist/proxy/serviceWorker.js +23 -5
- package/dist/proxy/windowBridgeProtocol.d.ts +1 -0
- package/dist/reconnect/index.js +33 -22
- package/dist/rpc/server.d.ts +9 -2
- package/dist/rpc/server.js +33 -10
- package/dist/yamux/session.d.ts +6 -1
- package/dist/yamux/session.js +12 -6
- package/dist/yamux/stream.d.ts +1 -0
- package/dist/yamux/stream.js +19 -4
- package/package.json +5 -1
package/dist/reconnect/index.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { getClientTermination } from "../client-connect/termination.js";
|
|
2
2
|
import { emitObserverDiagnostic, withObserverContext } from "../observability/observer.js";
|
|
3
|
+
import { SDK_DEFAULTS } from "../defaults.js";
|
|
3
4
|
export { createArtifactResolver, createControlplaneArtifactSource } from "./artifactControlplane.js";
|
|
4
5
|
function normalizeAutoReconnect(cfg) {
|
|
5
6
|
if (!cfg?.enabled) {
|
|
6
7
|
return {
|
|
7
8
|
enabled: false,
|
|
8
9
|
maxAttempts: 1,
|
|
9
|
-
initialDelayMs:
|
|
10
|
-
maxDelayMs:
|
|
11
|
-
factor:
|
|
12
|
-
jitterRatio:
|
|
10
|
+
initialDelayMs: SDK_DEFAULTS.reconnect.initialDelayMs,
|
|
11
|
+
maxDelayMs: SDK_DEFAULTS.reconnect.maxDelayMs,
|
|
12
|
+
factor: SDK_DEFAULTS.reconnect.factor,
|
|
13
|
+
jitterRatio: SDK_DEFAULTS.reconnect.jitterRatio,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
return {
|
|
16
17
|
enabled: true,
|
|
17
|
-
maxAttempts: Math.max(1, cfg.maxAttempts ??
|
|
18
|
-
initialDelayMs: Math.max(0, cfg.initialDelayMs ??
|
|
19
|
-
maxDelayMs: Math.max(0, cfg.maxDelayMs ??
|
|
20
|
-
factor: Math.max(1, cfg.factor ??
|
|
21
|
-
jitterRatio: Math.max(0, cfg.jitterRatio ??
|
|
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),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
function backoffDelayMs(attemptIndex, cfg) {
|
|
@@ -138,8 +139,9 @@ export function createReconnectManager() {
|
|
|
138
139
|
}
|
|
139
140
|
token += 1;
|
|
140
141
|
const nextToken = token;
|
|
142
|
+
const reconnectPromise = startConnectLoop(nextToken, cfg);
|
|
141
143
|
setState({ status: "connecting", error, client: null });
|
|
142
|
-
void
|
|
144
|
+
void reconnectPromise.catch(() => {
|
|
143
145
|
// connectWithRetry updates state; keep errors observable via state().
|
|
144
146
|
});
|
|
145
147
|
};
|
|
@@ -262,6 +264,22 @@ export function createReconnectManager() {
|
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
};
|
|
267
|
+
const startConnectLoop = (t, cfg) => {
|
|
268
|
+
let resolveLoop;
|
|
269
|
+
let rejectLoop;
|
|
270
|
+
const loop = new Promise((resolve, reject) => {
|
|
271
|
+
resolveLoop = resolve;
|
|
272
|
+
rejectLoop = reject;
|
|
273
|
+
});
|
|
274
|
+
let promise;
|
|
275
|
+
promise = loop.finally(() => {
|
|
276
|
+
if (activeConnectPromise === promise)
|
|
277
|
+
activeConnectPromise = null;
|
|
278
|
+
});
|
|
279
|
+
activeConnectPromise = promise;
|
|
280
|
+
void connectWithRetry(t, cfg).then(resolveLoop, rejectLoop);
|
|
281
|
+
return promise;
|
|
282
|
+
};
|
|
265
283
|
const connect = async (cfg) => {
|
|
266
284
|
cancelRetrySleep();
|
|
267
285
|
abortActiveAttempt();
|
|
@@ -277,25 +295,18 @@ export function createReconnectManager() {
|
|
|
277
295
|
// ignore
|
|
278
296
|
}
|
|
279
297
|
}
|
|
298
|
+
const connectPromise = startConnectLoop(t, cfg);
|
|
280
299
|
setState({ status: "connecting", error: null, client: null });
|
|
281
|
-
|
|
282
|
-
activeConnectPromise = p;
|
|
283
|
-
try {
|
|
284
|
-
await p;
|
|
285
|
-
}
|
|
286
|
-
finally {
|
|
287
|
-
if (activeConnectPromise === p)
|
|
288
|
-
activeConnectPromise = null;
|
|
289
|
-
}
|
|
300
|
+
await connectPromise;
|
|
290
301
|
};
|
|
291
302
|
const connectIfNeeded = async (cfg) => {
|
|
292
303
|
if (isSameConfig(active, cfg)) {
|
|
293
|
-
if (
|
|
294
|
-
return;
|
|
295
|
-
if (s.status === "connecting" && activeConnectPromise) {
|
|
304
|
+
if (activeConnectPromise) {
|
|
296
305
|
await activeConnectPromise;
|
|
297
306
|
return;
|
|
298
307
|
}
|
|
308
|
+
if (s.status === "connected" && s.client)
|
|
309
|
+
return;
|
|
299
310
|
}
|
|
300
311
|
await connect(cfg);
|
|
301
312
|
};
|
package/dist/rpc/server.d.ts
CHANGED
|
@@ -13,9 +13,14 @@ export type RpcServerTransport = Readonly<{
|
|
|
13
13
|
write(bytes: Uint8Array): Promise<void>;
|
|
14
14
|
close(error: unknown): void;
|
|
15
15
|
}>;
|
|
16
|
+
export declare class RpcRouter {
|
|
17
|
+
private readonly handlers;
|
|
18
|
+
register(typeId: number, handler: RpcHandler): void;
|
|
19
|
+
handler(typeId: number): RpcHandler | undefined;
|
|
20
|
+
}
|
|
16
21
|
export declare class RpcServer {
|
|
17
22
|
private readonly transport;
|
|
18
|
-
private readonly
|
|
23
|
+
private readonly router;
|
|
19
24
|
private closed;
|
|
20
25
|
private readonly options;
|
|
21
26
|
private readonly requests;
|
|
@@ -27,8 +32,9 @@ export declare class RpcServer {
|
|
|
27
32
|
private readonly terminalSignal;
|
|
28
33
|
private signalTerminal;
|
|
29
34
|
private transportClosed;
|
|
30
|
-
constructor(transport: RpcServerTransport, options?: RpcServerOptions);
|
|
35
|
+
constructor(transport: RpcServerTransport, options?: RpcServerOptions, router?: RpcRouter);
|
|
31
36
|
register(typeId: number, h: RpcHandler): void;
|
|
37
|
+
notify(typeId: number, payload: unknown): Promise<void>;
|
|
32
38
|
serve(signal?: AbortSignal): Promise<void>;
|
|
33
39
|
close(error?: unknown): void;
|
|
34
40
|
private fail;
|
|
@@ -37,4 +43,5 @@ export declare class RpcServer {
|
|
|
37
43
|
private nextWork;
|
|
38
44
|
private wakeOne;
|
|
39
45
|
private writeResponse;
|
|
46
|
+
private writeEnvelope;
|
|
40
47
|
}
|
package/dist/rpc/server.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { DEFAULT_MAX_JSON_FRAME_BYTES, readJsonFrame, writeJsonFrame } from "../framing/jsonframe.js";
|
|
2
2
|
import { assertRpcEnvelope } from "./validate.js";
|
|
3
|
+
import { SDK_DEFAULTS } from "../defaults.js";
|
|
3
4
|
const DEFAULT_RPC_SERVER_OPTIONS = Object.freeze({
|
|
4
|
-
maxConcurrentRequests:
|
|
5
|
-
maxQueuedRequests:
|
|
6
|
-
maxQueuedNotifications:
|
|
5
|
+
maxConcurrentRequests: SDK_DEFAULTS.rpc.maxConcurrentRequests,
|
|
6
|
+
maxQueuedRequests: SDK_DEFAULTS.rpc.maxQueuedRequests,
|
|
7
|
+
maxQueuedNotifications: SDK_DEFAULTS.rpc.maxQueuedNotifications,
|
|
7
8
|
});
|
|
9
|
+
export class RpcRouter {
|
|
10
|
+
handlers = new Map();
|
|
11
|
+
register(typeId, handler) {
|
|
12
|
+
this.handlers.set(typeId >>> 0, handler);
|
|
13
|
+
}
|
|
14
|
+
handler(typeId) {
|
|
15
|
+
return this.handlers.get(typeId >>> 0);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
8
18
|
// RpcServer dispatches request envelopes to registered handlers.
|
|
9
19
|
export class RpcServer {
|
|
10
20
|
transport;
|
|
11
|
-
|
|
12
|
-
handlers = new Map();
|
|
21
|
+
router;
|
|
13
22
|
// Closed flag to stop the serve loop.
|
|
14
23
|
closed = false;
|
|
15
24
|
options;
|
|
@@ -22,8 +31,9 @@ export class RpcServer {
|
|
|
22
31
|
terminalSignal;
|
|
23
32
|
signalTerminal;
|
|
24
33
|
transportClosed = false;
|
|
25
|
-
constructor(transport, options = {}) {
|
|
34
|
+
constructor(transport, options = {}, router = new RpcRouter()) {
|
|
26
35
|
this.transport = transport;
|
|
36
|
+
this.router = router;
|
|
27
37
|
this.terminalSignal = new Promise((resolve) => { this.signalTerminal = resolve; });
|
|
28
38
|
this.options = {
|
|
29
39
|
maxConcurrentRequests: positiveInteger(options.maxConcurrentRequests ?? DEFAULT_RPC_SERVER_OPTIONS.maxConcurrentRequests, "maxConcurrentRequests"),
|
|
@@ -33,7 +43,17 @@ export class RpcServer {
|
|
|
33
43
|
}
|
|
34
44
|
// register binds a handler to a type ID.
|
|
35
45
|
register(typeId, h) {
|
|
36
|
-
this.
|
|
46
|
+
this.router.register(typeId, h);
|
|
47
|
+
}
|
|
48
|
+
async notify(typeId, payload) {
|
|
49
|
+
if (this.closed)
|
|
50
|
+
throw new Error("rpc server closed");
|
|
51
|
+
await this.writeEnvelope({
|
|
52
|
+
type_id: typeId >>> 0,
|
|
53
|
+
request_id: 0,
|
|
54
|
+
response_to: 0,
|
|
55
|
+
payload,
|
|
56
|
+
});
|
|
37
57
|
}
|
|
38
58
|
// serve handles request/response frames until closed or aborted.
|
|
39
59
|
async serve(signal) {
|
|
@@ -106,7 +126,7 @@ export class RpcServer {
|
|
|
106
126
|
if (work == null)
|
|
107
127
|
return;
|
|
108
128
|
const v = work.envelope;
|
|
109
|
-
const h = this.
|
|
129
|
+
const h = this.router.handler(v.type_id);
|
|
110
130
|
let out;
|
|
111
131
|
if (h == null)
|
|
112
132
|
out = { payload: null, error: { code: 404, message: "handler not found" } };
|
|
@@ -129,7 +149,7 @@ export class RpcServer {
|
|
|
129
149
|
if (work == null)
|
|
130
150
|
return;
|
|
131
151
|
const v = work.envelope;
|
|
132
|
-
const h = this.
|
|
152
|
+
const h = this.router.handler(v.type_id);
|
|
133
153
|
if (h == null)
|
|
134
154
|
continue;
|
|
135
155
|
try {
|
|
@@ -158,7 +178,10 @@ export class RpcServer {
|
|
|
158
178
|
payload: out.payload,
|
|
159
179
|
...(out.error != null ? { error: out.error } : {}),
|
|
160
180
|
};
|
|
161
|
-
|
|
181
|
+
await this.writeEnvelope(resp);
|
|
182
|
+
}
|
|
183
|
+
async writeEnvelope(envelope) {
|
|
184
|
+
const write = this.writeChain.then(() => writeJsonFrame(this.transport.write, envelope));
|
|
162
185
|
this.writeChain = write.catch(() => { });
|
|
163
186
|
await write;
|
|
164
187
|
}
|
package/dist/yamux/session.d.ts
CHANGED
|
@@ -12,8 +12,11 @@ export type YamuxLimits = Readonly<{
|
|
|
12
12
|
preferredOutboundFrameBytes: number;
|
|
13
13
|
maxStreamReceiveBytes: number;
|
|
14
14
|
maxSessionReceiveBytes: number;
|
|
15
|
+
/** Maximum bytes retained by pending write calls on one stream. */
|
|
16
|
+
maxStreamWriteQueueBytes?: number;
|
|
15
17
|
}>;
|
|
16
|
-
|
|
18
|
+
type ResolvedYamuxLimits = Readonly<Required<YamuxLimits>>;
|
|
19
|
+
export declare const DEFAULT_YAMUX_LIMITS: ResolvedYamuxLimits;
|
|
17
20
|
export type ByteDuplex = {
|
|
18
21
|
/** Reads the next chunk from the underlying connection. */
|
|
19
22
|
read(): Promise<Uint8Array>;
|
|
@@ -60,6 +63,7 @@ export declare class YamuxSession {
|
|
|
60
63
|
getStream(id: number): YamuxStream | undefined;
|
|
61
64
|
writeRaw(chunk: Uint8Array): Promise<void>;
|
|
62
65
|
outboundFrameBytes(): number;
|
|
66
|
+
streamWriteQueueBytes(): number;
|
|
63
67
|
releaseReceiveBytes(bytes: number): void;
|
|
64
68
|
probeLiveness(timeoutMs?: number): Promise<number>;
|
|
65
69
|
private startLivenessProbe;
|
|
@@ -79,3 +83,4 @@ export declare class YamuxSession {
|
|
|
79
83
|
private isInboundStreamIdValid;
|
|
80
84
|
private diagnostic;
|
|
81
85
|
}
|
|
86
|
+
export {};
|
package/dist/yamux/session.js
CHANGED
|
@@ -3,13 +3,15 @@ import { decodeHeader, encodeHeader, HEADER_LEN } from "./header.js";
|
|
|
3
3
|
import { FLAG_ACK, FLAG_RST, FLAG_SYN, TYPE_DATA, TYPE_GO_AWAY, TYPE_PING, TYPE_WINDOW_UPDATE, YAMUX_VERSION } from "./constants.js";
|
|
4
4
|
import { YamuxStream } from "./stream.js";
|
|
5
5
|
import { YamuxResourceExhaustedError } from "./errors.js";
|
|
6
|
+
import { SDK_DEFAULTS } from "../defaults.js";
|
|
6
7
|
export const DEFAULT_YAMUX_LIMITS = Object.freeze({
|
|
7
|
-
maxActiveStreams:
|
|
8
|
-
maxInboundStreams:
|
|
9
|
-
maxFrameBytes:
|
|
10
|
-
preferredOutboundFrameBytes:
|
|
11
|
-
maxStreamReceiveBytes:
|
|
12
|
-
maxSessionReceiveBytes:
|
|
8
|
+
maxActiveStreams: SDK_DEFAULTS.yamux.maxActiveStreams,
|
|
9
|
+
maxInboundStreams: SDK_DEFAULTS.yamux.maxInboundStreams,
|
|
10
|
+
maxFrameBytes: SDK_DEFAULTS.yamux.maxFrameBytes,
|
|
11
|
+
preferredOutboundFrameBytes: SDK_DEFAULTS.yamux.preferredOutboundFrameBytes,
|
|
12
|
+
maxStreamReceiveBytes: SDK_DEFAULTS.yamux.maxStreamReceiveBytes,
|
|
13
|
+
maxSessionReceiveBytes: SDK_DEFAULTS.yamux.maxSessionReceiveBytes,
|
|
14
|
+
maxStreamWriteQueueBytes: SDK_DEFAULTS.e2ee.maxOutboundBufferedBytes,
|
|
13
15
|
});
|
|
14
16
|
// YamuxSession multiplexes multiple streams over a single byte stream.
|
|
15
17
|
export class YamuxSession {
|
|
@@ -92,6 +94,9 @@ export class YamuxSession {
|
|
|
92
94
|
outboundFrameBytes() {
|
|
93
95
|
return this.limits.preferredOutboundFrameBytes;
|
|
94
96
|
}
|
|
97
|
+
streamWriteQueueBytes() {
|
|
98
|
+
return this.limits.maxStreamWriteQueueBytes;
|
|
99
|
+
}
|
|
95
100
|
releaseReceiveBytes(bytes) {
|
|
96
101
|
this.sessionReceiveBytes = Math.max(0, this.sessionReceiveBytes - Math.max(0, bytes));
|
|
97
102
|
}
|
|
@@ -436,6 +441,7 @@ function normalizeYamuxLimits(input) {
|
|
|
436
441
|
preferredOutboundFrameBytes: input?.preferredOutboundFrameBytes ?? Math.min(DEFAULT_YAMUX_LIMITS.preferredOutboundFrameBytes, maxFrameBytes),
|
|
437
442
|
maxStreamReceiveBytes: input?.maxStreamReceiveBytes ?? DEFAULT_YAMUX_LIMITS.maxStreamReceiveBytes,
|
|
438
443
|
maxSessionReceiveBytes: input?.maxSessionReceiveBytes ?? DEFAULT_YAMUX_LIMITS.maxSessionReceiveBytes,
|
|
444
|
+
maxStreamWriteQueueBytes: input?.maxStreamWriteQueueBytes ?? DEFAULT_YAMUX_LIMITS.maxStreamWriteQueueBytes,
|
|
439
445
|
};
|
|
440
446
|
for (const [name, value] of Object.entries(limits)) {
|
|
441
447
|
if (!Number.isSafeInteger(value) || value <= 0)
|
package/dist/yamux/stream.d.ts
CHANGED
package/dist/yamux/stream.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { concatBytes } from "../utils/bin.js";
|
|
2
2
|
import { encodeHeader } from "./header.js";
|
|
3
3
|
import { DEFAULT_MAX_STREAM_WINDOW, FLAG_ACK, FLAG_FIN, FLAG_RST, FLAG_SYN, TYPE_DATA, TYPE_WINDOW_UPDATE } from "./constants.js";
|
|
4
|
+
import { YamuxResourceExhaustedError } from "./errors.js";
|
|
4
5
|
// YamuxStream manages per-stream flow control and state transitions.
|
|
5
6
|
export class YamuxStream {
|
|
6
7
|
// Stream identifier within the session.
|
|
@@ -24,6 +25,7 @@ export class YamuxStream {
|
|
|
24
25
|
error = null;
|
|
25
26
|
resetTask;
|
|
26
27
|
writeChain = Promise.resolve();
|
|
28
|
+
writeQueueBytes = 0;
|
|
27
29
|
finalized = false;
|
|
28
30
|
constructor(session, id, state) {
|
|
29
31
|
this.session = session;
|
|
@@ -87,10 +89,23 @@ export class YamuxStream {
|
|
|
87
89
|
}
|
|
88
90
|
// write sends DATA frames, respecting the send window.
|
|
89
91
|
async write(data) {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
this.ensureWritable();
|
|
93
|
+
const byteCount = data.byteLength;
|
|
94
|
+
const nextQueueBytes = this.writeQueueBytes + byteCount;
|
|
95
|
+
const limit = this.session.streamWriteQueueBytes();
|
|
96
|
+
if (!Number.isSafeInteger(nextQueueBytes) || nextQueueBytes > limit) {
|
|
97
|
+
throw new YamuxResourceExhaustedError("stream_write_queue_bytes", nextQueueBytes, limit);
|
|
98
|
+
}
|
|
99
|
+
this.writeQueueBytes = nextQueueBytes;
|
|
100
|
+
try {
|
|
101
|
+
const payload = data.slice();
|
|
102
|
+
const write = this.writeChain.then(() => this.writeSerial(payload));
|
|
103
|
+
this.writeChain = write.catch(() => { });
|
|
104
|
+
await write;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
this.writeQueueBytes = Math.max(0, this.writeQueueBytes - byteCount);
|
|
108
|
+
}
|
|
94
109
|
}
|
|
95
110
|
async writeSerial(data) {
|
|
96
111
|
this.ensureWritable();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floegence/flowersec-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Flowersec core TypeScript library (browser-friendly E2EE + multiplexing over WebSocket).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"types": "./dist/controlplane/index.d.ts",
|
|
41
41
|
"default": "./dist/controlplane/index.js"
|
|
42
42
|
},
|
|
43
|
+
"./endpoint": {
|
|
44
|
+
"types": "./dist/endpoint/index.d.ts",
|
|
45
|
+
"default": "./dist/endpoint/index.js"
|
|
46
|
+
},
|
|
43
47
|
"./framing": {
|
|
44
48
|
"types": "./dist/framing/index.d.ts",
|
|
45
49
|
"default": "./dist/framing/index.js"
|