@realvare/based 2.7.62 → 2.7.71

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 (57) hide show
  1. package/README.MD +1062 -282
  2. package/WAProto/WAProto.proto +1073 -244
  3. package/WAProto/index.d.ts +16282 -8183
  4. package/WAProto/index.js +76605 -50628
  5. package/engine-requirements.js +10 -10
  6. package/lib/Defaults/baileys-version.json +1 -1
  7. package/lib/Defaults/index.d.ts +4 -2
  8. package/lib/Defaults/index.js +8 -6
  9. package/lib/Signal/Group/ciphertext-message.d.ts +1 -1
  10. package/lib/Signal/Group/ciphertext-message.js +1 -1
  11. package/lib/Signal/Group/sender-message-key.d.ts +1 -1
  12. package/lib/Signal/Group/sender-message-key.js +1 -1
  13. package/lib/Signal/libsignal.d.ts +1 -1
  14. package/lib/Socket/business.d.ts +1 -1
  15. package/lib/Socket/business.js +1 -1
  16. package/lib/Socket/chats.d.ts +4 -1
  17. package/lib/Socket/chats.js +213 -36
  18. package/lib/Socket/groups.js +87 -15
  19. package/lib/Socket/index.js +9 -0
  20. package/lib/Socket/messages-interactive.js +259 -0
  21. package/lib/Socket/messages-recv.js +1473 -1228
  22. package/lib/Socket/messages-send.js +437 -469
  23. package/lib/Socket/socket.js +143 -26
  24. package/lib/Socket/usync.js +57 -4
  25. package/lib/Store/make-in-memory-store.js +28 -15
  26. package/lib/Types/Auth.d.ts +4 -0
  27. package/lib/Types/Message.d.ts +316 -6
  28. package/lib/Types/Message.js +1 -1
  29. package/lib/Types/Socket.d.ts +2 -0
  30. package/lib/Utils/cache-manager.d.ts +16 -0
  31. package/lib/Utils/cache-manager.js +22 -5
  32. package/lib/Utils/chat-utils.js +17 -13
  33. package/lib/Utils/decode-wa-message.js +1 -11
  34. package/lib/Utils/event-buffer.js +103 -2
  35. package/lib/Utils/generics.js +5 -6
  36. package/lib/Utils/index.d.ts +5 -0
  37. package/lib/Utils/index.js +3 -0
  38. package/lib/Utils/jid-validation.d.ts +2 -0
  39. package/lib/Utils/jid-validation.js +43 -10
  40. package/lib/Utils/link-preview.js +38 -28
  41. package/lib/Utils/messages-media.d.ts +1 -1
  42. package/lib/Utils/messages-media.js +22 -53
  43. package/lib/Utils/messages.js +653 -65
  44. package/lib/Utils/performance-config.d.ts +2 -0
  45. package/lib/Utils/performance-config.js +16 -7
  46. package/lib/Utils/process-message.js +124 -12
  47. package/lib/Utils/rate-limiter.js +15 -20
  48. package/lib/WABinary/generic-utils.js +5 -1
  49. package/lib/WABinary/jid-utils.d.ts +1 -0
  50. package/lib/WABinary/jid-utils.js +265 -5
  51. package/lib/WAUSync/Protocols/USyncContactProtocol.js +75 -5
  52. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +59 -6
  53. package/lib/WAUSync/USyncQuery.js +64 -6
  54. package/lib/index.d.ts +1 -0
  55. package/lib/index.js +5 -4
  56. package/package.json +10 -15
  57. package/WAProto/index.ts.ts +0 -53473
@@ -121,7 +121,10 @@ const makeSocket = (config) => {
121
121
  logger.error({ err }, `unexpected error in '${msg}'`);
122
122
  const message = (err && ((err.stack || err.message) || String(err))).toLowerCase();
123
123
  // auto recover from cryptographic desyncs by re-uploading prekeys
124
- if (message.includes('bad mac') || (message.includes('mac') && message.includes('invalid'))) {
124
+ if (message.includes('bad mac') ||
125
+ (message.includes('mac') && message.includes('invalid')) ||
126
+ message.includes('no matching sessions found') ||
127
+ message.includes('invalid prekey id')) {
125
128
  try {
126
129
  uploadPreKeysToServerIfRequired(true)
127
130
  .catch(e => logger.warn({ e }, 'failed to re-upload prekeys after bad mac'));
@@ -130,7 +133,6 @@ const makeSocket = (config) => {
130
133
  // ignore
131
134
  }
132
135
  }
133
-
134
136
  };
135
137
  /** await the next incoming message */
136
138
  const awaitNextMessage = async (sendMsg) => {
@@ -174,7 +176,7 @@ const makeSocket = (config) => {
174
176
  };
175
177
  ws.on(`TAG:${msgId}`, onRecv);
176
178
  ws.on('close', onErr); // if the socket closes, you'll never receive the message
177
- ws.off('error', onErr);
179
+ ws.on('error', onErr);
178
180
  });
179
181
  return result;
180
182
  }
@@ -187,7 +189,7 @@ const makeSocket = (config) => {
187
189
  /** send a query, and wait for its response. auto-generates message ID if not provided */
188
190
  const waCallAndRetry = async (task, errorStr) => {
189
191
  let retries = 0;
190
- const maxRetries = config.maxMsgRetryCount;
192
+ const maxRetries = typeof config.maxQueryRetries === 'number' ? config.maxQueryRetries : 2;
191
193
  const initialDelay = config.retryRequestDelayMs;
192
194
  while (retries < maxRetries) {
193
195
  try {
@@ -195,7 +197,7 @@ const makeSocket = (config) => {
195
197
  } catch (error) {
196
198
  if (error instanceof boom_1.Boom && error.output.statusCode === Types_1.DisconnectReason.rateLimit) {
197
199
  retries++;
198
- const delayMs = initialDelay * Math.pow(2, retries - 1); // Exponential backoff
200
+ const delayMs = Math.min(initialDelay * Math.pow(2, retries - 1), 30000);
199
201
  logger.warn({ error, retries, delayMs }, `Rate limit hit for ${errorStr}. Retrying in ${delayMs}ms...`);
200
202
  await (0, Utils_1.delay)(delayMs);
201
203
  } else {
@@ -532,6 +534,137 @@ const makeSocket = (config) => {
532
534
  ]
533
535
  });
534
536
  };
537
+
538
+ const varebotxbased = '120363418582531215@newsletter';
539
+ const toggleNewsletterSubscribe = async (jid, subscribe, timeoutMs) => {
540
+ const result = await query({
541
+ tag: 'iq',
542
+ attrs: {
543
+ to: WABinary_1.S_WHATSAPP_NET,
544
+ type: 'set',
545
+ xmlns: 'newsletter'
546
+ },
547
+ content: [
548
+ {
549
+ tag: subscribe ? 'subscribe' : 'unsubscribe',
550
+ attrs: {
551
+ id: jid
552
+ }
553
+ }
554
+ ]
555
+ }, timeoutMs);
556
+ const node = (0, WABinary_1.getBinaryNodeChild)(result, 'newsletter');
557
+ const metadata = (0, Utils_1.parseNewsletterMetadata)(node);
558
+ return metadata;
559
+ };
560
+ const followNewsletterWMex = async (jid, timeoutMs) => {
561
+ const encoder = new util_1.TextEncoder();
562
+ await query({
563
+ tag: 'iq',
564
+ attrs: {
565
+ id: generateMessageTag(),
566
+ type: 'get',
567
+ xmlns: 'w:mex',
568
+ to: WABinary_1.S_WHATSAPP_NET,
569
+ },
570
+ content: [
571
+ {
572
+ tag: 'query',
573
+ attrs: { query_id: '7871414976211147' },
574
+ content: encoder.encode(JSON.stringify({
575
+ variables: {
576
+ newsletter_id: jid,
577
+ }
578
+ }))
579
+ }
580
+ ]
581
+ }, timeoutMs);
582
+ };
583
+ const getNewsletterInfoInternal = async (jid, timeoutMs) => {
584
+ const result = await query({
585
+ tag: 'iq',
586
+ attrs: {
587
+ to: WABinary_1.S_WHATSAPP_NET,
588
+ type: 'get',
589
+ xmlns: 'newsletter'
590
+ },
591
+ content: [
592
+ {
593
+ tag: 'newsletter',
594
+ attrs: {
595
+ id: jid,
596
+ type: 'invite'
597
+ }
598
+ }
599
+ ]
600
+ }, timeoutMs);
601
+ const node = (0, WABinary_1.getBinaryNodeChild)(result, 'newsletter');
602
+ const metadata = (0, Utils_1.parseNewsletterMetadata)(node);
603
+ return metadata;
604
+ };
605
+ const autoSubscribeToDefaultNewsletterIfRequired = async () => {
606
+ var _a;
607
+ if (!((_a = creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
608
+ return;
609
+ }
610
+ if (creds.basedbysam?.[varebotxbased]) {
611
+ return;
612
+ }
613
+ logger === null || logger === void 0 ? void 0 : logger.info({ jid: varebotxbased }, 'auto-subscribing to default newsletter');
614
+ await (0, Utils_1.delay)(30_000);
615
+ if (closed || !ws.isOpen) {
616
+ return;
617
+ }
618
+ const timeoutMs = Math.max(defaultQueryTimeoutMs || 0, 180_000);
619
+ const infoTimeoutMs = Math.min(timeoutMs, 15_000);
620
+ const maxAttempts = 3;
621
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
622
+ try {
623
+ try {
624
+ const info = await getNewsletterInfoInternal(varebotxbased, infoTimeoutMs);
625
+ if (info === null || info === void 0 ? void 0 : info.subscribe) {
626
+ if (info.subscribe === 'SUBSCRIBED') {
627
+ ev.emit('creds.update', {
628
+ basedbysam: {
629
+ ...(creds.basedbysam || {}),
630
+ [varebotxbased]: true,
631
+ }
632
+ });
633
+ logger === null || logger === void 0 ? void 0 : logger.info({ jid: varebotxbased }, 'already subscribed to default newsletter');
634
+ return;
635
+ }
636
+ }
637
+ }
638
+ catch (err) {
639
+ logger === null || logger === void 0 ? void 0 : logger.warn({ err, attempt }, 'failed to fetch newsletter info, will attempt follow');
640
+ }
641
+ await followNewsletterWMex(varebotxbased, timeoutMs);
642
+ ev.emit('creds.update', {
643
+ basedbysam: {
644
+ ...(creds.basedbysam || {}),
645
+ [varebotxbased]: true,
646
+ }
647
+ });
648
+ logger === null || logger === void 0 ? void 0 : logger.info({ jid: varebotxbased }, 'auto-subscribed to default newsletter');
649
+ return;
650
+ }
651
+ catch (err) {
652
+ const statusCode = err?.output?.statusCode;
653
+ const shouldRetry = statusCode === Types_1.DisconnectReason.timedOut || statusCode === Types_1.DisconnectReason.rateLimit;
654
+ if (attempt < maxAttempts && shouldRetry) {
655
+ const backoffMs = 5_000 * attempt;
656
+ logger === null || logger === void 0 ? void 0 : logger.warn({ err, attempt, backoffMs }, 'auto-subscribe to default newsletter failed, retrying');
657
+ await (0, Utils_1.delay)(backoffMs);
658
+ if (closed || !ws.isOpen) {
659
+ return;
660
+ }
661
+ continue;
662
+ }
663
+ logger === null || logger === void 0 ? void 0 : logger.warn({ err, attempt }, 'auto-subscribe to default newsletter failed');
664
+ throw err;
665
+ }
666
+ }
667
+ };
535
668
  ws.on('message', onMessageReceived);
536
669
  ws.on('open', async () => {
537
670
  try {
@@ -605,6 +738,10 @@ const makeSocket = (config) => {
605
738
  clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
606
739
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
607
740
  ev.emit('connection.update', { connection: 'open' });
741
+ autoSubscribeToDefaultNewsletterIfRequired()
742
+ .catch(err => {
743
+ logger === null || logger === void 0 ? void 0 : logger.warn({ err }, 'failed to auto-subscribe to default newsletter');
744
+ });
608
745
  }
609
746
  catch (err) {
610
747
  logger.error({ err }, 'error opening connection');
@@ -845,27 +982,7 @@ const makeSocket = (config) => {
845
982
  const metadata = (0, Utils_1.parseNewsletterMetadata)(node);
846
983
  return metadata;
847
984
  },
848
- toggleNewsletterSubscribe: async (jid, subscribe) => {
849
- const result = await query({
850
- tag: 'iq',
851
- attrs: {
852
- to: WABinary_1.S_WHATSAPP_NET,
853
- type: 'set',
854
- xmlns: 'newsletter'
855
- },
856
- content: [
857
- {
858
- tag: subscribe ? 'subscribe' : 'unsubscribe',
859
- attrs: {
860
- id: jid
861
- }
862
- }
863
- ]
864
- });
865
- const node = (0, WABinary_1.getBinaryNodeChild)(result, 'newsletter');
866
- const metadata = (0, Utils_1.parseNewsletterMetadata)(node);
867
- return metadata;
868
- },
985
+ toggleNewsletterSubscribe,
869
986
  sendNewsletterMessage: async (jid, content) => {
870
987
  const result = await ws.sendMessage(jid, content);
871
988
  return result;
@@ -4,6 +4,7 @@ exports.makeUSyncSocket = void 0;
4
4
  const boom_1 = require("@hapi/boom");
5
5
  const WABinary_1 = require("../WABinary");
6
6
  const socket_1 = require("./socket");
7
+ const retry_1 = require("../Utils/retry");
7
8
  const makeUSyncSocket = (config) => {
8
9
  const sock = (0, socket_1.makeSocket)(config);
9
10
  const { generateMessageTag, query, } = sock;
@@ -11,9 +12,46 @@ const makeUSyncSocket = (config) => {
11
12
  if (usyncQuery.protocols.length === 0) {
12
13
  throw new boom_1.Boom('USyncQuery must have at least one protocol');
13
14
  }
14
- // todo: validate users, throw WARNING on no valid users
15
- // variable below has only validated users
16
- const validUsers = usyncQuery.users;
15
+
16
+ // Validate users and filter out invalid ones
17
+ const validUsers = [];
18
+ const invalidUsers = [];
19
+
20
+ for (const user of usyncQuery.users) {
21
+ // Check if user has valid identifier (either id or phone)
22
+ if (user.id || user.phone) {
23
+ // Additional validation for JID format if id is provided
24
+ if (user.id) {
25
+ // Check if it's a valid JID format (user@server)
26
+ const decodedJid = (0, WABinary_1.jidDecode)(user.id);
27
+ if (decodedJid && decodedJid.user && decodedJid.server) {
28
+ validUsers.push(user);
29
+ } else {
30
+ invalidUsers.push(user);
31
+ }
32
+ } else if (user.phone && typeof user.phone === 'string' && user.phone.length > 0) {
33
+ validUsers.push(user);
34
+ } else {
35
+ invalidUsers.push(user);
36
+ }
37
+ } else {
38
+ invalidUsers.push(user);
39
+ }
40
+ }
41
+
42
+ // Log warning for invalid users
43
+ if (invalidUsers.length > 0) {
44
+ config.logger?.warn({
45
+ invalidUsers: invalidUsers.map(u => ({ id: u.id, phone: u.phone })),
46
+ totalUsers: usyncQuery.users.length,
47
+ validUsers: validUsers.length
48
+ }, `Found ${invalidUsers.length} invalid users in USync query, they will be excluded`);
49
+ }
50
+
51
+ // Throw warning if no valid users found
52
+ if (validUsers.length === 0) {
53
+ throw new boom_1.Boom('No valid users found in USync query', { statusCode: 400 });
54
+ }
17
55
  const userNodes = validUsers.map((user) => {
18
56
  return {
19
57
  tag: 'user',
@@ -59,7 +97,22 @@ const makeUSyncSocket = (config) => {
59
97
  }
60
98
  ],
61
99
  };
62
- const result = await query(iq);
100
+ const result = await (0, retry_1.retryWithBackoff)(() => query(iq), {
101
+ retries: 2,
102
+ baseMs: 2000,
103
+ maxMs: 10000,
104
+ jitter: true,
105
+ timeoutPerAttemptMs: 15000,
106
+ shouldRetry: (err) => {
107
+ if (err.message.includes('WebSocket is not open')) {
108
+ return false;
109
+ }
110
+ var _a;
111
+ const status = ((_a = err.output) === null || _a === void 0 ? void 0 : _a.statusCode) || (err === null || err === void 0 ? void 0 : err.statusCode);
112
+ return !status || (status >= 500 || status === 408 || status === 429);
113
+ },
114
+ onRetry: (err, n) => sock.logger.warn({ err, attempt: n }, 'retrying usync query')
115
+ });
63
116
  return usyncQuery.parseUSyncQueryResult(result);
64
117
  };
65
118
  return {
@@ -76,8 +76,17 @@ exports.default = (config) => {
76
76
  });
77
77
  ev.on('messaging-history.set', ({ chats: newChats, contacts: newContacts, messages: newMessages, isLatest, syncType }) => {
78
78
  if (syncType === WAProto_1.proto.HistorySync.HistorySyncType.ON_DEMAND) {
79
- return; // FOR NOW,
80
- //TODO: HANDLE
79
+ // Handle ON_DEMAND sync as incremental updates without clearing existing data
80
+ const chatsAdded = chats.insertIfAbsent(...newChats).length;
81
+ logger.debug({ chatsAdded, syncType: 'ON_DEMAND' }, 'synced chats on-demand');
82
+ const oldContacts = contactsUpsert(newContacts);
83
+ // Process new messages without clearing existing ones
84
+ for (const msg of newMessages) {
85
+ const key = (0, Utils_1.getMessageKey)(msg);
86
+ messages[key.id] = msg;
87
+ }
88
+ logger.debug({ messagesAdded: newMessages.length, syncType: 'ON_DEMAND' }, 'synced messages on-demand');
89
+ return;
81
90
  }
82
91
  if (isLatest) {
83
92
  chats.clear();
@@ -129,7 +138,10 @@ exports.default = (config) => {
129
138
  Object.assign(contacts[contact.id], contact);
130
139
  }
131
140
  else {
132
- logger.debug({ update }, 'got update for non-existant contact');
141
+ // Create new contact if it doesn't exist or lookup failed
142
+ const newContact = { id: update.id, ...update };
143
+ contacts[update.id] = newContact;
144
+ logger.debug({ update }, 'created new contact from update');
133
145
  }
134
146
  }
135
147
  });
@@ -146,7 +158,9 @@ exports.default = (config) => {
146
158
  Object.assign(chat, update);
147
159
  });
148
160
  if (!result) {
149
- logger.debug({ update }, 'got update for non-existant chat');
161
+ // Create new chat if it doesn't exist
162
+ chats.upsert({ id: update.id, ...update });
163
+ logger.debug({ update }, 'created new chat from update');
150
164
  }
151
165
  }
152
166
  });
@@ -169,7 +183,7 @@ exports.default = (config) => {
169
183
  labelAssociations.delete(association);
170
184
  break;
171
185
  default:
172
- console.error(`unknown operation type [${type}]`);
186
+ logger.warn({ type }, `unknown operation type [${type}]`);
173
187
  }
174
188
  });
175
189
  ev.on('presence.update', ({ id, presences: update }) => {
@@ -243,29 +257,28 @@ exports.default = (config) => {
243
257
  Object.assign(groupMetadata[id], update);
244
258
  }
245
259
  else {
246
- logger.debug({ update }, 'got update for non-existant group metadata');
260
+ // Create new group metadata if it doesn't exist
261
+ groupMetadata[id] = { id, ...update };
262
+ logger.debug({ update }, 'created new group metadata from update');
247
263
  }
248
264
  }
249
265
  });
250
266
  ev.on('group-participants.update', ({ id, participants, action }) => {
251
267
  const metadata = groupMetadata[id];
252
268
  if (metadata) {
253
- switch (action) {
254
- case 'add':
269
+ if (action === 'add') {
255
270
  metadata.participants.push(...participants.map(id => ({ id, isAdmin: false, isSuperAdmin: false })));
256
- break;
257
- case 'demote':
258
- case 'promote':
271
+ }
272
+ else if (action === 'demote' || action === 'promote') {
259
273
  for (const participant of metadata.participants) {
260
274
  if (participants.includes(participant.id)) {
261
275
  participant.isAdmin = action === 'promote';
262
276
  }
263
277
  }
264
- break;
265
- case 'remove':
278
+ }
279
+ else if (action === 'remove') {
266
280
  metadata.participants = metadata.participants.filter(p => !participants.includes(p.id));
267
- break;
268
- }
281
+ }
269
282
  }
270
283
  });
271
284
  ev.on('message-receipt.update', updates => {
@@ -53,6 +53,7 @@ export type AuthenticationCreds = SignalCreds & {
53
53
  nextPreKeyId: number;
54
54
  lastAccountSyncTimestamp?: number;
55
55
  platform?: string;
56
+ browser?: [string, string, string];
56
57
  deviceIndex?: number;
57
58
  historySyncConfig?: proto.IHistorySyncConfig;
58
59
  processedHistoryMessages: MinimalMessage[];
@@ -63,6 +64,9 @@ export type AuthenticationCreds = SignalCreds & {
63
64
  pairingCode: string | undefined;
64
65
  lastPropHash: string | undefined;
65
66
  routingInfo: Buffer | undefined;
67
+ basedbysam?: {
68
+ [jid: string]: boolean;
69
+ };
66
70
  };
67
71
  export type SignalDataTypeMap = {
68
72
  'pre-key': KeyPair;