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.
- package/dist/client/rpcClient.d.ts +4 -0
- package/dist/client/rpcClient.d.ts.map +1 -1
- package/dist/client/rpcClient.js +206 -41
- package/dist/client/rpcClient.js.map +1 -1
- package/dist/server/config.d.ts +56 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +3 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/devServer.d.ts.map +1 -1
- package/dist/server/devServer.js +48 -3
- package/dist/server/devServer.js.map +1 -1
- package/dist/server/httpRouter.js +29 -5
- package/dist/server/httpRouter.js.map +1 -1
- package/dist/server/prodServer.d.ts.map +1 -1
- package/dist/server/prodServer.js +116 -7
- package/dist/server/prodServer.js.map +1 -1
- package/dist/server/rpcRegistry.d.ts +2 -0
- package/dist/server/rpcRegistry.d.ts.map +1 -1
- package/dist/server/rpcRegistry.js +44 -3
- package/dist/server/rpcRegistry.js.map +1 -1
- package/dist/server/security.d.ts +5 -0
- package/dist/server/security.d.ts.map +1 -1
- package/dist/server/security.js +25 -14
- package/dist/server/security.js.map +1 -1
- package/dist/server/serializer.d.ts +2 -1
- package/dist/server/serializer.d.ts.map +1 -1
- package/dist/server/serializer.js +20 -4
- package/dist/server/serializer.js.map +1 -1
- package/dist/utils/ipExtractor.d.ts.map +1 -1
- package/dist/utils/ipExtractor.js +20 -16
- package/dist/utils/ipExtractor.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
269
|
-
const
|
|
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;
|
|
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
|
|
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
|
-
|
|
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:
|
|
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":"AAAA,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEpF,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAKzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAWlC,MAAM,OAAO,WAAW;IAAxB;QACY,YAAO,GAAG,IAAI,GAAG,EAAqC,CAAC;QACvD,eAAU,GAA4B,IAAI,CAAC;QAC3C,gBAAW,GAAuB,IAAI,CAAC;QACvC,mBAAc,GAAG,IAAI,OAAO,EAA6B,CAAC;QAC1D,iBAAY,GAAW,EAAE,CAAC;IA2QtC,CAAC;IAzQG,QAAQ,CAAC,EAAU,EAAE,GAA8B;QAC/C,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,aAAa,CAAC,UAA4B;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,CAAC;IAED,cAAc,CAAC,WAAwB;QACnC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACnC,CAAC;IAED,eAAe,CAAC,IAAY;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,MAAiB,EAAE,EAAU,EAAE,GAAyB;QACtE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACjD,CAAC;IAEO,QAAQ,CAAC,MAAiB;QAC9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QAC9D,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,EAAE,iBAAiB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAEnE,OAAO;YACH,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;YAC1C,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC;SAC9C,CAAC;IACN,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,GAAe,EAAE,MAAiB;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC5B,KAAK,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE;aACxC,CAAC;QACN,CAAC;QAED,IAAI,CAAC;YACD,sCAAsC;YACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACjD,MAAM,GAAG,GAAkB;gBACvB,GAAG,EAAE;oBACD,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,SAAS;oBAC7B,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;oBACpC,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG;oBACtB,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM;oBAC5B,GAAG,EAAE,QAAQ,EAAE,GAA2B;iBAC7C;aACJ,CAAC;YACF,IAAI,MAAW,CAAC;YAEhB,gCAAgC;YAChC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,IAAI,UAAU,GAAG,KAAK,CAAC;gBACvB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CACzB;oBACI,GAAG;oBACH,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,GAAG,CAAC,MAAM;iBACzB,EACD,KAAK,IAAI,EAAE;oBACP,UAAU,GAAG,IAAI,CAAC;oBAClB,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC,CACJ,CAAC;gBAEF,+DAA+D;gBAC/D,IAAI,CAAC,UAAU,EAAE,CAAC;oBACd,OAAO;wBACH,EAAE,EAAE,GAAG,CAAC,EAAE;wBACV,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;wBAC5B,KAAK,EAAE,+BAA+B;qBACzC,CAAC;gBACN,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,0CAA0C;gBAC1C,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9C,CAAC;YAED,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,IAAI;gBACR,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC5B,MAAM;aACT,CAAC;QACN,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,CAAC,MAAM,WAAW,EAAE,GAAG,CAAC,CAAC;YACxD,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC5B,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC;aACnC,CAAC;QACN,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAiB,EAAE,GAAoB;QACvD,IAAI,GAA8B,CAAC;QACnC,IAAI,CAAC;YACD,4BAA4B;YAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,GAAG,GAAG,aAAa,CAAC,MAAM,CAA8B,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACL,OAAO;QACX,CAAC;QAED,2BAA2B;QAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACvD,MAAM,aAAa,GAAgB;gBAC/B,EAAE,EAAE,OAAO;gBACX,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC5B,KAAK,EAAE,cAAc,GAAG,CAAC,MAAM,uBAAuB,IAAI,CAAC,YAAY,EAAE;aAC5E,CAAC;YACF,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAW,CAAC,CAAC;YACvE,OAAO;QACX,CAAC;QAED,IAAI,QAAqC,CAAC;QAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACjF,CAAC;aAAM,CAAC;YACJ,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3D,8BAA8B;QAC9B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YACxB,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,OAAiB,CAAC,CAAC;QACnC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,GAAe,EAAE,EAAU,EAAE,OAA6B;QACvF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE;gBACzD,KAAK,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE;aACxC,CAAC;QACN,CAAC;QAED,IAAI,CAAC;YACD,sCAAsC;YACtC,MAAM,GAAG,GAAkB;gBACvB,GAAG,EAAE;oBACD,EAAE;oBACF,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,GAAG,EAAE,OAAO,CAAC,GAAG;oBAChB,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,GAAG,EAAE,OAAO;iBACf;aACJ,CAAC;YACF,IAAI,MAAe,CAAC;YAEpB,gCAAgC;YAChC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,IAAI,UAAU,GAAG,KAAK,CAAC;gBACvB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CACzB;oBACI,GAAG;oBACH,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,GAAG,CAAC,MAAM;iBACzB,EACD,KAAK,IAAI,EAAE;oBACP,UAAU,GAAG,IAAI,CAAC;oBAClB,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC,CACJ,CAAC;gBAEF,IAAI,CAAC,UAAU,EAAE,CAAC;oBACd,OAAO;wBACH,EAAE,EAAE,GAAG,CAAC,EAAE;wBACV,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE;wBACzD,KAAK,EAAE,+BAA+B;qBACzC,CAAC;gBACN,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9C,CAAC;YAED,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,IAAI;gBACR,KAAK,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE;gBACzD,MAAM;aACT,CAAC;QACN,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,GAAG,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,MAAM,WAAW,EAAE,GAAG,CAAC,CAAC;YAC7D,OAAO;gBACH,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,EAAE;gBACzD,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC;aACnC,CAAC;QACN,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CAAC,OAAwB,EAAE,EAAU,EAAE,OAA6B;QACvF,IAAI,GAA8B,CAAC;QAEnC,IAAI,CAAC;YACD,4BAA4B;YAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzE,GAAG,GAAG,aAAa,CAAC,MAAM,CAA8B,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,aAAa,GAAgB;gBAC/B,EAAE,EAAE,SAAS;gBACb,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE;gBAClD,KAAK,EAAE,wBAAwB;aAClC,CAAC;YACF,OAAO;gBACH,QAAQ,EAAE,aAAa;aAC1B,CAAC;QACN,CAAC;QAED,IAAI,QAAqC,CAAC;QAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,2BAA2B;YAC3B,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;gBACjC,MAAM,aAAa,GAAgB;oBAC/B,EAAE,EAAE,OAAO;oBACX,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE;oBAClD,KAAK,EAAE,cAAc,GAAG,CAAC,MAAM,uBAAuB,IAAI,CAAC,YAAY,EAAE;iBAC5E,CAAC;gBACF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;YACvC,CAAC;YACD,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1F,CAAC;aAAM,CAAC;YACJ,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAiB,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAC7E,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,CAAC;IACxB,CAAC;CACJ;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,GAAY;IACtC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QACxC,OAAO,cAAc,CAAC;IAC1B,CAAC;IACD,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,cAAc,CAAC;AAC1B,CAAC","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;
|
|
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"}
|