advanced-tls-client 1.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.
package/dist/index.js CHANGED
@@ -33,16 +33,22 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.AdvancedTLSClient = void 0;
36
+ exports.ProfileRegistry = exports.AdvancedTLSClient = void 0;
37
37
  const tls = __importStar(require("tls"));
38
38
  const net = __importStar(require("net"));
39
+ const https = __importStar(require("https"));
39
40
  const http2 = __importStar(require("http2"));
40
41
  const crypto = __importStar(require("crypto"));
41
42
  const events_1 = require("events");
42
43
  const zlib = __importStar(require("zlib"));
43
44
  const util_1 = require("util");
45
+ const stream_1 = require("stream");
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const url = __importStar(require("url"));
44
49
  const gunzip = (0, util_1.promisify)(zlib.gunzip);
45
50
  const brotliDecompress = (0, util_1.promisify)(zlib.brotliDecompress);
51
+ const inflate = (0, util_1.promisify)(zlib.inflate);
46
52
  const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
47
53
  class ProfileRegistry {
48
54
  static getGrease() {
@@ -58,72 +64,72 @@ class ProfileRegistry {
58
64
  version: '143.0.0.0',
59
65
  tls: {
60
66
  cipherSuites: [
61
- `GREASE_${grease1.toString(16)}`,
62
- 'TLS_AES_128_GCM_SHA256',
63
- 'TLS_AES_256_GCM_SHA384',
64
- 'TLS_CHACHA20_POLY1305_SHA256',
65
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
66
- 'ECDHE-RSA-AES128-GCM-SHA256',
67
- 'ECDHE-ECDSA-AES256-GCM-SHA384',
68
- 'ECDHE-RSA-AES256-GCM-SHA384',
69
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
70
- 'ECDHE-RSA-CHACHA20-POLY1305',
71
- 'ECDHE-RSA-AES128-SHA',
72
- 'ECDHE-RSA-AES256-SHA',
73
- 'AES128-GCM-SHA256',
74
- 'AES256-GCM-SHA384',
75
- 'AES128-SHA',
76
- 'AES256-SHA'
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
77
83
  ],
78
84
  extensions: [
79
85
  grease2,
80
- 0xfe0d,
81
- 0x001b,
82
- 0x002b,
83
- 0x0010,
84
- 0x0023,
85
- 0x002d,
86
- 0x000b,
87
- 0xff01,
88
- 0x0005,
89
- 0x000a,
90
- 0x0017,
91
- 0x0000,
92
- 0x44cd,
93
- 0x0033,
94
- 0x0012,
95
- 0x000d,
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,
96
102
  grease3
97
103
  ],
98
104
  supportedGroups: [
99
- `GREASE_${grease4.toString(16)}`,
100
- 'X25519Kyber768',
101
- 'X25519',
102
- 'prime256v1',
103
- 'secp384r1'
105
+ grease4,
106
+ 25497,
107
+ 29,
108
+ 23,
109
+ 24
104
110
  ],
105
111
  signatureAlgorithms: [
106
- 'ecdsa_secp256r1_sha256',
107
- 'rsa_pss_rsae_sha256',
108
- 'rsa_pkcs1_sha256',
109
- 'ecdsa_secp384r1_sha384',
110
- 'rsa_pss_rsae_sha384',
111
- 'rsa_pkcs1_sha384',
112
- 'rsa_pss_rsae_sha512',
113
- 'rsa_pkcs1_sha512'
112
+ 1027,
113
+ 2052,
114
+ 1025,
115
+ 1283,
116
+ 2053,
117
+ 1281,
118
+ 2054,
119
+ 1537
114
120
  ],
115
121
  supportedVersions: [
116
- `GREASE_${grease2.toString(16)}`,
117
- 'TLSv1.3',
118
- 'TLSv1.2'
122
+ grease2,
123
+ 772,
124
+ 771
119
125
  ],
120
- ecPointFormats: [0x00],
126
+ ecPointFormats: [0],
121
127
  alpnProtocols: ['h2', 'http/1.1'],
122
- pskKeyExchangeModes: [0x01],
123
- compressionMethods: [0x00],
128
+ pskKeyExchangeModes: [1],
129
+ compressionMethods: [0],
124
130
  grease: true,
125
131
  recordSizeLimit: 16385,
126
- certificateCompression: [0x02],
132
+ certificateCompression: [2],
127
133
  ocspStapling: true,
128
134
  signedCertificateTimestamp: true
129
135
  },
@@ -132,8 +138,8 @@ class ProfileRegistry {
132
138
  headerTableSize: 65536,
133
139
  enablePush: 0,
134
140
  initialWindowSize: 6291456,
135
- maxHeaderListSize: 262144,
136
141
  maxFrameSize: 16384,
142
+ maxHeaderListSize: 262144,
137
143
  settingsOrder: [1, 2, 4, 6],
138
144
  connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
139
145
  priorityFrames: [
@@ -182,68 +188,68 @@ class ProfileRegistry {
182
188
  version: '133.0.0.0',
183
189
  tls: {
184
190
  cipherSuites: [
185
- `GREASE_${grease1.toString(16)}`,
186
- 'TLS_AES_128_GCM_SHA256',
187
- 'TLS_AES_256_GCM_SHA384',
188
- 'TLS_CHACHA20_POLY1305_SHA256',
189
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
190
- 'ECDHE-RSA-AES128-GCM-SHA256',
191
- 'ECDHE-ECDSA-AES256-GCM-SHA384',
192
- 'ECDHE-RSA-AES256-GCM-SHA384',
193
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
194
- 'ECDHE-RSA-CHACHA20-POLY1305',
195
- 'ECDHE-RSA-AES128-SHA',
196
- 'ECDHE-RSA-AES256-SHA',
197
- 'AES128-GCM-SHA256',
198
- 'AES256-GCM-SHA384',
199
- 'AES128-SHA',
200
- 'AES256-SHA'
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
201
207
  ],
202
208
  extensions: [
203
209
  grease2,
204
- 0x0000,
205
- 0x0017,
206
- 0xff01,
207
- 0x000a,
208
- 0x000b,
209
- 0x0023,
210
- 0x0010,
211
- 0x0005,
212
- 0x000d,
213
- 0x0012,
214
- 0x0033,
215
- 0x002d,
216
- 0x002b,
217
- 0x001b,
218
- 0x0015,
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,
219
225
  grease3
220
226
  ],
221
227
  supportedGroups: [
222
- `GREASE_${grease4.toString(16)}`,
223
- 'X25519Kyber768',
224
- 'X25519',
225
- 'prime256v1',
226
- 'secp384r1'
228
+ grease4,
229
+ 25497,
230
+ 29,
231
+ 23,
232
+ 24
227
233
  ],
228
234
  signatureAlgorithms: [
229
- 'ecdsa_secp256r1_sha256',
230
- 'rsa_pss_rsae_sha256',
231
- 'rsa_pkcs1_sha256',
232
- 'ecdsa_secp384r1_sha384',
233
- 'rsa_pss_rsae_sha384',
234
- 'rsa_pkcs1_sha384',
235
- 'rsa_pss_rsae_sha512',
236
- 'rsa_pkcs1_sha512'
235
+ 1027,
236
+ 2052,
237
+ 1025,
238
+ 1283,
239
+ 2053,
240
+ 1281,
241
+ 2054,
242
+ 1537
237
243
  ],
238
244
  supportedVersions: [
239
- `GREASE_${grease2.toString(16)}`,
240
- 'TLSv1.3',
241
- 'TLSv1.2'
245
+ grease2,
246
+ 772,
247
+ 771
242
248
  ],
243
- ecPointFormats: [0x00],
249
+ ecPointFormats: [0],
244
250
  alpnProtocols: ['h2', 'http/1.1'],
245
- pskKeyExchangeModes: [0x01],
246
- compressionMethods: [0x00],
251
+ pskKeyExchangeModes: [1],
252
+ compressionMethods: [0],
247
253
  grease: true,
248
254
  recordSizeLimit: 16385
249
255
  },
@@ -300,68 +306,68 @@ class ProfileRegistry {
300
306
  version: '133.0.0.0',
301
307
  tls: {
302
308
  cipherSuites: [
303
- `GREASE_${grease1.toString(16)}`,
304
- 'TLS_AES_128_GCM_SHA256',
305
- 'TLS_AES_256_GCM_SHA384',
306
- 'TLS_CHACHA20_POLY1305_SHA256',
307
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
308
- 'ECDHE-RSA-AES128-GCM-SHA256',
309
- 'ECDHE-ECDSA-AES256-GCM-SHA384',
310
- 'ECDHE-RSA-AES256-GCM-SHA384',
311
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
312
- 'ECDHE-RSA-CHACHA20-POLY1305',
313
- 'ECDHE-RSA-AES128-SHA',
314
- 'ECDHE-RSA-AES256-SHA',
315
- 'AES128-GCM-SHA256',
316
- 'AES256-GCM-SHA384',
317
- 'AES128-SHA',
318
- 'AES256-SHA'
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
319
325
  ],
320
326
  extensions: [
321
327
  grease2,
322
- 0x0000,
323
- 0x0017,
324
- 0xff01,
325
- 0x000a,
326
- 0x000b,
327
- 0x0023,
328
- 0x0010,
329
- 0x0005,
330
- 0x000d,
331
- 0x0012,
332
- 0x0033,
333
- 0x002d,
334
- 0x002b,
335
- 0x001b,
336
- 0x0015,
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,
337
343
  grease3
338
344
  ],
339
345
  supportedGroups: [
340
- `GREASE_${grease4.toString(16)}`,
341
- 'X25519Kyber768',
342
- 'X25519',
343
- 'prime256v1',
344
- 'secp384r1'
346
+ grease4,
347
+ 25497,
348
+ 29,
349
+ 23,
350
+ 24
345
351
  ],
346
352
  signatureAlgorithms: [
347
- 'ecdsa_secp256r1_sha256',
348
- 'rsa_pss_rsae_sha256',
349
- 'rsa_pkcs1_sha256',
350
- 'ecdsa_secp384r1_sha384',
351
- 'rsa_pss_rsae_sha384',
352
- 'rsa_pkcs1_sha384',
353
- 'rsa_pss_rsae_sha512',
354
- 'rsa_pkcs1_sha512'
353
+ 1027,
354
+ 2052,
355
+ 1025,
356
+ 1283,
357
+ 2053,
358
+ 1281,
359
+ 2054,
360
+ 1537
355
361
  ],
356
362
  supportedVersions: [
357
- `GREASE_${grease2.toString(16)}`,
358
- 'TLSv1.3',
359
- 'TLSv1.2'
363
+ grease2,
364
+ 772,
365
+ 771
360
366
  ],
361
- ecPointFormats: [0x00],
367
+ ecPointFormats: [0],
362
368
  alpnProtocols: ['h2', 'http/1.1'],
363
- pskKeyExchangeModes: [0x01],
364
- compressionMethods: [0x00],
369
+ pskKeyExchangeModes: [1],
370
+ compressionMethods: [0],
365
371
  grease: true,
366
372
  recordSizeLimit: 16385
367
373
  },
@@ -414,44 +420,54 @@ class ProfileRegistry {
414
420
  version: '117.0',
415
421
  tls: {
416
422
  cipherSuites: [
417
- 'TLS_AES_128_GCM_SHA256',
418
- 'TLS_CHACHA20_POLY1305_SHA256',
419
- 'TLS_AES_256_GCM_SHA384',
420
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
421
- 'ECDHE-RSA-AES128-GCM-SHA256',
422
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
423
- 'ECDHE-RSA-CHACHA20-POLY1305',
424
- 'ECDHE-ECDSA-AES256-GCM-SHA384',
425
- 'ECDHE-RSA-AES256-GCM-SHA384',
426
- 'ECDHE-ECDSA-AES256-SHA',
427
- 'ECDHE-ECDSA-AES128-SHA',
428
- 'ECDHE-RSA-AES128-SHA',
429
- 'ECDHE-RSA-AES256-SHA',
430
- 'AES128-GCM-SHA256',
431
- 'AES256-GCM-SHA384',
432
- 'AES128-SHA',
433
- 'AES256-SHA'
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
434
449
  ],
435
- extensions: [0x0000, 0x0017, 0xff01, 0x000a, 0x000b, 0x0023, 0x0010, 0x0005, 0x0022, 0x0033, 0x002b, 0x000d, 0x002d, 0x0029, 0x0015],
436
- supportedGroups: ['X25519', 'prime256v1', 'secp384r1', 'secp521r1', 'ffdhe2048', 'ffdhe3072'],
437
450
  signatureAlgorithms: [
438
- 'ecdsa_secp256r1_sha256',
439
- 'ecdsa_secp384r1_sha384',
440
- 'ecdsa_secp521r1_sha512',
441
- 'rsa_pss_rsae_sha256',
442
- 'rsa_pss_rsae_sha384',
443
- 'rsa_pss_rsae_sha512',
444
- 'rsa_pkcs1_sha256',
445
- 'rsa_pkcs1_sha384',
446
- 'rsa_pkcs1_sha512',
447
- 'ecdsa_sha1',
448
- 'rsa_pkcs1_sha1'
451
+ 1027,
452
+ 1283,
453
+ 1539,
454
+ 2052,
455
+ 2053,
456
+ 2054,
457
+ 1025,
458
+ 1281,
459
+ 1537,
460
+ 513,
461
+ 515
449
462
  ],
450
- supportedVersions: ['TLSv1.3', 'TLSv1.2'],
451
- ecPointFormats: [0x00],
463
+ supportedVersions: [
464
+ 772,
465
+ 771
466
+ ],
467
+ ecPointFormats: [0],
452
468
  alpnProtocols: ['h2', 'http/1.1'],
453
- pskKeyExchangeModes: [0x01],
454
- compressionMethods: [0x00],
469
+ pskKeyExchangeModes: [1],
470
+ compressionMethods: [0],
455
471
  grease: false
456
472
  },
457
473
  http2: {
@@ -471,7 +487,8 @@ class ProfileRegistry {
471
487
  { streamId: 13, weight: 240, dependency: 0, exclusive: false }
472
488
  ],
473
489
  pseudoHeaderOrder: [':method', ':path', ':authority', ':scheme'],
474
- headerPriority: { streamDep: 13, exclusive: false, weight: 41 }
490
+ headerPriority: { streamDep: 13, exclusive: false, weight: 41 },
491
+ headerOrder: []
475
492
  },
476
493
  userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0',
477
494
  secChUa: '',
@@ -490,63 +507,73 @@ class ProfileRegistry {
490
507
  version: '18.0',
491
508
  tls: {
492
509
  cipherSuites: [
493
- `GREASE_${grease1.toString(16)}`,
494
- 'TLS_AES_128_GCM_SHA256',
495
- 'TLS_AES_256_GCM_SHA384',
496
- 'TLS_CHACHA20_POLY1305_SHA256',
497
- 'ECDHE-ECDSA-AES256-GCM-SHA384',
498
- 'ECDHE-ECDSA-AES128-GCM-SHA256',
499
- 'ECDHE-ECDSA-CHACHA20-POLY1305',
500
- 'ECDHE-RSA-AES256-GCM-SHA384',
501
- 'ECDHE-RSA-AES128-GCM-SHA256',
502
- 'ECDHE-RSA-CHACHA20-POLY1305',
503
- 'ECDHE-ECDSA-AES256-SHA',
504
- 'ECDHE-ECDSA-AES128-SHA',
505
- 'ECDHE-RSA-AES256-SHA',
506
- 'ECDHE-RSA-AES128-SHA',
507
- 'AES256-GCM-SHA384',
508
- 'AES128-GCM-SHA256',
509
- 'AES256-SHA',
510
- 'AES128-SHA',
511
- 'ECDHE-ECDSA-DES-CBC3-SHA',
512
- 'ECDHE-RSA-DES-CBC3-SHA',
513
- 'DES-CBC3-SHA'
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
514
531
  ],
515
532
  extensions: [
516
533
  grease2,
517
- 0x0000,
518
- 0x0017,
519
- 0xff01,
520
- 0x000a,
521
- 0x000b,
522
- 0x0010,
523
- 0x0005,
524
- 0x000d,
525
- 0x0012,
526
- 0x0033,
527
- 0x002d,
528
- 0x002b,
529
- 0x0015
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
530
553
  ],
531
- supportedGroups: ['X25519', 'prime256v1', 'secp384r1', 'secp521r1'],
532
554
  signatureAlgorithms: [
533
- 'ecdsa_secp256r1_sha256',
534
- 'rsa_pss_rsae_sha256',
535
- 'rsa_pkcs1_sha256',
536
- 'ecdsa_secp384r1_sha384',
537
- 'ecdsa_sha1',
538
- 'rsa_pss_rsae_sha384',
539
- 'rsa_pss_rsae_sha384',
540
- 'rsa_pkcs1_sha384',
541
- 'rsa_pss_rsae_sha512',
542
- 'rsa_pkcs1_sha512',
543
- 'rsa_pkcs1_sha1'
555
+ 1027,
556
+ 2052,
557
+ 1025,
558
+ 1283,
559
+ 513,
560
+ 2053,
561
+ 2053,
562
+ 1281,
563
+ 2054,
564
+ 1537,
565
+ 515
544
566
  ],
545
- supportedVersions: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1.0'],
546
- ecPointFormats: [0x00],
567
+ supportedVersions: [
568
+ 772,
569
+ 771,
570
+ 770,
571
+ 769
572
+ ],
573
+ ecPointFormats: [0],
547
574
  alpnProtocols: ['h2', 'http/1.1'],
548
- pskKeyExchangeModes: [0x01],
549
- compressionMethods: [0x00],
575
+ pskKeyExchangeModes: [1],
576
+ compressionMethods: [0],
550
577
  grease: true
551
578
  },
552
579
  http2: {
@@ -559,7 +586,8 @@ class ProfileRegistry {
559
586
  settingsOrder: [2, 3, 4, 8, 9],
560
587
  connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
561
588
  priorityFrames: [],
562
- pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path']
589
+ pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path'],
590
+ headerOrder: []
563
591
  },
564
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',
565
593
  secChUa: '',
@@ -587,6 +615,7 @@ class ProfileRegistry {
587
615
  return this.chromeMobile143Android();
588
616
  }
589
617
  }
618
+ exports.ProfileRegistry = ProfileRegistry;
590
619
  class CookieJar {
591
620
  constructor() {
592
621
  this.cookies = new Map();
@@ -594,9 +623,9 @@ class CookieJar {
594
623
  setCookie(domain, name, value, options = {}) {
595
624
  if (!this.cookies.has(domain))
596
625
  this.cookies.set(domain, new Map());
597
- this.cookies.get(domain).set(name, { value, expires: options.expires, path: options.path || '/', domain });
626
+ this.cookies.get(domain).set(name, { value, ...options, path: options.path || '/', domain });
598
627
  }
599
- getCookies(domain, path = '/') {
628
+ getCookies(domain, path = '/', secure = true) {
600
629
  const domainCookies = this.cookies.get(domain);
601
630
  if (!domainCookies)
602
631
  return '';
@@ -607,266 +636,443 @@ class CookieJar {
607
636
  domainCookies.delete(name);
608
637
  continue;
609
638
  }
639
+ if (cookie.secure && !secure)
640
+ continue;
610
641
  if (path.startsWith(cookie.path))
611
642
  validCookies.push(`${name}=${cookie.value}`);
612
643
  }
613
644
  return validCookies.join('; ');
614
645
  }
615
646
  parseSetCookie(domain, setCookieHeaders) {
616
- for (const header of setCookieHeaders) {
647
+ if (!setCookieHeaders)
648
+ return;
649
+ const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : typeof setCookieHeaders === 'string' ? [setCookieHeaders] : [];
650
+ for (const header of headers) {
617
651
  const parts = header.split(';').map(p => p.trim());
618
652
  const [nameValue] = parts;
619
- const [name, value] = nameValue.split('=');
653
+ if (!nameValue)
654
+ continue;
655
+ const [name, value = ''] = nameValue.split('=');
620
656
  const options = { path: '/' };
621
657
  for (let i = 1; i < parts.length; i++) {
622
- const [key, val] = parts[i].split('=');
623
- if (key.toLowerCase() === 'expires')
658
+ const part = parts[i];
659
+ if (!part)
660
+ continue;
661
+ const [key, val] = part.split('=');
662
+ const lowerKey = key.toLowerCase();
663
+ if (lowerKey === 'expires' && val)
624
664
  options.expires = new Date(val);
625
- else if (key.toLowerCase() === 'path')
665
+ else if (lowerKey === 'path' && val)
626
666
  options.path = val;
667
+ else if (lowerKey === 'secure')
668
+ options.secure = true;
669
+ else if (lowerKey === 'httponly')
670
+ options.httpOnly = true;
671
+ else if (lowerKey === 'samesite' && val)
672
+ options.sameSite = val;
627
673
  }
628
674
  this.setCookie(domain, name, value, options);
629
675
  }
630
676
  }
631
677
  }
632
- class TLSSocketManager extends events_1.EventEmitter {
633
- constructor(profile) {
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) {
634
685
  super();
635
- this.socket = null;
636
- this.tlsSocket = null;
637
- this.startTime = 0;
638
- this.hostname = '';
686
+ this.http2Client = null;
687
+ this.negotiatedProtocol = Protocol.HTTP1;
688
+ this.tlsSocket = tlsSocket;
639
689
  this.profile = profile;
640
- this.timing = { socket: 0, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
641
- }
642
- convertCipherSuite(suite) {
643
- const cipherMap = {
644
- 'TLS_AES_128_GCM_SHA256': 'TLS_AES_128_GCM_SHA256',
645
- 'TLS_AES_256_GCM_SHA384': 'TLS_AES_256_GCM_SHA384',
646
- 'TLS_CHACHA20_POLY1305_SHA256': 'TLS_CHACHA20_POLY1305_SHA256',
647
- 'ECDHE-ECDSA-AES128-GCM-SHA256': 'ECDHE-ECDSA-AES128-GCM-SHA256',
648
- 'ECDHE-RSA-AES128-GCM-SHA256': 'ECDHE-RSA-AES128-GCM-SHA256',
649
- 'ECDHE-ECDSA-AES256-GCM-SHA384': 'ECDHE-ECDSA-AES256-GCM-SHA384',
650
- 'ECDHE-RSA-AES256-GCM-SHA384': 'ECDHE-RSA-AES256-GCM-SHA384',
651
- 'ECDHE-ECDSA-CHACHA20-POLY1305': 'ECDHE-ECDSA-CHACHA20-POLY1305',
652
- 'ECDHE-RSA-CHACHA20-POLY1305': 'ECDHE-RSA-CHACHA20-POLY1305',
653
- 'ECDHE-RSA-AES128-SHA': 'ECDHE-RSA-AES128-SHA',
654
- 'ECDHE-RSA-AES256-SHA': 'ECDHE-RSA-AES256-SHA',
655
- 'AES128-GCM-SHA256': 'AES128-GCM-SHA256',
656
- 'AES256-GCM-SHA384': 'AES256-GCM-SHA384',
657
- 'AES128-SHA': 'AES128-SHA',
658
- 'AES256-SHA': 'AES256-SHA'
659
- };
660
- if (suite.startsWith('GREASE_'))
661
- return '';
662
- return cipherMap[suite] || suite;
663
- }
664
- convertSupportedGroup(group) {
665
- const groupMap = {
666
- 'X25519Kyber768': 'X25519',
667
- 'X25519': 'X25519',
668
- 'prime256v1': 'prime256v1',
669
- 'secp384r1': 'secp384r1'
670
- };
671
- if (group.startsWith('GREASE_'))
672
- return '';
673
- return groupMap[group] || group;
674
- }
675
- async connect(hostname, port) {
676
690
  this.hostname = hostname;
677
- this.startTime = Date.now();
678
- return new Promise((resolve, reject) => {
679
- const timeout = setTimeout(() => {
680
- this.destroy();
681
- reject(new Error('Connection timeout'));
682
- }, 30000);
683
- this.socket = new net.Socket();
684
- this.socket.setKeepAlive(true, 60000);
685
- this.socket.setNoDelay(true);
686
- this.timing.socket = Date.now() - this.startTime;
687
- this.socket.on('lookup', () => {
688
- this.timing.lookup = Date.now() - this.startTime;
689
- });
690
- this.socket.connect(port, hostname, () => {
691
- this.timing.connect = Date.now() - this.startTime;
692
- const ciphers = this.profile.tls.cipherSuites
693
- .map(suite => this.convertCipherSuite(suite))
694
- .filter(suite => suite !== '')
695
- .join(':');
696
- const curves = this.profile.tls.supportedGroups
697
- .map(group => this.convertSupportedGroup(group))
698
- .filter(group => group !== '')
699
- .join(':');
700
- const tlsOptions = {
701
- socket: this.socket,
702
- servername: hostname,
703
- ALPNProtocols: this.profile.tls.alpnProtocols,
704
- ciphers: ciphers,
705
- minVersion: 'TLSv1.2',
706
- maxVersion: 'TLSv1.3',
707
- ecdhCurve: curves,
708
- sigalgs: this.profile.tls.signatureAlgorithms.join(':'),
709
- rejectUnauthorized: false,
710
- requestCert: false,
711
- honorCipherOrder: true,
712
- sessionTimeout: 300
713
- };
714
- this.tlsSocket = tls.connect(tlsOptions);
715
- this.tlsSocket.on('secureConnect', () => {
716
- clearTimeout(timeout);
717
- this.timing.secureConnect = Date.now() - this.startTime;
718
- resolve(this.tlsSocket);
719
- });
720
- this.tlsSocket.on('error', (err) => {
721
- clearTimeout(timeout);
722
- this.destroy();
723
- reject(err);
724
- });
725
- });
726
- this.socket.on('error', (err) => {
727
- clearTimeout(timeout);
728
- this.destroy();
729
- reject(err);
730
- });
731
- this.socket.on('timeout', () => {
732
- clearTimeout(timeout);
733
- this.destroy();
734
- reject(new Error('Socket timeout'));
735
- });
736
- });
691
+ this.cookieJar = cookieJar;
737
692
  }
738
- getTiming() {
739
- return { ...this.timing, total: Date.now() - this.startTime };
740
- }
741
- destroy() {
742
- if (this.tlsSocket && !this.tlsSocket.destroyed) {
743
- this.tlsSocket.removeAllListeners();
744
- this.tlsSocket.destroy();
745
- this.tlsSocket = null;
693
+ async initialize() {
694
+ const alpn = this.tlsSocket.alpnProtocol;
695
+ if (alpn === 'h2') {
696
+ this.negotiatedProtocol = Protocol.HTTP2;
697
+ await this.createHttp2Session();
746
698
  }
747
- if (this.socket && !this.socket.destroyed) {
748
- this.socket.removeAllListeners();
749
- this.socket.destroy();
750
- this.socket = null;
699
+ else {
700
+ this.negotiatedProtocol = Protocol.HTTP1;
751
701
  }
752
702
  }
753
- isConnected() {
754
- return this.tlsSocket !== null && !this.tlsSocket.destroyed;
755
- }
756
- getSocket() {
757
- return this.tlsSocket;
758
- }
759
- getHostname() {
760
- return this.hostname;
761
- }
762
- }
763
- class HTTP2ClientManager {
764
- constructor(tlsSocket, profile, hostname) {
765
- this.client = null;
766
- this.tlsSocket = tlsSocket;
767
- this.profile = profile;
768
- this.hostname = hostname;
769
- }
770
- async createSession() {
703
+ async createHttp2Session() {
771
704
  return new Promise((resolve, reject) => {
772
- const settingsMap = {};
773
- this.profile.http2.settingsOrder.forEach(settingId => {
774
- switch (settingId) {
705
+ const settings = {};
706
+ this.profile.http2.settingsOrder.forEach(id => {
707
+ switch (id) {
775
708
  case 1:
776
- settingsMap.headerTableSize = this.profile.http2.headerTableSize;
709
+ settings.headerTableSize = this.profile.http2.headerTableSize;
777
710
  break;
778
711
  case 2:
779
- settingsMap.enablePush = this.profile.http2.enablePush === 1;
712
+ settings.enablePush = this.profile.http2.enablePush === 1;
780
713
  break;
781
714
  case 3:
782
- if (this.profile.http2.maxConcurrentStreams !== undefined) {
783
- settingsMap.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
784
- }
715
+ if (this.profile.http2.maxConcurrentStreams)
716
+ settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
785
717
  break;
786
718
  case 4:
787
- settingsMap.initialWindowSize = this.profile.http2.initialWindowSize;
719
+ settings.initialWindowSize = this.profile.http2.initialWindowSize;
788
720
  break;
789
721
  case 5:
790
- if (this.profile.http2.maxFrameSize !== undefined) {
791
- settingsMap.maxFrameSize = this.profile.http2.maxFrameSize;
792
- }
722
+ if (this.profile.http2.maxFrameSize)
723
+ settings.maxFrameSize = this.profile.http2.maxFrameSize;
793
724
  break;
794
725
  case 6:
795
- if (this.profile.http2.maxHeaderListSize !== undefined) {
796
- settingsMap.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
797
- }
726
+ if (this.profile.http2.maxHeaderListSize)
727
+ settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
798
728
  break;
799
729
  }
800
730
  });
801
- const settings = {
731
+ this.http2Client = http2.connect(`https://${this.hostname}`, {
802
732
  createConnection: () => this.tlsSocket,
803
- settings: settingsMap
804
- };
805
- this.client = http2.connect(`https://${this.hostname}`, settings);
806
- this.client.on('connect', () => {
807
- resolve(this.client);
733
+ settings
808
734
  });
809
- this.client.on('error', reject);
735
+ this.http2Client.once('connect', () => resolve());
736
+ this.http2Client.once('error', reject);
737
+ this.http2Client.once('timeout', () => reject(new Error('HTTP/2 session timeout')));
810
738
  });
811
739
  }
812
- request(path, headers) {
813
- if (!this.client)
814
- throw new Error('HTTP2 session not established');
815
- const orderedHeaders = {};
816
- this.profile.http2.pseudoHeaderOrder.forEach(pseudo => {
817
- if (headers[pseudo]) {
818
- orderedHeaders[pseudo] = headers[pseudo];
819
- }
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`
776
+ };
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);
820
794
  });
821
- if (this.profile.http2.headerOrder) {
822
- this.profile.http2.headerOrder.forEach(headerName => {
823
- if (headers[headerName] && !headerName.startsWith(':')) {
824
- orderedHeaders[headerName] = headers[headerName];
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);
825
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);
842
+ }
843
+ else {
844
+ stream.end();
845
+ }
846
+ }
847
+ async requestHttp1(path, options, timingStart, headers, post_data, out, callback) {
848
+ const reqOptions = {
849
+ method: options.method?.toUpperCase() || 'GET',
850
+ path,
851
+ headers: {
852
+ host: this.hostname,
853
+ ...headers
854
+ },
855
+ createConnection: () => this.tlsSocket
856
+ };
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);
826
881
  });
827
- Object.keys(headers).forEach(key => {
828
- if (!key.startsWith(':') && !orderedHeaders[key]) {
829
- orderedHeaders[key] = headers[key];
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
+ }
830
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
+ }
911
+ }
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);
831
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);
832
935
  }
833
936
  else {
834
- Object.assign(orderedHeaders, headers);
835
- }
836
- const requestOptions = { ...orderedHeaders };
837
- if (this.profile.http2.priorityFrames && this.profile.http2.priorityFrames.length > 0) {
838
- const priority = this.profile.http2.priorityFrames[0];
839
- if (priority.weight)
840
- requestOptions.weight = priority.weight;
841
- if (priority.exclusive !== undefined)
842
- requestOptions.exclusive = priority.exclusive;
843
- if (priority.dependency !== undefined && priority.dependency !== 0)
844
- requestOptions.parent = priority.dependency;
937
+ req.end();
845
938
  }
846
- const stream = this.client.request(requestOptions);
847
- if (this.profile.http2.headerPriority) {
848
- try {
849
- stream.priority({
850
- parent: this.profile.http2.headerPriority.streamDep,
851
- weight: this.profile.http2.headerPriority.weight,
852
- exclusive: this.profile.http2.headerPriority.exclusive
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
+ }
853
1002
  });
854
1003
  }
855
- catch (e) { }
856
- }
857
- return stream;
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
+ });
858
1019
  }
859
- getClient() {
860
- return this.client;
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
+ });
861
1064
  }
862
- isConnected() {
863
- return this.client !== null && !this.client.destroyed;
1065
+ getTiming() {
1066
+ return { ...this.timing, total: Date.now() - this.startTime };
864
1067
  }
865
1068
  destroy() {
866
- if (this.client && !this.client.destroyed) {
867
- this.client.close();
868
- this.client = null;
869
- }
1069
+ this.tlsSocket?.destroy();
1070
+ this.socket?.destroy();
1071
+ this.tlsSocket = null;
1072
+ this.socket = null;
1073
+ }
1074
+ getSocket() {
1075
+ return this.tlsSocket;
870
1076
  }
871
1077
  }
872
1078
  class AdvancedTLSClient {
@@ -875,267 +1081,300 @@ class AdvancedTLSClient {
875
1081
  this.cookieJar = new CookieJar();
876
1082
  this.maxCachedSessions = 10;
877
1083
  this.sessionTimeout = 300000;
878
- this.cleanupInterval = null;
879
- this.profile = profileName ? ProfileRegistry.get(profileName) : ProfileRegistry.latest;
880
- this.startSessionCleanup();
881
- }
882
- startSessionCleanup() {
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');
883
1111
  this.cleanupInterval = setInterval(() => {
884
1112
  const now = Date.now();
885
1113
  for (const [key, session] of this.sessionCache) {
886
1114
  if (now - session.lastUsed > this.sessionTimeout) {
887
- session.http2Manager.destroy();
1115
+ session.clientManager.destroy();
888
1116
  session.tlsManager.destroy();
889
1117
  this.sessionCache.delete(key);
890
1118
  }
891
1119
  }
892
1120
  }, 60000);
893
- if (this.cleanupInterval.unref) {
894
- this.cleanupInterval.unref();
895
- }
896
1121
  }
897
- async request(url, options = {}) {
898
- const maxRedirects = options.maxRedirects ?? 5;
899
- const followRedirects = options.followRedirects !== false;
900
- let redirectCount = 0;
901
- let currentUrl = url;
902
- let response;
903
- while (true) {
904
- response = await this.performRequest(currentUrl, options);
905
- if (followRedirects && [301, 302, 303, 307, 308].includes(response.statusCode)) {
906
- if (redirectCount >= maxRedirects) {
907
- throw new Error(`Too many redirects (max: ${maxRedirects})`);
908
- }
909
- const locationHeader = response.headers['location'];
910
- if (!locationHeader)
911
- break;
912
- const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
913
- currentUrl = new URL(location, currentUrl).toString();
914
- redirectCount++;
915
- if ([301, 302, 303].includes(response.statusCode)) {
916
- options.method = 'GET';
917
- delete options.body;
918
- }
919
- }
920
- else {
921
- break;
922
- }
923
- }
924
- return response;
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;
925
1143
  }
926
- async performRequest(url, options = {}) {
927
- const parsedUrl = new URL(url);
928
- const hostname = parsedUrl.hostname;
929
- const port = parsedUrl.port ? parseInt(parsedUrl.port) : 443;
930
- const path = parsedUrl.pathname + parsedUrl.search;
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;
931
1153
  const cacheKey = `${hostname}:${port}`;
1154
+ const startTime = Date.now();
1155
+ const out = new stream_1.PassThrough();
932
1156
  let session = this.sessionCache.get(cacheKey);
933
- if (!session || !this.isSessionAlive(session)) {
934
- if (session) {
935
- session.http2Manager.destroy();
936
- session.tlsManager.destroy();
937
- this.sessionCache.delete(cacheKey);
938
- }
939
- const tlsManager = new TLSSocketManager(this.profile);
1157
+ if (!session || session.tlsManager.getSocket()?.destroyed) {
1158
+ const tlsManager = new TLSSocketManager(this.profile, options.proxy);
940
1159
  const tlsSocket = await tlsManager.connect(hostname, port);
941
- const http2Manager = new HTTP2ClientManager(tlsSocket, this.profile, hostname);
942
- await http2Manager.createSession();
943
- session = { tlsManager, http2Manager, lastUsed: Date.now() };
944
- if (this.sessionCache.size >= this.maxCachedSessions) {
945
- const oldestKey = Array.from(this.sessionCache.entries())
946
- .sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0][0];
947
- const oldest = this.sessionCache.get(oldestKey);
948
- oldest.http2Manager.destroy();
949
- oldest.tlsManager.destroy();
950
- this.sessionCache.delete(oldestKey);
951
- }
1160
+ const clientManager = new UnifiedClientManager(tlsSocket, this.profile, hostname, this.cookieJar);
1161
+ await clientManager.initialize();
1162
+ session = { tlsManager, clientManager, lastUsed: Date.now() };
952
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
+ }
953
1170
  }
954
1171
  session.lastUsed = Date.now();
955
- const headers = this.buildHeaders(hostname, path, options);
956
- const stream = session.http2Manager.request(path, headers);
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
957
1211
  return new Promise((resolve, reject) => {
958
- const chunks = [];
959
- let responseHeaders = {};
960
1212
  let statusCode = 0;
961
- stream.on('response', (headers) => {
962
- responseHeaders = headers;
963
- statusCode = Number(headers[':status']);
964
- const setCookieHeaders = headers['set-cookie'];
965
- if (setCookieHeaders) {
966
- const cookieArray = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
967
- this.cookieJar.parseSetCookie(hostname, cookieArray);
968
- }
1213
+ let headers = {};
1214
+ const chunks = [];
1215
+ out.on('header', (status, hdrs) => {
1216
+ statusCode = status;
1217
+ headers = hdrs;
969
1218
  });
970
- stream.on('data', (chunk) => chunks.push(chunk));
971
- stream.on('end', async () => {
972
- stream.removeAllListeners();
1219
+ out.on('headers', (hdrs) => {
1220
+ headers = hdrs;
1221
+ });
1222
+ out.on('data', (chunk) => {
1223
+ chunks.push(chunk);
1224
+ });
1225
+ out.once('end', async () => {
973
1226
  let body = Buffer.concat(chunks);
974
- const timing = session.tlsManager.getTiming();
975
- timing.end = Date.now();
976
- timing.total = timing.end - (Date.now() - timing.end);
977
- if (options.decompress !== false) {
978
- const encoding = responseHeaders['content-encoding'];
979
- if (encoding === 'gzip')
980
- body = await gunzip(body);
981
- else if (encoding === 'br')
982
- body = await brotliDecompress(body);
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) {
1236
+ try {
1237
+ if (encoding.includes('gzip'))
1238
+ body = await gunzip(body);
1239
+ else if (encoding.includes('br'))
1240
+ body = await brotliDecompress(body);
1241
+ else if (encoding.includes('deflate'))
1242
+ body = await inflate(body);
1243
+ }
1244
+ catch (e) {
1245
+ console.error('Decompression failed:', e);
1246
+ }
1247
+ }
983
1248
  }
984
- let text;
985
- let json;
986
- try {
987
- text = body.toString('utf-8');
988
- if ((responseHeaders['content-type'] || '').includes('application/json')) {
989
- json = JSON.parse(text);
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')) {
1258
+ try {
1259
+ parsedJson = JSON.parse(text);
990
1260
  }
1261
+ catch { }
991
1262
  }
992
- catch (e) { }
1263
+ // Generate fingerprints
993
1264
  const ja3 = this.generateJA3();
994
- resolve({
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 = {
995
1268
  statusCode,
996
- headers: responseHeaders,
1269
+ headers,
997
1270
  body,
998
1271
  text,
999
- json,
1000
- timing,
1272
+ json: parsedJson,
1001
1273
  fingerprints: {
1002
1274
  ja3,
1003
- ja3Hash: crypto.createHash('md5').update(ja3).digest('hex'),
1004
- 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`
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
1005
1286
  }
1006
- });
1007
- });
1008
- stream.on('error', (err) => {
1009
- stream.removeAllListeners();
1010
- reject(err);
1287
+ };
1288
+ resolve(fullResponse);
1011
1289
  });
1012
- if (options.body) {
1013
- const bodyBuffer = Buffer.isBuffer(options.body) ? options.body : Buffer.from(options.body);
1014
- stream.write(bodyBuffer);
1015
- }
1016
- stream.end();
1290
+ out.once('error', (err) => reject(err));
1291
+ // Jalankan request
1292
+ session.clientManager.request(path, options, startTime, config.headers, post_data, out).catch(reject);
1017
1293
  });
1018
1294
  }
1019
1295
  generateJA3() {
1020
1296
  const version = '771';
1021
- const ciphers = this.profile.tls.cipherSuites
1022
- .filter(c => !c.startsWith('GREASE_'))
1023
- .map(c => {
1024
- const map = {
1025
- 'TLS_AES_128_GCM_SHA256': '4865',
1026
- 'TLS_AES_256_GCM_SHA384': '4866',
1027
- 'TLS_CHACHA20_POLY1305_SHA256': '4867',
1028
- 'ECDHE-ECDSA-AES128-GCM-SHA256': '49195',
1029
- 'ECDHE-RSA-AES128-GCM-SHA256': '49199',
1030
- 'ECDHE-ECDSA-AES256-GCM-SHA384': '49196',
1031
- 'ECDHE-RSA-AES256-GCM-SHA384': '49200',
1032
- 'ECDHE-ECDSA-CHACHA20-POLY1305': '52393',
1033
- 'ECDHE-RSA-CHACHA20-POLY1305': '52392',
1034
- 'ECDHE-RSA-AES128-SHA': '49171',
1035
- 'ECDHE-RSA-AES256-SHA': '49172',
1036
- 'AES128-GCM-SHA256': '156',
1037
- 'AES256-GCM-SHA384': '157',
1038
- 'AES128-SHA': '47',
1039
- 'AES256-SHA': '53'
1040
- };
1041
- return map[c] || '0';
1042
- }).join('-');
1043
- const extensions = this.profile.tls.extensions
1044
- .filter(e => typeof e === 'number')
1045
- .join('-');
1046
- const groups = this.profile.tls.supportedGroups
1047
- .filter(g => !g.startsWith('GREASE_'))
1048
- .map(g => {
1049
- const map = {
1050
- 'X25519': '29',
1051
- 'prime256v1': '23',
1052
- 'secp384r1': '24',
1053
- 'secp521r1': '25',
1054
- 'X25519Kyber768': '25497'
1055
- };
1056
- return map[g] || '0';
1057
- }).join('-');
1058
- const ecFormats = this.profile.tls.ecPointFormats.join('-');
1059
- return `${version},${ciphers},${extensions},${groups},${ecFormats}`;
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}`;
1060
1302
  }
1061
- buildHeaders(hostname, path, options) {
1062
- const method = (options.method || 'GET').toUpperCase();
1063
- const headers = {
1064
- ':method': method,
1065
- ':authority': hostname,
1066
- ':scheme': 'https',
1067
- ':path': path
1068
- };
1069
- const defaultHeaders = {
1070
- 'sec-ch-ua': this.profile.secChUa,
1071
- 'sec-ch-ua-mobile': this.profile.secChUaMobile,
1072
- 'sec-ch-ua-platform': this.profile.secChUaPlatform,
1073
- 'upgrade-insecure-requests': this.profile.upgradeInsecureRequests || '1',
1074
- 'user-agent': this.profile.userAgent,
1075
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
1076
- 'sec-fetch-site': this.profile.secFetchSite,
1077
- 'sec-fetch-mode': this.profile.secFetchMode,
1078
- 'sec-fetch-dest': this.profile.secFetchDest,
1079
- 'accept-encoding': 'gzip, deflate, br, zstd',
1080
- 'accept-language': this.profile.acceptLanguage || 'en-US,en;q=0.9'
1081
- };
1082
- if (this.profile.priority) {
1083
- defaultHeaders['priority'] = this.profile.priority;
1084
- }
1085
- if (this.profile.http2.headerOrder) {
1086
- this.profile.http2.headerOrder.forEach(headerName => {
1087
- if (defaultHeaders[headerName]) {
1088
- headers[headerName] = defaultHeaders[headerName];
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;
1320
+ }
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
+ });
1089
1334
  }
1090
- });
1091
- }
1092
- Object.keys(defaultHeaders).forEach(key => {
1093
- if (!headers[key]) {
1094
- headers[key] = defaultHeaders[key];
1095
1335
  }
1096
1336
  });
1097
- const cookies = options.cookies
1098
- ? Object.entries(options.cookies).map(([k, v]) => `${k}=${v}`).join('; ')
1099
- : this.cookieJar.getCookies(hostname, path);
1100
- if (cookies)
1101
- headers['cookie'] = cookies;
1102
- if (options.headers)
1103
- Object.assign(headers, options.headers);
1104
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
1105
- if (!headers['content-type'])
1106
- headers['content-type'] = 'application/x-www-form-urlencoded';
1107
- if (options.body) {
1108
- const bodyLength = Buffer.isBuffer(options.body) ? options.body.length : Buffer.byteLength(options.body);
1109
- headers['content-length'] = bodyLength.toString();
1110
- }
1111
- }
1112
- return headers;
1113
1337
  }
1114
- isSessionAlive(session) {
1115
- try {
1116
- return session.http2Manager.isConnected();
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);
1117
1355
  }
1118
- catch {
1119
- return false;
1356
+ else {
1357
+ return_part += '\r\n\r\n';
1358
+ return_part += String(part.value || '');
1359
+ return return_part + '\r\n';
1120
1360
  }
1121
1361
  }
1122
- getCookies(domain) {
1123
- return this.cookieJar.getCookies(domain);
1124
- }
1125
- setCookie(domain, name, value, options) {
1126
- this.cookieJar.setCookie(domain, name, value, options);
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;
1127
1372
  }
1128
1373
  destroy() {
1129
- if (this.cleanupInterval) {
1130
- clearInterval(this.cleanupInterval);
1131
- this.cleanupInterval = null;
1132
- }
1133
- this.sessionCache.forEach(session => {
1134
- try {
1135
- session.http2Manager.destroy();
1136
- session.tlsManager.destroy();
1137
- }
1138
- catch (e) { }
1374
+ clearInterval(this.cleanupInterval);
1375
+ this.sessionCache.forEach(s => {
1376
+ s.clientManager.destroy();
1377
+ s.tlsManager.destroy();
1139
1378
  });
1140
1379
  this.sessionCache.clear();
1141
1380
  }