advanced-tls-client 1.0.1 → 1.0.2
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.d.ts +76 -14
- package/dist/index.js +500 -606
- package/package.json +1 -1
- package/readme.md +375 -0
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ 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
39
|
const http2 = __importStar(require("http2"));
|
|
@@ -41,8 +41,10 @@ const crypto = __importStar(require("crypto"));
|
|
|
41
41
|
const events_1 = require("events");
|
|
42
42
|
const zlib = __importStar(require("zlib"));
|
|
43
43
|
const util_1 = require("util");
|
|
44
|
+
const stream_1 = require("stream");
|
|
44
45
|
const gunzip = (0, util_1.promisify)(zlib.gunzip);
|
|
45
46
|
const brotliDecompress = (0, util_1.promisify)(zlib.brotliDecompress);
|
|
47
|
+
const inflate = (0, util_1.promisify)(zlib.inflate);
|
|
46
48
|
const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
|
|
47
49
|
class ProfileRegistry {
|
|
48
50
|
static getGrease() {
|
|
@@ -58,72 +60,72 @@ class ProfileRegistry {
|
|
|
58
60
|
version: '143.0.0.0',
|
|
59
61
|
tls: {
|
|
60
62
|
cipherSuites: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
grease1,
|
|
64
|
+
4865,
|
|
65
|
+
4866,
|
|
66
|
+
4867,
|
|
67
|
+
49195,
|
|
68
|
+
49199,
|
|
69
|
+
49196,
|
|
70
|
+
49200,
|
|
71
|
+
52393,
|
|
72
|
+
52392,
|
|
73
|
+
49171,
|
|
74
|
+
49172,
|
|
75
|
+
156,
|
|
76
|
+
157,
|
|
77
|
+
47,
|
|
78
|
+
53
|
|
77
79
|
],
|
|
78
80
|
extensions: [
|
|
79
81
|
grease2,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
65037,
|
|
83
|
+
27,
|
|
84
|
+
43,
|
|
85
|
+
16,
|
|
86
|
+
35,
|
|
87
|
+
45,
|
|
88
|
+
11,
|
|
89
|
+
65281,
|
|
90
|
+
5,
|
|
91
|
+
10,
|
|
92
|
+
23,
|
|
93
|
+
0,
|
|
94
|
+
17513,
|
|
95
|
+
51,
|
|
96
|
+
18,
|
|
97
|
+
13,
|
|
96
98
|
grease3
|
|
97
99
|
],
|
|
98
100
|
supportedGroups: [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
grease4,
|
|
102
|
+
25497,
|
|
103
|
+
29,
|
|
104
|
+
23,
|
|
105
|
+
24
|
|
104
106
|
],
|
|
105
107
|
signatureAlgorithms: [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
1027,
|
|
109
|
+
2052,
|
|
110
|
+
1025,
|
|
111
|
+
1283,
|
|
112
|
+
2053,
|
|
113
|
+
1281,
|
|
114
|
+
2054,
|
|
115
|
+
1537
|
|
114
116
|
],
|
|
115
117
|
supportedVersions: [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
grease2,
|
|
119
|
+
772,
|
|
120
|
+
771
|
|
119
121
|
],
|
|
120
|
-
ecPointFormats: [
|
|
122
|
+
ecPointFormats: [0],
|
|
121
123
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
122
|
-
pskKeyExchangeModes: [
|
|
123
|
-
compressionMethods: [
|
|
124
|
+
pskKeyExchangeModes: [1],
|
|
125
|
+
compressionMethods: [0],
|
|
124
126
|
grease: true,
|
|
125
127
|
recordSizeLimit: 16385,
|
|
126
|
-
certificateCompression: [
|
|
128
|
+
certificateCompression: [2],
|
|
127
129
|
ocspStapling: true,
|
|
128
130
|
signedCertificateTimestamp: true
|
|
129
131
|
},
|
|
@@ -182,68 +184,68 @@ class ProfileRegistry {
|
|
|
182
184
|
version: '133.0.0.0',
|
|
183
185
|
tls: {
|
|
184
186
|
cipherSuites: [
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
187
|
+
grease1,
|
|
188
|
+
4865,
|
|
189
|
+
4866,
|
|
190
|
+
4867,
|
|
191
|
+
49195,
|
|
192
|
+
49199,
|
|
193
|
+
49196,
|
|
194
|
+
49200,
|
|
195
|
+
52393,
|
|
196
|
+
52392,
|
|
197
|
+
49171,
|
|
198
|
+
49172,
|
|
199
|
+
156,
|
|
200
|
+
157,
|
|
201
|
+
47,
|
|
202
|
+
53
|
|
201
203
|
],
|
|
202
204
|
extensions: [
|
|
203
205
|
grease2,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
0,
|
|
207
|
+
23,
|
|
208
|
+
65281,
|
|
209
|
+
10,
|
|
210
|
+
11,
|
|
211
|
+
35,
|
|
212
|
+
16,
|
|
213
|
+
5,
|
|
214
|
+
13,
|
|
215
|
+
18,
|
|
216
|
+
51,
|
|
217
|
+
45,
|
|
218
|
+
43,
|
|
219
|
+
27,
|
|
220
|
+
21,
|
|
219
221
|
grease3
|
|
220
222
|
],
|
|
221
223
|
supportedGroups: [
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
grease4,
|
|
225
|
+
25497,
|
|
226
|
+
29,
|
|
227
|
+
23,
|
|
228
|
+
24
|
|
227
229
|
],
|
|
228
230
|
signatureAlgorithms: [
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
1027,
|
|
232
|
+
2052,
|
|
233
|
+
1025,
|
|
234
|
+
1283,
|
|
235
|
+
2053,
|
|
236
|
+
1281,
|
|
237
|
+
2054,
|
|
238
|
+
1537
|
|
237
239
|
],
|
|
238
240
|
supportedVersions: [
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
grease2,
|
|
242
|
+
772,
|
|
243
|
+
771
|
|
242
244
|
],
|
|
243
|
-
ecPointFormats: [
|
|
245
|
+
ecPointFormats: [0],
|
|
244
246
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
245
|
-
pskKeyExchangeModes: [
|
|
246
|
-
compressionMethods: [
|
|
247
|
+
pskKeyExchangeModes: [1],
|
|
248
|
+
compressionMethods: [0],
|
|
247
249
|
grease: true,
|
|
248
250
|
recordSizeLimit: 16385
|
|
249
251
|
},
|
|
@@ -300,68 +302,68 @@ class ProfileRegistry {
|
|
|
300
302
|
version: '133.0.0.0',
|
|
301
303
|
tls: {
|
|
302
304
|
cipherSuites: [
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
305
|
+
grease1,
|
|
306
|
+
4865,
|
|
307
|
+
4866,
|
|
308
|
+
4867,
|
|
309
|
+
49195,
|
|
310
|
+
49199,
|
|
311
|
+
49196,
|
|
312
|
+
49200,
|
|
313
|
+
52393,
|
|
314
|
+
52392,
|
|
315
|
+
49171,
|
|
316
|
+
49172,
|
|
317
|
+
156,
|
|
318
|
+
157,
|
|
319
|
+
47,
|
|
320
|
+
53
|
|
319
321
|
],
|
|
320
322
|
extensions: [
|
|
321
323
|
grease2,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
324
|
+
0,
|
|
325
|
+
23,
|
|
326
|
+
65281,
|
|
327
|
+
10,
|
|
328
|
+
11,
|
|
329
|
+
35,
|
|
330
|
+
16,
|
|
331
|
+
5,
|
|
332
|
+
13,
|
|
333
|
+
18,
|
|
334
|
+
51,
|
|
335
|
+
45,
|
|
336
|
+
43,
|
|
337
|
+
27,
|
|
338
|
+
21,
|
|
337
339
|
grease3
|
|
338
340
|
],
|
|
339
341
|
supportedGroups: [
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
grease4,
|
|
343
|
+
25497,
|
|
344
|
+
29,
|
|
345
|
+
23,
|
|
346
|
+
24
|
|
345
347
|
],
|
|
346
348
|
signatureAlgorithms: [
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
349
|
+
1027,
|
|
350
|
+
2052,
|
|
351
|
+
1025,
|
|
352
|
+
1283,
|
|
353
|
+
2053,
|
|
354
|
+
1281,
|
|
355
|
+
2054,
|
|
356
|
+
1537
|
|
355
357
|
],
|
|
356
358
|
supportedVersions: [
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
grease2,
|
|
360
|
+
772,
|
|
361
|
+
771
|
|
360
362
|
],
|
|
361
|
-
ecPointFormats: [
|
|
363
|
+
ecPointFormats: [0],
|
|
362
364
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
363
|
-
pskKeyExchangeModes: [
|
|
364
|
-
compressionMethods: [
|
|
365
|
+
pskKeyExchangeModes: [1],
|
|
366
|
+
compressionMethods: [0],
|
|
365
367
|
grease: true,
|
|
366
368
|
recordSizeLimit: 16385
|
|
367
369
|
},
|
|
@@ -414,44 +416,54 @@ class ProfileRegistry {
|
|
|
414
416
|
version: '117.0',
|
|
415
417
|
tls: {
|
|
416
418
|
cipherSuites: [
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
419
|
+
4865,
|
|
420
|
+
4867,
|
|
421
|
+
4866,
|
|
422
|
+
49195,
|
|
423
|
+
49199,
|
|
424
|
+
52393,
|
|
425
|
+
52392,
|
|
426
|
+
49196,
|
|
427
|
+
49200,
|
|
428
|
+
159,
|
|
429
|
+
158,
|
|
430
|
+
49161,
|
|
431
|
+
49162,
|
|
432
|
+
156,
|
|
433
|
+
157,
|
|
434
|
+
47,
|
|
435
|
+
53
|
|
436
|
+
],
|
|
437
|
+
extensions: [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21],
|
|
438
|
+
supportedGroups: [
|
|
439
|
+
29,
|
|
440
|
+
23,
|
|
441
|
+
24,
|
|
442
|
+
25,
|
|
443
|
+
256,
|
|
444
|
+
257
|
|
434
445
|
],
|
|
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
446
|
signatureAlgorithms: [
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
1027,
|
|
448
|
+
1283,
|
|
449
|
+
1539,
|
|
450
|
+
2052,
|
|
451
|
+
2053,
|
|
452
|
+
2054,
|
|
453
|
+
1025,
|
|
454
|
+
1281,
|
|
455
|
+
1537,
|
|
456
|
+
513,
|
|
457
|
+
515
|
|
458
|
+
],
|
|
459
|
+
supportedVersions: [
|
|
460
|
+
772,
|
|
461
|
+
771
|
|
449
462
|
],
|
|
450
|
-
|
|
451
|
-
ecPointFormats: [0x00],
|
|
463
|
+
ecPointFormats: [0],
|
|
452
464
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
453
|
-
pskKeyExchangeModes: [
|
|
454
|
-
compressionMethods: [
|
|
465
|
+
pskKeyExchangeModes: [1],
|
|
466
|
+
compressionMethods: [0],
|
|
455
467
|
grease: false
|
|
456
468
|
},
|
|
457
469
|
http2: {
|
|
@@ -471,7 +483,8 @@ class ProfileRegistry {
|
|
|
471
483
|
{ streamId: 13, weight: 240, dependency: 0, exclusive: false }
|
|
472
484
|
],
|
|
473
485
|
pseudoHeaderOrder: [':method', ':path', ':authority', ':scheme'],
|
|
474
|
-
headerPriority: { streamDep: 13, exclusive: false, weight: 41 }
|
|
486
|
+
headerPriority: { streamDep: 13, exclusive: false, weight: 41 },
|
|
487
|
+
headerOrder: []
|
|
475
488
|
},
|
|
476
489
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0',
|
|
477
490
|
secChUa: '',
|
|
@@ -490,63 +503,73 @@ class ProfileRegistry {
|
|
|
490
503
|
version: '18.0',
|
|
491
504
|
tls: {
|
|
492
505
|
cipherSuites: [
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
506
|
+
grease1,
|
|
507
|
+
4865,
|
|
508
|
+
4866,
|
|
509
|
+
4867,
|
|
510
|
+
49196,
|
|
511
|
+
49195,
|
|
512
|
+
52393,
|
|
513
|
+
49200,
|
|
514
|
+
49199,
|
|
515
|
+
52392,
|
|
516
|
+
159,
|
|
517
|
+
158,
|
|
518
|
+
49162,
|
|
519
|
+
49161,
|
|
520
|
+
157,
|
|
521
|
+
156,
|
|
522
|
+
53,
|
|
523
|
+
47,
|
|
524
|
+
49188,
|
|
525
|
+
49187,
|
|
526
|
+
60
|
|
514
527
|
],
|
|
515
528
|
extensions: [
|
|
516
529
|
grease2,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
+
0,
|
|
531
|
+
23,
|
|
532
|
+
65281,
|
|
533
|
+
10,
|
|
534
|
+
11,
|
|
535
|
+
16,
|
|
536
|
+
5,
|
|
537
|
+
13,
|
|
538
|
+
18,
|
|
539
|
+
51,
|
|
540
|
+
45,
|
|
541
|
+
43,
|
|
542
|
+
21
|
|
543
|
+
],
|
|
544
|
+
supportedGroups: [
|
|
545
|
+
29,
|
|
546
|
+
23,
|
|
547
|
+
24,
|
|
548
|
+
25
|
|
530
549
|
],
|
|
531
|
-
supportedGroups: ['X25519', 'prime256v1', 'secp384r1', 'secp521r1'],
|
|
532
550
|
signatureAlgorithms: [
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
551
|
+
1027,
|
|
552
|
+
2052,
|
|
553
|
+
1025,
|
|
554
|
+
1283,
|
|
555
|
+
513,
|
|
556
|
+
2053,
|
|
557
|
+
2053,
|
|
558
|
+
1281,
|
|
559
|
+
2054,
|
|
560
|
+
1537,
|
|
561
|
+
515
|
|
544
562
|
],
|
|
545
|
-
supportedVersions: [
|
|
546
|
-
|
|
563
|
+
supportedVersions: [
|
|
564
|
+
772,
|
|
565
|
+
771,
|
|
566
|
+
770,
|
|
567
|
+
769
|
|
568
|
+
],
|
|
569
|
+
ecPointFormats: [0],
|
|
547
570
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
548
|
-
pskKeyExchangeModes: [
|
|
549
|
-
compressionMethods: [
|
|
571
|
+
pskKeyExchangeModes: [1],
|
|
572
|
+
compressionMethods: [0],
|
|
550
573
|
grease: true
|
|
551
574
|
},
|
|
552
575
|
http2: {
|
|
@@ -559,7 +582,8 @@ class ProfileRegistry {
|
|
|
559
582
|
settingsOrder: [2, 3, 4, 8, 9],
|
|
560
583
|
connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
|
|
561
584
|
priorityFrames: [],
|
|
562
|
-
pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path']
|
|
585
|
+
pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path'],
|
|
586
|
+
headerOrder: []
|
|
563
587
|
},
|
|
564
588
|
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
589
|
secChUa: '',
|
|
@@ -587,6 +611,7 @@ class ProfileRegistry {
|
|
|
587
611
|
return this.chromeMobile143Android();
|
|
588
612
|
}
|
|
589
613
|
}
|
|
614
|
+
exports.ProfileRegistry = ProfileRegistry;
|
|
590
615
|
class CookieJar {
|
|
591
616
|
constructor() {
|
|
592
617
|
this.cookies = new Map();
|
|
@@ -594,9 +619,9 @@ class CookieJar {
|
|
|
594
619
|
setCookie(domain, name, value, options = {}) {
|
|
595
620
|
if (!this.cookies.has(domain))
|
|
596
621
|
this.cookies.set(domain, new Map());
|
|
597
|
-
this.cookies.get(domain).set(name, { value,
|
|
622
|
+
this.cookies.get(domain).set(name, { value, ...options, path: options.path || '/', domain });
|
|
598
623
|
}
|
|
599
|
-
getCookies(domain, path = '/') {
|
|
624
|
+
getCookies(domain, path = '/', secure = true) {
|
|
600
625
|
const domainCookies = this.cookies.get(domain);
|
|
601
626
|
if (!domainCookies)
|
|
602
627
|
return '';
|
|
@@ -607,6 +632,8 @@ class CookieJar {
|
|
|
607
632
|
domainCookies.delete(name);
|
|
608
633
|
continue;
|
|
609
634
|
}
|
|
635
|
+
if (cookie.secure && !secure)
|
|
636
|
+
continue;
|
|
610
637
|
if (path.startsWith(cookie.path))
|
|
611
638
|
validCookies.push(`${name}=${cookie.value}`);
|
|
612
639
|
}
|
|
@@ -620,10 +647,17 @@ class CookieJar {
|
|
|
620
647
|
const options = { path: '/' };
|
|
621
648
|
for (let i = 1; i < parts.length; i++) {
|
|
622
649
|
const [key, val] = parts[i].split('=');
|
|
623
|
-
|
|
650
|
+
const lowerKey = key.toLowerCase();
|
|
651
|
+
if (lowerKey === 'expires')
|
|
624
652
|
options.expires = new Date(val);
|
|
625
|
-
else if (
|
|
653
|
+
else if (lowerKey === 'path')
|
|
626
654
|
options.path = val;
|
|
655
|
+
else if (lowerKey === 'secure')
|
|
656
|
+
options.secure = true;
|
|
657
|
+
else if (lowerKey === 'httponly')
|
|
658
|
+
options.httpOnly = true;
|
|
659
|
+
else if (lowerKey === 'samesite')
|
|
660
|
+
options.sameSite = val;
|
|
627
661
|
}
|
|
628
662
|
this.setCookie(domain, name, value, options);
|
|
629
663
|
}
|
|
@@ -636,44 +670,13 @@ class TLSSocketManager extends events_1.EventEmitter {
|
|
|
636
670
|
this.tlsSocket = null;
|
|
637
671
|
this.startTime = 0;
|
|
638
672
|
this.hostname = '';
|
|
673
|
+
this.port = 443;
|
|
639
674
|
this.profile = profile;
|
|
640
675
|
this.timing = { socket: 0, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
|
|
641
676
|
}
|
|
642
|
-
|
|
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) {
|
|
677
|
+
async connect(hostname, port = 443) {
|
|
676
678
|
this.hostname = hostname;
|
|
679
|
+
this.port = port;
|
|
677
680
|
this.startTime = Date.now();
|
|
678
681
|
return new Promise((resolve, reject) => {
|
|
679
682
|
const timeout = setTimeout(() => {
|
|
@@ -681,40 +684,53 @@ class TLSSocketManager extends events_1.EventEmitter {
|
|
|
681
684
|
reject(new Error('Connection timeout'));
|
|
682
685
|
}, 30000);
|
|
683
686
|
this.socket = new net.Socket();
|
|
684
|
-
this.socket.setKeepAlive(true, 60000);
|
|
685
687
|
this.socket.setNoDelay(true);
|
|
686
|
-
this.
|
|
688
|
+
this.socket.setKeepAlive(true, 60000);
|
|
687
689
|
this.socket.on('lookup', () => {
|
|
688
690
|
this.timing.lookup = Date.now() - this.startTime;
|
|
689
691
|
});
|
|
690
692
|
this.socket.connect(port, hostname, () => {
|
|
691
693
|
this.timing.connect = Date.now() - this.startTime;
|
|
692
|
-
const
|
|
693
|
-
.
|
|
694
|
-
.
|
|
695
|
-
.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
694
|
+
const validCiphers = this.profile.tls.cipherSuites
|
|
695
|
+
.filter(c => c < 0xff00)
|
|
696
|
+
.map(c => {
|
|
697
|
+
const hex = c.toString(16).toUpperCase();
|
|
698
|
+
return hex.length === 3 ? `0${hex}` : hex;
|
|
699
|
+
});
|
|
700
|
+
const cipherList = [
|
|
701
|
+
'TLS_AES_128_GCM_SHA256',
|
|
702
|
+
'TLS_AES_256_GCM_SHA384',
|
|
703
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
704
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
705
|
+
'ECDHE-RSA-AES128-GCM-SHA256',
|
|
706
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
707
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
708
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
709
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
710
|
+
'ECDHE-RSA-AES128-SHA',
|
|
711
|
+
'ECDHE-RSA-AES256-SHA',
|
|
712
|
+
'AES128-GCM-SHA256',
|
|
713
|
+
'AES256-GCM-SHA384',
|
|
714
|
+
'AES128-SHA',
|
|
715
|
+
'AES256-SHA'
|
|
716
|
+
].join(':');
|
|
700
717
|
const tlsOptions = {
|
|
701
718
|
socket: this.socket,
|
|
702
719
|
servername: hostname,
|
|
703
720
|
ALPNProtocols: this.profile.tls.alpnProtocols,
|
|
704
|
-
ciphers:
|
|
721
|
+
ciphers: cipherList,
|
|
705
722
|
minVersion: 'TLSv1.2',
|
|
706
723
|
maxVersion: 'TLSv1.3',
|
|
707
|
-
ecdhCurve: curves,
|
|
708
|
-
sigalgs: this.profile.tls.signatureAlgorithms.join(':'),
|
|
709
724
|
rejectUnauthorized: false,
|
|
710
725
|
requestCert: false,
|
|
711
726
|
honorCipherOrder: true,
|
|
712
727
|
sessionTimeout: 300
|
|
713
728
|
};
|
|
714
729
|
this.tlsSocket = tls.connect(tlsOptions);
|
|
715
|
-
this.tlsSocket.
|
|
730
|
+
this.tlsSocket.once('secureConnect', () => {
|
|
716
731
|
clearTimeout(timeout);
|
|
717
732
|
this.timing.secureConnect = Date.now() - this.startTime;
|
|
733
|
+
this.timing.socket = this.startTime;
|
|
718
734
|
resolve(this.tlsSocket);
|
|
719
735
|
});
|
|
720
736
|
this.tlsSocket.on('error', (err) => {
|
|
@@ -740,133 +756,203 @@ class TLSSocketManager extends events_1.EventEmitter {
|
|
|
740
756
|
}
|
|
741
757
|
destroy() {
|
|
742
758
|
if (this.tlsSocket && !this.tlsSocket.destroyed) {
|
|
743
|
-
this.tlsSocket.removeAllListeners();
|
|
744
759
|
this.tlsSocket.destroy();
|
|
745
|
-
this.tlsSocket = null;
|
|
746
760
|
}
|
|
747
761
|
if (this.socket && !this.socket.destroyed) {
|
|
748
|
-
this.socket.removeAllListeners();
|
|
749
762
|
this.socket.destroy();
|
|
750
|
-
this.socket = null;
|
|
751
763
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
return this.tlsSocket !== null && !this.tlsSocket.destroyed;
|
|
764
|
+
this.tlsSocket = null;
|
|
765
|
+
this.socket = null;
|
|
755
766
|
}
|
|
756
767
|
getSocket() {
|
|
757
768
|
return this.tlsSocket;
|
|
758
769
|
}
|
|
759
|
-
getHostname() {
|
|
760
|
-
return this.hostname;
|
|
761
|
-
}
|
|
762
770
|
}
|
|
763
|
-
class HTTP2ClientManager {
|
|
764
|
-
constructor(tlsSocket, profile, hostname) {
|
|
771
|
+
class HTTP2ClientManager extends events_1.EventEmitter {
|
|
772
|
+
constructor(tlsSocket, profile, hostname, cookieJar) {
|
|
773
|
+
super();
|
|
765
774
|
this.client = null;
|
|
766
775
|
this.tlsSocket = tlsSocket;
|
|
767
776
|
this.profile = profile;
|
|
768
777
|
this.hostname = hostname;
|
|
778
|
+
this.cookieJar = cookieJar;
|
|
769
779
|
}
|
|
770
780
|
async createSession() {
|
|
771
781
|
return new Promise((resolve, reject) => {
|
|
772
|
-
const
|
|
773
|
-
this.profile.http2.settingsOrder.forEach(
|
|
774
|
-
switch (
|
|
782
|
+
const settings = {};
|
|
783
|
+
this.profile.http2.settingsOrder.forEach(id => {
|
|
784
|
+
switch (id) {
|
|
775
785
|
case 1:
|
|
776
|
-
|
|
786
|
+
settings.headerTableSize = this.profile.http2.headerTableSize;
|
|
777
787
|
break;
|
|
778
788
|
case 2:
|
|
779
|
-
|
|
789
|
+
settings.enablePush = this.profile.http2.enablePush === 1;
|
|
780
790
|
break;
|
|
781
791
|
case 3:
|
|
782
|
-
if (this.profile.http2.maxConcurrentStreams
|
|
783
|
-
|
|
784
|
-
}
|
|
792
|
+
if (this.profile.http2.maxConcurrentStreams)
|
|
793
|
+
settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
|
|
785
794
|
break;
|
|
786
795
|
case 4:
|
|
787
|
-
|
|
796
|
+
settings.initialWindowSize = this.profile.http2.initialWindowSize;
|
|
788
797
|
break;
|
|
789
798
|
case 5:
|
|
790
|
-
if (this.profile.http2.maxFrameSize
|
|
791
|
-
|
|
792
|
-
}
|
|
799
|
+
if (this.profile.http2.maxFrameSize)
|
|
800
|
+
settings.maxFrameSize = this.profile.http2.maxFrameSize;
|
|
793
801
|
break;
|
|
794
802
|
case 6:
|
|
795
|
-
if (this.profile.http2.maxHeaderListSize
|
|
796
|
-
|
|
797
|
-
}
|
|
803
|
+
if (this.profile.http2.maxHeaderListSize)
|
|
804
|
+
settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
|
|
798
805
|
break;
|
|
799
806
|
}
|
|
800
807
|
});
|
|
801
|
-
|
|
808
|
+
this.client = http2.connect(`https://${this.hostname}`, {
|
|
802
809
|
createConnection: () => this.tlsSocket,
|
|
803
|
-
settings
|
|
804
|
-
};
|
|
805
|
-
this.client = http2.connect(`https://${this.hostname}`, settings);
|
|
806
|
-
this.client.on('connect', () => {
|
|
807
|
-
resolve(this.client);
|
|
810
|
+
settings
|
|
808
811
|
});
|
|
809
|
-
this.client.
|
|
812
|
+
this.client.once('connect', () => resolve(this.client));
|
|
813
|
+
this.client.once('error', reject);
|
|
814
|
+
this.client.once('timeout', () => reject(new Error('HTTP/2 session timeout')));
|
|
810
815
|
});
|
|
811
816
|
}
|
|
812
|
-
request(path,
|
|
813
|
-
if (!this.client)
|
|
814
|
-
throw new Error('
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
817
|
+
async request(path, options = {}, timingStart) {
|
|
818
|
+
if (!this.client || this.client.destroyed) {
|
|
819
|
+
throw new Error('HTTP/2 session not established or destroyed');
|
|
820
|
+
}
|
|
821
|
+
const headers = {
|
|
822
|
+
':method': options.method?.toUpperCase() || 'GET',
|
|
823
|
+
':authority': this.hostname,
|
|
824
|
+
':scheme': 'https',
|
|
825
|
+
':path': path
|
|
826
|
+
};
|
|
827
|
+
const defaultHeaders = {
|
|
828
|
+
'user-agent': this.profile.userAgent,
|
|
829
|
+
'sec-ch-ua': this.profile.secChUa,
|
|
830
|
+
'sec-ch-ua-mobile': this.profile.secChUaMobile,
|
|
831
|
+
'sec-ch-ua-platform': this.profile.secChUaPlatform,
|
|
832
|
+
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
|
833
|
+
'accept-language': this.profile.acceptLanguage || 'en-US,en;q=0.9',
|
|
834
|
+
'accept-encoding': 'gzip, deflate, br, zstd',
|
|
835
|
+
'sec-fetch-site': this.profile.secFetchSite,
|
|
836
|
+
'sec-fetch-mode': this.profile.secFetchMode,
|
|
837
|
+
'sec-fetch-dest': this.profile.secFetchDest,
|
|
838
|
+
};
|
|
839
|
+
if (this.profile.upgradeInsecureRequests)
|
|
840
|
+
defaultHeaders['upgrade-insecure-requests'] = '1';
|
|
841
|
+
if (this.profile.priority)
|
|
842
|
+
defaultHeaders['priority'] = this.profile.priority;
|
|
843
|
+
if (this.profile.http2.headerOrder.length > 0) {
|
|
844
|
+
this.profile.http2.headerOrder.forEach(name => {
|
|
845
|
+
if (defaultHeaders[name])
|
|
846
|
+
headers[name] = defaultHeaders[name];
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
Object.assign(headers, defaultHeaders);
|
|
850
|
+
const cookies = this.cookieJar.getCookies(this.hostname, path);
|
|
851
|
+
if (cookies)
|
|
852
|
+
headers['cookie'] = cookies;
|
|
853
|
+
if (options.headers)
|
|
854
|
+
Object.assign(headers, options.headers);
|
|
855
|
+
if (options.body && (options.method || 'GET').toUpperCase() !== 'GET') {
|
|
856
|
+
if (!headers['content-type'])
|
|
857
|
+
headers['content-type'] = 'application/x-www-form-urlencoded';
|
|
858
|
+
if (typeof options.body === 'string') {
|
|
859
|
+
headers['content-length'] = Buffer.byteLength(options.body).toString();
|
|
819
860
|
}
|
|
861
|
+
else if (Buffer.isBuffer(options.body)) {
|
|
862
|
+
headers['content-length'] = options.body.length.toString();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const stream = this.client.request(headers);
|
|
866
|
+
const response = new events_1.EventEmitter();
|
|
867
|
+
response.timing = {
|
|
868
|
+
socket: timingStart,
|
|
869
|
+
lookup: 0,
|
|
870
|
+
connect: 0,
|
|
871
|
+
secureConnect: 0,
|
|
872
|
+
response: 0,
|
|
873
|
+
end: 0,
|
|
874
|
+
total: 0
|
|
875
|
+
};
|
|
876
|
+
response.fingerprints = {
|
|
877
|
+
ja3: this.generateJA3(),
|
|
878
|
+
ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
|
|
879
|
+
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`
|
|
880
|
+
};
|
|
881
|
+
const chunks = [];
|
|
882
|
+
stream.once('response', (headers, flags) => {
|
|
883
|
+
response.statusCode = headers[':status'];
|
|
884
|
+
response.headers = headers;
|
|
885
|
+
const setCookies = headers['set-cookie'];
|
|
886
|
+
if (setCookies) {
|
|
887
|
+
const cookies = Array.isArray(setCookies) ? setCookies : [setCookies];
|
|
888
|
+
this.cookieJar.parseSetCookie(this.hostname, cookies);
|
|
889
|
+
}
|
|
890
|
+
response.timing.response = Date.now() - timingStart;
|
|
820
891
|
});
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
892
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
893
|
+
stream.once('end', async () => {
|
|
894
|
+
let body = Buffer.concat(chunks);
|
|
895
|
+
if (options.decompress !== false) {
|
|
896
|
+
const encoding = response.headers['content-encoding']?.toLowerCase();
|
|
897
|
+
try {
|
|
898
|
+
if (encoding === 'gzip')
|
|
899
|
+
body = await gunzip(body);
|
|
900
|
+
else if (encoding === 'br')
|
|
901
|
+
body = await brotliDecompress(body);
|
|
902
|
+
else if (encoding === 'deflate')
|
|
903
|
+
body = await inflate(body);
|
|
825
904
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
905
|
+
catch (e) { }
|
|
906
|
+
}
|
|
907
|
+
response.body = body;
|
|
908
|
+
try {
|
|
909
|
+
response.text = body.toString('utf-8');
|
|
910
|
+
if ((response.headers['content-type'] || '').includes('application/json')) {
|
|
911
|
+
response.json = JSON.parse(response.text);
|
|
830
912
|
}
|
|
831
|
-
}
|
|
913
|
+
}
|
|
914
|
+
catch { }
|
|
915
|
+
response.timing.end = Date.now() - timingStart;
|
|
916
|
+
response.timing.total = response.timing.end;
|
|
917
|
+
response.emit('complete', response);
|
|
918
|
+
});
|
|
919
|
+
stream.once('error', (err) => response.emit('error', err));
|
|
920
|
+
stream.once('close', () => response.emit('close'));
|
|
921
|
+
if (options.body) {
|
|
922
|
+
if (Buffer.isBuffer(options.body) || typeof options.body === 'string') {
|
|
923
|
+
stream.end(options.body);
|
|
924
|
+
}
|
|
925
|
+
else if (options.body instanceof stream_1.Readable) {
|
|
926
|
+
options.body.pipe(stream);
|
|
927
|
+
}
|
|
832
928
|
}
|
|
833
929
|
else {
|
|
834
|
-
|
|
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;
|
|
845
|
-
}
|
|
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
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
catch (e) { }
|
|
930
|
+
stream.end();
|
|
856
931
|
}
|
|
857
|
-
return
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
932
|
+
return new Promise((resolve, reject) => {
|
|
933
|
+
response.once('complete', () => resolve(response));
|
|
934
|
+
response.once('error', reject);
|
|
935
|
+
});
|
|
861
936
|
}
|
|
862
|
-
|
|
863
|
-
|
|
937
|
+
generateJA3() {
|
|
938
|
+
const version = '771';
|
|
939
|
+
const ciphers = this.profile.tls.cipherSuites
|
|
940
|
+
.filter(c => c < 0xff00)
|
|
941
|
+
.join('-');
|
|
942
|
+
const extensions = this.profile.tls.extensions
|
|
943
|
+
.filter(e => typeof e === 'number' && e < 0xff00)
|
|
944
|
+
.join('-');
|
|
945
|
+
const curves = this.profile.tls.supportedGroups
|
|
946
|
+
.filter(g => g < 0xff00)
|
|
947
|
+
.join('-');
|
|
948
|
+
const ecPoints = this.profile.tls.ecPointFormats.join('-');
|
|
949
|
+
return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
|
|
864
950
|
}
|
|
865
951
|
destroy() {
|
|
866
952
|
if (this.client && !this.client.destroyed) {
|
|
867
|
-
this.client.
|
|
868
|
-
this.client = null;
|
|
953
|
+
this.client.destroy();
|
|
869
954
|
}
|
|
955
|
+
this.client = null;
|
|
870
956
|
}
|
|
871
957
|
}
|
|
872
958
|
class AdvancedTLSClient {
|
|
@@ -875,11 +961,7 @@ class AdvancedTLSClient {
|
|
|
875
961
|
this.cookieJar = new CookieJar();
|
|
876
962
|
this.maxCachedSessions = 10;
|
|
877
963
|
this.sessionTimeout = 300000;
|
|
878
|
-
this.
|
|
879
|
-
this.profile = profileName ? ProfileRegistry.get(profileName) : ProfileRegistry.latest;
|
|
880
|
-
this.startSessionCleanup();
|
|
881
|
-
}
|
|
882
|
-
startSessionCleanup() {
|
|
964
|
+
this.profile = ProfileRegistry.get(profileName || 'chrome_133');
|
|
883
965
|
this.cleanupInterval = setInterval(() => {
|
|
884
966
|
const now = Date.now();
|
|
885
967
|
for (const [key, session] of this.sessionCache) {
|
|
@@ -890,252 +972,64 @@ class AdvancedTLSClient {
|
|
|
890
972
|
}
|
|
891
973
|
}
|
|
892
974
|
}, 60000);
|
|
893
|
-
if (this.cleanupInterval.unref) {
|
|
894
|
-
this.cleanupInterval.unref();
|
|
895
|
-
}
|
|
896
975
|
}
|
|
897
976
|
async request(url, options = {}) {
|
|
898
|
-
const
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
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;
|
|
925
|
-
}
|
|
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;
|
|
977
|
+
const parsed = new URL(url);
|
|
978
|
+
const hostname = parsed.hostname;
|
|
979
|
+
const port = parsed.port ? +parsed.port : 443;
|
|
980
|
+
const path = parsed.pathname + parsed.search;
|
|
931
981
|
const cacheKey = `${hostname}:${port}`;
|
|
932
982
|
let session = this.sessionCache.get(cacheKey);
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
session.http2Manager.destroy();
|
|
936
|
-
session.tlsManager.destroy();
|
|
937
|
-
this.sessionCache.delete(cacheKey);
|
|
938
|
-
}
|
|
983
|
+
const startTime = Date.now();
|
|
984
|
+
if (!session || session.tlsManager.getSocket()?.destroyed) {
|
|
939
985
|
const tlsManager = new TLSSocketManager(this.profile);
|
|
940
986
|
const tlsSocket = await tlsManager.connect(hostname, port);
|
|
941
|
-
const http2Manager = new HTTP2ClientManager(tlsSocket, this.profile, hostname);
|
|
987
|
+
const http2Manager = new HTTP2ClientManager(tlsSocket, this.profile, hostname, this.cookieJar);
|
|
942
988
|
await http2Manager.createSession();
|
|
943
989
|
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
|
-
}
|
|
952
990
|
this.sessionCache.set(cacheKey, session);
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const chunks = [];
|
|
959
|
-
let responseHeaders = {};
|
|
960
|
-
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
|
-
}
|
|
969
|
-
});
|
|
970
|
-
stream.on('data', (chunk) => chunks.push(chunk));
|
|
971
|
-
stream.on('end', async () => {
|
|
972
|
-
stream.removeAllListeners();
|
|
973
|
-
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);
|
|
983
|
-
}
|
|
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);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
catch (e) { }
|
|
993
|
-
const ja3 = this.generateJA3();
|
|
994
|
-
resolve({
|
|
995
|
-
statusCode,
|
|
996
|
-
headers: responseHeaders,
|
|
997
|
-
body,
|
|
998
|
-
text,
|
|
999
|
-
json,
|
|
1000
|
-
timing,
|
|
1001
|
-
fingerprints: {
|
|
1002
|
-
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`
|
|
1005
|
-
}
|
|
1006
|
-
});
|
|
1007
|
-
});
|
|
1008
|
-
stream.on('error', (err) => {
|
|
1009
|
-
stream.removeAllListeners();
|
|
1010
|
-
reject(err);
|
|
1011
|
-
});
|
|
1012
|
-
if (options.body) {
|
|
1013
|
-
const bodyBuffer = Buffer.isBuffer(options.body) ? options.body : Buffer.from(options.body);
|
|
1014
|
-
stream.write(bodyBuffer);
|
|
991
|
+
if (this.sessionCache.size > this.maxCachedSessions) {
|
|
992
|
+
const oldest = [...this.sessionCache.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];
|
|
993
|
+
oldest[1].http2Manager.destroy();
|
|
994
|
+
oldest[1].tlsManager.destroy();
|
|
995
|
+
this.sessionCache.delete(oldest[0]);
|
|
1015
996
|
}
|
|
1016
|
-
stream.end();
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
generateJA3() {
|
|
1020
|
-
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}`;
|
|
1060
|
-
}
|
|
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];
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
997
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
998
|
+
session.lastUsed = Date.now();
|
|
999
|
+
options.headers = options.headers || {};
|
|
1000
|
+
options.headers['user-agent'] = this.profile.userAgent;
|
|
1001
|
+
options.headers['accept-language'] = this.profile.acceptLanguage || 'en-US,en;q=0.9';
|
|
1002
|
+
options.headers['accept-encoding'] = 'gzip, deflate, br';
|
|
1003
|
+
options.headers['accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8';
|
|
1004
|
+
options.headers['sec-ch-ua'] = this.profile.secChUa;
|
|
1005
|
+
options.headers['sec-ch-ua-mobile'] = this.profile.secChUaMobile;
|
|
1006
|
+
options.headers['sec-ch-ua-platform'] = this.profile.secChUaPlatform;
|
|
1007
|
+
options.headers['sec-fetch-site'] = this.profile.secFetchSite;
|
|
1008
|
+
options.headers['sec-fetch-mode'] = this.profile.secFetchMode;
|
|
1009
|
+
options.headers['sec-fetch-dest'] = this.profile.secFetchDest;
|
|
1010
|
+
if (this.profile.upgradeInsecureRequests)
|
|
1011
|
+
options.headers['upgrade-insecure-requests'] = this.profile.upgradeInsecureRequests;
|
|
1012
|
+
if (this.profile.priority)
|
|
1013
|
+
options.headers['priority'] = this.profile.priority;
|
|
1014
|
+
const cookies = this.cookieJar.getCookies(hostname, parsed.pathname, parsed.protocol === 'https:');
|
|
1100
1015
|
if (cookies)
|
|
1101
|
-
headers['cookie'] = cookies;
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
headers['content-length'] = bodyLength.toString();
|
|
1016
|
+
options.headers['cookie'] = cookies;
|
|
1017
|
+
let response = await session.http2Manager.request(path, options, startTime);
|
|
1018
|
+
if (options.followRedirects !== false && [301, 302, 303, 307, 308].includes(response.statusCode) && (options.maxRedirects ?? 5) > 0) {
|
|
1019
|
+
options.maxRedirects = (options.maxRedirects ?? 5) - 1;
|
|
1020
|
+
const location = response.headers['location'];
|
|
1021
|
+
if (location) {
|
|
1022
|
+
const redirectUrl = new URL(location, url).toString();
|
|
1023
|
+
return this.request(redirectUrl, options);
|
|
1110
1024
|
}
|
|
1111
1025
|
}
|
|
1112
|
-
return
|
|
1113
|
-
}
|
|
1114
|
-
isSessionAlive(session) {
|
|
1115
|
-
try {
|
|
1116
|
-
return session.http2Manager.isConnected();
|
|
1117
|
-
}
|
|
1118
|
-
catch {
|
|
1119
|
-
return false;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
getCookies(domain) {
|
|
1123
|
-
return this.cookieJar.getCookies(domain);
|
|
1124
|
-
}
|
|
1125
|
-
setCookie(domain, name, value, options) {
|
|
1126
|
-
this.cookieJar.setCookie(domain, name, value, options);
|
|
1026
|
+
return response;
|
|
1127
1027
|
}
|
|
1128
1028
|
destroy() {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
this.sessionCache.forEach(session => {
|
|
1134
|
-
try {
|
|
1135
|
-
session.http2Manager.destroy();
|
|
1136
|
-
session.tlsManager.destroy();
|
|
1137
|
-
}
|
|
1138
|
-
catch (e) { }
|
|
1029
|
+
clearInterval(this.cleanupInterval);
|
|
1030
|
+
this.sessionCache.forEach(s => {
|
|
1031
|
+
s.http2Manager.destroy();
|
|
1032
|
+
s.tlsManager.destroy();
|
|
1139
1033
|
});
|
|
1140
1034
|
this.sessionCache.clear();
|
|
1141
1035
|
}
|