advanced-tls-client 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -139,7 +139,7 @@ declare class AdvancedTLSClient {
139
139
  private cleanupInterval;
140
140
  private defaults;
141
141
  constructor(profileName?: string);
142
- setup(uri: string, options: RequestOptions): any;
142
+ private setup;
143
143
  request(uri: string, data?: any, options?: RequestOptions, callback?: Function): Promise<FullResponse | PassThrough>;
144
144
  private generateJA3;
145
145
  private buildMultipart;
@@ -147,4 +147,4 @@ declare class AdvancedTLSClient {
147
147
  private flatten;
148
148
  destroy(): void;
149
149
  }
150
- export { AdvancedTLSClient, ProfileRegistry, FullResponse };
150
+ export { AdvancedTLSClient, ProfileRegistry };
package/dist/index.js CHANGED
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
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"));
40
39
  const http2 = __importStar(require("http2"));
41
40
  const crypto = __importStar(require("crypto"));
42
41
  const events_1 = require("events");
@@ -46,9 +45,9 @@ const stream_1 = require("stream");
46
45
  const fs = __importStar(require("fs"));
47
46
  const path = __importStar(require("path"));
48
47
  const url = __importStar(require("url"));
49
- const gunzip = (0, util_1.promisify)(zlib.gunzip);
50
- const brotliDecompress = (0, util_1.promisify)(zlib.brotliDecompress);
51
- const inflate = (0, util_1.promisify)(zlib.inflate);
48
+ const brotliDecompressAsync = (0, util_1.promisify)(zlib.brotliDecompress);
49
+ const gunzipAsync = (0, util_1.promisify)(zlib.gunzip);
50
+ const inflateAsync = (0, util_1.promisify)(zlib.inflate);
52
51
  const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
53
52
  class ProfileRegistry {
54
53
  static getGrease() {
@@ -675,16 +674,15 @@ class CookieJar {
675
674
  }
676
675
  }
677
676
  }
678
- var Protocol;
679
- (function (Protocol) {
680
- Protocol["HTTP1"] = "http1";
681
- Protocol["HTTP2"] = "http2";
682
- })(Protocol || (Protocol = {}));
677
+ const Protocol = {
678
+ HTTP1: 'http1',
679
+ HTTP2: 'http2'
680
+ };
683
681
  class UnifiedClientManager extends events_1.EventEmitter {
684
682
  constructor(tlsSocket, profile, hostname, cookieJar) {
685
683
  super();
686
684
  this.http2Client = null;
687
- this.negotiatedProtocol = Protocol.HTTP1;
685
+ this.negotiatedProtocol = 'http1';
688
686
  this.tlsSocket = tlsSocket;
689
687
  this.profile = profile;
690
688
  this.hostname = hostname;
@@ -693,17 +691,17 @@ class UnifiedClientManager extends events_1.EventEmitter {
693
691
  async initialize() {
694
692
  const alpn = this.tlsSocket.alpnProtocol;
695
693
  if (alpn === 'h2') {
696
- this.negotiatedProtocol = Protocol.HTTP2;
694
+ this.negotiatedProtocol = 'http2';
697
695
  await this.createHttp2Session();
698
696
  }
699
697
  else {
700
- this.negotiatedProtocol = Protocol.HTTP1;
698
+ this.negotiatedProtocol = 'http1';
701
699
  }
702
700
  }
703
701
  async createHttp2Session() {
704
702
  return new Promise((resolve, reject) => {
705
703
  const settings = {};
706
- this.profile.http2.settingsOrder.forEach(id => {
704
+ this.profile.http2.settingsOrder.forEach((id) => {
707
705
  switch (id) {
708
706
  case 1:
709
707
  settings.headerTableSize = this.profile.http2.headerTableSize;
@@ -711,17 +709,9 @@ class UnifiedClientManager extends events_1.EventEmitter {
711
709
  case 2:
712
710
  settings.enablePush = this.profile.http2.enablePush === 1;
713
711
  break;
714
- case 3:
715
- if (this.profile.http2.maxConcurrentStreams)
716
- settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
717
- break;
718
712
  case 4:
719
713
  settings.initialWindowSize = this.profile.http2.initialWindowSize;
720
714
  break;
721
- case 5:
722
- if (this.profile.http2.maxFrameSize)
723
- settings.maxFrameSize = this.profile.http2.maxFrameSize;
724
- break;
725
715
  case 6:
726
716
  if (this.profile.http2.maxHeaderListSize)
727
717
  settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
@@ -732,7 +722,7 @@ class UnifiedClientManager extends events_1.EventEmitter {
732
722
  createConnection: () => this.tlsSocket,
733
723
  settings
734
724
  });
735
- this.http2Client.once('connect', () => resolve());
725
+ this.http2Client.once('connect', resolve);
736
726
  this.http2Client.once('error', reject);
737
727
  this.http2Client.once('timeout', () => reject(new Error('HTTP/2 session timeout')));
738
728
  });
@@ -749,7 +739,7 @@ class UnifiedClientManager extends events_1.EventEmitter {
749
739
  delete cleanHeaders['Upgrade'];
750
740
  delete cleanHeaders['transfer-encoding'];
751
741
  delete cleanHeaders['Transfer-Encoding'];
752
- if (this.negotiatedProtocol === Protocol.HTTP2 && this.http2Client && !this.http2Client.destroyed) {
742
+ if (this.negotiatedProtocol === 'http2' && this.http2Client && !this.http2Client.destroyed) {
753
743
  await this.requestHttp2(path, options, timingStart, cleanHeaders, post_data, out, callback);
754
744
  }
755
745
  else {
@@ -757,8 +747,6 @@ class UnifiedClientManager extends events_1.EventEmitter {
757
747
  }
758
748
  }
759
749
  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
750
  const reqHeaders = {
763
751
  ':method': options.method?.toUpperCase() || 'GET',
764
752
  ':authority': this.hostname,
@@ -774,87 +762,113 @@ class UnifiedClientManager extends events_1.EventEmitter {
774
762
  ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
775
763
  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
764
  };
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
765
  const chunks = [];
787
766
  const rawChunks = [];
788
- stream.on('data', (chunk) => {
789
- // Convert string to Buffer if needed
790
- const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
791
- rawChunks.push(bufferChunk);
792
- chunks.push(bufferChunk);
793
- out.write(bufferChunk);
794
- });
795
- stream.once('end', async () => {
767
+ let finished = false;
768
+ const finish = async (error) => {
769
+ if (finished)
770
+ return;
771
+ finished = true;
772
+ stream.removeAllListeners();
773
+ if (error) {
774
+ out.emit('error', error);
775
+ if (callback)
776
+ setImmediate(() => callback(error));
777
+ return;
778
+ }
796
779
  let body = Buffer.concat(chunks);
797
780
  response.bytes = body.length;
798
781
  response.raw = Buffer.concat(rawChunks);
799
782
  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;
783
+ const encoding = response.headers['content-encoding']?.toLowerCase();
802
784
  if (encoding) {
803
785
  try {
804
786
  if (encoding.includes('gzip'))
805
- body = await gunzip(body);
787
+ body = await gunzipAsync(body);
806
788
  else if (encoding.includes('br'))
807
- body = await brotliDecompress(body);
789
+ body = await brotliDecompressAsync(body);
808
790
  else if (encoding.includes('deflate'))
809
- body = await inflate(body);
810
- }
811
- catch (e) {
812
- console.error('Decompression error:', e);
791
+ body = await inflateAsync(body);
813
792
  }
793
+ catch { }
814
794
  }
815
795
  }
816
796
  response.body = body;
817
797
  try {
818
798
  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')) {
799
+ if (response.headers['content-type']?.includes('application/json')) {
822
800
  response.json = JSON.parse(response.text);
823
801
  }
824
802
  }
825
803
  catch { }
826
804
  response.timing.end = Date.now() - timingStart;
827
805
  response.timing.total = response.timing.end;
828
- out.end();
806
+ if (!out.writableEnded && !out.destroyed)
807
+ out.end();
829
808
  if (callback)
830
- callback(null, response, response.body);
809
+ setImmediate(() => callback(null, response, response.body));
810
+ };
811
+ stream.once('response', (hdrs) => {
812
+ response.statusCode = hdrs[':status'];
813
+ response.headers = hdrs;
814
+ if (hdrs['set-cookie'])
815
+ this.cookieJar.parseSetCookie(this.hostname, hdrs['set-cookie']);
816
+ response.timing.response = Date.now() - timingStart;
817
+ out.emit('header', response.statusCode, hdrs);
818
+ out.emit('headers', hdrs);
831
819
  });
832
- stream.once('error', (err) => {
833
- out.emit('err', err);
834
- if (callback)
835
- callback(err);
820
+ stream.on('data', (chunk) => {
821
+ if (finished || out.destroyed || out.writableEnded)
822
+ return;
823
+ const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
824
+ rawChunks.push(bufferChunk);
825
+ chunks.push(bufferChunk);
826
+ if (!out.write(bufferChunk))
827
+ stream.pause();
836
828
  });
829
+ out.on('drain', () => stream.resume());
830
+ stream.once('end', finish);
831
+ stream.once('error', finish);
837
832
  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);
833
+ if (Buffer.isBuffer(post_data) || typeof post_data === 'string') {
834
+ if (!stream.closed && !stream.destroyed && stream.writable) {
835
+ try {
836
+ stream.write(post_data);
837
+ }
838
+ catch { }
839
+ }
840
+ setImmediate(() => {
841
+ if (!stream.closed && !stream.destroyed && stream.writable) {
842
+ stream.end();
843
+ }
844
+ });
845
+ }
846
+ else if (post_data instanceof stream_1.Readable) {
847
+ post_data.pipe(stream, { end: true });
848
+ post_data.on('error', err => stream.destroy(err));
849
+ }
850
+ else {
851
+ setImmediate(() => {
852
+ if (stream.writable)
853
+ stream.end();
854
+ });
855
+ }
842
856
  }
843
857
  else {
844
- stream.end();
858
+ setImmediate(() => {
859
+ if (stream.writable)
860
+ stream.end();
861
+ });
845
862
  }
846
863
  }
847
864
  async requestHttp1(path, options, timingStart, headers, post_data, out, callback) {
848
865
  const reqOptions = {
849
866
  method: options.method?.toUpperCase() || 'GET',
850
867
  path,
851
- headers: {
852
- host: this.hostname,
853
- ...headers
854
- },
868
+ headers: { host: this.hostname, ...headers },
855
869
  createConnection: () => this.tlsSocket
856
870
  };
857
- const req = https.request(reqOptions);
871
+ const req = require('https').request(reqOptions);
858
872
  const response = new events_1.EventEmitter();
859
873
  response.timing = { socket: timingStart, lookup: 0, connect: 0, secureConnect: 0, response: 0, end: 0, total: 0 };
860
874
  response.fingerprints = {
@@ -862,6 +876,54 @@ class UnifiedClientManager extends events_1.EventEmitter {
862
876
  ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
863
877
  akamai: ''
864
878
  };
879
+ const chunks = [];
880
+ const rawChunks = [];
881
+ let finished = false;
882
+ const finish = async (error, res) => {
883
+ if (finished)
884
+ return;
885
+ finished = true;
886
+ req.removeAllListeners();
887
+ if (res)
888
+ res.removeAllListeners();
889
+ if (error) {
890
+ out.emit('error', error);
891
+ if (callback)
892
+ setImmediate(() => callback(error));
893
+ return;
894
+ }
895
+ let body = Buffer.concat(chunks);
896
+ response.bytes = body.length;
897
+ response.raw = Buffer.concat(rawChunks);
898
+ if (options.decompress !== false && res?.headers) {
899
+ const encoding = (res.headers['content-encoding'] || '').toLowerCase();
900
+ if (encoding) {
901
+ try {
902
+ if (encoding.includes('gzip'))
903
+ body = await gunzipAsync(body);
904
+ else if (encoding.includes('br'))
905
+ body = await brotliDecompressAsync(body);
906
+ else if (encoding.includes('deflate'))
907
+ body = await inflateAsync(body);
908
+ }
909
+ catch { }
910
+ }
911
+ }
912
+ response.body = body;
913
+ try {
914
+ response.text = body.toString('utf-8');
915
+ if (res.headers['content-type']?.includes('application/json')) {
916
+ response.json = JSON.parse(response.text);
917
+ }
918
+ }
919
+ catch { }
920
+ response.timing.end = Date.now() - timingStart;
921
+ response.timing.total = response.timing.end;
922
+ if (!out.writableEnded && !out.destroyed)
923
+ out.end();
924
+ if (callback)
925
+ setImmediate(() => callback(null, response, response.body));
926
+ };
865
927
  req.once('response', (res) => {
866
928
  response.statusCode = res.statusCode;
867
929
  response.headers = res.headers;
@@ -870,68 +932,29 @@ class UnifiedClientManager extends events_1.EventEmitter {
870
932
  response.timing.response = Date.now() - timingStart;
871
933
  out.emit('header', response.statusCode, res.headers);
872
934
  out.emit('headers', res.headers);
873
- const chunks = [];
874
- const rawChunks = [];
875
935
  res.on('data', (chunk) => {
876
- // Convert string to Buffer if needed
936
+ if (finished || out.destroyed || out.writableEnded)
937
+ return;
877
938
  const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
878
939
  rawChunks.push(bufferChunk);
879
940
  chunks.push(bufferChunk);
880
941
  out.write(bufferChunk);
881
942
  });
882
- res.once('end', async () => {
883
- let body = Buffer.concat(chunks);
884
- response.bytes = body.length;
885
- response.raw = Buffer.concat(rawChunks);
886
- if (options.decompress !== false) {
887
- const encodingHeader = res.headers['content-encoding'];
888
- const encoding = Array.isArray(encodingHeader) ? encodingHeader[0]?.toLowerCase() : typeof encodingHeader === 'string' ? encodingHeader.toLowerCase() : undefined;
889
- if (encoding) {
890
- try {
891
- if (encoding.includes('gzip'))
892
- body = await gunzip(body);
893
- else if (encoding.includes('br'))
894
- body = await brotliDecompress(body);
895
- else if (encoding.includes('deflate'))
896
- body = await inflate(body);
897
- }
898
- catch (e) {
899
- console.error('Decompression error:', e);
900
- }
901
- }
902
- }
903
- response.body = body;
904
- try {
905
- response.text = body.toString('utf-8');
906
- const contentTypeHeader = res.headers['content-type'];
907
- const ct = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined;
908
- if (ct && ct.includes('application/json')) {
909
- response.json = JSON.parse(response.text);
910
- }
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);
923
- });
924
- });
925
- req.once('error', (err) => {
926
- out.emit('err', err);
927
- if (callback)
928
- callback(err);
943
+ res.once('end', () => finish(null, res));
944
+ res.once('error', finish);
929
945
  });
946
+ req.once('error', finish);
930
947
  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)
948
+ if (Buffer.isBuffer(post_data) || typeof post_data === 'string') {
949
+ req.write(post_data);
950
+ req.end();
951
+ }
952
+ else if (post_data instanceof stream_1.Readable) {
934
953
  post_data.pipe(req);
954
+ }
955
+ else {
956
+ req.end();
957
+ }
935
958
  }
936
959
  else {
937
960
  req.end();
@@ -939,9 +962,9 @@ class UnifiedClientManager extends events_1.EventEmitter {
939
962
  }
940
963
  generateJA3() {
941
964
  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('-');
965
+ const ciphers = this.profile.tls.cipherSuites.filter((c) => c < 0xff00).join('-');
966
+ const extensions = this.profile.tls.extensions.filter((e) => typeof e === 'number' && e < 0xff00).join('-');
967
+ const curves = this.profile.tls.supportedGroups.filter((g) => g < 0xff00).join('-');
945
968
  const ecPoints = this.profile.tls.ecPointFormats.join('-');
946
969
  return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
947
970
  }
@@ -950,9 +973,6 @@ class UnifiedClientManager extends events_1.EventEmitter {
950
973
  this.http2Client.destroy();
951
974
  this.http2Client = null;
952
975
  }
953
- getProtocol() {
954
- return this.negotiatedProtocol;
955
- }
956
976
  }
957
977
  class TLSSocketManager extends events_1.EventEmitter {
958
978
  constructor(profile, proxy) {
@@ -983,6 +1003,7 @@ class TLSSocketManager extends events_1.EventEmitter {
983
1003
  if (this.proxy) {
984
1004
  const proxyUrl = new url.URL(this.proxy);
985
1005
  this.socket = net.connect(+proxyUrl.port || 80, proxyUrl.hostname, () => {
1006
+ this.timing.connect = Date.now() - this.startTime;
986
1007
  let request = `CONNECT ${hostname}:${port} HTTP/1.1\r\nHost: ${hostname}:${port}\r\n`;
987
1008
  if (proxyUrl.username && proxyUrl.password) {
988
1009
  const auth = Buffer.from(`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`).toString('base64');
@@ -992,14 +1013,19 @@ class TLSSocketManager extends events_1.EventEmitter {
992
1013
  this.socket.write(request);
993
1014
  });
994
1015
  let response = '';
995
- this.socket.on('data', (chunk) => {
1016
+ const onData = (chunk) => {
996
1017
  response += chunk.toString();
997
1018
  if (response.includes('\r\n\r\n')) {
998
- if (!response.startsWith('HTTP/1.1 200'))
1019
+ this.socket.removeListener('data', onData);
1020
+ if (!response.startsWith('HTTP/1.1 200')) {
1021
+ clearTimeout(timeout);
1022
+ this.destroy();
999
1023
  return reject(new Error('Proxy failed: ' + response.split('\r\n')[0]));
1024
+ }
1000
1025
  this.proceedWithTLS(resolve, reject, timeout);
1001
1026
  }
1002
- });
1027
+ };
1028
+ this.socket.on('data', onData);
1003
1029
  }
1004
1030
  else {
1005
1031
  this.socket = new net.Socket();
@@ -1176,7 +1202,7 @@ class AdvancedTLSClient {
1176
1202
  if (options.multipart) {
1177
1203
  const boundary = options.boundary || this.defaults.boundary;
1178
1204
  post_data = await this.buildMultipart(data, boundary);
1179
- config.headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
1205
+ config.headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
1180
1206
  }
1181
1207
  else if (data instanceof stream_1.Readable) {
1182
1208
  post_data = data;
@@ -1202,78 +1228,63 @@ class AdvancedTLSClient {
1202
1228
  const cookies = this.cookieJar.getCookies(hostname, parsed.pathname, parsed.protocol === 'https:');
1203
1229
  if (cookies)
1204
1230
  config.headers['cookie'] = cookies;
1205
- // MODE LEGACY: Jika ada callback, kembalikan stream
1231
+ if (options.stream === true) {
1232
+ session.clientManager.request(path, options, startTime, config.headers, post_data, out);
1233
+ return out;
1234
+ }
1206
1235
  if (callback) {
1207
- await session.clientManager.request(path, options, startTime, config.headers, post_data, out, callback);
1236
+ const typedCallback = callback;
1237
+ session.clientManager.request(path, options, startTime, config.headers, post_data, out, typedCallback).catch(err => {
1238
+ if (typedCallback)
1239
+ typedCallback(err);
1240
+ });
1208
1241
  return out;
1209
1242
  }
1210
- // MODE BARU: Kembalikan object response lengkap
1211
1243
  return new Promise((resolve, reject) => {
1212
1244
  let statusCode = 0;
1213
1245
  let headers = {};
1214
1246
  const chunks = [];
1215
- out.on('header', (status, hdrs) => {
1216
- statusCode = status;
1217
- headers = hdrs;
1218
- });
1219
- out.on('headers', (hdrs) => {
1220
- headers = hdrs;
1221
- });
1222
- out.on('data', (chunk) => {
1223
- chunks.push(chunk);
1224
- });
1225
- out.once('end', async () => {
1247
+ let done = false;
1248
+ const complete = async (err) => {
1249
+ if (done)
1250
+ return;
1251
+ done = true;
1252
+ if (err)
1253
+ return reject(err);
1226
1254
  let body = Buffer.concat(chunks);
1227
- // Auto-decompress jika diminta
1228
1255
  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;
1256
+ const encoding = headers['content-encoding']?.toLowerCase();
1235
1257
  if (encoding) {
1236
1258
  try {
1237
1259
  if (encoding.includes('gzip'))
1238
- body = await gunzip(body);
1260
+ body = await gunzipAsync(body);
1239
1261
  else if (encoding.includes('br'))
1240
- body = await brotliDecompress(body);
1262
+ body = await brotliDecompressAsync(body);
1241
1263
  else if (encoding.includes('deflate'))
1242
- body = await inflate(body);
1243
- }
1244
- catch (e) {
1245
- console.error('Decompression failed:', e);
1264
+ body = await inflateAsync(body);
1246
1265
  }
1266
+ catch { }
1247
1267
  }
1248
1268
  }
1249
1269
  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')) {
1270
+ let parsedJson;
1271
+ const ct = headers['content-type']?.toLowerCase();
1272
+ if (ct?.includes('application/json')) {
1258
1273
  try {
1259
1274
  parsedJson = JSON.parse(text);
1260
1275
  }
1261
1276
  catch { }
1262
1277
  }
1263
- // Generate fingerprints
1264
- const ja3 = this.generateJA3();
1265
- const ja3Hash = crypto.createHash('md5').update(ja3).digest('hex');
1266
- const akamai = `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`;
1267
- const fullResponse = {
1278
+ resolve({
1268
1279
  statusCode,
1269
1280
  headers,
1270
1281
  body,
1271
1282
  text,
1272
1283
  json: parsedJson,
1273
1284
  fingerprints: {
1274
- ja3,
1275
- ja3Hash,
1276
- akamai
1285
+ ja3: this.generateJA3(),
1286
+ ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
1287
+ 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`
1277
1288
  },
1278
1289
  timing: {
1279
1290
  socket: startTime,
@@ -1284,19 +1295,26 @@ class AdvancedTLSClient {
1284
1295
  end: Date.now() - startTime,
1285
1296
  total: Date.now() - startTime
1286
1297
  }
1287
- };
1288
- resolve(fullResponse);
1298
+ });
1299
+ };
1300
+ out.on('header', (status, hdrs) => {
1301
+ statusCode = status;
1302
+ headers = hdrs;
1289
1303
  });
1290
- out.once('error', (err) => reject(err));
1291
- // Jalankan request
1292
- session.clientManager.request(path, options, startTime, config.headers, post_data, out).catch(reject);
1304
+ out.on('data', (chunk) => {
1305
+ if (!done)
1306
+ chunks.push(chunk);
1307
+ });
1308
+ out.once('end', () => complete());
1309
+ out.once('error', complete);
1310
+ session.clientManager.request(path, options, startTime, config.headers, post_data, out).catch(complete);
1293
1311
  });
1294
1312
  }
1295
1313
  generateJA3() {
1296
1314
  const version = '771';
1297
- const ciphers = this.profile.tls.cipherSuites.filter(c => c < 0xff00).join('-');
1298
- const extensions = this.profile.tls.extensions.filter(e => typeof e === 'number' && e < 0xff00).join('-');
1299
- const curves = this.profile.tls.supportedGroups.filter(g => g < 0xff00).join('-');
1315
+ const ciphers = this.profile.tls.cipherSuites.filter((c) => c < 0xff00).join('-');
1316
+ const extensions = this.profile.tls.extensions.filter((e) => typeof e === 'number' && e < 0xff00).join('-');
1317
+ const curves = this.profile.tls.supportedGroups.filter((g) => g < 0xff00).join('-');
1300
1318
  const ecPoints = this.profile.tls.ecPointFormats.join('-');
1301
1319
  return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
1302
1320
  }
@@ -1318,30 +1336,18 @@ class AdvancedTLSClient {
1318
1336
  done();
1319
1337
  continue;
1320
1338
  }
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
- });
1334
- }
1339
+ const part = Buffer.isBuffer(value) ? { buffer: value, content_type: 'application/octet-stream' } : (value.buffer || value.file || value.content_type) ? value : { value };
1340
+ this.generateMultipart(key, part, boundary).then(section => { body += section; done(); });
1335
1341
  }
1336
1342
  });
1337
1343
  }
1338
1344
  async generateMultipart(name, part, boundary) {
1339
- let return_part = '--' + boundary + '\r\n';
1340
- return_part += 'Content-Disposition: form-data; name="' + name + '"';
1345
+ let return_part = `--${boundary}\r\n`;
1346
+ return_part += `Content-Disposition: form-data; name="${name}"`;
1341
1347
  const append = (data, filename) => {
1342
1348
  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';
1349
+ return_part += `; filename="${encodeURIComponent(filename)}"\r\n`;
1350
+ return_part += `Content-Type: ${part.content_type || 'application/octet-stream'}\r\n\r\n`;
1345
1351
  return_part += data.toString('binary');
1346
1352
  }
1347
1353
  return return_part + '\r\n';
@@ -1354,19 +1360,20 @@ class AdvancedTLSClient {
1354
1360
  return append(data, filename);
1355
1361
  }
1356
1362
  else {
1357
- return_part += '\r\n\r\n';
1358
- return_part += String(part.value || '');
1359
- return return_part + '\r\n';
1363
+ return_part += '\r\n\r\n' + String(part.value || '') + '\r\n';
1364
+ return return_part;
1360
1365
  }
1361
1366
  }
1362
1367
  flatten(object, into = {}, prefix) {
1363
1368
  for (const key in object) {
1364
- const prefix_key = prefix ? prefix + '[' + key + ']' : key;
1369
+ const prefix_key = prefix ? `${prefix}[${key}]` : key;
1365
1370
  const prop = object[key];
1366
- if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type))
1371
+ if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type)) {
1367
1372
  this.flatten(prop, into, prefix_key);
1368
- else
1373
+ }
1374
+ else {
1369
1375
  into[prefix_key] = prop;
1376
+ }
1370
1377
  }
1371
1378
  return into;
1372
1379
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "advanced-tls-client",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {