@push.rocks/smartproxy 20.0.0 → 21.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.
@@ -1,312 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import { ForwardingHandler } from './base-handler.js';
3
- import type { IForwardConfig } from '../config/forwarding-types.js';
4
- import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
5
- import { setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
6
-
7
- /**
8
- * Handler for HTTPS termination with HTTP backend
9
- */
10
- export class HttpsTerminateToHttpHandler extends ForwardingHandler {
11
- private tlsServer: plugins.tls.Server | null = null;
12
- private secureContext: plugins.tls.SecureContext | null = null;
13
-
14
- /**
15
- * Create a new HTTPS termination with HTTP backend handler
16
- * @param config The forwarding configuration
17
- */
18
- constructor(config: IForwardConfig) {
19
- super(config);
20
-
21
- // Validate that this is an HTTPS terminate to HTTP configuration
22
- if (config.type !== 'https-terminate-to-http') {
23
- throw new Error(`Invalid configuration type for HttpsTerminateToHttpHandler: ${config.type}`);
24
- }
25
- }
26
-
27
- /**
28
- * Initialize the handler, setting up TLS context
29
- */
30
- public async initialize(): Promise<void> {
31
- // We need to load or create TLS certificates
32
- if (this.config.https?.customCert) {
33
- // Use custom certificate from configuration
34
- this.secureContext = plugins.tls.createSecureContext({
35
- key: this.config.https.customCert.key,
36
- cert: this.config.https.customCert.cert
37
- });
38
-
39
- this.emit(ForwardingHandlerEvents.CERTIFICATE_LOADED, {
40
- source: 'config',
41
- domain: this.config.target.host
42
- });
43
- } else if (this.config.acme?.enabled) {
44
- // Request certificate through ACME if needed
45
- this.emit(ForwardingHandlerEvents.CERTIFICATE_NEEDED, {
46
- domain: Array.isArray(this.config.target.host)
47
- ? this.config.target.host[0]
48
- : this.config.target.host,
49
- useProduction: this.config.acme.production || false
50
- });
51
-
52
- // In a real implementation, we would wait for the certificate to be issued
53
- // For now, we'll use a dummy context
54
- this.secureContext = plugins.tls.createSecureContext({
55
- key: '-----BEGIN PRIVATE KEY-----\nDummy key\n-----END PRIVATE KEY-----',
56
- cert: '-----BEGIN CERTIFICATE-----\nDummy cert\n-----END CERTIFICATE-----'
57
- });
58
- } else {
59
- throw new Error('HTTPS termination requires either a custom certificate or ACME enabled');
60
- }
61
- }
62
-
63
- /**
64
- * Set the secure context for TLS termination
65
- * Called when a certificate is available
66
- * @param context The secure context
67
- */
68
- public setSecureContext(context: plugins.tls.SecureContext): void {
69
- this.secureContext = context;
70
- }
71
-
72
- /**
73
- * Handle a TLS/SSL socket connection by terminating TLS and forwarding to HTTP backend
74
- * @param clientSocket The incoming socket from the client
75
- */
76
- public handleConnection(clientSocket: plugins.net.Socket): void {
77
- // Make sure we have a secure context
78
- if (!this.secureContext) {
79
- clientSocket.destroy(new Error('TLS secure context not initialized'));
80
- return;
81
- }
82
-
83
- const remoteAddress = clientSocket.remoteAddress || 'unknown';
84
- const remotePort = clientSocket.remotePort || 0;
85
-
86
- // Create a TLS socket using our secure context
87
- const tlsSocket = new plugins.tls.TLSSocket(clientSocket, {
88
- secureContext: this.secureContext,
89
- isServer: true,
90
- server: this.tlsServer || undefined
91
- });
92
-
93
- this.emit(ForwardingHandlerEvents.CONNECTED, {
94
- remoteAddress,
95
- remotePort,
96
- tls: true
97
- });
98
-
99
- // Variables to track connections
100
- let backendSocket: plugins.net.Socket | null = null;
101
- let dataBuffer = Buffer.alloc(0);
102
- let connectionEstablished = false;
103
- let forwardingSetup = false;
104
-
105
- // Set up initial error handling for TLS socket
106
- const tlsCleanupHandler = (reason: string) => {
107
- if (!forwardingSetup) {
108
- // If forwarding not set up yet, emit disconnected and cleanup
109
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
110
- remoteAddress,
111
- reason
112
- });
113
- dataBuffer = Buffer.alloc(0);
114
- connectionEstablished = false;
115
-
116
- if (!tlsSocket.destroyed) {
117
- tlsSocket.destroy();
118
- }
119
- if (backendSocket && !backendSocket.destroyed) {
120
- backendSocket.destroy();
121
- }
122
- }
123
- // If forwarding is setup, setupBidirectionalForwarding will handle cleanup
124
- };
125
-
126
- setupSocketHandlers(tlsSocket, tlsCleanupHandler, undefined, 'tls');
127
-
128
- // Set timeout
129
- const timeout = this.getTimeout();
130
- tlsSocket.setTimeout(timeout);
131
-
132
- tlsSocket.on('timeout', () => {
133
- this.emit(ForwardingHandlerEvents.ERROR, {
134
- remoteAddress,
135
- error: 'TLS connection timeout'
136
- });
137
- tlsCleanupHandler('timeout');
138
- });
139
-
140
- // Handle TLS data
141
- tlsSocket.on('data', (data) => {
142
- // If backend connection already established, just forward the data
143
- if (connectionEstablished && backendSocket && !backendSocket.destroyed) {
144
- backendSocket.write(data);
145
- return;
146
- }
147
-
148
- // Append to buffer
149
- dataBuffer = Buffer.concat([dataBuffer, data]);
150
-
151
- // Very basic HTTP parsing - in a real implementation, use http-parser
152
- if (dataBuffer.includes(Buffer.from('\r\n\r\n')) && !connectionEstablished) {
153
- const target = this.getTargetFromConfig();
154
-
155
- // Create backend connection with immediate error handling
156
- backendSocket = createSocketWithErrorHandler({
157
- port: target.port,
158
- host: target.host,
159
- onError: (error) => {
160
- this.emit(ForwardingHandlerEvents.ERROR, {
161
- error: error.message,
162
- code: (error as any).code || 'UNKNOWN',
163
- remoteAddress,
164
- target: `${target.host}:${target.port}`
165
- });
166
-
167
- // Clean up the TLS socket since we can't forward
168
- if (!tlsSocket.destroyed) {
169
- tlsSocket.destroy();
170
- }
171
-
172
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
173
- remoteAddress,
174
- reason: `backend_connection_failed: ${error.message}`
175
- });
176
- },
177
- onConnect: () => {
178
- connectionEstablished = true;
179
-
180
- // Send buffered data
181
- if (dataBuffer.length > 0) {
182
- backendSocket!.write(dataBuffer);
183
- dataBuffer = Buffer.alloc(0);
184
- }
185
-
186
- // Now set up bidirectional forwarding with proper cleanup
187
- forwardingSetup = true;
188
- setupBidirectionalForwarding(tlsSocket, backendSocket!, {
189
- onCleanup: (reason) => {
190
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
191
- remoteAddress,
192
- reason
193
- });
194
- dataBuffer = Buffer.alloc(0);
195
- connectionEstablished = false;
196
- forwardingSetup = false;
197
- },
198
- enableHalfOpen: false // Close both when one closes
199
- });
200
- }
201
- });
202
-
203
- // Additional error logging for backend socket
204
- backendSocket.on('error', (error) => {
205
- if (!connectionEstablished) {
206
- // Connection failed during setup
207
- this.emit(ForwardingHandlerEvents.ERROR, {
208
- remoteAddress,
209
- error: `Target connection error: ${error.message}`
210
- });
211
- }
212
- // If connected, setupBidirectionalForwarding handles cleanup
213
- });
214
- }
215
- });
216
- }
217
-
218
- /**
219
- * Handle an HTTP request by forwarding to the HTTP backend
220
- * @param req The HTTP request
221
- * @param res The HTTP response
222
- */
223
- public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
224
- // Check if we should redirect to HTTPS
225
- if (this.config.http?.redirectToHttps) {
226
- this.redirectToHttps(req, res);
227
- return;
228
- }
229
-
230
- // Get the target from configuration
231
- const target = this.getTargetFromConfig();
232
-
233
- // Create custom headers with variable substitution
234
- const variables = {
235
- clientIp: req.socket.remoteAddress || 'unknown'
236
- };
237
-
238
- // Prepare headers, merging with any custom headers from config
239
- const headers = this.applyCustomHeaders(req.headers, variables);
240
-
241
- // Create the proxy request options
242
- const options = {
243
- hostname: target.host,
244
- port: target.port,
245
- path: req.url,
246
- method: req.method,
247
- headers
248
- };
249
-
250
- // Create the proxy request
251
- const proxyReq = plugins.http.request(options, (proxyRes) => {
252
- // Copy status code and headers from the proxied response
253
- res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
254
-
255
- // Pipe the proxy response to the client response
256
- proxyRes.pipe(res);
257
-
258
- // Track response size for logging
259
- let responseSize = 0;
260
- proxyRes.on('data', (chunk) => {
261
- responseSize += chunk.length;
262
- });
263
-
264
- proxyRes.on('end', () => {
265
- this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
266
- statusCode: proxyRes.statusCode,
267
- headers: proxyRes.headers,
268
- size: responseSize
269
- });
270
- });
271
- });
272
-
273
- // Handle errors in the proxy request
274
- proxyReq.on('error', (error) => {
275
- this.emit(ForwardingHandlerEvents.ERROR, {
276
- remoteAddress: req.socket.remoteAddress,
277
- error: `Proxy request error: ${error.message}`
278
- });
279
-
280
- // Send an error response if headers haven't been sent yet
281
- if (!res.headersSent) {
282
- res.writeHead(502, { 'Content-Type': 'text/plain' });
283
- res.end(`Error forwarding request: ${error.message}`);
284
- } else {
285
- // Just end the response if headers have already been sent
286
- res.end();
287
- }
288
- });
289
-
290
- // Track request details for logging
291
- let requestSize = 0;
292
- req.on('data', (chunk) => {
293
- requestSize += chunk.length;
294
- });
295
-
296
- // Log the request
297
- this.emit(ForwardingHandlerEvents.HTTP_REQUEST, {
298
- method: req.method,
299
- url: req.url,
300
- headers: req.headers,
301
- remoteAddress: req.socket.remoteAddress,
302
- target: `${target.host}:${target.port}`
303
- });
304
-
305
- // Pipe the client request to the proxy request
306
- if (req.readable) {
307
- req.pipe(proxyReq);
308
- } else {
309
- proxyReq.end();
310
- }
311
- }
312
- }
@@ -1,297 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import { ForwardingHandler } from './base-handler.js';
3
- import type { IForwardConfig } from '../config/forwarding-types.js';
4
- import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
5
- import { setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
6
-
7
- /**
8
- * Handler for HTTPS termination with HTTPS backend
9
- */
10
- export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
11
- private secureContext: plugins.tls.SecureContext | null = null;
12
-
13
- /**
14
- * Create a new HTTPS termination with HTTPS backend handler
15
- * @param config The forwarding configuration
16
- */
17
- constructor(config: IForwardConfig) {
18
- super(config);
19
-
20
- // Validate that this is an HTTPS terminate to HTTPS configuration
21
- if (config.type !== 'https-terminate-to-https') {
22
- throw new Error(`Invalid configuration type for HttpsTerminateToHttpsHandler: ${config.type}`);
23
- }
24
- }
25
-
26
- /**
27
- * Initialize the handler, setting up TLS context
28
- */
29
- public async initialize(): Promise<void> {
30
- // We need to load or create TLS certificates for termination
31
- if (this.config.https?.customCert) {
32
- // Use custom certificate from configuration
33
- this.secureContext = plugins.tls.createSecureContext({
34
- key: this.config.https.customCert.key,
35
- cert: this.config.https.customCert.cert
36
- });
37
-
38
- this.emit(ForwardingHandlerEvents.CERTIFICATE_LOADED, {
39
- source: 'config',
40
- domain: this.config.target.host
41
- });
42
- } else if (this.config.acme?.enabled) {
43
- // Request certificate through ACME if needed
44
- this.emit(ForwardingHandlerEvents.CERTIFICATE_NEEDED, {
45
- domain: Array.isArray(this.config.target.host)
46
- ? this.config.target.host[0]
47
- : this.config.target.host,
48
- useProduction: this.config.acme.production || false
49
- });
50
-
51
- // In a real implementation, we would wait for the certificate to be issued
52
- // For now, we'll use a dummy context
53
- this.secureContext = plugins.tls.createSecureContext({
54
- key: '-----BEGIN PRIVATE KEY-----\nDummy key\n-----END PRIVATE KEY-----',
55
- cert: '-----BEGIN CERTIFICATE-----\nDummy cert\n-----END CERTIFICATE-----'
56
- });
57
- } else {
58
- throw new Error('HTTPS termination requires either a custom certificate or ACME enabled');
59
- }
60
- }
61
-
62
- /**
63
- * Set the secure context for TLS termination
64
- * Called when a certificate is available
65
- * @param context The secure context
66
- */
67
- public setSecureContext(context: plugins.tls.SecureContext): void {
68
- this.secureContext = context;
69
- }
70
-
71
- /**
72
- * Handle a TLS/SSL socket connection by terminating TLS and creating a new TLS connection to backend
73
- * @param clientSocket The incoming socket from the client
74
- */
75
- public handleConnection(clientSocket: plugins.net.Socket): void {
76
- // Make sure we have a secure context
77
- if (!this.secureContext) {
78
- clientSocket.destroy(new Error('TLS secure context not initialized'));
79
- return;
80
- }
81
-
82
- const remoteAddress = clientSocket.remoteAddress || 'unknown';
83
- const remotePort = clientSocket.remotePort || 0;
84
-
85
- // Create a TLS socket using our secure context
86
- const tlsSocket = new plugins.tls.TLSSocket(clientSocket, {
87
- secureContext: this.secureContext,
88
- isServer: true
89
- });
90
-
91
- this.emit(ForwardingHandlerEvents.CONNECTED, {
92
- remoteAddress,
93
- remotePort,
94
- tls: true
95
- });
96
-
97
- // Variable to track backend socket
98
- let backendSocket: plugins.tls.TLSSocket | null = null;
99
- let isConnectedToBackend = false;
100
-
101
- // Set up initial error handling for TLS socket
102
- const tlsCleanupHandler = (reason: string) => {
103
- if (!isConnectedToBackend) {
104
- // If backend not connected yet, just emit disconnected event
105
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
106
- remoteAddress,
107
- reason
108
- });
109
-
110
- // Cleanup TLS socket if needed
111
- if (!tlsSocket.destroyed) {
112
- tlsSocket.destroy();
113
- }
114
- }
115
- // If connected to backend, setupBidirectionalForwarding will handle cleanup
116
- };
117
-
118
- setupSocketHandlers(tlsSocket, tlsCleanupHandler, undefined, 'tls');
119
-
120
- // Set timeout
121
- const timeout = this.getTimeout();
122
- tlsSocket.setTimeout(timeout);
123
-
124
- tlsSocket.on('timeout', () => {
125
- this.emit(ForwardingHandlerEvents.ERROR, {
126
- remoteAddress,
127
- error: 'TLS connection timeout'
128
- });
129
- tlsCleanupHandler('timeout');
130
- });
131
-
132
- // Get the target from configuration
133
- const target = this.getTargetFromConfig();
134
-
135
- // Set up the connection to the HTTPS backend
136
- const connectToBackend = () => {
137
- backendSocket = plugins.tls.connect({
138
- host: target.host,
139
- port: target.port,
140
- // In a real implementation, we would configure TLS options
141
- rejectUnauthorized: false // For testing only, never use in production
142
- }, () => {
143
- isConnectedToBackend = true;
144
-
145
- this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
146
- direction: 'outbound',
147
- target: `${target.host}:${target.port}`,
148
- tls: true
149
- });
150
-
151
- // Set up bidirectional forwarding with proper cleanup
152
- setupBidirectionalForwarding(tlsSocket, backendSocket!, {
153
- onCleanup: (reason) => {
154
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
155
- remoteAddress,
156
- reason
157
- });
158
- },
159
- enableHalfOpen: false // Close both when one closes
160
- });
161
-
162
- // Set timeout for backend socket
163
- backendSocket!.setTimeout(timeout);
164
-
165
- backendSocket!.on('timeout', () => {
166
- this.emit(ForwardingHandlerEvents.ERROR, {
167
- remoteAddress,
168
- error: 'Backend connection timeout'
169
- });
170
- // Let setupBidirectionalForwarding handle the cleanup
171
- });
172
- });
173
-
174
- // Handle backend connection errors
175
- backendSocket.on('error', (error) => {
176
- this.emit(ForwardingHandlerEvents.ERROR, {
177
- remoteAddress,
178
- error: `Backend connection error: ${error.message}`
179
- });
180
-
181
- if (!isConnectedToBackend) {
182
- // Connection failed, clean up TLS socket
183
- if (!tlsSocket.destroyed) {
184
- tlsSocket.destroy();
185
- }
186
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
187
- remoteAddress,
188
- reason: `backend_connection_failed: ${error.message}`
189
- });
190
- }
191
- // If connected, let setupBidirectionalForwarding handle cleanup
192
- });
193
- };
194
-
195
- // Wait for the TLS handshake to complete before connecting to backend
196
- tlsSocket.on('secure', () => {
197
- connectToBackend();
198
- });
199
- }
200
-
201
- /**
202
- * Handle an HTTP request by forwarding to the HTTPS backend
203
- * @param req The HTTP request
204
- * @param res The HTTP response
205
- */
206
- public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
207
- // Check if we should redirect to HTTPS
208
- if (this.config.http?.redirectToHttps) {
209
- this.redirectToHttps(req, res);
210
- return;
211
- }
212
-
213
- // Get the target from configuration
214
- const target = this.getTargetFromConfig();
215
-
216
- // Create custom headers with variable substitution
217
- const variables = {
218
- clientIp: req.socket.remoteAddress || 'unknown'
219
- };
220
-
221
- // Prepare headers, merging with any custom headers from config
222
- const headers = this.applyCustomHeaders(req.headers, variables);
223
-
224
- // Create the proxy request options
225
- const options = {
226
- hostname: target.host,
227
- port: target.port,
228
- path: req.url,
229
- method: req.method,
230
- headers,
231
- // In a real implementation, we would configure TLS options
232
- rejectUnauthorized: false // For testing only, never use in production
233
- };
234
-
235
- // Create the proxy request using HTTPS
236
- const proxyReq = plugins.https.request(options, (proxyRes) => {
237
- // Copy status code and headers from the proxied response
238
- res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
239
-
240
- // Pipe the proxy response to the client response
241
- proxyRes.pipe(res);
242
-
243
- // Track response size for logging
244
- let responseSize = 0;
245
- proxyRes.on('data', (chunk) => {
246
- responseSize += chunk.length;
247
- });
248
-
249
- proxyRes.on('end', () => {
250
- this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
251
- statusCode: proxyRes.statusCode,
252
- headers: proxyRes.headers,
253
- size: responseSize
254
- });
255
- });
256
- });
257
-
258
- // Handle errors in the proxy request
259
- proxyReq.on('error', (error) => {
260
- this.emit(ForwardingHandlerEvents.ERROR, {
261
- remoteAddress: req.socket.remoteAddress,
262
- error: `Proxy request error: ${error.message}`
263
- });
264
-
265
- // Send an error response if headers haven't been sent yet
266
- if (!res.headersSent) {
267
- res.writeHead(502, { 'Content-Type': 'text/plain' });
268
- res.end(`Error forwarding request: ${error.message}`);
269
- } else {
270
- // Just end the response if headers have already been sent
271
- res.end();
272
- }
273
- });
274
-
275
- // Track request details for logging
276
- let requestSize = 0;
277
- req.on('data', (chunk) => {
278
- requestSize += chunk.length;
279
- });
280
-
281
- // Log the request
282
- this.emit(ForwardingHandlerEvents.HTTP_REQUEST, {
283
- method: req.method,
284
- url: req.url,
285
- headers: req.headers,
286
- remoteAddress: req.socket.remoteAddress,
287
- target: `${target.host}:${target.port}`
288
- });
289
-
290
- // Pipe the client request to the proxy request
291
- if (req.readable) {
292
- req.pipe(proxyReq);
293
- } else {
294
- proxyReq.end();
295
- }
296
- }
297
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Forwarding handler implementations
3
- */
4
-
5
- export { ForwardingHandler } from './base-handler.js';
6
- export { HttpForwardingHandler } from './http-handler.js';
7
- export { HttpsPassthroughHandler } from './https-passthrough-handler.js';
8
- export { HttpsTerminateToHttpHandler } from './https-terminate-to-http-handler.js';
9
- export { HttpsTerminateToHttpsHandler } from './https-terminate-to-https-handler.js';
@@ -1,35 +0,0 @@
1
- /**
2
- * Forwarding system module
3
- * Provides a flexible and type-safe way to configure and manage various forwarding strategies
4
- */
5
-
6
- // Export handlers
7
- export { ForwardingHandler } from './handlers/base-handler.js';
8
- export * from './handlers/http-handler.js';
9
- export * from './handlers/https-passthrough-handler.js';
10
- export * from './handlers/https-terminate-to-http-handler.js';
11
- export * from './handlers/https-terminate-to-https-handler.js';
12
-
13
- // Export factory
14
- export * from './factory/forwarding-factory.js';
15
-
16
- // Export types - these include TForwardingType and IForwardConfig
17
- export type {
18
- TForwardingType,
19
- IForwardConfig,
20
- IForwardingHandler
21
- } from './config/forwarding-types.js';
22
-
23
- export {
24
- ForwardingHandlerEvents
25
- } from './config/forwarding-types.js';
26
-
27
- // Export route helpers directly from route-patterns
28
- export {
29
- createHttpRoute,
30
- createHttpsTerminateRoute,
31
- createHttpsPassthroughRoute,
32
- createHttpToHttpsRedirect,
33
- createCompleteHttpsServer,
34
- createLoadBalancerRoute
35
- } from '../proxies/smart-proxy/utils/route-patterns.js';