@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,278 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { type INetworkProxyOptions, type ILogger, createLogger, type IReverseProxyConfig } from './classes.np.types.js';
3
+ import { ConnectionPool } from './classes.np.connectionpool.js';
4
+ import { ProxyRouter } from '../classes.router.js';
5
+
6
+ /**
7
+ * Interface for tracking metrics
8
+ */
9
+ export interface IMetricsTracker {
10
+ incrementRequestsServed(): void;
11
+ incrementFailedRequests(): void;
12
+ }
13
+
14
+ /**
15
+ * Handles HTTP request processing and proxying
16
+ */
17
+ export class RequestHandler {
18
+ private defaultHeaders: { [key: string]: string } = {};
19
+ private logger: ILogger;
20
+ private metricsTracker: IMetricsTracker | null = null;
21
+
22
+ constructor(
23
+ private options: INetworkProxyOptions,
24
+ private connectionPool: ConnectionPool,
25
+ private router: ProxyRouter
26
+ ) {
27
+ this.logger = createLogger(options.logLevel || 'info');
28
+ }
29
+
30
+ /**
31
+ * Set the metrics tracker instance
32
+ */
33
+ public setMetricsTracker(tracker: IMetricsTracker): void {
34
+ this.metricsTracker = tracker;
35
+ }
36
+
37
+ /**
38
+ * Set default headers to be included in all responses
39
+ */
40
+ public setDefaultHeaders(headers: { [key: string]: string }): void {
41
+ this.defaultHeaders = {
42
+ ...this.defaultHeaders,
43
+ ...headers
44
+ };
45
+ this.logger.info('Updated default response headers');
46
+ }
47
+
48
+ /**
49
+ * Get all default headers
50
+ */
51
+ public getDefaultHeaders(): { [key: string]: string } {
52
+ return { ...this.defaultHeaders };
53
+ }
54
+
55
+ /**
56
+ * Apply CORS headers to response if configured
57
+ */
58
+ private applyCorsHeaders(
59
+ res: plugins.http.ServerResponse,
60
+ req: plugins.http.IncomingMessage
61
+ ): void {
62
+ if (!this.options.cors) {
63
+ return;
64
+ }
65
+
66
+ // Apply CORS headers
67
+ if (this.options.cors.allowOrigin) {
68
+ res.setHeader('Access-Control-Allow-Origin', this.options.cors.allowOrigin);
69
+ }
70
+
71
+ if (this.options.cors.allowMethods) {
72
+ res.setHeader('Access-Control-Allow-Methods', this.options.cors.allowMethods);
73
+ }
74
+
75
+ if (this.options.cors.allowHeaders) {
76
+ res.setHeader('Access-Control-Allow-Headers', this.options.cors.allowHeaders);
77
+ }
78
+
79
+ if (this.options.cors.maxAge) {
80
+ res.setHeader('Access-Control-Max-Age', this.options.cors.maxAge.toString());
81
+ }
82
+
83
+ // Handle CORS preflight requests
84
+ if (req.method === 'OPTIONS') {
85
+ res.statusCode = 204; // No content
86
+ res.end();
87
+ return;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Apply default headers to response
93
+ */
94
+ private applyDefaultHeaders(res: plugins.http.ServerResponse): void {
95
+ // Apply default headers
96
+ for (const [key, value] of Object.entries(this.defaultHeaders)) {
97
+ if (!res.hasHeader(key)) {
98
+ res.setHeader(key, value);
99
+ }
100
+ }
101
+
102
+ // Add server identifier if not already set
103
+ if (!res.hasHeader('Server')) {
104
+ res.setHeader('Server', 'NetworkProxy');
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Handle an HTTP request
110
+ */
111
+ public async handleRequest(
112
+ req: plugins.http.IncomingMessage,
113
+ res: plugins.http.ServerResponse
114
+ ): Promise<void> {
115
+ // Record start time for logging
116
+ const startTime = Date.now();
117
+
118
+ // Apply CORS headers if configured
119
+ this.applyCorsHeaders(res, req);
120
+
121
+ // If this is an OPTIONS request, the response has already been ended in applyCorsHeaders
122
+ // so we should return early to avoid trying to set more headers
123
+ if (req.method === 'OPTIONS') {
124
+ // Increment metrics for OPTIONS requests too
125
+ if (this.metricsTracker) {
126
+ this.metricsTracker.incrementRequestsServed();
127
+ }
128
+ return;
129
+ }
130
+
131
+ // Apply default headers
132
+ this.applyDefaultHeaders(res);
133
+
134
+ try {
135
+ // Find target based on hostname
136
+ const proxyConfig = this.router.routeReq(req);
137
+
138
+ if (!proxyConfig) {
139
+ // No matching proxy configuration
140
+ this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
141
+ res.statusCode = 404;
142
+ res.end('Not Found: No proxy configuration for this host');
143
+
144
+ // Increment failed requests counter
145
+ if (this.metricsTracker) {
146
+ this.metricsTracker.incrementFailedRequests();
147
+ }
148
+
149
+ return;
150
+ }
151
+
152
+ // Get destination IP using round-robin if multiple IPs configured
153
+ const destination = this.connectionPool.getNextTarget(
154
+ proxyConfig.destinationIps,
155
+ proxyConfig.destinationPorts[0]
156
+ );
157
+
158
+ // Create options for the proxy request
159
+ const options: plugins.http.RequestOptions = {
160
+ hostname: destination.host,
161
+ port: destination.port,
162
+ path: req.url,
163
+ method: req.method,
164
+ headers: { ...req.headers }
165
+ };
166
+
167
+ // Remove host header to avoid issues with virtual hosts on target server
168
+ // The host header should match the target server's expected hostname
169
+ if (options.headers && options.headers.host) {
170
+ if ((proxyConfig as IReverseProxyConfig).rewriteHostHeader) {
171
+ options.headers.host = `${destination.host}:${destination.port}`;
172
+ }
173
+ }
174
+
175
+ this.logger.debug(
176
+ `Proxying request to ${destination.host}:${destination.port}${req.url}`,
177
+ { method: req.method }
178
+ );
179
+
180
+ // Create proxy request
181
+ const proxyReq = plugins.http.request(options, (proxyRes) => {
182
+ // Copy status code
183
+ res.statusCode = proxyRes.statusCode || 500;
184
+
185
+ // Copy headers from proxy response to client response
186
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
187
+ if (value !== undefined) {
188
+ res.setHeader(key, value);
189
+ }
190
+ }
191
+
192
+ // Pipe proxy response to client response
193
+ proxyRes.pipe(res);
194
+
195
+ // Increment served requests counter when the response finishes
196
+ res.on('finish', () => {
197
+ if (this.metricsTracker) {
198
+ this.metricsTracker.incrementRequestsServed();
199
+ }
200
+
201
+ // Log the completed request
202
+ const duration = Date.now() - startTime;
203
+ this.logger.debug(
204
+ `Request completed in ${duration}ms: ${req.method} ${req.url} ${res.statusCode}`,
205
+ { duration, statusCode: res.statusCode }
206
+ );
207
+ });
208
+ });
209
+
210
+ // Handle proxy request errors
211
+ proxyReq.on('error', (error) => {
212
+ const duration = Date.now() - startTime;
213
+ this.logger.error(
214
+ `Proxy error for ${req.method} ${req.url}: ${error.message}`,
215
+ { duration, error: error.message }
216
+ );
217
+
218
+ // Increment failed requests counter
219
+ if (this.metricsTracker) {
220
+ this.metricsTracker.incrementFailedRequests();
221
+ }
222
+
223
+ // Check if headers have already been sent
224
+ if (!res.headersSent) {
225
+ res.statusCode = 502;
226
+ res.end(`Bad Gateway: ${error.message}`);
227
+ } else {
228
+ // If headers already sent, just close the connection
229
+ res.end();
230
+ }
231
+ });
232
+
233
+ // Pipe request body to proxy request and handle client-side errors
234
+ req.pipe(proxyReq);
235
+
236
+ // Handle client disconnection
237
+ req.on('error', (error) => {
238
+ this.logger.debug(`Client connection error: ${error.message}`);
239
+ proxyReq.destroy();
240
+
241
+ // Increment failed requests counter on client errors
242
+ if (this.metricsTracker) {
243
+ this.metricsTracker.incrementFailedRequests();
244
+ }
245
+ });
246
+
247
+ // Handle response errors
248
+ res.on('error', (error) => {
249
+ this.logger.debug(`Response error: ${error.message}`);
250
+ proxyReq.destroy();
251
+
252
+ // Increment failed requests counter on response errors
253
+ if (this.metricsTracker) {
254
+ this.metricsTracker.incrementFailedRequests();
255
+ }
256
+ });
257
+
258
+ } catch (error) {
259
+ // Handle any unexpected errors
260
+ this.logger.error(
261
+ `Unexpected error handling request: ${error.message}`,
262
+ { error: error.stack }
263
+ );
264
+
265
+ // Increment failed requests counter
266
+ if (this.metricsTracker) {
267
+ this.metricsTracker.incrementFailedRequests();
268
+ }
269
+
270
+ if (!res.headersSent) {
271
+ res.statusCode = 500;
272
+ res.end('Internal Server Error');
273
+ } else {
274
+ res.end();
275
+ }
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,123 @@
1
+ import * as plugins from '../plugins.js';
2
+
3
+ /**
4
+ * Configuration options for NetworkProxy
5
+ */
6
+ export interface INetworkProxyOptions {
7
+ port: number;
8
+ maxConnections?: number;
9
+ keepAliveTimeout?: number;
10
+ headersTimeout?: number;
11
+ logLevel?: 'error' | 'warn' | 'info' | 'debug';
12
+ cors?: {
13
+ allowOrigin?: string;
14
+ allowMethods?: string;
15
+ allowHeaders?: string;
16
+ maxAge?: number;
17
+ };
18
+
19
+ // Settings for PortProxy integration
20
+ connectionPoolSize?: number; // Maximum connections to maintain in the pool to each backend
21
+ portProxyIntegration?: boolean; // Flag to indicate this proxy is used by PortProxy
22
+ useExternalPort80Handler?: boolean; // Flag to indicate using external Port80Handler
23
+
24
+ // ACME certificate management options
25
+ acme?: {
26
+ enabled?: boolean; // Whether to enable automatic certificate management
27
+ port?: number; // Port to listen on for ACME challenges (default: 80)
28
+ contactEmail?: string; // Email for Let's Encrypt account
29
+ useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
30
+ renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
31
+ autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
32
+ certificateStore?: string; // Directory to store certificates (default: ./certs)
33
+ skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Interface for a certificate entry in the cache
39
+ */
40
+ export interface ICertificateEntry {
41
+ key: string;
42
+ cert: string;
43
+ expires?: Date;
44
+ }
45
+
46
+ /**
47
+ * Interface for reverse proxy configuration
48
+ */
49
+ export interface IReverseProxyConfig {
50
+ destinationIps: string[];
51
+ destinationPorts: number[];
52
+ hostName: string;
53
+ privateKey: string;
54
+ publicKey: string;
55
+ authentication?: {
56
+ type: 'Basic';
57
+ user: string;
58
+ pass: string;
59
+ };
60
+ rewriteHostHeader?: boolean;
61
+ }
62
+
63
+ /**
64
+ * Interface for connection tracking in the pool
65
+ */
66
+ export interface IConnectionEntry {
67
+ socket: plugins.net.Socket;
68
+ lastUsed: number;
69
+ isIdle: boolean;
70
+ }
71
+
72
+ /**
73
+ * WebSocket with heartbeat interface
74
+ */
75
+ export interface IWebSocketWithHeartbeat extends plugins.wsDefault {
76
+ lastPong: number;
77
+ isAlive: boolean;
78
+ }
79
+
80
+ /**
81
+ * Logger interface for consistent logging across components
82
+ */
83
+ export interface ILogger {
84
+ debug(message: string, data?: any): void;
85
+ info(message: string, data?: any): void;
86
+ warn(message: string, data?: any): void;
87
+ error(message: string, data?: any): void;
88
+ }
89
+
90
+ /**
91
+ * Creates a logger based on the specified log level
92
+ */
93
+ export function createLogger(logLevel: string = 'info'): ILogger {
94
+ const logLevels = {
95
+ error: 0,
96
+ warn: 1,
97
+ info: 2,
98
+ debug: 3
99
+ };
100
+
101
+ return {
102
+ debug: (message: string, data?: any) => {
103
+ if (logLevels[logLevel] >= logLevels.debug) {
104
+ console.log(`[DEBUG] ${message}`, data || '');
105
+ }
106
+ },
107
+ info: (message: string, data?: any) => {
108
+ if (logLevels[logLevel] >= logLevels.info) {
109
+ console.log(`[INFO] ${message}`, data || '');
110
+ }
111
+ },
112
+ warn: (message: string, data?: any) => {
113
+ if (logLevels[logLevel] >= logLevels.warn) {
114
+ console.warn(`[WARN] ${message}`, data || '');
115
+ }
116
+ },
117
+ error: (message: string, data?: any) => {
118
+ if (logLevels[logLevel] >= logLevels.error) {
119
+ console.error(`[ERROR] ${message}`, data || '');
120
+ }
121
+ }
122
+ };
123
+ }
@@ -0,0 +1,226 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { type INetworkProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './classes.np.types.js';
3
+ import { ConnectionPool } from './classes.np.connectionpool.js';
4
+ import { ProxyRouter } from '../classes.router.js';
5
+
6
+ /**
7
+ * Handles WebSocket connections and proxying
8
+ */
9
+ export class WebSocketHandler {
10
+ private heartbeatInterval: NodeJS.Timeout | null = null;
11
+ private wsServer: plugins.ws.WebSocketServer | null = null;
12
+ private logger: ILogger;
13
+
14
+ constructor(
15
+ private options: INetworkProxyOptions,
16
+ private connectionPool: ConnectionPool,
17
+ private router: ProxyRouter
18
+ ) {
19
+ this.logger = createLogger(options.logLevel || 'info');
20
+ }
21
+
22
+ /**
23
+ * Initialize WebSocket server on an existing HTTPS server
24
+ */
25
+ public initialize(server: plugins.https.Server): void {
26
+ // Create WebSocket server
27
+ this.wsServer = new plugins.ws.WebSocketServer({
28
+ server: server,
29
+ clientTracking: true
30
+ });
31
+
32
+ // Handle WebSocket connections
33
+ this.wsServer.on('connection', (wsIncoming: IWebSocketWithHeartbeat, req: plugins.http.IncomingMessage) => {
34
+ this.handleWebSocketConnection(wsIncoming, req);
35
+ });
36
+
37
+ // Start the heartbeat interval
38
+ this.startHeartbeat();
39
+
40
+ this.logger.info('WebSocket handler initialized');
41
+ }
42
+
43
+ /**
44
+ * Start the heartbeat interval to check for inactive WebSocket connections
45
+ */
46
+ private startHeartbeat(): void {
47
+ // Clean up existing interval if any
48
+ if (this.heartbeatInterval) {
49
+ clearInterval(this.heartbeatInterval);
50
+ }
51
+
52
+ // Set up the heartbeat interval (check every 30 seconds)
53
+ this.heartbeatInterval = setInterval(() => {
54
+ if (!this.wsServer || this.wsServer.clients.size === 0) {
55
+ return; // Skip if no active connections
56
+ }
57
+
58
+ this.logger.debug(`WebSocket heartbeat check for ${this.wsServer.clients.size} clients`);
59
+
60
+ this.wsServer.clients.forEach((ws: plugins.wsDefault) => {
61
+ const wsWithHeartbeat = ws as IWebSocketWithHeartbeat;
62
+
63
+ if (wsWithHeartbeat.isAlive === false) {
64
+ this.logger.debug('Terminating inactive WebSocket connection');
65
+ return wsWithHeartbeat.terminate();
66
+ }
67
+
68
+ wsWithHeartbeat.isAlive = false;
69
+ wsWithHeartbeat.ping();
70
+ });
71
+ }, 30000);
72
+
73
+ // Make sure the interval doesn't keep the process alive
74
+ if (this.heartbeatInterval.unref) {
75
+ this.heartbeatInterval.unref();
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Handle a new WebSocket connection
81
+ */
82
+ private handleWebSocketConnection(wsIncoming: IWebSocketWithHeartbeat, req: plugins.http.IncomingMessage): void {
83
+ try {
84
+ // Initialize heartbeat tracking
85
+ wsIncoming.isAlive = true;
86
+ wsIncoming.lastPong = Date.now();
87
+
88
+ // Handle pong messages to track liveness
89
+ wsIncoming.on('pong', () => {
90
+ wsIncoming.isAlive = true;
91
+ wsIncoming.lastPong = Date.now();
92
+ });
93
+
94
+ // Find target configuration based on request
95
+ const proxyConfig = this.router.routeReq(req);
96
+
97
+ if (!proxyConfig) {
98
+ this.logger.warn(`No proxy configuration for WebSocket host: ${req.headers.host}`);
99
+ wsIncoming.close(1008, 'No proxy configuration for this host');
100
+ return;
101
+ }
102
+
103
+ // Get destination target using round-robin if multiple targets
104
+ const destination = this.connectionPool.getNextTarget(
105
+ proxyConfig.destinationIps,
106
+ proxyConfig.destinationPorts[0]
107
+ );
108
+
109
+ // Build target URL
110
+ const protocol = (req.socket as any).encrypted ? 'wss' : 'ws';
111
+ const targetUrl = `${protocol}://${destination.host}:${destination.port}${req.url}`;
112
+
113
+ this.logger.debug(`WebSocket connection from ${req.socket.remoteAddress} to ${targetUrl}`);
114
+
115
+ // Create headers for outgoing WebSocket connection
116
+ const headers: { [key: string]: string } = {};
117
+
118
+ // Copy relevant headers from incoming request
119
+ for (const [key, value] of Object.entries(req.headers)) {
120
+ if (value && typeof value === 'string' &&
121
+ key.toLowerCase() !== 'connection' &&
122
+ key.toLowerCase() !== 'upgrade' &&
123
+ key.toLowerCase() !== 'sec-websocket-key' &&
124
+ key.toLowerCase() !== 'sec-websocket-version') {
125
+ headers[key] = value;
126
+ }
127
+ }
128
+
129
+ // Override host header if needed
130
+ if ((proxyConfig as IReverseProxyConfig).rewriteHostHeader) {
131
+ headers['host'] = `${destination.host}:${destination.port}`;
132
+ }
133
+
134
+ // Create outgoing WebSocket connection
135
+ const wsOutgoing = new plugins.wsDefault(targetUrl, {
136
+ headers: headers,
137
+ followRedirects: true
138
+ });
139
+
140
+ // Handle connection errors
141
+ wsOutgoing.on('error', (err) => {
142
+ this.logger.error(`WebSocket target connection error: ${err.message}`);
143
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
144
+ wsIncoming.close(1011, 'Internal server error');
145
+ }
146
+ });
147
+
148
+ // Handle outgoing connection open
149
+ wsOutgoing.on('open', () => {
150
+ // Forward incoming messages to outgoing connection
151
+ wsIncoming.on('message', (data, isBinary) => {
152
+ if (wsOutgoing.readyState === wsOutgoing.OPEN) {
153
+ wsOutgoing.send(data, { binary: isBinary });
154
+ }
155
+ });
156
+
157
+ // Forward outgoing messages to incoming connection
158
+ wsOutgoing.on('message', (data, isBinary) => {
159
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
160
+ wsIncoming.send(data, { binary: isBinary });
161
+ }
162
+ });
163
+
164
+ // Handle closing of connections
165
+ wsIncoming.on('close', (code, reason) => {
166
+ this.logger.debug(`WebSocket client connection closed: ${code} ${reason}`);
167
+ if (wsOutgoing.readyState === wsOutgoing.OPEN) {
168
+ wsOutgoing.close(code, reason);
169
+ }
170
+ });
171
+
172
+ wsOutgoing.on('close', (code, reason) => {
173
+ this.logger.debug(`WebSocket target connection closed: ${code} ${reason}`);
174
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
175
+ wsIncoming.close(code, reason);
176
+ }
177
+ });
178
+
179
+ this.logger.debug(`WebSocket connection established: ${req.headers.host} -> ${destination.host}:${destination.port}`);
180
+ });
181
+
182
+ } catch (error) {
183
+ this.logger.error(`Error handling WebSocket connection: ${error.message}`);
184
+ if (wsIncoming.readyState === wsIncoming.OPEN) {
185
+ wsIncoming.close(1011, 'Internal server error');
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Get information about active WebSocket connections
192
+ */
193
+ public getConnectionInfo(): { activeConnections: number } {
194
+ return {
195
+ activeConnections: this.wsServer ? this.wsServer.clients.size : 0
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Shutdown the WebSocket handler
201
+ */
202
+ public shutdown(): void {
203
+ // Stop heartbeat interval
204
+ if (this.heartbeatInterval) {
205
+ clearInterval(this.heartbeatInterval);
206
+ this.heartbeatInterval = null;
207
+ }
208
+
209
+ // Close all WebSocket connections
210
+ if (this.wsServer) {
211
+ this.logger.info(`Closing ${this.wsServer.clients.size} WebSocket connections`);
212
+
213
+ for (const client of this.wsServer.clients) {
214
+ try {
215
+ client.terminate();
216
+ } catch (error) {
217
+ this.logger.error('Error terminating WebSocket client', error);
218
+ }
219
+ }
220
+
221
+ // Close the server
222
+ this.wsServer.close();
223
+ this.wsServer = null;
224
+ }
225
+ }
226
+ }
@@ -0,0 +1,7 @@
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';