@putuofc/fastp 0.0.1 → 3.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 (3) hide show
  1. package/dist/index.d.ts +133 -24
  2. package/dist/index.js +1260 -113
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33,27 +33,589 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.FastProxiedClient = void 0;
36
+ exports.ProfileRegistry = exports.AdvancedTLSClient = void 0;
37
+ const tls = __importStar(require("tls"));
38
+ const net = __importStar(require("net"));
37
39
  const https = __importStar(require("https"));
40
+ const http2 = __importStar(require("http2"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const events_1 = require("events");
38
43
  const zlib = __importStar(require("zlib"));
39
44
  const util_1 = require("util");
40
45
  const stream_1 = require("stream");
41
- const url_1 = require("url");
42
- const events_1 = require("events");
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const url = __importStar(require("url"));
43
49
  const gunzip = (0, util_1.promisify)(zlib.gunzip);
44
50
  const brotliDecompress = (0, util_1.promisify)(zlib.brotliDecompress);
45
51
  const inflate = (0, util_1.promisify)(zlib.inflate);
46
- const PROXIES = [
47
- 'https://snowy-hill-f293.apis8.workers.dev/',
48
- 'https://polished-resonance-0c97.apis9.workers.dev/',
49
- 'https://mute-wind-ee8d.apis10.workers.dev/',
50
- 'https://billowing-hall-ebe5.proxyserver2.workers.dev/',
51
- 'https://dark-shadow-1776.apis7.workers.dev/',
52
- 'https://aged-breeze-9d12.apis4.workers.dev/'
53
- ];
54
- function getRandomProxy() {
55
- return PROXIES[Math.floor(Math.random() * PROXIES.length)];
52
+ const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
53
+ class ProfileRegistry {
54
+ static getGrease() {
55
+ return GREASE_VALUES[Math.floor(Math.random() * GREASE_VALUES.length)];
56
+ }
57
+ static chromeMobile143Android() {
58
+ const grease1 = this.getGrease();
59
+ const grease2 = this.getGrease();
60
+ const grease3 = this.getGrease();
61
+ const grease4 = this.getGrease();
62
+ return {
63
+ name: 'chrome_mobile_143_android',
64
+ version: '143.0.0.0',
65
+ tls: {
66
+ cipherSuites: [
67
+ grease1,
68
+ 4865,
69
+ 4866,
70
+ 4867,
71
+ 49195,
72
+ 49199,
73
+ 49196,
74
+ 49200,
75
+ 52393,
76
+ 52392,
77
+ 49171,
78
+ 49172,
79
+ 156,
80
+ 157,
81
+ 47,
82
+ 53
83
+ ],
84
+ extensions: [
85
+ grease2,
86
+ 65037,
87
+ 27,
88
+ 43,
89
+ 16,
90
+ 35,
91
+ 45,
92
+ 11,
93
+ 65281,
94
+ 5,
95
+ 10,
96
+ 23,
97
+ 0,
98
+ 17513,
99
+ 51,
100
+ 18,
101
+ 13,
102
+ grease3
103
+ ],
104
+ supportedGroups: [
105
+ grease4,
106
+ 25497,
107
+ 29,
108
+ 23,
109
+ 24
110
+ ],
111
+ signatureAlgorithms: [
112
+ 1027,
113
+ 2052,
114
+ 1025,
115
+ 1283,
116
+ 2053,
117
+ 1281,
118
+ 2054,
119
+ 1537
120
+ ],
121
+ supportedVersions: [
122
+ grease2,
123
+ 772,
124
+ 771
125
+ ],
126
+ ecPointFormats: [0],
127
+ alpnProtocols: ['h2', 'http/1.1'],
128
+ pskKeyExchangeModes: [1],
129
+ compressionMethods: [0],
130
+ grease: true,
131
+ recordSizeLimit: 16385,
132
+ certificateCompression: [2],
133
+ ocspStapling: true,
134
+ signedCertificateTimestamp: true
135
+ },
136
+ http2: {
137
+ windowUpdate: 15663105,
138
+ headerTableSize: 65536,
139
+ enablePush: 0,
140
+ initialWindowSize: 6291456,
141
+ maxFrameSize: 16384,
142
+ maxHeaderListSize: 262144,
143
+ settingsOrder: [1, 2, 4, 6],
144
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
145
+ priorityFrames: [
146
+ {
147
+ streamId: 0,
148
+ weight: 256,
149
+ dependency: 0,
150
+ exclusive: true
151
+ }
152
+ ],
153
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
154
+ headerOrder: [
155
+ 'sec-ch-ua',
156
+ 'sec-ch-ua-mobile',
157
+ 'sec-ch-ua-platform',
158
+ 'upgrade-insecure-requests',
159
+ 'user-agent',
160
+ 'accept',
161
+ 'sec-fetch-site',
162
+ 'sec-fetch-mode',
163
+ 'sec-fetch-dest',
164
+ 'accept-encoding',
165
+ 'accept-language',
166
+ 'priority'
167
+ ]
168
+ },
169
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36',
170
+ secChUa: '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
171
+ secChUaPlatform: '"Android"',
172
+ secChUaMobile: '?1',
173
+ secFetchSite: 'none',
174
+ secFetchMode: 'navigate',
175
+ secFetchDest: 'document',
176
+ upgradeInsecureRequests: '1',
177
+ acceptLanguage: 'id,en-US;q=0.9,en;q=0.8,ms;q=0.7,ja;q=0.6,zh-CN;q=0.5,zh;q=0.4',
178
+ priority: 'u=0, i'
179
+ };
180
+ }
181
+ static chrome133PSK() {
182
+ const grease1 = this.getGrease();
183
+ const grease2 = this.getGrease();
184
+ const grease3 = this.getGrease();
185
+ const grease4 = this.getGrease();
186
+ return {
187
+ name: 'chrome_133_psk',
188
+ version: '133.0.0.0',
189
+ tls: {
190
+ cipherSuites: [
191
+ grease1,
192
+ 4865,
193
+ 4866,
194
+ 4867,
195
+ 49195,
196
+ 49199,
197
+ 49196,
198
+ 49200,
199
+ 52393,
200
+ 52392,
201
+ 49171,
202
+ 49172,
203
+ 156,
204
+ 157,
205
+ 47,
206
+ 53
207
+ ],
208
+ extensions: [
209
+ grease2,
210
+ 0,
211
+ 23,
212
+ 65281,
213
+ 10,
214
+ 11,
215
+ 35,
216
+ 16,
217
+ 5,
218
+ 13,
219
+ 18,
220
+ 51,
221
+ 45,
222
+ 43,
223
+ 27,
224
+ 21,
225
+ grease3
226
+ ],
227
+ supportedGroups: [
228
+ grease4,
229
+ 25497,
230
+ 29,
231
+ 23,
232
+ 24
233
+ ],
234
+ signatureAlgorithms: [
235
+ 1027,
236
+ 2052,
237
+ 1025,
238
+ 1283,
239
+ 2053,
240
+ 1281,
241
+ 2054,
242
+ 1537
243
+ ],
244
+ supportedVersions: [
245
+ grease2,
246
+ 772,
247
+ 771
248
+ ],
249
+ ecPointFormats: [0],
250
+ alpnProtocols: ['h2', 'http/1.1'],
251
+ pskKeyExchangeModes: [1],
252
+ compressionMethods: [0],
253
+ grease: true,
254
+ recordSizeLimit: 16385
255
+ },
256
+ http2: {
257
+ windowUpdate: 15663105,
258
+ headerTableSize: 65536,
259
+ enablePush: 0,
260
+ initialWindowSize: 6291456,
261
+ maxFrameSize: 16384,
262
+ maxHeaderListSize: 262144,
263
+ settingsOrder: [1, 2, 4, 6],
264
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
265
+ priorityFrames: [
266
+ {
267
+ streamId: 0,
268
+ weight: 256,
269
+ dependency: 0,
270
+ exclusive: true
271
+ }
272
+ ],
273
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
274
+ headerOrder: [
275
+ 'cache-control',
276
+ 'sec-ch-ua',
277
+ 'sec-ch-ua-mobile',
278
+ 'sec-ch-ua-platform',
279
+ 'upgrade-insecure-requests',
280
+ 'user-agent',
281
+ 'accept',
282
+ 'sec-fetch-site',
283
+ 'sec-fetch-mode',
284
+ 'sec-fetch-user',
285
+ 'sec-fetch-dest',
286
+ 'accept-encoding',
287
+ 'accept-language'
288
+ ]
289
+ },
290
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
291
+ secChUa: '"Google Chrome";v="133", "Chromium";v="133", "Not_A Brand";v="24"',
292
+ secChUaPlatform: '"Windows"',
293
+ secChUaMobile: '?0',
294
+ secFetchSite: 'none',
295
+ secFetchMode: 'navigate',
296
+ secFetchDest: 'document'
297
+ };
298
+ }
299
+ static chrome133() {
300
+ const grease1 = this.getGrease();
301
+ const grease2 = this.getGrease();
302
+ const grease3 = this.getGrease();
303
+ const grease4 = this.getGrease();
304
+ return {
305
+ name: 'chrome_133',
306
+ version: '133.0.0.0',
307
+ tls: {
308
+ cipherSuites: [
309
+ grease1,
310
+ 4865,
311
+ 4866,
312
+ 4867,
313
+ 49195,
314
+ 49199,
315
+ 49196,
316
+ 49200,
317
+ 52393,
318
+ 52392,
319
+ 49171,
320
+ 49172,
321
+ 156,
322
+ 157,
323
+ 47,
324
+ 53
325
+ ],
326
+ extensions: [
327
+ grease2,
328
+ 0,
329
+ 23,
330
+ 65281,
331
+ 10,
332
+ 11,
333
+ 35,
334
+ 16,
335
+ 5,
336
+ 13,
337
+ 18,
338
+ 51,
339
+ 45,
340
+ 43,
341
+ 27,
342
+ 21,
343
+ grease3
344
+ ],
345
+ supportedGroups: [
346
+ grease4,
347
+ 25497,
348
+ 29,
349
+ 23,
350
+ 24
351
+ ],
352
+ signatureAlgorithms: [
353
+ 1027,
354
+ 2052,
355
+ 1025,
356
+ 1283,
357
+ 2053,
358
+ 1281,
359
+ 2054,
360
+ 1537
361
+ ],
362
+ supportedVersions: [
363
+ grease2,
364
+ 772,
365
+ 771
366
+ ],
367
+ ecPointFormats: [0],
368
+ alpnProtocols: ['h2', 'http/1.1'],
369
+ pskKeyExchangeModes: [1],
370
+ compressionMethods: [0],
371
+ grease: true,
372
+ recordSizeLimit: 16385
373
+ },
374
+ http2: {
375
+ windowUpdate: 15663105,
376
+ headerTableSize: 65536,
377
+ enablePush: 0,
378
+ initialWindowSize: 6291456,
379
+ maxFrameSize: 16384,
380
+ maxHeaderListSize: 262144,
381
+ settingsOrder: [1, 2, 4, 6],
382
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
383
+ priorityFrames: [
384
+ {
385
+ streamId: 0,
386
+ weight: 256,
387
+ dependency: 0,
388
+ exclusive: true
389
+ }
390
+ ],
391
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
392
+ headerOrder: [
393
+ 'cache-control',
394
+ 'sec-ch-ua',
395
+ 'sec-ch-ua-mobile',
396
+ 'sec-ch-ua-platform',
397
+ 'upgrade-insecure-requests',
398
+ 'user-agent',
399
+ 'accept',
400
+ 'sec-fetch-site',
401
+ 'sec-fetch-mode',
402
+ 'sec-fetch-user',
403
+ 'sec-fetch-dest',
404
+ 'accept-encoding',
405
+ 'accept-language'
406
+ ]
407
+ },
408
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
409
+ secChUa: '"Google Chrome";v="133", "Chromium";v="133", "Not_A Brand";v="24"',
410
+ secChUaPlatform: '"Windows"',
411
+ secChUaMobile: '?0',
412
+ secFetchSite: 'none',
413
+ secFetchMode: 'navigate',
414
+ secFetchDest: 'document'
415
+ };
416
+ }
417
+ static firefox117() {
418
+ return {
419
+ name: 'firefox_117',
420
+ version: '117.0',
421
+ tls: {
422
+ cipherSuites: [
423
+ 4865,
424
+ 4867,
425
+ 4866,
426
+ 49195,
427
+ 49199,
428
+ 52393,
429
+ 52392,
430
+ 49196,
431
+ 49200,
432
+ 159,
433
+ 158,
434
+ 49161,
435
+ 49162,
436
+ 156,
437
+ 157,
438
+ 47,
439
+ 53
440
+ ],
441
+ extensions: [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21],
442
+ supportedGroups: [
443
+ 29,
444
+ 23,
445
+ 24,
446
+ 25,
447
+ 256,
448
+ 257
449
+ ],
450
+ signatureAlgorithms: [
451
+ 1027,
452
+ 1283,
453
+ 1539,
454
+ 2052,
455
+ 2053,
456
+ 2054,
457
+ 1025,
458
+ 1281,
459
+ 1537,
460
+ 513,
461
+ 515
462
+ ],
463
+ supportedVersions: [
464
+ 772,
465
+ 771
466
+ ],
467
+ ecPointFormats: [0],
468
+ alpnProtocols: ['h2', 'http/1.1'],
469
+ pskKeyExchangeModes: [1],
470
+ compressionMethods: [0],
471
+ grease: false
472
+ },
473
+ http2: {
474
+ windowUpdate: 12517377,
475
+ headerTableSize: 65536,
476
+ enablePush: 0,
477
+ initialWindowSize: 131072,
478
+ maxFrameSize: 16384,
479
+ settingsOrder: [1, 4, 5],
480
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
481
+ priorityFrames: [
482
+ { streamId: 3, weight: 200, dependency: 0, exclusive: false },
483
+ { streamId: 5, weight: 100, dependency: 0, exclusive: false },
484
+ { streamId: 7, weight: 0, dependency: 0, exclusive: false },
485
+ { streamId: 9, weight: 0, dependency: 7, exclusive: false },
486
+ { streamId: 11, weight: 0, dependency: 3, exclusive: false },
487
+ { streamId: 13, weight: 240, dependency: 0, exclusive: false }
488
+ ],
489
+ pseudoHeaderOrder: [':method', ':path', ':authority', ':scheme'],
490
+ headerPriority: { streamDep: 13, exclusive: false, weight: 41 },
491
+ headerOrder: []
492
+ },
493
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0',
494
+ secChUa: '',
495
+ secChUaPlatform: '',
496
+ secChUaMobile: '',
497
+ secFetchSite: 'none',
498
+ secFetchMode: 'navigate',
499
+ secFetchDest: 'document'
500
+ };
501
+ }
502
+ static safariIOS18() {
503
+ const grease1 = this.getGrease();
504
+ const grease2 = this.getGrease();
505
+ return {
506
+ name: 'safari_ios_18_0',
507
+ version: '18.0',
508
+ tls: {
509
+ cipherSuites: [
510
+ grease1,
511
+ 4865,
512
+ 4866,
513
+ 4867,
514
+ 49196,
515
+ 49195,
516
+ 52393,
517
+ 49200,
518
+ 49199,
519
+ 52392,
520
+ 159,
521
+ 158,
522
+ 49162,
523
+ 49161,
524
+ 157,
525
+ 156,
526
+ 53,
527
+ 47,
528
+ 49188,
529
+ 49187,
530
+ 60
531
+ ],
532
+ extensions: [
533
+ grease2,
534
+ 0,
535
+ 23,
536
+ 65281,
537
+ 10,
538
+ 11,
539
+ 16,
540
+ 5,
541
+ 13,
542
+ 18,
543
+ 51,
544
+ 45,
545
+ 43,
546
+ 21
547
+ ],
548
+ supportedGroups: [
549
+ 29,
550
+ 23,
551
+ 24,
552
+ 25
553
+ ],
554
+ signatureAlgorithms: [
555
+ 1027,
556
+ 2052,
557
+ 1025,
558
+ 1283,
559
+ 513,
560
+ 2053,
561
+ 2053,
562
+ 1281,
563
+ 2054,
564
+ 1537,
565
+ 515
566
+ ],
567
+ supportedVersions: [
568
+ 772,
569
+ 771,
570
+ 770,
571
+ 769
572
+ ],
573
+ ecPointFormats: [0],
574
+ alpnProtocols: ['h2', 'http/1.1'],
575
+ pskKeyExchangeModes: [1],
576
+ compressionMethods: [0],
577
+ grease: true
578
+ },
579
+ http2: {
580
+ windowUpdate: 10420225,
581
+ headerTableSize: 4096,
582
+ enablePush: 0,
583
+ maxConcurrentStreams: 100,
584
+ initialWindowSize: 2097152,
585
+ maxFrameSize: 16384,
586
+ settingsOrder: [2, 3, 4, 8, 9],
587
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
588
+ priorityFrames: [],
589
+ pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path'],
590
+ headerOrder: []
591
+ },
592
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',
593
+ secChUa: '',
594
+ secChUaPlatform: '',
595
+ secChUaMobile: '?1',
596
+ secFetchSite: 'none',
597
+ secFetchMode: 'navigate',
598
+ secFetchDest: 'document'
599
+ };
600
+ }
601
+ static get(name) {
602
+ switch (name.toLowerCase()) {
603
+ case 'chrome_mobile_143_android': return this.chromeMobile143Android();
604
+ case 'chrome_133_psk': return this.chrome133PSK();
605
+ case 'chrome_133': return this.chrome133();
606
+ case 'firefox_117': return this.firefox117();
607
+ case 'safari_ios_18_0': return this.safariIOS18();
608
+ default: return this.chrome133();
609
+ }
610
+ }
611
+ static get latest() {
612
+ return this.chrome133();
613
+ }
614
+ static get latestMobile() {
615
+ return this.chromeMobile143Android();
616
+ }
56
617
  }
618
+ exports.ProfileRegistry = ProfileRegistry;
57
619
  class CookieJar {
58
620
  constructor() {
59
621
  this.cookies = new Map();
@@ -82,97 +644,595 @@ class CookieJar {
82
644
  return validCookies.join('; ');
83
645
  }
84
646
  parseSetCookie(domain, setCookieHeaders) {
85
- const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
647
+ if (!setCookieHeaders)
648
+ return;
649
+ const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : typeof setCookieHeaders === 'string' ? [setCookieHeaders] : [];
86
650
  for (const header of headers) {
87
- if (!header)
88
- continue;
89
651
  const parts = header.split(';').map(p => p.trim());
90
652
  const [nameValue] = parts;
91
653
  if (!nameValue)
92
654
  continue;
93
- const [name, value = ''] = nameValue.split('=', 2);
655
+ const [name, value = ''] = nameValue.split('=');
94
656
  const options = { path: '/' };
95
657
  for (let i = 1; i < parts.length; i++) {
96
- const [key, val = ''] = parts[i].split('=', 2);
658
+ const part = parts[i];
659
+ if (!part)
660
+ continue;
661
+ const [key, val] = part.split('=');
97
662
  const lowerKey = key.toLowerCase();
98
- if (lowerKey === 'expires')
663
+ if (lowerKey === 'expires' && val)
99
664
  options.expires = new Date(val);
100
- else if (lowerKey === 'path')
665
+ else if (lowerKey === 'path' && val)
101
666
  options.path = val;
102
667
  else if (lowerKey === 'secure')
103
668
  options.secure = true;
104
669
  else if (lowerKey === 'httponly')
105
670
  options.httpOnly = true;
106
- else if (lowerKey === 'samesite')
671
+ else if (lowerKey === 'samesite' && val)
107
672
  options.sameSite = val;
108
673
  }
109
674
  this.setCookie(domain, name, value, options);
110
675
  }
111
676
  }
112
- clear() {
113
- this.cookies.clear();
114
- }
115
677
  }
116
- class FastProxiedClient {
117
- constructor() {
118
- this.cookieJar = new CookieJar();
678
+ var Protocol;
679
+ (function (Protocol) {
680
+ Protocol["HTTP1"] = "http1";
681
+ Protocol["HTTP2"] = "http2";
682
+ })(Protocol || (Protocol = {}));
683
+ class UnifiedClientManager extends events_1.EventEmitter {
684
+ constructor(tlsSocket, profile, hostname, cookieJar) {
685
+ super();
686
+ this.http2Client = null;
687
+ this.negotiatedProtocol = Protocol.HTTP1;
688
+ this.tlsSocket = tlsSocket;
689
+ this.profile = profile;
690
+ this.hostname = hostname;
691
+ this.cookieJar = cookieJar;
119
692
  }
120
- async request(url, options = {}) {
121
- const targetUrl = new url_1.URL(url);
122
- const proxyBase = getRandomProxy();
123
- const proxyUrl = new url_1.URL(proxyBase + url);
124
- const startTime = Date.now();
125
- const headers = {
126
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
127
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
128
- 'accept-language': 'en-US,en;q=0.9',
129
- 'accept-encoding': 'gzip, deflate, br, zstd',
130
- ...options.headers
693
+ async initialize() {
694
+ const alpn = this.tlsSocket.alpnProtocol;
695
+ if (alpn === 'h2') {
696
+ this.negotiatedProtocol = Protocol.HTTP2;
697
+ await this.createHttp2Session();
698
+ }
699
+ else {
700
+ this.negotiatedProtocol = Protocol.HTTP1;
701
+ }
702
+ }
703
+ async createHttp2Session() {
704
+ return new Promise((resolve, reject) => {
705
+ const settings = {};
706
+ this.profile.http2.settingsOrder.forEach(id => {
707
+ switch (id) {
708
+ case 1:
709
+ settings.headerTableSize = this.profile.http2.headerTableSize;
710
+ break;
711
+ case 2:
712
+ settings.enablePush = this.profile.http2.enablePush === 1;
713
+ break;
714
+ case 3:
715
+ if (this.profile.http2.maxConcurrentStreams)
716
+ settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
717
+ break;
718
+ case 4:
719
+ settings.initialWindowSize = this.profile.http2.initialWindowSize;
720
+ break;
721
+ case 5:
722
+ if (this.profile.http2.maxFrameSize)
723
+ settings.maxFrameSize = this.profile.http2.maxFrameSize;
724
+ break;
725
+ case 6:
726
+ if (this.profile.http2.maxHeaderListSize)
727
+ settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
728
+ break;
729
+ }
730
+ });
731
+ this.http2Client = http2.connect(`https://${this.hostname}`, {
732
+ createConnection: () => this.tlsSocket,
733
+ settings
734
+ });
735
+ this.http2Client.once('connect', () => resolve());
736
+ this.http2Client.once('error', reject);
737
+ this.http2Client.once('timeout', () => reject(new Error('HTTP/2 session timeout')));
738
+ });
739
+ }
740
+ async request(path, options, timingStart, headers, post_data, out, callback) {
741
+ const cleanHeaders = { ...headers };
742
+ delete cleanHeaders['connection'];
743
+ delete cleanHeaders['Connection'];
744
+ delete cleanHeaders['keep-alive'];
745
+ delete cleanHeaders['Keep-Alive'];
746
+ delete cleanHeaders['proxy-connection'];
747
+ delete cleanHeaders['Proxy-Connection'];
748
+ delete cleanHeaders['upgrade'];
749
+ delete cleanHeaders['Upgrade'];
750
+ delete cleanHeaders['transfer-encoding'];
751
+ delete cleanHeaders['Transfer-Encoding'];
752
+ if (this.negotiatedProtocol === Protocol.HTTP2 && this.http2Client && !this.http2Client.destroyed) {
753
+ await this.requestHttp2(path, options, timingStart, cleanHeaders, post_data, out, callback);
754
+ }
755
+ else {
756
+ await this.requestHttp1(path, options, timingStart, cleanHeaders, post_data, out, callback);
757
+ }
758
+ }
759
+ async requestHttp2(path, options, timingStart, headers, post_data, out, callback) {
760
+ if (!this.http2Client || this.http2Client.destroyed)
761
+ throw new Error('HTTP/2 session not available');
762
+ const reqHeaders = {
763
+ ':method': options.method?.toUpperCase() || 'GET',
764
+ ':authority': this.hostname,
765
+ ':scheme': 'https',
766
+ ':path': path,
767
+ ...headers
768
+ };
769
+ const stream = this.http2Client.request(reqHeaders);
770
+ const response = new events_1.EventEmitter();
771
+ response.timing = { socket: timingStart, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
772
+ response.fingerprints = {
773
+ ja3: this.generateJA3(),
774
+ ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
775
+ akamai: `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`
131
776
  };
132
- const cookieString = this.cookieJar.getCookies(targetUrl.hostname, targetUrl.pathname, true);
133
- if (cookieString) {
134
- headers['cookie'] = headers['cookie'] ? `${headers['cookie']}; ${cookieString}` : cookieString;
777
+ stream.once('response', (hdrs) => {
778
+ response.statusCode = hdrs[':status'];
779
+ response.headers = hdrs;
780
+ if (hdrs['set-cookie'])
781
+ this.cookieJar.parseSetCookie(this.hostname, hdrs['set-cookie']);
782
+ response.timing.response = Date.now() - timingStart;
783
+ out.emit('header', response.statusCode, hdrs);
784
+ out.emit('headers', hdrs);
785
+ });
786
+ const chunks = [];
787
+ const rawChunks = [];
788
+ stream.on('data', (chunk) => {
789
+ // Convert string to Buffer if needed
790
+ const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
791
+ rawChunks.push(bufferChunk);
792
+ chunks.push(bufferChunk);
793
+ out.write(bufferChunk);
794
+ });
795
+ stream.once('end', async () => {
796
+ let body = Buffer.concat(chunks);
797
+ response.bytes = body.length;
798
+ response.raw = Buffer.concat(rawChunks);
799
+ if (options.decompress !== false && response.headers) {
800
+ const encodingHeader = response.headers['content-encoding'];
801
+ const encoding = Array.isArray(encodingHeader) ? encodingHeader[0]?.toLowerCase() : typeof encodingHeader === 'string' ? encodingHeader.toLowerCase() : undefined;
802
+ if (encoding) {
803
+ try {
804
+ if (encoding.includes('gzip'))
805
+ body = await gunzip(body);
806
+ else if (encoding.includes('br'))
807
+ body = await brotliDecompress(body);
808
+ else if (encoding.includes('deflate'))
809
+ body = await inflate(body);
810
+ }
811
+ catch (e) {
812
+ console.error('Decompression error:', e);
813
+ }
814
+ }
815
+ }
816
+ response.body = body;
817
+ try {
818
+ response.text = body.toString('utf-8');
819
+ const contentTypeHeader = response.headers['content-type'];
820
+ const ct = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined;
821
+ if (ct && ct.includes('application/json')) {
822
+ response.json = JSON.parse(response.text);
823
+ }
824
+ }
825
+ catch { }
826
+ response.timing.end = Date.now() - timingStart;
827
+ response.timing.total = response.timing.end;
828
+ out.end();
829
+ if (callback)
830
+ callback(null, response, response.body);
831
+ });
832
+ stream.once('error', (err) => {
833
+ out.emit('err', err);
834
+ if (callback)
835
+ callback(err);
836
+ });
837
+ if (post_data) {
838
+ if (Buffer.isBuffer(post_data) || typeof post_data === 'string')
839
+ stream.end(post_data);
840
+ else if (post_data instanceof stream_1.Readable)
841
+ post_data.pipe(stream);
135
842
  }
136
- if (options.cookies) {
137
- const manualCookies = Object.entries(options.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
138
- headers['cookie'] = headers['cookie'] ? `${headers['cookie']}; ${manualCookies}` : manualCookies;
843
+ else {
844
+ stream.end();
139
845
  }
846
+ }
847
+ async requestHttp1(path, options, timingStart, headers, post_data, out, callback) {
140
848
  const reqOptions = {
141
849
  method: options.method?.toUpperCase() || 'GET',
142
- headers,
143
- timeout: options.timeout || 30000
850
+ path,
851
+ headers: {
852
+ host: this.hostname,
853
+ ...headers
854
+ },
855
+ createConnection: () => this.tlsSocket
144
856
  };
145
- return new Promise((resolve, reject) => {
146
- const req = https.request(proxyUrl.toString(), reqOptions, async (res) => {
147
- const response = new events_1.EventEmitter();
148
- response.statusCode = res.statusCode || 500;
149
- response.headers = res.headers;
150
- response.timing = {
151
- socket: startTime,
152
- lookup: startTime,
153
- connect: startTime,
154
- secureConnect: startTime,
155
- response: Date.now() - startTime,
156
- end: 0,
157
- total: 0
158
- };
159
- response.fingerprints = { ja3: '', ja3Hash: '', akamai: '' };
160
- if (res.headers['set-cookie']) {
161
- this.cookieJar.parseSetCookie(targetUrl.hostname, res.headers['set-cookie']);
857
+ const req = https.request(reqOptions);
858
+ const response = new events_1.EventEmitter();
859
+ response.timing = { socket: timingStart, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
860
+ response.fingerprints = {
861
+ ja3: this.generateJA3(),
862
+ ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
863
+ akamai: ''
864
+ };
865
+ req.once('response', (res) => {
866
+ response.statusCode = res.statusCode;
867
+ response.headers = res.headers;
868
+ if (res.headers['set-cookie'])
869
+ this.cookieJar.parseSetCookie(this.hostname, res.headers['set-cookie']);
870
+ response.timing.response = Date.now() - timingStart;
871
+ out.emit('header', response.statusCode, res.headers);
872
+ out.emit('headers', res.headers);
873
+ const chunks = [];
874
+ const rawChunks = [];
875
+ res.on('data', (chunk) => {
876
+ // Convert string to Buffer if needed
877
+ const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
878
+ rawChunks.push(bufferChunk);
879
+ chunks.push(bufferChunk);
880
+ out.write(bufferChunk);
881
+ });
882
+ res.once('end', async () => {
883
+ let body = Buffer.concat(chunks);
884
+ response.bytes = body.length;
885
+ response.raw = Buffer.concat(rawChunks);
886
+ if (options.decompress !== false) {
887
+ const encodingHeader = res.headers['content-encoding'];
888
+ const encoding = Array.isArray(encodingHeader) ? encodingHeader[0]?.toLowerCase() : typeof encodingHeader === 'string' ? encodingHeader.toLowerCase() : undefined;
889
+ if (encoding) {
890
+ try {
891
+ if (encoding.includes('gzip'))
892
+ body = await gunzip(body);
893
+ else if (encoding.includes('br'))
894
+ body = await brotliDecompress(body);
895
+ else if (encoding.includes('deflate'))
896
+ body = await inflate(body);
897
+ }
898
+ catch (e) {
899
+ console.error('Decompression error:', e);
900
+ }
901
+ }
902
+ }
903
+ response.body = body;
904
+ try {
905
+ response.text = body.toString('utf-8');
906
+ const contentTypeHeader = res.headers['content-type'];
907
+ const ct = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined;
908
+ if (ct && ct.includes('application/json')) {
909
+ response.json = JSON.parse(response.text);
910
+ }
162
911
  }
163
- if (options.stream) {
164
- response.body = res;
165
- response.timing.end = Date.now() - startTime;
166
- response.timing.total = response.timing.end;
167
- resolve(response);
168
- return;
912
+ catch { }
913
+ response.timing.end = Date.now() - timingStart;
914
+ response.timing.total = response.timing.end;
915
+ out.end();
916
+ if (callback)
917
+ callback(null, response, response.body);
918
+ });
919
+ res.once('error', (err) => {
920
+ out.emit('err', err);
921
+ if (callback)
922
+ callback(err);
923
+ });
924
+ });
925
+ req.once('error', (err) => {
926
+ out.emit('err', err);
927
+ if (callback)
928
+ callback(err);
929
+ });
930
+ if (post_data) {
931
+ if (Buffer.isBuffer(post_data) || typeof post_data === 'string')
932
+ req.end(post_data);
933
+ else if (post_data instanceof stream_1.Readable)
934
+ post_data.pipe(req);
935
+ }
936
+ else {
937
+ req.end();
938
+ }
939
+ }
940
+ generateJA3() {
941
+ const version = '771';
942
+ const ciphers = this.profile.tls.cipherSuites.filter(c => c < 0xff00).join('-');
943
+ const extensions = this.profile.tls.extensions.filter(e => typeof e === 'number' && e < 0xff00).join('-');
944
+ const curves = this.profile.tls.supportedGroups.filter(g => g < 0xff00).join('-');
945
+ const ecPoints = this.profile.tls.ecPointFormats.join('-');
946
+ return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
947
+ }
948
+ destroy() {
949
+ if (this.http2Client && !this.http2Client.destroyed)
950
+ this.http2Client.destroy();
951
+ this.http2Client = null;
952
+ }
953
+ getProtocol() {
954
+ return this.negotiatedProtocol;
955
+ }
956
+ }
957
+ class TLSSocketManager extends events_1.EventEmitter {
958
+ constructor(profile, proxy) {
959
+ super();
960
+ this.socket = null;
961
+ this.tlsSocket = null;
962
+ this.startTime = 0;
963
+ this.hostname = '';
964
+ this.port = 443;
965
+ this.profile = profile;
966
+ this.proxy = proxy;
967
+ this.timing = { socket: 0, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
968
+ }
969
+ async connect(hostname, port = 443) {
970
+ this.hostname = hostname;
971
+ this.port = port;
972
+ this.startTime = Date.now();
973
+ return new Promise((resolve, reject) => {
974
+ const timeout = setTimeout(() => {
975
+ this.destroy();
976
+ reject(new Error('Connection timeout'));
977
+ }, 30000);
978
+ const handleError = (err) => {
979
+ clearTimeout(timeout);
980
+ this.destroy();
981
+ reject(err);
982
+ };
983
+ if (this.proxy) {
984
+ const proxyUrl = new url.URL(this.proxy);
985
+ this.socket = net.connect(+proxyUrl.port || 80, proxyUrl.hostname, () => {
986
+ let request = `CONNECT ${hostname}:${port} HTTP/1.1\r\nHost: ${hostname}:${port}\r\n`;
987
+ if (proxyUrl.username && proxyUrl.password) {
988
+ const auth = Buffer.from(`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`).toString('base64');
989
+ request += `Proxy-Authorization: Basic ${auth}\r\n`;
990
+ }
991
+ request += '\r\n';
992
+ this.socket.write(request);
993
+ });
994
+ let response = '';
995
+ this.socket.on('data', (chunk) => {
996
+ response += chunk.toString();
997
+ if (response.includes('\r\n\r\n')) {
998
+ if (!response.startsWith('HTTP/1.1 200'))
999
+ return reject(new Error('Proxy failed: ' + response.split('\r\n')[0]));
1000
+ this.proceedWithTLS(resolve, reject, timeout);
1001
+ }
1002
+ });
1003
+ }
1004
+ else {
1005
+ this.socket = new net.Socket();
1006
+ this.socket.setNoDelay(true);
1007
+ this.socket.setKeepAlive(true, 60000);
1008
+ this.socket.once('lookup', () => {
1009
+ this.timing.lookup = Date.now() - this.startTime;
1010
+ });
1011
+ this.socket.connect(port, hostname, () => {
1012
+ this.timing.connect = Date.now() - this.startTime;
1013
+ this.proceedWithTLS(resolve, reject, timeout);
1014
+ });
1015
+ }
1016
+ this.socket.on('error', handleError);
1017
+ this.socket.on('timeout', () => handleError(new Error('Socket timeout')));
1018
+ });
1019
+ }
1020
+ proceedWithTLS(resolve, reject, timeout) {
1021
+ const validCiphers = [
1022
+ 'TLS_AES_256_GCM_SHA384',
1023
+ 'TLS_CHACHA20_POLY1305_SHA256',
1024
+ 'TLS_AES_128_GCM_SHA256',
1025
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
1026
+ 'ECDHE-RSA-AES256-GCM-SHA384',
1027
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
1028
+ 'ECDHE-RSA-CHACHA20-POLY1305',
1029
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
1030
+ 'ECDHE-RSA-AES128-GCM-SHA256',
1031
+ 'ECDHE-ECDSA-AES256-SHA',
1032
+ 'ECDHE-RSA-AES256-SHA',
1033
+ 'ECDHE-ECDSA-AES128-SHA',
1034
+ 'ECDHE-RSA-AES128-SHA',
1035
+ 'AES256-GCM-SHA384',
1036
+ 'AES128-GCM-SHA256',
1037
+ 'AES256-SHA',
1038
+ 'AES128-SHA'
1039
+ ].join(':');
1040
+ const tlsOptions = {
1041
+ socket: this.socket,
1042
+ servername: this.hostname,
1043
+ ALPNProtocols: this.profile.tls.alpnProtocols,
1044
+ ciphers: validCiphers,
1045
+ minVersion: 'TLSv1.2',
1046
+ maxVersion: 'TLSv1.3',
1047
+ rejectUnauthorized: false,
1048
+ requestCert: false,
1049
+ honorCipherOrder: true,
1050
+ sessionTimeout: 300
1051
+ };
1052
+ this.tlsSocket = tls.connect(tlsOptions);
1053
+ this.tlsSocket.once('secureConnect', () => {
1054
+ clearTimeout(timeout);
1055
+ this.timing.secureConnect = Date.now() - this.startTime;
1056
+ this.timing.socket = this.startTime;
1057
+ resolve(this.tlsSocket);
1058
+ });
1059
+ this.tlsSocket.on('error', (err) => {
1060
+ clearTimeout(timeout);
1061
+ this.destroy();
1062
+ reject(err);
1063
+ });
1064
+ }
1065
+ getTiming() {
1066
+ return { ...this.timing, total: Date.now() - this.startTime };
1067
+ }
1068
+ destroy() {
1069
+ this.tlsSocket?.destroy();
1070
+ this.socket?.destroy();
1071
+ this.tlsSocket = null;
1072
+ this.socket = null;
1073
+ }
1074
+ getSocket() {
1075
+ return this.tlsSocket;
1076
+ }
1077
+ }
1078
+ class AdvancedTLSClient {
1079
+ constructor(profileName) {
1080
+ this.sessionCache = new Map();
1081
+ this.cookieJar = new CookieJar();
1082
+ this.maxCachedSessions = 10;
1083
+ this.sessionTimeout = 300000;
1084
+ this.defaults = {
1085
+ boundary: '--------------------SIPUTZXCOMPANY',
1086
+ encoding: 'utf8',
1087
+ parse_response: 'all',
1088
+ proxy: null,
1089
+ agent: null,
1090
+ headers: {},
1091
+ accept: '*/*',
1092
+ user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
1093
+ open_timeout: 10000,
1094
+ response_timeout: 0,
1095
+ read_timeout: 0,
1096
+ follow_max: 0,
1097
+ stream_length: -1,
1098
+ signal: null,
1099
+ compressed: false,
1100
+ decode_response: true,
1101
+ parse_cookies: true,
1102
+ follow_set_cookies: false,
1103
+ follow_set_referer: false,
1104
+ follow_keep_method: false,
1105
+ follow_if_same_host: false,
1106
+ follow_if_same_protocol: false,
1107
+ follow_if_same_location: false,
1108
+ use_proxy_from_env_var: true
1109
+ };
1110
+ this.profile = ProfileRegistry.get(profileName || 'chrome_133');
1111
+ this.cleanupInterval = setInterval(() => {
1112
+ const now = Date.now();
1113
+ for (const [key, session] of this.sessionCache) {
1114
+ if (now - session.lastUsed > this.sessionTimeout) {
1115
+ session.clientManager.destroy();
1116
+ session.tlsManager.destroy();
1117
+ this.sessionCache.delete(key);
169
1118
  }
170
- const chunks = [];
171
- res.on('data', chunk => chunks.push(chunk));
172
- res.on('end', async () => {
173
- let body = Buffer.concat(chunks);
174
- if (options.decompress !== false) {
175
- const encoding = (res.headers['content-encoding'] || '').toString().toLowerCase();
1119
+ }
1120
+ }, 60000);
1121
+ }
1122
+ setup(uri, options) {
1123
+ const config = {
1124
+ headers: { ...options.headers },
1125
+ proxy: options.proxy || this.defaults.proxy,
1126
+ decompress: options.decompress !== undefined ? options.decompress : true
1127
+ };
1128
+ config.headers['user-agent'] = this.profile.userAgent;
1129
+ config.headers['accept-language'] = this.profile.acceptLanguage || 'en-US,en;q=0.9';
1130
+ config.headers['accept-encoding'] = 'gzip, deflate, br';
1131
+ config.headers['accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8';
1132
+ config.headers['sec-ch-ua'] = this.profile.secChUa;
1133
+ config.headers['sec-ch-ua-mobile'] = this.profile.secChUaMobile;
1134
+ config.headers['sec-ch-ua-platform'] = this.profile.secChUaPlatform;
1135
+ config.headers['sec-fetch-site'] = this.profile.secFetchSite;
1136
+ config.headers['sec-fetch-mode'] = this.profile.secFetchMode;
1137
+ config.headers['sec-fetch-dest'] = this.profile.secFetchDest;
1138
+ if (this.profile.upgradeInsecureRequests)
1139
+ config.headers['upgrade-insecure-requests'] = this.profile.upgradeInsecureRequests;
1140
+ if (this.profile.priority)
1141
+ config.headers['priority'] = this.profile.priority;
1142
+ return config;
1143
+ }
1144
+ async request(uri, data, options = {}, callback) {
1145
+ if (typeof options === 'function') {
1146
+ callback = options;
1147
+ options = {};
1148
+ }
1149
+ const parsed = new url.URL(uri);
1150
+ const hostname = parsed.hostname;
1151
+ const port = parsed.port ? +parsed.port : 443;
1152
+ const path = parsed.pathname + parsed.search;
1153
+ const cacheKey = `${hostname}:${port}`;
1154
+ const startTime = Date.now();
1155
+ const out = new stream_1.PassThrough();
1156
+ let session = this.sessionCache.get(cacheKey);
1157
+ if (!session || session.tlsManager.getSocket()?.destroyed) {
1158
+ const tlsManager = new TLSSocketManager(this.profile, options.proxy);
1159
+ const tlsSocket = await tlsManager.connect(hostname, port);
1160
+ const clientManager = new UnifiedClientManager(tlsSocket, this.profile, hostname, this.cookieJar);
1161
+ await clientManager.initialize();
1162
+ session = { tlsManager, clientManager, lastUsed: Date.now() };
1163
+ this.sessionCache.set(cacheKey, session);
1164
+ if (this.sessionCache.size > this.maxCachedSessions) {
1165
+ const oldest = [...this.sessionCache.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];
1166
+ oldest[1].clientManager.destroy();
1167
+ oldest[1].tlsManager.destroy();
1168
+ this.sessionCache.delete(oldest[0]);
1169
+ }
1170
+ }
1171
+ session.lastUsed = Date.now();
1172
+ const config = this.setup(uri, options);
1173
+ let post_data = null;
1174
+ let json = options.json || (options.json !== false && config.headers['content-type']?.includes('application/json'));
1175
+ if (data) {
1176
+ if (options.multipart) {
1177
+ const boundary = options.boundary || this.defaults.boundary;
1178
+ post_data = await this.buildMultipart(data, boundary);
1179
+ config.headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
1180
+ }
1181
+ else if (data instanceof stream_1.Readable) {
1182
+ post_data = data;
1183
+ }
1184
+ else if (Buffer.isBuffer(data)) {
1185
+ post_data = data;
1186
+ }
1187
+ else if (typeof data === 'string') {
1188
+ post_data = data;
1189
+ }
1190
+ else if (json) {
1191
+ post_data = JSON.stringify(data);
1192
+ config.headers['content-type'] = 'application/json; charset=utf-8';
1193
+ }
1194
+ else {
1195
+ post_data = new URLSearchParams(data).toString();
1196
+ config.headers['content-type'] = 'application/x-www-form-urlencoded';
1197
+ }
1198
+ if (post_data && (typeof post_data === 'string' || Buffer.isBuffer(post_data))) {
1199
+ config.headers['content-length'] = Buffer.byteLength(post_data).toString();
1200
+ }
1201
+ }
1202
+ const cookies = this.cookieJar.getCookies(hostname, parsed.pathname, parsed.protocol === 'https:');
1203
+ if (cookies)
1204
+ config.headers['cookie'] = cookies;
1205
+ // MODE LEGACY: Jika ada callback, kembalikan stream
1206
+ if (callback) {
1207
+ await session.clientManager.request(path, options, startTime, config.headers, post_data, out, callback);
1208
+ return out;
1209
+ }
1210
+ // MODE BARU: Kembalikan object response lengkap
1211
+ return new Promise((resolve, reject) => {
1212
+ let statusCode = 0;
1213
+ let headers = {};
1214
+ const chunks = [];
1215
+ out.on('header', (status, hdrs) => {
1216
+ statusCode = status;
1217
+ headers = hdrs;
1218
+ });
1219
+ out.on('headers', (hdrs) => {
1220
+ headers = hdrs;
1221
+ });
1222
+ out.on('data', (chunk) => {
1223
+ chunks.push(chunk);
1224
+ });
1225
+ out.once('end', async () => {
1226
+ let body = Buffer.concat(chunks);
1227
+ // Auto-decompress jika diminta
1228
+ if (options.decompress !== false && headers) {
1229
+ const encodingHeader = headers['content-encoding'];
1230
+ const encoding = Array.isArray(encodingHeader)
1231
+ ? encodingHeader[0]?.toLowerCase()
1232
+ : typeof encodingHeader === 'string'
1233
+ ? encodingHeader.toLowerCase()
1234
+ : undefined;
1235
+ if (encoding) {
176
1236
  try {
177
1237
  if (encoding.includes('gzip'))
178
1238
  body = await gunzip(body);
@@ -181,55 +1241,142 @@ class FastProxiedClient {
181
1241
  else if (encoding.includes('deflate'))
182
1242
  body = await inflate(body);
183
1243
  }
184
- catch (e) { }
185
- }
186
- response.body = body;
187
- try {
188
- response.text = body.toString('utf-8');
1244
+ catch (e) {
1245
+ console.error('Decompression failed:', e);
1246
+ }
189
1247
  }
190
- catch { }
1248
+ }
1249
+ const text = body.toString('utf-8');
1250
+ let parsedJson = undefined;
1251
+ const contentType = headers['content-type'];
1252
+ const ct = Array.isArray(contentType)
1253
+ ? contentType[0]
1254
+ : typeof contentType === 'string'
1255
+ ? contentType
1256
+ : undefined;
1257
+ if (ct && ct.includes('application/json')) {
191
1258
  try {
192
- if ((res.headers['content-type'] || '').includes('application/json')) {
193
- response.json = JSON.parse(response.text);
194
- }
1259
+ parsedJson = JSON.parse(text);
195
1260
  }
196
1261
  catch { }
197
- response.timing.end = Date.now() - startTime;
198
- response.timing.total = response.timing.end;
199
- if (options.followRedirects !== false &&
200
- [301, 302, 303, 307, 308].includes(response.statusCode) &&
201
- (options.maxRedirects ?? 10) > 0) {
202
- const location = res.headers['location'];
203
- if (location) {
204
- const newUrl = new url_1.URL(location, url).toString();
205
- const newOptions = { ...options, maxRedirects: (options.maxRedirects ?? 10) - 1 };
206
- return this.request(newUrl, newOptions).then(resolve).catch(reject);
207
- }
1262
+ }
1263
+ // Generate fingerprints
1264
+ const ja3 = this.generateJA3();
1265
+ const ja3Hash = crypto.createHash('md5').update(ja3).digest('hex');
1266
+ const akamai = `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`;
1267
+ const fullResponse = {
1268
+ statusCode,
1269
+ headers,
1270
+ body,
1271
+ text,
1272
+ json: parsedJson,
1273
+ fingerprints: {
1274
+ ja3,
1275
+ ja3Hash,
1276
+ akamai
1277
+ },
1278
+ timing: {
1279
+ socket: startTime,
1280
+ lookup: 0,
1281
+ connect: 0,
1282
+ secureConnect: 0,
1283
+ response: Date.now() - startTime,
1284
+ end: Date.now() - startTime,
1285
+ total: Date.now() - startTime
208
1286
  }
209
- resolve(response);
210
- });
211
- res.on('error', reject);
212
- });
213
- req.on('error', reject);
214
- req.on('timeout', () => {
215
- req.destroy();
216
- reject(new Error('Request timeout'));
1287
+ };
1288
+ resolve(fullResponse);
217
1289
  });
218
- if (options.body) {
219
- if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {
220
- req.end(options.body);
1290
+ out.once('error', (err) => reject(err));
1291
+ // Jalankan request
1292
+ session.clientManager.request(path, options, startTime, config.headers, post_data, out).catch(reject);
1293
+ });
1294
+ }
1295
+ generateJA3() {
1296
+ const version = '771';
1297
+ const ciphers = this.profile.tls.cipherSuites.filter(c => c < 0xff00).join('-');
1298
+ const extensions = this.profile.tls.extensions.filter(e => typeof e === 'number' && e < 0xff00).join('-');
1299
+ const curves = this.profile.tls.supportedGroups.filter(g => g < 0xff00).join('-');
1300
+ const ecPoints = this.profile.tls.ecPointFormats.join('-');
1301
+ return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
1302
+ }
1303
+ async buildMultipart(data, boundary) {
1304
+ return new Promise((resolve, reject) => {
1305
+ let body = '';
1306
+ const object = this.flatten(data);
1307
+ const count = Object.keys(object).length;
1308
+ if (count === 0)
1309
+ return reject(new Error('Empty multipart body'));
1310
+ let doneCount = count;
1311
+ const done = () => {
1312
+ if (--doneCount === 0)
1313
+ resolve(Buffer.from(body + '--' + boundary + '--\r\n'));
1314
+ };
1315
+ for (const key in object) {
1316
+ const value = object[key];
1317
+ if (value === null || typeof value === 'undefined') {
1318
+ done();
1319
+ continue;
221
1320
  }
222
- else if (options.body instanceof stream_1.Readable) {
223
- options.body.pipe(req);
1321
+ if (Buffer.isBuffer(value)) {
1322
+ const part = { buffer: value, content_type: 'application/octet-stream' };
1323
+ this.generateMultipart(key, part, boundary).then(section => {
1324
+ body += section;
1325
+ done();
1326
+ });
1327
+ }
1328
+ else {
1329
+ const part = (value.buffer || value.file || value.content_type) ? value : { value: value };
1330
+ this.generateMultipart(key, part, boundary).then(section => {
1331
+ body += section;
1332
+ done();
1333
+ });
224
1334
  }
225
- }
226
- else {
227
- req.end();
228
1335
  }
229
1336
  });
230
1337
  }
1338
+ async generateMultipart(name, part, boundary) {
1339
+ let return_part = '--' + boundary + '\r\n';
1340
+ return_part += 'Content-Disposition: form-data; name="' + name + '"';
1341
+ const append = (data, filename) => {
1342
+ if (data) {
1343
+ return_part += '; filename="' + encodeURIComponent(filename) + '"\r\n';
1344
+ return_part += 'Content-Type: ' + (part.content_type || 'application/octet-stream') + '\r\n\r\n';
1345
+ return_part += data.toString('binary');
1346
+ }
1347
+ return return_part + '\r\n';
1348
+ };
1349
+ if ((part.file || part.buffer) && part.content_type) {
1350
+ const filename = part.filename || (part.file ? path.basename(part.file) : name);
1351
+ if (part.buffer)
1352
+ return append(part.buffer, filename);
1353
+ const data = await fs.promises.readFile(part.file);
1354
+ return append(data, filename);
1355
+ }
1356
+ else {
1357
+ return_part += '\r\n\r\n';
1358
+ return_part += String(part.value || '');
1359
+ return return_part + '\r\n';
1360
+ }
1361
+ }
1362
+ flatten(object, into = {}, prefix) {
1363
+ for (const key in object) {
1364
+ const prefix_key = prefix ? prefix + '[' + key + ']' : key;
1365
+ const prop = object[key];
1366
+ if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type))
1367
+ this.flatten(prop, into, prefix_key);
1368
+ else
1369
+ into[prefix_key] = prop;
1370
+ }
1371
+ return into;
1372
+ }
231
1373
  destroy() {
232
- this.cookieJar.clear();
1374
+ clearInterval(this.cleanupInterval);
1375
+ this.sessionCache.forEach(s => {
1376
+ s.clientManager.destroy();
1377
+ s.tlsManager.destroy();
1378
+ });
1379
+ this.sessionCache.clear();
233
1380
  }
234
1381
  }
235
- exports.FastProxiedClient = FastProxiedClient;
1382
+ exports.AdvancedTLSClient = AdvancedTLSClient;