@push.rocks/smartproxy 19.5.3 → 19.5.5

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 (42) hide show
  1. package/dist_ts/core/utils/async-utils.d.ts +81 -0
  2. package/dist_ts/core/utils/async-utils.js +216 -0
  3. package/dist_ts/core/utils/binary-heap.d.ts +73 -0
  4. package/dist_ts/core/utils/binary-heap.js +193 -0
  5. package/dist_ts/core/utils/enhanced-connection-pool.d.ts +110 -0
  6. package/dist_ts/core/utils/enhanced-connection-pool.js +320 -0
  7. package/dist_ts/core/utils/fs-utils.d.ts +144 -0
  8. package/dist_ts/core/utils/fs-utils.js +252 -0
  9. package/dist_ts/core/utils/index.d.ts +5 -2
  10. package/dist_ts/core/utils/index.js +6 -3
  11. package/dist_ts/core/utils/lifecycle-component.d.ts +59 -0
  12. package/dist_ts/core/utils/lifecycle-component.js +195 -0
  13. package/dist_ts/plugins.d.ts +2 -1
  14. package/dist_ts/plugins.js +3 -2
  15. package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +15 -0
  16. package/dist_ts/proxies/http-proxy/certificate-manager.js +49 -2
  17. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +10 -0
  18. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +53 -43
  19. package/dist_ts/proxies/smart-proxy/cert-store.js +22 -20
  20. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +37 -7
  21. package/dist_ts/proxies/smart-proxy/connection-manager.js +257 -180
  22. package/package.json +2 -2
  23. package/readme.hints.md +96 -1
  24. package/readme.md +515 -301
  25. package/readme.plan.md +1135 -221
  26. package/readme.problems.md +167 -83
  27. package/ts/core/utils/async-utils.ts +275 -0
  28. package/ts/core/utils/binary-heap.ts +225 -0
  29. package/ts/core/utils/enhanced-connection-pool.ts +420 -0
  30. package/ts/core/utils/fs-utils.ts +270 -0
  31. package/ts/core/utils/index.ts +5 -2
  32. package/ts/core/utils/lifecycle-component.ts +231 -0
  33. package/ts/plugins.ts +2 -1
  34. package/ts/proxies/http-proxy/certificate-manager.ts +52 -1
  35. package/ts/proxies/nftables-proxy/nftables-proxy.ts +64 -79
  36. package/ts/proxies/smart-proxy/cert-store.ts +26 -20
  37. package/ts/proxies/smart-proxy/connection-manager.ts +291 -189
  38. package/readme.plan2.md +0 -764
  39. package/ts/common/eventUtils.ts +0 -34
  40. package/ts/common/types.ts +0 -91
  41. package/ts/core/utils/event-system.ts +0 -376
  42. package/ts/core/utils/event-utils.ts +0 -25
@@ -0,0 +1,225 @@
1
+ /**
2
+ * A binary heap implementation for efficient priority queue operations
3
+ * Supports O(log n) insert and extract operations
4
+ */
5
+ export class BinaryHeap<T> {
6
+ private heap: T[] = [];
7
+ private keyMap?: Map<string, number>; // For efficient key-based lookups
8
+
9
+ constructor(
10
+ private compareFn: (a: T, b: T) => number,
11
+ private extractKey?: (item: T) => string
12
+ ) {
13
+ if (extractKey) {
14
+ this.keyMap = new Map();
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Get the current size of the heap
20
+ */
21
+ public get size(): number {
22
+ return this.heap.length;
23
+ }
24
+
25
+ /**
26
+ * Check if the heap is empty
27
+ */
28
+ public isEmpty(): boolean {
29
+ return this.heap.length === 0;
30
+ }
31
+
32
+ /**
33
+ * Peek at the top element without removing it
34
+ */
35
+ public peek(): T | undefined {
36
+ return this.heap[0];
37
+ }
38
+
39
+ /**
40
+ * Insert a new item into the heap
41
+ * O(log n) time complexity
42
+ */
43
+ public insert(item: T): void {
44
+ const index = this.heap.length;
45
+ this.heap.push(item);
46
+
47
+ if (this.keyMap && this.extractKey) {
48
+ const key = this.extractKey(item);
49
+ this.keyMap.set(key, index);
50
+ }
51
+
52
+ this.bubbleUp(index);
53
+ }
54
+
55
+ /**
56
+ * Extract the top element from the heap
57
+ * O(log n) time complexity
58
+ */
59
+ public extract(): T | undefined {
60
+ if (this.heap.length === 0) return undefined;
61
+ if (this.heap.length === 1) {
62
+ const item = this.heap.pop()!;
63
+ if (this.keyMap && this.extractKey) {
64
+ this.keyMap.delete(this.extractKey(item));
65
+ }
66
+ return item;
67
+ }
68
+
69
+ const result = this.heap[0];
70
+ const lastItem = this.heap.pop()!;
71
+ this.heap[0] = lastItem;
72
+
73
+ if (this.keyMap && this.extractKey) {
74
+ this.keyMap.delete(this.extractKey(result));
75
+ this.keyMap.set(this.extractKey(lastItem), 0);
76
+ }
77
+
78
+ this.bubbleDown(0);
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * Extract an element that matches the predicate
84
+ * O(n) time complexity for search, O(log n) for extraction
85
+ */
86
+ public extractIf(predicate: (item: T) => boolean): T | undefined {
87
+ const index = this.heap.findIndex(predicate);
88
+ if (index === -1) return undefined;
89
+
90
+ return this.extractAt(index);
91
+ }
92
+
93
+ /**
94
+ * Extract an element by its key (if extractKey was provided)
95
+ * O(log n) time complexity
96
+ */
97
+ public extractByKey(key: string): T | undefined {
98
+ if (!this.keyMap || !this.extractKey) {
99
+ throw new Error('extractKey function must be provided to use key-based extraction');
100
+ }
101
+
102
+ const index = this.keyMap.get(key);
103
+ if (index === undefined) return undefined;
104
+
105
+ return this.extractAt(index);
106
+ }
107
+
108
+ /**
109
+ * Check if a key exists in the heap
110
+ * O(1) time complexity
111
+ */
112
+ public hasKey(key: string): boolean {
113
+ if (!this.keyMap) return false;
114
+ return this.keyMap.has(key);
115
+ }
116
+
117
+ /**
118
+ * Get all elements as an array (does not modify heap)
119
+ * O(n) time complexity
120
+ */
121
+ public toArray(): T[] {
122
+ return [...this.heap];
123
+ }
124
+
125
+ /**
126
+ * Clear the heap
127
+ */
128
+ public clear(): void {
129
+ this.heap = [];
130
+ if (this.keyMap) {
131
+ this.keyMap.clear();
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Extract element at specific index
137
+ */
138
+ private extractAt(index: number): T {
139
+ const item = this.heap[index];
140
+
141
+ if (this.keyMap && this.extractKey) {
142
+ this.keyMap.delete(this.extractKey(item));
143
+ }
144
+
145
+ if (index === this.heap.length - 1) {
146
+ this.heap.pop();
147
+ return item;
148
+ }
149
+
150
+ const lastItem = this.heap.pop()!;
151
+ this.heap[index] = lastItem;
152
+
153
+ if (this.keyMap && this.extractKey) {
154
+ this.keyMap.set(this.extractKey(lastItem), index);
155
+ }
156
+
157
+ // Try bubbling up first
158
+ const parentIndex = Math.floor((index - 1) / 2);
159
+ if (parentIndex >= 0 && this.compareFn(this.heap[index], this.heap[parentIndex]) < 0) {
160
+ this.bubbleUp(index);
161
+ } else {
162
+ this.bubbleDown(index);
163
+ }
164
+
165
+ return item;
166
+ }
167
+
168
+ /**
169
+ * Bubble up element at given index to maintain heap property
170
+ */
171
+ private bubbleUp(index: number): void {
172
+ while (index > 0) {
173
+ const parentIndex = Math.floor((index - 1) / 2);
174
+
175
+ if (this.compareFn(this.heap[index], this.heap[parentIndex]) >= 0) {
176
+ break;
177
+ }
178
+
179
+ this.swap(index, parentIndex);
180
+ index = parentIndex;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Bubble down element at given index to maintain heap property
186
+ */
187
+ private bubbleDown(index: number): void {
188
+ const length = this.heap.length;
189
+
190
+ while (true) {
191
+ const leftChild = 2 * index + 1;
192
+ const rightChild = 2 * index + 2;
193
+ let smallest = index;
194
+
195
+ if (leftChild < length &&
196
+ this.compareFn(this.heap[leftChild], this.heap[smallest]) < 0) {
197
+ smallest = leftChild;
198
+ }
199
+
200
+ if (rightChild < length &&
201
+ this.compareFn(this.heap[rightChild], this.heap[smallest]) < 0) {
202
+ smallest = rightChild;
203
+ }
204
+
205
+ if (smallest === index) break;
206
+
207
+ this.swap(index, smallest);
208
+ index = smallest;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Swap two elements in the heap
214
+ */
215
+ private swap(i: number, j: number): void {
216
+ const temp = this.heap[i];
217
+ this.heap[i] = this.heap[j];
218
+ this.heap[j] = temp;
219
+
220
+ if (this.keyMap && this.extractKey) {
221
+ this.keyMap.set(this.extractKey(this.heap[i]), i);
222
+ this.keyMap.set(this.extractKey(this.heap[j]), j);
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,420 @@
1
+ import { LifecycleComponent } from './lifecycle-component.js';
2
+ import { BinaryHeap } from './binary-heap.js';
3
+ import { AsyncMutex } from './async-utils.js';
4
+ import { EventEmitter } from 'events';
5
+
6
+ /**
7
+ * Interface for pooled connection
8
+ */
9
+ export interface IPooledConnection<T> {
10
+ id: string;
11
+ connection: T;
12
+ createdAt: number;
13
+ lastUsedAt: number;
14
+ useCount: number;
15
+ inUse: boolean;
16
+ metadata?: any;
17
+ }
18
+
19
+ /**
20
+ * Configuration options for the connection pool
21
+ */
22
+ export interface IConnectionPoolOptions<T> {
23
+ minSize?: number;
24
+ maxSize?: number;
25
+ acquireTimeout?: number;
26
+ idleTimeout?: number;
27
+ maxUseCount?: number;
28
+ validateOnAcquire?: boolean;
29
+ validateOnReturn?: boolean;
30
+ queueTimeout?: number;
31
+ connectionFactory: () => Promise<T>;
32
+ connectionValidator?: (connection: T) => Promise<boolean>;
33
+ connectionDestroyer?: (connection: T) => Promise<void>;
34
+ onConnectionError?: (error: Error, connection?: T) => void;
35
+ }
36
+
37
+ /**
38
+ * Interface for queued acquire request
39
+ */
40
+ interface IAcquireRequest<T> {
41
+ id: string;
42
+ priority: number;
43
+ timestamp: number;
44
+ resolve: (connection: IPooledConnection<T>) => void;
45
+ reject: (error: Error) => void;
46
+ timeoutHandle?: NodeJS.Timeout;
47
+ }
48
+
49
+ /**
50
+ * Enhanced connection pool with priority queue, backpressure, and lifecycle management
51
+ */
52
+ export class EnhancedConnectionPool<T> extends LifecycleComponent {
53
+ private readonly options: Required<Omit<IConnectionPoolOptions<T>, 'connectionValidator' | 'connectionDestroyer' | 'onConnectionError'>> & Pick<IConnectionPoolOptions<T>, 'connectionValidator' | 'connectionDestroyer' | 'onConnectionError'>;
54
+ private readonly availableConnections: IPooledConnection<T>[] = [];
55
+ private readonly activeConnections: Map<string, IPooledConnection<T>> = new Map();
56
+ private readonly waitQueue: BinaryHeap<IAcquireRequest<T>>;
57
+ private readonly mutex = new AsyncMutex();
58
+ private readonly eventEmitter = new EventEmitter();
59
+
60
+ private connectionIdCounter = 0;
61
+ private requestIdCounter = 0;
62
+ private isClosing = false;
63
+
64
+ // Metrics
65
+ private metrics = {
66
+ connectionsCreated: 0,
67
+ connectionsDestroyed: 0,
68
+ connectionsAcquired: 0,
69
+ connectionsReleased: 0,
70
+ acquireTimeouts: 0,
71
+ validationFailures: 0,
72
+ queueHighWaterMark: 0,
73
+ };
74
+
75
+ constructor(options: IConnectionPoolOptions<T>) {
76
+ super();
77
+
78
+ this.options = {
79
+ minSize: 0,
80
+ maxSize: 10,
81
+ acquireTimeout: 30000,
82
+ idleTimeout: 300000, // 5 minutes
83
+ maxUseCount: Infinity,
84
+ validateOnAcquire: true,
85
+ validateOnReturn: false,
86
+ queueTimeout: 60000,
87
+ ...options,
88
+ };
89
+
90
+ // Initialize priority queue (higher priority = extracted first)
91
+ this.waitQueue = new BinaryHeap<IAcquireRequest<T>>(
92
+ (a, b) => b.priority - a.priority || a.timestamp - b.timestamp,
93
+ (item) => item.id
94
+ );
95
+
96
+ // Start maintenance cycle
97
+ this.startMaintenance();
98
+
99
+ // Initialize minimum connections
100
+ this.initializeMinConnections();
101
+ }
102
+
103
+ /**
104
+ * Initialize minimum number of connections
105
+ */
106
+ private async initializeMinConnections(): Promise<void> {
107
+ const promises: Promise<void>[] = [];
108
+
109
+ for (let i = 0; i < this.options.minSize; i++) {
110
+ promises.push(
111
+ this.createConnection()
112
+ .then(conn => {
113
+ this.availableConnections.push(conn);
114
+ })
115
+ .catch(err => {
116
+ if (this.options.onConnectionError) {
117
+ this.options.onConnectionError(err);
118
+ }
119
+ })
120
+ );
121
+ }
122
+
123
+ await Promise.all(promises);
124
+ }
125
+
126
+ /**
127
+ * Start maintenance timer for idle connection cleanup
128
+ */
129
+ private startMaintenance(): void {
130
+ this.setInterval(() => {
131
+ this.performMaintenance();
132
+ }, 30000); // Every 30 seconds
133
+ }
134
+
135
+ /**
136
+ * Perform maintenance tasks
137
+ */
138
+ private async performMaintenance(): Promise<void> {
139
+ await this.mutex.runExclusive(async () => {
140
+ const now = Date.now();
141
+ const toRemove: IPooledConnection<T>[] = [];
142
+
143
+ // Check for idle connections beyond minimum size
144
+ for (let i = this.availableConnections.length - 1; i >= 0; i--) {
145
+ const conn = this.availableConnections[i];
146
+
147
+ // Keep minimum connections
148
+ if (this.availableConnections.length <= this.options.minSize) {
149
+ break;
150
+ }
151
+
152
+ // Remove idle connections
153
+ if (now - conn.lastUsedAt > this.options.idleTimeout) {
154
+ toRemove.push(conn);
155
+ this.availableConnections.splice(i, 1);
156
+ }
157
+ }
158
+
159
+ // Destroy idle connections
160
+ for (const conn of toRemove) {
161
+ await this.destroyConnection(conn);
162
+ }
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Acquire a connection from the pool
168
+ */
169
+ public async acquire(priority: number = 0, timeout?: number): Promise<IPooledConnection<T>> {
170
+ if (this.isClosing) {
171
+ throw new Error('Connection pool is closing');
172
+ }
173
+
174
+ return this.mutex.runExclusive(async () => {
175
+ // Try to get an available connection
176
+ const connection = await this.tryAcquireConnection();
177
+ if (connection) {
178
+ return connection;
179
+ }
180
+
181
+ // Check if we can create a new connection
182
+ const totalConnections = this.availableConnections.length + this.activeConnections.size;
183
+ if (totalConnections < this.options.maxSize) {
184
+ try {
185
+ const newConnection = await this.createConnection();
186
+ return this.checkoutConnection(newConnection);
187
+ } catch (err) {
188
+ // Fall through to queue if creation fails
189
+ }
190
+ }
191
+
192
+ // Add to wait queue
193
+ return this.queueAcquireRequest(priority, timeout);
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Try to acquire an available connection
199
+ */
200
+ private async tryAcquireConnection(): Promise<IPooledConnection<T> | null> {
201
+ while (this.availableConnections.length > 0) {
202
+ const connection = this.availableConnections.shift()!;
203
+
204
+ // Check if connection exceeded max use count
205
+ if (connection.useCount >= this.options.maxUseCount) {
206
+ await this.destroyConnection(connection);
207
+ continue;
208
+ }
209
+
210
+ // Validate connection if required
211
+ if (this.options.validateOnAcquire && this.options.connectionValidator) {
212
+ try {
213
+ const isValid = await this.options.connectionValidator(connection.connection);
214
+ if (!isValid) {
215
+ this.metrics.validationFailures++;
216
+ await this.destroyConnection(connection);
217
+ continue;
218
+ }
219
+ } catch (err) {
220
+ this.metrics.validationFailures++;
221
+ await this.destroyConnection(connection);
222
+ continue;
223
+ }
224
+ }
225
+
226
+ return this.checkoutConnection(connection);
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Checkout a connection for use
234
+ */
235
+ private checkoutConnection(connection: IPooledConnection<T>): IPooledConnection<T> {
236
+ connection.inUse = true;
237
+ connection.lastUsedAt = Date.now();
238
+ connection.useCount++;
239
+
240
+ this.activeConnections.set(connection.id, connection);
241
+ this.metrics.connectionsAcquired++;
242
+
243
+ this.eventEmitter.emit('acquire', connection);
244
+ return connection;
245
+ }
246
+
247
+ /**
248
+ * Queue an acquire request
249
+ */
250
+ private queueAcquireRequest(priority: number, timeout?: number): Promise<IPooledConnection<T>> {
251
+ return new Promise<IPooledConnection<T>>((resolve, reject) => {
252
+ const request: IAcquireRequest<T> = {
253
+ id: `req-${this.requestIdCounter++}`,
254
+ priority,
255
+ timestamp: Date.now(),
256
+ resolve,
257
+ reject,
258
+ };
259
+
260
+ // Set timeout
261
+ const timeoutMs = timeout || this.options.queueTimeout;
262
+ request.timeoutHandle = this.setTimeout(() => {
263
+ if (this.waitQueue.extractByKey(request.id)) {
264
+ this.metrics.acquireTimeouts++;
265
+ reject(new Error(`Connection acquire timeout after ${timeoutMs}ms`));
266
+ }
267
+ }, timeoutMs);
268
+
269
+ this.waitQueue.insert(request);
270
+ this.metrics.queueHighWaterMark = Math.max(
271
+ this.metrics.queueHighWaterMark,
272
+ this.waitQueue.size
273
+ );
274
+
275
+ this.eventEmitter.emit('enqueue', { queueSize: this.waitQueue.size });
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Release a connection back to the pool
281
+ */
282
+ public async release(connection: IPooledConnection<T>): Promise<void> {
283
+ return this.mutex.runExclusive(async () => {
284
+ if (!connection.inUse || !this.activeConnections.has(connection.id)) {
285
+ throw new Error('Connection is not active');
286
+ }
287
+
288
+ this.activeConnections.delete(connection.id);
289
+ connection.inUse = false;
290
+ connection.lastUsedAt = Date.now();
291
+ this.metrics.connectionsReleased++;
292
+
293
+ // Check if connection should be destroyed
294
+ if (connection.useCount >= this.options.maxUseCount) {
295
+ await this.destroyConnection(connection);
296
+ return;
297
+ }
298
+
299
+ // Validate on return if required
300
+ if (this.options.validateOnReturn && this.options.connectionValidator) {
301
+ try {
302
+ const isValid = await this.options.connectionValidator(connection.connection);
303
+ if (!isValid) {
304
+ await this.destroyConnection(connection);
305
+ return;
306
+ }
307
+ } catch (err) {
308
+ await this.destroyConnection(connection);
309
+ return;
310
+ }
311
+ }
312
+
313
+ // Check if there are waiting requests
314
+ const request = this.waitQueue.extract();
315
+ if (request) {
316
+ this.clearTimeout(request.timeoutHandle!);
317
+ request.resolve(this.checkoutConnection(connection));
318
+ this.eventEmitter.emit('dequeue', { queueSize: this.waitQueue.size });
319
+ } else {
320
+ // Return to available pool
321
+ this.availableConnections.push(connection);
322
+ this.eventEmitter.emit('release', connection);
323
+ }
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Create a new connection
329
+ */
330
+ private async createConnection(): Promise<IPooledConnection<T>> {
331
+ const rawConnection = await this.options.connectionFactory();
332
+
333
+ const connection: IPooledConnection<T> = {
334
+ id: `conn-${this.connectionIdCounter++}`,
335
+ connection: rawConnection,
336
+ createdAt: Date.now(),
337
+ lastUsedAt: Date.now(),
338
+ useCount: 0,
339
+ inUse: false,
340
+ };
341
+
342
+ this.metrics.connectionsCreated++;
343
+ this.eventEmitter.emit('create', connection);
344
+
345
+ return connection;
346
+ }
347
+
348
+ /**
349
+ * Destroy a connection
350
+ */
351
+ private async destroyConnection(connection: IPooledConnection<T>): Promise<void> {
352
+ try {
353
+ if (this.options.connectionDestroyer) {
354
+ await this.options.connectionDestroyer(connection.connection);
355
+ }
356
+
357
+ this.metrics.connectionsDestroyed++;
358
+ this.eventEmitter.emit('destroy', connection);
359
+ } catch (err) {
360
+ if (this.options.onConnectionError) {
361
+ this.options.onConnectionError(err as Error, connection.connection);
362
+ }
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Get current pool statistics
368
+ */
369
+ public getStats() {
370
+ return {
371
+ available: this.availableConnections.length,
372
+ active: this.activeConnections.size,
373
+ waiting: this.waitQueue.size,
374
+ total: this.availableConnections.length + this.activeConnections.size,
375
+ ...this.metrics,
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Subscribe to pool events
381
+ */
382
+ public on(event: string, listener: Function): void {
383
+ this.addEventListener(this.eventEmitter, event, listener);
384
+ }
385
+
386
+ /**
387
+ * Close the pool and cleanup resources
388
+ */
389
+ protected async onCleanup(): Promise<void> {
390
+ this.isClosing = true;
391
+
392
+ // Clear the wait queue
393
+ while (!this.waitQueue.isEmpty()) {
394
+ const request = this.waitQueue.extract();
395
+ if (request) {
396
+ this.clearTimeout(request.timeoutHandle!);
397
+ request.reject(new Error('Connection pool is closing'));
398
+ }
399
+ }
400
+
401
+ // Wait for active connections to be released (with timeout)
402
+ const timeout = 30000;
403
+ const startTime = Date.now();
404
+
405
+ while (this.activeConnections.size > 0 && Date.now() - startTime < timeout) {
406
+ await new Promise(resolve => setTimeout(resolve, 100));
407
+ }
408
+
409
+ // Destroy all connections
410
+ const allConnections = [
411
+ ...this.availableConnections,
412
+ ...this.activeConnections.values(),
413
+ ];
414
+
415
+ await Promise.all(allConnections.map(conn => this.destroyConnection(conn)));
416
+
417
+ this.availableConnections.length = 0;
418
+ this.activeConnections.clear();
419
+ }
420
+ }