@push.rocks/smartproxy 3.0.61 → 3.1.3

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 (33) hide show
  1. package/assets/certs/cert.pem +19 -0
  2. package/assets/certs/key.pem +28 -0
  3. package/dist_ts/00_commitinfo_data.js +2 -2
  4. package/dist_ts/smartproxy.classes.networkproxy.d.ts +5 -4
  5. package/dist_ts/smartproxy.classes.networkproxy.js +193 -233
  6. package/dist_ts/smartproxy.classes.router.d.ts +0 -1
  7. package/dist_ts/smartproxy.classes.router.js +11 -2
  8. package/dist_ts/smartproxy.classes.sslredirect.d.ts +0 -1
  9. package/dist_ts/smartproxy.helpers.certificates.d.ts +5 -0
  10. package/dist_ts/smartproxy.helpers.certificates.js +23 -0
  11. package/dist_ts/smartproxy.portproxy.d.ts +0 -1
  12. package/package.json +18 -14
  13. package/readme.md +3 -1
  14. package/ts/00_commitinfo_data.ts +1 -1
  15. package/ts/smartproxy.classes.networkproxy.ts +225 -258
  16. package/ts/smartproxy.classes.router.ts +10 -1
  17. package/ts/smartproxy.helpers.certificates.ts +30 -0
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.js +0 -7
  20. package/dist/smartproxy.classes.proxyworker.d.ts +0 -23
  21. package/dist/smartproxy.classes.proxyworker.js +0 -262
  22. package/dist/smartproxy.classes.router.d.ts +0 -16
  23. package/dist/smartproxy.classes.router.js +0 -41
  24. package/dist/smartproxy.classes.smartproxy.d.ts +0 -17
  25. package/dist/smartproxy.classes.smartproxy.js +0 -43
  26. package/dist/smartproxy.plugins.d.ts +0 -17
  27. package/dist/smartproxy.plugins.js +0 -43
  28. package/dist/smartproxy.portproxy.d.ts +0 -6
  29. package/dist/smartproxy.portproxy.js +0 -38
  30. package/dist_ts/smartproxy.classes.proxyworker.d.ts +0 -33
  31. package/dist_ts/smartproxy.classes.proxyworker.js +0 -303
  32. package/dist_ts/smartproxy.classes.smartproxy.d.ts +0 -20
  33. package/dist_ts/smartproxy.classes.smartproxy.js +0 -38
@@ -1,18 +1,26 @@
1
1
  import * as plugins from './smartproxy.plugins.js';
2
2
  import { ProxyRouter } from './smartproxy.classes.router.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { fileURLToPath } from 'url';
3
6
 
4
7
  export interface INetworkProxyOptions {
5
8
  port: number;
6
9
  }
7
10
 
11
+ interface IWebSocketWithHeartbeat extends plugins.wsDefault {
12
+ lastPong: number;
13
+ }
14
+
8
15
  export class NetworkProxy {
9
- // INSTANCE
10
16
  public options: INetworkProxyOptions;
11
17
  public proxyConfigs: plugins.tsclass.network.IReverseProxyConfig[] = [];
12
18
  public httpsServer: plugins.https.Server;
13
19
  public router = new ProxyRouter();
14
20
  public socketMap = new plugins.lik.ObjectMap<plugins.net.Socket>();
15
21
  public defaultHeaders: { [key: string]: string } = {};
22
+ public heartbeatInterval: NodeJS.Timeout;
23
+ private defaultCertificates: { key: string; cert: string };
16
24
 
17
25
  public alreadyAddedReverseConfigs: {
18
26
  [hostName: string]: plugins.tsclass.network.IReverseProxyConfig;
@@ -20,263 +28,90 @@ export class NetworkProxy {
20
28
 
21
29
  constructor(optionsArg: INetworkProxyOptions) {
22
30
  this.options = optionsArg;
31
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
32
+ const certPath = path.join(__dirname, '..', 'assets', 'certs');
33
+
34
+ try {
35
+ this.defaultCertificates = {
36
+ key: fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8'),
37
+ cert: fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8')
38
+ };
39
+ } catch (error) {
40
+ console.error('Error loading certificates:', error);
41
+ throw error;
42
+ }
23
43
  }
24
44
 
25
- /**
26
- * starts the proxyInstance
27
- */
28
45
  public async start() {
46
+ // Instead of marking the callback async (which Node won't await),
47
+ // we call our async handler and catch errors.
29
48
  this.httpsServer = plugins.https.createServer(
30
- // ================
31
- // Spotted this keypair in the code?
32
- // Don't get exited:
33
- // It is an invalid default keypair.
34
- // For proper requests custom domain level keypairs are used that are provided in the reverse config
35
- // ================
36
49
  {
37
- key: `-----BEGIN PRIVATE KEY-----
38
- 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
- 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
- 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
- b+szLBZqxrNDBFl2SRdviconYgVnHbaqcAPj/lK6D6x94qgUEX+vMjbIruuiCe3u
42
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 1ZSU4OwqNavphy6cklONShQOmE8LMI0wRbunLjIFY8fme/8u+tVvWrTuJiCGPnXQ
59
- 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
- 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
- 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
- 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
- YtjEuQNmPCgnfz8iXx+vDYx8hwLHNv+DoX2WYuThUnul/QGSKL3xh3qWd8rotnUB
64
- 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
- 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
- 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
- rlUMl6/2Gr9uzuSC0WPVAE6OLvLNcQafw1mQ1UTJiEzYvczJKwipzXcgGQWO9Q9a
68
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 8jCyWKoZ0gJNEeK32GO5UA7dcgBHD3vQWa3lijo8COsznboaJe7M6PQpa/2S2H3+
82
- 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
- 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
- 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
- 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
- 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
87
- 2TkK01XDDJURK5Lhuvc7WrK2rSJ/fK+0GA==
88
- -----END PRIVATE KEY-----
89
- `,
90
- cert: `-----BEGIN CERTIFICATE-----
91
- MIIEljCCAn4CCQDY+ZbC9FASVjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJE
92
- RTAeFw0xOTA5MjAxNjAxNDRaFw0yMDA5MTkxNjAxNDRaMA0xCzAJBgNVBAYTAkRF
93
- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4thf9JEK/epoXt8hFr8t
94
- pkRzmaEkgbSKoOga3uGXDLvdNf3BzSIxZ8pzRhZfUnutcmW1thdz3wre/pEJR7oN
95
- QsfixbLL8/oS5QeXKiUGX0Ssfdg4W0TsoLcRva+1AZsf38MfiUPhzh1/UW/rMywW
96
- asazQwRZdkkXb4nKJ2IFZx22qnAD4/5Sug+sfeKoFBF/rzI2yK7rognt7kW2LHv6
97
- rswHnZ1Z2P/gbhlZ/EhG9hFVRZwRLDscWKcuWcxkePDt2J1pDNqD6SYa6ZjGC3AE
98
- TJw5iEA1bLQ9YvjDNpVYcf6ZvcSilIFjSQu5cs9sUbHGeKTrS5HzfeJXh1PfJyL8
99
- X0Hu7UBSjfSudso3baE9FGiBFBW2cnXZKDZGtV8eq/qxPetOOgS09pVbNP6508WV
100
- BR+rz98/VDZLZqcbZ2UpOuz4+kAKmbYE9GplxKQZZO7wWEox7Mid/uUdcqEo4QKn
101
- no6ujOuzQzn5a2oOS0k5Hk3uHapNJWlW9YI3LHtfADpYH+6cOR+/c3JWBzQJ6AD7
102
- muvNzA9mWXeHqLxMMP4pkmb7otzZYrEkodUqJgAQxcYhGh6XsCPfJ/D9RN734OJc
103
- gleVXFI8Kz455HxCW19XNfz16k7T6kqhZ/6SOBbkxEuqg7oEthAP109ZZzgx4oDo
104
- hQsw24TjLkI4SPIc7nr60UUCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAu0+zrg0C
105
- mlSv4Yi24OwB7TBvx+WHesl1IilCUdTiiUMo3NumvsU9Dr3Jkd0jGqYI0eyH4gIt
106
- KrhAveXfEw7tAOEHiYicmAdIFtyzh++ZWb8mgbBeqij1MP/76Jv+cc0lUqpfRo/A
107
- qytAsPAILuyL1o1jh28JHcq+v+WYn/FEhjUlH6emhGKGlsAjhUPjzK8MEshNolhj
108
- t2UXw9WB5B2xWvrqlNMy0F3NAZBkZ/+k21HZo6FmVi+q6OEGcOo7wJt6wrH/lko9
109
- LxX96GC1JoN1Pfr2FoTKy1WHzrSfyGmDIUCrbaYQ58UuMOR+5eIPPdkf/030u5eX
110
- xXhF2fBujD57E2zQGh/l2MrOjamcSo0+wYhOqlX3WNdaKNAzPqloBnF6w7eqLYde
111
- h9He39ySmxjENwv3miOjEP1sBeMBSRfL/ckEonfK5uJgYA5nVMQ3ojUeDMZzLfFE
112
- Ue2WHt+uPyYk7mMZfOrK2uHzI2/Coqj7lbfRodFwj+fCArYBck2NZannDPKA6X8V
113
- TzJTbTCteOUUJTrcfZ0gGhGkF4nYLmX5OI+TPqrDJf0fZ+mzAEHzDDVXcBYpYRDr
114
- r8d9QwrK+WaqVi2ofbMfMByVF72jgeJNa4nxwT9bVbu/Q1T2Lt+YPb4pQ7yCoUgS
115
- JNj2Dr5H0XoLFFnvuvzcRbhlJ9J67JzR+7g=
116
- -----END CERTIFICATE-----
117
- `,
50
+ key: this.defaultCertificates.key,
51
+ cert: this.defaultCertificates.cert
118
52
  },
119
- async (originRequest, originResponse) => {
120
- /**
121
- * endRequest function
122
- * can be used to prematurely end a request
123
- */
124
- const endOriginReqRes = (
125
- statusArg: number = 404,
126
- messageArg: string = 'This route is not available on this server.',
127
- headers: plugins.http.OutgoingHttpHeaders = {}
128
- ) => {
129
- originResponse.writeHead(statusArg, messageArg);
130
- originResponse.end(messageArg);
131
- if (originRequest.socket !== originResponse.socket) {
132
- console.log('hey, something is strange.');
53
+ (originRequest, originResponse) => {
54
+ this.handleRequest(originRequest, originResponse).catch((error) => {
55
+ console.error('Unhandled error in request handler:', error);
56
+ try {
57
+ originResponse.end();
58
+ } catch (err) {
59
+ // ignore errors during cleanup
133
60
  }
134
- originResponse.destroy();
135
- };
136
-
137
- console.log(
138
- `got request: ${originRequest.headers.host}${plugins.url.parse(originRequest.url).path}`
139
- );
140
- const destinationConfig = this.router.routeReq(originRequest);
61
+ });
62
+ },
63
+ );
141
64
 
142
- if (!destinationConfig) {
143
- console.log(
144
- `${originRequest.headers.host} can't be routed properly. Terminating request.`
145
- );
146
- endOriginReqRes();
147
- return;
148
- }
65
+ // Enable websockets
66
+ const wsServer = new plugins.ws.WebSocketServer({ server: this.httpsServer });
149
67
 
150
- // authentication
151
- if (destinationConfig.authentication) {
152
- const authInfo = destinationConfig.authentication;
153
- switch (authInfo.type) {
154
- case 'Basic':
155
- const authHeader = originRequest.headers.authorization;
156
- if (authHeader) {
157
- if (!authHeader.includes('Basic ')) {
158
- return endOriginReqRes(401, 'Authentication required', {
159
- 'WWW-Authenticate': 'Basic realm="Access to the staging site", charset="UTF-8"',
160
- });
161
- }
162
- const authStringBase64 = originRequest.headers.authorization.replace('Basic ', '');
163
- const authString: string = plugins.smartstring.base64.decode(authStringBase64);
164
- const userPassArray = authString.split(':');
165
- const user = userPassArray[0];
166
- const pass = userPassArray[1];
167
- if (user === authInfo.user && pass === authInfo.pass) {
168
- console.log('request successfully authenticated');
169
- } else {
170
- return endOriginReqRes(403, 'Forbidden: Wrong credentials');
171
- }
172
- }
173
- break;
174
- default:
175
- return endOriginReqRes(
176
- 403,
177
- 'Forbidden: unsupported authentication method configured. Please report to the admin.'
178
- );
179
- }
68
+ // Set up the heartbeat interval
69
+ this.heartbeatInterval = setInterval(() => {
70
+ wsServer.clients.forEach((ws: plugins.wsDefault) => {
71
+ const wsIncoming = ws as IWebSocketWithHeartbeat;
72
+ if (!wsIncoming.lastPong) {
73
+ wsIncoming.lastPong = Date.now();
180
74
  }
181
-
182
- let destinationUrl: string;
183
- if (destinationConfig) {
184
- destinationUrl = `http://${destinationConfig.destinationIp}:${destinationConfig.destinationPort}${originRequest.url}`;
75
+ if (Date.now() - wsIncoming.lastPong > 5 * 60 * 1000) {
76
+ console.log('Terminating websocket due to missing pong for 5 minutes.');
77
+ wsIncoming.terminate();
185
78
  } else {
186
- return endOriginReqRes();
79
+ wsIncoming.ping();
187
80
  }
188
- console.log(destinationUrl);
189
- try {
190
- const proxyResponse = await plugins.smartrequest.request(
191
- destinationUrl,
192
- {
193
- method: originRequest.method,
194
- headers: {
195
- ...originRequest.headers,
196
- 'X-Forwarded-Host': originRequest.headers.host,
197
- 'X-Forwarded-Proto': 'https',
198
- },
199
- keepAlive: true,
200
- },
201
- true, // lets make this streaming
202
- (proxyRequest) => {
203
- originRequest.on('data', (data) => {
204
- proxyRequest.write(data);
205
- });
206
- originRequest.on('end', () => {
207
- proxyRequest.end();
208
- });
209
- originRequest.on('error', () => {
210
- proxyRequest.end();
211
- });
212
- originRequest.on('close', () => {
213
- proxyRequest.end();
214
- });
215
- originRequest.on('timeout', () => {
216
- proxyRequest.end();
217
- originRequest.destroy();
218
- });
219
- proxyRequest.on('error', () => {
220
- endOriginReqRes();
221
- });
222
- }
223
- );
224
- originResponse.statusCode = proxyResponse.statusCode;
225
- console.log(proxyResponse.statusCode);
226
- for (const defaultHeader of Object.keys(this.defaultHeaders)) {
227
- originResponse.setHeader(defaultHeader, this.defaultHeaders[defaultHeader]);
228
- }
229
- for (const header of Object.keys(proxyResponse.headers)) {
230
- originResponse.setHeader(header, proxyResponse.headers[header]);
231
- }
232
- proxyResponse.on('data', (data) => {
233
- originResponse.write(data);
234
- });
235
- proxyResponse.on('end', () => {
236
- originResponse.end();
237
- });
238
- proxyResponse.on('error', () => {
239
- originResponse.destroy();
240
- });
241
- proxyResponse.on('close', () => {
242
- originResponse.end();
243
- });
244
- proxyResponse.on('timeout', () => {
245
- originResponse.end();
246
- originResponse.destroy();
247
- });
248
- } catch (error) {
249
- console.error('Error while processing request:', error);
250
- endOriginReqRes(502, 'Bad Gateway: Error processing the request');
251
- }
252
- }
253
- );
81
+ });
82
+ }, 60000); // runs every 1 minute
254
83
 
255
- // Enable websockets
256
- const wsServer = new plugins.ws.WebSocketServer({ server: this.httpsServer });
257
84
  wsServer.on(
258
85
  'connection',
259
- async (wsIncoming: plugins.wsDefault, reqArg: plugins.http.IncomingMessage) => {
86
+ (wsIncoming: IWebSocketWithHeartbeat, reqArg: plugins.http.IncomingMessage) => {
260
87
  console.log(
261
- `wss proxy: got connection for wsc for https://${reqArg.headers.host}${reqArg.url}`
88
+ `wss proxy: got connection for wsc for https://${reqArg.headers.host}${reqArg.url}`,
262
89
  );
263
90
 
264
- let wsOutgoing: plugins.wsDefault;
91
+ wsIncoming.lastPong = Date.now();
92
+ wsIncoming.on('pong', () => {
93
+ wsIncoming.lastPong = Date.now();
94
+ });
265
95
 
96
+ let wsOutgoing: plugins.wsDefault;
266
97
  const outGoingDeferred = plugins.smartpromise.defer();
267
98
 
99
+ // --- Improvement 2: Only call routeReq once ---
100
+ const wsDestinationConfig = this.router.routeReq(reqArg);
101
+ if (!wsDestinationConfig) {
102
+ wsIncoming.terminate();
103
+ return;
104
+ }
268
105
  try {
269
106
  wsOutgoing = new plugins.wsDefault(
270
- `ws://${this.router.routeReq(reqArg).destinationIp}:${
271
- this.router.routeReq(reqArg).destinationPort
272
- }${reqArg.url}`
107
+ `ws://${wsDestinationConfig.destinationIp}:${wsDestinationConfig.destinationPort}${reqArg.url}`,
273
108
  );
274
109
  console.log('wss proxy: initiated outgoing proxy');
275
110
  wsOutgoing.on('open', async () => {
276
111
  outGoingDeferred.resolve();
277
112
  });
278
113
  } catch (err) {
279
- console.log(err);
114
+ console.error('Error initiating outgoing WebSocket:', err);
280
115
  wsIncoming.terminate();
281
116
  return;
282
117
  }
@@ -301,23 +136,21 @@ JNj2Dr5H0XoLFFnvuvzcRbhlJ9J67JzR+7g=
301
136
  const terminateWsOutgoing = () => {
302
137
  if (wsOutgoing) {
303
138
  wsOutgoing.terminate();
304
- console.log('terminated outgoing ws.');
139
+ console.log('Terminated outgoing ws.');
305
140
  }
306
141
  };
307
- wsIncoming.on('error', () => terminateWsOutgoing());
308
- wsIncoming.on('close', () => terminateWsOutgoing());
142
+ wsIncoming.on('error', terminateWsOutgoing);
143
+ wsIncoming.on('close', terminateWsOutgoing);
309
144
 
310
145
  const terminateWsIncoming = () => {
311
146
  if (wsIncoming) {
312
147
  wsIncoming.terminate();
313
- console.log('terminated incoming ws.');
148
+ console.log('Terminated incoming ws.');
314
149
  }
315
150
  };
316
- wsOutgoing.on('error', () => terminateWsIncoming());
317
- wsOutgoing.on('close', () => terminateWsIncoming());
318
-
319
-
320
- }
151
+ wsOutgoing.on('error', terminateWsIncoming);
152
+ wsOutgoing.on('close', terminateWsIncoming);
153
+ },
321
154
  );
322
155
 
323
156
  this.httpsServer.keepAliveTimeout = 600 * 1000;
@@ -325,41 +158,174 @@ JNj2Dr5H0XoLFFnvuvzcRbhlJ9J67JzR+7g=
325
158
 
326
159
  this.httpsServer.on('connection', (connection: plugins.net.Socket) => {
327
160
  this.socketMap.add(connection);
328
- console.log(`added connection. now ${this.socketMap.getArray().length} sockets connected.`);
161
+ console.log(`Added connection. Now ${this.socketMap.getArray().length} sockets connected.`);
329
162
  const cleanupConnection = () => {
330
163
  if (this.socketMap.checkForObject(connection)) {
331
164
  this.socketMap.remove(connection);
332
- console.log(`removed connection. ${this.socketMap.getArray().length} sockets remaining.`);
165
+ console.log(`Removed connection. ${this.socketMap.getArray().length} sockets remaining.`);
333
166
  connection.destroy();
334
167
  }
335
168
  };
336
- connection.on('close', () => {
337
- cleanupConnection();
338
- });
339
- connection.on('error', () => {
340
- cleanupConnection();
341
- });
342
- connection.on('end', () => {
343
- cleanupConnection();
344
- });
345
- connection.on('timeout', () => {
346
- cleanupConnection();
347
- });
169
+ connection.on('close', cleanupConnection);
170
+ connection.on('error', cleanupConnection);
171
+ connection.on('end', cleanupConnection);
172
+ connection.on('timeout', cleanupConnection);
348
173
  });
349
174
 
350
175
  this.httpsServer.listen(this.options.port);
351
176
  console.log(
352
- `NetworkProxy -> OK: now listening for new connections on port ${this.options.port}`
177
+ `NetworkProxy -> OK: now listening for new connections on port ${this.options.port}`,
353
178
  );
354
179
  }
355
180
 
356
- public async updateProxyConfigs(proxyConfigsArg: plugins.tsclass.network.IReverseProxyConfig[]) {
181
+ /**
182
+ * Internal async handler for processing HTTP/HTTPS requests.
183
+ */
184
+ private async handleRequest(
185
+ originRequest: plugins.http.IncomingMessage,
186
+ originResponse: plugins.http.ServerResponse,
187
+ ): Promise<void> {
188
+ const endOriginReqRes = (
189
+ statusArg: number = 404,
190
+ messageArg: string = 'This route is not available on this server.',
191
+ headers: plugins.http.OutgoingHttpHeaders = {},
192
+ ) => {
193
+ originResponse.writeHead(statusArg, messageArg);
194
+ originResponse.end(messageArg);
195
+ if (originRequest.socket !== originResponse.socket) {
196
+ console.log('hey, something is strange.');
197
+ }
198
+ originResponse.destroy();
199
+ };
200
+
201
+ console.log(
202
+ `got request: ${originRequest.headers.host}${plugins.url.parse(originRequest.url).path}`,
203
+ );
204
+ const destinationConfig = this.router.routeReq(originRequest);
205
+
206
+ if (!destinationConfig) {
207
+ console.log(
208
+ `${originRequest.headers.host} can't be routed properly. Terminating request.`,
209
+ );
210
+ endOriginReqRes();
211
+ return;
212
+ }
213
+
214
+ // authentication
215
+ if (destinationConfig.authentication) {
216
+ const authInfo = destinationConfig.authentication;
217
+ switch (authInfo.type) {
218
+ case 'Basic': {
219
+ const authHeader = originRequest.headers.authorization;
220
+ if (!authHeader) {
221
+ return endOriginReqRes(401, 'Authentication required', {
222
+ 'WWW-Authenticate': 'Basic realm="Access to the staging site", charset="UTF-8"',
223
+ });
224
+ }
225
+ if (!authHeader.includes('Basic ')) {
226
+ return endOriginReqRes(401, 'Authentication required', {
227
+ 'WWW-Authenticate': 'Basic realm="Access to the staging site", charset="UTF-8"',
228
+ });
229
+ }
230
+ const authStringBase64 = authHeader.replace('Basic ', '');
231
+ const authString: string = plugins.smartstring.base64.decode(authStringBase64);
232
+ const userPassArray = authString.split(':');
233
+ const user = userPassArray[0];
234
+ const pass = userPassArray[1];
235
+ if (user === authInfo.user && pass === authInfo.pass) {
236
+ console.log('Request successfully authenticated');
237
+ } else {
238
+ return endOriginReqRes(403, 'Forbidden: Wrong credentials');
239
+ }
240
+ break;
241
+ }
242
+ default:
243
+ return endOriginReqRes(
244
+ 403,
245
+ 'Forbidden: unsupported authentication method configured. Please report to the admin.',
246
+ );
247
+ }
248
+ }
249
+
250
+ let destinationUrl: string;
251
+ if (destinationConfig) {
252
+ destinationUrl = `http://${destinationConfig.destinationIp}:${destinationConfig.destinationPort}${originRequest.url}`;
253
+ } else {
254
+ return endOriginReqRes();
255
+ }
256
+ console.log(destinationUrl);
257
+ try {
258
+ const proxyResponse = await plugins.smartrequest.request(
259
+ destinationUrl,
260
+ {
261
+ method: originRequest.method,
262
+ headers: {
263
+ ...originRequest.headers,
264
+ 'X-Forwarded-Host': originRequest.headers.host,
265
+ 'X-Forwarded-Proto': 'https',
266
+ },
267
+ keepAlive: true,
268
+ },
269
+ true, // streaming (keepAlive)
270
+ (proxyRequest) => {
271
+ originRequest.on('data', (data) => {
272
+ proxyRequest.write(data);
273
+ });
274
+ originRequest.on('end', () => {
275
+ proxyRequest.end();
276
+ });
277
+ originRequest.on('error', () => {
278
+ proxyRequest.end();
279
+ });
280
+ originRequest.on('close', () => {
281
+ proxyRequest.end();
282
+ });
283
+ originRequest.on('timeout', () => {
284
+ proxyRequest.end();
285
+ originRequest.destroy();
286
+ });
287
+ proxyRequest.on('error', () => {
288
+ endOriginReqRes();
289
+ });
290
+ },
291
+ );
292
+ originResponse.statusCode = proxyResponse.statusCode;
293
+ console.log(proxyResponse.statusCode);
294
+ for (const defaultHeader of Object.keys(this.defaultHeaders)) {
295
+ originResponse.setHeader(defaultHeader, this.defaultHeaders[defaultHeader]);
296
+ }
297
+ for (const header of Object.keys(proxyResponse.headers)) {
298
+ originResponse.setHeader(header, proxyResponse.headers[header]);
299
+ }
300
+ proxyResponse.on('data', (data) => {
301
+ originResponse.write(data);
302
+ });
303
+ proxyResponse.on('end', () => {
304
+ originResponse.end();
305
+ });
306
+ proxyResponse.on('error', () => {
307
+ originResponse.destroy();
308
+ });
309
+ proxyResponse.on('close', () => {
310
+ originResponse.end();
311
+ });
312
+ proxyResponse.on('timeout', () => {
313
+ originResponse.end();
314
+ originResponse.destroy();
315
+ });
316
+ } catch (error) {
317
+ console.error('Error while processing request:', error);
318
+ endOriginReqRes(502, 'Bad Gateway: Error processing the request');
319
+ }
320
+ }
321
+
322
+ public async updateProxyConfigs(
323
+ proxyConfigsArg: plugins.tsclass.network.IReverseProxyConfig[],
324
+ ) {
357
325
  console.log(`got new proxy configs`);
358
326
  this.proxyConfigs = proxyConfigsArg;
359
327
  this.router.setNewProxyConfigs(proxyConfigsArg);
360
328
  for (const hostCandidate of this.proxyConfigs) {
361
- // console.log(hostCandidate);
362
-
363
329
  const existingHostNameConfig = this.alreadyAddedReverseConfigs[hostCandidate.hostName];
364
330
 
365
331
  if (!existingHostNameConfig) {
@@ -393,10 +359,11 @@ JNj2Dr5H0XoLFFnvuvzcRbhlJ9J67JzR+7g=
393
359
  this.httpsServer.close(() => {
394
360
  done.resolve();
395
361
  });
396
- await this.socketMap.forEach(async (socket) => {
362
+ for (const socket of this.socketMap.getArray()) {
397
363
  socket.destroy();
398
- });
364
+ }
399
365
  await done.promise;
366
+ clearInterval(this.heartbeatInterval);
400
367
  console.log('NetworkProxy -> OK: Server has been stopped and all connections closed.');
401
368
  }
402
369
  }
@@ -16,9 +16,18 @@ export class ProxyRouter {
16
16
  */
17
17
  public routeReq(req: plugins.http.IncomingMessage): plugins.tsclass.network.IReverseProxyConfig {
18
18
  const originalHost = req.headers.host;
19
+ if (!originalHost) {
20
+ console.error('No host header found in request');
21
+ return undefined;
22
+ }
23
+ // Strip port from host if present
24
+ const hostWithoutPort = originalHost.split(':')[0];
19
25
  const correspodingReverseProxyConfig = this.reverseProxyConfigs.find((reverseConfig) => {
20
- return reverseConfig.hostName === originalHost;
26
+ return reverseConfig.hostName === hostWithoutPort;
21
27
  });
28
+ if (!correspodingReverseProxyConfig) {
29
+ console.error(`No config found for host: ${hostWithoutPort}`);
30
+ }
22
31
  return correspodingReverseProxyConfig;
23
32
  }
24
33
  }
@@ -0,0 +1,30 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export interface ICertificates {
8
+ privateKey: string;
9
+ publicKey: string;
10
+ }
11
+
12
+ export function loadDefaultCertificates(): ICertificates {
13
+ try {
14
+ const certPath = path.join(__dirname, '..', 'assets', 'certs');
15
+ const privateKey = fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8');
16
+ const publicKey = fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8');
17
+
18
+ if (!privateKey || !publicKey) {
19
+ throw new Error('Failed to load default certificates');
20
+ }
21
+
22
+ return {
23
+ privateKey,
24
+ publicKey
25
+ };
26
+ } catch (error) {
27
+ console.error('Error loading default certificates:', error);
28
+ throw error;
29
+ }
30
+ }
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from './smartproxy.classes.smartproxy';
package/dist/index.js DELETED
@@ -1,7 +0,0 @@
1
- "use strict";
2
- function __export(m) {
3
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4
- }
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- __export(require("./smartproxy.classes.smartproxy"));
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLHFEQUFnRCJ9
@@ -1,23 +0,0 @@
1
- /// <reference types="node" />
2
- import * as plugins from './smartproxy.plugins';
3
- import { SmartproxyRouter } from './smartproxy.classes.router';
4
- export declare class ProxyWorker {
5
- proxyConfigs: plugins.tsclass.network.IReverseProxyConfig[];
6
- httpsServer: plugins.https.Server;
7
- port: number;
8
- router: SmartproxyRouter;
9
- socketMap: plugins.lik.Objectmap<plugins.net.Socket>;
10
- /**
11
- * starts the proxyInstance
12
- */
13
- start(): Promise<void>;
14
- updateProxyConfigs(proxyConfigsArg: plugins.tsclass.network.IReverseProxyConfig[]): Promise<void>;
15
- stop(): Promise<void>;
16
- }
17
- declare const proxyWorkerCalls: {
18
- stop: () => Promise<void>;
19
- start: () => Promise<void>;
20
- updateReverseConfigs: (configArray: plugins.tsclass.network.IReverseProxyConfig[]) => Promise<void>;
21
- };
22
- export declare type TProxyWorkerCalls = typeof proxyWorkerCalls;
23
- export {};