@floegence/flowersec-core 0.2.1 → 0.3.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.
@@ -9,7 +9,7 @@ export declare class WsFactoryRequiredError extends Error {
9
9
  }
10
10
  export declare function createWebSocket(url: string, origin: string, wsFactory: ((url: string, origin: string) => WebSocketLike) | undefined): WebSocketLike;
11
11
  export declare function classifyConnectError(err: unknown): "websocket_error" | "websocket_closed" | "timeout" | "canceled";
12
- export declare function classifyHandshakeError(err: unknown): "auth_tag_mismatch" | "handshake_failed" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
12
+ export declare function classifyHandshakeError(err: unknown): "auth_tag_mismatch" | "handshake_failed" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
13
13
  export declare function withAbortAndTimeout<T>(p: Promise<T>, opts: Readonly<{
14
14
  signal?: AbortSignal;
15
15
  timeoutMs?: number;
@@ -1,3 +1,3 @@
1
- export declare const tunnelAttachCloseReasons: readonly ["too_many_connections", "expected_attach", "invalid_attach", "invalid_token", "channel_mismatch", "role_mismatch", "token_replay", "replace_rate_limited", "attach_failed"];
1
+ export declare const tunnelAttachCloseReasons: readonly ["too_many_connections", "expected_attach", "invalid_attach", "invalid_token", "channel_mismatch", "init_exp_mismatch", "idle_timeout_mismatch", "role_mismatch", "token_replay", "replace_rate_limited", "attach_failed"];
2
2
  export type TunnelAttachCloseReason = (typeof tunnelAttachCloseReasons)[number];
3
3
  export declare function isTunnelAttachCloseReason(v: string | undefined): v is TunnelAttachCloseReason;
@@ -4,6 +4,8 @@ export const tunnelAttachCloseReasons = [
4
4
  "invalid_attach",
5
5
  "invalid_token",
6
6
  "channel_mismatch",
7
+ "init_exp_mismatch",
8
+ "idle_timeout_mismatch",
7
9
  "role_mismatch",
8
10
  "token_replay",
9
11
  "replace_rate_limited",
@@ -1,4 +1,4 @@
1
- export type E2EEHandshakeErrorCode = "auth_tag_mismatch" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew";
1
+ export type E2EEHandshakeErrorCode = "auth_tag_mismatch" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew";
2
2
  export declare class E2EEHandshakeError extends Error {
3
3
  readonly code: E2EEHandshakeErrorCode;
4
4
  constructor(code: E2EEHandshakeErrorCode, message: string);
@@ -18,7 +18,7 @@ export type HandshakeClientOptions = Readonly<{
18
18
  maxBufferedBytes?: number;
19
19
  /** Optional AbortSignal to cancel the handshake. */
20
20
  signal?: AbortSignal;
21
- /** Optional total handshake timeout in milliseconds (0 disables). */
21
+ /** Optional total handshake timeout in milliseconds (>= 0; 0 disables). */
22
22
  timeoutMs?: number;
23
23
  }>;
24
24
  export type HandshakeServerOptions = Readonly<{
@@ -42,7 +42,7 @@ export type HandshakeServerOptions = Readonly<{
42
42
  maxBufferedBytes?: number;
43
43
  /** Optional AbortSignal to cancel the handshake. */
44
44
  signal?: AbortSignal;
45
- /** Optional total handshake timeout in milliseconds (0 disables). */
45
+ /** Optional total handshake timeout in milliseconds (>= 0; 0 disables). */
46
46
  timeoutMs?: number;
47
47
  }>;
48
48
  export declare function clientHandshake(transport: BinaryTransport, opts: HandshakeClientOptions): Promise<SecureChannel>;
@@ -13,7 +13,9 @@ import { TimeoutError, throwIfAborted } from "../utils/errors.js";
13
13
  const te = new TextEncoder();
14
14
  const td = new TextDecoder();
15
15
  function handshakeDeadlineMs(timeoutMs) {
16
- const ms = Math.max(0, timeoutMs ?? 10_000);
16
+ const ms = timeoutMs ?? 10_000;
17
+ if (!Number.isFinite(ms) || ms < 0)
18
+ throw new Error("timeoutMs must be >= 0");
17
19
  if (ms <= 0)
18
20
  return null;
19
21
  return Date.now() + ms;
@@ -172,8 +174,15 @@ export class ServerHandshakeCache {
172
174
  // Maximum number of cached entries.
173
175
  maxEntries;
174
176
  constructor(opts = {}) {
175
- this.ttlMs = Math.max(0, opts.ttlMs ?? 60_000);
176
- this.maxEntries = Math.max(0, opts.maxEntries ?? 4096);
177
+ const ttlMs = opts.ttlMs ?? 60_000;
178
+ if (!Number.isFinite(ttlMs) || ttlMs < 0)
179
+ throw new Error("ttlMs must be >= 0");
180
+ const maxEntries = opts.maxEntries ?? 4096;
181
+ if (!Number.isFinite(maxEntries) || maxEntries < 0 || !Number.isInteger(maxEntries)) {
182
+ throw new Error("maxEntries must be an integer >= 0");
183
+ }
184
+ this.ttlMs = ttlMs;
185
+ this.maxEntries = maxEntries;
177
186
  }
178
187
  cleanup(nowMs) {
179
188
  if (this.ttlMs <= 0)
@@ -233,7 +242,7 @@ export async function serverHandshake(transport, cache, opts) {
233
242
  throw new Error("bad channel_id");
234
243
  const suite = init.suite;
235
244
  if (suite !== opts.suite)
236
- throw new Error("bad suite");
245
+ throw new E2EEHandshakeError("invalid_suite", "bad suite");
237
246
  const clientPub = base64urlDecode(init.client_eph_pub_b64u);
238
247
  const nonceC = base64urlDecode(init.nonce_c_b64u);
239
248
  if (nonceC.length !== 32)
@@ -2,9 +2,9 @@ import type { ClientPath } from "../client.js";
2
2
  export type ConnectResult = "ok" | "fail";
3
3
  export type ConnectReason = "websocket_error" | "websocket_closed" | "timeout" | "canceled";
4
4
  export type AttachResult = "ok" | "fail";
5
- export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "token_replay" | "replace_rate_limited" | "attach_failed";
5
+ export type AttachReason = "send_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "role_mismatch" | "init_exp_mismatch" | "idle_timeout_mismatch" | "token_replay" | "replace_rate_limited" | "attach_failed";
6
6
  export type HandshakeResult = "ok" | "fail";
7
- export type HandshakeReason = "auth_tag_mismatch" | "handshake_failed" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
7
+ export type HandshakeReason = "auth_tag_mismatch" | "handshake_failed" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
8
8
  export type WsCloseKind = "local" | "peer_or_error";
9
9
  export type WsErrorReason = "error" | "recv_buffer_exceeded" | "unexpected_text_frame" | "unexpected_message_type";
10
10
  export type RpcCallResult = "ok" | "rpc_error" | "handler_not_found" | "transport_error" | "canceled";
@@ -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" | "random_failed" | "upgrade_failed" | "dial_failed" | "attach_failed" | "too_many_connections" | "expected_attach" | "invalid_attach" | "invalid_token" | "channel_mismatch" | "token_replay" | "replace_rate_limited" | "handshake_failed" | "ping_failed" | "mux_failed" | "accept_stream_failed" | "open_stream_failed" | "stream_hello_failed" | "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" | "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" | "replace_rate_limited" | "handshake_failed" | "ping_failed" | "mux_failed" | "accept_stream_failed" | "open_stream_failed" | "stream_hello_failed" | "not_connected";
10
10
  export declare class FlowersecError extends Error {
11
11
  readonly code: FlowersecErrorCode;
12
12
  readonly stage: FlowersecStage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/flowersec-core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Flowersec core TypeScript library (browser-friendly E2EE + multiplexing over WebSocket).",
5
5
  "license": "MIT",
6
6
  "repository": {