@queenanya/baileys 9.5.4 → 9.5.5-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +309 -1831
  2. package/WAProto/fix-imports.js +22 -18
  3. package/WAProto/index.js +22 -18
  4. package/lib/Defaults/index.d.ts +2 -0
  5. package/lib/Defaults/index.d.ts.map +1 -1
  6. package/lib/Defaults/index.js +3 -1
  7. package/lib/Defaults/index.js.map +1 -1
  8. package/lib/Socket/business.d.ts +80 -3
  9. package/lib/Socket/business.d.ts.map +1 -1
  10. package/lib/Socket/chats.d.ts +9 -0
  11. package/lib/Socket/chats.d.ts.map +1 -1
  12. package/lib/Socket/chats.js +216 -58
  13. package/lib/Socket/chats.js.map +1 -1
  14. package/lib/Socket/communities.d.ts +80 -3
  15. package/lib/Socket/communities.d.ts.map +1 -1
  16. package/lib/Socket/groups.d.ts +6 -0
  17. package/lib/Socket/groups.d.ts.map +1 -1
  18. package/lib/Socket/groups.js +6 -0
  19. package/lib/Socket/groups.js.map +1 -1
  20. package/lib/Socket/index.d.ts +80 -3
  21. package/lib/Socket/index.d.ts.map +1 -1
  22. package/lib/Socket/messages-recv.d.ts +80 -3
  23. package/lib/Socket/messages-recv.d.ts.map +1 -1
  24. package/lib/Socket/messages-recv.js +315 -60
  25. package/lib/Socket/messages-recv.js.map +1 -1
  26. package/lib/Socket/messages-send.d.ts +109 -3
  27. package/lib/Socket/messages-send.d.ts.map +1 -1
  28. package/lib/Socket/messages-send.js +170 -4
  29. package/lib/Socket/messages-send.js.map +1 -1
  30. package/lib/Socket/newsletter.d.ts +6 -0
  31. package/lib/Socket/newsletter.d.ts.map +1 -1
  32. package/lib/Socket/newsletter.js +2 -2
  33. package/lib/Socket/newsletter.js.map +1 -1
  34. package/lib/Socket/socket.d.ts.map +1 -1
  35. package/lib/Socket/socket.js +3 -3
  36. package/lib/Socket/socket.js.map +1 -1
  37. package/lib/Types/Auth.d.ts +1 -0
  38. package/lib/Types/Auth.d.ts.map +1 -1
  39. package/lib/Types/Call.d.ts +1 -1
  40. package/lib/Types/Call.d.ts.map +1 -1
  41. package/lib/Types/Contact.d.ts +2 -0
  42. package/lib/Types/Contact.d.ts.map +1 -1
  43. package/lib/Types/Events.d.ts +18 -1
  44. package/lib/Types/Events.d.ts.map +1 -1
  45. package/lib/Types/GroupMetadata.d.ts +4 -0
  46. package/lib/Types/GroupMetadata.d.ts.map +1 -1
  47. package/lib/Types/Message.d.ts +360 -9
  48. package/lib/Types/Message.d.ts.map +1 -1
  49. package/lib/Types/Message.js.map +1 -1
  50. package/lib/Types/Newsletter.d.ts +37 -42
  51. package/lib/Types/Newsletter.d.ts.map +1 -1
  52. package/lib/Types/Newsletter.js +18 -23
  53. package/lib/Types/Newsletter.js.map +1 -1
  54. package/lib/Types/State.d.ts +54 -0
  55. package/lib/Types/State.d.ts.map +1 -1
  56. package/lib/Types/State.js +42 -0
  57. package/lib/Types/State.js.map +1 -1
  58. package/lib/Utils/chat-utils.d.ts +30 -0
  59. package/lib/Utils/chat-utils.d.ts.map +1 -1
  60. package/lib/Utils/chat-utils.js +34 -8
  61. package/lib/Utils/chat-utils.js.map +1 -1
  62. package/lib/Utils/decode-wa-message.d.ts +12 -0
  63. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  64. package/lib/Utils/decode-wa-message.js +16 -0
  65. package/lib/Utils/decode-wa-message.js.map +1 -1
  66. package/lib/Utils/event-buffer.js +2 -0
  67. package/lib/Utils/event-buffer.js.map +1 -1
  68. package/lib/Utils/generics.d.ts.map +1 -1
  69. package/lib/Utils/generics.js +9 -0
  70. package/lib/Utils/generics.js.map +1 -1
  71. package/lib/Utils/history.d.ts.map +1 -1
  72. package/lib/Utils/history.js +12 -10
  73. package/lib/Utils/history.js.map +1 -1
  74. package/lib/Utils/identity-change-handler.d.ts +7 -0
  75. package/lib/Utils/identity-change-handler.d.ts.map +1 -1
  76. package/lib/Utils/identity-change-handler.js +1 -0
  77. package/lib/Utils/identity-change-handler.js.map +1 -1
  78. package/lib/Utils/index.d.ts +3 -0
  79. package/lib/Utils/index.d.ts.map +1 -1
  80. package/lib/Utils/index.js +3 -0
  81. package/lib/Utils/index.js.map +1 -1
  82. package/lib/Utils/interactive-message.js.map +1 -1
  83. package/lib/Utils/message-composer.d.ts +5 -0
  84. package/lib/Utils/message-composer.d.ts.map +1 -0
  85. package/lib/Utils/message-composer.js +5 -0
  86. package/lib/Utils/message-composer.js.map +1 -0
  87. package/lib/Utils/message-retry-manager.js.map +1 -1
  88. package/lib/Utils/messages-media.d.ts +1 -1
  89. package/lib/Utils/messages-media.d.ts.map +1 -1
  90. package/lib/Utils/messages-media.js +2 -2
  91. package/lib/Utils/messages-media.js.map +1 -1
  92. package/lib/Utils/messages.d.ts.map +1 -1
  93. package/lib/Utils/messages.js +14 -5
  94. package/lib/Utils/messages.js.map +1 -1
  95. package/lib/Utils/noise-handler.js.map +1 -1
  96. package/lib/Utils/process-message.d.ts.map +1 -1
  97. package/lib/Utils/process-message.js +58 -2
  98. package/lib/Utils/process-message.js.map +1 -1
  99. package/lib/Utils/sync-action-utils.d.ts.map +1 -1
  100. package/lib/Utils/sync-action-utils.js +1 -0
  101. package/lib/Utils/sync-action-utils.js.map +1 -1
  102. package/lib/Utils/tc-token-utils.d.ts +26 -1
  103. package/lib/Utils/tc-token-utils.d.ts.map +1 -1
  104. package/lib/Utils/tc-token-utils.js +149 -4
  105. package/lib/Utils/tc-token-utils.js.map +1 -1
  106. package/lib/WABinary/jid-utils.js.map +1 -1
  107. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -1
  108. package/lib/WAUSync/Protocols/USyncContactProtocol.js +26 -3
  109. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -1
  110. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  111. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  112. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  113. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  114. package/lib/WAUSync/Protocols/index.d.ts +1 -0
  115. package/lib/WAUSync/Protocols/index.d.ts.map +1 -1
  116. package/lib/WAUSync/Protocols/index.js +1 -0
  117. package/lib/WAUSync/Protocols/index.js.map +1 -1
  118. package/lib/WAUSync/USyncQuery.d.ts +1 -0
  119. package/lib/WAUSync/USyncQuery.d.ts.map +1 -1
  120. package/lib/WAUSync/USyncQuery.js +5 -1
  121. package/lib/WAUSync/USyncQuery.js.map +1 -1
  122. package/lib/WAUSync/USyncUser.d.ts +4 -0
  123. package/lib/WAUSync/USyncUser.d.ts.map +1 -1
  124. package/lib/WAUSync/USyncUser.js +8 -0
  125. package/lib/WAUSync/USyncUser.js.map +1 -1
  126. package/lib/addons/auto-reply.js.map +1 -1
  127. package/lib/addons/browser-presets.d.ts +16 -0
  128. package/lib/addons/browser-presets.d.ts.map +1 -0
  129. package/lib/addons/browser-presets.js +24 -0
  130. package/lib/addons/browser-presets.js.map +1 -0
  131. package/lib/addons/button-sender.d.ts +0 -2
  132. package/lib/addons/button-sender.d.ts.map +1 -1
  133. package/lib/addons/button-sender.js +21 -23
  134. package/lib/addons/button-sender.js.map +1 -1
  135. package/lib/addons/call-handler.d.ts.map +1 -1
  136. package/lib/addons/call-handler.js.map +1 -1
  137. package/lib/addons/from-messages-recv.d.ts.map +1 -1
  138. package/lib/addons/from-messages-recv.js.map +1 -1
  139. package/lib/addons/from-messages.js.map +1 -1
  140. package/lib/addons/index.d.ts +22 -5
  141. package/lib/addons/index.d.ts.map +1 -1
  142. package/lib/addons/index.js +29 -17
  143. package/lib/addons/index.js.map +1 -1
  144. package/lib/addons/interactive-message.js.map +1 -1
  145. package/lib/addons/jid-plot.d.ts +49 -0
  146. package/lib/addons/jid-plot.d.ts.map +1 -0
  147. package/lib/addons/jid-plot.js +84 -0
  148. package/lib/addons/jid-plot.js.map +1 -0
  149. package/lib/addons/lid-support.d.ts +41 -0
  150. package/lib/addons/lid-support.d.ts.map +1 -0
  151. package/lib/addons/lid-support.js +42 -0
  152. package/lib/addons/lid-support.js.map +1 -0
  153. package/lib/addons/message-composer.d.ts +142 -0
  154. package/lib/addons/message-composer.d.ts.map +1 -0
  155. package/lib/addons/message-composer.js +377 -0
  156. package/lib/addons/message-composer.js.map +1 -0
  157. package/lib/addons/message-scheduler.d.ts +77 -0
  158. package/lib/addons/message-scheduler.d.ts.map +1 -0
  159. package/lib/addons/message-scheduler.js +108 -0
  160. package/lib/addons/message-scheduler.js.map +1 -0
  161. package/lib/addons/message-utils.d.ts.map +1 -1
  162. package/lib/addons/message-utils.js.map +1 -1
  163. package/lib/addons/outgoing-calls.d.ts +64 -0
  164. package/lib/addons/outgoing-calls.d.ts.map +1 -0
  165. package/lib/addons/outgoing-calls.js +139 -0
  166. package/lib/addons/outgoing-calls.js.map +1 -0
  167. package/lib/addons/pairing-fix.d.ts +31 -0
  168. package/lib/addons/pairing-fix.d.ts.map +1 -0
  169. package/lib/addons/pairing-fix.js +74 -0
  170. package/lib/addons/pairing-fix.js.map +1 -0
  171. package/lib/addons/past-participants.d.ts +42 -0
  172. package/lib/addons/past-participants.d.ts.map +1 -0
  173. package/lib/addons/past-participants.js +41 -0
  174. package/lib/addons/past-participants.js.map +1 -0
  175. package/lib/addons/rich-response.d.ts +111 -0
  176. package/lib/addons/rich-response.d.ts.map +1 -0
  177. package/lib/addons/rich-response.js +152 -0
  178. package/lib/addons/rich-response.js.map +1 -0
  179. package/lib/addons/status-posting.d.ts.map +1 -1
  180. package/lib/addons/status-posting.js +1 -3
  181. package/lib/addons/status-posting.js.map +1 -1
  182. package/lib/addons/stickerpack.d.ts +37 -0
  183. package/lib/addons/stickerpack.d.ts.map +1 -0
  184. package/lib/addons/stickerpack.js +39 -0
  185. package/lib/addons/stickerpack.js.map +1 -0
  186. package/package.json +3 -3
@@ -4,16 +4,18 @@ import { randomBytes } from 'crypto';
4
4
  import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
7
- import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
7
+ import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
10
11
  import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
12
  import { extractGroupMetadata } from './groups.js';
12
13
  import { makeMessagesSocket } from './messages-send.js';
13
14
  export const makeMessagesRecvSocket = (config) => {
14
15
  const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
15
16
  const sock = makeMessagesSocket(config);
16
- const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, getUSyncDevices, createParticipantNodes, messageRetryManager } = sock;
17
+ const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, getUSyncDevices, createParticipantNodes, messageRetryManager, issuePrivacyTokens } = sock;
18
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
17
19
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
18
20
  const retryMutex = makeMutex();
19
21
  const msgRetryCache = config.msgRetryCounterCache ||
@@ -84,25 +86,145 @@ export const makeMessagesRecvSocket = (config) => {
84
86
  }, 8000);
85
87
  return sendPeerDataOperationMessage(pdoMessage);
86
88
  };
87
- // Handles mex newsletter notifications
88
- const handleMexNewsletterNotification = async (node) => {
89
+ const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
90
+ function isValidEnforcementType(value) {
91
+ return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
92
+ }
93
+ // ── Top-level mex dispatcher (PR: feat-mex-notification-dispatch) ─────────
94
+ const handleMexNotification = async (node) => {
95
+ const updateNode = getBinaryNodeChild(node, 'update');
96
+ if (updateNode) {
97
+ const opName = updateNode.attrs?.op_name;
98
+ if (!opName) {
99
+ logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name');
100
+ return;
101
+ }
102
+ let mexResponse;
103
+ try {
104
+ mexResponse = JSON.parse(updateNode.content.toString());
105
+ }
106
+ catch (error) {
107
+ logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
108
+ return;
109
+ }
110
+ if (mexResponse.errors?.length) {
111
+ logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
112
+ return;
113
+ }
114
+ const data = mexResponse.data;
115
+ if (!data) {
116
+ logger.warn({ opName }, 'mex notification has null data');
117
+ return;
118
+ }
119
+ logger.debug({ opName }, 'processing mex notification');
120
+ switch (opName) {
121
+ case 'NotificationUserReachoutTimelockUpdate':
122
+ handleReachoutTimelockNotification(data);
123
+ break;
124
+ case 'MessageCappingInfoNotification':
125
+ handleMessageCappingNotification(data);
126
+ break;
127
+ case 'NotificationLinkedProfilesUpdates': {
128
+ // PR: fix-mex-linked-profiles
129
+ const linkedProfiles = data.xwa2_notify_linked_profiles;
130
+ if (!linkedProfiles)
131
+ break;
132
+ const lid = linkedProfiles.jid;
133
+ for (const profile of linkedProfiles.added_profiles ?? []) {
134
+ const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
135
+ if (lid && pn)
136
+ ev.emit('lid-mapping.update', { lid, pn });
137
+ }
138
+ break;
139
+ }
140
+ // newsletter ops still use the legacy <mex> child structure
141
+ case 'NotificationNewsletterUpdate':
142
+ case 'NotificationNewsletterAdminPromote':
143
+ case 'NotificationNewsletterAdminDemote':
144
+ case 'NotificationNewsletterUserSettingChange':
145
+ case 'NotificationNewsletterJoin':
146
+ case 'NotificationNewsletterLeave':
147
+ case 'NotificationNewsletterStateChange':
148
+ case 'NotificationNewsletterAdminMetadataUpdate':
149
+ case 'NotificationNewsletterOwnerUpdate':
150
+ case 'NotificationNewsletterAdminInviteRevoke':
151
+ case 'NotificationNewsletterWamoSubStatusChange':
152
+ case 'NotificationNewsletterBlockUser':
153
+ case 'NotificationNewsletterPaidPartnership':
154
+ case 'NotificationNewsletterMilestone':
155
+ case 'NewsletterResponseStateUpdate':
156
+ await handleLegacyMexNewsletterNotification(node);
157
+ break;
158
+ default:
159
+ logger.debug({ opName }, 'unhandled mex notification');
160
+ break;
161
+ }
162
+ return;
163
+ }
164
+ await handleLegacyMexNewsletterNotification(node);
165
+ };
166
+ const handleReachoutTimelockNotification = (data) => {
167
+ const payload = data.xwa2_notify_account_reachout_timelock;
168
+ if (!payload) {
169
+ logger.warn('reachout timelock notification missing payload');
170
+ return;
171
+ }
172
+ if (!payload.is_active) {
173
+ logger.info('reachout timelock restriction lifted');
174
+ ev.emit('connection.update', {
175
+ reachoutTimeLock: {
176
+ isActive: false,
177
+ enforcementType: ReachoutTimelockEnforcementType.DEFAULT
178
+ }
179
+ });
180
+ return;
181
+ }
182
+ // WA Web defaults to now+60s when the server omits the expiry
183
+ const timeEnforcementEnds = payload.time_enforcement_ends
184
+ ? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
185
+ : new Date(Date.now() + 60000);
186
+ const enforcementType = isValidEnforcementType(payload.enforcement_type)
187
+ ? payload.enforcement_type
188
+ : ReachoutTimelockEnforcementType.DEFAULT;
189
+ logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
190
+ ev.emit('connection.update', {
191
+ reachoutTimeLock: {
192
+ isActive: true,
193
+ timeEnforcementEnds,
194
+ enforcementType
195
+ }
196
+ });
197
+ };
198
+ const handleMessageCappingNotification = (data) => {
199
+ const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
200
+ if (!payload) {
201
+ logger.warn('message capping notification missing payload');
202
+ return;
203
+ }
204
+ logger.info({ payload }, 'received message capping update');
205
+ ev.emit('message-capping.update', payload);
206
+ };
207
+ // ── Legacy mex newsletter notification handler ────────────────────────────
208
+ const handleLegacyMexNewsletterNotification = async (node) => {
89
209
  const mexNode = getBinaryNodeChild(node, 'mex');
90
210
  if (!mexNode?.content) {
91
- logger.warn({ node }, 'Invalid mex newsletter notification');
211
+ logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
92
212
  return;
93
213
  }
94
- let data;
214
+ let parsed;
95
215
  try {
96
- data = JSON.parse(mexNode.content.toString());
216
+ // PR fix-mex-linked-profiles: handle binary-encoded content correctly
217
+ const payloadContent = mexNode.content;
218
+ const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
219
+ parsed = JSON.parse(contentBuf.toString());
97
220
  }
98
221
  catch (error) {
99
- logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
222
+ logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
100
223
  return;
101
224
  }
102
- const operation = data?.operation;
103
- const updates = data?.updates;
225
+ const { operation, updates } = parsed;
104
226
  if (!updates || !operation) {
105
- logger.warn({ data }, 'Invalid mex newsletter notification content');
227
+ logger.warn({ parsed }, 'invalid mex newsletter notification content');
106
228
  return;
107
229
  }
108
230
  logger.info({ operation, updates }, 'got mex newsletter notification');
@@ -131,7 +253,7 @@ export const makeMessagesRecvSocket = (config) => {
131
253
  }
132
254
  break;
133
255
  default:
134
- logger.info({ operation, data }, 'Unhandled mex newsletter notification');
256
+ logger.info({ operation, parsed }, 'unhandled mex newsletter notification');
135
257
  break;
136
258
  }
137
259
  };
@@ -664,7 +786,8 @@ export const makeMessagesRecvSocket = (config) => {
664
786
  validateSession: signalRepository.validateSession,
665
787
  assertSessions,
666
788
  debounceCache: identityAssertDebounce,
667
- logger
789
+ logger,
790
+ onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
668
791
  });
669
792
  if (result.action === 'no_identity_node') {
670
793
  logger.info({ node }, 'unknown encrypt notification');
@@ -675,6 +798,7 @@ export const makeMessagesRecvSocket = (config) => {
675
798
  // TODO: Support PN/LID (Here is only LID now)
676
799
  const actingParticipantLid = fullNode.attrs.participant;
677
800
  const actingParticipantPn = fullNode.attrs.participant_pn;
801
+ const actingParticipantUsername = fullNode.attrs.participant_username;
678
802
  const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
679
803
  const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
680
804
  switch (child?.tag) {
@@ -694,7 +818,8 @@ export const makeMessagesRecvSocket = (config) => {
694
818
  {
695
819
  ...metadata,
696
820
  author: actingParticipantLid,
697
- authorPn: actingParticipantPn
821
+ authorPn: actingParticipantPn,
822
+ authorUsername: actingParticipantUsername
698
823
  }
699
824
  ]);
700
825
  break;
@@ -804,7 +929,7 @@ export const makeMessagesRecvSocket = (config) => {
804
929
  await handleNewsletterNotification(node);
805
930
  break;
806
931
  case 'mex':
807
- await handleMexNewsletterNotification(node);
932
+ await handleMexNotification(node);
808
933
  break;
809
934
  case 'w:gp2':
810
935
  // TODO: HANDLE PARTICIPANT_PN
@@ -950,27 +1075,91 @@ export const makeMessagesRecvSocket = (config) => {
950
1075
  return result;
951
1076
  }
952
1077
  };
1078
+ /**
1079
+ * In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
1080
+ */
1081
+ const tcTokenKnownJids = new Set();
1082
+ const tcTokenIndexLoaded = (async () => {
1083
+ try {
1084
+ const jids = await readTcTokenIndex(authState.keys);
1085
+ for (const jid of jids)
1086
+ tcTokenKnownJids.add(jid);
1087
+ logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
1088
+ }
1089
+ catch (err) {
1090
+ logger.warn({ err: err?.message }, 'failed to load tctoken index');
1091
+ }
1092
+ })();
1093
+ let tcTokenIndexTimer;
1094
+ async function flushTcTokenIndex() {
1095
+ if (tcTokenIndexTimer) {
1096
+ clearTimeout(tcTokenIndexTimer);
1097
+ tcTokenIndexTimer = undefined;
1098
+ }
1099
+ const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
1100
+ return authState.keys.set({ tctoken: write });
1101
+ }
1102
+ function scheduleTcTokenIndexSave() {
1103
+ if (tcTokenIndexTimer) {
1104
+ clearTimeout(tcTokenIndexTimer);
1105
+ }
1106
+ tcTokenIndexTimer = setTimeout(() => {
1107
+ tcTokenIndexTimer = undefined;
1108
+ flushTcTokenIndex().catch(err => {
1109
+ logger.warn({ err: err?.message }, 'failed to save tctoken index');
1110
+ });
1111
+ }, 5000);
1112
+ }
1113
+ function trackTcTokenJid(jid) {
1114
+ if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
1115
+ tcTokenKnownJids.add(jid);
1116
+ scheduleTcTokenIndexSave();
1117
+ }
1118
+ }
953
1119
  const handlePrivacyTokenNotification = async (node) => {
954
1120
  const tokensNode = getBinaryNodeChild(node, 'tokens');
955
- const from = jidNormalizedUser(node.attrs.from);
956
1121
  if (!tokensNode)
957
1122
  return;
958
- const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
959
- for (const tokenNode of tokenNodes) {
960
- const { attrs, content } = tokenNode;
961
- const type = attrs.type;
962
- const timestamp = attrs.t;
963
- if (type === 'trusted_contact' && content instanceof Buffer) {
964
- logger.debug({
965
- from,
966
- timestamp,
967
- tcToken: content
968
- }, 'received trusted contact token');
969
- await authState.keys.set({
970
- tctoken: { [from]: { token: content, timestamp } }
971
- });
1123
+ const from = jidNormalizedUser(node.attrs.from);
1124
+ const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
1125
+ ? jidNormalizedUser(node.attrs.sender_lid)
1126
+ : undefined;
1127
+ const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
1128
+ logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
1129
+ await storeTcTokensFromIqResult({
1130
+ result: node,
1131
+ fallbackJid,
1132
+ keys: authState.keys,
1133
+ getLIDForPN,
1134
+ onNewJidStored: trackTcTokenJid
1135
+ });
1136
+ };
1137
+ /**
1138
+ * Fire-and-forget tctoken re-issuance after a peer's device identity changed.
1139
+ */
1140
+ const reissueTcTokenAfterIdentityChange = (from) => {
1141
+ void (async () => {
1142
+ const normalizedJid = jidNormalizedUser(from);
1143
+ const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
1144
+ const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
1145
+ const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
1146
+ if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
1147
+ return;
972
1148
  }
973
- }
1149
+ logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
1150
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
1151
+ const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
1152
+ const result = await issuePrivacyTokens([issueJid], senderTs);
1153
+ await storeTcTokensFromIqResult({
1154
+ result,
1155
+ fallbackJid: tcJid,
1156
+ keys: authState.keys,
1157
+ getLIDForPN,
1158
+ onNewJidStored: trackTcTokenJid
1159
+ });
1160
+ })().catch(err => {
1161
+ logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
1162
+ });
974
1163
  };
975
1164
  async function decipherLinkPublicKey(data) {
976
1165
  const buffer = toRequiredBuffer(data);
@@ -1437,23 +1626,22 @@ export const makeMessagesRecvSocket = (config) => {
1437
1626
  };
1438
1627
  const handleBadAck = async ({ attrs }) => {
1439
1628
  const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1440
- // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1441
- // // current hypothesis is that if pash is sent in the ack
1442
- // // it means -- the message hasn't reached all devices yet
1443
- // // we'll retry sending the message here
1444
- // if(attrs.phash) {
1445
- // logger.info({ attrs }, 'received phash in ack, resending message...')
1446
- // const msg = await getMessage(key)
1447
- // if(msg) {
1448
- // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1449
- // } else {
1450
- // logger.warn({ attrs }, 'could not send message again, as it was not found')
1451
- // }
1452
- // }
1453
1629
  // error in acknowledgement,
1454
1630
  // device could not display the message
1455
1631
  if (attrs.error) {
1456
- logger.warn({ attrs }, 'received error in ack');
1632
+ if (attrs.error === SERVER_ERROR_CODES.MissingTcToken) {
1633
+ // 463 = account restricted + no tctoken for this contact.
1634
+ // WA Web prevents this client-side (disables compose bar).
1635
+ // No retry — retrying worsens the restriction by counting
1636
+ // as another "reach out" to an unknown contact.
1637
+ logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
1638
+ }
1639
+ else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
1640
+ logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
1641
+ }
1642
+ else {
1643
+ logger.warn({ attrs }, 'received error in ack');
1644
+ }
1457
1645
  ev.emit('messages.update', [
1458
1646
  {
1459
1647
  key,
@@ -1463,19 +1651,6 @@ export const makeMessagesRecvSocket = (config) => {
1463
1651
  }
1464
1652
  }
1465
1653
  ]);
1466
- // resend the message with device_fanout=false, use at your own risk
1467
- // if (attrs.error === '475') {
1468
- // const msg = await getMessage(key)
1469
- // if (msg) {
1470
- // await relayMessage(key.remoteJid!, msg, {
1471
- // messageId: key.id!,
1472
- // useUserDevicesCache: false,
1473
- // additionalAttributes: {
1474
- // device_fanout: 'false'
1475
- // }
1476
- // })
1477
- // }
1478
- // }
1479
1654
  }
1480
1655
  };
1481
1656
  /// processes a node with the given function
@@ -1590,12 +1765,92 @@ export const makeMessagesRecvSocket = (config) => {
1590
1765
  await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
1591
1766
  }
1592
1767
  });
1593
- ev.on('connection.update', ({ isOnline }) => {
1768
+ /** timestamp of last tctoken prune run — throttles to once per 24h */
1769
+ let lastTcTokenPruneTs = 0;
1770
+ ev.on('connection.update', ({ isOnline, connection }) => {
1594
1771
  if (typeof isOnline !== 'undefined') {
1595
1772
  sendActiveReceipts = isOnline;
1596
1773
  logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
1597
1774
  }
1775
+ // Flush pending tctoken index save on disconnect to avoid writing after close
1776
+ if (connection === 'close' && tcTokenIndexTimer) {
1777
+ clearTimeout(tcTokenIndexTimer);
1778
+ tcTokenIndexTimer = undefined;
1779
+ try {
1780
+ void Promise.resolve(flushTcTokenIndex()).catch(() => { });
1781
+ }
1782
+ catch {
1783
+ /* ignore sync errors */
1784
+ }
1785
+ }
1786
+ // Prune expired tctokens when coming online, at most once per 24 hours
1787
+ if (isOnline) {
1788
+ const now = Date.now();
1789
+ const DAY_MS = 24 * 60 * 60 * 1000;
1790
+ if (now - lastTcTokenPruneTs >= DAY_MS) {
1791
+ lastTcTokenPruneTs = now;
1792
+ void pruneExpiredTcTokens();
1793
+ }
1794
+ }
1598
1795
  });
1796
+ async function pruneExpiredTcTokens() {
1797
+ try {
1798
+ await tcTokenIndexLoaded;
1799
+ const persisted = await readTcTokenIndex(authState.keys);
1800
+ const allJids = new Set(tcTokenKnownJids);
1801
+ for (const jid of persisted)
1802
+ allJids.add(jid);
1803
+ if (!allJids.size)
1804
+ return;
1805
+ const jids = [...allJids];
1806
+ const allTokens = await authState.keys.get('tctoken', jids);
1807
+ const writes = {};
1808
+ const survivors = new Set();
1809
+ let mutated = 0;
1810
+ for (const jid of jids) {
1811
+ const entry = allTokens[jid];
1812
+ if (!entry) {
1813
+ mutated++;
1814
+ continue;
1815
+ }
1816
+ const hasPeerToken = !!entry.token?.length;
1817
+ const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
1818
+ const hasSenderTs = entry.senderTimestamp !== undefined;
1819
+ const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
1820
+ const keepPeerToken = hasPeerToken && !peerTokenExpired;
1821
+ const keepSenderTs = hasSenderTs && !senderTsExpired;
1822
+ if (!keepPeerToken && !keepSenderTs) {
1823
+ writes[jid] = null;
1824
+ mutated++;
1825
+ }
1826
+ else if (peerTokenExpired && keepSenderTs) {
1827
+ writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
1828
+ survivors.add(jid);
1829
+ mutated++;
1830
+ }
1831
+ else {
1832
+ survivors.add(jid);
1833
+ }
1834
+ }
1835
+ if (mutated === 0)
1836
+ return;
1837
+ await authState.keys.set({
1838
+ tctoken: {
1839
+ ...writes,
1840
+ [TC_TOKEN_INDEX_KEY]: {
1841
+ token: Buffer.from(JSON.stringify([...survivors]))
1842
+ }
1843
+ }
1844
+ });
1845
+ tcTokenKnownJids.clear();
1846
+ for (const jid of survivors)
1847
+ tcTokenKnownJids.add(jid);
1848
+ logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
1849
+ }
1850
+ catch (err) {
1851
+ logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
1852
+ }
1853
+ }
1599
1854
  return {
1600
1855
  ...sock,
1601
1856
  sendMessageAck,