heliumts 0.5.0 → 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.
@@ -46,10 +46,37 @@ export function startProdServer(options) {
46
46
  httpRouter.setTrustProxyDepth(trustProxyDepth);
47
47
  registerHandlers(registry, httpRouter);
48
48
  registry.setRateLimiter(rateLimiter);
49
+ registry.setMaxBatchSize(rpcConfig.maxBatchSize);
50
+ // Security: max body size for HTTP requests (1 MB default)
51
+ const maxBodySize = rpcConfig.maxBodySize ?? 1048576;
52
+ // Security: max batch size for RPC requests
53
+ const maxBatchSize = rpcConfig.maxBatchSize ?? 20;
49
54
  // Create HTTP server
50
55
  const server = http.createServer(async (req, res) => {
56
+ // Apply security headers to all responses
57
+ setSecurityHeaders(res, config);
58
+ // Handle CORS preflight
59
+ if (req.method === "OPTIONS") {
60
+ handleCorsHeaders(req, res, config);
61
+ res.writeHead(204);
62
+ res.end();
63
+ return;
64
+ }
65
+ handleCorsHeaders(req, res, config);
51
66
  // Handle token refresh endpoint
52
67
  if (req.url === "/__helium__/refresh-token") {
68
+ // Security: only allow POST to prevent CSRF via <img>/<script> tags
69
+ if (req.method !== "POST") {
70
+ res.writeHead(405, { "Content-Type": "application/json" });
71
+ res.end(JSON.stringify({ error: "Method not allowed" }));
72
+ return;
73
+ }
74
+ // Security: require custom header to prevent cross-origin requests
75
+ if (!req.headers["x-requested-with"]) {
76
+ res.writeHead(403, { "Content-Type": "application/json" });
77
+ res.end(JSON.stringify({ error: "Forbidden" }));
78
+ return;
79
+ }
53
80
  const token = generateConnectionToken();
54
81
  res.writeHead(200, { "Content-Type": "application/json" });
55
82
  res.end(JSON.stringify({ token }));
@@ -57,9 +84,37 @@ export function startProdServer(options) {
57
84
  }
58
85
  // Handle HTTP-based RPC endpoint (alternative to WebSocket for mobile networks)
59
86
  if (req.url === "/__helium__/rpc" && req.method === "POST") {
87
+ // Security: verify connection token for HTTP RPC
88
+ const authToken = req.headers["x-helium-token"];
89
+ if (!authToken || !verifyConnectionToken(authToken)) {
90
+ res.writeHead(401, { "Content-Type": "application/json" });
91
+ res.end(JSON.stringify({ ok: false, error: "Unauthorized" }));
92
+ return;
93
+ }
94
+ // Security: check Content-Length before reading body
95
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
96
+ if (contentLength > maxBodySize) {
97
+ res.writeHead(413, { "Content-Type": "application/json" });
98
+ res.end(JSON.stringify({ ok: false, error: "Request entity too large" }));
99
+ return;
100
+ }
60
101
  const chunks = [];
61
- req.on("data", (chunk) => chunks.push(chunk));
102
+ let totalSize = 0;
103
+ let aborted = false;
104
+ req.on("data", (chunk) => {
105
+ totalSize += chunk.length;
106
+ if (totalSize > maxBodySize) {
107
+ aborted = true;
108
+ req.destroy();
109
+ res.writeHead(413, { "Content-Type": "application/json" });
110
+ res.end(JSON.stringify({ ok: false, error: "Request entity too large" }));
111
+ return;
112
+ }
113
+ chunks.push(chunk);
114
+ });
62
115
  req.on("end", async () => {
116
+ if (aborted)
117
+ return;
63
118
  try {
64
119
  const body = Buffer.concat(chunks);
65
120
  const ip = extractClientIP(req, trustProxyDepth);
@@ -107,7 +162,7 @@ export function startProdServer(options) {
107
162
  // Block access to sensitive configuration and server files
108
163
  const blockedFiles = ["helium.config.js", "helium.config.mjs", "helium.config.ts", "server.js", ".env", ".env.local", ".env.production"];
109
164
  const requestedFile = path.basename(url.split("?")[0]);
110
- let filePath;
165
+ let filePath = path.join(staticDir, "index.html");
111
166
  let is404 = false;
112
167
  if (blockedFiles.some((blocked) => requestedFile === blocked || requestedFile.startsWith(".env"))) {
113
168
  // Serve index.html so the SPA router can render the 404 page
@@ -117,8 +172,15 @@ export function startProdServer(options) {
117
172
  else {
118
173
  // Clean URL (remove query params and trailing slash)
119
174
  const cleanUrl = url.split("?")[0].replace(/\/$/, "") || "/";
175
+ // Security: path traversal prevention — resolve and verify
176
+ const resolvedStaticDir = path.resolve(staticDir);
177
+ const candidatePath = path.resolve(staticDir, "." + cleanUrl);
178
+ if (!candidatePath.startsWith(resolvedStaticDir + path.sep) && candidatePath !== resolvedStaticDir) {
179
+ filePath = path.join(staticDir, "index.html");
180
+ is404 = true;
181
+ }
120
182
  // Try different file paths for SSG support
121
- if (cleanUrl === "/") {
183
+ if (!is404 && cleanUrl === "/") {
122
184
  // Try index.ssg.html first (if root page has SSG)
123
185
  const ssgIndexPath = path.join(staticDir, "index.ssg.html");
124
186
  if (fs.existsSync(ssgIndexPath)) {
@@ -128,7 +190,7 @@ export function startProdServer(options) {
128
190
  filePath = path.join(staticDir, "index.html");
129
191
  }
130
192
  }
131
- else {
193
+ else if (!is404) {
132
194
  // If cleanUrl has no extension, prioritize .html files for SSG pages
133
195
  if (!path.extname(cleanUrl)) {
134
196
  const htmlPath = path.join(staticDir, cleanUrl + ".html");
@@ -146,7 +208,7 @@ export function startProdServer(options) {
146
208
  }
147
209
  }
148
210
  // If file doesn't exist or is a directory, fall back to index.html for SPA routing
149
- const isFileOrExists = fs.existsSync(filePath) && fs.statSync(filePath).isFile();
211
+ const isFileOrExists = !is404 && filePath && fs.existsSync(filePath) && fs.statSync(filePath).isFile();
150
212
  if (!isFileOrExists && !url.startsWith("/api") && !url.startsWith("/webhooks") && !url.startsWith("/auth")) {
151
213
  // Fall back to index.html for SPA routing
152
214
  // Note: We don't set is404 here because the client-side router will determine
@@ -197,6 +259,7 @@ export function startProdServer(options) {
197
259
  // Setup WebSocket server for RPC
198
260
  const wss = new WebSocketServer({
199
261
  noServer: true,
262
+ maxPayload: rpcConfig.maxWsPayload,
200
263
  perMessageDeflate: compressionConfig.enabled
201
264
  ? {
202
265
  zlibDeflateOptions: {
@@ -265,8 +328,9 @@ export function startProdServer(options) {
265
328
  // Handle WebSocket upgrade requests
266
329
  server.on("upgrade", (req, socket, head) => {
267
330
  if (req.url?.startsWith("/rpc")) {
268
- const url = new URL(req.url, "http://localhost");
269
- const token = url.searchParams.get("token");
331
+ // Security: read token from Sec-WebSocket-Protocol header instead of query string
332
+ const protocols = req.headers["sec-websocket-protocol"];
333
+ const token = typeof protocols === "string" ? protocols.split(",").map((p) => p.trim()).find((p) => p.includes(".")) : undefined;
270
334
  if (!token || !verifyConnectionToken(token)) {
271
335
  log("warn", "WebSocket connection rejected - invalid token");
272
336
  socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
@@ -337,4 +401,49 @@ export function startProdServer(options) {
337
401
  process.on("SIGTERM", shutdown);
338
402
  return server;
339
403
  }
404
+ // ============================================================================
405
+ // Security helper functions
406
+ // ============================================================================
407
+ /**
408
+ * Set default security headers on every HTTP response.
409
+ */
410
+ function setSecurityHeaders(res, config) {
411
+ res.setHeader("X-Content-Type-Options", "nosniff");
412
+ res.setHeader("X-Frame-Options", "DENY");
413
+ res.setHeader("X-XSS-Protection", "0");
414
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
415
+ res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
416
+ const csp = config.security?.contentSecurityPolicy;
417
+ if (csp) {
418
+ res.setHeader("Content-Security-Policy", csp);
419
+ }
420
+ if (config.security?.hsts !== false) {
421
+ res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
422
+ }
423
+ }
424
+ /**
425
+ * Handle CORS headers based on configuration.
426
+ * Default: restrict to same-origin (no CORS header = browser blocks cross-origin).
427
+ */
428
+ function handleCorsHeaders(req, res, config) {
429
+ const allowedOrigins = config.security?.corsOrigins;
430
+ if (!allowedOrigins || allowedOrigins.length === 0) {
431
+ // No CORS configured — same-origin only by default
432
+ return;
433
+ }
434
+ const origin = req.headers.origin;
435
+ if (!origin) {
436
+ return;
437
+ }
438
+ const isAllowed = allowedOrigins.includes("*") || allowedOrigins.includes(origin);
439
+ if (isAllowed) {
440
+ res.setHeader("Access-Control-Allow-Origin", allowedOrigins.includes("*") ? "*" : origin);
441
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
442
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With, X-Helium-Token");
443
+ res.setHeader("Access-Control-Max-Age", "86400");
444
+ if (!allowedOrigins.includes("*")) {
445
+ res.setHeader("Vary", "Origin");
446
+ }
447
+ }
448
+ }
340
449
  //# sourceMappingURL=prodServer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prodServer.js","sourceRoot":"","sources":["../../src/server/prodServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport fs from \"fs\";\nimport http from \"http\";\nimport path from \"path\";\nimport { promisify } from \"util\";\nimport type WebSocket from \"ws\";\nimport { WebSocketServer } from \"ws\";\nimport { brotliCompress, deflate, gzip } from \"zlib\";\n\nimport { extractClientIP } from \"../utils/ipExtractor.js\";\nimport { log } from \"../utils/logger.js\";\nimport type { HeliumConfig } from \"./config.js\";\nimport { getRpcConfig, getRpcSecurityConfig, getTrustProxyDepth } from \"./config.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumWorkerDef } from \"./defineWorker.js\";\nimport { startWorker, stopAllWorkers } from \"./defineWorker.js\";\nimport { HTTPRouter } from \"./httpRouter.js\";\nimport { RateLimiter } from \"./rateLimiter.js\";\nimport { RpcRegistry } from \"./rpcRegistry.js\";\nimport { generateConnectionToken, initializeSecurity, verifyConnectionToken } from \"./security.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\nconst brotliCompressAsync = promisify(brotliCompress);\n\ninterface WorkerEntry {\n name: string;\n worker: HeliumWorkerDef;\n}\n\ninterface ProdServerOptions {\n port?: number;\n distDir?: string;\n staticDir?: string;\n registerHandlers: (registry: RpcRegistry, httpRouter: HTTPRouter) => void;\n config?: HeliumConfig;\n workers?: WorkerEntry[];\n}\n\n/**\n * Starts a production HTTP server that:\n * - Serves static files from the dist directory\n * - Supports SSG (Static Site Generation) by serving .html files for routes (e.g., /about -> about.html)\n * - Falls back to index.html for client-side routing (SPA)\n * - Handles custom HTTP endpoints (webhooks, auth, etc.)\n * - Hosts WebSocket RPC server\n * - Starts background workers\n *\n * SSG Behavior:\n * - Production correctly serves SSG pages (e.g., /about serves about.html with pre-rendered content)\n * - This ensures search engines and social media crawlers see the correct content\n * - Client-side navigation between pages still works via React Router\n */\nexport function startProdServer(options: ProdServerOptions) {\n const { port = Number(process.env.PORT || 3000), distDir = \"dist\", staticDir = path.resolve(process.cwd(), distDir), registerHandlers, config = {}, workers = [] } = options;\n\n // Load configuration\n const trustProxyDepth = getTrustProxyDepth(config);\n const rpcSecurity = getRpcSecurityConfig(config);\n const rpcConfig = getRpcConfig(config);\n const compressionConfig = rpcConfig.compression;\n initializeSecurity(rpcSecurity);\n\n // Initialize rate limiter\n const rateLimiter = new RateLimiter(rpcSecurity.maxMessagesPerWindow, rpcSecurity.rateLimitWindowMs, rpcSecurity.maxConnectionsPerIP);\n\n const registry = new RpcRegistry();\n const httpRouter = new HTTPRouter();\n httpRouter.setTrustProxyDepth(trustProxyDepth);\n registerHandlers(registry, httpRouter);\n registry.setRateLimiter(rateLimiter);\n\n // Create HTTP server\n const server = http.createServer(async (req, res) => {\n // Handle token refresh endpoint\n if (req.url === \"/__helium__/refresh-token\") {\n const token = generateConnectionToken();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ token }));\n return;\n }\n\n // Handle HTTP-based RPC endpoint (alternative to WebSocket for mobile networks)\n if (req.url === \"/__helium__/rpc\" && req.method === \"POST\") {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", async () => {\n try {\n const body = Buffer.concat(chunks);\n const ip = extractClientIP(req, trustProxyDepth);\n const result = await registry.handleHttpRequest(body, ip, req);\n\n const encoded = msgpackEncode(prepareForMsgpack(result.response));\n let responseBody = Buffer.from(encoded as Uint8Array);\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/msgpack\",\n \"Cache-Control\": \"no-store\",\n };\n\n // Handle compression\n const acceptEncoding = req.headers[\"accept-encoding\"] as string;\n if (acceptEncoding && responseBody.length > 1024) {\n if (acceptEncoding.includes(\"br\")) {\n responseBody = await brotliCompressAsync(responseBody);\n headers[\"Content-Encoding\"] = \"br\";\n } else if (acceptEncoding.includes(\"gzip\")) {\n responseBody = await gzipAsync(responseBody);\n headers[\"Content-Encoding\"] = \"gzip\";\n } else if (acceptEncoding.includes(\"deflate\")) {\n responseBody = await deflateAsync(responseBody);\n headers[\"Content-Encoding\"] = \"deflate\";\n }\n }\n\n res.writeHead(200, headers);\n res.end(responseBody);\n } catch (error) {\n log(\"error\", \"HTTP RPC error:\", error);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, error: \"Internal server error\" }));\n }\n });\n return;\n }\n\n // Try HTTP handlers first (webhooks, auth, etc.)\n const handled = await httpRouter.handleRequest(req, res);\n if (handled) {\n return;\n }\n\n // Serve static files\n const url = req.url || \"/\";\n\n // Block access to sensitive configuration and server files\n const blockedFiles = [\"helium.config.js\", \"helium.config.mjs\", \"helium.config.ts\", \"server.js\", \".env\", \".env.local\", \".env.production\"];\n\n const requestedFile = path.basename(url.split(\"?\")[0]);\n let filePath: string;\n let is404 = false;\n\n if (blockedFiles.some((blocked) => requestedFile === blocked || requestedFile.startsWith(\".env\"))) {\n // Serve index.html so the SPA router can render the 404 page\n filePath = path.join(staticDir, \"index.html\");\n is404 = true;\n } else {\n // Clean URL (remove query params and trailing slash)\n const cleanUrl = url.split(\"?\")[0].replace(/\\/$/, \"\") || \"/\";\n\n // Try different file paths for SSG support\n if (cleanUrl === \"/\") {\n // Try index.ssg.html first (if root page has SSG)\n const ssgIndexPath = path.join(staticDir, \"index.ssg.html\");\n if (fs.existsSync(ssgIndexPath)) {\n filePath = ssgIndexPath;\n } else {\n filePath = path.join(staticDir, \"index.html\");\n }\n } else {\n // If cleanUrl has no extension, prioritize .html files for SSG pages\n if (!path.extname(cleanUrl)) {\n const htmlPath = path.join(staticDir, cleanUrl + \".html\");\n if (fs.existsSync(htmlPath)) {\n filePath = htmlPath;\n } else {\n // Fall back to exact path (for assets or directories)\n filePath = path.join(staticDir, cleanUrl);\n }\n } else {\n // Has an extension, try exact path (for assets like /assets/main.js)\n filePath = path.join(staticDir, cleanUrl);\n }\n }\n\n // If file doesn't exist or is a directory, fall back to index.html for SPA routing\n const isFileOrExists = fs.existsSync(filePath) && fs.statSync(filePath).isFile();\n if (!isFileOrExists && !url.startsWith(\"/api\") && !url.startsWith(\"/webhooks\") && !url.startsWith(\"/auth\")) {\n // Fall back to index.html for SPA routing\n // Note: We don't set is404 here because the client-side router will determine\n // if the route exists. If it doesn't, the router will render the 404 page.\n filePath = path.join(staticDir, \"index.html\");\n // Don't set is404 = true here - let the client-side router handle it\n }\n }\n\n // Check if file exists (should always exist now since we fallback to index.html)\n if (!fs.existsSync(filePath)) {\n // This should rarely happen - only if index.html itself is missing\n res.writeHead(404, { \"Content-Type\": \"text/html\" });\n res.end(\"Not found\");\n return;\n }\n\n // Determine content type\n const ext = path.extname(filePath);\n const contentTypes: Record<string, string> = {\n \".html\": \"text/html\",\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n };\n const contentType = contentTypes[ext] || \"application/octet-stream\";\n\n try {\n const content = fs.readFileSync(filePath);\n\n // Set status code to 404 if serving the 404 page\n const statusCode = is404 ? 404 : 200;\n res.writeHead(statusCode, { \"Content-Type\": contentType });\n res.end(content);\n } catch (error) {\n log(\"error\", \"Error serving file:\", error);\n res.writeHead(500, { \"Content-Type\": \"text/plain\" });\n res.end(\"Internal server error\");\n }\n });\n\n // Setup WebSocket server for RPC\n const wss = new WebSocketServer({\n noServer: true,\n perMessageDeflate: compressionConfig.enabled\n ? {\n zlibDeflateOptions: {\n chunkSize: 1024,\n memLevel: 7,\n level: 9, // 6 is default compression level (balanced)\n },\n zlibInflateOptions: {\n chunkSize: 10 * 1024,\n },\n threshold: compressionConfig.threshold,\n }\n : false,\n });\n\n wss.on(\"connection\", (socket: WebSocket, req: http.IncomingMessage) => {\n // Extract client IP with proxy configuration\n const ip = extractClientIP(req, trustProxyDepth);\n\n // Store connection metadata for RPC context\n registry.setSocketMetadata(socket, ip, req);\n\n // Track connection and check IP limit\n if (!rateLimiter.trackConnection(socket, ip)) {\n socket.close(1008, \"Too many connections from your IP\");\n return;\n }\n\n socket.on(\"message\", (msg: WebSocket.RawData, _isBinary: boolean) => {\n // Check rate limit\n if (!rateLimiter.checkRateLimit(socket)) {\n // Parse request to get the ID for proper error response\n try {\n let req: any;\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(msg) ? msg : Buffer.from(msg as any);\n const { decode: msgpackDecode } = require(\"@msgpack/msgpack\");\n req = msgpackDecode(buffer);\n\n const stats = rateLimiter.getConnectionStats(socket);\n const now = Date.now();\n const resetInSeconds = stats ? Math.ceil((stats.resetTimeMs - now) / 1000) : 0;\n\n const createError = (id: string) => ({\n id,\n ok: false,\n stats: {\n remainingRequests: stats ? stats.remainingMessages : 0,\n resetInSeconds,\n },\n error: \"Rate limit exceeded\",\n });\n\n let errorResponse: any;\n if (Array.isArray(req)) {\n errorResponse = req.map((r: any) => createError(r.id));\n } else {\n errorResponse = createError(req.id);\n }\n\n log(\"warn\", `Rate limit exceeded for IP ${ip}, resets in ${resetInSeconds} seconds`);\n socket.send(msgpackEncode(errorResponse) as Buffer);\n } catch {\n // If we can't parse the request, just close the connection\n socket.close();\n }\n return;\n }\n\n registry.handleMessage(socket, Buffer.isBuffer(msg) ? msg : Buffer.from(msg as any));\n });\n });\n\n // Handle WebSocket upgrade requests\n server.on(\"upgrade\", (req, socket, head) => {\n if (req.url?.startsWith(\"/rpc\")) {\n const url = new URL(req.url, \"http://localhost\");\n const token = url.searchParams.get(\"token\");\n\n if (!token || !verifyConnectionToken(token)) {\n log(\"warn\", \"WebSocket connection rejected - invalid token\");\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n\n // Check IP connection limit before upgrading\n const ip = extractClientIP(req, trustProxyDepth);\n if (rpcSecurity.maxConnectionsPerIP > 0) {\n const currentConnections = rateLimiter.getIPConnectionCount(ip);\n if (currentConnections >= rpcSecurity.maxConnectionsPerIP) {\n log(\"warn\", `WebSocket connection rejected - IP ${ip} has ${currentConnections} connections`);\n socket.write(\"HTTP/1.1 429 Too Many Requests\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n\n // Start server\n server.listen(port, () => {\n log(\"info\", `Production server listening on http://localhost:${port}`);\n log(\"info\", `Serving static files from ${staticDir}`);\n log(\"info\", `WebSocket RPC available at ws://localhost:${port}/rpc`);\n\n // Start workers\n if (workers.length > 0) {\n log(\"info\", `Starting ${workers.length} worker(s)...`);\n for (const { name, worker } of workers) {\n // Use export name if worker name is anonymous\n if (worker.name === \"anonymous\") {\n worker.name = name;\n worker.__id = name;\n worker.options.name = name;\n }\n if (worker.options.autoStart) {\n const createContext = (): HeliumContext => ({\n req: {\n ip: \"127.0.0.1\",\n headers: {},\n url: undefined,\n method: undefined,\n raw: {} as http.IncomingMessage,\n },\n });\n startWorker(worker, createContext).catch((err) => {\n log(\"error\", `Failed to start worker '${worker.name}':`, err);\n });\n }\n }\n }\n });\n\n // Handle graceful shutdown\n const shutdown = async () => {\n log(\"info\", \"Shutting down...\");\n await stopAllWorkers();\n server.close(() => {\n log(\"info\", \"Server closed\");\n process.exit(0);\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n return server;\n}\n"]}
1
+ {"version":3,"file":"prodServer.js","sourceRoot":"","sources":["../../src/server/prodServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport fs from \"fs\";\nimport http from \"http\";\nimport path from \"path\";\nimport { promisify } from \"util\";\nimport type WebSocket from \"ws\";\nimport { WebSocketServer } from \"ws\";\nimport { brotliCompress, deflate, gzip } from \"zlib\";\n\nimport { extractClientIP } from \"../utils/ipExtractor.js\";\nimport { log } from \"../utils/logger.js\";\nimport type { HeliumConfig } from \"./config.js\";\nimport { getRpcConfig, getRpcSecurityConfig, getTrustProxyDepth } from \"./config.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumWorkerDef } from \"./defineWorker.js\";\nimport { startWorker, stopAllWorkers } from \"./defineWorker.js\";\nimport { HTTPRouter } from \"./httpRouter.js\";\nimport { RateLimiter } from \"./rateLimiter.js\";\nimport { RpcRegistry } from \"./rpcRegistry.js\";\nimport { generateConnectionToken, initializeSecurity, verifyConnectionToken } from \"./security.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\nconst brotliCompressAsync = promisify(brotliCompress);\n\ninterface WorkerEntry {\n name: string;\n worker: HeliumWorkerDef;\n}\n\ninterface ProdServerOptions {\n port?: number;\n distDir?: string;\n staticDir?: string;\n registerHandlers: (registry: RpcRegistry, httpRouter: HTTPRouter) => void;\n config?: HeliumConfig;\n workers?: WorkerEntry[];\n}\n\n/**\n * Starts a production HTTP server that:\n * - Serves static files from the dist directory\n * - Supports SSG (Static Site Generation) by serving .html files for routes (e.g., /about -> about.html)\n * - Falls back to index.html for client-side routing (SPA)\n * - Handles custom HTTP endpoints (webhooks, auth, etc.)\n * - Hosts WebSocket RPC server\n * - Starts background workers\n *\n * SSG Behavior:\n * - Production correctly serves SSG pages (e.g., /about serves about.html with pre-rendered content)\n * - This ensures search engines and social media crawlers see the correct content\n * - Client-side navigation between pages still works via React Router\n */\nexport function startProdServer(options: ProdServerOptions) {\n const { port = Number(process.env.PORT || 3000), distDir = \"dist\", staticDir = path.resolve(process.cwd(), distDir), registerHandlers, config = {}, workers = [] } = options;\n\n // Load configuration\n const trustProxyDepth = getTrustProxyDepth(config);\n const rpcSecurity = getRpcSecurityConfig(config);\n const rpcConfig = getRpcConfig(config);\n const compressionConfig = rpcConfig.compression;\n initializeSecurity(rpcSecurity);\n\n // Initialize rate limiter\n const rateLimiter = new RateLimiter(rpcSecurity.maxMessagesPerWindow, rpcSecurity.rateLimitWindowMs, rpcSecurity.maxConnectionsPerIP);\n\n const registry = new RpcRegistry();\n const httpRouter = new HTTPRouter();\n httpRouter.setTrustProxyDepth(trustProxyDepth);\n registerHandlers(registry, httpRouter);\n registry.setRateLimiter(rateLimiter);\n registry.setMaxBatchSize(rpcConfig.maxBatchSize);\n\n // Security: max body size for HTTP requests (1 MB default)\n const maxBodySize = rpcConfig.maxBodySize ?? 1_048_576;\n // Security: max batch size for RPC requests\n const maxBatchSize = rpcConfig.maxBatchSize ?? 20;\n\n // Create HTTP server\n const server = http.createServer(async (req, res) => {\n // Apply security headers to all responses\n setSecurityHeaders(res, config);\n\n // Handle CORS preflight\n if (req.method === \"OPTIONS\") {\n handleCorsHeaders(req, res, config);\n res.writeHead(204);\n res.end();\n return;\n }\n handleCorsHeaders(req, res, config);\n\n // Handle token refresh endpoint\n if (req.url === \"/__helium__/refresh-token\") {\n // Security: only allow POST to prevent CSRF via <img>/<script> tags\n if (req.method !== \"POST\") {\n res.writeHead(405, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Method not allowed\" }));\n return;\n }\n // Security: require custom header to prevent cross-origin requests\n if (!req.headers[\"x-requested-with\"]) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n const token = generateConnectionToken();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ token }));\n return;\n }\n\n // Handle HTTP-based RPC endpoint (alternative to WebSocket for mobile networks)\n if (req.url === \"/__helium__/rpc\" && req.method === \"POST\") {\n // Security: verify connection token for HTTP RPC\n const authToken = req.headers[\"x-helium-token\"] as string | undefined;\n if (!authToken || !verifyConnectionToken(authToken)) {\n res.writeHead(401, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, error: \"Unauthorized\" }));\n return;\n }\n\n // Security: check Content-Length before reading body\n const contentLength = parseInt(req.headers[\"content-length\"] || \"0\", 10);\n if (contentLength > maxBodySize) {\n res.writeHead(413, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, error: \"Request entity too large\" }));\n return;\n }\n\n const chunks: Buffer[] = [];\n let totalSize = 0;\n let aborted = false;\n req.on(\"data\", (chunk: Buffer) => {\n totalSize += chunk.length;\n if (totalSize > maxBodySize) {\n aborted = true;\n req.destroy();\n res.writeHead(413, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, error: \"Request entity too large\" }));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", async () => {\n if (aborted) return;\n try {\n const body = Buffer.concat(chunks);\n const ip = extractClientIP(req, trustProxyDepth);\n const result = await registry.handleHttpRequest(body, ip, req);\n\n const encoded = msgpackEncode(prepareForMsgpack(result.response));\n let responseBody = Buffer.from(encoded as Uint8Array);\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/msgpack\",\n \"Cache-Control\": \"no-store\",\n };\n\n // Handle compression\n const acceptEncoding = req.headers[\"accept-encoding\"] as string;\n if (acceptEncoding && responseBody.length > 1024) {\n if (acceptEncoding.includes(\"br\")) {\n responseBody = await brotliCompressAsync(responseBody);\n headers[\"Content-Encoding\"] = \"br\";\n } else if (acceptEncoding.includes(\"gzip\")) {\n responseBody = await gzipAsync(responseBody);\n headers[\"Content-Encoding\"] = \"gzip\";\n } else if (acceptEncoding.includes(\"deflate\")) {\n responseBody = await deflateAsync(responseBody);\n headers[\"Content-Encoding\"] = \"deflate\";\n }\n }\n\n res.writeHead(200, headers);\n res.end(responseBody);\n } catch (error) {\n log(\"error\", \"HTTP RPC error:\", error);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, error: \"Internal server error\" }));\n }\n });\n return;\n }\n\n // Try HTTP handlers first (webhooks, auth, etc.)\n const handled = await httpRouter.handleRequest(req, res);\n if (handled) {\n return;\n }\n\n // Serve static files\n const url = req.url || \"/\";\n\n // Block access to sensitive configuration and server files\n const blockedFiles = [\"helium.config.js\", \"helium.config.mjs\", \"helium.config.ts\", \"server.js\", \".env\", \".env.local\", \".env.production\"];\n\n const requestedFile = path.basename(url.split(\"?\")[0]);\n let filePath: string = path.join(staticDir, \"index.html\");\n let is404 = false;\n\n if (blockedFiles.some((blocked) => requestedFile === blocked || requestedFile.startsWith(\".env\"))) {\n // Serve index.html so the SPA router can render the 404 page\n filePath = path.join(staticDir, \"index.html\");\n is404 = true;\n } else {\n // Clean URL (remove query params and trailing slash)\n const cleanUrl = url.split(\"?\")[0].replace(/\\/$/, \"\") || \"/\";\n\n // Security: path traversal prevention — resolve and verify\n const resolvedStaticDir = path.resolve(staticDir);\n const candidatePath = path.resolve(staticDir, \".\" + cleanUrl);\n if (!candidatePath.startsWith(resolvedStaticDir + path.sep) && candidatePath !== resolvedStaticDir) {\n filePath = path.join(staticDir, \"index.html\");\n is404 = true;\n }\n\n // Try different file paths for SSG support\n if (!is404 && cleanUrl === \"/\") {\n // Try index.ssg.html first (if root page has SSG)\n const ssgIndexPath = path.join(staticDir, \"index.ssg.html\");\n if (fs.existsSync(ssgIndexPath)) {\n filePath = ssgIndexPath;\n } else {\n filePath = path.join(staticDir, \"index.html\");\n }\n } else if (!is404) {\n // If cleanUrl has no extension, prioritize .html files for SSG pages\n if (!path.extname(cleanUrl)) {\n const htmlPath = path.join(staticDir, cleanUrl + \".html\");\n if (fs.existsSync(htmlPath)) {\n filePath = htmlPath;\n } else {\n // Fall back to exact path (for assets or directories)\n filePath = path.join(staticDir, cleanUrl);\n }\n } else {\n // Has an extension, try exact path (for assets like /assets/main.js)\n filePath = path.join(staticDir, cleanUrl);\n }\n }\n\n // If file doesn't exist or is a directory, fall back to index.html for SPA routing\n const isFileOrExists = !is404 && filePath && fs.existsSync(filePath) && fs.statSync(filePath).isFile();\n if (!isFileOrExists && !url.startsWith(\"/api\") && !url.startsWith(\"/webhooks\") && !url.startsWith(\"/auth\")) {\n // Fall back to index.html for SPA routing\n // Note: We don't set is404 here because the client-side router will determine\n // if the route exists. If it doesn't, the router will render the 404 page.\n filePath = path.join(staticDir, \"index.html\");\n // Don't set is404 = true here - let the client-side router handle it\n }\n }\n\n // Check if file exists (should always exist now since we fallback to index.html)\n if (!fs.existsSync(filePath)) {\n // This should rarely happen - only if index.html itself is missing\n res.writeHead(404, { \"Content-Type\": \"text/html\" });\n res.end(\"Not found\");\n return;\n }\n\n // Determine content type\n const ext = path.extname(filePath);\n const contentTypes: Record<string, string> = {\n \".html\": \"text/html\",\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n };\n const contentType = contentTypes[ext] || \"application/octet-stream\";\n\n try {\n const content = fs.readFileSync(filePath);\n\n // Set status code to 404 if serving the 404 page\n const statusCode = is404 ? 404 : 200;\n res.writeHead(statusCode, { \"Content-Type\": contentType });\n res.end(content);\n } catch (error) {\n log(\"error\", \"Error serving file:\", error);\n res.writeHead(500, { \"Content-Type\": \"text/plain\" });\n res.end(\"Internal server error\");\n }\n });\n\n // Setup WebSocket server for RPC\n const wss = new WebSocketServer({\n noServer: true,\n maxPayload: rpcConfig.maxWsPayload,\n perMessageDeflate: compressionConfig.enabled\n ? {\n zlibDeflateOptions: {\n chunkSize: 1024,\n memLevel: 7,\n level: 9, // 6 is default compression level (balanced)\n },\n zlibInflateOptions: {\n chunkSize: 10 * 1024,\n },\n threshold: compressionConfig.threshold,\n }\n : false,\n });\n\n wss.on(\"connection\", (socket: WebSocket, req: http.IncomingMessage) => {\n // Extract client IP with proxy configuration\n const ip = extractClientIP(req, trustProxyDepth);\n\n // Store connection metadata for RPC context\n registry.setSocketMetadata(socket, ip, req);\n\n // Track connection and check IP limit\n if (!rateLimiter.trackConnection(socket, ip)) {\n socket.close(1008, \"Too many connections from your IP\");\n return;\n }\n\n socket.on(\"message\", (msg: WebSocket.RawData, _isBinary: boolean) => {\n // Check rate limit\n if (!rateLimiter.checkRateLimit(socket)) {\n // Parse request to get the ID for proper error response\n try {\n let req: any;\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(msg) ? msg : Buffer.from(msg as any);\n const { decode: msgpackDecode } = require(\"@msgpack/msgpack\");\n req = msgpackDecode(buffer);\n\n const stats = rateLimiter.getConnectionStats(socket);\n const now = Date.now();\n const resetInSeconds = stats ? Math.ceil((stats.resetTimeMs - now) / 1000) : 0;\n\n const createError = (id: string) => ({\n id,\n ok: false,\n stats: {\n remainingRequests: stats ? stats.remainingMessages : 0,\n resetInSeconds,\n },\n error: \"Rate limit exceeded\",\n });\n\n let errorResponse: any;\n if (Array.isArray(req)) {\n errorResponse = req.map((r: any) => createError(r.id));\n } else {\n errorResponse = createError(req.id);\n }\n\n log(\"warn\", `Rate limit exceeded for IP ${ip}, resets in ${resetInSeconds} seconds`);\n socket.send(msgpackEncode(errorResponse) as Buffer);\n } catch {\n // If we can't parse the request, just close the connection\n socket.close();\n }\n return;\n }\n\n registry.handleMessage(socket, Buffer.isBuffer(msg) ? msg : Buffer.from(msg as any));\n });\n });\n\n // Handle WebSocket upgrade requests\n server.on(\"upgrade\", (req, socket, head) => {\n if (req.url?.startsWith(\"/rpc\")) {\n // Security: read token from Sec-WebSocket-Protocol header instead of query string\n const protocols = req.headers[\"sec-websocket-protocol\"];\n const token = typeof protocols === \"string\" ? protocols.split(\",\").map((p) => p.trim()).find((p) => p.includes(\".\")) : undefined;\n\n if (!token || !verifyConnectionToken(token)) {\n log(\"warn\", \"WebSocket connection rejected - invalid token\");\n socket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n\n // Check IP connection limit before upgrading\n const ip = extractClientIP(req, trustProxyDepth);\n if (rpcSecurity.maxConnectionsPerIP > 0) {\n const currentConnections = rateLimiter.getIPConnectionCount(ip);\n if (currentConnections >= rpcSecurity.maxConnectionsPerIP) {\n log(\"warn\", `WebSocket connection rejected - IP ${ip} has ${currentConnections} connections`);\n socket.write(\"HTTP/1.1 429 Too Many Requests\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n\n // Start server\n server.listen(port, () => {\n log(\"info\", `Production server listening on http://localhost:${port}`);\n log(\"info\", `Serving static files from ${staticDir}`);\n log(\"info\", `WebSocket RPC available at ws://localhost:${port}/rpc`);\n\n // Start workers\n if (workers.length > 0) {\n log(\"info\", `Starting ${workers.length} worker(s)...`);\n for (const { name, worker } of workers) {\n // Use export name if worker name is anonymous\n if (worker.name === \"anonymous\") {\n worker.name = name;\n worker.__id = name;\n worker.options.name = name;\n }\n if (worker.options.autoStart) {\n const createContext = (): HeliumContext => ({\n req: {\n ip: \"127.0.0.1\",\n headers: {},\n url: undefined,\n method: undefined,\n raw: {} as http.IncomingMessage,\n },\n });\n startWorker(worker, createContext).catch((err) => {\n log(\"error\", `Failed to start worker '${worker.name}':`, err);\n });\n }\n }\n }\n });\n\n // Handle graceful shutdown\n const shutdown = async () => {\n log(\"info\", \"Shutting down...\");\n await stopAllWorkers();\n server.close(() => {\n log(\"info\", \"Server closed\");\n process.exit(0);\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n return server;\n}\n\n// ============================================================================\n// Security helper functions\n// ============================================================================\n\n/**\n * Set default security headers on every HTTP response.\n */\nfunction setSecurityHeaders(res: http.ServerResponse, config: HeliumConfig): void {\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n res.setHeader(\"X-Frame-Options\", \"DENY\");\n res.setHeader(\"X-XSS-Protection\", \"0\");\n res.setHeader(\"Referrer-Policy\", \"strict-origin-when-cross-origin\");\n res.setHeader(\"Permissions-Policy\", \"camera=(), microphone=(), geolocation=()\");\n\n const csp = config.security?.contentSecurityPolicy;\n if (csp) {\n res.setHeader(\"Content-Security-Policy\", csp);\n }\n\n if (config.security?.hsts !== false) {\n res.setHeader(\"Strict-Transport-Security\", \"max-age=31536000; includeSubDomains\");\n }\n}\n\n/**\n * Handle CORS headers based on configuration.\n * Default: restrict to same-origin (no CORS header = browser blocks cross-origin).\n */\nfunction handleCorsHeaders(req: http.IncomingMessage, res: http.ServerResponse, config: HeliumConfig): void {\n const allowedOrigins = config.security?.corsOrigins;\n if (!allowedOrigins || allowedOrigins.length === 0) {\n // No CORS configured — same-origin only by default\n return;\n }\n\n const origin = req.headers.origin;\n if (!origin) {\n return;\n }\n\n const isAllowed = allowedOrigins.includes(\"*\") || allowedOrigins.includes(origin);\n if (isAllowed) {\n res.setHeader(\"Access-Control-Allow-Origin\", allowedOrigins.includes(\"*\") ? \"*\" : origin);\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, X-Requested-With, X-Helium-Token\");\n res.setHeader(\"Access-Control-Max-Age\", \"86400\");\n\n if (!allowedOrigins.includes(\"*\")) {\n res.setHeader(\"Vary\", \"Origin\");\n }\n }\n}"]}
@@ -12,9 +12,11 @@ export declare class RpcRegistry {
12
12
  private middleware;
13
13
  private rateLimiter;
14
14
  private socketMetadata;
15
+ private maxBatchSize;
15
16
  register(id: string, def: HeliumMethodDef<any, any>): void;
16
17
  setMiddleware(middleware: HeliumMiddleware): void;
17
18
  setRateLimiter(rateLimiter: RateLimiter): void;
19
+ setMaxBatchSize(size: number): void;
18
20
  /**
19
21
  * Store metadata about a WebSocket connection.
20
22
  * Should be called when a new connection is established.
@@ -1 +1 @@
1
- {"version":3,"file":"rpcRegistry.d.ts","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,SAAS,MAAM,IAAI,CAAC;AAG3B,OAAO,KAAK,EAAc,WAAW,EAAY,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAUpD,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACzC;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,cAAc,CAA4C;IAElE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;IAKnD,aAAa,CAAC,UAAU,EAAE,gBAAgB;IAI1C,cAAc,CAAC,WAAW,EAAE,WAAW;IAIvC;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe;IAI1E,OAAO,CAAC,QAAQ;YAmBF,cAAc;IAsEtB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;YA2B7C,kBAAkB;IAoEhC;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CA4BvH"}
1
+ {"version":3,"file":"rpcRegistry.d.ts","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,SAAS,MAAM,IAAI,CAAC;AAG3B,OAAO,KAAK,EAAc,WAAW,EAAY,MAAM,wBAAwB,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAUpD,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACzC;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,YAAY,CAAc;IAElC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;IAKnD,aAAa,CAAC,UAAU,EAAE,gBAAgB;IAI1C,cAAc,CAAC,WAAW,EAAE,WAAW;IAIvC,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe;IAI1E,OAAO,CAAC,QAAQ;YAmBF,cAAc;IAuEtB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;YAuC7C,kBAAkB;IAoEhC;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CAsCvH"}
@@ -1,6 +1,7 @@
1
1
  import { decode as msgpackDecode, encode as msgpackEncode } from "@msgpack/msgpack";
2
2
  import { promisify } from "util";
3
3
  import { gzip } from "zlib";
4
+ import { log } from "../utils/logger.js";
4
5
  import { prepareForMsgpack } from "./serializer.js";
5
6
  const gzipAsync = promisify(gzip);
6
7
  export class RpcRegistry {
@@ -9,6 +10,7 @@ export class RpcRegistry {
9
10
  this.middleware = null;
10
11
  this.rateLimiter = null;
11
12
  this.socketMetadata = new WeakMap();
13
+ this.maxBatchSize = 20;
12
14
  }
13
15
  register(id, def) {
14
16
  def.__id = id;
@@ -20,6 +22,9 @@ export class RpcRegistry {
20
22
  setRateLimiter(rateLimiter) {
21
23
  this.rateLimiter = rateLimiter;
22
24
  }
25
+ setMaxBatchSize(size) {
26
+ this.maxBatchSize = size;
27
+ }
23
28
  /**
24
29
  * Store metadata about a WebSocket connection.
25
30
  * Should be called when a new connection is established.
@@ -98,11 +103,12 @@ export class RpcRegistry {
98
103
  };
99
104
  }
100
105
  catch (err) {
106
+ log("error", `RPC method '${req.method}' failed:`, err);
101
107
  return {
102
108
  id: req.id,
103
109
  ok: false,
104
110
  stats: this.getStats(socket),
105
- error: err?.message ?? "Server error",
111
+ error: sanitizeErrorMessage(err),
106
112
  };
107
113
  }
108
114
  }
@@ -116,6 +122,17 @@ export class RpcRegistry {
116
122
  catch {
117
123
  return;
118
124
  }
125
+ // Security: cap batch size
126
+ if (Array.isArray(req) && req.length > this.maxBatchSize) {
127
+ const errorResponse = {
128
+ id: "batch",
129
+ ok: false,
130
+ stats: this.getStats(socket),
131
+ error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,
132
+ };
133
+ socket.send(msgpackEncode(prepareForMsgpack(errorResponse)));
134
+ return;
135
+ }
119
136
  let response;
120
137
  if (Array.isArray(req)) {
121
138
  response = await Promise.all(req.map((r) => this.processRequest(r, socket)));
@@ -186,12 +203,12 @@ export class RpcRegistry {
186
203
  };
187
204
  }
188
205
  catch (err) {
189
- const errorMessage = err instanceof Error ? err.message : "Server error";
206
+ log("error", `HTTP RPC method '${req.method}' failed:`, err);
190
207
  return {
191
208
  id: req.id,
192
209
  ok: false,
193
210
  stats: { remainingRequests: Infinity, resetInSeconds: 0 },
194
- error: errorMessage,
211
+ error: sanitizeErrorMessage(err),
195
212
  };
196
213
  }
197
214
  }
@@ -220,6 +237,16 @@ export class RpcRegistry {
220
237
  }
221
238
  let response;
222
239
  if (Array.isArray(req)) {
240
+ // Security: cap batch size
241
+ if (req.length > this.maxBatchSize) {
242
+ const errorResponse = {
243
+ id: "batch",
244
+ ok: false,
245
+ stats: { remainingRequests: 0, resetInSeconds: 0 },
246
+ error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,
247
+ };
248
+ return { response: errorResponse };
249
+ }
223
250
  response = await Promise.all(req.map((r) => this.processRequestHttp(r, ip, httpReq)));
224
251
  }
225
252
  else {
@@ -228,4 +255,18 @@ export class RpcRegistry {
228
255
  return { response };
229
256
  }
230
257
  }
258
+ /**
259
+ * Sanitize error messages before sending to clients.
260
+ * In production, returns a generic message to prevent information leakage.
261
+ * In development, returns the actual error message for debugging.
262
+ */
263
+ function sanitizeErrorMessage(err) {
264
+ if (process.env.NODE_ENV === "production") {
265
+ return "Server error";
266
+ }
267
+ if (err instanceof Error) {
268
+ return err.message;
269
+ }
270
+ return "Server error";
271
+ }
231
272
  //# sourceMappingURL=rpcRegistry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpcRegistry.js","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport type http from \"http\";\nimport { promisify } from \"util\";\nimport WebSocket from \"ws\";\nimport { gzip } from \"zlib\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumMethodDef } from \"./defineMethod.js\";\nimport type { HeliumMiddleware } from \"./middleware.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\n\ninterface SocketMetadata {\n ip: string;\n req: http.IncomingMessage;\n}\n\nexport interface HttpRpcResult {\n response: RpcResponse | RpcResponse[];\n}\n\nexport class RpcRegistry {\n private methods = new Map<string, HeliumMethodDef<any, any>>();\n private middleware: HeliumMiddleware | null = null;\n private rateLimiter: RateLimiter | null = null;\n private socketMetadata = new WeakMap<WebSocket, SocketMetadata>();\n\n register(id: string, def: HeliumMethodDef<any, any>) {\n def.__id = id;\n this.methods.set(id, def);\n }\n\n setMiddleware(middleware: HeliumMiddleware) {\n this.middleware = middleware;\n }\n\n setRateLimiter(rateLimiter: RateLimiter) {\n this.rateLimiter = rateLimiter;\n }\n\n /**\n * Store metadata about a WebSocket connection.\n * Should be called when a new connection is established.\n */\n setSocketMetadata(socket: WebSocket, ip: string, req: http.IncomingMessage) {\n this.socketMetadata.set(socket, { ip, req });\n }\n\n private getStats(socket: WebSocket): RpcStats {\n if (!this.rateLimiter) {\n return { remainingRequests: Infinity, resetInSeconds: 0 };\n }\n\n const stats = this.rateLimiter.getConnectionStats(socket);\n if (!stats) {\n return { remainingRequests: 0, resetInSeconds: 0 };\n }\n\n const now = Date.now();\n const resetInSeconds = Math.ceil((stats.resetTimeMs - now) / 1000);\n\n return {\n remainingRequests: stats.remainingMessages,\n resetInSeconds: Math.max(0, resetInSeconds),\n };\n }\n\n private async processRequest(req: RpcRequest, socket: WebSocket): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const metadata = this.socketMetadata.get(socket);\n const ctx: HeliumContext = {\n req: {\n ip: metadata?.ip || \"unknown\",\n headers: metadata?.req.headers || {},\n url: metadata?.req.url,\n method: metadata?.req.method,\n raw: metadata?.req as http.IncomingMessage,\n },\n };\n let result: any;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n // If next() was not called, the middleware blocked the request\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n // No middleware, execute handler directly\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: this.getStats(socket),\n result,\n };\n } catch (err: any) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: err?.message ?? \"Server error\",\n };\n }\n }\n\n async handleMessage(socket: WebSocket, raw: string | Buffer) {\n let req: RpcRequest | RpcRequest[];\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n return;\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n response = await Promise.all(req.map((r) => this.processRequest(r, socket)));\n } else {\n response = await this.processRequest(req, socket);\n }\n\n const encoded = msgpackEncode(prepareForMsgpack(response));\n // Compress if larger than 1KB\n if (encoded.length > 1024) {\n const compressed = await gzipAsync(encoded);\n socket.send(compressed);\n } else {\n socket.send(encoded as Buffer);\n }\n }\n\n private async processRequestHttp(req: RpcRequest, ip: string, httpReq: http.IncomingMessage): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const ctx: HeliumContext = {\n req: {\n ip,\n headers: httpReq.headers,\n url: httpReq.url,\n method: httpReq.method,\n raw: httpReq,\n },\n };\n let result: unknown;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n result,\n };\n } catch (err: unknown) {\n const errorMessage = err instanceof Error ? err.message : \"Server error\";\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: errorMessage,\n };\n }\n }\n\n /**\n * Handle an HTTP-based RPC request.\n * This is an alternative to WebSocket for environments where HTTP performs better\n * (e.g., mobile networks with high latency where HTTP/2 multiplexing helps).\n */\n async handleHttpRequest(reqBody: Buffer | string, ip: string, httpReq: http.IncomingMessage): Promise<HttpRpcResult> {\n let req: RpcRequest | RpcRequest[];\n\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(reqBody) ? reqBody : Buffer.from(reqBody);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n const errorResponse: RpcResponse = {\n id: \"unknown\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: \"Invalid request format\",\n };\n return {\n response: errorResponse,\n };\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n response = await Promise.all(req.map((r) => this.processRequestHttp(r, ip, httpReq)));\n } else {\n response = await this.processRequestHttp(req as RpcRequest, ip, httpReq);\n }\n\n return { response };\n }\n}\n"]}
1
+ {"version":3,"file":"rpcRegistry.js","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport type http from \"http\";\nimport { promisify } from \"util\";\nimport WebSocket from \"ws\";\nimport { gzip } from \"zlib\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { log } from \"../utils/logger.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumMethodDef } from \"./defineMethod.js\";\nimport type { HeliumMiddleware } from \"./middleware.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\n\ninterface SocketMetadata {\n ip: string;\n req: http.IncomingMessage;\n}\n\nexport interface HttpRpcResult {\n response: RpcResponse | RpcResponse[];\n}\n\nexport class RpcRegistry {\n private methods = new Map<string, HeliumMethodDef<any, any>>();\n private middleware: HeliumMiddleware | null = null;\n private rateLimiter: RateLimiter | null = null;\n private socketMetadata = new WeakMap<WebSocket, SocketMetadata>();\n private maxBatchSize: number = 20;\n\n register(id: string, def: HeliumMethodDef<any, any>) {\n def.__id = id;\n this.methods.set(id, def);\n }\n\n setMiddleware(middleware: HeliumMiddleware) {\n this.middleware = middleware;\n }\n\n setRateLimiter(rateLimiter: RateLimiter) {\n this.rateLimiter = rateLimiter;\n }\n\n setMaxBatchSize(size: number) {\n this.maxBatchSize = size;\n }\n\n /**\n * Store metadata about a WebSocket connection.\n * Should be called when a new connection is established.\n */\n setSocketMetadata(socket: WebSocket, ip: string, req: http.IncomingMessage) {\n this.socketMetadata.set(socket, { ip, req });\n }\n\n private getStats(socket: WebSocket): RpcStats {\n if (!this.rateLimiter) {\n return { remainingRequests: Infinity, resetInSeconds: 0 };\n }\n\n const stats = this.rateLimiter.getConnectionStats(socket);\n if (!stats) {\n return { remainingRequests: 0, resetInSeconds: 0 };\n }\n\n const now = Date.now();\n const resetInSeconds = Math.ceil((stats.resetTimeMs - now) / 1000);\n\n return {\n remainingRequests: stats.remainingMessages,\n resetInSeconds: Math.max(0, resetInSeconds),\n };\n }\n\n private async processRequest(req: RpcRequest, socket: WebSocket): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const metadata = this.socketMetadata.get(socket);\n const ctx: HeliumContext = {\n req: {\n ip: metadata?.ip || \"unknown\",\n headers: metadata?.req.headers || {},\n url: metadata?.req.url,\n method: metadata?.req.method,\n raw: metadata?.req as http.IncomingMessage,\n },\n };\n let result: any;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n // If next() was not called, the middleware blocked the request\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n // No middleware, execute handler directly\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: this.getStats(socket),\n result,\n };\n } catch (err: any) {\n log(\"error\", `RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n async handleMessage(socket: WebSocket, raw: string | Buffer) {\n let req: RpcRequest | RpcRequest[];\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n return;\n }\n\n // Security: cap batch size\n if (Array.isArray(req) && req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: this.getStats(socket),\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n socket.send(msgpackEncode(prepareForMsgpack(errorResponse)) as Buffer);\n return;\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n response = await Promise.all(req.map((r) => this.processRequest(r, socket)));\n } else {\n response = await this.processRequest(req, socket);\n }\n\n const encoded = msgpackEncode(prepareForMsgpack(response));\n // Compress if larger than 1KB\n if (encoded.length > 1024) {\n const compressed = await gzipAsync(encoded);\n socket.send(compressed);\n } else {\n socket.send(encoded as Buffer);\n }\n }\n\n private async processRequestHttp(req: RpcRequest, ip: string, httpReq: http.IncomingMessage): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const ctx: HeliumContext = {\n req: {\n ip,\n headers: httpReq.headers,\n url: httpReq.url,\n method: httpReq.method,\n raw: httpReq,\n },\n };\n let result: unknown;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n result,\n };\n } catch (err: unknown) {\n log(\"error\", `HTTP RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n /**\n * Handle an HTTP-based RPC request.\n * This is an alternative to WebSocket for environments where HTTP performs better\n * (e.g., mobile networks with high latency where HTTP/2 multiplexing helps).\n */\n async handleHttpRequest(reqBody: Buffer | string, ip: string, httpReq: http.IncomingMessage): Promise<HttpRpcResult> {\n let req: RpcRequest | RpcRequest[];\n\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(reqBody) ? reqBody : Buffer.from(reqBody);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n const errorResponse: RpcResponse = {\n id: \"unknown\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: \"Invalid request format\",\n };\n return {\n response: errorResponse,\n };\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n // Security: cap batch size\n if (req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n return { response: errorResponse };\n }\n response = await Promise.all(req.map((r) => this.processRequestHttp(r, ip, httpReq)));\n } else {\n response = await this.processRequestHttp(req as RpcRequest, ip, httpReq);\n }\n\n return { response };\n }\n}\n\n/**\n * Sanitize error messages before sending to clients.\n * In production, returns a generic message to prevent information leakage.\n * In development, returns the actual error message for debugging.\n */\nfunction sanitizeErrorMessage(err: unknown): string {\n if (process.env.NODE_ENV === \"production\") {\n return \"Server error\";\n }\n if (err instanceof Error) {\n return err.message;\n }\n return \"Server error\";\n}\n"]}
@@ -2,4 +2,9 @@ import type { HeliumRpcSecurityConfig } from "./config.js";
2
2
  export declare function initializeSecurity(config: Required<HeliumRpcSecurityConfig>): void;
3
3
  export declare function generateConnectionToken(): string;
4
4
  export declare function verifyConnectionToken(token: string): boolean;
5
+ /**
6
+ * Reset security state. Only for testing.
7
+ * @internal
8
+ */
9
+ export declare function resetSecurity(): void;
5
10
  //# sourceMappingURL=security.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/server/security.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAU3D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAAG,IAAI,CAalF;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAmC5D"}
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/server/security.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAO3D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAAG,IAAI,CAelF;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAmC5D;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}