@push.rocks/smartproxy 18.0.2 → 18.2.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/certificate/certificate-manager.d.ts +150 -0
- package/dist_ts/certificate/certificate-manager.js +505 -0
- package/dist_ts/certificate/events/simplified-events.d.ts +56 -0
- package/dist_ts/certificate/events/simplified-events.js +13 -0
- package/dist_ts/certificate/models/certificate-errors.d.ts +69 -0
- package/dist_ts/certificate/models/certificate-errors.js +141 -0
- package/dist_ts/certificate/models/certificate-strategy.d.ts +60 -0
- package/dist_ts/certificate/models/certificate-strategy.js +73 -0
- package/dist_ts/certificate/simplified-certificate-manager.d.ts +150 -0
- package/dist_ts/certificate/simplified-certificate-manager.js +501 -0
- package/dist_ts/http/index.d.ts +1 -9
- package/dist_ts/http/index.js +5 -11
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +4 -2
- package/dist_ts/proxies/network-proxy/network-proxy.js +3 -1
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.d.ts +48 -0
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.js +76 -0
- package/dist_ts/proxies/network-proxy/websocket-handler.js +41 -4
- package/dist_ts/proxies/smart-proxy/cert-store.d.ts +10 -0
- package/dist_ts/proxies/smart-proxy/cert-store.js +70 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +116 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +401 -0
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.d.ts +168 -0
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.js +642 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +26 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.d.ts +65 -0
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.js +31 -0
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.d.ts +102 -0
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.js +73 -0
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +10 -44
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +66 -202
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +62 -2
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.d.ts +41 -0
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.js +132 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +18 -13
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +79 -196
- package/package.json +7 -5
- package/readme.md +224 -10
- package/readme.plan.md +1405 -617
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/http/index.ts +5 -12
- package/ts/plugins.ts +4 -1
- package/ts/proxies/network-proxy/network-proxy.ts +3 -0
- package/ts/proxies/network-proxy/websocket-handler.ts +38 -3
- package/ts/proxies/smart-proxy/cert-store.ts +86 -0
- package/ts/proxies/smart-proxy/certificate-manager.ts +506 -0
- package/ts/proxies/smart-proxy/models/route-types.ts +33 -3
- package/ts/proxies/smart-proxy/network-proxy-bridge.ts +86 -239
- package/ts/proxies/smart-proxy/route-connection-handler.ts +74 -1
- package/ts/proxies/smart-proxy/smart-proxy.ts +105 -222
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '18.0
|
|
6
|
+
version: '18.2.0',
|
|
7
7
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
package/ts/http/index.ts
CHANGED
|
@@ -5,19 +5,12 @@
|
|
|
5
5
|
// Export types and models
|
|
6
6
|
export * from './models/http-types.js';
|
|
7
7
|
|
|
8
|
-
// Export submodules
|
|
9
|
-
export * from './port80/index.js';
|
|
8
|
+
// Export submodules (remove port80 export)
|
|
10
9
|
export * from './router/index.js';
|
|
11
10
|
export * from './redirects/index.js';
|
|
11
|
+
// REMOVED: export * from './port80/index.js';
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
import { Port80Handler } from './port80/port80-handler.js';
|
|
15
|
-
import { ChallengeResponder } from './port80/challenge-responder.js';
|
|
16
|
-
|
|
17
|
-
// Convenience namespace exports
|
|
13
|
+
// Convenience namespace exports (no more Port80)
|
|
18
14
|
export const Http = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ChallengeResponder: ChallengeResponder
|
|
22
|
-
}
|
|
23
|
-
};
|
|
15
|
+
// Only router and redirect functionality remain
|
|
16
|
+
};
|
package/ts/plugins.ts
CHANGED
|
@@ -21,7 +21,8 @@ import * as smartdelay from '@push.rocks/smartdelay';
|
|
|
21
21
|
import * as smartpromise from '@push.rocks/smartpromise';
|
|
22
22
|
import * as smartrequest from '@push.rocks/smartrequest';
|
|
23
23
|
import * as smartstring from '@push.rocks/smartstring';
|
|
24
|
-
|
|
24
|
+
import * as smartfile from '@push.rocks/smartfile';
|
|
25
|
+
import * as smartcrypto from '@push.rocks/smartcrypto';
|
|
25
26
|
import * as smartacme from '@push.rocks/smartacme';
|
|
26
27
|
import * as smartacmePlugins from '@push.rocks/smartacme/dist_ts/smartacme.plugins.js';
|
|
27
28
|
import * as smartacmeHandlers from '@push.rocks/smartacme/dist_ts/handlers/index.js';
|
|
@@ -33,6 +34,8 @@ export {
|
|
|
33
34
|
smartrequest,
|
|
34
35
|
smartpromise,
|
|
35
36
|
smartstring,
|
|
37
|
+
smartfile,
|
|
38
|
+
smartcrypto,
|
|
36
39
|
smartacme,
|
|
37
40
|
smartacmePlugins,
|
|
38
41
|
smartacmeHandlers,
|
|
@@ -500,6 +500,9 @@ export class NetworkProxy implements IMetricsTracker {
|
|
|
500
500
|
this.logger.warn('Router has no recognized configuration method');
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
// Update WebSocket handler with new routes
|
|
504
|
+
this.webSocketHandler.setRoutes(routes);
|
|
505
|
+
|
|
503
506
|
this.logger.info(`Route configuration updated with ${routes.length} routes and ${legacyConfigs.length} proxy configs`);
|
|
504
507
|
}
|
|
505
508
|
|
|
@@ -115,6 +115,8 @@ export class WebSocketHandler {
|
|
|
115
115
|
* Handle a new WebSocket connection
|
|
116
116
|
*/
|
|
117
117
|
private handleWebSocketConnection(wsIncoming: IWebSocketWithHeartbeat, req: plugins.http.IncomingMessage): void {
|
|
118
|
+
this.logger.debug(`WebSocket connection initiated from ${req.headers.host}`);
|
|
119
|
+
|
|
118
120
|
try {
|
|
119
121
|
// Initialize heartbeat tracking
|
|
120
122
|
wsIncoming.isAlive = true;
|
|
@@ -217,6 +219,8 @@ export class WebSocketHandler {
|
|
|
217
219
|
host: selectedHost,
|
|
218
220
|
port: targetPort
|
|
219
221
|
};
|
|
222
|
+
|
|
223
|
+
this.logger.debug(`WebSocket destination resolved: ${selectedHost}:${targetPort}`);
|
|
220
224
|
} catch (err) {
|
|
221
225
|
this.logger.error(`Error evaluating function-based target for WebSocket: ${err}`);
|
|
222
226
|
wsIncoming.close(1011, 'Internal server error');
|
|
@@ -240,7 +244,10 @@ export class WebSocketHandler {
|
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
// Build target URL with potential path rewriting
|
|
243
|
-
|
|
247
|
+
// Determine protocol based on the target's configuration
|
|
248
|
+
// For WebSocket connections, we use ws for HTTP backends and wss for HTTPS backends
|
|
249
|
+
const isTargetSecure = destination.port === 443;
|
|
250
|
+
const protocol = isTargetSecure ? 'wss' : 'ws';
|
|
244
251
|
let targetPath = req.url || '/';
|
|
245
252
|
|
|
246
253
|
// Apply path rewriting if configured
|
|
@@ -319,7 +326,12 @@ export class WebSocketHandler {
|
|
|
319
326
|
}
|
|
320
327
|
|
|
321
328
|
// Create outgoing WebSocket connection
|
|
329
|
+
this.logger.debug(`Creating WebSocket connection to ${targetUrl} with options:`, {
|
|
330
|
+
headers: wsOptions.headers,
|
|
331
|
+
protocols: wsOptions.protocols
|
|
332
|
+
});
|
|
322
333
|
const wsOutgoing = new plugins.wsDefault(targetUrl, wsOptions);
|
|
334
|
+
this.logger.debug(`WebSocket instance created, waiting for connection...`);
|
|
323
335
|
|
|
324
336
|
// Handle connection errors
|
|
325
337
|
wsOutgoing.on('error', (err) => {
|
|
@@ -331,6 +343,7 @@ export class WebSocketHandler {
|
|
|
331
343
|
|
|
332
344
|
// Handle outgoing connection open
|
|
333
345
|
wsOutgoing.on('open', () => {
|
|
346
|
+
this.logger.debug(`WebSocket target connection opened to ${targetUrl}`);
|
|
334
347
|
// Set up custom ping interval if configured
|
|
335
348
|
let pingInterval: NodeJS.Timeout | null = null;
|
|
336
349
|
if (route?.action.websocket?.pingInterval && route.action.websocket.pingInterval > 0) {
|
|
@@ -376,6 +389,7 @@ export class WebSocketHandler {
|
|
|
376
389
|
|
|
377
390
|
// Forward incoming messages to outgoing connection
|
|
378
391
|
wsIncoming.on('message', (data, isBinary) => {
|
|
392
|
+
this.logger.debug(`WebSocket forwarding message from client to target: ${data.toString()}`);
|
|
379
393
|
if (wsOutgoing.readyState === wsOutgoing.OPEN) {
|
|
380
394
|
// Check message size if limit is set
|
|
381
395
|
const messageSize = getMessageSize(data);
|
|
@@ -386,13 +400,18 @@ export class WebSocketHandler {
|
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
wsOutgoing.send(data, { binary: isBinary });
|
|
403
|
+
} else {
|
|
404
|
+
this.logger.warn(`WebSocket target connection not open (state: ${wsOutgoing.readyState})`);
|
|
389
405
|
}
|
|
390
406
|
});
|
|
391
407
|
|
|
392
408
|
// Forward outgoing messages to incoming connection
|
|
393
409
|
wsOutgoing.on('message', (data, isBinary) => {
|
|
410
|
+
this.logger.debug(`WebSocket forwarding message from target to client: ${data.toString()}`);
|
|
394
411
|
if (wsIncoming.readyState === wsIncoming.OPEN) {
|
|
395
412
|
wsIncoming.send(data, { binary: isBinary });
|
|
413
|
+
} else {
|
|
414
|
+
this.logger.warn(`WebSocket client connection not open (state: ${wsIncoming.readyState})`);
|
|
396
415
|
}
|
|
397
416
|
});
|
|
398
417
|
|
|
@@ -400,7 +419,15 @@ export class WebSocketHandler {
|
|
|
400
419
|
wsIncoming.on('close', (code, reason) => {
|
|
401
420
|
this.logger.debug(`WebSocket client connection closed: ${code} ${reason}`);
|
|
402
421
|
if (wsOutgoing.readyState === wsOutgoing.OPEN) {
|
|
403
|
-
|
|
422
|
+
// Ensure code is a valid WebSocket close code number
|
|
423
|
+
const validCode = typeof code === 'number' && code >= 1000 && code <= 4999 ? code : 1000;
|
|
424
|
+
try {
|
|
425
|
+
const reasonString = reason ? toBuffer(reason).toString() : '';
|
|
426
|
+
wsOutgoing.close(validCode, reasonString);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
this.logger.error('Error closing wsOutgoing:', err);
|
|
429
|
+
wsOutgoing.close(validCode);
|
|
430
|
+
}
|
|
404
431
|
}
|
|
405
432
|
|
|
406
433
|
// Clean up timers
|
|
@@ -411,7 +438,15 @@ export class WebSocketHandler {
|
|
|
411
438
|
wsOutgoing.on('close', (code, reason) => {
|
|
412
439
|
this.logger.debug(`WebSocket target connection closed: ${code} ${reason}`);
|
|
413
440
|
if (wsIncoming.readyState === wsIncoming.OPEN) {
|
|
414
|
-
|
|
441
|
+
// Ensure code is a valid WebSocket close code number
|
|
442
|
+
const validCode = typeof code === 'number' && code >= 1000 && code <= 4999 ? code : 1000;
|
|
443
|
+
try {
|
|
444
|
+
const reasonString = reason ? toBuffer(reason).toString() : '';
|
|
445
|
+
wsIncoming.close(validCode, reasonString);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
this.logger.error('Error closing wsIncoming:', err);
|
|
448
|
+
wsIncoming.close(validCode);
|
|
449
|
+
}
|
|
415
450
|
}
|
|
416
451
|
|
|
417
452
|
// Clean up timers
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { ICertificateData } from './certificate-manager.js';
|
|
3
|
+
|
|
4
|
+
export class CertStore {
|
|
5
|
+
constructor(private certDir: string) {}
|
|
6
|
+
|
|
7
|
+
public async initialize(): Promise<void> {
|
|
8
|
+
await plugins.smartfile.fs.ensureDirSync(this.certDir);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public async getCertificate(routeName: string): Promise<ICertificateData | null> {
|
|
12
|
+
const certPath = this.getCertPath(routeName);
|
|
13
|
+
const metaPath = `${certPath}/meta.json`;
|
|
14
|
+
|
|
15
|
+
if (!await plugins.smartfile.fs.fileExistsSync(metaPath)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const metaFile = await plugins.smartfile.SmartFile.fromFilePath(metaPath);
|
|
21
|
+
const meta = JSON.parse(metaFile.contents.toString());
|
|
22
|
+
|
|
23
|
+
const certFile = await plugins.smartfile.SmartFile.fromFilePath(`${certPath}/cert.pem`);
|
|
24
|
+
const cert = certFile.contents.toString();
|
|
25
|
+
|
|
26
|
+
const keyFile = await plugins.smartfile.SmartFile.fromFilePath(`${certPath}/key.pem`);
|
|
27
|
+
const key = keyFile.contents.toString();
|
|
28
|
+
|
|
29
|
+
let ca: string | undefined;
|
|
30
|
+
const caPath = `${certPath}/ca.pem`;
|
|
31
|
+
if (await plugins.smartfile.fs.fileExistsSync(caPath)) {
|
|
32
|
+
const caFile = await plugins.smartfile.SmartFile.fromFilePath(caPath);
|
|
33
|
+
ca = caFile.contents.toString();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
cert,
|
|
38
|
+
key,
|
|
39
|
+
ca,
|
|
40
|
+
expiryDate: new Date(meta.expiryDate),
|
|
41
|
+
issueDate: new Date(meta.issueDate)
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`Failed to load certificate for ${routeName}: ${error}`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async saveCertificate(
|
|
50
|
+
routeName: string,
|
|
51
|
+
certData: ICertificateData
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const certPath = this.getCertPath(routeName);
|
|
54
|
+
await plugins.smartfile.fs.ensureDirSync(certPath);
|
|
55
|
+
|
|
56
|
+
// Save certificate files
|
|
57
|
+
await plugins.smartfile.memory.toFs(certData.cert, `${certPath}/cert.pem`);
|
|
58
|
+
await plugins.smartfile.memory.toFs(certData.key, `${certPath}/key.pem`);
|
|
59
|
+
|
|
60
|
+
if (certData.ca) {
|
|
61
|
+
await plugins.smartfile.memory.toFs(certData.ca, `${certPath}/ca.pem`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Save metadata
|
|
65
|
+
const meta = {
|
|
66
|
+
expiryDate: certData.expiryDate.toISOString(),
|
|
67
|
+
issueDate: certData.issueDate.toISOString(),
|
|
68
|
+
savedAt: new Date().toISOString()
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await plugins.smartfile.memory.toFs(JSON.stringify(meta, null, 2), `${certPath}/meta.json`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async deleteCertificate(routeName: string): Promise<void> {
|
|
75
|
+
const certPath = this.getCertPath(routeName);
|
|
76
|
+
if (await plugins.smartfile.fs.fileExistsSync(certPath)) {
|
|
77
|
+
await plugins.smartfile.fs.removeManySync([certPath]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private getCertPath(routeName: string): string {
|
|
82
|
+
// Sanitize route name for filesystem
|
|
83
|
+
const safeName = routeName.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
84
|
+
return `${this.certDir}/${safeName}`;
|
|
85
|
+
}
|
|
86
|
+
}
|