@robiki/proxy 1.0.0 → 1.0.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.
@@ -0,0 +1,172 @@
1
+ import { OutgoingHttpHeaders, IncomingMessage } from 'node:http';
2
+ import { IncomingHttpHeaders } from 'node:http2';
3
+ import { TLSSocket } from 'node:tls';
4
+ import WebSocket from 'ws';
5
+
6
+ declare enum RequestType {
7
+ API = "api",
8
+ STREAM = "stream",
9
+ WEBSOCKET = "websocket"
10
+ }
11
+ interface TLSWebSocket extends WebSocket {
12
+ _socket: TLSSocket;
13
+ }
14
+ type Router = (req: any, res: any) => void;
15
+ type WebSocketRouter = (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders) => void;
16
+ type Streamer = (stream: any, headers: any, flags: any) => void;
17
+ interface ForwardValidationResult {
18
+ status: boolean;
19
+ message?: string;
20
+ code?: number;
21
+ headers?: OutgoingHttpHeaders;
22
+ }
23
+ interface ConnectionInfo {
24
+ id: number;
25
+ method: string;
26
+ path: string;
27
+ remoteAddress: string;
28
+ scheme: string;
29
+ authority: string;
30
+ origin: string;
31
+ headers: IncomingHttpHeaders;
32
+ query: URLSearchParams;
33
+ type: RequestType;
34
+ respond: (status: number, headers?: Record<string, string>, body?: string) => void;
35
+ end: (body?: string) => void;
36
+ }
37
+
38
+ /**
39
+ * Route configuration for a specific domain/host
40
+ */
41
+ interface RouteConfig {
42
+ /** Target host:port to proxy to */
43
+ target: string;
44
+ /** Enable SSL/TLS for the target */
45
+ ssl?: boolean;
46
+ /** Remap the URL path before forwarding */
47
+ remap?: (url: string) => string;
48
+ /** Custom CORS configuration */
49
+ cors?: CorsConfig;
50
+ /** Validation function for this route */
51
+ validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
52
+ }
53
+ /**
54
+ * CORS configuration
55
+ */
56
+ interface CorsConfig {
57
+ /** Allowed origins (array of strings or '*' for all) */
58
+ origin?: string | string[];
59
+ /** Allowed HTTP methods */
60
+ methods?: string[];
61
+ /** Allowed headers */
62
+ allowedHeaders?: string[];
63
+ /** Exposed headers */
64
+ exposedHeaders?: string[];
65
+ /** Allow credentials */
66
+ credentials?: boolean;
67
+ /** Max age for preflight cache */
68
+ maxAge?: number;
69
+ }
70
+ /**
71
+ * SSL/TLS certificate configuration
72
+ */
73
+ interface CertificateConfig {
74
+ /** Path to private key file or key content */
75
+ key: string;
76
+ /** Path to certificate file or cert content */
77
+ cert: string;
78
+ /** Path to CA file or CA content */
79
+ ca?: string;
80
+ /** Allow HTTP/1.1 fallback */
81
+ allowHTTP1?: boolean;
82
+ }
83
+ /**
84
+ * Server configuration
85
+ */
86
+ interface ServerConfig {
87
+ /** SSL/TLS certificate configuration */
88
+ ssl?: CertificateConfig;
89
+ /** Route configurations mapped by host */
90
+ routes: Record<string, RouteConfig>;
91
+ /** Default CORS configuration */
92
+ cors?: CorsConfig;
93
+ /** Global validation function */
94
+ validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
95
+ }
96
+ /**
97
+ * Proxy configuration manager
98
+ */
99
+ declare class ProxyConfig {
100
+ private config;
101
+ private sslConfig?;
102
+ constructor(config: ServerConfig);
103
+ /**
104
+ * Initialize SSL configuration
105
+ */
106
+ private initializeSSL;
107
+ /**
108
+ * Get SSL configuration
109
+ */
110
+ getSSL(): {
111
+ key: Buffer;
112
+ cert: Buffer;
113
+ ca?: Buffer;
114
+ allowHTTP1?: boolean;
115
+ } | undefined;
116
+ /**
117
+ * Get route configuration for a host
118
+ */
119
+ getRoute(host: string): RouteConfig | undefined;
120
+ /**
121
+ * Get target for a host
122
+ */
123
+ getTarget(host: string): {
124
+ target: undefined;
125
+ ssl: undefined;
126
+ remap: undefined;
127
+ } | {
128
+ target: string;
129
+ ssl: {
130
+ key: Buffer;
131
+ cert: Buffer;
132
+ ca?: Buffer;
133
+ allowHTTP1?: boolean;
134
+ } | undefined;
135
+ remap: ((url: string) => string) | undefined;
136
+ };
137
+ /**
138
+ * Get CORS headers for a request
139
+ */
140
+ getCorsHeaders(origin: string, host?: string): OutgoingHttpHeaders;
141
+ /**
142
+ * Validate a request
143
+ */
144
+ validate(info: ConnectionInfo): Promise<ForwardValidationResult>;
145
+ /**
146
+ * Get ports to listen on
147
+ */
148
+ getPorts(): number[];
149
+ /**
150
+ * Get the full configuration
151
+ */
152
+ getConfig(): ServerConfig;
153
+ }
154
+ /**
155
+ * Load configuration with cascading priority:
156
+ * 1. Programmatic config (highest priority)
157
+ * 2. Environment variables
158
+ * 3. Config file
159
+ * 4. Defaults (lowest priority)
160
+ */
161
+ declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): ProxyConfig;
162
+ /**
163
+ * Load configuration from a file (deprecated - use loadConfig instead)
164
+ */
165
+ declare function loadConfigFromFile(path: string): ProxyConfig;
166
+ /**
167
+ * Load configuration from environment variables (deprecated - use loadConfig instead)
168
+ */
169
+ declare function loadConfigFromEnv(): ProxyConfig;
170
+
171
+ export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
172
+ export type { CorsConfig as C, ForwardValidationResult as F, Router as R, ServerConfig as S, TLSWebSocket as T, WebSocketRouter as W, Streamer as a, RouteConfig as b, CertificateConfig as c, ConnectionInfo as d };
@@ -0,0 +1,174 @@
1
+ import { OutgoingHttpHeaders, IncomingMessage } from 'node:http';
2
+ import { IncomingHttpHeaders } from 'node:http2';
3
+ import { TLSSocket } from 'node:tls';
4
+ import WebSocket from 'ws';
5
+
6
+ declare enum RequestType {
7
+ API = "api",
8
+ STREAM = "stream",
9
+ WEBSOCKET = "websocket"
10
+ }
11
+ interface TLSWebSocket extends WebSocket {
12
+ _socket: TLSSocket;
13
+ }
14
+ type Router = (req: any, res: any) => void;
15
+ type WebSocketRouter = (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders) => void;
16
+ type Streamer = (stream: any, headers: any, flags: any) => void;
17
+ interface ForwardValidationResult {
18
+ status: boolean;
19
+ message?: string;
20
+ code?: number;
21
+ headers?: OutgoingHttpHeaders;
22
+ }
23
+ interface ConnectionInfo {
24
+ id: number;
25
+ method: string;
26
+ path: string;
27
+ remoteAddress: string;
28
+ scheme: string;
29
+ authority: string;
30
+ origin: string;
31
+ headers: IncomingHttpHeaders;
32
+ query: URLSearchParams;
33
+ type: RequestType;
34
+ respond: (status: number, headers?: Record<string, string>, body?: string) => void;
35
+ end: (body?: string) => void;
36
+ }
37
+
38
+ /**
39
+ * Route configuration for a specific domain/host
40
+ */
41
+ interface RouteConfig {
42
+ /** Target host:port to proxy to */
43
+ target: string;
44
+ /** Enable SSL/TLS for the target */
45
+ ssl?: boolean;
46
+ /** Remap the URL path before forwarding */
47
+ remap?: (url: string) => string;
48
+ /** Custom CORS configuration */
49
+ cors?: CorsConfig;
50
+ /** Validation function for this route */
51
+ validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
52
+ }
53
+ /**
54
+ * CORS configuration
55
+ */
56
+ interface CorsConfig {
57
+ /** Allowed origins (array of strings or '*' for all) */
58
+ origin?: string | string[];
59
+ /** Allowed HTTP methods */
60
+ methods?: string[];
61
+ /** Allowed headers */
62
+ allowedHeaders?: string[];
63
+ /** Exposed headers */
64
+ exposedHeaders?: string[];
65
+ /** Allow credentials */
66
+ credentials?: boolean;
67
+ /** Max age for preflight cache */
68
+ maxAge?: number;
69
+ }
70
+ /**
71
+ * SSL/TLS certificate configuration
72
+ */
73
+ interface CertificateConfig {
74
+ /** Path to private key file or key content */
75
+ key: string;
76
+ /** Path to certificate file or cert content */
77
+ cert: string;
78
+ /** Path to CA file or CA content */
79
+ ca?: string;
80
+ /** Allow HTTP/1.1 fallback */
81
+ allowHTTP1?: boolean;
82
+ }
83
+ /**
84
+ * Server configuration
85
+ */
86
+ interface ServerConfig {
87
+ /** SSL/TLS certificate configuration */
88
+ ssl?: CertificateConfig;
89
+ /** Route configurations mapped by host */
90
+ routes: Record<string, RouteConfig>;
91
+ /** Default CORS configuration */
92
+ cors?: CorsConfig;
93
+ /** Global validation function */
94
+ validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
95
+ /** Ports to listen on (defaults to [443, 8080, 9229]) */
96
+ ports?: number[];
97
+ }
98
+ /**
99
+ * Proxy configuration manager
100
+ */
101
+ declare class ProxyConfig {
102
+ private config;
103
+ private sslConfig?;
104
+ constructor(config: ServerConfig);
105
+ /**
106
+ * Initialize SSL configuration
107
+ */
108
+ private initializeSSL;
109
+ /**
110
+ * Get SSL configuration
111
+ */
112
+ getSSL(): {
113
+ key: Buffer;
114
+ cert: Buffer;
115
+ ca?: Buffer;
116
+ allowHTTP1?: boolean;
117
+ } | undefined;
118
+ /**
119
+ * Get route configuration for a host
120
+ */
121
+ getRoute(host: string): RouteConfig | undefined;
122
+ /**
123
+ * Get target for a host
124
+ */
125
+ getTarget(host: string): {
126
+ target: undefined;
127
+ ssl: undefined;
128
+ remap: undefined;
129
+ } | {
130
+ target: string;
131
+ ssl: {
132
+ key: Buffer;
133
+ cert: Buffer;
134
+ ca?: Buffer;
135
+ allowHTTP1?: boolean;
136
+ } | undefined;
137
+ remap: ((url: string) => string) | undefined;
138
+ };
139
+ /**
140
+ * Get CORS headers for a request
141
+ */
142
+ getCorsHeaders(origin: string, host?: string): OutgoingHttpHeaders;
143
+ /**
144
+ * Validate a request
145
+ */
146
+ validate(info: ConnectionInfo): Promise<ForwardValidationResult>;
147
+ /**
148
+ * Get ports to listen on
149
+ */
150
+ getPorts(): number[];
151
+ /**
152
+ * Get the full configuration
153
+ */
154
+ getConfig(): ServerConfig;
155
+ }
156
+ /**
157
+ * Load configuration with cascading priority:
158
+ * 1. Programmatic config (highest priority)
159
+ * 2. Environment variables
160
+ * 3. Config file
161
+ * 4. Defaults (lowest priority)
162
+ */
163
+ declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): ProxyConfig;
164
+ /**
165
+ * Load configuration from a file (deprecated - use loadConfig instead)
166
+ */
167
+ declare function loadConfigFromFile(path: string): ProxyConfig;
168
+ /**
169
+ * Load configuration from environment variables (deprecated - use loadConfig instead)
170
+ */
171
+ declare function loadConfigFromEnv(): ProxyConfig;
172
+
173
+ export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
174
+ export type { CorsConfig as C, ForwardValidationResult as F, Router as R, ServerConfig as S, TLSWebSocket as T, WebSocketRouter as W, Streamer as a, RouteConfig as b, CertificateConfig as c, ConnectionInfo as d };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { T as TLSWebSocket, P as ProxyConfig, S as ServerConfig, R as Router, a as Streamer, W as WebSocketRouter } from './config-_6LOsppp.js';
2
- export { c as CertificateConfig, d as ConnectionInfo, C as CorsConfig, F as ForwardValidationResult, e as RequestType, b as RouteConfig, g as getConfig, l as loadConfig } from './config-_6LOsppp.js';
1
+ import { P as ProxyConfig, T as TLSWebSocket, S as ServerConfig, R as Router, a as Streamer, W as WebSocketRouter } from './config-CeJ1tf8T.js';
2
+ export { c as CertificateConfig, d as ConnectionInfo, C as CorsConfig, F as ForwardValidationResult, e as RequestType, b as RouteConfig, l as loadConfig } from './config-CeJ1tf8T.js';
3
3
  import { IncomingMessage, ServerResponse, IncomingHttpHeaders as IncomingHttpHeaders$1 } from 'node:http';
4
4
  import { ServerHttp2Stream, IncomingHttpHeaders } from 'node:http2';
5
5
  import 'node:tls';
6
6
  import 'ws';
7
7
 
8
- declare const restAPIProxyHandler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
8
+ declare const restAPIProxyHandler: (req: IncomingMessage, res: ServerResponse, config: ProxyConfig) => Promise<void>;
9
9
 
10
- declare const streamAPIProxyHandler: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders) => Promise<void>;
10
+ declare const streamAPIProxyHandler: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, config: ProxyConfig) => Promise<void>;
11
11
 
12
- declare const websocketAPIProxyHandler: (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders$1) => Promise<void>;
12
+ declare const websocketAPIProxyHandler: (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders$1, config: ProxyConfig) => Promise<void>;
13
13
 
14
14
  /**
15
15
  * Proxy server instance
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { createServer as createServer$1, request } from 'node:https';
5
5
  import { constants, createSecureServer, createServer, connect } from 'node:http2';
6
6
  import { exec } from 'node:child_process';
7
7
  import { WebSocketServer, WebSocket } from 'ws';
8
- import { getConfig, loadConfig } from './utils/config.js';
8
+ import { loadConfig } from './utils/config.js';
9
9
  import 'node:fs';
10
10
  import 'node:path';
11
11
 
@@ -212,11 +212,6 @@ function isMediaFile(path) {
212
212
  ".flac",
213
213
  ".aac",
214
214
  ".m4a",
215
- ".ogg",
216
- ".wav",
217
- ".flac",
218
- ".aac",
219
- ".m4a",
220
215
  ".woff2",
221
216
  ".woff",
222
217
  ".ttf",
@@ -229,8 +224,8 @@ function isMediaFile(path) {
229
224
  });
230
225
  }
231
226
 
232
- const restAPIProxyHandler = async (req, res) => {
233
- const config = getConfig();
227
+ const DEBUG$2 = process.env.DEBUG === "true";
228
+ const restAPIProxyHandler = async (req, res, config) => {
234
229
  const { target, ssl, remap } = config.getTarget(req.headers.host || req.headers[":authority"]?.toString() || "");
235
230
  if (req.httpVersion === "2.0" && ssl) return;
236
231
  if (!target) {
@@ -238,12 +233,12 @@ const restAPIProxyHandler = async (req, res) => {
238
233
  res.end("Not Found");
239
234
  return;
240
235
  }
241
- console.log("HTTP1 rest proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, req.headers.host);
236
+ if (DEBUG$2) console.log("HTTP1 rest proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, req.headers.host);
242
237
  if (remap) req.url = remap(req.url || "");
243
238
  const requestFn = ssl ? request : request$1;
244
239
  const headers = req.httpVersion === "2.0" ? http2HeadersToHttp1Headers(req.headers) : req.headers;
245
240
  const method = req.httpVersion === "2.0" ? req.headers[":method"]?.toString() : req.method;
246
- console.log("Proxy Request::", req.url, method, headers);
241
+ if (DEBUG$2) console.log("Proxy Request::", req.url, method, headers);
247
242
  const proxy = requestFn(
248
243
  `${ssl ? "https" : "http"}://${target}${req.url || ""}`,
249
244
  {
@@ -268,11 +263,18 @@ const restAPIProxyHandler = async (req, res) => {
268
263
  }
269
264
  });
270
265
  proxyRes.on("error", (error) => {
271
- console.error("Proxy response error:", error);
266
+ if (DEBUG$2) console.error("Proxy response error:", error);
272
267
  if (!res.destroyed) res.destroy(error);
273
268
  });
274
269
  }
275
270
  );
271
+ proxy.on("error", (error) => {
272
+ if (DEBUG$2) console.error("Proxy request error:", error);
273
+ if (!res.headersSent) {
274
+ res.writeHead(502, { "Content-Type": "text/plain" });
275
+ res.end("Bad Gateway");
276
+ }
277
+ });
276
278
  req.on("data", (chunk) => {
277
279
  if (!proxy.writableEnded && !proxy.closed && !proxy.destroyed) {
278
280
  proxy.write(chunk);
@@ -284,22 +286,27 @@ const restAPIProxyHandler = async (req, res) => {
284
286
  }
285
287
  });
286
288
  req.on("error", (error) => {
287
- console.error("Client request error:", error);
289
+ if (DEBUG$2) console.error("Client request error:", error);
288
290
  if (!proxy.destroyed) proxy.destroy(error);
289
291
  });
290
292
  };
291
293
 
292
- const streamAPIProxyHandler = async (stream, headers) => {
293
- const config = getConfig();
294
+ const DEBUG$1 = process.env.DEBUG === "true";
295
+ const streamAPIProxyHandler = async (stream, headers, config) => {
294
296
  const { target, ssl, remap } = config.getTarget(headers[":authority"] || "");
295
297
  if (!ssl) return;
296
298
  if (!target) {
297
299
  stream.destroy(new Error("Not Found"));
298
300
  return;
299
301
  }
300
- console.log("HTTP2 stream proxy", `${ssl ? "https" : "http"}://${target}${headers[":path"]}`, headers[":authority"]);
302
+ if (DEBUG$1)
303
+ console.log(
304
+ "HTTP2 stream proxy",
305
+ `${ssl ? "https" : "http"}://${target}${headers[":path"]}`,
306
+ headers[":authority"]
307
+ );
301
308
  if (remap) headers[":path"] = remap(headers[":path"] || "");
302
- console.log("Proxy Request::", headers[":path"]);
309
+ if (DEBUG$1) console.log("Proxy Request::", headers[":path"]);
303
310
  const proxy = connect(`https://${target}${headers[":path"]}`, {
304
311
  ...ssl,
305
312
  rejectUnauthorized: false
@@ -308,7 +315,7 @@ const streamAPIProxyHandler = async (stream, headers) => {
308
315
  const request = proxy.request(headers);
309
316
  request.on("response", (headerResponse) => {
310
317
  if (!stream.writableEnded && !stream.closed && !stream.destroyed) {
311
- console.log("Proxy Response::", headerResponse[":status"], `for ${headers[":path"]}`);
318
+ if (DEBUG$1) console.log("Proxy Response::", headerResponse[":status"], `for ${headers[":path"]}`);
312
319
  if (headers[":path"] && isMediaFile(headers[":path"])) {
313
320
  headerResponse["cache-control"] = `public, max-age=${day()}`;
314
321
  }
@@ -335,7 +342,7 @@ const streamAPIProxyHandler = async (stream, headers) => {
335
342
  if (!stream.closed && !stream.destroyed) stream.close();
336
343
  });
337
344
  stream.on("error", (error) => {
338
- console.error("HTTP2 stream proxy error:", error);
345
+ if (DEBUG$1) console.error("HTTP2 stream proxy error:", error);
339
346
  if (!request.destroyed) request.destroy(error);
340
347
  if (!proxy.closed) proxy.close();
341
348
  });
@@ -353,28 +360,28 @@ const streamAPIProxyHandler = async (stream, headers) => {
353
360
  if (!stream.closed && !stream.destroyed) stream.close();
354
361
  });
355
362
  request.on("error", (error) => {
356
- console.error("HTTP2 request proxy error:", error);
363
+ if (DEBUG$1) console.error("HTTP2 request proxy error:", error);
357
364
  if (!stream.destroyed) stream.destroy(error);
358
365
  return !proxy.closed && proxy.close();
359
366
  });
360
367
  proxy.on("timeout", () => {
361
- console.error("HTTP/2 client timeout");
368
+ if (DEBUG$1) console.error("HTTP/2 client timeout");
362
369
  if (!stream.destroyed) stream.destroy(new Error("HTTP/2 client timeout"));
363
370
  });
364
371
  });
365
372
  proxy.on("error", (error) => {
366
- console.error("HTTP2 proxy connection error:", error);
373
+ if (DEBUG$1) console.error("HTTP2 proxy connection error:", error);
367
374
  if (!stream.destroyed) {
368
375
  stream.destroy(error);
369
376
  }
370
377
  });
371
378
  };
372
379
 
373
- const websocketAPIProxyHandler = async (req, socket, headers) => {
374
- const config = getConfig();
380
+ const DEBUG = process.env.DEBUG === "true";
381
+ const websocketAPIProxyHandler = async (req, socket, headers, config) => {
375
382
  const { target, ssl, remap } = config.getTarget(req.headers.host || "");
376
383
  if (!target) return socket.close();
377
- console.log("HTTP2 websocket proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, headers.host);
384
+ if (DEBUG) console.log("HTTP2 websocket proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, headers.host);
378
385
  if (remap) req.url = remap(req.url || "");
379
386
  const proxy = new WebSocket(
380
387
  `${ssl ? "wss" : "ws"}://${target}${req.url || ""}`,
@@ -398,7 +405,7 @@ const websocketAPIProxyHandler = async (req, socket, headers) => {
398
405
  proxy.on("close", () => socket.close());
399
406
  socket.on("close", () => proxy.close());
400
407
  proxy.on("error", (error) => {
401
- console.error("WebSocket proxy error:", error);
408
+ if (DEBUG) console.error("WebSocket proxy error:", error);
402
409
  socket.close();
403
410
  });
404
411
  };
@@ -422,14 +429,24 @@ class ProxyServer {
422
429
  return { ssl, ports };
423
430
  };
424
431
  const createServers = ({ ssl: ssl2, ports: ports2 }) => {
432
+ const boundRestHandler = (req, res) => {
433
+ if (req.url === "/robiki-proxy/health" && req.method === "GET") {
434
+ res.writeHead(200, { "Content-Type": "text/plain" });
435
+ res.end("OK");
436
+ return;
437
+ }
438
+ return restAPIProxyHandler(req, res, this.config);
439
+ };
440
+ const boundStreamHandler = (stream, headers) => streamAPIProxyHandler(stream, headers, this.config);
441
+ const boundWebsocketHandler = (req, socket, headers) => websocketAPIProxyHandler(req, socket, headers, this.config);
425
442
  for (const port of ports2) {
426
443
  let server;
427
444
  if (ssl2) {
428
- server = http2(restAPIProxyHandler, streamAPIProxyHandler, { ...ssl2, port });
445
+ server = http2(boundRestHandler, boundStreamHandler, { ...ssl2, port });
429
446
  } else {
430
- server = http(restAPIProxyHandler, { port });
447
+ server = http(boundRestHandler, { port });
431
448
  }
432
- websocket(server, websocketAPIProxyHandler, (info) => this.config.validate(info));
449
+ websocket(server, boundWebsocketHandler, (info) => this.config.validate(info));
433
450
  this.servers.push(server);
434
451
  }
435
452
  return this.servers;
@@ -448,9 +465,16 @@ class ProxyServer {
448
465
  */
449
466
  async stop() {
450
467
  console.log("Stopping proxy server...");
451
- for (const server of this.servers) {
452
- server.close();
453
- }
468
+ await Promise.all(
469
+ this.servers.map(
470
+ (server) => new Promise((resolve, reject) => {
471
+ server.close((err) => {
472
+ if (err) reject(err);
473
+ else resolve();
474
+ });
475
+ })
476
+ )
477
+ );
454
478
  this.servers = [];
455
479
  console.log("Proxy server stopped");
456
480
  }
@@ -485,17 +509,27 @@ function createCustomProxy(config, handlers) {
485
509
  return cfg;
486
510
  };
487
511
  const createServers = ({ ssl, ports, proxyConfig }) => {
512
+ const boundRestHandler = (req, res) => {
513
+ if (req.url === "/robiki-proxy/health" && req.method === "GET") {
514
+ res.writeHead(200, { "Content-Type": "text/plain" });
515
+ res.end("OK");
516
+ return;
517
+ }
518
+ return restAPIProxyHandler(req, res, proxyConfig);
519
+ };
520
+ const boundStreamHandler = (stream, headers) => streamAPIProxyHandler(stream, headers, proxyConfig);
521
+ const boundWebsocketHandler = (req, socket, headers) => websocketAPIProxyHandler(req, socket, headers, proxyConfig);
488
522
  for (const port of ports) {
489
523
  let server;
490
524
  if (ssl) {
491
- server = http2(handlers.rest || restAPIProxyHandler, handlers.stream || streamAPIProxyHandler, {
525
+ server = http2(handlers.rest || boundRestHandler, handlers.stream || boundStreamHandler, {
492
526
  ...ssl,
493
527
  port
494
528
  });
495
529
  } else {
496
- server = http(handlers.rest || restAPIProxyHandler, { port });
530
+ server = http(handlers.rest || boundRestHandler, { port });
497
531
  }
498
- websocket(server, handlers.websocket || websocketAPIProxyHandler);
532
+ websocket(server, handlers.websocket || boundWebsocketHandler, (info) => proxyConfig.validate(info));
499
533
  servers.push(server);
500
534
  }
501
535
  return proxyConfig;
@@ -507,9 +541,16 @@ function createCustomProxy(config, handlers) {
507
541
  start: async () => {
508
542
  },
509
543
  stop: async () => {
510
- for (const server of servers) {
511
- server.close();
512
- }
544
+ await Promise.all(
545
+ servers.map(
546
+ (server) => new Promise((resolve, reject) => {
547
+ server.close((err) => {
548
+ if (err) reject(err);
549
+ else resolve();
550
+ });
551
+ })
552
+ )
553
+ );
513
554
  }
514
555
  };
515
556
  };
@@ -539,4 +580,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
539
580
  Promise.resolve().then(() => startProxyServer()).catch((error) => handleStartupError(error));
540
581
  }
541
582
 
542
- export { ProxyServer, RequestType, createCustomProxy, createProxy, getConfig, loadConfig, restAPIProxyHandler, streamAPIProxyHandler, websocketAPIProxyHandler };
583
+ export { ProxyServer, RequestType, createCustomProxy, createProxy, loadConfig, restAPIProxyHandler, streamAPIProxyHandler, websocketAPIProxyHandler };
@@ -1,5 +1,5 @@
1
1
  import 'node:http';
2
- export { c as CertificateConfig, C as CorsConfig, P as ProxyConfig, b as RouteConfig, S as ServerConfig, g as getConfig, i as initConfig, l as loadConfig, h as loadConfigFromEnv, f as loadConfigFromFile } from '../config-_6LOsppp.js';
2
+ export { c as CertificateConfig, C as CorsConfig, P as ProxyConfig, b as RouteConfig, S as ServerConfig, l as loadConfig, g as loadConfigFromEnv, f as loadConfigFromFile } from '../config-CeJ1tf8T.js';
3
3
  import 'node:http2';
4
4
  import 'node:tls';
5
5
  import 'ws';
@@ -131,7 +131,7 @@ class ProxyConfig {
131
131
  * Get ports to listen on
132
132
  */
133
133
  getPorts() {
134
- return [443, 8080, 9229];
134
+ return this.config.ports || [443, 8080, 9229];
135
135
  }
136
136
  /**
137
137
  * Get the full configuration
@@ -140,17 +140,6 @@ class ProxyConfig {
140
140
  return this.config;
141
141
  }
142
142
  }
143
- let globalConfig;
144
- function initConfig(config) {
145
- globalConfig = new ProxyConfig(config);
146
- return globalConfig;
147
- }
148
- function getConfig() {
149
- if (!globalConfig) {
150
- throw new Error("Configuration not initialized. Call initConfig() first.");
151
- }
152
- return globalConfig;
153
- }
154
143
  function deepMerge(...objects) {
155
144
  const result = {};
156
145
  for (const obj of objects) {
@@ -206,13 +195,13 @@ function loadConfig(programmaticConfig) {
206
195
  const fileConfig = getConfigFromFile();
207
196
  const envConfig = getConfigFromEnv();
208
197
  const merged = deepMerge(defaults, fileConfig, envConfig, programmaticConfig || {});
209
- return initConfig(merged);
198
+ return new ProxyConfig(merged);
210
199
  }
211
200
  function loadConfigFromFile(path) {
212
201
  try {
213
202
  const configFile = readFileSync(resolve(path), "utf-8");
214
203
  const config = JSON.parse(configFile);
215
- return initConfig(config);
204
+ return new ProxyConfig(config);
216
205
  } catch (error) {
217
206
  console.error("Failed to load configuration file:", error);
218
207
  throw error;
@@ -220,7 +209,7 @@ function loadConfigFromFile(path) {
220
209
  }
221
210
  function loadConfigFromEnv() {
222
211
  const config = getConfigFromEnv();
223
- return initConfig(config);
212
+ return new ProxyConfig(config);
224
213
  }
225
214
 
226
- export { ProxyConfig, getConfig, initConfig, loadConfig, loadConfigFromEnv, loadConfigFromFile };
215
+ export { ProxyConfig, loadConfig, loadConfigFromEnv, loadConfigFromFile };