@robiki/proxy 1.0.2 → 1.0.4

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.
@@ -154,21 +154,15 @@ declare class ProxyConfig {
154
154
  getConfig(): ServerConfig;
155
155
  }
156
156
  /**
157
- * Load configuration with cascading priority:
157
+ * Load configuration with cascading priority (async):
158
158
  * 1. Programmatic config (highest priority)
159
159
  * 2. Environment variables
160
160
  * 3. Config file
161
161
  * 4. Defaults (lowest priority)
162
+ *
163
+ * Supports config file types: .json, .ts, .js
162
164
  */
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;
165
+ declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): Promise<ProxyConfig>;
172
166
 
173
- export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
167
+ export { ProxyConfig as P, RequestType as e, loadConfig as l };
174
168
  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,315 @@
1
+ import { stat, readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ function debug(message, data) {
5
+ if (process.env.DEBUG === "true") {
6
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
7
+ if (data !== void 0) {
8
+ const sanitized = JSON.stringify(data, (_, value) => typeof value === "function" ? "<function>" : value, 2);
9
+ return console.log(`[${timestamp}] [CONFIG] ${message}`, sanitized);
10
+ }
11
+ return console.log(`[${timestamp}] [CONFIG] ${message}`);
12
+ }
13
+ }
14
+
15
+ function loadSSLKey(keyConfig) {
16
+ debug(`Loading SSL key from: ${keyConfig.includes("-----BEGIN") ? "inline" : keyConfig}`);
17
+ return keyConfig.includes("-----BEGIN") ? Promise.resolve(Buffer.from(keyConfig)) : readFile(resolve(keyConfig));
18
+ }
19
+ function loadSSLCert(certConfig) {
20
+ debug(`Loading SSL cert from: ${certConfig.includes("-----BEGIN") ? "inline" : certConfig}`);
21
+ return certConfig.includes("-----BEGIN") ? Promise.resolve(Buffer.from(certConfig)) : readFile(resolve(certConfig));
22
+ }
23
+ function loadSSLCA(caConfig) {
24
+ if (!caConfig) {
25
+ debug("Loading SSL CA from: none");
26
+ return Promise.resolve(void 0);
27
+ }
28
+ debug(`Loading SSL CA from: ${caConfig.includes("-----BEGIN") ? "inline" : caConfig}`);
29
+ return caConfig.includes("-----BEGIN") ? Promise.resolve(Buffer.from(caConfig)) : readFile(resolve(caConfig));
30
+ }
31
+ function initializeSSL(sslConfig) {
32
+ debug("Initializing SSL configuration...");
33
+ return Promise.all([loadSSLKey(sslConfig.key), loadSSLCert(sslConfig.cert), loadSSLCA(sslConfig.ca)]).then(([key, cert, ca]) => {
34
+ const loaded = {
35
+ key,
36
+ cert,
37
+ ca,
38
+ allowHTTP1: sslConfig.allowHTTP1 ?? true
39
+ };
40
+ debug("SSL configuration loaded successfully", {
41
+ keySize: key.length,
42
+ certSize: cert.length,
43
+ caSize: ca?.length || 0,
44
+ allowHTTP1: loaded.allowHTTP1
45
+ });
46
+ return loaded;
47
+ }).catch((error) => {
48
+ debug("Failed to load SSL certificates", error);
49
+ console.error("Failed to load SSL certificates:", error);
50
+ throw error;
51
+ });
52
+ }
53
+ function getRoute(config, host) {
54
+ if (config.routes[host]) {
55
+ return config.routes[host];
56
+ }
57
+ const hostWithoutPort = host.split(":")[0];
58
+ if (config.routes[hostWithoutPort]) {
59
+ return config.routes[hostWithoutPort];
60
+ }
61
+ for (const [pattern, route] of Object.entries(config.routes)) {
62
+ if (pattern.includes("*")) {
63
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
64
+ if (regex.test(host) || regex.test(hostWithoutPort)) {
65
+ return route;
66
+ }
67
+ }
68
+ }
69
+ return void 0;
70
+ }
71
+ function getTarget(config, sslConfig, host) {
72
+ const route = getRoute(config, host);
73
+ if (!route) {
74
+ return { target: void 0, ssl: void 0, remap: void 0 };
75
+ }
76
+ return {
77
+ target: route.target,
78
+ ssl: route.ssl ? sslConfig : void 0,
79
+ remap: route.remap
80
+ };
81
+ }
82
+ function getCorsHeaders(config, origin, host) {
83
+ const route = host ? getRoute(config, host) : void 0;
84
+ const corsConfig = route?.cors || config.cors;
85
+ if (!corsConfig) {
86
+ return {
87
+ "access-control-allow-origin": origin,
88
+ "access-control-allow-methods": "*",
89
+ "access-control-allow-headers": "*",
90
+ "access-control-allow-credentials": "true"
91
+ };
92
+ }
93
+ const headers = {};
94
+ if (corsConfig.origin === "*") {
95
+ headers["access-control-allow-origin"] = "*";
96
+ } else if (Array.isArray(corsConfig.origin)) {
97
+ if (corsConfig.origin.includes(origin)) {
98
+ headers["access-control-allow-origin"] = origin;
99
+ }
100
+ } else if (corsConfig.origin) {
101
+ headers["access-control-allow-origin"] = corsConfig.origin;
102
+ } else {
103
+ headers["access-control-allow-origin"] = origin;
104
+ }
105
+ if (corsConfig.methods) {
106
+ headers["access-control-allow-methods"] = corsConfig.methods.join(", ");
107
+ } else {
108
+ headers["access-control-allow-methods"] = "*";
109
+ }
110
+ if (corsConfig.allowedHeaders) {
111
+ headers["access-control-allow-headers"] = corsConfig.allowedHeaders.join(", ");
112
+ } else {
113
+ headers["access-control-allow-headers"] = "*";
114
+ }
115
+ if (corsConfig.exposedHeaders) {
116
+ headers["access-control-expose-headers"] = corsConfig.exposedHeaders.join(", ");
117
+ }
118
+ if (corsConfig.credentials !== void 0) {
119
+ headers["access-control-allow-credentials"] = corsConfig.credentials ? "true" : "false";
120
+ } else {
121
+ headers["access-control-allow-credentials"] = "true";
122
+ }
123
+ if (corsConfig.maxAge) {
124
+ headers["access-control-max-age"] = corsConfig.maxAge.toString();
125
+ }
126
+ return headers;
127
+ }
128
+ function validateRequest(config, info) {
129
+ const route = getRoute(config, info.authority);
130
+ if (route?.validate) return route.validate(info);
131
+ if (config.validate) return config.validate(info);
132
+ return Promise.resolve({ status: true });
133
+ }
134
+ function getPorts(config) {
135
+ return config.ports || [443, 8080, 9229];
136
+ }
137
+ function createProxyConfig(config, sslConfig) {
138
+ return {
139
+ config,
140
+ sslConfig,
141
+ getSSL: () => sslConfig,
142
+ getRoute: (host) => getRoute(config, host),
143
+ getTarget: (host) => getTarget(config, sslConfig, host),
144
+ getCorsHeaders: (origin, host) => getCorsHeaders(config, origin, host),
145
+ validate: (info) => validateRequest(config, info),
146
+ getPorts: () => getPorts(config),
147
+ getConfig: () => config
148
+ };
149
+ }
150
+ function deepMerge(...objects) {
151
+ const result = {};
152
+ for (const obj of objects) {
153
+ if (!obj) continue;
154
+ for (const key in obj) {
155
+ if (obj[key] === void 0) continue;
156
+ if (typeof obj[key] === "object" && !Array.isArray(obj[key]) && obj[key] !== null) {
157
+ result[key] = deepMerge(result[key] || {}, obj[key]);
158
+ } else {
159
+ result[key] = obj[key];
160
+ }
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ function getConfigFromEnv() {
166
+ debug("Loading configuration from environment variables...");
167
+ const config = {};
168
+ if (process.env.SSL_KEY && process.env.SSL_CERT) {
169
+ debug("Found SSL configuration in environment variables");
170
+ config.ssl = {
171
+ key: process.env.SSL_KEY,
172
+ cert: process.env.SSL_CERT,
173
+ ca: process.env.SSL_CA,
174
+ allowHTTP1: process.env.SSL_ALLOW_HTTP1 === "true"
175
+ };
176
+ }
177
+ if (process.env.CORS_ORIGIN) {
178
+ debug("Found CORS configuration in environment variables", {
179
+ origin: process.env.CORS_ORIGIN,
180
+ methods: process.env.CORS_METHODS,
181
+ headers: process.env.CORS_HEADERS,
182
+ credentials: process.env.CORS_CREDENTIALS
183
+ });
184
+ config.cors = {
185
+ origin: process.env.CORS_ORIGIN === "*" ? "*" : process.env.CORS_ORIGIN.split(","),
186
+ methods: process.env.CORS_METHODS?.split(","),
187
+ allowedHeaders: process.env.CORS_HEADERS?.split(","),
188
+ credentials: process.env.CORS_CREDENTIALS === "true"
189
+ };
190
+ }
191
+ if (Object.keys(config).length === 0) {
192
+ debug("No configuration found in environment variables");
193
+ }
194
+ return config;
195
+ }
196
+ async function getConfigFromFile() {
197
+ const configPath = process.env.PROXY_CONFIG || "./proxy.config.json";
198
+ const resolvedPath = resolve(configPath);
199
+ debug(`Looking for config file at: ${resolvedPath}`);
200
+ try {
201
+ const stats = await stat(resolvedPath);
202
+ debug(`Config file found (${stats.size} bytes)`);
203
+ if (resolvedPath.endsWith(".json")) {
204
+ debug("Loading JSON config file...");
205
+ const configFile2 = await readFile(resolvedPath, "utf-8");
206
+ const config2 = JSON.parse(configFile2);
207
+ debug("JSON config loaded successfully", {
208
+ routes: Object.keys(config2.routes || {}),
209
+ hasCors: !!config2.cors,
210
+ hasSsl: !!config2.ssl,
211
+ ports: config2.ports
212
+ });
213
+ return config2;
214
+ }
215
+ if (resolvedPath.endsWith(".ts") || resolvedPath.endsWith(".js")) {
216
+ const fileType = resolvedPath.endsWith(".ts") ? "TypeScript" : "JavaScript";
217
+ debug(`Loading ${fileType} config file...`);
218
+ const configModule = await import(`file://${resolvedPath}`);
219
+ const config2 = configModule.default || configModule;
220
+ debug(`${fileType} config loaded successfully`, {
221
+ routes: Object.keys(config2.routes || {}),
222
+ hasCors: !!config2.cors,
223
+ hasSsl: !!config2.ssl,
224
+ ports: config2.ports
225
+ });
226
+ return config2;
227
+ }
228
+ debug("Loading config as JSON (no extension match)...");
229
+ const configFile = await readFile(resolvedPath, "utf-8");
230
+ const config = JSON.parse(configFile);
231
+ debug("Config loaded successfully", {
232
+ routes: Object.keys(config.routes || {}),
233
+ hasCors: !!config.cors,
234
+ hasSsl: !!config.ssl,
235
+ ports: config.ports
236
+ });
237
+ return config;
238
+ } catch (error) {
239
+ if (error.code === "ENOENT") {
240
+ debug(`Config file not found: ${resolvedPath}`);
241
+ } else {
242
+ debug(`Error loading config file: ${error.message}`);
243
+ }
244
+ return {};
245
+ }
246
+ }
247
+ function loadDefaults() {
248
+ debug("==========================================");
249
+ debug("Starting configuration loading process...");
250
+ debug("==========================================");
251
+ debug("Step 1: Loading default configuration...");
252
+ const defaults = {
253
+ routes: {},
254
+ cors: {
255
+ origin: "*",
256
+ credentials: true
257
+ }
258
+ };
259
+ debug("Defaults loaded", defaults);
260
+ return defaults;
261
+ }
262
+ function loadAndMergeFileConfig(defaults) {
263
+ debug("Step 2: Loading configuration from file...");
264
+ return getConfigFromFile().then((fileConfig) => {
265
+ if (Object.keys(fileConfig).length > 0) {
266
+ debug("File config loaded", fileConfig);
267
+ } else {
268
+ debug("No file configuration loaded");
269
+ }
270
+ return deepMerge(defaults, fileConfig);
271
+ });
272
+ }
273
+ function mergeEnvConfig(config) {
274
+ debug("Step 3: Loading configuration from environment...");
275
+ const envConfig = getConfigFromEnv();
276
+ return deepMerge(config, envConfig);
277
+ }
278
+ function mergeProgrammaticConfig(config, programmaticConfig) {
279
+ if (programmaticConfig && Object.keys(programmaticConfig).length > 0) {
280
+ debug("Step 4: Programmatic configuration provided", programmaticConfig);
281
+ return deepMerge(config, programmaticConfig);
282
+ }
283
+ return config;
284
+ }
285
+ function logMergedConfig(merged) {
286
+ debug("Step 5: Final merged configuration", {
287
+ routes: Object.keys(merged.routes || {}),
288
+ ports: merged.ports,
289
+ hasCors: !!merged.cors,
290
+ hasSsl: !!merged.ssl,
291
+ hasValidate: !!merged.validate
292
+ });
293
+ return merged;
294
+ }
295
+ function initializeSSLIfConfigured(config) {
296
+ debug("Step 6: Checking SSL configuration...");
297
+ if (!config.ssl) {
298
+ debug("No SSL configuration provided");
299
+ return Promise.resolve({ config, sslConfig: void 0 });
300
+ }
301
+ return initializeSSL(config.ssl).then((sslConfig) => ({ config, sslConfig }));
302
+ }
303
+ function createFinalConfig({ config, sslConfig }) {
304
+ debug("Step 7: Creating ProxyConfig object...");
305
+ const proxyConfig = createProxyConfig(config, sslConfig);
306
+ debug("==========================================");
307
+ debug("Configuration loading completed successfully");
308
+ debug("==========================================");
309
+ return proxyConfig;
310
+ }
311
+ function loadConfig(programmaticConfig) {
312
+ return Promise.resolve(loadDefaults()).then((defaults) => loadAndMergeFileConfig(defaults)).then((config) => mergeEnvConfig(config)).then((config) => mergeProgrammaticConfig(config, programmaticConfig)).then((merged) => logMergedConfig(merged)).then((merged) => initializeSSLIfConfigured(merged)).then((result) => createFinalConfig(result));
313
+ }
314
+
315
+ export { debug as d, loadConfig as l };
@@ -92,6 +92,8 @@ interface ServerConfig {
92
92
  cors?: CorsConfig;
93
93
  /** Global validation function */
94
94
  validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
95
+ /** Ports to listen on (defaults to [443, 8080, 9229]) */
96
+ ports?: number[];
95
97
  }
96
98
  /**
97
99
  * Proxy configuration manager
@@ -152,21 +154,15 @@ declare class ProxyConfig {
152
154
  getConfig(): ServerConfig;
153
155
  }
154
156
  /**
155
- * Load configuration with cascading priority:
157
+ * Load configuration with cascading priority (async):
156
158
  * 1. Programmatic config (highest priority)
157
159
  * 2. Environment variables
158
160
  * 3. Config file
159
161
  * 4. Defaults (lowest priority)
162
+ *
163
+ * Supports all config file types: .json, .cjs, .ts, .js, .mjs
160
164
  */
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;
165
+ declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): Promise<ProxyConfig>;
170
166
 
171
- export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
167
+ export { ProxyConfig as P, RequestType as e, loadConfig as l };
172
168
  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 };
@@ -92,89 +92,46 @@ interface ServerConfig {
92
92
  cors?: CorsConfig;
93
93
  /** Global validation function */
94
94
  validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
95
+ /** Ports to listen on (defaults to [443, 8080, 9229]) */
96
+ ports?: number[];
95
97
  }
96
98
  /**
97
- * Proxy configuration manager
99
+ * Processed SSL configuration with loaded certificates
98
100
  */
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;
101
+ interface LoadedSSLConfig {
102
+ key: Buffer;
103
+ cert: Buffer;
104
+ ca?: Buffer;
105
+ allowHTTP1: boolean;
153
106
  }
154
107
  /**
155
- * Initialize the global configuration
156
- */
157
- declare function initConfig(config: ServerConfig): ProxyConfig;
158
- /**
159
- * Get the global configuration
108
+ * Proxy configuration object with loaded SSL
160
109
  */
161
- declare function getConfig(): ProxyConfig;
110
+ interface ProxyConfig {
111
+ config: ServerConfig;
112
+ sslConfig?: LoadedSSLConfig;
113
+ getSSL: () => LoadedSSLConfig | undefined;
114
+ getRoute: (host: string) => RouteConfig | undefined;
115
+ getTarget: (host: string) => {
116
+ target: string | undefined;
117
+ ssl: LoadedSSLConfig | undefined;
118
+ remap: ((url: string) => string) | undefined;
119
+ };
120
+ getCorsHeaders: (origin: string, host?: string) => OutgoingHttpHeaders;
121
+ validate: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
122
+ getPorts: () => number[];
123
+ getConfig: () => ServerConfig;
124
+ }
162
125
  /**
163
- * Load configuration with cascading priority:
126
+ * Load configuration with cascading priority using promise chain:
164
127
  * 1. Programmatic config (highest priority)
165
128
  * 2. Environment variables
166
129
  * 3. Config file
167
130
  * 4. Defaults (lowest priority)
131
+ *
132
+ * Supports config file types: .json, .ts, .js
168
133
  */
169
- declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): ProxyConfig;
170
- /**
171
- * Load configuration from a file (deprecated - use loadConfig instead)
172
- */
173
- declare function loadConfigFromFile(path: string): ProxyConfig;
174
- /**
175
- * Load configuration from environment variables (deprecated - use loadConfig instead)
176
- */
177
- declare function loadConfigFromEnv(): ProxyConfig;
134
+ declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): Promise<ProxyConfig>;
178
135
 
179
- export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, getConfig as g, loadConfigFromEnv as h, initConfig as i, loadConfig as l };
180
- 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 };
136
+ export { RequestType as e, loadConfig as l };
137
+ export type { CorsConfig as C, ForwardValidationResult as F, LoadedSSLConfig as L, ProxyConfig as P, 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,5 +1,5 @@
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';
1
+ import { P as ProxyConfig, T as TLSWebSocket, S as ServerConfig, R as Router, a as Streamer, W as WebSocketRouter } from './config-DtfpOtWI.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-DtfpOtWI.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';
@@ -33,10 +33,22 @@ declare class ProxyServer {
33
33
  }
34
34
  /**
35
35
  * Create and start a proxy server
36
+ * Supports config file types: .json, .ts, .js
37
+ *
38
+ * Promise chain:
39
+ * 1. Load configuration from file/env/programmatic
40
+ * 2. Initialize SSL certificates
41
+ * 3. Create proxy server instance
42
+ * 4. Start listening on configured ports
36
43
  */
37
44
  declare function createProxy(config?: Partial<ServerConfig>): Promise<ProxyServer>;
38
45
  /**
39
46
  * Create a proxy server with custom handlers
47
+ *
48
+ * Promise chain:
49
+ * 1. Load configuration from file/env/programmatic
50
+ * 2. Initialize SSL certificates
51
+ * 3. Create servers with custom handlers
40
52
  */
41
53
  declare function createCustomProxy(config: Partial<ServerConfig> | undefined, handlers: {
42
54
  rest?: Router;