@push.rocks/smartproxy 5.0.0 → 6.0.0

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.
Files changed (78) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.pp.interfaces.d.ts +23 -0
  3. package/dist_ts/classes.pp.networkproxybridge.d.ts +15 -1
  4. package/dist_ts/classes.pp.networkproxybridge.js +116 -21
  5. package/dist_ts/classes.pp.portproxy.d.ts +20 -4
  6. package/dist_ts/classes.pp.portproxy.js +321 -22
  7. package/dist_ts/index.d.ts +6 -6
  8. package/dist_ts/index.js +7 -7
  9. package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +77 -0
  10. package/dist_ts/networkproxy/classes.np.certificatemanager.js +354 -0
  11. package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +47 -0
  12. package/dist_ts/networkproxy/classes.np.connectionpool.js +210 -0
  13. package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +117 -0
  14. package/dist_ts/networkproxy/classes.np.networkproxy.js +375 -0
  15. package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +51 -0
  16. package/dist_ts/networkproxy/classes.np.requesthandler.js +210 -0
  17. package/dist_ts/networkproxy/classes.np.types.d.ts +82 -0
  18. package/dist_ts/networkproxy/classes.np.types.js +35 -0
  19. package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +38 -0
  20. package/dist_ts/networkproxy/classes.np.websockethandler.js +188 -0
  21. package/dist_ts/networkproxy/index.d.ts +6 -0
  22. package/dist_ts/networkproxy/index.js +8 -0
  23. package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +219 -0
  24. package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +1542 -0
  25. package/dist_ts/port80handler/classes.port80handler.d.ts +260 -0
  26. package/dist_ts/port80handler/classes.port80handler.js +928 -0
  27. package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +39 -0
  28. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +754 -0
  29. package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +78 -0
  30. package/dist_ts/smartproxy/classes.pp.connectionmanager.js +378 -0
  31. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +55 -0
  32. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +103 -0
  33. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +133 -0
  34. package/dist_ts/smartproxy/classes.pp.interfaces.js +2 -0
  35. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +57 -0
  36. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +306 -0
  37. package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +56 -0
  38. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +179 -0
  39. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +47 -0
  40. package/dist_ts/smartproxy/classes.pp.securitymanager.js +126 -0
  41. package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +153 -0
  42. package/dist_ts/smartproxy/classes.pp.snihandler.js +1053 -0
  43. package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +47 -0
  44. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +154 -0
  45. package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +149 -0
  46. package/dist_ts/smartproxy/classes.pp.tlsalert.js +225 -0
  47. package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +57 -0
  48. package/dist_ts/smartproxy/classes.pp.tlsmanager.js +132 -0
  49. package/dist_ts/smartproxy/classes.smartproxy.d.ts +64 -0
  50. package/dist_ts/smartproxy/classes.smartproxy.js +567 -0
  51. package/package.json +1 -1
  52. package/readme.md +77 -27
  53. package/ts/00_commitinfo_data.ts +1 -1
  54. package/ts/index.ts +6 -6
  55. package/ts/networkproxy/classes.np.certificatemanager.ts +398 -0
  56. package/ts/networkproxy/classes.np.connectionpool.ts +241 -0
  57. package/ts/networkproxy/classes.np.networkproxy.ts +469 -0
  58. package/ts/networkproxy/classes.np.requesthandler.ts +278 -0
  59. package/ts/networkproxy/classes.np.types.ts +123 -0
  60. package/ts/networkproxy/classes.np.websockethandler.ts +226 -0
  61. package/ts/networkproxy/index.ts +7 -0
  62. package/ts/{classes.port80handler.ts → port80handler/classes.port80handler.ts} +249 -1
  63. package/ts/{classes.pp.connectionhandler.ts → smartproxy/classes.pp.connectionhandler.ts} +1 -1
  64. package/ts/{classes.pp.connectionmanager.ts → smartproxy/classes.pp.connectionmanager.ts} +1 -1
  65. package/ts/{classes.pp.domainconfigmanager.ts → smartproxy/classes.pp.domainconfigmanager.ts} +1 -1
  66. package/ts/{classes.pp.interfaces.ts → smartproxy/classes.pp.interfaces.ts} +31 -5
  67. package/ts/{classes.pp.networkproxybridge.ts → smartproxy/classes.pp.networkproxybridge.ts} +129 -28
  68. package/ts/{classes.pp.securitymanager.ts → smartproxy/classes.pp.securitymanager.ts} +1 -1
  69. package/ts/{classes.pp.tlsmanager.ts → smartproxy/classes.pp.tlsmanager.ts} +1 -1
  70. package/ts/smartproxy/classes.smartproxy.ts +679 -0
  71. package/ts/classes.networkproxy.ts +0 -1730
  72. package/ts/classes.pp.acmemanager.ts +0 -149
  73. package/ts/classes.pp.portproxy.ts +0 -344
  74. /package/ts/{classes.nftablesproxy.ts → nfttablesproxy/classes.nftablesproxy.ts} +0 -0
  75. /package/ts/{classes.pp.portrangemanager.ts → smartproxy/classes.pp.portrangemanager.ts} +0 -0
  76. /package/ts/{classes.pp.snihandler.ts → smartproxy/classes.pp.snihandler.ts} +0 -0
  77. /package/ts/{classes.pp.timeoutmanager.ts → smartproxy/classes.pp.timeoutmanager.ts} +0 -0
  78. /package/ts/{classes.pp.tlsalert.ts → smartproxy/classes.pp.tlsalert.ts} +0 -0
@@ -0,0 +1,210 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { createLogger } from './classes.np.types.js';
3
+ import { ConnectionPool } from './classes.np.connectionpool.js';
4
+ import { ProxyRouter } from '../classes.router.js';
5
+ /**
6
+ * Handles HTTP request processing and proxying
7
+ */
8
+ export class RequestHandler {
9
+ constructor(options, connectionPool, router) {
10
+ this.options = options;
11
+ this.connectionPool = connectionPool;
12
+ this.router = router;
13
+ this.defaultHeaders = {};
14
+ this.metricsTracker = null;
15
+ this.logger = createLogger(options.logLevel || 'info');
16
+ }
17
+ /**
18
+ * Set the metrics tracker instance
19
+ */
20
+ setMetricsTracker(tracker) {
21
+ this.metricsTracker = tracker;
22
+ }
23
+ /**
24
+ * Set default headers to be included in all responses
25
+ */
26
+ setDefaultHeaders(headers) {
27
+ this.defaultHeaders = {
28
+ ...this.defaultHeaders,
29
+ ...headers
30
+ };
31
+ this.logger.info('Updated default response headers');
32
+ }
33
+ /**
34
+ * Get all default headers
35
+ */
36
+ getDefaultHeaders() {
37
+ return { ...this.defaultHeaders };
38
+ }
39
+ /**
40
+ * Apply CORS headers to response if configured
41
+ */
42
+ applyCorsHeaders(res, req) {
43
+ if (!this.options.cors) {
44
+ return;
45
+ }
46
+ // Apply CORS headers
47
+ if (this.options.cors.allowOrigin) {
48
+ res.setHeader('Access-Control-Allow-Origin', this.options.cors.allowOrigin);
49
+ }
50
+ if (this.options.cors.allowMethods) {
51
+ res.setHeader('Access-Control-Allow-Methods', this.options.cors.allowMethods);
52
+ }
53
+ if (this.options.cors.allowHeaders) {
54
+ res.setHeader('Access-Control-Allow-Headers', this.options.cors.allowHeaders);
55
+ }
56
+ if (this.options.cors.maxAge) {
57
+ res.setHeader('Access-Control-Max-Age', this.options.cors.maxAge.toString());
58
+ }
59
+ // Handle CORS preflight requests
60
+ if (req.method === 'OPTIONS') {
61
+ res.statusCode = 204; // No content
62
+ res.end();
63
+ return;
64
+ }
65
+ }
66
+ /**
67
+ * Apply default headers to response
68
+ */
69
+ applyDefaultHeaders(res) {
70
+ // Apply default headers
71
+ for (const [key, value] of Object.entries(this.defaultHeaders)) {
72
+ if (!res.hasHeader(key)) {
73
+ res.setHeader(key, value);
74
+ }
75
+ }
76
+ // Add server identifier if not already set
77
+ if (!res.hasHeader('Server')) {
78
+ res.setHeader('Server', 'NetworkProxy');
79
+ }
80
+ }
81
+ /**
82
+ * Handle an HTTP request
83
+ */
84
+ async handleRequest(req, res) {
85
+ // Record start time for logging
86
+ const startTime = Date.now();
87
+ // Apply CORS headers if configured
88
+ this.applyCorsHeaders(res, req);
89
+ // If this is an OPTIONS request, the response has already been ended in applyCorsHeaders
90
+ // so we should return early to avoid trying to set more headers
91
+ if (req.method === 'OPTIONS') {
92
+ // Increment metrics for OPTIONS requests too
93
+ if (this.metricsTracker) {
94
+ this.metricsTracker.incrementRequestsServed();
95
+ }
96
+ return;
97
+ }
98
+ // Apply default headers
99
+ this.applyDefaultHeaders(res);
100
+ try {
101
+ // Find target based on hostname
102
+ const proxyConfig = this.router.routeReq(req);
103
+ if (!proxyConfig) {
104
+ // No matching proxy configuration
105
+ this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
106
+ res.statusCode = 404;
107
+ res.end('Not Found: No proxy configuration for this host');
108
+ // Increment failed requests counter
109
+ if (this.metricsTracker) {
110
+ this.metricsTracker.incrementFailedRequests();
111
+ }
112
+ return;
113
+ }
114
+ // Get destination IP using round-robin if multiple IPs configured
115
+ const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
116
+ // Create options for the proxy request
117
+ const options = {
118
+ hostname: destination.host,
119
+ port: destination.port,
120
+ path: req.url,
121
+ method: req.method,
122
+ headers: { ...req.headers }
123
+ };
124
+ // Remove host header to avoid issues with virtual hosts on target server
125
+ // The host header should match the target server's expected hostname
126
+ if (options.headers && options.headers.host) {
127
+ if (proxyConfig.rewriteHostHeader) {
128
+ options.headers.host = `${destination.host}:${destination.port}`;
129
+ }
130
+ }
131
+ this.logger.debug(`Proxying request to ${destination.host}:${destination.port}${req.url}`, { method: req.method });
132
+ // Create proxy request
133
+ const proxyReq = plugins.http.request(options, (proxyRes) => {
134
+ // Copy status code
135
+ res.statusCode = proxyRes.statusCode || 500;
136
+ // Copy headers from proxy response to client response
137
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
138
+ if (value !== undefined) {
139
+ res.setHeader(key, value);
140
+ }
141
+ }
142
+ // Pipe proxy response to client response
143
+ proxyRes.pipe(res);
144
+ // Increment served requests counter when the response finishes
145
+ res.on('finish', () => {
146
+ if (this.metricsTracker) {
147
+ this.metricsTracker.incrementRequestsServed();
148
+ }
149
+ // Log the completed request
150
+ const duration = Date.now() - startTime;
151
+ this.logger.debug(`Request completed in ${duration}ms: ${req.method} ${req.url} ${res.statusCode}`, { duration, statusCode: res.statusCode });
152
+ });
153
+ });
154
+ // Handle proxy request errors
155
+ proxyReq.on('error', (error) => {
156
+ const duration = Date.now() - startTime;
157
+ this.logger.error(`Proxy error for ${req.method} ${req.url}: ${error.message}`, { duration, error: error.message });
158
+ // Increment failed requests counter
159
+ if (this.metricsTracker) {
160
+ this.metricsTracker.incrementFailedRequests();
161
+ }
162
+ // Check if headers have already been sent
163
+ if (!res.headersSent) {
164
+ res.statusCode = 502;
165
+ res.end(`Bad Gateway: ${error.message}`);
166
+ }
167
+ else {
168
+ // If headers already sent, just close the connection
169
+ res.end();
170
+ }
171
+ });
172
+ // Pipe request body to proxy request and handle client-side errors
173
+ req.pipe(proxyReq);
174
+ // Handle client disconnection
175
+ req.on('error', (error) => {
176
+ this.logger.debug(`Client connection error: ${error.message}`);
177
+ proxyReq.destroy();
178
+ // Increment failed requests counter on client errors
179
+ if (this.metricsTracker) {
180
+ this.metricsTracker.incrementFailedRequests();
181
+ }
182
+ });
183
+ // Handle response errors
184
+ res.on('error', (error) => {
185
+ this.logger.debug(`Response error: ${error.message}`);
186
+ proxyReq.destroy();
187
+ // Increment failed requests counter on response errors
188
+ if (this.metricsTracker) {
189
+ this.metricsTracker.incrementFailedRequests();
190
+ }
191
+ });
192
+ }
193
+ catch (error) {
194
+ // Handle any unexpected errors
195
+ this.logger.error(`Unexpected error handling request: ${error.message}`, { error: error.stack });
196
+ // Increment failed requests counter
197
+ if (this.metricsTracker) {
198
+ this.metricsTracker.incrementFailedRequests();
199
+ }
200
+ if (!res.headersSent) {
201
+ res.statusCode = 500;
202
+ res.end('Internal Server Error');
203
+ }
204
+ else {
205
+ res.end();
206
+ }
207
+ }
208
+ }
209
+ }
210
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC5yZXF1ZXN0aGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL25ldHdvcmtwcm94eS9jbGFzc2VzLm5wLnJlcXVlc3RoYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBMkMsWUFBWSxFQUE0QixNQUFNLHVCQUF1QixDQUFDO0FBQ3hILE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFVbkQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUt6QixZQUNVLE9BQTZCLEVBQzdCLGNBQThCLEVBQzlCLE1BQW1CO1FBRm5CLFlBQU8sR0FBUCxPQUFPLENBQXNCO1FBQzdCLG1CQUFjLEdBQWQsY0FBYyxDQUFnQjtRQUM5QixXQUFNLEdBQU4sTUFBTSxDQUFhO1FBUHJCLG1CQUFjLEdBQThCLEVBQUUsQ0FBQztRQUUvQyxtQkFBYyxHQUEyQixJQUFJLENBQUM7UUFPcEQsSUFBSSxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxNQUFNLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxpQkFBaUIsQ0FBQyxPQUF3QjtRQUMvQyxJQUFJLENBQUMsY0FBYyxHQUFHLE9BQU8sQ0FBQztJQUNoQyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxpQkFBaUIsQ0FBQyxPQUFrQztRQUN6RCxJQUFJLENBQUMsY0FBYyxHQUFHO1lBQ3BCLEdBQUcsSUFBSSxDQUFDLGNBQWM7WUFDdEIsR0FBRyxPQUFPO1NBQ1gsQ0FBQztRQUNGLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGtDQUFrQyxDQUFDLENBQUM7SUFDdkQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksaUJBQWlCO1FBQ3RCLE9B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
@@ -0,0 +1,82 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * Configuration options for NetworkProxy
4
+ */
5
+ export interface INetworkProxyOptions {
6
+ port: number;
7
+ maxConnections?: number;
8
+ keepAliveTimeout?: number;
9
+ headersTimeout?: number;
10
+ logLevel?: 'error' | 'warn' | 'info' | 'debug';
11
+ cors?: {
12
+ allowOrigin?: string;
13
+ allowMethods?: string;
14
+ allowHeaders?: string;
15
+ maxAge?: number;
16
+ };
17
+ connectionPoolSize?: number;
18
+ portProxyIntegration?: boolean;
19
+ useExternalPort80Handler?: boolean;
20
+ acme?: {
21
+ enabled?: boolean;
22
+ port?: number;
23
+ contactEmail?: string;
24
+ useProduction?: boolean;
25
+ renewThresholdDays?: number;
26
+ autoRenew?: boolean;
27
+ certificateStore?: string;
28
+ skipConfiguredCerts?: boolean;
29
+ };
30
+ }
31
+ /**
32
+ * Interface for a certificate entry in the cache
33
+ */
34
+ export interface ICertificateEntry {
35
+ key: string;
36
+ cert: string;
37
+ expires?: Date;
38
+ }
39
+ /**
40
+ * Interface for reverse proxy configuration
41
+ */
42
+ export interface IReverseProxyConfig {
43
+ destinationIps: string[];
44
+ destinationPorts: number[];
45
+ hostName: string;
46
+ privateKey: string;
47
+ publicKey: string;
48
+ authentication?: {
49
+ type: 'Basic';
50
+ user: string;
51
+ pass: string;
52
+ };
53
+ rewriteHostHeader?: boolean;
54
+ }
55
+ /**
56
+ * Interface for connection tracking in the pool
57
+ */
58
+ export interface IConnectionEntry {
59
+ socket: plugins.net.Socket;
60
+ lastUsed: number;
61
+ isIdle: boolean;
62
+ }
63
+ /**
64
+ * WebSocket with heartbeat interface
65
+ */
66
+ export interface IWebSocketWithHeartbeat extends plugins.wsDefault {
67
+ lastPong: number;
68
+ isAlive: boolean;
69
+ }
70
+ /**
71
+ * Logger interface for consistent logging across components
72
+ */
73
+ export interface ILogger {
74
+ debug(message: string, data?: any): void;
75
+ info(message: string, data?: any): void;
76
+ warn(message: string, data?: any): void;
77
+ error(message: string, data?: any): void;
78
+ }
79
+ /**
80
+ * Creates a logger based on the specified log level
81
+ */
82
+ export declare function createLogger(logLevel?: string): ILogger;
@@ -0,0 +1,35 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * Creates a logger based on the specified log level
4
+ */
5
+ export function createLogger(logLevel = 'info') {
6
+ const logLevels = {
7
+ error: 0,
8
+ warn: 1,
9
+ info: 2,
10
+ debug: 3
11
+ };
12
+ return {
13
+ debug: (message, data) => {
14
+ if (logLevels[logLevel] >= logLevels.debug) {
15
+ console.log(`[DEBUG] ${message}`, data || '');
16
+ }
17
+ },
18
+ info: (message, data) => {
19
+ if (logLevels[logLevel] >= logLevels.info) {
20
+ console.log(`[INFO] ${message}`, data || '');
21
+ }
22
+ },
23
+ warn: (message, data) => {
24
+ if (logLevels[logLevel] >= logLevels.warn) {
25
+ console.warn(`[WARN] ${message}`, data || '');
26
+ }
27
+ },
28
+ error: (message, data) => {
29
+ if (logLevels[logLevel] >= logLevels.error) {
30
+ console.error(`[ERROR] ${message}`, data || '');
31
+ }
32
+ }
33
+ };
34
+ }
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC50eXBlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL25ldHdvcmtwcm94eS9jbGFzc2VzLm5wLnR5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBeUZ6Qzs7R0FFRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQUMsV0FBbUIsTUFBTTtJQUNwRCxNQUFNLFNBQVMsR0FBRztRQUNoQixLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksRUFBRSxDQUFDO1FBQ1AsSUFBSSxFQUFFLENBQUM7UUFDUCxLQUFLLEVBQUUsQ0FBQztLQUNULENBQUM7SUFFRixPQUFPO1FBQ0wsS0FBSyxFQUFFLENBQUMsT0FBZSxFQUFFLElBQVUsRUFBRSxFQUFFO1lBQ3JDLElBQUksU0FBUyxDQUFDLFFBQVEsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDM0MsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLE9BQU8sRUFBRSxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNoRCxDQUFDO1FBQ0gsQ0FBQztRQUNELElBQUksRUFBRSxDQUFDLE9BQWUsRUFBRSxJQUFVLEVBQUUsRUFBRTtZQUNwQyxJQUFJLFNBQVMsQ0FBQyxRQUFRLENBQUMsSUFBSSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQzFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxPQUFPLEVBQUUsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUM7WUFDL0MsQ0FBQztRQUNILENBQUM7UUFDRCxJQUFJLEVBQUUsQ0FBQyxPQUFlLEVBQUUsSUFBVSxFQUFFLEVBQUU7WUFDcEMsSUFBSSxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUMxQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsT0FBTyxFQUFFLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQ2hELENBQUM7UUFDSCxDQUFDO1FBQ0QsS0FBSyxFQUFFLENBQUMsT0FBZSxFQUFFLElBQVUsRUFBRSxFQUFFO1lBQ3JDLElBQUksU0FBUyxDQUFDLFFBQVEsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDM0MsT0FBTyxDQUFDLEtBQUssQ0FBQyxXQUFXLE9BQU8sRUFBRSxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNsRCxDQUFDO1FBQ0gsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDIn0=
@@ -0,0 +1,38 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { type INetworkProxyOptions } from './classes.np.types.js';
3
+ import { ConnectionPool } from './classes.np.connectionpool.js';
4
+ import { ProxyRouter } from '../classes.router.js';
5
+ /**
6
+ * Handles WebSocket connections and proxying
7
+ */
8
+ export declare class WebSocketHandler {
9
+ private options;
10
+ private connectionPool;
11
+ private router;
12
+ private heartbeatInterval;
13
+ private wsServer;
14
+ private logger;
15
+ constructor(options: INetworkProxyOptions, connectionPool: ConnectionPool, router: ProxyRouter);
16
+ /**
17
+ * Initialize WebSocket server on an existing HTTPS server
18
+ */
19
+ initialize(server: plugins.https.Server): void;
20
+ /**
21
+ * Start the heartbeat interval to check for inactive WebSocket connections
22
+ */
23
+ private startHeartbeat;
24
+ /**
25
+ * Handle a new WebSocket connection
26
+ */
27
+ private handleWebSocketConnection;
28
+ /**
29
+ * Get information about active WebSocket connections
30
+ */
31
+ getConnectionInfo(): {
32
+ activeConnections: number;
33
+ };
34
+ /**
35
+ * Shutdown the WebSocket handler
36
+ */
37
+ shutdown(): void;
38
+ }
@@ -0,0 +1,188 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { createLogger } from './classes.np.types.js';
3
+ import { ConnectionPool } from './classes.np.connectionpool.js';
4
+ import { ProxyRouter } from '../classes.router.js';
5
+ /**
6
+ * Handles WebSocket connections and proxying
7
+ */
8
+ export class WebSocketHandler {
9
+ constructor(options, connectionPool, router) {
10
+ this.options = options;
11
+ this.connectionPool = connectionPool;
12
+ this.router = router;
13
+ this.heartbeatInterval = null;
14
+ this.wsServer = null;
15
+ this.logger = createLogger(options.logLevel || 'info');
16
+ }
17
+ /**
18
+ * Initialize WebSocket server on an existing HTTPS server
19
+ */
20
+ initialize(server) {
21
+ // Create WebSocket server
22
+ this.wsServer = new plugins.ws.WebSocketServer({
23
+ server: server,
24
+ clientTracking: true
25
+ });
26
+ // Handle WebSocket connections
27
+ this.wsServer.on('connection', (wsIncoming, req) => {
28
+ this.handleWebSocketConnection(wsIncoming, req);
29
+ });
30
+ // Start the heartbeat interval
31
+ this.startHeartbeat();
32
+ this.logger.info('WebSocket handler initialized');
33
+ }
34
+ /**
35
+ * Start the heartbeat interval to check for inactive WebSocket connections
36
+ */
37
+ startHeartbeat() {
38
+ // Clean up existing interval if any
39
+ if (this.heartbeatInterval) {
40
+ clearInterval(this.heartbeatInterval);
41
+ }
42
+ // Set up the heartbeat interval (check every 30 seconds)
43
+ this.heartbeatInterval = setInterval(() => {
44
+ if (!this.wsServer || this.wsServer.clients.size === 0) {
45
+ return; // Skip if no active connections
46
+ }
47
+ this.logger.debug(`WebSocket heartbeat check for ${this.wsServer.clients.size} clients`);
48
+ this.wsServer.clients.forEach((ws) => {
49
+ const wsWithHeartbeat = ws;
50
+ if (wsWithHeartbeat.isAlive === false) {
51
+ this.logger.debug('Terminating inactive WebSocket connection');
52
+ return wsWithHeartbeat.terminate();
53
+ }
54
+ wsWithHeartbeat.isAlive = false;
55
+ wsWithHeartbeat.ping();
56
+ });
57
+ }, 30000);
58
+ // Make sure the interval doesn't keep the process alive
59
+ if (this.heartbeatInterval.unref) {
60
+ this.heartbeatInterval.unref();
61
+ }
62
+ }
63
+ /**
64
+ * Handle a new WebSocket connection
65
+ */
66
+ handleWebSocketConnection(wsIncoming, req) {
67
+ try {
68
+ // Initialize heartbeat tracking
69
+ wsIncoming.isAlive = true;
70
+ wsIncoming.lastPong = Date.now();
71
+ // Handle pong messages to track liveness
72
+ wsIncoming.on('pong', () => {
73
+ wsIncoming.isAlive = true;
74
+ wsIncoming.lastPong = Date.now();
75
+ });
76
+ // Find target configuration based on request
77
+ const proxyConfig = this.router.routeReq(req);
78
+ if (!proxyConfig) {
79
+ this.logger.warn(`No proxy configuration for WebSocket host: ${req.headers.host}`);
80
+ wsIncoming.close(1008, 'No proxy configuration for this host');
81
+ return;
82
+ }
83
+ // Get destination target using round-robin if multiple targets
84
+ const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
85
+ // Build target URL
86
+ const protocol = req.socket.encrypted ? 'wss' : 'ws';
87
+ const targetUrl = `${protocol}://${destination.host}:${destination.port}${req.url}`;
88
+ this.logger.debug(`WebSocket connection from ${req.socket.remoteAddress} to ${targetUrl}`);
89
+ // Create headers for outgoing WebSocket connection
90
+ const headers = {};
91
+ // Copy relevant headers from incoming request
92
+ for (const [key, value] of Object.entries(req.headers)) {
93
+ if (value && typeof value === 'string' &&
94
+ key.toLowerCase() !== 'connection' &&
95
+ key.toLowerCase() !== 'upgrade' &&
96
+ key.toLowerCase() !== 'sec-websocket-key' &&
97
+ key.toLowerCase() !== 'sec-websocket-version') {
98
+ headers[key] = value;
99
+ }
100
+ }
101
+ // Override host header if needed
102
+ if (proxyConfig.rewriteHostHeader) {
103
+ headers['host'] = `${destination.host}:${destination.port}`;
104
+ }
105
+ // Create outgoing WebSocket connection
106
+ const wsOutgoing = new plugins.wsDefault(targetUrl, {
107
+ headers: headers,
108
+ followRedirects: true
109
+ });
110
+ // Handle connection errors
111
+ wsOutgoing.on('error', (err) => {
112
+ this.logger.error(`WebSocket target connection error: ${err.message}`);
113
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
114
+ wsIncoming.close(1011, 'Internal server error');
115
+ }
116
+ });
117
+ // Handle outgoing connection open
118
+ wsOutgoing.on('open', () => {
119
+ // Forward incoming messages to outgoing connection
120
+ wsIncoming.on('message', (data, isBinary) => {
121
+ if (wsOutgoing.readyState === wsOutgoing.OPEN) {
122
+ wsOutgoing.send(data, { binary: isBinary });
123
+ }
124
+ });
125
+ // Forward outgoing messages to incoming connection
126
+ wsOutgoing.on('message', (data, isBinary) => {
127
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
128
+ wsIncoming.send(data, { binary: isBinary });
129
+ }
130
+ });
131
+ // Handle closing of connections
132
+ wsIncoming.on('close', (code, reason) => {
133
+ this.logger.debug(`WebSocket client connection closed: ${code} ${reason}`);
134
+ if (wsOutgoing.readyState === wsOutgoing.OPEN) {
135
+ wsOutgoing.close(code, reason);
136
+ }
137
+ });
138
+ wsOutgoing.on('close', (code, reason) => {
139
+ this.logger.debug(`WebSocket target connection closed: ${code} ${reason}`);
140
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
141
+ wsIncoming.close(code, reason);
142
+ }
143
+ });
144
+ this.logger.debug(`WebSocket connection established: ${req.headers.host} -> ${destination.host}:${destination.port}`);
145
+ });
146
+ }
147
+ catch (error) {
148
+ this.logger.error(`Error handling WebSocket connection: ${error.message}`);
149
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
150
+ wsIncoming.close(1011, 'Internal server error');
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Get information about active WebSocket connections
156
+ */
157
+ getConnectionInfo() {
158
+ return {
159
+ activeConnections: this.wsServer ? this.wsServer.clients.size : 0
160
+ };
161
+ }
162
+ /**
163
+ * Shutdown the WebSocket handler
164
+ */
165
+ shutdown() {
166
+ // Stop heartbeat interval
167
+ if (this.heartbeatInterval) {
168
+ clearInterval(this.heartbeatInterval);
169
+ this.heartbeatInterval = null;
170
+ }
171
+ // Close all WebSocket connections
172
+ if (this.wsServer) {
173
+ this.logger.info(`Closing ${this.wsServer.clients.size} WebSocket connections`);
174
+ for (const client of this.wsServer.clients) {
175
+ try {
176
+ client.terminate();
177
+ }
178
+ catch (error) {
179
+ this.logger.error('Error terminating WebSocket client', error);
180
+ }
181
+ }
182
+ // Close the server
183
+ this.wsServer.close();
184
+ this.wsServer = null;
185
+ }
186
+ }
187
+ }
188
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC53ZWJzb2NrZXRoYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvbmV0d29ya3Byb3h5L2NsYXNzZXMubnAud2Vic29ja2V0aGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQXlFLFlBQVksRUFBNEIsTUFBTSx1QkFBdUIsQ0FBQztBQUN0SixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRW5EOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGdCQUFnQjtJQUszQixZQUNVLE9BQTZCLEVBQzdCLGNBQThCLEVBQzlCLE1BQW1CO1FBRm5CLFlBQU8sR0FBUCxPQUFPLENBQXNCO1FBQzdCLG1CQUFjLEdBQWQsY0FBYyxDQUFnQjtRQUM5QixXQUFNLEdBQU4sTUFBTSxDQUFhO1FBUHJCLHNCQUFpQixHQUEwQixJQUFJLENBQUM7UUFDaEQsYUFBUSxHQUFzQyxJQUFJLENBQUM7UUFRekQsSUFBSSxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxNQUFNLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxVQUFVLENBQUMsTUFBNEI7UUFDNUMsMEJBQTBCO1FBQzFCLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxPQUFPLENBQUMsRUFBRSxDQUFDLGVBQWUsQ0FBQztZQUM3QyxNQUFNLEVBQUUsTUFBTTtZQUNkLGNBQWMsRUFBRSxJQUFJO1NBQ3JCLENBQUMsQ0FBQztRQUVILCtCQUErQjtRQUMvQixJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxVQUFtQyxFQUFFLEdBQWlDLEVBQUUsRUFBRTtZQUN4RyxJQUFJLENBQUMseUJBQXlCLENBQUMs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
@@ -0,0 +1,6 @@
1
+ export * from './classes.np.types.js';
2
+ export * from './classes.np.certificatemanager.js';
3
+ export * from './classes.np.connectionpool.js';
4
+ export * from './classes.np.requesthandler.js';
5
+ export * from './classes.np.websockethandler.js';
6
+ export * from './classes.np.networkproxy.js';
@@ -0,0 +1,8 @@
1
+ // Re-export all components for easier imports
2
+ export * from './classes.np.types.js';
3
+ export * from './classes.np.certificatemanager.js';
4
+ export * from './classes.np.connectionpool.js';
5
+ export * from './classes.np.requesthandler.js';
6
+ export * from './classes.np.websockethandler.js';
7
+ export * from './classes.np.networkproxy.js';
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9uZXR3b3JrcHJveHkvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsOENBQThDO0FBQzlDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyxvQ0FBb0MsQ0FBQztBQUNuRCxjQUFjLGdDQUFnQyxDQUFDO0FBQy9DLGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYyxrQ0FBa0MsQ0FBQztBQUNqRCxjQUFjLDhCQUE4QixDQUFDIn0=