@push.rocks/smartproxy 5.1.0 → 6.0.1

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 +64 -68
  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,354 @@
1
+ import * as plugins from '../plugins.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { createLogger } from './classes.np.types.js';
6
+ import { Port80Handler, Port80HandlerEvents } from '../port80handler/classes.port80handler.js';
7
+ /**
8
+ * Manages SSL certificates for NetworkProxy including ACME integration
9
+ */
10
+ export class CertificateManager {
11
+ constructor(options) {
12
+ this.options = options;
13
+ this.certificateCache = new Map();
14
+ this.port80Handler = null;
15
+ this.externalPort80Handler = false;
16
+ this.httpsServer = null;
17
+ this.certificateStoreDir = path.resolve(options.acme?.certificateStore || './certs');
18
+ this.logger = createLogger(options.logLevel || 'info');
19
+ // Ensure certificate store directory exists
20
+ try {
21
+ if (!fs.existsSync(this.certificateStoreDir)) {
22
+ fs.mkdirSync(this.certificateStoreDir, { recursive: true });
23
+ this.logger.info(`Created certificate store directory: ${this.certificateStoreDir}`);
24
+ }
25
+ }
26
+ catch (error) {
27
+ this.logger.warn(`Failed to create certificate store directory: ${error}`);
28
+ }
29
+ this.loadDefaultCertificates();
30
+ }
31
+ /**
32
+ * Loads default certificates from the filesystem
33
+ */
34
+ loadDefaultCertificates() {
35
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
+ const certPath = path.join(__dirname, '..', '..', 'assets', 'certs');
37
+ try {
38
+ this.defaultCertificates = {
39
+ key: fs.readFileSync(path.join(certPath, 'key.pem'), 'utf8'),
40
+ cert: fs.readFileSync(path.join(certPath, 'cert.pem'), 'utf8')
41
+ };
42
+ this.logger.info('Default certificates loaded successfully');
43
+ }
44
+ catch (error) {
45
+ this.logger.error('Error loading default certificates', error);
46
+ // Generate self-signed fallback certificates
47
+ try {
48
+ // This is a placeholder for actual certificate generation code
49
+ // In a real implementation, you would use a library like selfsigned to generate certs
50
+ this.defaultCertificates = {
51
+ key: "FALLBACK_KEY_CONTENT",
52
+ cert: "FALLBACK_CERT_CONTENT"
53
+ };
54
+ this.logger.warn('Using fallback self-signed certificates');
55
+ }
56
+ catch (fallbackError) {
57
+ this.logger.error('Failed to generate fallback certificates', fallbackError);
58
+ throw new Error('Could not load or generate SSL certificates');
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Set the HTTPS server reference for context updates
64
+ */
65
+ setHttpsServer(server) {
66
+ this.httpsServer = server;
67
+ }
68
+ /**
69
+ * Get default certificates
70
+ */
71
+ getDefaultCertificates() {
72
+ return { ...this.defaultCertificates };
73
+ }
74
+ /**
75
+ * Sets an external Port80Handler for certificate management
76
+ */
77
+ setExternalPort80Handler(handler) {
78
+ if (this.port80Handler && !this.externalPort80Handler) {
79
+ this.logger.warn('Replacing existing internal Port80Handler with external handler');
80
+ // Clean up existing handler if needed
81
+ if (this.port80Handler !== handler) {
82
+ // Unregister event handlers to avoid memory leaks
83
+ this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_ISSUED);
84
+ this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_RENEWED);
85
+ this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_FAILED);
86
+ this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_EXPIRING);
87
+ }
88
+ }
89
+ // Set the external handler
90
+ this.port80Handler = handler;
91
+ this.externalPort80Handler = true;
92
+ // Register event handlers
93
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateIssued.bind(this));
94
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateIssued.bind(this));
95
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, this.handleCertificateFailed.bind(this));
96
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
97
+ this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
98
+ });
99
+ this.logger.info('External Port80Handler connected to CertificateManager');
100
+ // Register domains with Port80Handler if we have any certificates cached
101
+ if (this.certificateCache.size > 0) {
102
+ const domains = Array.from(this.certificateCache.keys())
103
+ .filter(domain => !domain.includes('*')); // Skip wildcard domains
104
+ this.registerDomainsWithPort80Handler(domains);
105
+ }
106
+ }
107
+ /**
108
+ * Handle newly issued or renewed certificates from Port80Handler
109
+ */
110
+ handleCertificateIssued(data) {
111
+ const { domain, certificate, privateKey, expiryDate } = data;
112
+ this.logger.info(`Certificate ${this.certificateCache.has(domain) ? 'renewed' : 'issued'} for ${domain}, valid until ${expiryDate.toISOString()}`);
113
+ // Update certificate in HTTPS server
114
+ this.updateCertificateCache(domain, certificate, privateKey, expiryDate);
115
+ // Save the certificate to the filesystem if not using external handler
116
+ if (!this.externalPort80Handler && this.options.acme?.certificateStore) {
117
+ this.saveCertificateToStore(domain, certificate, privateKey);
118
+ }
119
+ }
120
+ /**
121
+ * Handle certificate issuance failures
122
+ */
123
+ handleCertificateFailed(data) {
124
+ this.logger.error(`Certificate issuance failed for ${data.domain}: ${data.error}`);
125
+ }
126
+ /**
127
+ * Saves certificate and private key to the filesystem
128
+ */
129
+ saveCertificateToStore(domain, certificate, privateKey) {
130
+ try {
131
+ const certPath = path.join(this.certificateStoreDir, `${domain}.cert.pem`);
132
+ const keyPath = path.join(this.certificateStoreDir, `${domain}.key.pem`);
133
+ fs.writeFileSync(certPath, certificate);
134
+ fs.writeFileSync(keyPath, privateKey);
135
+ // Ensure private key has restricted permissions
136
+ try {
137
+ fs.chmodSync(keyPath, 0o600);
138
+ }
139
+ catch (error) {
140
+ this.logger.warn(`Failed to set permissions on private key for ${domain}: ${error}`);
141
+ }
142
+ this.logger.info(`Saved certificate for ${domain} to ${certPath}`);
143
+ }
144
+ catch (error) {
145
+ this.logger.error(`Failed to save certificate for ${domain}: ${error}`);
146
+ }
147
+ }
148
+ /**
149
+ * Handles SNI (Server Name Indication) for TLS connections
150
+ * Used by the HTTPS server to select the correct certificate for each domain
151
+ */
152
+ handleSNI(domain, cb) {
153
+ this.logger.debug(`SNI request for domain: ${domain}`);
154
+ // Check if we have a certificate for this domain
155
+ const certs = this.certificateCache.get(domain);
156
+ if (certs) {
157
+ try {
158
+ // Create TLS context with the cached certificate
159
+ const context = plugins.tls.createSecureContext({
160
+ key: certs.key,
161
+ cert: certs.cert
162
+ });
163
+ this.logger.debug(`Using cached certificate for ${domain}`);
164
+ cb(null, context);
165
+ return;
166
+ }
167
+ catch (err) {
168
+ this.logger.error(`Error creating secure context for ${domain}:`, err);
169
+ }
170
+ }
171
+ // Check if we should trigger certificate issuance
172
+ if (this.options.acme?.enabled && this.port80Handler && !domain.includes('*')) {
173
+ // Check if this domain is already registered
174
+ const certData = this.port80Handler.getCertificate(domain);
175
+ if (!certData) {
176
+ this.logger.info(`No certificate found for ${domain}, registering for issuance`);
177
+ // Register with new domain options format
178
+ const domainOptions = {
179
+ domainName: domain,
180
+ sslRedirect: true,
181
+ acmeMaintenance: true
182
+ };
183
+ this.port80Handler.addDomain(domainOptions);
184
+ }
185
+ }
186
+ // Fall back to default certificate
187
+ try {
188
+ const context = plugins.tls.createSecureContext({
189
+ key: this.defaultCertificates.key,
190
+ cert: this.defaultCertificates.cert
191
+ });
192
+ this.logger.debug(`Using default certificate for ${domain}`);
193
+ cb(null, context);
194
+ }
195
+ catch (err) {
196
+ this.logger.error(`Error creating default secure context:`, err);
197
+ cb(new Error('Cannot create secure context'), null);
198
+ }
199
+ }
200
+ /**
201
+ * Updates certificate in cache
202
+ */
203
+ updateCertificateCache(domain, certificate, privateKey, expiryDate) {
204
+ // Update certificate context in HTTPS server if it's running
205
+ if (this.httpsServer) {
206
+ try {
207
+ this.httpsServer.addContext(domain, {
208
+ key: privateKey,
209
+ cert: certificate
210
+ });
211
+ this.logger.debug(`Updated SSL context for domain: ${domain}`);
212
+ }
213
+ catch (error) {
214
+ this.logger.error(`Error updating SSL context for domain ${domain}:`, error);
215
+ }
216
+ }
217
+ // Update certificate in cache
218
+ this.certificateCache.set(domain, {
219
+ key: privateKey,
220
+ cert: certificate,
221
+ expires: expiryDate
222
+ });
223
+ }
224
+ /**
225
+ * Gets a certificate for a domain
226
+ */
227
+ getCertificate(domain) {
228
+ return this.certificateCache.get(domain);
229
+ }
230
+ /**
231
+ * Requests a new certificate for a domain
232
+ */
233
+ async requestCertificate(domain) {
234
+ if (!this.options.acme?.enabled && !this.externalPort80Handler) {
235
+ this.logger.warn('ACME certificate management is not enabled');
236
+ return false;
237
+ }
238
+ if (!this.port80Handler) {
239
+ this.logger.error('Port80Handler is not initialized');
240
+ return false;
241
+ }
242
+ // Skip wildcard domains - can't get certs for these with HTTP-01 validation
243
+ if (domain.includes('*')) {
244
+ this.logger.error(`Cannot request certificate for wildcard domain: ${domain}`);
245
+ return false;
246
+ }
247
+ try {
248
+ // Use the new domain options format
249
+ const domainOptions = {
250
+ domainName: domain,
251
+ sslRedirect: true,
252
+ acmeMaintenance: true
253
+ };
254
+ this.port80Handler.addDomain(domainOptions);
255
+ this.logger.info(`Certificate request submitted for domain: ${domain}`);
256
+ return true;
257
+ }
258
+ catch (error) {
259
+ this.logger.error(`Error requesting certificate for domain ${domain}:`, error);
260
+ return false;
261
+ }
262
+ }
263
+ /**
264
+ * Registers domains with Port80Handler for ACME certificate management
265
+ */
266
+ registerDomainsWithPort80Handler(domains) {
267
+ if (!this.port80Handler) {
268
+ this.logger.warn('Port80Handler is not initialized');
269
+ return;
270
+ }
271
+ for (const domain of domains) {
272
+ // Skip wildcard domains - can't get certs for these with HTTP-01 validation
273
+ if (domain.includes('*')) {
274
+ this.logger.info(`Skipping wildcard domain for ACME: ${domain}`);
275
+ continue;
276
+ }
277
+ // Skip domains already with certificates if configured to do so
278
+ if (this.options.acme?.skipConfiguredCerts) {
279
+ const cachedCert = this.certificateCache.get(domain);
280
+ if (cachedCert) {
281
+ this.logger.info(`Skipping domain with existing certificate: ${domain}`);
282
+ continue;
283
+ }
284
+ }
285
+ // Register the domain for certificate issuance with new domain options format
286
+ const domainOptions = {
287
+ domainName: domain,
288
+ sslRedirect: true,
289
+ acmeMaintenance: true
290
+ };
291
+ this.port80Handler.addDomain(domainOptions);
292
+ this.logger.info(`Registered domain for ACME certificate issuance: ${domain}`);
293
+ }
294
+ }
295
+ /**
296
+ * Initialize internal Port80Handler
297
+ */
298
+ async initializePort80Handler() {
299
+ // Skip if using external handler
300
+ if (this.externalPort80Handler) {
301
+ this.logger.info('Using external Port80Handler, skipping initialization');
302
+ return this.port80Handler;
303
+ }
304
+ if (!this.options.acme?.enabled) {
305
+ return null;
306
+ }
307
+ // Create certificate manager
308
+ this.port80Handler = new Port80Handler({
309
+ port: this.options.acme.port,
310
+ contactEmail: this.options.acme.contactEmail,
311
+ useProduction: this.options.acme.useProduction,
312
+ renewThresholdDays: this.options.acme.renewThresholdDays,
313
+ httpsRedirectPort: this.options.port, // Redirect to our HTTPS port
314
+ renewCheckIntervalHours: 24, // Check daily for renewals
315
+ enabled: this.options.acme.enabled,
316
+ autoRenew: this.options.acme.autoRenew,
317
+ certificateStore: this.options.acme.certificateStore,
318
+ skipConfiguredCerts: this.options.acme.skipConfiguredCerts
319
+ });
320
+ // Register event handlers
321
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateIssued.bind(this));
322
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateIssued.bind(this));
323
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, this.handleCertificateFailed.bind(this));
324
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
325
+ this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
326
+ });
327
+ // Start the handler
328
+ try {
329
+ await this.port80Handler.start();
330
+ this.logger.info(`Port80Handler started on port ${this.options.acme.port}`);
331
+ return this.port80Handler;
332
+ }
333
+ catch (error) {
334
+ this.logger.error(`Failed to start Port80Handler: ${error}`);
335
+ this.port80Handler = null;
336
+ return null;
337
+ }
338
+ }
339
+ /**
340
+ * Stop the Port80Handler if it was internally created
341
+ */
342
+ async stopPort80Handler() {
343
+ if (this.port80Handler && !this.externalPort80Handler) {
344
+ try {
345
+ await this.port80Handler.stop();
346
+ this.logger.info('Port80Handler stopped');
347
+ }
348
+ catch (error) {
349
+ this.logger.error('Error stopping Port80Handler', error);
350
+ }
351
+ }
352
+ }
353
+ }
354
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC5jZXJ0aWZpY2F0ZW1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9uZXR3b3JrcHJveHkvY2xhc3Nlcy5ucC5jZXJ0aWZpY2F0ZW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUNwQyxPQUFPLEVBQW1FLFlBQVksRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3RILE9BQU8sRUFBRSxhQUFhLEVBQUUsbUJBQW1CLEVBQXVCLE1BQU0sMkNBQTJDLENBQUM7QUFFcEg7O0dBRUc7QUFDSCxNQUFNLE9BQU8sa0JBQWtCO0lBUzdCLFlBQW9CLE9BQTZCO1FBQTdCLFlBQU8sR0FBUCxPQUFPLENBQXNCO1FBUHpDLHFCQUFnQixHQUFtQyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQzdELGtCQUFhLEdBQXlCLElBQUksQ0FBQztRQUMzQywwQkFBcUIsR0FBWSxLQUFLLENBQUM7UUFHdkMsZ0JBQVcsR0FBZ0MsSUFBSSxDQUFDO1FBR3RELElBQUksQ0FBQyxtQkFBbUIsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsZ0JBQWdCLElBQUksU0FBUyxDQUFDLENBQUM7UUFDckYsSUFBSSxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxNQUFNLENBQUMsQ0FBQztRQUV2RCw0Q0FBNEM7UUFDNUMsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLG1CQUFtQixDQUFDLEVBQUUsQ0FBQztnQkFDN0MsRUFBRSxDQUFDLFNBQVMsQ0FB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
@@ -0,0 +1,47 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { type INetworkProxyOptions } from './classes.np.types.js';
3
+ /**
4
+ * Manages a pool of backend connections for efficient reuse
5
+ */
6
+ export declare class ConnectionPool {
7
+ private options;
8
+ private connectionPool;
9
+ private roundRobinPositions;
10
+ private logger;
11
+ constructor(options: INetworkProxyOptions);
12
+ /**
13
+ * Get a connection from the pool or create a new one
14
+ */
15
+ getConnection(host: string, port: number): Promise<plugins.net.Socket>;
16
+ /**
17
+ * Return a connection to the pool for reuse
18
+ */
19
+ returnConnection(socket: plugins.net.Socket, host: string, port: number): void;
20
+ /**
21
+ * Cleanup the connection pool by removing idle connections
22
+ * or reducing pool size if it exceeds the configured maximum
23
+ */
24
+ cleanupConnectionPool(): void;
25
+ /**
26
+ * Close all connections in the pool
27
+ */
28
+ closeAllConnections(): void;
29
+ /**
30
+ * Get load balancing target using round-robin
31
+ */
32
+ getNextTarget(targets: string[], port: number): {
33
+ host: string;
34
+ port: number;
35
+ };
36
+ /**
37
+ * Gets the connection pool status
38
+ */
39
+ getPoolStatus(): Record<string, {
40
+ total: number;
41
+ idle: number;
42
+ }>;
43
+ /**
44
+ * Setup a periodic cleanup task
45
+ */
46
+ setupPeriodicCleanup(interval?: number): NodeJS.Timeout;
47
+ }
@@ -0,0 +1,210 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { createLogger } from './classes.np.types.js';
3
+ /**
4
+ * Manages a pool of backend connections for efficient reuse
5
+ */
6
+ export class ConnectionPool {
7
+ constructor(options) {
8
+ this.options = options;
9
+ this.connectionPool = new Map();
10
+ this.roundRobinPositions = new Map();
11
+ this.logger = createLogger(options.logLevel || 'info');
12
+ }
13
+ /**
14
+ * Get a connection from the pool or create a new one
15
+ */
16
+ getConnection(host, port) {
17
+ return new Promise((resolve, reject) => {
18
+ const poolKey = `${host}:${port}`;
19
+ const connectionList = this.connectionPool.get(poolKey) || [];
20
+ // Look for an idle connection
21
+ const idleConnectionIndex = connectionList.findIndex(c => c.isIdle);
22
+ if (idleConnectionIndex >= 0) {
23
+ // Get existing connection from pool
24
+ const connection = connectionList[idleConnectionIndex];
25
+ connection.isIdle = false;
26
+ connection.lastUsed = Date.now();
27
+ this.logger.debug(`Reusing connection from pool for ${poolKey}`);
28
+ // Update the pool
29
+ this.connectionPool.set(poolKey, connectionList);
30
+ resolve(connection.socket);
31
+ return;
32
+ }
33
+ // No idle connection available, create a new one if pool isn't full
34
+ const poolSize = this.options.connectionPoolSize || 50;
35
+ if (connectionList.length < poolSize) {
36
+ this.logger.debug(`Creating new connection to ${host}:${port}`);
37
+ try {
38
+ const socket = plugins.net.connect({
39
+ host,
40
+ port,
41
+ keepAlive: true,
42
+ keepAliveInitialDelay: 30000 // 30 seconds
43
+ });
44
+ socket.once('connect', () => {
45
+ // Add to connection pool
46
+ const connection = {
47
+ socket,
48
+ lastUsed: Date.now(),
49
+ isIdle: false
50
+ };
51
+ connectionList.push(connection);
52
+ this.connectionPool.set(poolKey, connectionList);
53
+ // Setup cleanup when the connection is closed
54
+ socket.once('close', () => {
55
+ const idx = connectionList.findIndex(c => c.socket === socket);
56
+ if (idx >= 0) {
57
+ connectionList.splice(idx, 1);
58
+ this.connectionPool.set(poolKey, connectionList);
59
+ this.logger.debug(`Removed closed connection from pool for ${poolKey}`);
60
+ }
61
+ });
62
+ resolve(socket);
63
+ });
64
+ socket.once('error', (err) => {
65
+ this.logger.error(`Error creating connection to ${host}:${port}`, err);
66
+ reject(err);
67
+ });
68
+ }
69
+ catch (err) {
70
+ this.logger.error(`Failed to create connection to ${host}:${port}`, err);
71
+ reject(err);
72
+ }
73
+ }
74
+ else {
75
+ // Pool is full, wait for an idle connection or reject
76
+ this.logger.warn(`Connection pool for ${poolKey} is full (${connectionList.length})`);
77
+ reject(new Error(`Connection pool for ${poolKey} is full`));
78
+ }
79
+ });
80
+ }
81
+ /**
82
+ * Return a connection to the pool for reuse
83
+ */
84
+ returnConnection(socket, host, port) {
85
+ const poolKey = `${host}:${port}`;
86
+ const connectionList = this.connectionPool.get(poolKey) || [];
87
+ // Find this connection in the pool
88
+ const connectionIndex = connectionList.findIndex(c => c.socket === socket);
89
+ if (connectionIndex >= 0) {
90
+ // Mark as idle and update last used time
91
+ connectionList[connectionIndex].isIdle = true;
92
+ connectionList[connectionIndex].lastUsed = Date.now();
93
+ this.logger.debug(`Returned connection to pool for ${poolKey}`);
94
+ }
95
+ else {
96
+ this.logger.warn(`Attempted to return unknown connection to pool for ${poolKey}`);
97
+ }
98
+ }
99
+ /**
100
+ * Cleanup the connection pool by removing idle connections
101
+ * or reducing pool size if it exceeds the configured maximum
102
+ */
103
+ cleanupConnectionPool() {
104
+ const now = Date.now();
105
+ const idleTimeout = this.options.keepAliveTimeout || 120000; // 2 minutes default
106
+ for (const [host, connections] of this.connectionPool.entries()) {
107
+ // Sort by last used time (oldest first)
108
+ connections.sort((a, b) => a.lastUsed - b.lastUsed);
109
+ // Remove idle connections older than the idle timeout
110
+ let removed = 0;
111
+ while (connections.length > 0) {
112
+ const connection = connections[0];
113
+ // Remove if idle and exceeds timeout, or if pool is too large
114
+ if ((connection.isIdle && now - connection.lastUsed > idleTimeout) ||
115
+ connections.length > (this.options.connectionPoolSize || 50)) {
116
+ try {
117
+ if (!connection.socket.destroyed) {
118
+ connection.socket.end();
119
+ connection.socket.destroy();
120
+ }
121
+ }
122
+ catch (err) {
123
+ this.logger.error(`Error destroying pooled connection to ${host}`, err);
124
+ }
125
+ connections.shift(); // Remove from pool
126
+ removed++;
127
+ }
128
+ else {
129
+ break; // Stop removing if we've reached active or recent connections
130
+ }
131
+ }
132
+ if (removed > 0) {
133
+ this.logger.debug(`Removed ${removed} idle connections from pool for ${host}, ${connections.length} remaining`);
134
+ }
135
+ // Update the pool with the remaining connections
136
+ if (connections.length === 0) {
137
+ this.connectionPool.delete(host);
138
+ }
139
+ else {
140
+ this.connectionPool.set(host, connections);
141
+ }
142
+ }
143
+ }
144
+ /**
145
+ * Close all connections in the pool
146
+ */
147
+ closeAllConnections() {
148
+ for (const [host, connections] of this.connectionPool.entries()) {
149
+ this.logger.debug(`Closing ${connections.length} connections to ${host}`);
150
+ for (const connection of connections) {
151
+ try {
152
+ if (!connection.socket.destroyed) {
153
+ connection.socket.end();
154
+ connection.socket.destroy();
155
+ }
156
+ }
157
+ catch (error) {
158
+ this.logger.error(`Error closing connection to ${host}:`, error);
159
+ }
160
+ }
161
+ }
162
+ this.connectionPool.clear();
163
+ this.roundRobinPositions.clear();
164
+ }
165
+ /**
166
+ * Get load balancing target using round-robin
167
+ */
168
+ getNextTarget(targets, port) {
169
+ const targetKey = targets.join(',');
170
+ // Initialize position if not exists
171
+ if (!this.roundRobinPositions.has(targetKey)) {
172
+ this.roundRobinPositions.set(targetKey, 0);
173
+ }
174
+ // Get current position and increment for next time
175
+ const currentPosition = this.roundRobinPositions.get(targetKey);
176
+ const nextPosition = (currentPosition + 1) % targets.length;
177
+ this.roundRobinPositions.set(targetKey, nextPosition);
178
+ // Return the selected target
179
+ return {
180
+ host: targets[currentPosition],
181
+ port
182
+ };
183
+ }
184
+ /**
185
+ * Gets the connection pool status
186
+ */
187
+ getPoolStatus() {
188
+ return Object.fromEntries(Array.from(this.connectionPool.entries()).map(([host, connections]) => [
189
+ host,
190
+ {
191
+ total: connections.length,
192
+ idle: connections.filter(c => c.isIdle).length
193
+ }
194
+ ]));
195
+ }
196
+ /**
197
+ * Setup a periodic cleanup task
198
+ */
199
+ setupPeriodicCleanup(interval = 60000) {
200
+ const timer = setInterval(() => {
201
+ this.cleanupConnectionPool();
202
+ }, interval);
203
+ // Don't prevent process exit
204
+ if (timer.unref) {
205
+ timer.unref();
206
+ }
207
+ return timer;
208
+ }
209
+ }
210
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC5jb25uZWN0aW9ucG9vbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL25ldHdvcmtwcm94eS9jbGFzc2VzLm5wLmNvbm5lY3Rpb25wb29sLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBa0UsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFckg7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUt6QixZQUFvQixPQUE2QjtRQUE3QixZQUFPLEdBQVAsT0FBTyxDQUFzQjtRQUp6QyxtQkFBYyxHQUF5QyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ2pFLHdCQUFtQixHQUF3QixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBSTNELElBQUksQ0FBQyxNQUFNLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxRQUFRLElBQUksTUFBTSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksYUFBYSxDQUFDLElBQVksRUFBRSxJQUFZO1FBQzdDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDckMsTUFBTSxPQUFPLEdBQUcsR0FBRyxJQUFJLElBQUksSUFBSSxFQUFFLENBQUM7WUFDbEMsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBRTlELDhCQUE4QjtZQUM5QixNQUFNLG1CQUFtQixHQUFHLGNBQWMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7WUFFcEUsSUFBSSxtQkFBbUIsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDN0Isb0NBQW9DO2dCQUNwQyxNQUFNLFVBQVUsR0FBRyxjQUFjLENBQUMsbUJBQW1CLENBQUMsQ0FBQztnQkFD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