@floegence/flowersec-core 0.2.0 → 0.2.1

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.
@@ -12,8 +12,6 @@ import { isTunnelAttachCloseReason } from "./tunnelAttachCloseReason.js";
12
12
  export async function connectCore(args) {
13
13
  const observer = normalizeObserver(args.opts.observer);
14
14
  const signal = args.opts.signal;
15
- const connectTimeoutMs = args.opts.connectTimeoutMs ?? 10_000;
16
- const handshakeTimeoutMs = args.opts.handshakeTimeoutMs ?? 10_000;
17
15
  const connectStart = nowSeconds();
18
16
  const origin = args.opts.origin;
19
17
  if (origin == null || origin === "") {
@@ -23,6 +21,41 @@ export async function connectCore(args) {
23
21
  const code = args.path === "tunnel" ? "missing_tunnel_url" : "missing_ws_url";
24
22
  throw new FlowersecError({ path: args.path, stage: "validate", code, message: "missing websocket url" });
25
23
  }
24
+ const invalidOption = (message) => {
25
+ throw new FlowersecError({ path: args.path, stage: "validate", code: "invalid_option", message });
26
+ };
27
+ const connectTimeoutMs = args.opts.connectTimeoutMs ?? 10_000;
28
+ if (!Number.isFinite(connectTimeoutMs) || connectTimeoutMs < 0) {
29
+ invalidOption("connectTimeoutMs must be a non-negative number");
30
+ }
31
+ const handshakeTimeoutMs = args.opts.handshakeTimeoutMs ?? 10_000;
32
+ if (!Number.isFinite(handshakeTimeoutMs) || handshakeTimeoutMs < 0) {
33
+ invalidOption("handshakeTimeoutMs must be a non-negative number");
34
+ }
35
+ const keepaliveIntervalMs = args.opts.keepaliveIntervalMs ?? 0;
36
+ if (!Number.isFinite(keepaliveIntervalMs) || keepaliveIntervalMs < 0) {
37
+ invalidOption("keepaliveIntervalMs must be a non-negative number");
38
+ }
39
+ const clientFeatures = args.opts.clientFeatures ?? 0;
40
+ if (!Number.isSafeInteger(clientFeatures) || clientFeatures < 0 || clientFeatures > 0xffffffff) {
41
+ invalidOption("clientFeatures must be a uint32");
42
+ }
43
+ const maxHandshakePayload = args.opts.maxHandshakePayload ?? 0;
44
+ if (!Number.isSafeInteger(maxHandshakePayload) || maxHandshakePayload < 0) {
45
+ invalidOption("maxHandshakePayload must be a non-negative integer");
46
+ }
47
+ const maxRecordBytes = args.opts.maxRecordBytes ?? 0;
48
+ if (!Number.isSafeInteger(maxRecordBytes) || maxRecordBytes < 0) {
49
+ invalidOption("maxRecordBytes must be a non-negative integer");
50
+ }
51
+ const maxBufferedBytes = args.opts.maxBufferedBytes ?? 0;
52
+ if (!Number.isSafeInteger(maxBufferedBytes) || maxBufferedBytes < 0) {
53
+ invalidOption("maxBufferedBytes must be a non-negative integer");
54
+ }
55
+ const maxWsQueuedBytes = args.opts.maxWsQueuedBytes ?? 0;
56
+ if (!Number.isSafeInteger(maxWsQueuedBytes) || maxWsQueuedBytes < 0) {
57
+ invalidOption("maxWsQueuedBytes must be a non-negative integer");
58
+ }
26
59
  let ws;
27
60
  try {
28
61
  ws = createWebSocket(args.wsUrl, origin, args.opts.wsFactory);
@@ -39,7 +72,7 @@ export async function connectCore(args) {
39
72
  // Install close/error/message listeners before waiting for "open" to avoid a gap where a peer close
40
73
  // (for example a tunnel attach rejection with a reason token) can be missed and misclassified as a handshake timeout.
41
74
  const transport = new WebSocketBinaryTransport(ws, {
42
- ...(args.opts.maxWsQueuedBytes != null && args.opts.maxWsQueuedBytes > 0 ? { maxQueuedBytes: args.opts.maxWsQueuedBytes } : {}),
75
+ ...(maxWsQueuedBytes > 0 ? { maxQueuedBytes: maxWsQueuedBytes } : {}),
43
76
  observer,
44
77
  });
45
78
  try {
@@ -116,14 +149,11 @@ export async function connectCore(args) {
116
149
  const handshakeStart = nowSeconds();
117
150
  let secure;
118
151
  try {
119
- const maxHandshakePayload = args.opts.maxHandshakePayload ?? 0;
120
- const maxRecordBytes = args.opts.maxRecordBytes ?? 0;
121
- const maxBufferedBytes = args.opts.maxBufferedBytes ?? 0;
122
152
  secure = await withAbortAndTimeout(clientHandshake(transport, {
123
153
  channelId: args.channelId,
124
154
  suite,
125
155
  psk,
126
- clientFeatures: args.opts.clientFeatures ?? 0,
156
+ clientFeatures,
127
157
  maxHandshakePayload: maxHandshakePayload > 0 ? maxHandshakePayload : 8 * 1024,
128
158
  maxRecordBytes: maxRecordBytes > 0 ? maxRecordBytes : (1 << 20),
129
159
  ...(maxBufferedBytes > 0 ? { maxBufferedBytes } : {}),
@@ -209,7 +239,6 @@ export async function connectCore(args) {
209
239
  throw new FlowersecError({ path: args.path, stage: "secure", code: "ping_failed", message: "ping failed", cause: e });
210
240
  }
211
241
  };
212
- const keepaliveIntervalMs = Math.max(0, args.opts.keepaliveIntervalMs ?? 0);
213
242
  let keepaliveTimer;
214
243
  let keepaliveInFlight = false;
215
244
  const stopKeepalive = () => {
@@ -218,6 +247,27 @@ export async function connectCore(args) {
218
247
  clearInterval(keepaliveTimer);
219
248
  keepaliveTimer = undefined;
220
249
  };
250
+ const closeAll = () => {
251
+ stopKeepalive();
252
+ try {
253
+ rpc.close();
254
+ }
255
+ catch {
256
+ // ignore
257
+ }
258
+ try {
259
+ mux.close();
260
+ }
261
+ catch {
262
+ // ignore
263
+ }
264
+ try {
265
+ secure.close();
266
+ }
267
+ catch {
268
+ // ignore
269
+ }
270
+ };
221
271
  if (keepaliveIntervalMs > 0) {
222
272
  keepaliveTimer = setInterval(() => {
223
273
  if (keepaliveInFlight)
@@ -225,12 +275,7 @@ export async function connectCore(args) {
225
275
  keepaliveInFlight = true;
226
276
  ping()
227
277
  .catch(() => {
228
- try {
229
- ws.close();
230
- }
231
- catch {
232
- // ignore
233
- }
278
+ closeAll();
234
279
  })
235
280
  .finally(() => {
236
281
  keepaliveInFlight = false;
@@ -343,12 +388,7 @@ export async function connectCore(args) {
343
388
  }
344
389
  return s;
345
390
  },
346
- close: () => {
347
- stopKeepalive();
348
- rpc.close();
349
- mux.close();
350
- secure.close();
351
- },
391
+ close: closeAll,
352
392
  };
353
393
  }
354
394
  catch (e) {
@@ -105,7 +105,7 @@ export async function connectTunnel(grant, opts) {
105
105
  endpoint_instance_id: endpointInstanceId
106
106
  };
107
107
  const attachJson = JSON.stringify(attach);
108
- const keepaliveIntervalMs = opts.keepaliveIntervalMs !== undefined ? Math.max(0, opts.keepaliveIntervalMs) : defaultKeepaliveIntervalMs(idleTimeoutSeconds);
108
+ const keepaliveIntervalMs = opts.keepaliveIntervalMs ?? defaultKeepaliveIntervalMs(idleTimeoutSeconds);
109
109
  return await connectCore({
110
110
  path: "tunnel",
111
111
  wsUrl: checkedGrant.tunnel_url,
@@ -119,7 +119,13 @@ export async function connectTunnel(grant, opts) {
119
119
  function defaultKeepaliveIntervalMs(idleTimeoutSeconds) {
120
120
  if (!Number.isFinite(idleTimeoutSeconds) || idleTimeoutSeconds <= 0)
121
121
  return 0;
122
- const idleMs = idleTimeoutSeconds * 1000;
123
- const half = Math.floor(idleMs / 2);
124
- return Math.max(500, half);
122
+ const idleMs = Math.floor(idleTimeoutSeconds * 1000);
123
+ if (idleMs <= 0)
124
+ return 0;
125
+ let interval = Math.floor(idleMs / 2);
126
+ if (interval < 500)
127
+ interval = 500;
128
+ if (interval >= idleMs)
129
+ interval = Math.floor(idleMs / 2);
130
+ return interval;
125
131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/flowersec-core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Flowersec core TypeScript library (browser-friendly E2EE + multiplexing over WebSocket).",
5
5
  "license": "MIT",
6
6
  "repository": {