alipclutch-baileys 8.5.4 → 8.5.5

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.
@@ -20,15 +20,16 @@ const Client_1 = require("./Client");
20
20
  const makeSocket = (config) => {
21
21
  var _a, _b;
22
22
  const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository, } = config;
23
- let url = typeof waWebSocketUrl === 'string' ? new url_1.URL(waWebSocketUrl) : waWebSocketUrl;
24
- config.mobile = config.mobile || url.protocol === 'tcp:';
25
- if (config.mobile && url.protocol !== 'tcp:') {
26
- url = new url_1.URL(`tcp://${Defaults_1.MOBILE_ENDPOINT}:${Defaults_1.MOBILE_PORT}`);
23
+ const url = typeof waWebSocketUrl === 'string' ? new url_1.URL(waWebSocketUrl) : waWebSocketUrl;
24
+ if (config.mobile || url.protocol === 'tcp:') {
25
+ throw new boom_1.Boom('Mobile API is not supported anymore', {
26
+ statusCode: Types_1.DisconnectReason.loggedOut
27
+ });
27
28
  }
28
- if (!config.mobile && url.protocol === 'wss' && ((_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.routingInfo)) {
29
+ if (url.protocol === 'wss' && ((_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.routingInfo)) {
29
30
  url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'));
30
31
  }
31
- const ws = config.socket ? config.socket : config.mobile ? new Client_1.MobileSocketClient(url, config) : new Client_1.WebSocketClient(url, config);
32
+ const ws = new Client_1.WebSocketClient(url, config);
32
33
  ws.connect();
33
34
  const ev = (0, Utils_1.makeEventBuffer)(logger);
34
35
  /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
@@ -36,8 +37,7 @@ const makeSocket = (config) => {
36
37
  /** WA noise protocol wrapper */
37
38
  const noise = (0, Utils_1.makeNoiseHandler)({
38
39
  keyPair: ephemeralKeyPair,
39
- NOISE_HEADER: config.mobile ? Defaults_1.MOBILE_NOISE_HEADER : Defaults_1.NOISE_WA_HEADER,
40
- mobile: config.mobile,
40
+ NOISE_HEADER: Defaults_1.NOISE_WA_HEADER,
41
41
  logger,
42
42
  routingInfo: (_b = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _b === void 0 ? void 0 : _b.routingInfo
43
43
  });
@@ -80,6 +80,25 @@ const makeSocket = (config) => {
80
80
  /** log & process any unexpected errors */
81
81
  const onUnexpectedError = (err, msg) => {
82
82
  logger.error({ err }, `unexpected error in '${msg}'`);
83
+ const message = (err && ((err.stack || err.message) || String(err))).toLowerCase();
84
+ // auto recover from cryptographic desyncs by re-uploading prekeys
85
+ if (message.includes('bad mac') || (message.includes('mac') && message.includes('invalid'))) {
86
+ try {
87
+ uploadPreKeysToServerIfRequired(true)
88
+ .catch(e => logger.warn({ e }, 'failed to re-upload prekeys after bad mac'));
89
+ }
90
+ catch (_e) {
91
+ // ignore
92
+ }
93
+ }
94
+ // gently back off when encountering rate limits (429)
95
+ if (message.includes('429') || message.includes('rate limit')) {
96
+ const wait = Math.min(30000, (config.backoffDelayMs || 5000));
97
+ logger.info({ wait }, 'backing off due to rate limit');
98
+ setTimeout(() => {
99
+ // intentionally empty; wait to delay further sends
100
+ }, wait);
101
+ }
83
102
  };
84
103
  /** await the next incoming message */
85
104
  const awaitNextMessage = async (sendMsg) => {
@@ -116,7 +135,7 @@ const makeSocket = (config) => {
116
135
  let onRecv;
117
136
  let onErr;
118
137
  try {
119
- return await (0, Utils_1.promiseTimeout)(timeoutMs, (resolve, reject) => {
138
+ const result = await (0, Utils_1.promiseTimeout)(timeoutMs, (resolve, reject) => {
120
139
  onRecv = resolve;
121
140
  onErr = err => {
122
141
  reject(err || new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed }));
@@ -125,6 +144,7 @@ const makeSocket = (config) => {
125
144
  ws.on('close', onErr); // if the socket closes, you'll never receive the message
126
145
  ws.off('error', onErr);
127
146
  });
147
+ return result;
128
148
  }
129
149
  finally {
130
150
  ws.off(`TAG:${msgId}`, onRecv);
@@ -138,9 +158,10 @@ const makeSocket = (config) => {
138
158
  node.attrs.id = generateMessageTag();
139
159
  }
140
160
  const msgId = node.attrs.id;
141
- const wait = waitForMessage(msgId, timeoutMs);
142
- await sendNode(node);
143
- const result = await wait;
161
+ const [result] = await Promise.all([
162
+ waitForMessage(msgId, timeoutMs),
163
+ sendNode(node)
164
+ ]);
144
165
  if ('tag' in result) {
145
166
  (0, WABinary_1.assertNodeErrorFree)(result);
146
167
  }
@@ -157,12 +178,9 @@ const makeSocket = (config) => {
157
178
  const result = await awaitNextMessage(init);
158
179
  const handshake = WAProto_1.proto.HandshakeMessage.decode(result);
159
180
  logger.trace({ handshake }, 'handshake recv from WA');
160
- const keyEnc = noise.processHandshake(handshake, creds.noiseKey);
181
+ const keyEnc = await noise.processHandshake(handshake, creds.noiseKey);
161
182
  let node;
162
- if (config.mobile) {
163
- node = (0, Utils_1.generateMobileNode)(config);
164
- }
165
- else if (!creds.me) {
183
+ if (!creds.me) {
166
184
  node = (0, Utils_1.generateRegistrationNode)(creds, config);
167
185
  logger.info({ node }, 'not logged in, attempting registration...');
168
186
  }
@@ -232,11 +250,11 @@ const makeSocket = (config) => {
232
250
  const l0 = frame.tag;
233
251
  const l1 = frame.attrs || {};
234
252
  const l2 = Array.isArray(frame.content) ? (_a = frame.content[0]) === null || _a === void 0 ? void 0 : _a.tag : '';
235
- Object.keys(l1).forEach(key => {
253
+ for (const key of Object.keys(l1)) {
236
254
  anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered;
237
255
  anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered;
238
256
  anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered;
239
- });
257
+ }
240
258
  anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered;
241
259
  anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered;
242
260
  if (!anyTriggered && logger.level === 'debug') {
@@ -365,11 +383,11 @@ const makeSocket = (config) => {
365
383
  }
366
384
  end(new boom_1.Boom(msg || 'Intentional Logout', { statusCode: Types_1.DisconnectReason.loggedOut }));
367
385
  };
368
- /** This method was created by snowi, and implemented by KyuuRzy */
369
386
 
387
+ /** This method was created by snowi, and implemented by KyuuRzy */
370
388
  /** hey bro, if you delete this text */
371
389
  /** you are the most cursed human being who likes to claim other people's property 😹🙌🏻 */
372
- const requestPairingCode = async (phoneNumber, pairKey = "DENZYMOD") => {
390
+ const requestPairingCode = async (phoneNumber, pairKey) => {
373
391
  if (pairKey) {
374
392
  authState.creds.pairingCode = pairKey.toUpperCase();
375
393
  } else {
@@ -431,12 +449,11 @@ const makeSocket = (config) => {
431
449
  });
432
450
 
433
451
  return authState.creds.pairingCode;
434
- };
435
-
452
+ }
436
453
  async function generatePairingKey() {
437
454
  const salt = (0, crypto_1.randomBytes)(32);
438
455
  const randomIv = (0, crypto_1.randomBytes)(16);
439
- const key = (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
456
+ const key = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
440
457
  const ciphered = (0, Utils_1.aesEncryptCTR)(authState.creds.pairingEphemeralKeyPair.public, key, randomIv);
441
458
  return Buffer.concat([salt, randomIv, ciphered]);
442
459
  }
@@ -523,12 +540,18 @@ const makeSocket = (config) => {
523
540
  });
524
541
  // login complete
525
542
  ws.on('CB:success', async (node) => {
526
- await uploadPreKeysToServerIfRequired();
527
- await sendPassiveIq('active');
528
- logger.info('opened connection to WA');
529
- clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
530
- ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
531
- ev.emit('connection.update', { connection: 'open' });
543
+ try {
544
+ await uploadPreKeysToServerIfRequired();
545
+ await sendPassiveIq('active');
546
+ logger.info('opened connection to WA');
547
+ clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
548
+ ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
549
+ ev.emit('connection.update', { connection: 'open' });
550
+ }
551
+ catch (err) {
552
+ logger.error({ err }, 'error opening connection');
553
+ end(err);
554
+ }
532
555
  });
533
556
  ws.on('CB:stream:error', (node) => {
534
557
  logger.error({ node }, 'stream errored out');
@@ -543,11 +566,20 @@ const makeSocket = (config) => {
543
566
  ws.on('CB:ib,,downgrade_webclient', () => {
544
567
  end(new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.multideviceMismatch }));
545
568
  });
569
+ ws.on('CB:ib,,offline_preview', (node) => {
570
+ logger.info('offline preview received', JSON.stringify(node));
571
+ sendNode({
572
+ tag: 'ib',
573
+ attrs: {},
574
+ content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
575
+ });
576
+ });
546
577
  ws.on('CB:ib,,edge_routing', (node) => {
547
578
  const edgeRoutingNode = (0, WABinary_1.getBinaryNodeChild)(node, 'edge_routing');
548
579
  const routingInfo = (0, WABinary_1.getBinaryNodeChild)(edgeRoutingNode, 'routing_info');
549
580
  if (routingInfo === null || routingInfo === void 0 ? void 0 : routingInfo.content) {
550
581
  authState.creds.routingInfo = Buffer.from(routingInfo === null || routingInfo === void 0 ? void 0 : routingInfo.content);
582
+ ev.emit('creds.update', authState.creds);
551
583
  }
552
584
  });
553
585
  let didStartBuffer = false;
@@ -596,7 +628,10 @@ const makeSocket = (config) => {
596
628
  type: 'md',
597
629
  ws,
598
630
  ev,
599
- authState: { creds, keys },
631
+ authState: {
632
+ creds,
633
+ keys
634
+ },
600
635
  signalRepository,
601
636
  get user() {
602
637
  return authState.creds.me;
@@ -1,11 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useMultiFileAuthState = void 0;
4
+ const async_mutex_1 = require("async-mutex");
4
5
  const promises_1 = require("fs/promises");
5
6
  const path_1 = require("path");
6
7
  const WAProto_1 = require("../../WAProto");
7
8
  const auth_utils_1 = require("./auth-utils");
8
9
  const generics_1 = require("./generics");
10
+ const fileLocks = new Map();
11
+ const getFileLock = (path) => {
12
+ let mutex = fileLocks.get(path);
13
+ if (!mutex) {
14
+ mutex = new async_mutex_1.Mutex();
15
+ fileLocks.set(path, mutex);
16
+ }
17
+ return mutex;
18
+ };
9
19
  /**
10
20
  * stores the full authentication state in a single folder.
11
21
  * Far more efficient than singlefileauthstate
@@ -14,13 +24,31 @@ const generics_1 = require("./generics");
14
24
  * Would recommend writing an auth state for use with a proper SQL or No-SQL DB
15
25
  * */
16
26
  const useMultiFileAuthState = async (folder) => {
17
- const writeData = (data, file) => {
18
- return (0, promises_1.writeFile)((0, path_1.join)(folder, fixFileName(file)), JSON.stringify(data, generics_1.BufferJSON.replacer));
27
+ const writeData = async (data, file) => {
28
+ const filePath = (0, path_1.join)(folder, fixFileName(file));
29
+ const mutex = getFileLock(filePath);
30
+ return mutex.acquire().then(async (release) => {
31
+ try {
32
+ await (0, promises_1.writeFile)(filePath, JSON.stringify(data, generics_1.BufferJSON.replacer));
33
+ }
34
+ finally {
35
+ release();
36
+ }
37
+ });
19
38
  };
20
39
  const readData = async (file) => {
21
40
  try {
22
- const data = await (0, promises_1.readFile)((0, path_1.join)(folder, fixFileName(file)), { encoding: 'utf-8' });
23
- return JSON.parse(data, generics_1.BufferJSON.reviver);
41
+ const filePath = (0, path_1.join)(folder, fixFileName(file));
42
+ const mutex = getFileLock(filePath);
43
+ return await mutex.acquire().then(async (release) => {
44
+ try {
45
+ const data = await (0, promises_1.readFile)(filePath, { encoding: 'utf-8' });
46
+ return JSON.parse(data, generics_1.BufferJSON.reviver);
47
+ }
48
+ finally {
49
+ release();
50
+ }
51
+ });
24
52
  }
25
53
  catch (error) {
26
54
  return null;
@@ -28,7 +56,18 @@ const useMultiFileAuthState = async (folder) => {
28
56
  };
29
57
  const removeData = async (file) => {
30
58
  try {
31
- await (0, promises_1.unlink)((0, path_1.join)(folder, fixFileName(file)));
59
+ const filePath = (0, path_1.join)(folder, fixFileName(file));
60
+ const mutex = getFileLock(filePath);
61
+ return mutex.acquire().then(async (release) => {
62
+ try {
63
+ await (0, promises_1.unlink)(filePath);
64
+ }
65
+ catch (_a) {
66
+ }
67
+ finally {
68
+ release();
69
+ }
70
+ });
32
71
  }
33
72
  catch (_a) {
34
73
  }
@@ -72,7 +111,7 @@ const useMultiFileAuthState = async (folder) => {
72
111
  }
73
112
  }
74
113
  },
75
- saveCreds: () => {
114
+ saveCreds: async () => {
76
115
  return writeData(creds, 'creds.json');
77
116
  }
78
117
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alipclutch-baileys",
3
- "version": "8.5.4",
3
+ "version": "8.5.5",
4
4
  "description": "Modified WhatsApp Web API Library",
5
5
  "keywords": [
6
6
  "baileys",