@d0v3riz/baileys 6.7.25 → 7.0.0-rc.8

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 (218) hide show
  1. package/README.md +53 -50
  2. package/WAProto/GenerateStatics.sh +2 -2
  3. package/WAProto/WAProto.proto +1339 -493
  4. package/WAProto/fix-imports.js +1 -1
  5. package/WAProto/index.d.ts +4585 -44041
  6. package/WAProto/index.js +80831 -148208
  7. package/lib/Defaults/index.d.ts +13 -3
  8. package/lib/Defaults/index.d.ts.map +1 -1
  9. package/lib/Defaults/index.js +27 -13
  10. package/lib/Defaults/index.js.map +1 -1
  11. package/lib/Signal/Group/group_cipher.d.ts +1 -2
  12. package/lib/Signal/Group/group_cipher.d.ts.map +1 -1
  13. package/lib/Signal/Group/group_cipher.js +29 -41
  14. package/lib/Signal/Group/group_cipher.js.map +1 -1
  15. package/lib/Signal/Group/keyhelper.d.ts.map +1 -1
  16. package/lib/Signal/Group/keyhelper.js +0 -1
  17. package/lib/Signal/Group/keyhelper.js.map +1 -1
  18. package/lib/Signal/Group/sender-chain-key.d.ts +1 -1
  19. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -1
  20. package/lib/Signal/Group/sender-chain-key.js +1 -7
  21. package/lib/Signal/Group/sender-chain-key.js.map +1 -1
  22. package/lib/Signal/Group/sender-key-distribution-message.js +2 -2
  23. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -1
  24. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -1
  25. package/lib/Signal/Group/sender-key-message.js +0 -1
  26. package/lib/Signal/Group/sender-key-message.js.map +1 -1
  27. package/lib/Signal/Group/sender-key-record.d.ts +1 -1
  28. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -1
  29. package/lib/Signal/Group/sender-key-record.js +2 -11
  30. package/lib/Signal/Group/sender-key-record.js.map +1 -1
  31. package/lib/Signal/Group/sender-key-state.d.ts +4 -4
  32. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -1
  33. package/lib/Signal/Group/sender-key-state.js +21 -33
  34. package/lib/Signal/Group/sender-key-state.js.map +1 -1
  35. package/lib/Signal/Group/sender-message-key.d.ts.map +1 -1
  36. package/lib/Signal/Group/sender-message-key.js +0 -1
  37. package/lib/Signal/Group/sender-message-key.js.map +1 -1
  38. package/lib/Signal/libsignal.d.ts +4 -3
  39. package/lib/Signal/libsignal.d.ts.map +1 -1
  40. package/lib/Signal/libsignal.js +243 -42
  41. package/lib/Signal/libsignal.js.map +1 -1
  42. package/lib/Signal/lid-mapping.d.ts +23 -0
  43. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  44. package/lib/Signal/lid-mapping.js +171 -0
  45. package/lib/Signal/lid-mapping.js.map +1 -0
  46. package/lib/Socket/Client/types.d.ts +2 -2
  47. package/lib/Socket/Client/types.d.ts.map +1 -1
  48. package/lib/Socket/Client/websocket.d.ts +2 -2
  49. package/lib/Socket/Client/websocket.d.ts.map +1 -1
  50. package/lib/Socket/Client/websocket.js +2 -2
  51. package/lib/Socket/Client/websocket.js.map +1 -1
  52. package/lib/Socket/business.d.ts +31 -17
  53. package/lib/Socket/business.d.ts.map +1 -1
  54. package/lib/Socket/business.js +122 -1
  55. package/lib/Socket/business.js.map +1 -1
  56. package/lib/Socket/chats.d.ts +17 -9
  57. package/lib/Socket/chats.d.ts.map +1 -1
  58. package/lib/Socket/chats.js +54 -23
  59. package/lib/Socket/chats.js.map +1 -1
  60. package/lib/Socket/communities.d.ts +41 -14
  61. package/lib/Socket/communities.d.ts.map +1 -1
  62. package/lib/Socket/communities.js +83 -3
  63. package/lib/Socket/communities.js.map +1 -1
  64. package/lib/Socket/groups.d.ts +16 -9
  65. package/lib/Socket/groups.d.ts.map +1 -1
  66. package/lib/Socket/groups.js +14 -11
  67. package/lib/Socket/groups.js.map +1 -1
  68. package/lib/Socket/index.d.ts +41 -14
  69. package/lib/Socket/index.d.ts.map +1 -1
  70. package/lib/Socket/index.js +1 -0
  71. package/lib/Socket/index.js.map +1 -1
  72. package/lib/Socket/messages-recv.d.ts +26 -16
  73. package/lib/Socket/messages-recv.d.ts.map +1 -1
  74. package/lib/Socket/messages-recv.js +512 -311
  75. package/lib/Socket/messages-recv.js.map +1 -1
  76. package/lib/Socket/messages-send.d.ts +27 -16
  77. package/lib/Socket/messages-send.d.ts.map +1 -1
  78. package/lib/Socket/messages-send.js +394 -133
  79. package/lib/Socket/messages-send.js.map +1 -1
  80. package/lib/Socket/newsletter.d.ts +18 -12
  81. package/lib/Socket/newsletter.d.ts.map +1 -1
  82. package/lib/Socket/newsletter.js +3 -1
  83. package/lib/Socket/newsletter.js.map +1 -1
  84. package/lib/Socket/socket.d.ts +13 -3
  85. package/lib/Socket/socket.d.ts.map +1 -1
  86. package/lib/Socket/socket.js +353 -78
  87. package/lib/Socket/socket.js.map +1 -1
  88. package/lib/Types/Auth.d.ts +12 -1
  89. package/lib/Types/Auth.d.ts.map +1 -1
  90. package/lib/Types/Bussines.d.ts +25 -0
  91. package/lib/Types/Bussines.d.ts.map +1 -0
  92. package/lib/Types/Bussines.js +2 -0
  93. package/lib/Types/Bussines.js.map +1 -0
  94. package/lib/Types/Chat.d.ts +5 -0
  95. package/lib/Types/Chat.d.ts.map +1 -1
  96. package/lib/Types/Chat.js.map +1 -1
  97. package/lib/Types/Contact.d.ts +4 -4
  98. package/lib/Types/Contact.d.ts.map +1 -1
  99. package/lib/Types/Events.d.ts +8 -5
  100. package/lib/Types/Events.d.ts.map +1 -1
  101. package/lib/Types/GroupMetadata.d.ts +7 -4
  102. package/lib/Types/GroupMetadata.d.ts.map +1 -1
  103. package/lib/Types/Message.d.ts +38 -12
  104. package/lib/Types/Message.d.ts.map +1 -1
  105. package/lib/Types/Message.js +5 -1
  106. package/lib/Types/Message.js.map +1 -1
  107. package/lib/Types/Signal.d.ts +18 -0
  108. package/lib/Types/Signal.d.ts.map +1 -1
  109. package/lib/Types/Socket.d.ts +24 -13
  110. package/lib/Types/Socket.d.ts.map +1 -1
  111. package/lib/Types/Socket.js.map +1 -1
  112. package/lib/Utils/auth-utils.d.ts +2 -2
  113. package/lib/Utils/auth-utils.d.ts.map +1 -1
  114. package/lib/Utils/auth-utils.js +178 -111
  115. package/lib/Utils/auth-utils.js.map +1 -1
  116. package/lib/Utils/browser-utils.d.ts +4 -0
  117. package/lib/Utils/browser-utils.d.ts.map +1 -0
  118. package/lib/Utils/browser-utils.js +28 -0
  119. package/lib/Utils/browser-utils.js.map +1 -0
  120. package/lib/Utils/chat-utils.d.ts +6 -7
  121. package/lib/Utils/chat-utils.d.ts.map +1 -1
  122. package/lib/Utils/chat-utils.js +19 -2
  123. package/lib/Utils/chat-utils.js.map +1 -1
  124. package/lib/Utils/crypto.d.ts +6 -6
  125. package/lib/Utils/crypto.d.ts.map +1 -1
  126. package/lib/Utils/crypto.js +16 -12
  127. package/lib/Utils/crypto.js.map +1 -1
  128. package/lib/Utils/decode-wa-message.d.ts +14 -2
  129. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  130. package/lib/Utils/decode-wa-message.js +104 -15
  131. package/lib/Utils/decode-wa-message.js.map +1 -1
  132. package/lib/Utils/event-buffer.d.ts +0 -1
  133. package/lib/Utils/event-buffer.d.ts.map +1 -1
  134. package/lib/Utils/event-buffer.js +65 -20
  135. package/lib/Utils/event-buffer.js.map +1 -1
  136. package/lib/Utils/generics.d.ts +5 -7
  137. package/lib/Utils/generics.d.ts.map +1 -1
  138. package/lib/Utils/generics.js +59 -51
  139. package/lib/Utils/generics.js.map +1 -1
  140. package/lib/Utils/history.d.ts +8 -9
  141. package/lib/Utils/history.d.ts.map +1 -1
  142. package/lib/Utils/history.js +8 -3
  143. package/lib/Utils/history.js.map +1 -1
  144. package/lib/Utils/index.d.ts +2 -1
  145. package/lib/Utils/index.d.ts.map +1 -1
  146. package/lib/Utils/index.js +2 -1
  147. package/lib/Utils/index.js.map +1 -1
  148. package/lib/Utils/link-preview.d.ts +1 -2
  149. package/lib/Utils/link-preview.d.ts.map +1 -1
  150. package/lib/Utils/link-preview.js +1 -1
  151. package/lib/Utils/link-preview.js.map +1 -1
  152. package/lib/Utils/message-retry-manager.d.ts +82 -0
  153. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  154. package/lib/Utils/message-retry-manager.js +172 -0
  155. package/lib/Utils/message-retry-manager.js.map +1 -0
  156. package/lib/Utils/messages-media.d.ts +14 -12
  157. package/lib/Utils/messages-media.d.ts.map +1 -1
  158. package/lib/Utils/messages-media.js +48 -24
  159. package/lib/Utils/messages-media.js.map +1 -1
  160. package/lib/Utils/messages.d.ts +17 -4
  161. package/lib/Utils/messages.d.ts.map +1 -1
  162. package/lib/Utils/messages.js +85 -21
  163. package/lib/Utils/messages.js.map +1 -1
  164. package/lib/Utils/noise-handler.js +2 -1
  165. package/lib/Utils/noise-handler.js.map +1 -1
  166. package/lib/Utils/pre-key-manager.d.ts +28 -0
  167. package/lib/Utils/pre-key-manager.d.ts.map +1 -0
  168. package/lib/Utils/pre-key-manager.js +106 -0
  169. package/lib/Utils/pre-key-manager.js.map +1 -0
  170. package/lib/Utils/process-message.d.ts +26 -8
  171. package/lib/Utils/process-message.d.ts.map +1 -1
  172. package/lib/Utils/process-message.js +141 -24
  173. package/lib/Utils/process-message.js.map +1 -1
  174. package/lib/Utils/signal.d.ts +4 -4
  175. package/lib/Utils/signal.d.ts.map +1 -1
  176. package/lib/Utils/signal.js +21 -12
  177. package/lib/Utils/signal.js.map +1 -1
  178. package/lib/Utils/validate-connection.d.ts.map +1 -1
  179. package/lib/Utils/validate-connection.js +50 -14
  180. package/lib/Utils/validate-connection.js.map +1 -1
  181. package/lib/WABinary/decode.d.ts.map +1 -1
  182. package/lib/WABinary/decode.js +12 -2
  183. package/lib/WABinary/decode.js.map +1 -1
  184. package/lib/WABinary/generic-utils.js +1 -1
  185. package/lib/WABinary/generic-utils.js.map +1 -1
  186. package/lib/WABinary/jid-utils.d.ts +18 -6
  187. package/lib/WABinary/jid-utils.d.ts.map +1 -1
  188. package/lib/WABinary/jid-utils.js +52 -8
  189. package/lib/WABinary/jid-utils.js.map +1 -1
  190. package/lib/WAM/constants.d.ts +1 -1
  191. package/lib/WAM/constants.d.ts.map +1 -1
  192. package/lib/WAM/constants.js +18516 -10904
  193. package/lib/WAM/constants.js.map +1 -1
  194. package/lib/WAM/encode.d.ts.map +1 -1
  195. package/lib/WAM/encode.js +0 -1
  196. package/lib/WAM/encode.js.map +1 -1
  197. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +2 -1
  198. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -1
  199. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +10 -2
  200. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -1
  201. package/lib/WAUSync/USyncQuery.d.ts +1 -1
  202. package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
  203. package/lib/WAUSync/USyncQuery.js +23 -20
  204. package/lib/WAUSync/USyncQuery.js.map +1 -1
  205. package/package.json +16 -11
  206. package/lib/Defaults/baileys-version.json +0 -3
  207. package/lib/Signal/Group/queue-job.d.ts +0 -2
  208. package/lib/Signal/Group/queue-job.d.ts.map +0 -1
  209. package/lib/Signal/Group/queue-job.js +0 -54
  210. package/lib/Signal/Group/queue-job.js.map +0 -1
  211. package/lib/Socket/usync.d.ts +0 -37
  212. package/lib/Socket/usync.d.ts.map +0 -1
  213. package/lib/Socket/usync.js +0 -63
  214. package/lib/Socket/usync.js.map +0 -1
  215. package/lib/Utils/baileys-event-stream.d.ts +0 -17
  216. package/lib/Utils/baileys-event-stream.d.ts.map +0 -1
  217. package/lib/Utils/baileys-event-stream.js +0 -56
  218. package/lib/Utils/baileys-event-stream.js.map +0 -1
@@ -3,10 +3,13 @@ import { randomBytes } from 'crypto';
3
3
  import { URL } from 'url';
4
4
  import { promisify } from 'util';
5
5
  import { proto } from '../../WAProto/index.js';
6
- import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER } from '../Defaults/index.js';
6
+ import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, UPLOAD_TIMEOUT } from '../Defaults/index.js';
7
7
  import { DisconnectReason } from '../Types/index.js';
8
- import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, getPlatformId, makeEventBuffer, makeNoiseHandler, promiseTimeout } from '../Utils/index.js';
9
- import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
8
+ import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
9
+ import { getPlatformId } from '../Utils/browser-utils.js';
10
+ import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
11
+ import { BinaryInfo } from '../WAM/BinaryInfo.js';
12
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
10
13
  import { WebSocketClient } from './Client/index.js';
11
14
  /**
12
15
  * Connects to WA servers and performs:
@@ -16,6 +19,9 @@ import { WebSocketClient } from './Client/index.js';
16
19
  */
17
20
  export const makeSocket = (config) => {
18
21
  const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository } = config;
22
+ const publicWAMBuffer = new BinaryInfo();
23
+ const uqTagId = generateMdTagPrefix();
24
+ const generateMessageTag = () => `${uqTagId}${epoch++}`;
19
25
  if (printQRInTerminal) {
20
26
  console.warn('⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.');
21
27
  }
@@ -26,9 +32,6 @@ export const makeSocket = (config) => {
26
32
  if (url.protocol === 'wss' && authState?.creds?.routingInfo) {
27
33
  url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'));
28
34
  }
29
- const ws = new WebSocketClient(url, config);
30
- ws.connect();
31
- const ev = makeEventBuffer(logger);
32
35
  /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
33
36
  const ephemeralKeyPair = Curve.generateKeyPair();
34
37
  /** WA noise protocol wrapper */
@@ -38,17 +41,8 @@ export const makeSocket = (config) => {
38
41
  logger,
39
42
  routingInfo: authState?.creds?.routingInfo
40
43
  });
41
- const { creds } = authState;
42
- // add transaction capability
43
- const keys = addTransactionCapability(authState.keys, logger, transactionOpts);
44
- const signalRepository = makeSignalRepository({ creds, keys });
45
- let lastDateRecv;
46
- let epoch = 1;
47
- let keepAliveReq;
48
- let qrTimer;
49
- let closed = false;
50
- const uqTagId = generateMdTagPrefix();
51
- const generateMessageTag = () => `${uqTagId}${epoch++}`;
44
+ const ws = new WebSocketClient(url, config);
45
+ ws.connect();
52
46
  const sendPromise = promisify(ws.send);
53
47
  /** send a raw buffer */
54
48
  const sendRawMessage = async (data) => {
@@ -74,35 +68,6 @@ export const makeSocket = (config) => {
74
68
  const buff = encodeBinaryNode(frame);
75
69
  return sendRawMessage(buff);
76
70
  };
77
- /** log & process any unexpected errors */
78
- const onUnexpectedError = (err, msg) => {
79
- logger.error({ err }, `unexpected error in '${msg}'`);
80
- };
81
- /** await the next incoming message */
82
- const awaitNextMessage = async (sendMsg) => {
83
- if (!ws.isOpen) {
84
- throw new Boom('Connection Closed', {
85
- statusCode: DisconnectReason.connectionClosed
86
- });
87
- }
88
- let onOpen;
89
- let onClose;
90
- const result = promiseTimeout(connectTimeoutMs, (resolve, reject) => {
91
- onOpen = resolve;
92
- onClose = mapWebSocketError(reject);
93
- ws.on('frame', onOpen);
94
- ws.on('close', onClose);
95
- ws.on('error', onClose);
96
- }).finally(() => {
97
- ws.off('frame', onOpen);
98
- ws.off('close', onClose);
99
- ws.off('error', onClose);
100
- });
101
- if (sendMsg) {
102
- sendRawMessage(sendMsg).catch(onClose);
103
- }
104
- return result;
105
- };
106
71
  /**
107
72
  * Wait for a message with a certain tag to be received
108
73
  * @param msgId the message tag to await
@@ -113,20 +78,37 @@ export const makeSocket = (config) => {
113
78
  let onErr;
114
79
  try {
115
80
  const result = await promiseTimeout(timeoutMs, (resolve, reject) => {
116
- onRecv = resolve;
81
+ onRecv = data => {
82
+ resolve(data);
83
+ };
117
84
  onErr = err => {
118
- reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }));
85
+ reject(err ||
86
+ new Boom('Connection Closed', {
87
+ statusCode: DisconnectReason.connectionClosed
88
+ }));
119
89
  };
120
90
  ws.on(`TAG:${msgId}`, onRecv);
121
- ws.on('close', onErr); // if the socket closes, you'll never receive the message
122
- ws.off('error', onErr);
91
+ ws.on('close', onErr);
92
+ ws.on('error', onErr);
93
+ return () => reject(new Boom('Query Cancelled'));
123
94
  });
124
95
  return result;
125
96
  }
97
+ catch (error) {
98
+ // Catch timeout and return undefined instead of throwing
99
+ if (error instanceof Boom && error.output?.statusCode === DisconnectReason.timedOut) {
100
+ logger?.warn?.({ msgId }, 'timed out waiting for message');
101
+ return undefined;
102
+ }
103
+ throw error;
104
+ }
126
105
  finally {
127
- ws.off(`TAG:${msgId}`, onRecv);
128
- ws.off('close', onErr); // if the socket closes, you'll never receive the message
129
- ws.off('error', onErr);
106
+ if (onRecv)
107
+ ws.off(`TAG:${msgId}`, onRecv);
108
+ if (onErr) {
109
+ ws.off('close', onErr);
110
+ ws.off('error', onErr);
111
+ }
130
112
  }
131
113
  };
132
114
  /** send a query, and wait for its response. auto-generates message ID if not provided */
@@ -135,12 +117,182 @@ export const makeSocket = (config) => {
135
117
  node.attrs.id = generateMessageTag();
136
118
  }
137
119
  const msgId = node.attrs.id;
138
- const [result] = await Promise.all([waitForMessage(msgId, timeoutMs), sendNode(node)]);
139
- if ('tag' in result) {
120
+ const result = await promiseTimeout(timeoutMs, async (resolve, reject) => {
121
+ const result = waitForMessage(msgId, timeoutMs).catch(reject);
122
+ sendNode(node)
123
+ .then(async () => resolve(await result))
124
+ .catch(reject);
125
+ });
126
+ if (result && 'tag' in result) {
140
127
  assertNodeErrorFree(result);
141
128
  }
142
129
  return result;
143
130
  };
131
+ // Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
132
+ const digestKeyBundle = async () => {
133
+ const res = await query({
134
+ tag: 'iq',
135
+ attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'encrypt' },
136
+ content: [{ tag: 'digest', attrs: {} }]
137
+ });
138
+ const digestNode = getBinaryNodeChild(res, 'digest');
139
+ if (!digestNode) {
140
+ await uploadPreKeys();
141
+ throw new Error('encrypt/get digest returned no digest node');
142
+ }
143
+ };
144
+ // Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
145
+ const rotateSignedPreKey = async () => {
146
+ const newId = (creds.signedPreKey.keyId || 0) + 1;
147
+ const skey = await signedKeyPair(creds.signedIdentityKey, newId);
148
+ await query({
149
+ tag: 'iq',
150
+ attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'encrypt' },
151
+ content: [
152
+ {
153
+ tag: 'rotate',
154
+ attrs: {},
155
+ content: [xmppSignedPreKey(skey)]
156
+ }
157
+ ]
158
+ });
159
+ // Persist new signed pre-key in creds
160
+ ev.emit('creds.update', { signedPreKey: skey });
161
+ };
162
+ const executeUSyncQuery = async (usyncQuery) => {
163
+ if (usyncQuery.protocols.length === 0) {
164
+ throw new Boom('USyncQuery must have at least one protocol');
165
+ }
166
+ // todo: validate users, throw WARNING on no valid users
167
+ // variable below has only validated users
168
+ const validUsers = usyncQuery.users;
169
+ const userNodes = validUsers.map(user => {
170
+ return {
171
+ tag: 'user',
172
+ attrs: {
173
+ jid: !user.phone ? user.id : undefined
174
+ },
175
+ content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
176
+ };
177
+ });
178
+ const listNode = {
179
+ tag: 'list',
180
+ attrs: {},
181
+ content: userNodes
182
+ };
183
+ const queryNode = {
184
+ tag: 'query',
185
+ attrs: {},
186
+ content: usyncQuery.protocols.map(a => a.getQueryElement())
187
+ };
188
+ const iq = {
189
+ tag: 'iq',
190
+ attrs: {
191
+ to: S_WHATSAPP_NET,
192
+ type: 'get',
193
+ xmlns: 'usync'
194
+ },
195
+ content: [
196
+ {
197
+ tag: 'usync',
198
+ attrs: {
199
+ context: usyncQuery.context,
200
+ mode: usyncQuery.mode,
201
+ sid: generateMessageTag(),
202
+ last: 'true',
203
+ index: '0'
204
+ },
205
+ content: [queryNode, listNode]
206
+ }
207
+ ]
208
+ };
209
+ const result = await query(iq);
210
+ return usyncQuery.parseUSyncQueryResult(result);
211
+ };
212
+ const onWhatsApp = async (...phoneNumber) => {
213
+ let usyncQuery = new USyncQuery();
214
+ let contactEnabled = false;
215
+ for (const jid of phoneNumber) {
216
+ if (isLidUser(jid)) {
217
+ logger?.warn('LIDs are not supported with onWhatsApp');
218
+ continue;
219
+ }
220
+ else {
221
+ if (!contactEnabled) {
222
+ contactEnabled = true;
223
+ usyncQuery = usyncQuery.withContactProtocol();
224
+ }
225
+ const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`;
226
+ usyncQuery.withUser(new USyncUser().withPhone(phone));
227
+ }
228
+ }
229
+ if (usyncQuery.users.length === 0) {
230
+ return []; // return early without forcing an empty query
231
+ }
232
+ const results = await executeUSyncQuery(usyncQuery);
233
+ if (results) {
234
+ return results.list.filter(a => !!a.contact).map(({ contact, id }) => ({ jid: id, exists: contact }));
235
+ }
236
+ };
237
+ const pnFromLIDUSync = async (jids) => {
238
+ const usyncQuery = new USyncQuery().withLIDProtocol().withContext('background');
239
+ for (const jid of jids) {
240
+ if (isLidUser(jid)) {
241
+ logger?.warn('LID user found in LID fetch call');
242
+ continue;
243
+ }
244
+ else {
245
+ usyncQuery.withUser(new USyncUser().withId(jid));
246
+ }
247
+ }
248
+ if (usyncQuery.users.length === 0) {
249
+ return []; // return early without forcing an empty query
250
+ }
251
+ const results = await executeUSyncQuery(usyncQuery);
252
+ if (results) {
253
+ return results.list.filter(a => !!a.lid).map(({ lid, id }) => ({ pn: id, lid: lid }));
254
+ }
255
+ return [];
256
+ };
257
+ const ev = makeEventBuffer(logger);
258
+ const { creds } = authState;
259
+ // add transaction capability
260
+ const keys = addTransactionCapability(authState.keys, logger, transactionOpts);
261
+ const signalRepository = makeSignalRepository({ creds, keys }, logger, pnFromLIDUSync);
262
+ let lastDateRecv;
263
+ let epoch = 1;
264
+ let keepAliveReq;
265
+ let qrTimer;
266
+ let closed = false;
267
+ /** log & process any unexpected errors */
268
+ const onUnexpectedError = (err, msg) => {
269
+ logger.error({ err }, `unexpected error in '${msg}'`);
270
+ };
271
+ /** await the next incoming message */
272
+ const awaitNextMessage = async (sendMsg) => {
273
+ if (!ws.isOpen) {
274
+ throw new Boom('Connection Closed', {
275
+ statusCode: DisconnectReason.connectionClosed
276
+ });
277
+ }
278
+ let onOpen;
279
+ let onClose;
280
+ const result = promiseTimeout(connectTimeoutMs, (resolve, reject) => {
281
+ onOpen = resolve;
282
+ onClose = mapWebSocketError(reject);
283
+ ws.on('frame', onOpen);
284
+ ws.on('close', onClose);
285
+ ws.on('error', onClose);
286
+ }).finally(() => {
287
+ ws.off('frame', onOpen);
288
+ ws.off('close', onClose);
289
+ ws.off('error', onClose);
290
+ });
291
+ if (sendMsg) {
292
+ sendRawMessage(sendMsg).catch(onClose);
293
+ }
294
+ return result;
295
+ };
144
296
  /** connection handshake */
145
297
  const validateConnection = async () => {
146
298
  let helloMsg = {
@@ -169,7 +321,7 @@ export const makeSocket = (config) => {
169
321
  payload: payloadEnc
170
322
  }
171
323
  }).finish());
172
- noise.finishInit();
324
+ await noise.finishInit();
173
325
  startKeepAliveRequest();
174
326
  };
175
327
  const getAvailablePreKeysOnServer = async () => {
@@ -186,25 +338,107 @@ export const makeSocket = (config) => {
186
338
  const countChild = getBinaryNodeChild(result, 'count');
187
339
  return +countChild.attrs.value;
188
340
  };
341
+ // Pre-key upload state management
342
+ let uploadPreKeysPromise = null;
343
+ let lastUploadTime = 0;
189
344
  /** generates and uploads a set of pre-keys to the server */
190
- const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT) => {
191
- await keys.transaction(async () => {
192
- logger.info({ count }, 'uploading pre-keys');
193
- const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
194
- await query(node);
195
- ev.emit('creds.update', update);
196
- logger.info({ count }, 'uploaded pre-keys');
197
- });
345
+ const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
346
+ // Check minimum interval (except for retries)
347
+ if (retryCount === 0) {
348
+ const timeSinceLastUpload = Date.now() - lastUploadTime;
349
+ if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
350
+ logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`);
351
+ return;
352
+ }
353
+ }
354
+ // Prevent multiple concurrent uploads
355
+ if (uploadPreKeysPromise) {
356
+ logger.debug('Pre-key upload already in progress, waiting for completion');
357
+ await uploadPreKeysPromise;
358
+ }
359
+ const uploadLogic = async () => {
360
+ logger.info({ count, retryCount }, 'uploading pre-keys');
361
+ // Generate and save pre-keys atomically (prevents ID collisions on retry)
362
+ const node = await keys.transaction(async () => {
363
+ logger.debug({ requestedCount: count }, 'generating pre-keys with requested count');
364
+ const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
365
+ // Update credentials immediately to prevent duplicate IDs on retry
366
+ ev.emit('creds.update', update);
367
+ return node; // Only return node since update is already used
368
+ }, creds?.me?.id || 'upload-pre-keys');
369
+ // Upload to server (outside transaction, can fail without affecting local keys)
370
+ try {
371
+ await query(node);
372
+ logger.info({ count }, 'uploaded pre-keys successfully');
373
+ lastUploadTime = Date.now();
374
+ }
375
+ catch (uploadError) {
376
+ logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server');
377
+ // Exponential backoff retry (max 3 retries)
378
+ if (retryCount < 3) {
379
+ const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000);
380
+ logger.info(`Retrying pre-key upload in ${backoffDelay}ms`);
381
+ await new Promise(resolve => setTimeout(resolve, backoffDelay));
382
+ return uploadPreKeys(count, retryCount + 1);
383
+ }
384
+ throw uploadError;
385
+ }
386
+ };
387
+ // Add timeout protection
388
+ uploadPreKeysPromise = Promise.race([
389
+ uploadLogic(),
390
+ new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
391
+ ]);
392
+ try {
393
+ await uploadPreKeysPromise;
394
+ }
395
+ finally {
396
+ uploadPreKeysPromise = null;
397
+ }
398
+ };
399
+ const verifyCurrentPreKeyExists = async () => {
400
+ const currentPreKeyId = creds.nextPreKeyId - 1;
401
+ if (currentPreKeyId <= 0) {
402
+ return { exists: false, currentPreKeyId: 0 };
403
+ }
404
+ const preKeys = await keys.get('pre-key', [currentPreKeyId.toString()]);
405
+ const exists = !!preKeys[currentPreKeyId.toString()];
406
+ return { exists, currentPreKeyId };
198
407
  };
199
408
  const uploadPreKeysToServerIfRequired = async () => {
200
- const preKeyCount = await getAvailablePreKeysOnServer();
201
- logger.info(`${preKeyCount} pre-keys found on server`);
202
- if (preKeyCount <= MIN_PREKEY_COUNT) {
203
- await uploadPreKeys();
409
+ try {
410
+ let count = 0;
411
+ const preKeyCount = await getAvailablePreKeysOnServer();
412
+ if (preKeyCount === 0)
413
+ count = INITIAL_PREKEY_COUNT;
414
+ else
415
+ count = MIN_PREKEY_COUNT;
416
+ const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists();
417
+ logger.info(`${preKeyCount} pre-keys found on server`);
418
+ logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`);
419
+ const lowServerCount = preKeyCount <= count;
420
+ const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0;
421
+ const shouldUpload = lowServerCount || missingCurrentPreKey;
422
+ if (shouldUpload) {
423
+ const reasons = [];
424
+ if (lowServerCount)
425
+ reasons.push(`server count low (${preKeyCount})`);
426
+ if (missingCurrentPreKey)
427
+ reasons.push(`current prekey ${currentPreKeyId} missing from storage`);
428
+ logger.info(`Uploading PreKeys due to: ${reasons.join(', ')}`);
429
+ await uploadPreKeys(count);
430
+ }
431
+ else {
432
+ logger.info(`PreKey validation passed - Server: ${preKeyCount}, Current prekey ${currentPreKeyId} exists`);
433
+ }
434
+ }
435
+ catch (error) {
436
+ logger.error({ error }, 'Failed to check/upload pre-keys during initialization');
437
+ // Don't throw - allow connection to continue even if pre-key check fails
204
438
  }
205
439
  };
206
- const onMessageReceived = (data) => {
207
- noise.decodeFrame(data, frame => {
440
+ const onMessageReceived = async (data) => {
441
+ await noise.decodeFrame(data, frame => {
208
442
  // reset ping timeout
209
443
  lastDateRecv = new Date();
210
444
  let anyTriggered = false;
@@ -425,7 +659,7 @@ export const makeSocket = (config) => {
425
659
  content: [
426
660
  {
427
661
  tag: 'add',
428
- attrs: {},
662
+ attrs: { t: Math.round(Date.now() / 1000) + '' },
429
663
  content: wamBuffer
430
664
  }
431
665
  ]
@@ -497,17 +731,53 @@ export const makeSocket = (config) => {
497
731
  });
498
732
  // login complete
499
733
  ws.on('CB:success', async (node) => {
500
- await uploadPreKeysToServerIfRequired();
501
- await sendPassiveIq('active');
734
+ try {
735
+ await uploadPreKeysToServerIfRequired();
736
+ await sendPassiveIq('active');
737
+ // After successful login, validate our key-bundle against server
738
+ try {
739
+ await digestKeyBundle();
740
+ }
741
+ catch (e) {
742
+ logger.warn({ e }, 'failed to run digest after login');
743
+ }
744
+ }
745
+ catch (err) {
746
+ logger.warn({ err }, 'failed to send initial passive iq');
747
+ }
502
748
  logger.info('opened connection to WA');
503
749
  clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
504
750
  ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
505
751
  ev.emit('connection.update', { connection: 'open' });
752
+ if (node.attrs.lid && authState.creds.me?.id) {
753
+ const myLID = node.attrs.lid;
754
+ process.nextTick(async () => {
755
+ try {
756
+ const myPN = authState.creds.me.id;
757
+ // Store our own LID-PN mapping
758
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }]);
759
+ // Create device list for our own user (needed for bulk migration)
760
+ const { user, device } = jidDecode(myPN);
761
+ await authState.keys.set({
762
+ 'device-list': {
763
+ [user]: [device?.toString() || '0']
764
+ }
765
+ });
766
+ // migrate our own session
767
+ await signalRepository.migrateSession(myPN, myLID);
768
+ logger.info({ myPN, myLID }, 'Own LID session created successfully');
769
+ }
770
+ catch (error) {
771
+ logger.error({ error, lid: myLID }, 'Failed to create own LID session');
772
+ }
773
+ });
774
+ }
506
775
  });
507
776
  ws.on('CB:stream:error', (node) => {
508
- logger.error({ node }, 'stream errored out');
777
+ const [reasonNode] = getAllBinaryNodeChildren(node);
778
+ logger.error({ reasonNode, fullErrorNode: node }, 'stream errored out');
509
779
  const { reason, statusCode } = getErrorCodeFromStreamError(node);
510
- end(new Boom(`Stream Errored (${reason})`, { statusCode, data: node }));
780
+ end(new Boom(`Stream Errored (${reason})`, { statusCode, data: reasonNode || node }));
511
781
  });
512
782
  // stream fail, possible logout
513
783
  ws.on('CB:failure', (node) => {
@@ -517,9 +787,9 @@ export const makeSocket = (config) => {
517
787
  ws.on('CB:ib,,downgrade_webclient', () => {
518
788
  end(new Boom('Multi-device beta not joined', { statusCode: DisconnectReason.multideviceMismatch }));
519
789
  });
520
- ws.on('CB:ib,,offline_preview', (node) => {
790
+ ws.on('CB:ib,,offline_preview', async (node) => {
521
791
  logger.info('offline preview received', JSON.stringify(node));
522
- sendNode({
792
+ await sendNode({
523
793
  tag: 'ib',
524
794
  attrs: {},
525
795
  content: [{ tag: 'offline_batch', attrs: { count: '100' } }]
@@ -589,10 +859,15 @@ export const makeSocket = (config) => {
589
859
  onUnexpectedError,
590
860
  uploadPreKeys,
591
861
  uploadPreKeysToServerIfRequired,
862
+ digestKeyBundle,
863
+ rotateSignedPreKey,
592
864
  requestPairingCode,
865
+ wamBuffer: publicWAMBuffer,
593
866
  /** Waits for the connection to WA to reach a state */
594
867
  waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
595
- sendWAMBuffer
868
+ sendWAMBuffer,
869
+ executeUSyncQuery,
870
+ onWhatsApp
596
871
  };
597
872
  };
598
873
  /**