heliumts 0.5.1 → 0.5.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rpcClient.d.ts","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA2B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGhF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAUzD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAieD;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAE7H;AA6BD;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAYjC"}
1
+ {"version":3,"file":"rpcClient.d.ts","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA2B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGhF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAUzD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAmgBD;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAE7H;AA6BD;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAYjC"}
@@ -76,12 +76,18 @@ async function flushBatch() {
76
76
  async function sendBatchHttp(batch) {
77
77
  const requests = batch.map((b) => b.req);
78
78
  const encoded = msgpackEncode(requests);
79
+ // Fetch a fresh token for HTTP RPC authentication
80
+ const token = await fetchFreshToken();
81
+ const headers = {
82
+ "Content-Type": "application/msgpack",
83
+ Accept: "application/msgpack",
84
+ };
85
+ if (token) {
86
+ headers["X-Helium-Token"] = token;
87
+ }
79
88
  const response = await fetch("/__helium__/rpc", {
80
89
  method: "POST",
81
- headers: {
82
- "Content-Type": "application/msgpack",
83
- Accept: "application/msgpack",
84
- },
90
+ headers,
85
91
  body: encoded,
86
92
  });
87
93
  if (!response.ok) {
@@ -241,7 +247,12 @@ function nextId() {
241
247
  }
242
248
  async function fetchFreshToken() {
243
249
  try {
244
- const response = await fetch("/__helium__/refresh-token");
250
+ const response = await fetch("/__helium__/refresh-token", {
251
+ method: "POST",
252
+ headers: {
253
+ "X-Requested-With": "HeliumRPC",
254
+ },
255
+ });
245
256
  if (!response.ok) {
246
257
  console.warn("Failed to fetch fresh token:", response.status);
247
258
  return undefined;
@@ -260,8 +271,9 @@ async function createSocket() {
260
271
  // Use the same protocol, hostname and port as the current page
261
272
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
262
273
  const host = window.location.host; // includes hostname and port
263
- const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : ""}`;
264
- const ws = new WebSocket(url);
274
+ const url = `${protocol}//${host}/rpc`;
275
+ // Security: pass token via Sec-WebSocket-Protocol header instead of query string
276
+ const ws = token ? new WebSocket(url, [token]) : new WebSocket(url);
265
277
  ws.binaryType = "arraybuffer";
266
278
  ws.onmessage = async (event) => {
267
279
  let data = new Uint8Array(event.data);
@@ -274,8 +286,29 @@ async function createSocket() {
274
286
  const stream = new Response(data).body;
275
287
  if (stream) {
276
288
  const decompressed = stream.pipeThrough(ds);
277
- const buffer = await new Response(decompressed).arrayBuffer();
278
- data = new Uint8Array(buffer);
289
+ const reader = decompressed.getReader();
290
+ const chunks = [];
291
+ let totalSize = 0;
292
+ const MAX_DECOMPRESSED_SIZE = 10 * 1024 * 1024; // 10 MB
293
+ while (true) {
294
+ const { value, done } = await reader.read();
295
+ if (done)
296
+ break;
297
+ totalSize += value.length;
298
+ if (totalSize > MAX_DECOMPRESSED_SIZE) {
299
+ reader.cancel();
300
+ console.error("Decompressed message exceeds size limit");
301
+ return;
302
+ }
303
+ chunks.push(value);
304
+ }
305
+ const combined = new Uint8Array(totalSize);
306
+ let offset = 0;
307
+ for (const chunk of chunks) {
308
+ combined.set(chunk, offset);
309
+ offset += chunk.length;
310
+ }
311
+ data = combined;
279
312
  }
280
313
  }
281
314
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rpcClient.js","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { RpcError } from \"./RpcError.js\";\n\nexport type RpcResult<T> = {\n data: T;\n stats: RpcStats;\n};\n\n/**\n * Transport mode for RPC calls.\n * - \"http\": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)\n * - \"websocket\": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)\n * - \"auto\": Automatically selects based on connection type\n */\nexport type RpcTransport = \"http\" | \"websocket\" | \"auto\";\n\n// Build-time configuration injected by Vite plugin from helium.config.js\ndeclare const __HELIUM_RPC_TRANSPORT__: RpcTransport;\ndeclare const __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__: boolean;\n\n// Read build-time config with fallback defaults\nconst configuredTransport: RpcTransport = typeof __HELIUM_RPC_TRANSPORT__ !== \"undefined\" ? __HELIUM_RPC_TRANSPORT__ : \"websocket\";\nconst configuredAutoHttpOnMobile: boolean = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== \"undefined\" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;\n\n/**\n * Get the configured RPC transport mode (from helium.config.js).\n */\nexport function getRpcTransport(): RpcTransport {\n return configuredTransport;\n}\n\n/**\n * Check if auto HTTP on mobile is enabled (from helium.config.js).\n */\nexport function isAutoHttpOnMobileEnabled(): boolean {\n return configuredAutoHttpOnMobile;\n}\n\n// Detect if we should prefer HTTP transport (mobile/slow networks)\nfunction shouldUseHttpTransport(): boolean {\n if (configuredTransport === \"http\") {\n return true;\n }\n if (configuredTransport === \"websocket\") {\n return false;\n }\n\n // Auto mode: check if mobile HTTP optimization is enabled\n if (!configuredAutoHttpOnMobile) {\n return false;\n }\n\n // Prefer HTTP on mobile/slow connections\n if (typeof navigator !== \"undefined\") {\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n // Use HTTP for cellular connections or slow effective types\n const slowTypes = [\"slow-2g\", \"2g\", \"3g\"];\n if (conn.type === \"cellular\" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {\n return true;\n }\n }\n }\n\n return false;\n}\n\ninterface NetworkInformation {\n type?: string;\n effectiveType?: string;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n connection?: NetworkInformation;\n}\n\n// ============================================================================\n// Batching Logic\n// ============================================================================\n\ntype PendingRequest = {\n req: RpcRequest;\n resolve: (value: RpcResult<any>) => void;\n reject: (reason?: any) => void;\n};\n\nlet pendingBatch: PendingRequest[] = [];\nlet isBatchScheduled = false;\n\nfunction scheduleBatch() {\n if (isBatchScheduled) {\n return;\n }\n isBatchScheduled = true;\n queueMicrotask(() => {\n isBatchScheduled = false;\n flushBatch();\n });\n}\n\nasync function flushBatch() {\n const batch = pendingBatch;\n pendingBatch = [];\n\n if (batch.length === 0) {\n return;\n }\n\n try {\n if (shouldUseHttpTransport()) {\n await sendBatchHttp(batch);\n } else {\n await sendBatchWebSocket(batch);\n }\n } catch (err) {\n // Transport error, fail all\n for (const item of batch) {\n item.reject(err);\n }\n }\n}\n\nasync function sendBatchHttp(batch: PendingRequest[]) {\n const requests = batch.map((b) => b.req);\n const encoded = msgpackEncode(requests);\n\n const response = await fetch(\"/__helium__/rpc\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/msgpack\",\n Accept: \"application/msgpack\",\n },\n body: encoded as unknown as BodyInit,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP RPC failed: ${response.status}`);\n }\n\n const responseBuffer = await response.arrayBuffer();\n const msg = msgpackDecode(new Uint8Array(responseBuffer)) as RpcResponse | RpcResponse[];\n\n const responses = Array.isArray(msg) ? msg : [msg];\n const responseMap = new Map(responses.map((r) => [r.id, r]));\n\n for (const item of batch) {\n const res = responseMap.get(item.req.id);\n if (res) {\n if (res.ok) {\n item.resolve({ data: res.result, stats: res.stats });\n } else {\n item.reject(new RpcError(res.error, res.stats));\n }\n } else {\n item.reject(new RpcError(\"No response for request\"));\n }\n }\n}\n\nasync function sendBatchWebSocket(batch: PendingRequest[]) {\n const ws = await ensureSocketReady();\n const requests = batch.map((b) => b.req);\n\n // Register pending promises with timeout safeguards\n batch.forEach((item) => {\n trackPending(item.req.id, (v: unknown) => item.resolve(v as RpcResult<any>), item.reject);\n });\n\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(requests);\n ws.send(encoded);\n } catch (err) {\n batch.forEach((item) => {\n removePending(item.req.id);\n item.reject(err);\n });\n }\n}\n\n// ============================================================================\n// WebSocket Transport (original implementation)\n// ============================================================================\n\nlet socket: WebSocket | null = null;\nlet connectionPromise: Promise<WebSocket> | null = null;\n\nconst pending = new Map<string | number, { resolve: (v: unknown) => void; reject: (e: unknown) => void }>();\nconst pendingTimeouts = new Map<string | number, ReturnType<typeof setTimeout>>();\n\n// ── Connection resilience constants ──────────────────────────────────────────\n\n/** How long (ms) the page must be hidden before we consider the WebSocket stale. */\nconst STALE_THRESHOLD_MS = 15_000;\n\n/** Max time (ms) to wait for a response before timing out a request. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\n/** Number of automatic retries on retriable connection errors. */\nconst MAX_RETRIES = 3;\n\n/** Base delay (ms) for exponential backoff between retries (doubles each attempt). */\nconst RETRY_BASE_DELAY_MS = 500;\n\n/** Maximum delay (ms) cap for backoff to avoid excessively long waits. */\nconst RETRY_MAX_DELAY_MS = 5_000;\n\n/** Timestamp when the page was last hidden (for visibility-change detection). */\nlet lastHiddenTimestamp: number | null = null;\n\n// ── Pending-request helpers ──────────────────────────────────────────────────\n\n/**\n * Register a pending request with an automatic timeout safeguard.\n * If no response arrives within REQUEST_TIMEOUT_MS the promise is rejected\n * so the caller's retry logic can kick in.\n */\nfunction trackPending(id: string | number, resolve: (v: unknown) => void, reject: (e: unknown) => void): void {\n pending.set(id, { resolve, reject });\n const timer = setTimeout(() => {\n const entry = pending.get(id);\n if (entry) {\n pending.delete(id);\n pendingTimeouts.delete(id);\n entry.reject(new RpcError(\"Request timed out\"));\n }\n }, REQUEST_TIMEOUT_MS);\n pendingTimeouts.set(id, timer);\n}\n\n/**\n * Remove a pending request and clear its timeout.\n * Returns the entry so the caller can resolve/reject it.\n */\nfunction removePending(id: string | number): { resolve: (v: unknown) => void; reject: (e: unknown) => void } | undefined {\n const entry = pending.get(id);\n if (!entry) {\n return undefined;\n }\n pending.delete(id);\n const timer = pendingTimeouts.get(id);\n if (timer) {\n clearTimeout(timer);\n pendingTimeouts.delete(id);\n }\n return entry;\n}\n\n/** Reject every in-flight request (e.g. when the socket closes unexpectedly). */\nfunction rejectAllPending(reason: Error): void {\n for (const timer of pendingTimeouts.values()) {\n clearTimeout(timer);\n }\n pendingTimeouts.clear();\n const entries = [...pending.entries()];\n pending.clear();\n for (const [, entry] of entries) {\n entry.reject(reason);\n }\n}\n\n// ── Reconnection helpers ─────────────────────────────────────────────────────\n\n/**\n * Force-close the current WebSocket so the next call creates a fresh\n * connection (which fetches a brand-new token).\n */\nfunction forceReconnect(): void {\n const oldSocket = socket;\n socket = null;\n connectionPromise = null;\n\n if (oldSocket) {\n // Detach handlers to avoid double-rejecting pending from the close event\n oldSocket.onclose = null;\n oldSocket.onerror = null;\n oldSocket.onmessage = null;\n oldSocket.close();\n }\n\n // Reject all in-flight requests – callers with retry logic will resend\n rejectAllPending(new Error(\"Connection reset\"));\n}\n\n/** Determine whether an error warrants an automatic retry. */\nfunction isRetriableError(err: unknown): boolean {\n // Network / connection errors are always retriable\n if (err instanceof Error && !(err instanceof RpcError)) {\n return true;\n }\n // Timed-out requests are retriable (socket may have died silently)\n if (err instanceof RpcError && err.message === \"Request timed out\") {\n return true;\n }\n return false;\n}\n\n// Clean up WebSocket connection on HMR (Hot Module Replacement)\nif (import.meta.hot) {\n import.meta.hot.dispose(() => {\n if (socket) {\n socket.onclose = null;\n socket.close();\n socket = null;\n connectionPromise = null;\n }\n rejectAllPending(new Error(\"Module reloaded\"));\n });\n}\n\nlet msgId = 0;\nfunction nextId() {\n return msgId++;\n}\n\nasync function fetchFreshToken(): Promise<string | undefined> {\n try {\n const response = await fetch(\"/__helium__/refresh-token\");\n if (!response.ok) {\n console.warn(\"Failed to fetch fresh token:\", response.status);\n return undefined;\n }\n const data = await response.json();\n return data.token;\n } catch (error) {\n console.warn(\"Error fetching fresh token:\", error);\n return undefined;\n }\n}\n\nasync function createSocket(): Promise<WebSocket> {\n // Fetch a fresh token before creating the WebSocket connection\n const token = await fetchFreshToken();\n\n // Use the same protocol, hostname and port as the current page\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host; // includes hostname and port\n const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : \"\"}`;\n const ws = new WebSocket(url);\n ws.binaryType = \"arraybuffer\";\n\n ws.onmessage = async (event) => {\n let data = new Uint8Array(event.data as ArrayBuffer);\n\n // Check for Gzip header (0x1f 0x8b) to detect compressed messages\n if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {\n try {\n // Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)\n if (typeof DecompressionStream !== \"undefined\") {\n const ds = new DecompressionStream(\"gzip\");\n const stream = new Response(data).body;\n if (stream) {\n const decompressed = stream.pipeThrough(ds);\n const buffer = await new Response(decompressed).arrayBuffer();\n data = new Uint8Array(buffer);\n }\n }\n } catch (err) {\n console.error(\"Failed to decompress WebSocket message:\", err);\n return;\n }\n }\n\n // Always expect binary MessagePack\n const msg = msgpackDecode(data) as RpcResponse | RpcResponse[];\n\n const handleResponse = (res: RpcResponse) => {\n const entry = removePending(res.id);\n if (!entry) {\n return;\n }\n if (res.ok) {\n entry.resolve({ data: res.result, stats: res.stats });\n } else {\n entry.reject(new RpcError(res.error, res.stats));\n }\n };\n\n if (Array.isArray(msg)) {\n msg.forEach(handleResponse);\n } else {\n handleResponse(msg);\n }\n };\n\n ws.onerror = () => {\n // WebSocket errors are always followed by a close event.\n // The close handler takes care of rejecting pending promises.\n };\n\n ws.onclose = () => {\n if (socket === ws) {\n socket = null;\n connectionPromise = null;\n // Reject every in-flight request so callers can retry\n rejectAllPending(new Error(\"WebSocket connection closed\"));\n }\n };\n\n return ws;\n}\n\nasync function ensureSocketReady(): Promise<WebSocket> {\n // If we have an open socket, return it immediately\n if (socket && socket.readyState === WebSocket.OPEN) {\n return socket;\n }\n\n // If we have a connection in progress, reuse that promise\n if (connectionPromise) {\n return connectionPromise;\n }\n\n // If we have a socket that's connecting, wait for it\n if (socket && socket.readyState === WebSocket.CONNECTING) {\n connectionPromise = new Promise((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n return connectionPromise;\n }\n\n // Create a new socket and connection promise\n connectionPromise = (async () => {\n socket = await createSocket();\n return new Promise<WebSocket>((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n })();\n\n return connectionPromise;\n}\n\nasync function rpcCallWebSocket<TResult, TArgs>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n // Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)\n if (socket && socket.readyState === WebSocket.OPEN) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n const encoded = msgpackEncode(req);\n socket!.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n }\n\n const ws = await ensureSocketReady();\n const id = nextId();\n\n const req: RpcRequest = { id, method: methodId, args };\n\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(req);\n ws.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n}\n\n/**\n * Make an RPC call using the appropriate transport.\n * Automatically selects HTTP or WebSocket based on network conditions and configuration.\n *\n * Includes automatic retry logic: if a call fails due to a connection error\n * (e.g. stale WebSocket after mobile browser was backgrounded), the client\n * forces a fresh connection (with a new token) and retries once.\n */\nexport async function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n return rpcCallWithRetry<TResult, TArgs>(methodId, args);\n}\n\nasync function rpcCallWithRetry<TResult, TArgs>(methodId: string, args: TArgs | undefined, attempt = 0): Promise<RpcResult<TResult>> {\n try {\n if (shouldUseHttpTransport()) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n\n return await new Promise<RpcResult<TResult>>((resolve, reject) => {\n pendingBatch.push({ req, resolve: resolve as (value: RpcResult<TResult>) => void, reject });\n scheduleBatch();\n });\n }\n\n return await rpcCallWebSocket<TResult, TArgs>(methodId, args);\n } catch (err) {\n if (attempt < MAX_RETRIES && isRetriableError(err)) {\n // Force a fresh connection (fetches a new token)\n forceReconnect();\n // Exponential backoff with jitter: 500ms, 1000ms, 2000ms (capped at 5s)\n const baseDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** attempt, RETRY_MAX_DELAY_MS);\n const jitter = Math.random() * baseDelay * 0.3;\n await new Promise<void>((r) => setTimeout(r, baseDelay + jitter));\n return rpcCallWithRetry<TResult, TArgs>(methodId, args, attempt + 1);\n }\n throw err;\n }\n}\n\n/**\n * Pre-establishes the WebSocket connection.\n * Call this early (e.g., on page load) to avoid connection latency on first RPC call.\n * This is especially beneficial on high-latency networks like mobile LTE.\n * Note: Only effective when using WebSocket transport (not HTTP transport).\n */\nexport function preconnect(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n // Only preconnect if we're using WebSocket transport\n if (shouldUseHttpTransport()) {\n return;\n }\n // Fire and forget - establishes connection in background\n ensureSocketReady().catch(() => {\n // Silently ignore preconnect failures, will retry on actual call\n });\n}\n\n// Auto-preconnect when the module loads (browser only, WebSocket transport only)\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Use requestIdleCallback if available, otherwise setTimeout\n const schedulePreconnect = window.requestIdleCallback || ((cb: () => void) => setTimeout(cb, 1));\n schedulePreconnect(() => preconnect());\n}\n\n// ============================================================================\n// Visibility-change reconnection (critical for mobile browsers)\n// ============================================================================\n//\n// Mobile browsers freeze or kill WebSocket connections when the tab is\n// backgrounded. When the user returns the socket may *appear* open but\n// is actually stale. We detect this via the Page Visibility API and\n// proactively tear down the old connection so the next RPC call creates\n// a fresh one (with a brand-new auth token).\n// ============================================================================\n\nif (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", () => {\n if (document.hidden) {\n lastHiddenTimestamp = Date.now();\n } else {\n // Page became visible again\n if (lastHiddenTimestamp !== null) {\n const hiddenDuration = Date.now() - lastHiddenTimestamp;\n if (hiddenDuration > STALE_THRESHOLD_MS && socket) {\n forceReconnect();\n }\n lastHiddenTimestamp = null;\n }\n }\n });\n}\n"]}
1
+ {"version":3,"file":"rpcClient.js","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { RpcError } from \"./RpcError.js\";\n\nexport type RpcResult<T> = {\n data: T;\n stats: RpcStats;\n};\n\n/**\n * Transport mode for RPC calls.\n * - \"http\": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)\n * - \"websocket\": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)\n * - \"auto\": Automatically selects based on connection type\n */\nexport type RpcTransport = \"http\" | \"websocket\" | \"auto\";\n\n// Build-time configuration injected by Vite plugin from helium.config.js\ndeclare const __HELIUM_RPC_TRANSPORT__: RpcTransport;\ndeclare const __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__: boolean;\n\n// Read build-time config with fallback defaults\nconst configuredTransport: RpcTransport = typeof __HELIUM_RPC_TRANSPORT__ !== \"undefined\" ? __HELIUM_RPC_TRANSPORT__ : \"websocket\";\nconst configuredAutoHttpOnMobile: boolean = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== \"undefined\" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;\n\n/**\n * Get the configured RPC transport mode (from helium.config.js).\n */\nexport function getRpcTransport(): RpcTransport {\n return configuredTransport;\n}\n\n/**\n * Check if auto HTTP on mobile is enabled (from helium.config.js).\n */\nexport function isAutoHttpOnMobileEnabled(): boolean {\n return configuredAutoHttpOnMobile;\n}\n\n// Detect if we should prefer HTTP transport (mobile/slow networks)\nfunction shouldUseHttpTransport(): boolean {\n if (configuredTransport === \"http\") {\n return true;\n }\n if (configuredTransport === \"websocket\") {\n return false;\n }\n\n // Auto mode: check if mobile HTTP optimization is enabled\n if (!configuredAutoHttpOnMobile) {\n return false;\n }\n\n // Prefer HTTP on mobile/slow connections\n if (typeof navigator !== \"undefined\") {\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n // Use HTTP for cellular connections or slow effective types\n const slowTypes = [\"slow-2g\", \"2g\", \"3g\"];\n if (conn.type === \"cellular\" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {\n return true;\n }\n }\n }\n\n return false;\n}\n\ninterface NetworkInformation {\n type?: string;\n effectiveType?: string;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n connection?: NetworkInformation;\n}\n\n// ============================================================================\n// Batching Logic\n// ============================================================================\n\ntype PendingRequest = {\n req: RpcRequest;\n resolve: (value: RpcResult<any>) => void;\n reject: (reason?: any) => void;\n};\n\nlet pendingBatch: PendingRequest[] = [];\nlet isBatchScheduled = false;\n\nfunction scheduleBatch() {\n if (isBatchScheduled) {\n return;\n }\n isBatchScheduled = true;\n queueMicrotask(() => {\n isBatchScheduled = false;\n flushBatch();\n });\n}\n\nasync function flushBatch() {\n const batch = pendingBatch;\n pendingBatch = [];\n\n if (batch.length === 0) {\n return;\n }\n\n try {\n if (shouldUseHttpTransport()) {\n await sendBatchHttp(batch);\n } else {\n await sendBatchWebSocket(batch);\n }\n } catch (err) {\n // Transport error, fail all\n for (const item of batch) {\n item.reject(err);\n }\n }\n}\n\nasync function sendBatchHttp(batch: PendingRequest[]) {\n const requests = batch.map((b) => b.req);\n const encoded = msgpackEncode(requests);\n\n // Fetch a fresh token for HTTP RPC authentication\n const token = await fetchFreshToken();\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/msgpack\",\n Accept: \"application/msgpack\",\n };\n if (token) {\n headers[\"X-Helium-Token\"] = token;\n }\n\n const response = await fetch(\"/__helium__/rpc\", {\n method: \"POST\",\n headers,\n body: encoded as unknown as BodyInit,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP RPC failed: ${response.status}`);\n }\n\n const responseBuffer = await response.arrayBuffer();\n const msg = msgpackDecode(new Uint8Array(responseBuffer)) as RpcResponse | RpcResponse[];\n\n const responses = Array.isArray(msg) ? msg : [msg];\n const responseMap = new Map(responses.map((r) => [r.id, r]));\n\n for (const item of batch) {\n const res = responseMap.get(item.req.id);\n if (res) {\n if (res.ok) {\n item.resolve({ data: res.result, stats: res.stats });\n } else {\n item.reject(new RpcError(res.error, res.stats));\n }\n } else {\n item.reject(new RpcError(\"No response for request\"));\n }\n }\n}\n\nasync function sendBatchWebSocket(batch: PendingRequest[]) {\n const ws = await ensureSocketReady();\n const requests = batch.map((b) => b.req);\n\n // Register pending promises with timeout safeguards\n batch.forEach((item) => {\n trackPending(item.req.id, (v: unknown) => item.resolve(v as RpcResult<any>), item.reject);\n });\n\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(requests);\n ws.send(encoded);\n } catch (err) {\n batch.forEach((item) => {\n removePending(item.req.id);\n item.reject(err);\n });\n }\n}\n\n// ============================================================================\n// WebSocket Transport (original implementation)\n// ============================================================================\n\nlet socket: WebSocket | null = null;\nlet connectionPromise: Promise<WebSocket> | null = null;\n\nconst pending = new Map<string | number, { resolve: (v: unknown) => void; reject: (e: unknown) => void }>();\nconst pendingTimeouts = new Map<string | number, ReturnType<typeof setTimeout>>();\n\n// ── Connection resilience constants ──────────────────────────────────────────\n\n/** How long (ms) the page must be hidden before we consider the WebSocket stale. */\nconst STALE_THRESHOLD_MS = 15_000;\n\n/** Max time (ms) to wait for a response before timing out a request. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\n/** Number of automatic retries on retriable connection errors. */\nconst MAX_RETRIES = 3;\n\n/** Base delay (ms) for exponential backoff between retries (doubles each attempt). */\nconst RETRY_BASE_DELAY_MS = 500;\n\n/** Maximum delay (ms) cap for backoff to avoid excessively long waits. */\nconst RETRY_MAX_DELAY_MS = 5_000;\n\n/** Timestamp when the page was last hidden (for visibility-change detection). */\nlet lastHiddenTimestamp: number | null = null;\n\n// ── Pending-request helpers ──────────────────────────────────────────────────\n\n/**\n * Register a pending request with an automatic timeout safeguard.\n * If no response arrives within REQUEST_TIMEOUT_MS the promise is rejected\n * so the caller's retry logic can kick in.\n */\nfunction trackPending(id: string | number, resolve: (v: unknown) => void, reject: (e: unknown) => void): void {\n pending.set(id, { resolve, reject });\n const timer = setTimeout(() => {\n const entry = pending.get(id);\n if (entry) {\n pending.delete(id);\n pendingTimeouts.delete(id);\n entry.reject(new RpcError(\"Request timed out\"));\n }\n }, REQUEST_TIMEOUT_MS);\n pendingTimeouts.set(id, timer);\n}\n\n/**\n * Remove a pending request and clear its timeout.\n * Returns the entry so the caller can resolve/reject it.\n */\nfunction removePending(id: string | number): { resolve: (v: unknown) => void; reject: (e: unknown) => void } | undefined {\n const entry = pending.get(id);\n if (!entry) {\n return undefined;\n }\n pending.delete(id);\n const timer = pendingTimeouts.get(id);\n if (timer) {\n clearTimeout(timer);\n pendingTimeouts.delete(id);\n }\n return entry;\n}\n\n/** Reject every in-flight request (e.g. when the socket closes unexpectedly). */\nfunction rejectAllPending(reason: Error): void {\n for (const timer of pendingTimeouts.values()) {\n clearTimeout(timer);\n }\n pendingTimeouts.clear();\n const entries = [...pending.entries()];\n pending.clear();\n for (const [, entry] of entries) {\n entry.reject(reason);\n }\n}\n\n// ── Reconnection helpers ─────────────────────────────────────────────────────\n\n/**\n * Force-close the current WebSocket so the next call creates a fresh\n * connection (which fetches a brand-new token).\n */\nfunction forceReconnect(): void {\n const oldSocket = socket;\n socket = null;\n connectionPromise = null;\n\n if (oldSocket) {\n // Detach handlers to avoid double-rejecting pending from the close event\n oldSocket.onclose = null;\n oldSocket.onerror = null;\n oldSocket.onmessage = null;\n oldSocket.close();\n }\n\n // Reject all in-flight requests – callers with retry logic will resend\n rejectAllPending(new Error(\"Connection reset\"));\n}\n\n/** Determine whether an error warrants an automatic retry. */\nfunction isRetriableError(err: unknown): boolean {\n // Network / connection errors are always retriable\n if (err instanceof Error && !(err instanceof RpcError)) {\n return true;\n }\n // Timed-out requests are retriable (socket may have died silently)\n if (err instanceof RpcError && err.message === \"Request timed out\") {\n return true;\n }\n return false;\n}\n\n// Clean up WebSocket connection on HMR (Hot Module Replacement)\nif (import.meta.hot) {\n import.meta.hot.dispose(() => {\n if (socket) {\n socket.onclose = null;\n socket.close();\n socket = null;\n connectionPromise = null;\n }\n rejectAllPending(new Error(\"Module reloaded\"));\n });\n}\n\nlet msgId = 0;\nfunction nextId() {\n return msgId++;\n}\n\nasync function fetchFreshToken(): Promise<string | undefined> {\n try {\n const response = await fetch(\"/__helium__/refresh-token\", {\n method: \"POST\",\n headers: {\n \"X-Requested-With\": \"HeliumRPC\",\n },\n });\n if (!response.ok) {\n console.warn(\"Failed to fetch fresh token:\", response.status);\n return undefined;\n }\n const data = await response.json();\n return data.token;\n } catch (error) {\n console.warn(\"Error fetching fresh token:\", error);\n return undefined;\n }\n}\n\nasync function createSocket(): Promise<WebSocket> {\n // Fetch a fresh token before creating the WebSocket connection\n const token = await fetchFreshToken();\n\n // Use the same protocol, hostname and port as the current page\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host; // includes hostname and port\n const url = `${protocol}//${host}/rpc`;\n // Security: pass token via Sec-WebSocket-Protocol header instead of query string\n const ws = token ? new WebSocket(url, [token]) : new WebSocket(url);\n ws.binaryType = \"arraybuffer\";\n\n ws.onmessage = async (event) => {\n let data = new Uint8Array(event.data as ArrayBuffer);\n\n // Check for Gzip header (0x1f 0x8b) to detect compressed messages\n if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {\n try {\n // Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)\n if (typeof DecompressionStream !== \"undefined\") {\n const ds = new DecompressionStream(\"gzip\");\n const stream = new Response(data).body;\n if (stream) {\n const decompressed = stream.pipeThrough(ds);\n const reader = decompressed.getReader();\n const chunks: Uint8Array[] = [];\n let totalSize = 0;\n const MAX_DECOMPRESSED_SIZE = 10 * 1024 * 1024; // 10 MB\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n totalSize += value.length;\n if (totalSize > MAX_DECOMPRESSED_SIZE) {\n reader.cancel();\n console.error(\"Decompressed message exceeds size limit\");\n return;\n }\n chunks.push(value);\n }\n const combined = new Uint8Array(totalSize);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n data = combined;\n }\n }\n } catch (err) {\n console.error(\"Failed to decompress WebSocket message:\", err);\n return;\n }\n }\n\n // Always expect binary MessagePack\n const msg = msgpackDecode(data) as RpcResponse | RpcResponse[];\n\n const handleResponse = (res: RpcResponse) => {\n const entry = removePending(res.id);\n if (!entry) {\n return;\n }\n if (res.ok) {\n entry.resolve({ data: res.result, stats: res.stats });\n } else {\n entry.reject(new RpcError(res.error, res.stats));\n }\n };\n\n if (Array.isArray(msg)) {\n msg.forEach(handleResponse);\n } else {\n handleResponse(msg);\n }\n };\n\n ws.onerror = () => {\n // WebSocket errors are always followed by a close event.\n // The close handler takes care of rejecting pending promises.\n };\n\n ws.onclose = () => {\n if (socket === ws) {\n socket = null;\n connectionPromise = null;\n // Reject every in-flight request so callers can retry\n rejectAllPending(new Error(\"WebSocket connection closed\"));\n }\n };\n\n return ws;\n}\n\nasync function ensureSocketReady(): Promise<WebSocket> {\n // If we have an open socket, return it immediately\n if (socket && socket.readyState === WebSocket.OPEN) {\n return socket;\n }\n\n // If we have a connection in progress, reuse that promise\n if (connectionPromise) {\n return connectionPromise;\n }\n\n // If we have a socket that's connecting, wait for it\n if (socket && socket.readyState === WebSocket.CONNECTING) {\n connectionPromise = new Promise((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n return connectionPromise;\n }\n\n // Create a new socket and connection promise\n connectionPromise = (async () => {\n socket = await createSocket();\n return new Promise<WebSocket>((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n })();\n\n return connectionPromise;\n}\n\nasync function rpcCallWebSocket<TResult, TArgs>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n // Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)\n if (socket && socket.readyState === WebSocket.OPEN) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n const encoded = msgpackEncode(req);\n socket!.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n }\n\n const ws = await ensureSocketReady();\n const id = nextId();\n\n const req: RpcRequest = { id, method: methodId, args };\n\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(req);\n ws.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n}\n\n/**\n * Make an RPC call using the appropriate transport.\n * Automatically selects HTTP or WebSocket based on network conditions and configuration.\n *\n * Includes automatic retry logic: if a call fails due to a connection error\n * (e.g. stale WebSocket after mobile browser was backgrounded), the client\n * forces a fresh connection (with a new token) and retries once.\n */\nexport async function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n return rpcCallWithRetry<TResult, TArgs>(methodId, args);\n}\n\nasync function rpcCallWithRetry<TResult, TArgs>(methodId: string, args: TArgs | undefined, attempt = 0): Promise<RpcResult<TResult>> {\n try {\n if (shouldUseHttpTransport()) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n\n return await new Promise<RpcResult<TResult>>((resolve, reject) => {\n pendingBatch.push({ req, resolve: resolve as (value: RpcResult<TResult>) => void, reject });\n scheduleBatch();\n });\n }\n\n return await rpcCallWebSocket<TResult, TArgs>(methodId, args);\n } catch (err) {\n if (attempt < MAX_RETRIES && isRetriableError(err)) {\n // Force a fresh connection (fetches a new token)\n forceReconnect();\n // Exponential backoff with jitter: 500ms, 1000ms, 2000ms (capped at 5s)\n const baseDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** attempt, RETRY_MAX_DELAY_MS);\n const jitter = Math.random() * baseDelay * 0.3;\n await new Promise<void>((r) => setTimeout(r, baseDelay + jitter));\n return rpcCallWithRetry<TResult, TArgs>(methodId, args, attempt + 1);\n }\n throw err;\n }\n}\n\n/**\n * Pre-establishes the WebSocket connection.\n * Call this early (e.g., on page load) to avoid connection latency on first RPC call.\n * This is especially beneficial on high-latency networks like mobile LTE.\n * Note: Only effective when using WebSocket transport (not HTTP transport).\n */\nexport function preconnect(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n // Only preconnect if we're using WebSocket transport\n if (shouldUseHttpTransport()) {\n return;\n }\n // Fire and forget - establishes connection in background\n ensureSocketReady().catch(() => {\n // Silently ignore preconnect failures, will retry on actual call\n });\n}\n\n// Auto-preconnect when the module loads (browser only, WebSocket transport only)\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Use requestIdleCallback if available, otherwise setTimeout\n const schedulePreconnect = window.requestIdleCallback || ((cb: () => void) => setTimeout(cb, 1));\n schedulePreconnect(() => preconnect());\n}\n\n// ============================================================================\n// Visibility-change reconnection (critical for mobile browsers)\n// ============================================================================\n//\n// Mobile browsers freeze or kill WebSocket connections when the tab is\n// backgrounded. When the user returns the socket may *appear* open but\n// is actually stale. We detect this via the Page Visibility API and\n// proactively tear down the old connection so the next RPC call creates\n// a fresh one (with a brand-new auth token).\n// ============================================================================\n\nif (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", () => {\n if (document.hidden) {\n lastHiddenTimestamp = Date.now();\n } else {\n // Page became visible again\n if (lastHiddenTimestamp !== null) {\n const hiddenDuration = Date.now() - lastHiddenTimestamp;\n if (hiddenDuration > STALE_THRESHOLD_MS && socket) {\n forceReconnect();\n }\n lastHiddenTimestamp = null;\n }\n }\n });\n}\n"]}
@@ -56,6 +56,33 @@ export interface HeliumRpcSecurityConfig {
56
56
  */
57
57
  tokenValidityMs?: number;
58
58
  }
59
+ /**
60
+ * Security configuration for HTTP responses.
61
+ */
62
+ export interface HeliumSecurityConfig {
63
+ /**
64
+ * Content-Security-Policy header value.
65
+ * Set to a CSP string to enable, or omit to skip CSP.
66
+ *
67
+ * @default undefined (no CSP header)
68
+ */
69
+ contentSecurityPolicy?: string;
70
+ /**
71
+ * Enable Strict-Transport-Security header.
72
+ * Set to false to disable HSTS.
73
+ *
74
+ * @default true
75
+ */
76
+ hsts?: boolean;
77
+ /**
78
+ * Allowed CORS origins.
79
+ * Set to ["*"] to allow all origins, or provide specific origins.
80
+ * Empty array or omit to restrict to same-origin only (default, most secure).
81
+ *
82
+ * @default [] (same-origin only)
83
+ */
84
+ corsOrigins?: string[];
85
+ }
59
86
  /**
60
87
  * Helium framework configuration.
61
88
  *
@@ -97,6 +124,11 @@ export interface HeliumConfig {
97
124
  * @default 0
98
125
  */
99
126
  trustProxyDepth?: number;
127
+ /**
128
+ * HTTP response security configuration.
129
+ * Controls CORS, CSP, HSTS, and other security headers.
130
+ */
131
+ security?: HeliumSecurityConfig;
100
132
  /**
101
133
  * RPC transport configuration.
102
134
  *
@@ -149,6 +181,27 @@ export interface HeliumConfig {
149
181
  * to protect your RPC endpoints from abuse.
150
182
  */
151
183
  security?: HeliumRpcSecurityConfig;
184
+ /**
185
+ * Maximum HTTP request body size in bytes.
186
+ * Requests exceeding this limit receive a 413 status.
187
+ *
188
+ * @default 1048576 (1 MB)
189
+ */
190
+ maxBodySize?: number;
191
+ /**
192
+ * Maximum number of RPC calls in a single batch request.
193
+ * Batches exceeding this limit are rejected.
194
+ *
195
+ * @default 20
196
+ */
197
+ maxBatchSize?: number;
198
+ /**
199
+ * Maximum WebSocket message payload size in bytes.
200
+ * Messages exceeding this limit cause the connection to be closed.
201
+ *
202
+ * @default 1048576 (1 MB)
203
+ */
204
+ maxWsPayload?: number;
152
205
  };
153
206
  }
154
207
  /**
@@ -190,6 +243,9 @@ export declare function getCompressionConfig(config?: HeliumConfig): Required<He
190
243
  export declare function getRpcConfig(config?: HeliumConfig): {
191
244
  compression: Required<HeliumCompressionConfig>;
192
245
  security: Required<HeliumRpcSecurityConfig>;
246
+ maxBodySize: number;
247
+ maxBatchSize: number;
248
+ maxWsPayload: number;
193
249
  };
194
250
  /**
195
251
  * Client-side RPC transport configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;OAKG;IACH,GAAG,CAAC,EAAE;QACF;;;;;;;;;;;;;;;;;;WAkBG;QACH,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;QAE1C;;;;;;;;;WASG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAE3B;;;;;WAKG;QACH,WAAW,CAAC,EAAE,uBAAuB,CAAC;QAEtC;;;;;WAKG;QACH,QAAQ,CAAC,EAAE,uBAAuB,CAAC;KACtC,CAAC;CACL;AAgBD;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuCpF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,YAAiB,GAAG,MAAM,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,YAAiB,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CASjG;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,YAAiB,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAOjG;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,YAAiB;;;EAKrD;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACrC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IACzC,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,YAAiB,GAAG,wBAAwB,CAKtF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,SAE/B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAEhC;;;;;OAKG;IACH,GAAG,CAAC,EAAE;QACF;;;;;;;;;;;;;;;;;;WAkBG;QACH,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;QAE1C;;;;;;;;;WASG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAE3B;;;;;WAKG;QACH,WAAW,CAAC,EAAE,uBAAuB,CAAC;QAEtC;;;;;WAKG;QACH,QAAQ,CAAC,EAAE,uBAAuB,CAAC;QAEnC;;;;;WAKG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB;;;;;WAKG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB;;;;;WAKG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACL;AAgBD;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuCpF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,YAAiB,GAAG,MAAM,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,YAAiB,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CASjG;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,YAAiB,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAOjG;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,YAAiB;;;;;;EAQrD;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACrC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IACzC,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE,YAAiB,GAAG,wBAAwB,CAKtF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,SAE/B"}
@@ -104,6 +104,9 @@ export function getRpcConfig(config = {}) {
104
104
  return {
105
105
  compression: getCompressionConfig(config),
106
106
  security: getRpcSecurityConfig(config),
107
+ maxBodySize: config.rpc?.maxBodySize ?? 1048576,
108
+ maxBatchSize: config.rpc?.maxBatchSize ?? 20,
109
+ maxWsPayload: config.rpc?.maxWsPayload ?? 1048576,
107
110
  };
108
111
  }
109
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\n\n/**\n * WebSocket per-message compression configuration.\n * Uses the permessage-deflate extension to compress messages on the wire.\n */\nexport interface HeliumCompressionConfig {\n /**\n * Enable WebSocket per-message compression (permessage-deflate extension).\n * When enabled, messages are compressed before sending to reduce bandwidth usage.\n *\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Minimum message size in bytes to apply compression.\n * Messages smaller than this threshold will not be compressed to avoid overhead.\n * Only applies when compression is enabled.\n *\n * @default 1024 (1KB)\n */\n threshold?: number;\n}\n\n/**\n * RPC security and rate limiting configuration.\n * Controls WebSocket connection limits, message rate limits, and token-based authentication.\n */\nexport interface HeliumRpcSecurityConfig {\n /**\n * Maximum number of concurrent WebSocket connections allowed per IP address.\n * Helps prevent a single client from exhausting connection resources.\n * Set to 0 to disable this limit.\n *\n * @default 10\n */\n maxConnectionsPerIP?: number;\n\n /**\n * Maximum number of RPC messages allowed per connection within the time window.\n * Helps prevent abuse by limiting message throughput per connection.\n * Set to 0 to disable rate limiting.\n *\n * @default 100\n */\n maxMessagesPerWindow?: number;\n\n /**\n * Time window in milliseconds for rate limiting.\n * Rate limits reset after this duration.\n *\n * @default 60000 (1 minute)\n */\n rateLimitWindowMs?: number;\n\n /**\n * WebSocket connection token validity duration in milliseconds.\n * Tokens are generated server-side and must be used within this timeframe.\n * Shorter durations improve security but may cause issues with slow networks.\n *\n * @default 30000 (30 seconds)\n */\n tokenValidityMs?: number;\n}\n\n/**\n * Helium framework configuration.\n *\n * Configure your Helium application behavior including RPC transport settings,\n * compression, security, and proxy configuration for production deployments.\n */\nexport interface HeliumConfig {\n /**\n * Number of proxy levels to trust when extracting client IP addresses.\n *\n * This setting is crucial for deployments behind reverse proxies, load balancers,\n * or CDNs (like Vercel, Cloudflare, AWS ALB, etc.). It determines how the framework\n * extracts the real client IP from headers like X-Forwarded-For.\n *\n * **How it works:**\n * When behind proxies, the X-Forwarded-For header contains a chain of IPs:\n * `X-Forwarded-For: <client-ip>, <proxy1-ip>, <proxy2-ip>`\n *\n * This setting tells Helium how many proxy IPs to skip from the right to find the real client IP.\n *\n * **Values:**\n * - `0`: Don't trust any proxies, use direct connection IP (default, most secure)\n * - `1`: Trust 1 proxy level (recommended for most platforms: Vercel, Netlify, Railway)\n * - `2+`: Trust multiple proxy levels (for complex setups like Cloudflare → Load Balancer → Your Server)\n *\n * **Common configurations:**\n * - Local development: `0`\n * - Vercel/Netlify/Railway: `1`\n * - Cloudflare → Your server: `1` or `2`\n * - AWS ALB → EC2: `1`\n * - Nginx → Node.js: `1`\n * - Cloudflare → AWS ALB → EC2: `2`\n *\n * **Security note:** Setting this too high can allow IP spoofing. Only trust as many\n * proxy levels as you actually have in your infrastructure.\n *\n * This setting applies to both HTTP requests and WebSocket connections.\n *\n * @default 0\n */\n trustProxyDepth?: number;\n\n /**\n * RPC transport configuration.\n *\n * Configure the WebSocket-based RPC layer including compression\n * and security settings.\n */\n rpc?: {\n /**\n * Client-side transport mode for RPC calls.\n *\n * - `\"websocket\"` (default): Uses persistent WebSocket connection\n * - ✅ Lower latency for subsequent calls (connection reuse)\n * - ✅ Real-time bidirectional communication ready\n * - ⚠️ Higher initial connection overhead\n *\n * - `\"http\"`: Uses HTTP POST requests for each RPC call\n * - ✅ Better performance on mobile/cellular networks (HTTP/2 optimizations)\n * - ✅ No connection state to maintain\n * - ⚠️ Slightly higher per-request overhead on fast networks\n *\n * - `\"auto\"`: Automatically selects based on network conditions\n * - Uses HTTP on cellular/slow networks when `autoHttpOnMobile` is true\n * - Uses WebSocket on fast networks (WiFi, wired)\n *\n * @default \"websocket\"\n */\n transport?: \"http\" | \"websocket\" | \"auto\";\n\n /**\n * Automatically switch to HTTP transport on mobile/cellular networks.\n *\n * When enabled and `transport` is `\"auto\"`, the client will use HTTP\n * instead of WebSocket on cellular connections (4G/LTE, 5G) and slow\n * connections (2G, 3G). This improves performance on mobile networks\n * where HTTP/2 is more efficient due to carrier network optimizations.\n *\n * @default false\n */\n autoHttpOnMobile?: boolean;\n\n /**\n * WebSocket per-message compression configuration.\n *\n * Enable and configure the permessage-deflate extension to compress\n * messages on the wire, reducing bandwidth usage.\n */\n compression?: HeliumCompressionConfig;\n\n /**\n * RPC security and rate limiting configuration.\n *\n * Configure connection limits, message rate limits, and token validity\n * to protect your RPC endpoints from abuse.\n */\n security?: HeliumRpcSecurityConfig;\n };\n}\n\nconst DEFAULT_RPC_SECURITY: Required<HeliumRpcSecurityConfig> = {\n maxConnectionsPerIP: 10,\n maxMessagesPerWindow: 100,\n rateLimitWindowMs: 60000,\n tokenValidityMs: 30000,\n};\n\nconst DEFAULT_COMPRESSION: Required<HeliumCompressionConfig> = {\n enabled: true,\n threshold: 1024,\n};\n\nlet cachedConfig: HeliumConfig | null = null;\n\n/**\n * Load Helium configuration from the project root.\n * Searches for helium.config.js, helium.config.mjs, or helium.config.ts.\n * Results are cached for the lifetime of the process.\n *\n * In production, the build process automatically transpiles .ts config files\n * to .js in the dist directory. The loader checks dist/ first when available.\n *\n * @internal - Used by framework internals only\n */\nexport async function loadConfig(root: string = process.cwd()): Promise<HeliumConfig> {\n if (cachedConfig) {\n return cachedConfig;\n }\n\n // Check if there's a custom config directory (used in production)\n const configDir = process.env.HELIUM_CONFIG_DIR || root;\n\n // Prioritize .js/.mjs (work in both dev and production)\n // .ts files work in dev with Vite but fail in production without transpilation\n const configFiles = [\"helium.config.js\", \"helium.config.mjs\", \"helium.config.ts\"];\n\n // In production with HELIUM_CONFIG_DIR set, check dist directory first\n const searchPaths = configDir !== root ? [configDir, root] : [root];\n\n for (const searchPath of searchPaths) {\n for (const configFile of configFiles) {\n const configPath = path.join(searchPath, configFile);\n if (fs.existsSync(configPath)) {\n try {\n const fileUrl = pathToFileURL(configPath).href;\n const module = await import(/* @vite-ignore */ `${fileUrl}?t=${Date.now()}`);\n const config = module.default || {};\n cachedConfig = config;\n return config;\n } catch (err) {\n // In production, .ts files will fail to load without a TypeScript loader\n if (configFile.endsWith(\".ts\") && err instanceof Error && \"code\" in err && err.code === \"ERR_UNKNOWN_FILE_EXTENSION\") {\n console.warn(`[Helium] Cannot load ${configFile} in production. The build process should have transpiled it.`);\n } else {\n console.warn(`[Helium] Failed to load config from ${configFile}:`, err);\n }\n }\n }\n }\n }\n\n cachedConfig = {};\n return cachedConfig;\n}\n\n/**\n * Get the proxy trust depth from config.\n * Used for extracting client IPs from X-Forwarded-For headers.\n *\n * @internal - Used by framework internals only\n */\nexport function getTrustProxyDepth(config: HeliumConfig = {}): number {\n return config.trustProxyDepth ?? 0;\n}\n\n/**\n * Get RPC security configuration with defaults applied.\n * Returns rate limiting, connection limits, and token settings.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcSecurityConfig(config: HeliumConfig = {}): Required<HeliumRpcSecurityConfig> {\n const src = config.rpc?.security;\n\n return {\n maxConnectionsPerIP: src?.maxConnectionsPerIP ?? DEFAULT_RPC_SECURITY.maxConnectionsPerIP,\n maxMessagesPerWindow: src?.maxMessagesPerWindow ?? DEFAULT_RPC_SECURITY.maxMessagesPerWindow,\n rateLimitWindowMs: src?.rateLimitWindowMs ?? DEFAULT_RPC_SECURITY.rateLimitWindowMs,\n tokenValidityMs: src?.tokenValidityMs ?? DEFAULT_RPC_SECURITY.tokenValidityMs,\n };\n}\n\n/**\n * Get WebSocket compression configuration with defaults applied.\n *\n * @internal - Used by framework internals only\n */\nexport function getCompressionConfig(config: HeliumConfig = {}): Required<HeliumCompressionConfig> {\n const src = config.rpc?.compression;\n\n return {\n enabled: src?.enabled ?? DEFAULT_COMPRESSION.enabled,\n threshold: src?.threshold ?? DEFAULT_COMPRESSION.threshold,\n };\n}\n\n/**\n * Get complete RPC configuration including compression, and security.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcConfig(config: HeliumConfig = {}) {\n return {\n compression: getCompressionConfig(config),\n security: getRpcSecurityConfig(config),\n };\n}\n\n/**\n * Client-side RPC transport configuration.\n * This is injected into the client bundle at build time.\n */\nexport interface RpcClientTransportConfig {\n transport: \"http\" | \"websocket\" | \"auto\";\n autoHttpOnMobile: boolean;\n}\n\n/**\n * Get client-side RPC transport configuration.\n * This configuration is injected into the client bundle via Vite defines.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcClientConfig(config: HeliumConfig = {}): RpcClientTransportConfig {\n return {\n transport: config.rpc?.transport ?? \"websocket\",\n autoHttpOnMobile: config.rpc?.autoHttpOnMobile ?? false,\n };\n}\n\n/**\n * Clear the cached configuration.\n * Useful for testing or when you need to reload config.\n *\n * @internal - Used by framework internals only\n */\nexport function clearConfigCache() {\n cachedConfig = null;\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\n\n/**\n * WebSocket per-message compression configuration.\n * Uses the permessage-deflate extension to compress messages on the wire.\n */\nexport interface HeliumCompressionConfig {\n /**\n * Enable WebSocket per-message compression (permessage-deflate extension).\n * When enabled, messages are compressed before sending to reduce bandwidth usage.\n *\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Minimum message size in bytes to apply compression.\n * Messages smaller than this threshold will not be compressed to avoid overhead.\n * Only applies when compression is enabled.\n *\n * @default 1024 (1KB)\n */\n threshold?: number;\n}\n\n/**\n * RPC security and rate limiting configuration.\n * Controls WebSocket connection limits, message rate limits, and token-based authentication.\n */\nexport interface HeliumRpcSecurityConfig {\n /**\n * Maximum number of concurrent WebSocket connections allowed per IP address.\n * Helps prevent a single client from exhausting connection resources.\n * Set to 0 to disable this limit.\n *\n * @default 10\n */\n maxConnectionsPerIP?: number;\n\n /**\n * Maximum number of RPC messages allowed per connection within the time window.\n * Helps prevent abuse by limiting message throughput per connection.\n * Set to 0 to disable rate limiting.\n *\n * @default 100\n */\n maxMessagesPerWindow?: number;\n\n /**\n * Time window in milliseconds for rate limiting.\n * Rate limits reset after this duration.\n *\n * @default 60000 (1 minute)\n */\n rateLimitWindowMs?: number;\n\n /**\n * WebSocket connection token validity duration in milliseconds.\n * Tokens are generated server-side and must be used within this timeframe.\n * Shorter durations improve security but may cause issues with slow networks.\n *\n * @default 30000 (30 seconds)\n */\n tokenValidityMs?: number;\n}\n\n/**\n * Security configuration for HTTP responses.\n */\nexport interface HeliumSecurityConfig {\n /**\n * Content-Security-Policy header value.\n * Set to a CSP string to enable, or omit to skip CSP.\n *\n * @default undefined (no CSP header)\n */\n contentSecurityPolicy?: string;\n\n /**\n * Enable Strict-Transport-Security header.\n * Set to false to disable HSTS.\n *\n * @default true\n */\n hsts?: boolean;\n\n /**\n * Allowed CORS origins.\n * Set to [\"*\"] to allow all origins, or provide specific origins.\n * Empty array or omit to restrict to same-origin only (default, most secure).\n *\n * @default [] (same-origin only)\n */\n corsOrigins?: string[];\n}\n\n/**\n * Helium framework configuration.\n *\n * Configure your Helium application behavior including RPC transport settings,\n * compression, security, and proxy configuration for production deployments.\n */\nexport interface HeliumConfig {\n /**\n * Number of proxy levels to trust when extracting client IP addresses.\n *\n * This setting is crucial for deployments behind reverse proxies, load balancers,\n * or CDNs (like Vercel, Cloudflare, AWS ALB, etc.). It determines how the framework\n * extracts the real client IP from headers like X-Forwarded-For.\n *\n * **How it works:**\n * When behind proxies, the X-Forwarded-For header contains a chain of IPs:\n * `X-Forwarded-For: <client-ip>, <proxy1-ip>, <proxy2-ip>`\n *\n * This setting tells Helium how many proxy IPs to skip from the right to find the real client IP.\n *\n * **Values:**\n * - `0`: Don't trust any proxies, use direct connection IP (default, most secure)\n * - `1`: Trust 1 proxy level (recommended for most platforms: Vercel, Netlify, Railway)\n * - `2+`: Trust multiple proxy levels (for complex setups like Cloudflare → Load Balancer → Your Server)\n *\n * **Common configurations:**\n * - Local development: `0`\n * - Vercel/Netlify/Railway: `1`\n * - Cloudflare → Your server: `1` or `2`\n * - AWS ALB → EC2: `1`\n * - Nginx → Node.js: `1`\n * - Cloudflare → AWS ALB → EC2: `2`\n *\n * **Security note:** Setting this too high can allow IP spoofing. Only trust as many\n * proxy levels as you actually have in your infrastructure.\n *\n * This setting applies to both HTTP requests and WebSocket connections.\n *\n * @default 0\n */\n trustProxyDepth?: number;\n\n /**\n * HTTP response security configuration.\n * Controls CORS, CSP, HSTS, and other security headers.\n */\n security?: HeliumSecurityConfig;\n\n /**\n * RPC transport configuration.\n *\n * Configure the WebSocket-based RPC layer including compression\n * and security settings.\n */\n rpc?: {\n /**\n * Client-side transport mode for RPC calls.\n *\n * - `\"websocket\"` (default): Uses persistent WebSocket connection\n * - ✅ Lower latency for subsequent calls (connection reuse)\n * - ✅ Real-time bidirectional communication ready\n * - ⚠️ Higher initial connection overhead\n *\n * - `\"http\"`: Uses HTTP POST requests for each RPC call\n * - ✅ Better performance on mobile/cellular networks (HTTP/2 optimizations)\n * - ✅ No connection state to maintain\n * - ⚠️ Slightly higher per-request overhead on fast networks\n *\n * - `\"auto\"`: Automatically selects based on network conditions\n * - Uses HTTP on cellular/slow networks when `autoHttpOnMobile` is true\n * - Uses WebSocket on fast networks (WiFi, wired)\n *\n * @default \"websocket\"\n */\n transport?: \"http\" | \"websocket\" | \"auto\";\n\n /**\n * Automatically switch to HTTP transport on mobile/cellular networks.\n *\n * When enabled and `transport` is `\"auto\"`, the client will use HTTP\n * instead of WebSocket on cellular connections (4G/LTE, 5G) and slow\n * connections (2G, 3G). This improves performance on mobile networks\n * where HTTP/2 is more efficient due to carrier network optimizations.\n *\n * @default false\n */\n autoHttpOnMobile?: boolean;\n\n /**\n * WebSocket per-message compression configuration.\n *\n * Enable and configure the permessage-deflate extension to compress\n * messages on the wire, reducing bandwidth usage.\n */\n compression?: HeliumCompressionConfig;\n\n /**\n * RPC security and rate limiting configuration.\n *\n * Configure connection limits, message rate limits, and token validity\n * to protect your RPC endpoints from abuse.\n */\n security?: HeliumRpcSecurityConfig;\n\n /**\n * Maximum HTTP request body size in bytes.\n * Requests exceeding this limit receive a 413 status.\n *\n * @default 1048576 (1 MB)\n */\n maxBodySize?: number;\n\n /**\n * Maximum number of RPC calls in a single batch request.\n * Batches exceeding this limit are rejected.\n *\n * @default 20\n */\n maxBatchSize?: number;\n\n /**\n * Maximum WebSocket message payload size in bytes.\n * Messages exceeding this limit cause the connection to be closed.\n *\n * @default 1048576 (1 MB)\n */\n maxWsPayload?: number;\n };\n}\n\nconst DEFAULT_RPC_SECURITY: Required<HeliumRpcSecurityConfig> = {\n maxConnectionsPerIP: 10,\n maxMessagesPerWindow: 100,\n rateLimitWindowMs: 60000,\n tokenValidityMs: 30000,\n};\n\nconst DEFAULT_COMPRESSION: Required<HeliumCompressionConfig> = {\n enabled: true,\n threshold: 1024,\n};\n\nlet cachedConfig: HeliumConfig | null = null;\n\n/**\n * Load Helium configuration from the project root.\n * Searches for helium.config.js, helium.config.mjs, or helium.config.ts.\n * Results are cached for the lifetime of the process.\n *\n * In production, the build process automatically transpiles .ts config files\n * to .js in the dist directory. The loader checks dist/ first when available.\n *\n * @internal - Used by framework internals only\n */\nexport async function loadConfig(root: string = process.cwd()): Promise<HeliumConfig> {\n if (cachedConfig) {\n return cachedConfig;\n }\n\n // Check if there's a custom config directory (used in production)\n const configDir = process.env.HELIUM_CONFIG_DIR || root;\n\n // Prioritize .js/.mjs (work in both dev and production)\n // .ts files work in dev with Vite but fail in production without transpilation\n const configFiles = [\"helium.config.js\", \"helium.config.mjs\", \"helium.config.ts\"];\n\n // In production with HELIUM_CONFIG_DIR set, check dist directory first\n const searchPaths = configDir !== root ? [configDir, root] : [root];\n\n for (const searchPath of searchPaths) {\n for (const configFile of configFiles) {\n const configPath = path.join(searchPath, configFile);\n if (fs.existsSync(configPath)) {\n try {\n const fileUrl = pathToFileURL(configPath).href;\n const module = await import(/* @vite-ignore */ `${fileUrl}?t=${Date.now()}`);\n const config = module.default || {};\n cachedConfig = config;\n return config;\n } catch (err) {\n // In production, .ts files will fail to load without a TypeScript loader\n if (configFile.endsWith(\".ts\") && err instanceof Error && \"code\" in err && err.code === \"ERR_UNKNOWN_FILE_EXTENSION\") {\n console.warn(`[Helium] Cannot load ${configFile} in production. The build process should have transpiled it.`);\n } else {\n console.warn(`[Helium] Failed to load config from ${configFile}:`, err);\n }\n }\n }\n }\n }\n\n cachedConfig = {};\n return cachedConfig;\n}\n\n/**\n * Get the proxy trust depth from config.\n * Used for extracting client IPs from X-Forwarded-For headers.\n *\n * @internal - Used by framework internals only\n */\nexport function getTrustProxyDepth(config: HeliumConfig = {}): number {\n return config.trustProxyDepth ?? 0;\n}\n\n/**\n * Get RPC security configuration with defaults applied.\n * Returns rate limiting, connection limits, and token settings.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcSecurityConfig(config: HeliumConfig = {}): Required<HeliumRpcSecurityConfig> {\n const src = config.rpc?.security;\n\n return {\n maxConnectionsPerIP: src?.maxConnectionsPerIP ?? DEFAULT_RPC_SECURITY.maxConnectionsPerIP,\n maxMessagesPerWindow: src?.maxMessagesPerWindow ?? DEFAULT_RPC_SECURITY.maxMessagesPerWindow,\n rateLimitWindowMs: src?.rateLimitWindowMs ?? DEFAULT_RPC_SECURITY.rateLimitWindowMs,\n tokenValidityMs: src?.tokenValidityMs ?? DEFAULT_RPC_SECURITY.tokenValidityMs,\n };\n}\n\n/**\n * Get WebSocket compression configuration with defaults applied.\n *\n * @internal - Used by framework internals only\n */\nexport function getCompressionConfig(config: HeliumConfig = {}): Required<HeliumCompressionConfig> {\n const src = config.rpc?.compression;\n\n return {\n enabled: src?.enabled ?? DEFAULT_COMPRESSION.enabled,\n threshold: src?.threshold ?? DEFAULT_COMPRESSION.threshold,\n };\n}\n\n/**\n * Get complete RPC configuration including compression, and security.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcConfig(config: HeliumConfig = {}) {\n return {\n compression: getCompressionConfig(config),\n security: getRpcSecurityConfig(config),\n maxBodySize: config.rpc?.maxBodySize ?? 1_048_576,\n maxBatchSize: config.rpc?.maxBatchSize ?? 20,\n maxWsPayload: config.rpc?.maxWsPayload ?? 1_048_576,\n };\n}\n\n/**\n * Client-side RPC transport configuration.\n * This is injected into the client bundle at build time.\n */\nexport interface RpcClientTransportConfig {\n transport: \"http\" | \"websocket\" | \"auto\";\n autoHttpOnMobile: boolean;\n}\n\n/**\n * Get client-side RPC transport configuration.\n * This configuration is injected into the client bundle via Vite defines.\n *\n * @internal - Used by framework internals only\n */\nexport function getRpcClientConfig(config: HeliumConfig = {}): RpcClientTransportConfig {\n return {\n transport: config.rpc?.transport ?? \"websocket\",\n autoHttpOnMobile: config.rpc?.autoHttpOnMobile ?? false,\n };\n}\n\n/**\n * Clear the cached configuration.\n * Useful for testing or when you need to reload config.\n *\n * @internal - Used by framework internals only\n */\nexport function clearConfigCache() {\n cachedConfig = null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"devServer.d.ts","sourceRoot":"","sources":["../../src/server/devServer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAC9E,KAAK,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,iBAAiB,CAAC;AAE3F,UAAU,WAAW;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;CAC3B;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,GAAE,YAAiB,EAAE,OAAO,GAAE,WAAW,EAAO,QAiR7I"}
1
+ {"version":3,"file":"devServer.d.ts","sourceRoot":"","sources":["../../src/server/devServer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAC9E,KAAK,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,iBAAiB,CAAC;AAE3F,UAAU,WAAW;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;CAC3B;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,GAAE,YAAiB,EAAE,OAAO,GAAE,WAAW,EAAO,QAgU7I"}
@@ -41,6 +41,7 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
41
41
  httpRouter.setTrustProxyDepth(trustProxyDepth);
42
42
  loadHandlers(registry, httpRouter);
43
43
  registry.setRateLimiter(rateLimiter);
44
+ registry.setMaxBatchSize(rpcConfig.maxBatchSize);
44
45
  currentRegistry = registry;
45
46
  currentHttpRouter = httpRouter;
46
47
  // Start workers if they changed
@@ -104,6 +105,7 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
104
105
  if (!wss) {
105
106
  wss = new WebSocketServer({
106
107
  noServer: true,
108
+ maxPayload: rpcConfig.maxWsPayload,
107
109
  perMessageDeflate: compressionConfig.enabled
108
110
  ? {
109
111
  zlibDeflateOptions: {
@@ -176,8 +178,9 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
176
178
  // Handle WebSocket upgrade requests
177
179
  httpServer.on("upgrade", (req, socket, head) => {
178
180
  if (req.url?.startsWith("/rpc")) {
179
- const url = new URL(req.url, "http://localhost");
180
- const token = url.searchParams.get("token");
181
+ // Security: read token from Sec-WebSocket-Protocol header instead of query string
182
+ const protocols = req.headers["sec-websocket-protocol"];
183
+ const token = typeof protocols === "string" ? protocols.split(",").map((p) => p.trim()).find((p) => p.includes(".")) : undefined;
181
184
  if (!token || !verifyConnectionToken(token)) {
182
185
  log("warn", "WebSocket connection rejected - invalid token");
183
186
  socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
@@ -202,6 +205,8 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
202
205
  });
203
206
  log("info", "WebSocket RPC attached to dev server at /rpc");
204
207
  }
208
+ // Security: max body size for HTTP requests
209
+ const maxBodySize = rpcConfig.maxBodySize ?? 1048576;
205
210
  // Attach HTTP request handler
206
211
  // We need to intercept requests before Vite handles them
207
212
  const originalListeners = httpServer.listeners("request").slice();
@@ -209,6 +214,18 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
209
214
  httpServer.on("request", async (req, res) => {
210
215
  // Handle token refresh endpoint
211
216
  if (req.url === "/__helium__/refresh-token") {
217
+ // Security: only allow POST to prevent CSRF via <img>/<script> tags
218
+ if (req.method !== "POST") {
219
+ res.writeHead(405, { "Content-Type": "application/json" });
220
+ res.end(JSON.stringify({ error: "Method not allowed" }));
221
+ return;
222
+ }
223
+ // Security: require custom header to prevent cross-origin requests
224
+ if (!req.headers["x-requested-with"]) {
225
+ res.writeHead(403, { "Content-Type": "application/json" });
226
+ res.end(JSON.stringify({ error: "Forbidden" }));
227
+ return;
228
+ }
212
229
  const { generateConnectionToken } = await import("./security.js");
213
230
  const token = generateConnectionToken();
214
231
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -217,9 +234,37 @@ export function attachToDevServer(httpServer, loadHandlers, config = {}, workers
217
234
  }
218
235
  // Handle HTTP-based RPC endpoint (alternative to WebSocket for mobile networks)
219
236
  if (req.url === "/__helium__/rpc" && req.method === "POST") {
237
+ // Security: verify connection token for HTTP RPC
238
+ const authToken = req.headers["x-helium-token"];
239
+ if (!authToken || !verifyConnectionToken(authToken)) {
240
+ res.writeHead(401, { "Content-Type": "application/json" });
241
+ res.end(JSON.stringify({ ok: false, error: "Unauthorized" }));
242
+ return;
243
+ }
244
+ // Security: check Content-Length before reading body
245
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
246
+ if (contentLength > maxBodySize) {
247
+ res.writeHead(413, { "Content-Type": "application/json" });
248
+ res.end(JSON.stringify({ ok: false, error: "Request entity too large" }));
249
+ return;
250
+ }
220
251
  const chunks = [];
221
- req.on("data", (chunk) => chunks.push(chunk));
252
+ let totalSize = 0;
253
+ let aborted = false;
254
+ req.on("data", (chunk) => {
255
+ totalSize += chunk.length;
256
+ if (totalSize > maxBodySize) {
257
+ aborted = true;
258
+ req.destroy();
259
+ res.writeHead(413, { "Content-Type": "application/json" });
260
+ res.end(JSON.stringify({ ok: false, error: "Request entity too large" }));
261
+ return;
262
+ }
263
+ chunks.push(chunk);
264
+ });
222
265
  req.on("end", async () => {
266
+ if (aborted)
267
+ return;
223
268
  try {
224
269
  if (!currentRegistry) {
225
270
  res.writeHead(503, { "Content-Type": "application/json" });