advanced-tls-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.ts +1319 -0
  2. package/package.json +17 -0
package/index.ts ADDED
@@ -0,0 +1,1319 @@
1
+ import * as tls from 'tls';
2
+ import * as net from 'net';
3
+ import * as http2 from 'http2';
4
+ import * as crypto from 'crypto';
5
+ import { EventEmitter } from 'events';
6
+ import * as zlib from 'zlib';
7
+ import { promisify } from 'util';
8
+
9
+ const gunzip = promisify(zlib.gunzip);
10
+ const brotliDecompress = promisify(zlib.brotliDecompress);
11
+
12
+ const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
13
+
14
+ interface TLSExtension {
15
+ type: number;
16
+ data?: Buffer | number[] | string[];
17
+ }
18
+
19
+ interface TLSFingerprint {
20
+ cipherSuites: string[];
21
+ extensions: number[];
22
+ supportedGroups: string[];
23
+ signatureAlgorithms: string[];
24
+ supportedVersions: string[];
25
+ ecPointFormats: number[];
26
+ alpnProtocols: string[];
27
+ pskKeyExchangeModes: number[];
28
+ compressionMethods: number[];
29
+ grease: boolean;
30
+ recordSizeLimit?: number;
31
+ }
32
+
33
+ interface HTTP2Fingerprint {
34
+ windowUpdate: number;
35
+ headerTableSize: number;
36
+ maxConcurrentStreams?: number;
37
+ initialWindowSize: number;
38
+ maxFrameSize: number;
39
+ maxHeaderListSize?: number;
40
+ enablePush: number;
41
+ settingsOrder: number[];
42
+ connectionPreface: Buffer;
43
+ priorityFrames: Array<{
44
+ streamId: number;
45
+ weight: number;
46
+ dependency: number;
47
+ exclusive: boolean;
48
+ }>;
49
+ pseudoHeaderOrder: string[];
50
+ headerPriority?: {
51
+ streamDep: number;
52
+ exclusive: boolean;
53
+ weight: number;
54
+ };
55
+ }
56
+
57
+ interface ChromeProfile {
58
+ name: string;
59
+ version: string;
60
+ tls: TLSFingerprint;
61
+ http2: HTTP2Fingerprint;
62
+ userAgent: string;
63
+ secChUa: string;
64
+ secChUaPlatform: string;
65
+ secChUaMobile: string;
66
+ secFetchSite: string;
67
+ secFetchMode: string;
68
+ secFetchDest: string;
69
+ }
70
+
71
+ interface RequestOptions {
72
+ method?: string;
73
+ headers?: Record<string, string>;
74
+ body?: string | Buffer;
75
+ cookies?: Record<string, string>;
76
+ decompress?: boolean;
77
+ followRedirects?: boolean;
78
+ maxRedirects?: number;
79
+ }
80
+
81
+ interface Response {
82
+ statusCode: number;
83
+ headers: Record<string, string | string[]>;
84
+ body: Buffer;
85
+ text?: string;
86
+ json?: any;
87
+ timing: {
88
+ socket: number;
89
+ lookup: number;
90
+ connect: number;
91
+ secureConnect: number;
92
+ response: number;
93
+ end: number;
94
+ total: number;
95
+ };
96
+ fingerprints: {
97
+ ja3: string;
98
+ ja3Hash: string;
99
+ akamai: string;
100
+ };
101
+ }
102
+
103
+ class ProfileRegistry {
104
+ private static profiles: Map<string, ChromeProfile> = new Map();
105
+
106
+ private static getGrease(): number {
107
+ const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
108
+ return GREASE_VALUES[Math.floor(Math.random() * GREASE_VALUES.length)];
109
+ }
110
+
111
+ static chromeMobile143Android(): ChromeProfile {
112
+ const grease1 = this.getGrease();
113
+ const grease2 = this.getGrease();
114
+ const grease3 = this.getGrease();
115
+ const grease4 = this.getGrease();
116
+
117
+ return {
118
+ name: 'chrome_mobile_143_android',
119
+ version: '143.0.0.0',
120
+ tls: {
121
+ cipherSuites: [
122
+ `GREASE_${grease1.toString(16)}`,
123
+ 'TLS_AES_128_GCM_SHA256',
124
+ 'TLS_AES_256_GCM_SHA384',
125
+ 'TLS_CHACHA20_POLY1305_SHA256',
126
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
127
+ 'ECDHE-RSA-AES128-GCM-SHA256',
128
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
129
+ 'ECDHE-RSA-AES256-GCM-SHA384',
130
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
131
+ 'ECDHE-RSA-CHACHA20-POLY1305',
132
+ 'ECDHE-RSA-AES128-SHA',
133
+ 'ECDHE-RSA-AES256-SHA',
134
+ 'AES128-GCM-SHA256',
135
+ 'AES256-GCM-SHA384',
136
+ 'AES128-SHA',
137
+ 'AES256-SHA'
138
+ ],
139
+ extensions: [
140
+ grease2,
141
+ 0xfe0d,
142
+ 0x001b,
143
+ 0x002b,
144
+ 0x0010,
145
+ 0x0023,
146
+ 0x002d,
147
+ 0x000b,
148
+ 0xff01,
149
+ 0x0005,
150
+ 0x000a,
151
+ 0x0017,
152
+ 0x0000,
153
+ 0x44cd,
154
+ 0x0033,
155
+ 0x0012,
156
+ 0x000d,
157
+ grease3
158
+ ],
159
+ supportedGroups: [
160
+ `GREASE_${grease4.toString(16)}`,
161
+ 'X25519Kyber768',
162
+ 'X25519',
163
+ 'prime256v1',
164
+ 'secp384r1'
165
+ ],
166
+ signatureAlgorithms: [
167
+ 'ecdsa_secp256r1_sha256',
168
+ 'rsa_pss_rsae_sha256',
169
+ 'rsa_pkcs1_sha256',
170
+ 'ecdsa_secp384r1_sha384',
171
+ 'rsa_pss_rsae_sha384',
172
+ 'rsa_pkcs1_sha384',
173
+ 'rsa_pss_rsae_sha512',
174
+ 'rsa_pkcs1_sha512'
175
+ ],
176
+ supportedVersions: [
177
+ `GREASE_${grease2.toString(16)}`,
178
+ 'TLSv1.3',
179
+ 'TLSv1.2'
180
+ ],
181
+ ecPointFormats: [0x00],
182
+ alpnProtocols: ['h2', 'http/1.1'],
183
+ pskKeyExchangeModes: [0x01],
184
+ compressionMethods: [0x00],
185
+ grease: true,
186
+ recordSizeLimit: 16385,
187
+ encryptedClientHello: true,
188
+ certificateCompression: [0x02],
189
+ ocspStapling: true,
190
+ signedCertificateTimestamp: true
191
+ },
192
+ http2: {
193
+ windowUpdate: 15663105,
194
+ headerTableSize: 65536,
195
+ enablePush: 0,
196
+ initialWindowSize: 6291456,
197
+ maxHeaderListSize: 262144,
198
+ settingsOrder: [1, 2, 4, 6],
199
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
200
+ priorityFrames: [
201
+ {
202
+ streamId: 0,
203
+ weight: 256,
204
+ dependency: 0,
205
+ exclusive: 1
206
+ }
207
+ ],
208
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
209
+ headerOrder: [
210
+ 'sec-ch-ua',
211
+ 'sec-ch-ua-mobile',
212
+ 'sec-ch-ua-platform',
213
+ 'upgrade-insecure-requests',
214
+ 'user-agent',
215
+ 'accept',
216
+ 'sec-fetch-site',
217
+ 'sec-fetch-mode',
218
+ 'sec-fetch-dest',
219
+ 'accept-encoding',
220
+ 'accept-language',
221
+ 'priority'
222
+ ]
223
+ },
224
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36',
225
+ secChUa: '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
226
+ secChUaPlatform: '"Android"',
227
+ secChUaMobile: '?1',
228
+ secFetchSite: 'none',
229
+ secFetchMode: 'navigate',
230
+ secFetchDest: 'document',
231
+ upgradeInsecureRequests: '1',
232
+ acceptLanguage: 'id,en-US;q=0.9,en;q=0.8,ms;q=0.7,ja;q=0.6,zh-CN;q=0.5,zh;q=0.4',
233
+ priority: 'u=0, i'
234
+ };
235
+ }
236
+
237
+ static chrome133PSK(): ChromeProfile {
238
+ const grease1 = this.getGrease();
239
+ const grease2 = this.getGrease();
240
+ const grease3 = this.getGrease();
241
+ const grease4 = this.getGrease();
242
+
243
+ return {
244
+ name: 'chrome_133_psk',
245
+ version: '133.0.0.0',
246
+ tls: {
247
+ cipherSuites: [
248
+ `GREASE_${grease1.toString(16)}`,
249
+ 'TLS_AES_128_GCM_SHA256',
250
+ 'TLS_AES_256_GCM_SHA384',
251
+ 'TLS_CHACHA20_POLY1305_SHA256',
252
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
253
+ 'ECDHE-RSA-AES128-GCM-SHA256',
254
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
255
+ 'ECDHE-RSA-AES256-GCM-SHA384',
256
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
257
+ 'ECDHE-RSA-CHACHA20-POLY1305',
258
+ 'ECDHE-RSA-AES128-SHA',
259
+ 'ECDHE-RSA-AES256-SHA',
260
+ 'AES128-GCM-SHA256',
261
+ 'AES256-GCM-SHA384',
262
+ 'AES128-SHA',
263
+ 'AES256-SHA'
264
+ ],
265
+ extensions: [
266
+ grease2,
267
+ 0x0000,
268
+ 0x0017,
269
+ 0xff01,
270
+ 0x000a,
271
+ 0x000b,
272
+ 0x0023,
273
+ 0x0010,
274
+ 0x0005,
275
+ 0x000d,
276
+ 0x0012,
277
+ 0x0033,
278
+ 0x002d,
279
+ 0x002b,
280
+ 0x001b,
281
+ 0x0015,
282
+ grease3
283
+ ],
284
+ supportedGroups: [
285
+ `GREASE_${grease4.toString(16)}`,
286
+ 'X25519Kyber768',
287
+ 'X25519',
288
+ 'prime256v1',
289
+ 'secp384r1'
290
+ ],
291
+ signatureAlgorithms: [
292
+ 'ecdsa_secp256r1_sha256',
293
+ 'rsa_pss_rsae_sha256',
294
+ 'rsa_pkcs1_sha256',
295
+ 'ecdsa_secp384r1_sha384',
296
+ 'rsa_pss_rsae_sha384',
297
+ 'rsa_pkcs1_sha384',
298
+ 'rsa_pss_rsae_sha512',
299
+ 'rsa_pkcs1_sha512'
300
+ ],
301
+ supportedVersions: [
302
+ `GREASE_${grease2.toString(16)}`,
303
+ 'TLSv1.3',
304
+ 'TLSv1.2'
305
+ ],
306
+ ecPointFormats: [0x00],
307
+ alpnProtocols: ['h2', 'http/1.1'],
308
+ pskKeyExchangeModes: [0x01],
309
+ compressionMethods: [0x00],
310
+ grease: true,
311
+ recordSizeLimit: 16385
312
+ },
313
+ http2: {
314
+ windowUpdate: 15663105,
315
+ headerTableSize: 65536,
316
+ enablePush: 0,
317
+ initialWindowSize: 6291456,
318
+ maxFrameSize: 16384,
319
+ maxHeaderListSize: 262144,
320
+ settingsOrder: [1, 2, 4, 6],
321
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
322
+ priorityFrames: [
323
+ {
324
+ streamId: 0,
325
+ weight: 256,
326
+ dependency: 0,
327
+ exclusive: 1
328
+ }
329
+ ],
330
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
331
+ headerOrder: [
332
+ 'cache-control',
333
+ 'sec-ch-ua',
334
+ 'sec-ch-ua-mobile',
335
+ 'sec-ch-ua-platform',
336
+ 'upgrade-insecure-requests',
337
+ 'user-agent',
338
+ 'accept',
339
+ 'sec-fetch-site',
340
+ 'sec-fetch-mode',
341
+ 'sec-fetch-user',
342
+ 'sec-fetch-dest',
343
+ 'accept-encoding',
344
+ 'accept-language'
345
+ ]
346
+ },
347
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
348
+ secChUa: '"Google Chrome";v="133", "Chromium";v="133", "Not_A Brand";v="24"',
349
+ secChUaPlatform: '"Windows"',
350
+ secChUaMobile: '?0',
351
+ secFetchSite: 'none',
352
+ secFetchMode: 'navigate',
353
+ secFetchDest: 'document'
354
+ };
355
+ }
356
+
357
+ static chrome133(): ChromeProfile {
358
+ const grease1 = this.getGrease();
359
+ const grease2 = this.getGrease();
360
+ const grease3 = this.getGrease();
361
+ const grease4 = this.getGrease();
362
+
363
+ return {
364
+ name: 'chrome_133',
365
+ version: '133.0.0.0',
366
+ tls: {
367
+ cipherSuites: [
368
+ `GREASE_${grease1.toString(16)}`,
369
+ 'TLS_AES_128_GCM_SHA256',
370
+ 'TLS_AES_256_GCM_SHA384',
371
+ 'TLS_CHACHA20_POLY1305_SHA256',
372
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
373
+ 'ECDHE-RSA-AES128-GCM-SHA256',
374
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
375
+ 'ECDHE-RSA-AES256-GCM-SHA384',
376
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
377
+ 'ECDHE-RSA-CHACHA20-POLY1305',
378
+ 'ECDHE-RSA-AES128-SHA',
379
+ 'ECDHE-RSA-AES256-SHA',
380
+ 'AES128-GCM-SHA256',
381
+ 'AES256-GCM-SHA384',
382
+ 'AES128-SHA',
383
+ 'AES256-SHA'
384
+ ],
385
+ extensions: [
386
+ grease2,
387
+ 0x0000,
388
+ 0x0017,
389
+ 0xff01,
390
+ 0x000a,
391
+ 0x000b,
392
+ 0x0023,
393
+ 0x0010,
394
+ 0x0005,
395
+ 0x000d,
396
+ 0x0012,
397
+ 0x0033,
398
+ 0x002d,
399
+ 0x002b,
400
+ 0x001b,
401
+ 0x0015,
402
+ grease3
403
+ ],
404
+ supportedGroups: [
405
+ `GREASE_${grease4.toString(16)}`,
406
+ 'X25519Kyber768',
407
+ 'X25519',
408
+ 'prime256v1',
409
+ 'secp384r1'
410
+ ],
411
+ signatureAlgorithms: [
412
+ 'ecdsa_secp256r1_sha256',
413
+ 'rsa_pss_rsae_sha256',
414
+ 'rsa_pkcs1_sha256',
415
+ 'ecdsa_secp384r1_sha384',
416
+ 'rsa_pss_rsae_sha384',
417
+ 'rsa_pkcs1_sha384',
418
+ 'rsa_pss_rsae_sha512',
419
+ 'rsa_pkcs1_sha512'
420
+ ],
421
+ supportedVersions: [
422
+ `GREASE_${grease2.toString(16)}`,
423
+ 'TLSv1.3',
424
+ 'TLSv1.2'
425
+ ],
426
+ ecPointFormats: [0x00],
427
+ alpnProtocols: ['h2', 'http/1.1'],
428
+ pskKeyExchangeModes: [0x01],
429
+ compressionMethods: [0x00],
430
+ grease: true,
431
+ recordSizeLimit: 16385
432
+ },
433
+ http2: {
434
+ windowUpdate: 15663105,
435
+ headerTableSize: 65536,
436
+ enablePush: 0,
437
+ initialWindowSize: 6291456,
438
+ maxFrameSize: 16384,
439
+ maxHeaderListSize: 262144,
440
+ settingsOrder: [1, 2, 4, 6],
441
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
442
+ priorityFrames: [
443
+ {
444
+ streamId: 0,
445
+ weight: 256,
446
+ dependency: 0,
447
+ exclusive: 1
448
+ }
449
+ ],
450
+ pseudoHeaderOrder: [':method', ':authority', ':scheme', ':path'],
451
+ headerOrder: [
452
+ 'cache-control',
453
+ 'sec-ch-ua',
454
+ 'sec-ch-ua-mobile',
455
+ 'sec-ch-ua-platform',
456
+ 'upgrade-insecure-requests',
457
+ 'user-agent',
458
+ 'accept',
459
+ 'sec-fetch-site',
460
+ 'sec-fetch-mode',
461
+ 'sec-fetch-user',
462
+ 'sec-fetch-dest',
463
+ 'accept-encoding',
464
+ 'accept-language'
465
+ ]
466
+ },
467
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
468
+ secChUa: '"Google Chrome";v="133", "Chromium";v="133", "Not_A Brand";v="24"',
469
+ secChUaPlatform: '"Windows"',
470
+ secChUaMobile: '?0',
471
+ secFetchSite: 'none',
472
+ secFetchMode: 'navigate',
473
+ secFetchDest: 'document'
474
+ };
475
+ }
476
+
477
+ static firefox117(): ChromeProfile {
478
+ return {
479
+ name: 'firefox_117',
480
+ version: '117.0',
481
+ tls: {
482
+ cipherSuites: [
483
+ 'TLS_AES_128_GCM_SHA256',
484
+ 'TLS_CHACHA20_POLY1305_SHA256',
485
+ 'TLS_AES_256_GCM_SHA384',
486
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
487
+ 'ECDHE-RSA-AES128-GCM-SHA256',
488
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
489
+ 'ECDHE-RSA-CHACHA20-POLY1305',
490
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
491
+ 'ECDHE-RSA-AES256-GCM-SHA384',
492
+ 'ECDHE-ECDSA-AES256-SHA',
493
+ 'ECDHE-ECDSA-AES128-SHA',
494
+ 'ECDHE-RSA-AES128-SHA',
495
+ 'ECDHE-RSA-AES256-SHA',
496
+ 'AES128-GCM-SHA256',
497
+ 'AES256-GCM-SHA384',
498
+ 'AES128-SHA',
499
+ 'AES256-SHA'
500
+ ],
501
+ extensions: [0x0000, 0x0017, 0xff01, 0x000a, 0x000b, 0x0023, 0x0010, 0x0005, 0x0022, 0x0033, 0x002b, 0x000d, 0x002d, 0x0029, 0x0015],
502
+ supportedGroups: ['X25519', 'prime256v1', 'secp384r1', 'secp521r1', 'ffdhe2048', 'ffdhe3072'],
503
+ signatureAlgorithms: [
504
+ 'ecdsa_secp256r1_sha256',
505
+ 'ecdsa_secp384r1_sha384',
506
+ 'ecdsa_secp521r1_sha512',
507
+ 'rsa_pss_rsae_sha256',
508
+ 'rsa_pss_rsae_sha384',
509
+ 'rsa_pss_rsae_sha512',
510
+ 'rsa_pkcs1_sha256',
511
+ 'rsa_pkcs1_sha384',
512
+ 'rsa_pkcs1_sha512',
513
+ 'ecdsa_sha1',
514
+ 'rsa_pkcs1_sha1'
515
+ ],
516
+ supportedVersions: ['TLSv1.3', 'TLSv1.2'],
517
+ ecPointFormats: [0x00],
518
+ alpnProtocols: ['h2', 'http/1.1'],
519
+ pskKeyExchangeModes: [0x01],
520
+ compressionMethods: [0x00],
521
+ grease: false
522
+ },
523
+ http2: {
524
+ windowUpdate: 12517377,
525
+ headerTableSize: 65536,
526
+ enablePush: 0,
527
+ initialWindowSize: 131072,
528
+ maxFrameSize: 16384,
529
+ settingsOrder: [1, 4, 5],
530
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
531
+ priorityFrames: [
532
+ { streamId: 3, weight: 200, dependency: 0, exclusive: false },
533
+ { streamId: 5, weight: 100, dependency: 0, exclusive: false },
534
+ { streamId: 7, weight: 0, dependency: 0, exclusive: false },
535
+ { streamId: 9, weight: 0, dependency: 7, exclusive: false },
536
+ { streamId: 11, weight: 0, dependency: 3, exclusive: false },
537
+ { streamId: 13, weight: 240, dependency: 0, exclusive: false }
538
+ ],
539
+ pseudoHeaderOrder: [':method', ':path', ':authority', ':scheme'],
540
+ headerPriority: { streamDep: 13, exclusive: false, weight: 41 }
541
+ },
542
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0',
543
+ secChUa: '',
544
+ secChUaPlatform: '',
545
+ secChUaMobile: '',
546
+ secFetchSite: 'none',
547
+ secFetchMode: 'navigate',
548
+ secFetchDest: 'document'
549
+ };
550
+ }
551
+
552
+ static safariIOS18(): ChromeProfile {
553
+ const grease1 = this.getGrease();
554
+ const grease2 = this.getGrease();
555
+
556
+ return {
557
+ name: 'safari_ios_18_0',
558
+ version: '18.0',
559
+ tls: {
560
+ cipherSuites: [
561
+ `GREASE_${grease1.toString(16)}`,
562
+ 'TLS_AES_128_GCM_SHA256',
563
+ 'TLS_AES_256_GCM_SHA384',
564
+ 'TLS_CHACHA20_POLY1305_SHA256',
565
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
566
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
567
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
568
+ 'ECDHE-RSA-AES256-GCM-SHA384',
569
+ 'ECDHE-RSA-AES128-GCM-SHA256',
570
+ 'ECDHE-RSA-CHACHA20-POLY1305',
571
+ 'ECDHE-ECDSA-AES256-SHA',
572
+ 'ECDHE-ECDSA-AES128-SHA',
573
+ 'ECDHE-RSA-AES256-SHA',
574
+ 'ECDHE-RSA-AES128-SHA',
575
+ 'AES256-GCM-SHA384',
576
+ 'AES128-GCM-SHA256',
577
+ 'AES256-SHA',
578
+ 'AES128-SHA',
579
+ 'ECDHE-ECDSA-DES-CBC3-SHA',
580
+ 'ECDHE-RSA-DES-CBC3-SHA',
581
+ 'DES-CBC3-SHA'
582
+ ],
583
+ extensions: [
584
+ grease2,
585
+ 0x0000,
586
+ 0x0017,
587
+ 0xff01,
588
+ 0x000a,
589
+ 0x000b,
590
+ 0x0010,
591
+ 0x0005,
592
+ 0x000d,
593
+ 0x0012,
594
+ 0x0033,
595
+ 0x002d,
596
+ 0x002b,
597
+ 0x0015
598
+ ],
599
+ supportedGroups: ['X25519', 'prime256v1', 'secp384r1', 'secp521r1'],
600
+ signatureAlgorithms: [
601
+ 'ecdsa_secp256r1_sha256',
602
+ 'rsa_pss_rsae_sha256',
603
+ 'rsa_pkcs1_sha256',
604
+ 'ecdsa_secp384r1_sha384',
605
+ 'ecdsa_sha1',
606
+ 'rsa_pss_rsae_sha384',
607
+ 'rsa_pss_rsae_sha384',
608
+ 'rsa_pkcs1_sha384',
609
+ 'rsa_pss_rsae_sha512',
610
+ 'rsa_pkcs1_sha512',
611
+ 'rsa_pkcs1_sha1'
612
+ ],
613
+ supportedVersions: ['TLSv1.3', 'TLSv1.2', 'TLSv1.1', 'TLSv1.0'],
614
+ ecPointFormats: [0x00],
615
+ alpnProtocols: ['h2', 'http/1.1'],
616
+ pskKeyExchangeModes: [0x01],
617
+ compressionMethods: [0x00],
618
+ grease: true
619
+ },
620
+ http2: {
621
+ windowUpdate: 10420225,
622
+ headerTableSize: 4096,
623
+ enablePush: 0,
624
+ maxConcurrentStreams: 100,
625
+ initialWindowSize: 2097152,
626
+ settingsOrder: [2, 3, 4, 8, 9],
627
+ connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
628
+ priorityFrames: [],
629
+ pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path']
630
+ },
631
+ 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',
632
+ secChUa: '',
633
+ secChUaPlatform: '',
634
+ secChUaMobile: '?1',
635
+ secFetchSite: 'none',
636
+ secFetchMode: 'navigate',
637
+ secFetchDest: 'document'
638
+ };
639
+ }
640
+
641
+ static get(name: string): ChromeProfile {
642
+ switch(name.toLowerCase()) {
643
+ case 'chrome_mobile_143_android': return this.chromeMobile143Android();
644
+ case 'chrome_133_psk': return this.chrome133PSK();
645
+ case 'chrome_133': return this.chrome133();
646
+ case 'firefox_117': return this.firefox117();
647
+ case 'safari_ios_18_0': return this.safariIOS18();
648
+ default: return this.chrome133();
649
+ }
650
+ }
651
+
652
+ static get latest(): ChromeProfile {
653
+ return this.chrome133();
654
+ }
655
+
656
+ static get latestMobile(): ChromeProfile {
657
+ return this.chromeMobile143Android();
658
+ }
659
+ }
660
+
661
+
662
+ class CookieJar {
663
+ private cookies: Map<string, Map<string, { value: string; expires?: Date; path: string; domain: string }>> = new Map();
664
+
665
+ setCookie(domain: string, name: string, value: string, options: { expires?: Date; path?: string } = {}): void {
666
+ if (!this.cookies.has(domain)) this.cookies.set(domain, new Map());
667
+ this.cookies.get(domain)!.set(name, { value, expires: options.expires, path: options.path || '/', domain });
668
+ }
669
+
670
+ getCookies(domain: string, path: string = '/'): string {
671
+ const domainCookies = this.cookies.get(domain);
672
+ if (!domainCookies) return '';
673
+ const validCookies: string[] = [];
674
+ const now = new Date();
675
+ for (const [name, cookie] of domainCookies) {
676
+ if (cookie.expires && cookie.expires < now) {
677
+ domainCookies.delete(name);
678
+ continue;
679
+ }
680
+ if (path.startsWith(cookie.path)) validCookies.push(`${name}=${cookie.value}`);
681
+ }
682
+ return validCookies.join('; ');
683
+ }
684
+
685
+ parseSetCookie(domain: string, setCookieHeaders: string[]): void {
686
+ for (const header of setCookieHeaders) {
687
+ const parts = header.split(';').map(p => p.trim());
688
+ const [nameValue] = parts;
689
+ const [name, value] = nameValue.split('=');
690
+ const options: { expires?: Date; path?: string } = { path: '/' };
691
+ for (let i = 1; i < parts.length; i++) {
692
+ const [key, val] = parts[i].split('=');
693
+ if (key.toLowerCase() === 'expires') options.expires = new Date(val);
694
+ else if (key.toLowerCase() === 'path') options.path = val;
695
+ }
696
+ this.setCookie(domain, name, value, options);
697
+ }
698
+ }
699
+ }
700
+
701
+ class TLSSocketManager extends EventEmitter {
702
+ private socket: net.Socket | null = null;
703
+ private tlsSocket: tls.TLSSocket | null = null;
704
+ private profile: ChromeProfile;
705
+ private timing: Response['timing'];
706
+ private startTime: number = 0;
707
+
708
+ constructor(profile: ChromeProfile) {
709
+ super();
710
+ this.profile = profile;
711
+ this.timing = { socket: 0, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
712
+ }
713
+
714
+ private convertCipherSuite(suite: string): string {
715
+ const cipherMap: { [key: string]: string } = {
716
+ 'TLS_AES_128_GCM_SHA256': 'TLS_AES_128_GCM_SHA256',
717
+ 'TLS_AES_256_GCM_SHA384': 'TLS_AES_256_GCM_SHA384',
718
+ 'TLS_CHACHA20_POLY1305_SHA256': 'TLS_CHACHA20_POLY1305_SHA256',
719
+ 'ECDHE-ECDSA-AES128-GCM-SHA256': 'ECDHE-ECDSA-AES128-GCM-SHA256',
720
+ 'ECDHE-RSA-AES128-GCM-SHA256': 'ECDHE-RSA-AES128-GCM-SHA256',
721
+ 'ECDHE-ECDSA-AES256-GCM-SHA384': 'ECDHE-ECDSA-AES256-GCM-SHA384',
722
+ 'ECDHE-RSA-AES256-GCM-SHA384': 'ECDHE-RSA-AES256-GCM-SHA384',
723
+ 'ECDHE-ECDSA-CHACHA20-POLY1305': 'ECDHE-ECDSA-CHACHA20-POLY1305',
724
+ 'ECDHE-RSA-CHACHA20-POLY1305': 'ECDHE-RSA-CHACHA20-POLY1305',
725
+ 'ECDHE-RSA-AES128-SHA': 'ECDHE-RSA-AES128-SHA',
726
+ 'ECDHE-RSA-AES256-SHA': 'ECDHE-RSA-AES256-SHA',
727
+ 'AES128-GCM-SHA256': 'AES128-GCM-SHA256',
728
+ 'AES256-GCM-SHA384': 'AES256-GCM-SHA384',
729
+ 'AES128-SHA': 'AES128-SHA',
730
+ 'AES256-SHA': 'AES256-SHA'
731
+ };
732
+
733
+ if (suite.startsWith('GREASE_')) {
734
+ return '';
735
+ }
736
+
737
+ return cipherMap[suite] || suite;
738
+ }
739
+
740
+ private convertSupportedGroup(group: string): string {
741
+ const groupMap: { [key: string]: string } = {
742
+ 'X25519Kyber768': 'X25519',
743
+ 'X25519': 'X25519',
744
+ 'prime256v1': 'prime256v1',
745
+ 'secp256r1': 'prime256v1',
746
+ 'P-256': 'prime256v1',
747
+ 'secp384r1': 'secp384r1',
748
+ 'P-384': 'secp384r1',
749
+ 'secp521r1': 'secp521r1',
750
+ 'P-521': 'secp521r1'
751
+ };
752
+
753
+ if (group.startsWith('GREASE_')) {
754
+ return '';
755
+ }
756
+
757
+ return groupMap[group] || group;
758
+ }
759
+
760
+ async connect(hostname: string, port: number): Promise<tls.TLSSocket> {
761
+ this.startTime = Date.now();
762
+ return new Promise((resolve, reject) => {
763
+ const timeout = setTimeout(() => {
764
+ this.destroy();
765
+ reject(new Error('Connection timeout'));
766
+ }, 30000);
767
+
768
+ this.socket = new net.Socket();
769
+ this.socket.setKeepAlive(true, 60000);
770
+ this.socket.setNoDelay(true);
771
+ this.timing.socket = Date.now() - this.startTime;
772
+
773
+ this.socket.on('lookup', () => {
774
+ this.timing.lookup = Date.now() - this.startTime;
775
+ });
776
+
777
+ this.socket.connect(port, hostname, () => {
778
+ this.timing.connect = Date.now() - this.startTime;
779
+
780
+ const ciphers = this.profile.tls.cipherSuites
781
+ .map(suite => this.convertCipherSuite(suite))
782
+ .filter(suite => suite !== '')
783
+ .join(':');
784
+
785
+ const curves = this.profile.tls.supportedGroups
786
+ .map(group => this.convertSupportedGroup(group))
787
+ .filter(group => group !== '')
788
+ .join(':');
789
+
790
+ const tlsOptions: tls.ConnectionOptions = {
791
+ socket: this.socket!,
792
+ servername: hostname,
793
+ ALPNProtocols: this.profile.tls.alpnProtocols,
794
+ ciphers: ciphers,
795
+ minVersion: 'TLSv1.2' as any,
796
+ maxVersion: 'TLSv1.3' as any,
797
+ ecdhCurve: curves,
798
+ sigalgs: this.profile.tls.signatureAlgorithms.join(':'),
799
+ rejectUnauthorized: false,
800
+ requestCert: false,
801
+ honorCipherOrder: true,
802
+ sessionTimeout: 300
803
+ };
804
+
805
+ this.tlsSocket = tls.connect(tlsOptions);
806
+
807
+ this.tlsSocket.on('secureConnect', () => {
808
+ clearTimeout(timeout);
809
+ this.timing.secureConnect = Date.now() - this.startTime;
810
+ resolve(this.tlsSocket!);
811
+ });
812
+
813
+ this.tlsSocket.on('error', (err) => {
814
+ clearTimeout(timeout);
815
+ this.destroy();
816
+ reject(err);
817
+ });
818
+ });
819
+
820
+ this.socket.on('error', (err) => {
821
+ clearTimeout(timeout);
822
+ this.destroy();
823
+ reject(err);
824
+ });
825
+
826
+ this.socket.on('timeout', () => {
827
+ clearTimeout(timeout);
828
+ this.destroy();
829
+ reject(new Error('Socket timeout'));
830
+ });
831
+ });
832
+ }
833
+
834
+ getTiming(): Response['timing'] {
835
+ return { ...this.timing, total: Date.now() - this.startTime };
836
+ }
837
+
838
+ destroy(): void {
839
+ if (this.tlsSocket && !this.tlsSocket.destroyed) {
840
+ this.tlsSocket.removeAllListeners();
841
+ this.tlsSocket.destroy();
842
+ this.tlsSocket = null;
843
+ }
844
+ if (this.socket && !this.socket.destroyed) {
845
+ this.socket.removeAllListeners();
846
+ this.socket.destroy();
847
+ this.socket = null;
848
+ }
849
+ }
850
+
851
+ isConnected(): boolean {
852
+ return this.tlsSocket !== null && !this.tlsSocket.destroyed;
853
+ }
854
+
855
+ getSocket(): tls.TLSSocket | null {
856
+ return this.tlsSocket;
857
+ }
858
+
859
+ getCipherInfo(): any {
860
+ return this.tlsSocket?.getCipher();
861
+ }
862
+
863
+ getProtocol(): string | undefined {
864
+ return this.tlsSocket?.getProtocol();
865
+ }
866
+
867
+ getALPNProtocol(): string | false | null | undefined {
868
+ return this.tlsSocket?.alpnProtocol;
869
+ }
870
+ }
871
+
872
+ class HTTP2ClientManager {
873
+ private client: http2.ClientHttp2Session | null = null;
874
+ private profile: ChromeProfile;
875
+ private tlsSocket: tls.TLSSocket;
876
+
877
+ constructor(tlsSocket: tls.TLSSocket, profile: ChromeProfile) {
878
+ this.tlsSocket = tlsSocket;
879
+ this.profile = profile;
880
+ }
881
+
882
+ async createSession(): Promise<http2.ClientHttp2Session> {
883
+ return new Promise((resolve, reject) => {
884
+ const settingsMap: any = {};
885
+
886
+ this.profile.http2.settingsOrder.forEach(settingId => {
887
+ switch (settingId) {
888
+ case 1:
889
+ settingsMap.headerTableSize = this.profile.http2.headerTableSize;
890
+ break;
891
+ case 2:
892
+ settingsMap.enablePush = this.profile.http2.enablePush === 1;
893
+ break;
894
+ case 3:
895
+ if (this.profile.http2.maxConcurrentStreams !== undefined) {
896
+ settingsMap.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
897
+ }
898
+ break;
899
+ case 4:
900
+ settingsMap.initialWindowSize = this.profile.http2.initialWindowSize;
901
+ break;
902
+ case 5:
903
+ if (this.profile.http2.maxFrameSize !== undefined) {
904
+ settingsMap.maxFrameSize = this.profile.http2.maxFrameSize;
905
+ }
906
+ break;
907
+ case 6:
908
+ if (this.profile.http2.maxHeaderListSize !== undefined) {
909
+ settingsMap.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
910
+ }
911
+ break;
912
+ }
913
+ });
914
+
915
+ const settings: http2.SecureClientSessionOptions = {
916
+ createConnection: () => this.tlsSocket,
917
+ settings: settingsMap
918
+ };
919
+
920
+ this.client = http2.connect(`https://${this.tlsSocket.servername}`, settings);
921
+
922
+ this.client.on('connect', () => {
923
+ resolve(this.client!);
924
+ });
925
+
926
+ this.client.on('error', reject);
927
+ });
928
+ }
929
+
930
+ request(path: string, headers: Record<string, string>): http2.ClientHttp2Stream {
931
+ if (!this.client) throw new Error('HTTP2 session not established');
932
+
933
+ const orderedHeaders: Record<string, string> = {};
934
+
935
+ this.profile.http2.pseudoHeaderOrder.forEach(pseudo => {
936
+ if (headers[pseudo]) {
937
+ orderedHeaders[pseudo] = headers[pseudo];
938
+ }
939
+ });
940
+
941
+ if (this.profile.http2.headerOrder) {
942
+ this.profile.http2.headerOrder.forEach(headerName => {
943
+ if (headers[headerName] && !headerName.startsWith(':')) {
944
+ orderedHeaders[headerName] = headers[headerName];
945
+ }
946
+ });
947
+
948
+ Object.keys(headers).forEach(key => {
949
+ if (!key.startsWith(':') && !orderedHeaders[key]) {
950
+ orderedHeaders[key] = headers[key];
951
+ }
952
+ });
953
+ } else {
954
+ Object.keys(headers).forEach(key => {
955
+ if (!key.startsWith(':') && !orderedHeaders[key]) {
956
+ orderedHeaders[key] = headers[key];
957
+ }
958
+ });
959
+ }
960
+
961
+ const requestOptions: any = { ...orderedHeaders };
962
+
963
+ if (this.profile.http2.priorityFrames && this.profile.http2.priorityFrames.length > 0) {
964
+ const priority = this.profile.http2.priorityFrames[0];
965
+ if (priority.weight) {
966
+ requestOptions.weight = priority.weight;
967
+ }
968
+ if (priority.exclusive !== undefined) {
969
+ requestOptions.exclusive = priority.exclusive === 1;
970
+ }
971
+ if (priority.dependency !== undefined && priority.dependency !== 0) {
972
+ requestOptions.parent = priority.dependency;
973
+ }
974
+ }
975
+
976
+ const stream = this.client.request(requestOptions);
977
+
978
+ if (this.profile.http2.headerPriority) {
979
+ try {
980
+ stream.priority({
981
+ parent: this.profile.http2.headerPriority.streamDep,
982
+ weight: this.profile.http2.headerPriority.weight,
983
+ exclusive: this.profile.http2.headerPriority.exclusive
984
+ });
985
+ } catch (e) {}
986
+ }
987
+
988
+ return stream;
989
+ }
990
+
991
+ getClient(): http2.ClientHttp2Session | null {
992
+ return this.client;
993
+ }
994
+
995
+ isConnected(): boolean {
996
+ return this.client !== null && !this.client.destroyed;
997
+ }
998
+
999
+ destroy(): void {
1000
+ if (this.client && !this.client.destroyed) {
1001
+ this.client.close();
1002
+ this.client = null;
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ class AdvancedTLSClient {
1008
+ private profile: ChromeProfile;
1009
+ private sessionCache: Map<string, { tlsManager: TLSSocketManager; http2Manager: HTTP2ClientManager; lastUsed: number }> = new Map();
1010
+ private cookieJar: CookieJar = new CookieJar();
1011
+ private maxCachedSessions: number = 10;
1012
+ private sessionTimeout: number = 300000;
1013
+ private cleanupInterval: NodeJS.Timeout | null = null;
1014
+
1015
+ constructor(profileName?: string) {
1016
+ this.profile = profileName ? ProfileRegistry.get(profileName) : ProfileRegistry.latest;
1017
+ this.startSessionCleanup();
1018
+ }
1019
+
1020
+ private startSessionCleanup(): void {
1021
+ this.cleanupInterval = setInterval(() => {
1022
+ const now = Date.now();
1023
+ for (const [key, session] of this.sessionCache) {
1024
+ if (now - session.lastUsed > this.sessionTimeout) {
1025
+ session.http2Manager.destroy();
1026
+ session.tlsManager.destroy();
1027
+ this.sessionCache.delete(key);
1028
+ }
1029
+ }
1030
+ }, 60000);
1031
+
1032
+ if (this.cleanupInterval.unref) {
1033
+ this.cleanupInterval.unref();
1034
+ }
1035
+ }
1036
+
1037
+ async request(url: string, options: RequestOptions = {}): Promise<Response> {
1038
+ const maxRedirects = options.maxRedirects !== undefined ? options.maxRedirects : 5;
1039
+ const followRedirects = options.followRedirects !== false;
1040
+ let redirectCount = 0;
1041
+ let currentUrl = url;
1042
+ let response: Response;
1043
+
1044
+ while (true) {
1045
+ response = await this.performRequest(currentUrl, options);
1046
+
1047
+ if (followRedirects && [301, 302, 303, 307, 308].includes(response.statusCode)) {
1048
+ if (redirectCount >= maxRedirects) {
1049
+ throw new Error(`Too many redirects (max: ${maxRedirects})`);
1050
+ }
1051
+
1052
+ const locationHeader = response.headers['location'];
1053
+ if (!locationHeader) {
1054
+ break;
1055
+ }
1056
+
1057
+ const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
1058
+ currentUrl = new URL(location, currentUrl).toString();
1059
+ redirectCount++;
1060
+
1061
+ if ([301, 302, 303].includes(response.statusCode)) {
1062
+ options.method = 'GET';
1063
+ delete options.body;
1064
+ }
1065
+ } else {
1066
+ break;
1067
+ }
1068
+ }
1069
+
1070
+ return response;
1071
+ }
1072
+
1073
+ private async performRequest(url: string, options: RequestOptions = {}): Promise<Response> {
1074
+ const parsedUrl = new URL(url);
1075
+ const hostname = parsedUrl.hostname;
1076
+ const port = parsedUrl.port ? parseInt(parsedUrl.port) : 443;
1077
+ const path = parsedUrl.pathname + parsedUrl.search;
1078
+
1079
+ const cacheKey = `${hostname}:${port}`;
1080
+ let session = this.sessionCache.get(cacheKey);
1081
+
1082
+ if (!session || !this.isSessionAlive(session)) {
1083
+ if (session) {
1084
+ session.http2Manager.destroy();
1085
+ session.tlsManager.destroy();
1086
+ this.sessionCache.delete(cacheKey);
1087
+ }
1088
+
1089
+ const tlsManager = new TLSSocketManager(this.profile);
1090
+ const tlsSocket = await tlsManager.connect(hostname, port);
1091
+ const http2Manager = new HTTP2ClientManager(tlsSocket, this.profile);
1092
+ await http2Manager.createSession();
1093
+
1094
+ session = { tlsManager, http2Manager, lastUsed: Date.now() };
1095
+
1096
+ if (this.sessionCache.size >= this.maxCachedSessions) {
1097
+ const oldestKey = Array.from(this.sessionCache.entries())
1098
+ .sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0][0];
1099
+ const oldest = this.sessionCache.get(oldestKey)!;
1100
+ oldest.http2Manager.destroy();
1101
+ oldest.tlsManager.destroy();
1102
+ this.sessionCache.delete(oldestKey);
1103
+ }
1104
+
1105
+ this.sessionCache.set(cacheKey, session);
1106
+ }
1107
+
1108
+ session.lastUsed = Date.now();
1109
+
1110
+ const headers = this.buildHeaders(hostname, path, options);
1111
+ const stream = session.http2Manager.request(path, headers);
1112
+
1113
+ return new Promise((resolve, reject) => {
1114
+ const chunks: Buffer[] = [];
1115
+ let responseHeaders: Record<string, string | string[]> = {};
1116
+ let statusCode = 0;
1117
+ const responseTime = Date.now();
1118
+
1119
+ stream.on('response', (headers) => {
1120
+ responseHeaders = headers;
1121
+ statusCode = parseInt(headers[':status'] as string);
1122
+ session!.tlsManager.getTiming().response = Date.now() - responseTime;
1123
+
1124
+ const setCookieHeaders = headers['set-cookie'];
1125
+ if (setCookieHeaders) {
1126
+ const cookieArray = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
1127
+ this.cookieJar.parseSetCookie(hostname, cookieArray);
1128
+ }
1129
+ });
1130
+
1131
+ stream.on('data', (chunk) => chunks.push(chunk));
1132
+
1133
+ stream.on('end', async () => {
1134
+ stream.removeAllListeners();
1135
+
1136
+ let body = Buffer.concat(chunks);
1137
+ const timing = session!.tlsManager.getTiming();
1138
+ timing.end = Date.now();
1139
+
1140
+ if (options.decompress !== false) {
1141
+ const encoding = responseHeaders['content-encoding'];
1142
+ if (encoding === 'gzip') body = await gunzip(body);
1143
+ else if (encoding === 'br') body = await brotliDecompress(body);
1144
+ }
1145
+
1146
+ let text: string | undefined;
1147
+ let json: any;
1148
+
1149
+ try {
1150
+ text = body.toString('utf-8');
1151
+ if (responseHeaders['content-type']?.toString().includes('application/json')) {
1152
+ json = JSON.parse(text);
1153
+ }
1154
+ } catch (e) {}
1155
+
1156
+ const ja3 = this.generateJA3();
1157
+
1158
+ resolve({
1159
+ statusCode,
1160
+ headers: responseHeaders,
1161
+ body,
1162
+ text,
1163
+ json,
1164
+ timing,
1165
+ fingerprints: {
1166
+ ja3,
1167
+ ja3Hash: crypto.createHash('md5').update(ja3).digest('hex'),
1168
+ 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`
1169
+ }
1170
+ });
1171
+ });
1172
+
1173
+ stream.on('error', (err) => {
1174
+ stream.removeAllListeners();
1175
+ reject(err);
1176
+ });
1177
+
1178
+ if (options.body) {
1179
+ const bodyBuffer = Buffer.isBuffer(options.body) ? options.body : Buffer.from(options.body);
1180
+ stream.write(bodyBuffer);
1181
+ }
1182
+
1183
+ stream.end();
1184
+ });
1185
+ }
1186
+
1187
+ private generateJA3(): string {
1188
+ const version = '771';
1189
+ const ciphers = this.profile.tls.cipherSuites
1190
+ .filter(c => !c.startsWith('GREASE_'))
1191
+ .map(c => {
1192
+ const map: any = {
1193
+ 'TLS_AES_128_GCM_SHA256': '4865',
1194
+ 'TLS_AES_256_GCM_SHA384': '4866',
1195
+ 'TLS_CHACHA20_POLY1305_SHA256': '4867',
1196
+ 'ECDHE-ECDSA-AES128-GCM-SHA256': '49195',
1197
+ 'ECDHE-RSA-AES128-GCM-SHA256': '49199',
1198
+ 'ECDHE-ECDSA-AES256-GCM-SHA384': '49196',
1199
+ 'ECDHE-RSA-AES256-GCM-SHA384': '49200',
1200
+ 'ECDHE-ECDSA-CHACHA20-POLY1305': '52393',
1201
+ 'ECDHE-RSA-CHACHA20-POLY1305': '52392',
1202
+ 'ECDHE-RSA-AES128-SHA': '49171',
1203
+ 'ECDHE-RSA-AES256-SHA': '49172',
1204
+ 'AES128-GCM-SHA256': '156',
1205
+ 'AES256-GCM-SHA384': '157',
1206
+ 'AES128-SHA': '47',
1207
+ 'AES256-SHA': '53'
1208
+ };
1209
+ return map[c] || '0';
1210
+ }).join('-');
1211
+
1212
+ const extensions = this.profile.tls.extensions
1213
+ .filter(e => typeof e === 'number')
1214
+ .join('-');
1215
+ const groups = this.profile.tls.supportedGroups
1216
+ .filter(g => !g.startsWith('GREASE_'))
1217
+ .map(g => {
1218
+ const map: any = {
1219
+ 'X25519': '29',
1220
+ 'prime256v1': '23',
1221
+ 'secp384r1': '24',
1222
+ 'secp521r1': '25',
1223
+ 'X25519Kyber768': '25497'
1224
+ };
1225
+ return map[g] || '0';
1226
+ }).join('-');
1227
+ const ecFormats = this.profile.tls.ecPointFormats.join('-');
1228
+
1229
+ return `${version},${ciphers},${extensions},${groups},${ecFormats}`;
1230
+ }
1231
+
1232
+ private buildHeaders(hostname: string, path: string, options: RequestOptions): Record<string, string> {
1233
+ const method = (options.method || 'GET').toUpperCase();
1234
+
1235
+ const headers: Record<string, string> = {
1236
+ ':method': method,
1237
+ ':authority': hostname,
1238
+ ':scheme': 'https',
1239
+ ':path': path
1240
+ };
1241
+
1242
+ if (this.profile.http2.headerOrder) {
1243
+ const defaultHeaders: Record<string, string> = {
1244
+ 'sec-ch-ua': this.profile.secChUa,
1245
+ 'sec-ch-ua-mobile': this.profile.secChUaMobile,
1246
+ 'sec-ch-ua-platform': this.profile.secChUaPlatform,
1247
+ 'upgrade-insecure-requests': this.profile.upgradeInsecureRequests || '1',
1248
+ 'user-agent': this.profile.userAgent,
1249
+ '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',
1250
+ 'sec-fetch-site': this.profile.secFetchSite,
1251
+ 'sec-fetch-mode': this.profile.secFetchMode,
1252
+ 'sec-fetch-dest': this.profile.secFetchDest,
1253
+ 'accept-encoding': 'gzip, deflate, br, zstd',
1254
+ 'accept-language': this.profile.acceptLanguage || 'en-US,en;q=0.9'
1255
+ };
1256
+
1257
+ if (this.profile.priority) {
1258
+ defaultHeaders['priority'] = this.profile.priority;
1259
+ }
1260
+
1261
+ this.profile.http2.headerOrder.forEach(headerName => {
1262
+ if (defaultHeaders[headerName]) {
1263
+ headers[headerName] = defaultHeaders[headerName];
1264
+ }
1265
+ });
1266
+ }
1267
+
1268
+ const cookies = options.cookies
1269
+ ? Object.entries(options.cookies).map(([k, v]) => `${k}=${v}`).join('; ')
1270
+ : this.cookieJar.getCookies(hostname, path);
1271
+
1272
+ if (cookies) headers['cookie'] = cookies;
1273
+
1274
+ if (options.headers) Object.assign(headers, options.headers);
1275
+
1276
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
1277
+ if (!headers['content-type']) headers['content-type'] = 'application/x-www-form-urlencoded';
1278
+ if (options.body) {
1279
+ const bodyLength = Buffer.isBuffer(options.body) ? options.body.length : Buffer.byteLength(options.body);
1280
+ headers['content-length'] = bodyLength.toString();
1281
+ }
1282
+ }
1283
+
1284
+ return headers;
1285
+ }
1286
+
1287
+ private isSessionAlive(session: { http2Manager: HTTP2ClientManager }): boolean {
1288
+ try {
1289
+ return session.http2Manager.isConnected();
1290
+ } catch {
1291
+ return false;
1292
+ }
1293
+ }
1294
+
1295
+ getCookies(domain: string): string {
1296
+ return this.cookieJar.getCookies(domain);
1297
+ }
1298
+
1299
+ setCookie(domain: string, name: string, value: string, options?: { expires?: Date; path?: string }): void {
1300
+ this.cookieJar.setCookie(domain, name, value, options);
1301
+ }
1302
+
1303
+ destroy(): void {
1304
+ if (this.cleanupInterval) {
1305
+ clearInterval(this.cleanupInterval);
1306
+ this.cleanupInterval = null;
1307
+ }
1308
+
1309
+ this.sessionCache.forEach(session => {
1310
+ try {
1311
+ session.http2Manager.destroy();
1312
+ session.tlsManager.destroy();
1313
+ } catch (e) {}
1314
+ });
1315
+ this.sessionCache.clear();
1316
+ }
1317
+ }
1318
+
1319
+ export { AdvancedTLSClient };
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "advanced-tls-client",
3
+ "version": "1.0.0",
4
+ "description": "Advanced TLS/HTTP2 Client with JA3 fingerprinting",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "scripts": {},
8
+ "keywords": ["tls", "http2", "ja3", "fingerprint"],
9
+ "author": "",
10
+ "license": "MIT",
11
+ "engines": {
12
+ "node": ">=16.0.0"
13
+ },
14
+ "dependencies": {
15
+ "@types/node": "^20.0.0"
16
+ }
17
+ }