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.d.ts +124 -26
- package/dist/index.js +899 -660
- package/package.json +1 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
grease4,
|
|
106
|
+
25497,
|
|
107
|
+
29,
|
|
108
|
+
23,
|
|
109
|
+
24
|
|
104
110
|
],
|
|
105
111
|
signatureAlgorithms: [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
1027,
|
|
113
|
+
2052,
|
|
114
|
+
1025,
|
|
115
|
+
1283,
|
|
116
|
+
2053,
|
|
117
|
+
1281,
|
|
118
|
+
2054,
|
|
119
|
+
1537
|
|
114
120
|
],
|
|
115
121
|
supportedVersions: [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
grease2,
|
|
123
|
+
772,
|
|
124
|
+
771
|
|
119
125
|
],
|
|
120
|
-
ecPointFormats: [
|
|
126
|
+
ecPointFormats: [0],
|
|
121
127
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
122
|
-
pskKeyExchangeModes: [
|
|
123
|
-
compressionMethods: [
|
|
128
|
+
pskKeyExchangeModes: [1],
|
|
129
|
+
compressionMethods: [0],
|
|
124
130
|
grease: true,
|
|
125
131
|
recordSizeLimit: 16385,
|
|
126
|
-
certificateCompression: [
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
228
|
+
grease4,
|
|
229
|
+
25497,
|
|
230
|
+
29,
|
|
231
|
+
23,
|
|
232
|
+
24
|
|
227
233
|
],
|
|
228
234
|
signatureAlgorithms: [
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
1027,
|
|
236
|
+
2052,
|
|
237
|
+
1025,
|
|
238
|
+
1283,
|
|
239
|
+
2053,
|
|
240
|
+
1281,
|
|
241
|
+
2054,
|
|
242
|
+
1537
|
|
237
243
|
],
|
|
238
244
|
supportedVersions: [
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
245
|
+
grease2,
|
|
246
|
+
772,
|
|
247
|
+
771
|
|
242
248
|
],
|
|
243
|
-
ecPointFormats: [
|
|
249
|
+
ecPointFormats: [0],
|
|
244
250
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
245
|
-
pskKeyExchangeModes: [
|
|
246
|
-
compressionMethods: [
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
346
|
+
grease4,
|
|
347
|
+
25497,
|
|
348
|
+
29,
|
|
349
|
+
23,
|
|
350
|
+
24
|
|
345
351
|
],
|
|
346
352
|
signatureAlgorithms: [
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
1027,
|
|
354
|
+
2052,
|
|
355
|
+
1025,
|
|
356
|
+
1283,
|
|
357
|
+
2053,
|
|
358
|
+
1281,
|
|
359
|
+
2054,
|
|
360
|
+
1537
|
|
355
361
|
],
|
|
356
362
|
supportedVersions: [
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
363
|
+
grease2,
|
|
364
|
+
772,
|
|
365
|
+
771
|
|
360
366
|
],
|
|
361
|
-
ecPointFormats: [
|
|
367
|
+
ecPointFormats: [0],
|
|
362
368
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
363
|
-
pskKeyExchangeModes: [
|
|
364
|
-
compressionMethods: [
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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: [
|
|
451
|
-
|
|
463
|
+
supportedVersions: [
|
|
464
|
+
772,
|
|
465
|
+
771
|
|
466
|
+
],
|
|
467
|
+
ecPointFormats: [0],
|
|
452
468
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
453
|
-
pskKeyExchangeModes: [
|
|
454
|
-
compressionMethods: [
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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: [
|
|
546
|
-
|
|
567
|
+
supportedVersions: [
|
|
568
|
+
772,
|
|
569
|
+
771,
|
|
570
|
+
770,
|
|
571
|
+
769
|
|
572
|
+
],
|
|
573
|
+
ecPointFormats: [0],
|
|
547
574
|
alpnProtocols: ['h2', 'http/1.1'],
|
|
548
|
-
pskKeyExchangeModes: [
|
|
549
|
-
compressionMethods: [
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
623
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
633
|
-
|
|
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.
|
|
636
|
-
this.
|
|
637
|
-
this.
|
|
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.
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
748
|
-
this.
|
|
749
|
-
this.socket.destroy();
|
|
750
|
-
this.socket = null;
|
|
699
|
+
else {
|
|
700
|
+
this.negotiatedProtocol = Protocol.HTTP1;
|
|
751
701
|
}
|
|
752
702
|
}
|
|
753
|
-
|
|
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
|
|
773
|
-
this.profile.http2.settingsOrder.forEach(
|
|
774
|
-
switch (
|
|
705
|
+
const settings = {};
|
|
706
|
+
this.profile.http2.settingsOrder.forEach(id => {
|
|
707
|
+
switch (id) {
|
|
775
708
|
case 1:
|
|
776
|
-
|
|
709
|
+
settings.headerTableSize = this.profile.http2.headerTableSize;
|
|
777
710
|
break;
|
|
778
711
|
case 2:
|
|
779
|
-
|
|
712
|
+
settings.enablePush = this.profile.http2.enablePush === 1;
|
|
780
713
|
break;
|
|
781
714
|
case 3:
|
|
782
|
-
if (this.profile.http2.maxConcurrentStreams
|
|
783
|
-
|
|
784
|
-
}
|
|
715
|
+
if (this.profile.http2.maxConcurrentStreams)
|
|
716
|
+
settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
|
|
785
717
|
break;
|
|
786
718
|
case 4:
|
|
787
|
-
|
|
719
|
+
settings.initialWindowSize = this.profile.http2.initialWindowSize;
|
|
788
720
|
break;
|
|
789
721
|
case 5:
|
|
790
|
-
if (this.profile.http2.maxFrameSize
|
|
791
|
-
|
|
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
|
|
796
|
-
|
|
797
|
-
}
|
|
726
|
+
if (this.profile.http2.maxHeaderListSize)
|
|
727
|
+
settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
|
|
798
728
|
break;
|
|
799
729
|
}
|
|
800
730
|
});
|
|
801
|
-
|
|
731
|
+
this.http2Client = http2.connect(`https://${this.hostname}`, {
|
|
802
732
|
createConnection: () => this.tlsSocket,
|
|
803
|
-
settings
|
|
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.
|
|
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
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
-
|
|
860
|
-
|
|
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
|
-
|
|
863
|
-
return this.
|
|
1065
|
+
getTiming() {
|
|
1066
|
+
return { ...this.timing, total: Date.now() - this.startTime };
|
|
864
1067
|
}
|
|
865
1068
|
destroy() {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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.
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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.
|
|
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
|
-
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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 ||
|
|
934
|
-
|
|
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
|
|
942
|
-
await
|
|
943
|
-
session = { tlsManager,
|
|
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
|
|
956
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
985
|
-
let
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
|
|
1263
|
+
// Generate fingerprints
|
|
993
1264
|
const ja3 = this.generateJA3();
|
|
994
|
-
|
|
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
|
|
1269
|
+
headers,
|
|
997
1270
|
body,
|
|
998
1271
|
text,
|
|
999
|
-
json,
|
|
1000
|
-
timing,
|
|
1272
|
+
json: parsedJson,
|
|
1001
1273
|
fingerprints: {
|
|
1002
1274
|
ja3,
|
|
1003
|
-
ja3Hash
|
|
1004
|
-
akamai
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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
|
}
|