@bellaxchuu/flowred 0.8.6

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 (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/WAProto/GenerateStatics.sh +3 -0
  4. package/WAProto/WAProto.proto +6902 -0
  5. package/WAProto/fix-imports.js +85 -0
  6. package/WAProto/index.js +242946 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +190 -0
  9. package/lib/Defaults/phonenumber-mcc.json +223 -0
  10. package/lib/Signal/Group/ciphertext-message.js +15 -0
  11. package/lib/Signal/Group/group-session-builder.js +92 -0
  12. package/lib/Signal/Group/group_cipher.js +89 -0
  13. package/lib/Signal/Group/index.js +136 -0
  14. package/lib/Signal/Group/keyhelper.js +73 -0
  15. package/lib/Signal/Group/sender-chain-key.js +32 -0
  16. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  17. package/lib/Signal/Group/sender-key-message.js +69 -0
  18. package/lib/Signal/Group/sender-key-name.js +50 -0
  19. package/lib/Signal/Group/sender-key-record.js +44 -0
  20. package/lib/Signal/Group/sender-key-state.js +97 -0
  21. package/lib/Signal/Group/sender-message-key.js +30 -0
  22. package/lib/Signal/libsignal.js +490 -0
  23. package/lib/Signal/lid-mapping.js +262 -0
  24. package/lib/Socket/Client/index.js +30 -0
  25. package/lib/Socket/Client/types.js +13 -0
  26. package/lib/Socket/Client/websocket.js +62 -0
  27. package/lib/Socket/aigroups.js +240 -0
  28. package/lib/Socket/business.js +422 -0
  29. package/lib/Socket/chats.js +1816 -0
  30. package/lib/Socket/communities.js +549 -0
  31. package/lib/Socket/graphql.js +656 -0
  32. package/lib/Socket/groups.js +764 -0
  33. package/lib/Socket/index.js +39 -0
  34. package/lib/Socket/interactive-handler.js +522 -0
  35. package/lib/Socket/interop.js +549 -0
  36. package/lib/Socket/luxu.js +387 -0
  37. package/lib/Socket/managed-account.js +183 -0
  38. package/lib/Socket/messages-recv.js +2440 -0
  39. package/lib/Socket/messages-send.js +1853 -0
  40. package/lib/Socket/mex.js +60 -0
  41. package/lib/Socket/newsletter.js +820 -0
  42. package/lib/Socket/privacy.js +449 -0
  43. package/lib/Socket/registration.js +427 -0
  44. package/lib/Socket/socket.js +1092 -0
  45. package/lib/Socket/text-router.js +83 -0
  46. package/lib/Socket/username.js +243 -0
  47. package/lib/Store/index.js +36 -0
  48. package/lib/Store/keyed-db.js +108 -0
  49. package/lib/Store/make-cache-manager-store.js +90 -0
  50. package/lib/Store/make-in-memory-store.js +604 -0
  51. package/lib/Store/make-ordered-dictionary.js +81 -0
  52. package/lib/Store/object-repository.js +29 -0
  53. package/lib/Types/Auth.js +38 -0
  54. package/lib/Types/Bussines.js +2 -0
  55. package/lib/Types/Call.js +2 -0
  56. package/lib/Types/Chat.js +4 -0
  57. package/lib/Types/Contact.js +2 -0
  58. package/lib/Types/Events.js +2 -0
  59. package/lib/Types/GroupMetadata.js +2 -0
  60. package/lib/Types/Label.js +27 -0
  61. package/lib/Types/LabelAssociation.js +9 -0
  62. package/lib/Types/Message.js +95 -0
  63. package/lib/Types/Mex.js +112 -0
  64. package/lib/Types/Newsletter.js +121 -0
  65. package/lib/Types/Product.js +2 -0
  66. package/lib/Types/Signal.js +2 -0
  67. package/lib/Types/Socket.js +2 -0
  68. package/lib/Types/State.js +70 -0
  69. package/lib/Types/USync.js +2 -0
  70. package/lib/Types/index.js +55 -0
  71. package/lib/Utils/auth-utils.js +306 -0
  72. package/lib/Utils/browser-utils.js +114 -0
  73. package/lib/Utils/business.js +247 -0
  74. package/lib/Utils/chat-utils.js +1208 -0
  75. package/lib/Utils/command-loader.js +100 -0
  76. package/lib/Utils/companion-reg-client-utils.js +42 -0
  77. package/lib/Utils/consumer-application.js +107 -0
  78. package/lib/Utils/crypto.js +155 -0
  79. package/lib/Utils/curve25519-js.js +275 -0
  80. package/lib/Utils/decode-wa-message.js +560 -0
  81. package/lib/Utils/event-buffer.js +607 -0
  82. package/lib/Utils/generics.js +563 -0
  83. package/lib/Utils/group-history.js +60 -0
  84. package/lib/Utils/history.js +244 -0
  85. package/lib/Utils/identity-change-handler.js +52 -0
  86. package/lib/Utils/index.js +57 -0
  87. package/lib/Utils/jid-display-normalization.js +218 -0
  88. package/lib/Utils/link-preview.js +143 -0
  89. package/lib/Utils/logger.js +9 -0
  90. package/lib/Utils/lt-hash.js +39 -0
  91. package/lib/Utils/make-mutex.js +36 -0
  92. package/lib/Utils/message-composer.js +479 -0
  93. package/lib/Utils/message-retry-manager.js +270 -0
  94. package/lib/Utils/messages-media.js +896 -0
  95. package/lib/Utils/messages.js +2904 -0
  96. package/lib/Utils/meta-ai-msmsg.js +262 -0
  97. package/lib/Utils/native-bridge.js +82 -0
  98. package/lib/Utils/noise-handler.js +196 -0
  99. package/lib/Utils/offline-node-processor.js +42 -0
  100. package/lib/Utils/pre-key-manager.js +107 -0
  101. package/lib/Utils/process-message.js +1048 -0
  102. package/lib/Utils/reporting-utils.js +262 -0
  103. package/lib/Utils/session-pool.js +94 -0
  104. package/lib/Utils/signal.js +224 -0
  105. package/lib/Utils/stanza-ack.js +40 -0
  106. package/lib/Utils/sticker.js +145 -0
  107. package/lib/Utils/sync-action-utils.js +54 -0
  108. package/lib/Utils/tc-token-utils.js +161 -0
  109. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  110. package/lib/Utils/validate-connection.js +219 -0
  111. package/lib/Utils/view-once-cache.js +79 -0
  112. package/lib/Utils/voip-rekey.js +22 -0
  113. package/lib/WABinary/constants.js +1304 -0
  114. package/lib/WABinary/decode.js +343 -0
  115. package/lib/WABinary/encode.js +228 -0
  116. package/lib/WABinary/generic-utils.js +240 -0
  117. package/lib/WABinary/index.js +33 -0
  118. package/lib/WABinary/jid-utils.js +361 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +13 -0
  121. package/lib/WAM/constants.js +39486 -0
  122. package/lib/WAM/encode.js +142 -0
  123. package/lib/WAM/index.js +31 -0
  124. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +55 -0
  125. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  126. package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  128. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  129. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +59 -0
  130. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +31 -0
  131. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
  132. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
  133. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
  134. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  135. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  136. package/lib/WAUSync/Protocols/index.js +39 -0
  137. package/lib/WAUSync/USyncQuery.js +133 -0
  138. package/lib/WAUSync/USyncUser.js +50 -0
  139. package/lib/WAUSync/index.js +31 -0
  140. package/lib/antiban.js +4637 -0
  141. package/lib/index.js +45 -0
  142. package/package.json +107 -0
@@ -0,0 +1,1816 @@
1
+ 'use strict'
2
+ var __importDefault =
3
+ (this && this.__importDefault) ||
4
+ function (mod) {
5
+ return mod && mod.__esModule ? mod : { default: mod }
6
+ }
7
+ Object.defineProperty(exports, '__esModule', { value: true })
8
+ exports.makeChatsSocket = void 0
9
+ const node_cache_1 = __importDefault(require('@cacheable/node-cache'))
10
+ const boom_1 = require('@hapi/boom')
11
+ const index_js_1 = require('../../WAProto/index.js')
12
+ const Defaults_1 = require('../Defaults')
13
+ const Types_1 = require('../Types')
14
+ const State_1 = require('../Types/State')
15
+ const Utils_1 = require('../Utils')
16
+ const make_mutex_1 = require('../Utils/make-mutex')
17
+ const process_message_1 = __importDefault(require('../Utils/process-message'))
18
+ const tc_token_utils_1 = require('../Utils/tc-token-utils')
19
+ const WABinary_1 = require('../WABinary')
20
+ const WAUSync_1 = require('../WAUSync')
21
+ const socket_js_1 = require('./socket.js')
22
+ const interop_js_1 = require('./interop.js')
23
+ const makeChatsSocket = config => {
24
+ const {
25
+ logger,
26
+ markOnlineOnConnect,
27
+ fireInitQueries,
28
+ appStateMacVerification,
29
+ shouldIgnoreJid,
30
+ shouldSyncHistoryMessage,
31
+ getMessage
32
+ } = config
33
+ const sock = (0, interop_js_1.makeInteropSocket)((0, socket_js_1.makeSocket)(config))
34
+ const {
35
+ ev,
36
+ ws,
37
+ authState,
38
+ generateMessageTag,
39
+ sendNode,
40
+ query,
41
+ signalRepository,
42
+ onUnexpectedError,
43
+ sendUnifiedSession,
44
+ initInterop,
45
+ fetchIntegrators,
46
+ acceptInteropTOS,
47
+ optInIntegrators,
48
+ optOutIntegrators,
49
+ resolveInteropUser,
50
+ resolveInteropUsers,
51
+ getReachabilitySettings,
52
+ setReachabilitySettings,
53
+ blockInteropUser,
54
+ unblockInteropUser,
55
+ reportInteropSpam,
56
+ trustInteropContact,
57
+ createInteropGroup,
58
+ leaveInteropGroup,
59
+ getInteropGroupAddPrivacy,
60
+ INTEGRATOR_BIRDYCHAT,
61
+ INTEGRATOR_HAIKET
62
+ } = sock
63
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
64
+ let privacySettings
65
+ /** Server-assigned AB props for protocol behavior. */
66
+ const serverProps = {
67
+ /** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
68
+ privacyTokenOn1to1: true,
69
+ /** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
70
+ profilePicPrivacyToken: true,
71
+ /** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
72
+ lidTrustedTokenIssueToLid: false
73
+ }
74
+ let syncState = State_1.SyncState.Connecting
75
+ /** this mutex ensures that messages are processed in order */
76
+ const messageMutex = (0, make_mutex_1.makeMutex)()
77
+ /** this mutex ensures that receipts are processed in order */
78
+ const receiptMutex = (0, make_mutex_1.makeMutex)()
79
+ /** this mutex ensures that app state patches are processed in order */
80
+ const appStatePatchMutex = (0, make_mutex_1.makeMutex)()
81
+ /** this mutex ensures that notifications are processed in order */
82
+ const notificationMutex = (0, make_mutex_1.makeMutex)()
83
+ // Timeout for AwaitingInitialSync state
84
+ let awaitingSyncTimeout
85
+ // In-memory history sync completion tracking (resets on reconnection)
86
+ const historySyncStatus = {
87
+ initialBootstrapComplete: false,
88
+ recentSyncComplete: false
89
+ }
90
+ let historySyncPausedTimeout
91
+ // Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
92
+ // When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
93
+ const blockedCollections = new Set()
94
+ const placeholderResendCache =
95
+ config.placeholderResendCache ||
96
+ new node_cache_1.default({
97
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
98
+ useClones: false
99
+ })
100
+ if (!config.placeholderResendCache) {
101
+ config.placeholderResendCache = placeholderResendCache
102
+ }
103
+ /** helper function to fetch the given app state sync key */
104
+ const getAppStateSyncKey = async keyId => {
105
+ const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId])
106
+ return key
107
+ }
108
+ const fetchPrivacySettings = async (force = false) => {
109
+ if (!privacySettings || force) {
110
+ const { content } = await query({
111
+ tag: 'iq',
112
+ attrs: {
113
+ xmlns: 'privacy',
114
+ to: WABinary_1.S_WHATSAPP_NET,
115
+ type: 'get'
116
+ },
117
+ content: [{ tag: 'privacy', attrs: {} }]
118
+ })
119
+ privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content?.[0], 'category')
120
+ }
121
+ return privacySettings
122
+ }
123
+ /** helper function to run a privacy IQ query */
124
+ const privacyQuery = async (name, value) => {
125
+ await query({
126
+ tag: 'iq',
127
+ attrs: {
128
+ xmlns: 'privacy',
129
+ to: WABinary_1.S_WHATSAPP_NET,
130
+ type: 'set'
131
+ },
132
+ content: [
133
+ {
134
+ tag: 'privacy',
135
+ attrs: {},
136
+ content: [
137
+ {
138
+ tag: 'category',
139
+ attrs: { name, value }
140
+ }
141
+ ]
142
+ }
143
+ ]
144
+ })
145
+ }
146
+ const updateMessagesPrivacy = async value => {
147
+ await privacyQuery('messages', value)
148
+ }
149
+ const updateCallPrivacy = async value => {
150
+ await privacyQuery('calladd', value)
151
+ }
152
+ const updateLastSeenPrivacy = async value => {
153
+ await privacyQuery('last', value)
154
+ }
155
+ const updateOnlinePrivacy = async value => {
156
+ await privacyQuery('online', value)
157
+ }
158
+ const updateProfilePicturePrivacy = async value => {
159
+ await privacyQuery('profile', value)
160
+ }
161
+ const updateStatusPrivacy = async value => {
162
+ await privacyQuery('status', value)
163
+ }
164
+ /**
165
+ * Fetch status privacy settings via `xmlns="status"` IQ GET.
166
+ * Returns the current distribution type and any custom lists.
167
+ * Source: GetStatusPrivacyJob.java, feature flag 3843 controls retry.
168
+ */
169
+ const getStatusPrivacy = async () => {
170
+ const result = await query({
171
+ tag: 'iq',
172
+ attrs: {
173
+ xmlns: 'status',
174
+ to: WABinary_1.S_WHATSAPP_NET,
175
+ type: 'get'
176
+ },
177
+ content: [{ tag: 'privacy', attrs: {} }]
178
+ })
179
+ const privacyNode = result?.content?.[0]
180
+ if (!privacyNode) return null
181
+ const lists = []
182
+ for (const listNode of (privacyNode.content || [])) {
183
+ const { type, id, listname, emoji, selected, deleted } = listNode.attrs || {}
184
+ const members = (listNode.content || []).map(u => u.attrs?.jid).filter(Boolean)
185
+ lists.push({ type, id, listname, emoji, selected: selected === 'true', deleted: deleted === 'true', members })
186
+ }
187
+ return lists
188
+ }
189
+ /**
190
+ * Set status privacy via `xmlns="status"` IQ SET.
191
+ * Supports simple distribution types and contact-level whitelist/blacklist/customlist.
192
+ *
193
+ * @param {'contacts'|'whitelist'|'blacklist'|'null'} type - Distribution type
194
+ * @param {string[]} [jids] - JIDs for the list (whitelist/blacklist)
195
+ * @param {Array<{id: string, listname: string, emoji?: string, selected?: boolean, deleted?: boolean, members?: string[]}>} [customLists]
196
+ */
197
+ const setStatusPrivacy = async (type, jids = [], customLists = []) => {
198
+ const content = []
199
+ // main distribution list
200
+ const mainList = {
201
+ tag: 'list',
202
+ attrs: { type },
203
+ content: jids.map(jid => ({ tag: 'user', attrs: { jid }, content: [] }))
204
+ }
205
+ content.push(mainList)
206
+ // custom named lists
207
+ for (const cl of customLists) {
208
+ const attrs = { type: 'customlist', id: cl.id, listname: cl.listname }
209
+ if (cl.emoji) attrs.emoji = cl.emoji
210
+ if (cl.selected) attrs.selected = 'true'
211
+ if (cl.deleted) attrs.deleted = 'true'
212
+ content.push({
213
+ tag: 'list',
214
+ attrs,
215
+ content: (cl.members || []).map(jid => ({ tag: 'user', attrs: { jid }, content: [] }))
216
+ })
217
+ }
218
+ await query({
219
+ tag: 'iq',
220
+ attrs: {
221
+ xmlns: 'status',
222
+ to: WABinary_1.S_WHATSAPP_NET,
223
+ type: 'set'
224
+ },
225
+ content: [{ tag: 'privacy', attrs: {}, content }]
226
+ })
227
+ }
228
+ const updateReadReceiptsPrivacy = async value => {
229
+ await privacyQuery('readreceipts', value)
230
+ }
231
+ const updateGroupsAddPrivacy = async value => {
232
+ await privacyQuery('groupadd', value)
233
+ }
234
+ const updateDefaultDisappearingMode = async duration => {
235
+ await query({
236
+ tag: 'iq',
237
+ attrs: {
238
+ xmlns: 'disappearing_mode',
239
+ to: WABinary_1.S_WHATSAPP_NET,
240
+ type: 'set'
241
+ },
242
+ content: [
243
+ {
244
+ tag: 'disappearing_mode',
245
+ attrs: {
246
+ duration: duration.toString()
247
+ }
248
+ }
249
+ ]
250
+ })
251
+ }
252
+ /**
253
+ * Fetch broadcast list quota from the server.
254
+ * Source: BroadcastListQuotaProtocol.java — IQ xmlns="w:biz", 32s timeout.
255
+ *
256
+ * Returns: { messagesLeft, totalLimit, isHeavySender, startTs, endTs, resetTs }
257
+ */
258
+ const fetchBroadcastListQuota = async () => {
259
+ const result = await query(
260
+ {
261
+ tag: 'iq',
262
+ attrs: {
263
+ xmlns: 'w:biz',
264
+ to: WABinary_1.S_WHATSAPP_NET,
265
+ type: 'get'
266
+ },
267
+ content: [{ tag: 'broadcast_list_quota', attrs: {}, content: [] }]
268
+ },
269
+ 32000
270
+ )
271
+ const limitsNode = (0, WABinary_1.getBinaryNodeChild)(result, 'limits')
272
+ const timeframeNode = (0, WABinary_1.getBinaryNodeChild)(result, 'timeframe')
273
+ if (!limitsNode) return null
274
+ return {
275
+ messagesLeft: parseInt(limitsNode.attrs?.messages_left ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'messages_left')?.content ?? '0'),
276
+ totalLimit: parseInt(limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0'),
277
+ isHeavySender: (limitsNode.attrs?.is_heavy_sender ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
278
+ startTs: parseInt(timeframeNode?.attrs?.start_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ?? '0'),
279
+ endTs: parseInt(timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0'),
280
+ resetTs: parseInt(timeframeNode?.attrs?.reset_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ?? '0')
281
+ }
282
+ }
283
+
284
+ const getBotListV2 = async () => {
285
+ const resp = await query({
286
+ tag: 'iq',
287
+ attrs: {
288
+ xmlns: 'bot',
289
+ to: WABinary_1.S_WHATSAPP_NET,
290
+ type: 'get'
291
+ },
292
+ content: [
293
+ {
294
+ tag: 'bot',
295
+ attrs: {
296
+ v: '2'
297
+ }
298
+ }
299
+ ]
300
+ })
301
+ const botNode = (0, WABinary_1.getBinaryNodeChild)(resp, 'bot')
302
+ const botList = []
303
+ for (const section of (0, WABinary_1.getBinaryNodeChildren)(botNode, 'section')) {
304
+ if (section.attrs.type === 'all') {
305
+ for (const bot of (0, WABinary_1.getBinaryNodeChildren)(section, 'bot')) {
306
+ botList.push({
307
+ jid: bot.attrs.jid,
308
+ personaId: bot.attrs['persona_id']
309
+ })
310
+ }
311
+ }
312
+ }
313
+ return botList
314
+ }
315
+ /**
316
+ * Get the global chat-blocking status (block messages from unknown accounts).
317
+ * Ported from WhatsApp Web's WASmaxPsaChatBlockGetRPC (xmlns `w:comms:chat`).
318
+ * @returns {Promise<'blocked' | 'unblocked' | undefined>}
319
+ */
320
+ const getChatBlockingStatus = async () => {
321
+ const result = await query({
322
+ tag: 'iq',
323
+ attrs: {
324
+ to: WABinary_1.S_WHATSAPP_NET,
325
+ xmlns: 'w:comms:chat',
326
+ type: 'get'
327
+ },
328
+ content: [{ tag: 'query', attrs: {}, content: [{ tag: 'blocking_status', attrs: {} }] }]
329
+ })
330
+ const blocking =
331
+ (0, WABinary_1.getBinaryNodeChild)(result, 'blocking') ||
332
+ (0, WABinary_1.getBinaryNodeChild)((0, WABinary_1.getBinaryNodeChild)(result, 'query'), 'blocking')
333
+ return blocking?.attrs?.status
334
+ }
335
+ /**
336
+ * Set the global chat-blocking status (block messages from unknown accounts).
337
+ * Ported from WhatsApp Web's WASmaxPsaChatBlockSetRPC (xmlns `w:comms:chat`).
338
+ * @param {'block' | 'unblock'} action
339
+ * @returns {Promise<'blocked' | 'unblocked' | undefined>} the resulting status
340
+ */
341
+ const updateChatBlockingStatus = async action => {
342
+ const result = await query({
343
+ tag: 'iq',
344
+ attrs: {
345
+ to: WABinary_1.S_WHATSAPP_NET,
346
+ xmlns: 'w:comms:chat',
347
+ type: 'set'
348
+ },
349
+ content: [{ tag: 'blocking', attrs: { action } }]
350
+ })
351
+ const blocking = (0, WABinary_1.getBinaryNodeChild)(result, 'blocking')
352
+ return blocking?.attrs?.status
353
+ }
354
+ /**
355
+ * Get the user's pending TOS disclosures / notices.
356
+ * Ported from WhatsApp Web's WASmaxUserNoticeGetDisclosuresRPC (xmlns `tos`).
357
+ * @param {number} [t] last-seen disclosure timestamp
358
+ * @returns {Promise<Array<Record<string, string>>>} the `<notice>` attributes
359
+ */
360
+ const getUserDisclosures = async (t = 0) => {
361
+ const result = await query({
362
+ tag: 'iq',
363
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'tos', type: 'get' },
364
+ content: [{ tag: 'get_user_disclosures', attrs: { t: String(t) } }]
365
+ })
366
+ return (0, WABinary_1.getBinaryNodeChildren)(result, 'notice').map(n => ({ ...n.attrs }))
367
+ }
368
+ /**
369
+ * Get the account opt-out list (`optoutlist` IQ). Returns the raw result node.
370
+ */
371
+ const getOptOutList = async () => {
372
+ return query({
373
+ tag: 'iq',
374
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'optoutlist', type: 'get' }
375
+ })
376
+ }
377
+ /**
378
+ * Get push-notification settings (`urn:xmpp:whatsapp:push`).
379
+ */
380
+ const getPushConfig = async () => {
381
+ const result = await query({
382
+ tag: 'iq',
383
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'urn:xmpp:whatsapp:push', type: 'get' },
384
+ content: [{ tag: 'settings', attrs: {} }]
385
+ })
386
+ return (0, WABinary_1.getBinaryNodeChild)(result, 'settings')
387
+ }
388
+ /**
389
+ * Set push-notification config (`urn:xmpp:whatsapp:push`). Mainly web push —
390
+ * pass the FCM-style config (platform / endpoint / auth / p256dh).
391
+ * @param {Record<string, string>} config
392
+ */
393
+ const setPushConfig = async config => {
394
+ await query({
395
+ tag: 'iq',
396
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'urn:xmpp:whatsapp:push', type: 'set' },
397
+ content: [{ tag: 'config', attrs: config }]
398
+ })
399
+ }
400
+ const fetchStatus = async (...jids) => {
401
+ const usyncQuery = new WAUSync_1.USyncQuery().withStatusProtocol()
402
+ for (const jid of jids) {
403
+ usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
404
+ }
405
+ const result = await sock.executeUSyncQuery(usyncQuery)
406
+ if (result) {
407
+ return result.list
408
+ }
409
+ }
410
+ const fetchDisappearingDuration = async (...jids) => {
411
+ const usyncQuery = new WAUSync_1.USyncQuery().withDisappearingModeProtocol()
412
+ for (const jid of jids) {
413
+ usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
414
+ }
415
+ const result = await sock.executeUSyncQuery(usyncQuery)
416
+ if (result) {
417
+ return result.list
418
+ }
419
+ }
420
+ /** update the profile picture for yourself or a group */
421
+ const updateProfilePicture = async (jid, content, dimensions) => {
422
+ let targetJid
423
+ if (!jid) {
424
+ throw new boom_1.Boom(
425
+ 'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update'
426
+ )
427
+ }
428
+ if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
429
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
430
+ } else {
431
+ targetJid = undefined
432
+ }
433
+ const { img } = await (0, Utils_1.generateProfilePicture)(content, dimensions)
434
+ await query({
435
+ tag: 'iq',
436
+ attrs: {
437
+ to: WABinary_1.S_WHATSAPP_NET,
438
+ type: 'set',
439
+ xmlns: 'w:profile:picture',
440
+ ...(targetJid ? { target: targetJid } : {})
441
+ },
442
+ content: [
443
+ {
444
+ tag: 'picture',
445
+ attrs: { type: 'image' },
446
+ content: img
447
+ }
448
+ ]
449
+ })
450
+ }
451
+ /** remove the profile picture for yourself or a group */
452
+ const removeProfilePicture = async jid => {
453
+ let targetJid
454
+ if (!jid) {
455
+ throw new boom_1.Boom(
456
+ 'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update'
457
+ )
458
+ }
459
+ if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
460
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
461
+ } else {
462
+ targetJid = undefined
463
+ }
464
+ await query({
465
+ tag: 'iq',
466
+ attrs: {
467
+ to: WABinary_1.S_WHATSAPP_NET,
468
+ type: 'set',
469
+ xmlns: 'w:profile:picture',
470
+ ...(targetJid ? { target: targetJid } : {})
471
+ }
472
+ })
473
+ }
474
+ /** update the profile status for yourself */
475
+ const updateProfileStatus = async status => {
476
+ await query({
477
+ tag: 'iq',
478
+ attrs: {
479
+ to: WABinary_1.S_WHATSAPP_NET,
480
+ type: 'set',
481
+ xmlns: 'status'
482
+ },
483
+ content: [
484
+ {
485
+ tag: 'status',
486
+ attrs: {},
487
+ content: Buffer.from(status, 'utf-8')
488
+ }
489
+ ]
490
+ })
491
+ }
492
+ const updateProfileName = async name => {
493
+ await chatModify({ pushNameSetting: name }, '')
494
+ }
495
+ const fetchBlocklist = async () => {
496
+ const result = await query({
497
+ tag: 'iq',
498
+ attrs: {
499
+ xmlns: 'blocklist',
500
+ to: WABinary_1.S_WHATSAPP_NET,
501
+ type: 'get'
502
+ }
503
+ })
504
+ const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list')
505
+ return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
506
+ }
507
+ const updateBlockStatus = async (jid, action) => {
508
+ await query({
509
+ tag: 'iq',
510
+ attrs: {
511
+ xmlns: 'blocklist',
512
+ to: WABinary_1.S_WHATSAPP_NET,
513
+ type: 'set'
514
+ },
515
+ content: [
516
+ {
517
+ tag: 'item',
518
+ attrs: {
519
+ action,
520
+ jid
521
+ }
522
+ }
523
+ ]
524
+ })
525
+ }
526
+ const getBusinessProfile = async jid => {
527
+ const results = await query({
528
+ tag: 'iq',
529
+ attrs: {
530
+ to: 's.whatsapp.net',
531
+ xmlns: 'w:biz',
532
+ type: 'get'
533
+ },
534
+ content: [
535
+ {
536
+ tag: 'business_profile',
537
+ attrs: { v: '244' },
538
+ content: [
539
+ {
540
+ tag: 'profile',
541
+ attrs: { jid }
542
+ }
543
+ ]
544
+ }
545
+ ]
546
+ })
547
+ const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile')
548
+ const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile')
549
+ if (profiles) {
550
+ const address = (0, WABinary_1.getBinaryNodeChild)(profiles, 'address')
551
+ const description = (0, WABinary_1.getBinaryNodeChild)(profiles, 'description')
552
+ const website = (0, WABinary_1.getBinaryNodeChild)(profiles, 'website')
553
+ const email = (0, WABinary_1.getBinaryNodeChild)(profiles, 'email')
554
+ const category = (0, WABinary_1.getBinaryNodeChild)(
555
+ (0, WABinary_1.getBinaryNodeChild)(profiles, 'categories'),
556
+ 'category'
557
+ )
558
+ const businessHours = (0, WABinary_1.getBinaryNodeChild)(profiles, 'business_hours')
559
+ const businessHoursConfig = businessHours
560
+ ? (0, WABinary_1.getBinaryNodeChildren)(businessHours, 'business_hours_config')
561
+ : undefined
562
+ const websiteStr = website?.content?.toString()
563
+ return {
564
+ wid: profiles.attrs?.jid,
565
+ address: address?.content?.toString(),
566
+ description: description?.content?.toString() || '',
567
+ website: websiteStr ? [websiteStr] : [],
568
+ email: email?.content?.toString(),
569
+ category: category?.content?.toString(),
570
+ business_hours: {
571
+ timezone: businessHours?.attrs?.timezone,
572
+ business_config: businessHoursConfig?.map(({ attrs }) => attrs)
573
+ }
574
+ }
575
+ }
576
+ }
577
+ const cleanDirtyBits = async (type, fromTimestamp) => {
578
+ logger.info({ fromTimestamp }, 'clean dirty bits ' + type)
579
+ await sendNode({
580
+ tag: 'iq',
581
+ attrs: {
582
+ to: WABinary_1.S_WHATSAPP_NET,
583
+ type: 'set',
584
+ xmlns: 'urn:xmpp:whatsapp:dirty',
585
+ id: generateMessageTag()
586
+ },
587
+ content: [
588
+ {
589
+ tag: 'clean',
590
+ attrs: {
591
+ type,
592
+ ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null)
593
+ }
594
+ }
595
+ ]
596
+ })
597
+ }
598
+ const newAppStateChunkHandler = isInitialSync => {
599
+ return {
600
+ onMutation(mutation) {
601
+ ;(0, Utils_1.processSyncAction)(
602
+ mutation,
603
+ ev,
604
+ authState.creds.me,
605
+ isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
606
+ logger
607
+ )
608
+ }
609
+ }
610
+ }
611
+ const resyncAppState = ev.createBufferedFunction(async (collections, isInitialSync) => {
612
+ const appStateSyncKeyCache = new Map()
613
+ const getCachedAppStateSyncKey = async keyId => {
614
+ if (appStateSyncKeyCache.has(keyId)) {
615
+ return appStateSyncKeyCache.get(keyId) ?? undefined
616
+ }
617
+ const key = await getAppStateSyncKey(keyId)
618
+ appStateSyncKeyCache.set(keyId, key ?? null)
619
+ return key
620
+ }
621
+ // we use this to determine which events to fire
622
+ // otherwise when we resync from scratch -- all notifications will fire
623
+ const initialVersionMap = {}
624
+ const globalMutationMap = {}
625
+ await authState.keys.transaction(async () => {
626
+ const collectionsToHandle = new Set(collections)
627
+ // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
628
+ const attemptsMap = {}
629
+ const forceSnapshotCollections = new Set()
630
+ // keep executing till all collections are done
631
+ // sometimes a single patch request will not return all the patches (God knows why)
632
+ // so we fetch till they're all done (this is determined by the "has_more_patches" flag)
633
+ while (collectionsToHandle.size) {
634
+ const states = {}
635
+ const nodes = []
636
+ for (const name of collectionsToHandle) {
637
+ const result = await authState.keys.get('app-state-sync-version', [name])
638
+ let state = result[name]
639
+ if (state) {
640
+ if (typeof initialVersionMap[name] === 'undefined') {
641
+ initialVersionMap[name] = state.version
642
+ }
643
+ } else {
644
+ state = (0, Utils_1.newLTHashState)()
645
+ }
646
+ states[name] = state
647
+ const shouldForceSnapshot = forceSnapshotCollections.has(name)
648
+ if (shouldForceSnapshot) {
649
+ forceSnapshotCollections.delete(name)
650
+ }
651
+ logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`)
652
+ nodes.push({
653
+ tag: 'collection',
654
+ attrs: {
655
+ name,
656
+ version: state.version.toString(),
657
+ // return snapshot if syncing from scratch or forcing after a failed attempt
658
+ return_snapshot: (shouldForceSnapshot || !state.version).toString()
659
+ }
660
+ })
661
+ }
662
+ const result = await query({
663
+ tag: 'iq',
664
+ attrs: {
665
+ to: WABinary_1.S_WHATSAPP_NET,
666
+ xmlns: 'w:sync:app:state',
667
+ type: 'set'
668
+ },
669
+ content: [
670
+ {
671
+ tag: 'sync',
672
+ attrs: {},
673
+ content: nodes
674
+ }
675
+ ]
676
+ })
677
+ // extract from binary node
678
+ const decoded = await (0, Utils_1.extractSyncdPatches)(result, config?.options)
679
+ for (const key in decoded) {
680
+ const name = key
681
+ const { patches, hasMorePatches, snapshot } = decoded[name]
682
+ try {
683
+ if (snapshot) {
684
+ const { state: newState, mutationMap } = await (0, Utils_1.decodeSyncdSnapshot)(
685
+ name,
686
+ snapshot,
687
+ getCachedAppStateSyncKey,
688
+ initialVersionMap[name],
689
+ appStateMacVerification.snapshot
690
+ )
691
+ states[name] = newState
692
+ Object.assign(globalMutationMap, mutationMap)
693
+ logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`)
694
+ await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
695
+ }
696
+ // only process if there are syncd patches
697
+ if (patches.length) {
698
+ const { state: newState, mutationMap } = await (0, Utils_1.decodePatches)(
699
+ name,
700
+ patches,
701
+ states[name],
702
+ getCachedAppStateSyncKey,
703
+ config.options,
704
+ initialVersionMap[name],
705
+ logger,
706
+ appStateMacVerification.patch
707
+ )
708
+ await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
709
+ logger.info(`synced ${name} to v${newState.version}`)
710
+ initialVersionMap[name] = newState.version
711
+ Object.assign(globalMutationMap, mutationMap)
712
+ }
713
+ if (hasMorePatches) {
714
+ logger.info(`${name} has more patches...`)
715
+ } else {
716
+ // collection is done with sync
717
+ collectionsToHandle.delete(name)
718
+ }
719
+ } catch (error) {
720
+ attemptsMap[name] = (attemptsMap[name] || 0) + 1
721
+ const logData = {
722
+ name,
723
+ attempt: attemptsMap[name],
724
+ version: states[name].version,
725
+ statusCode: error.output?.statusCode,
726
+ errorType: error.name,
727
+ error: error.stack
728
+ }
729
+ if ((0, Utils_1.isMissingKeyError)(error) && attemptsMap[name] >= Utils_1.MAX_SYNC_ATTEMPTS) {
730
+ logger.warn(
731
+ logData,
732
+ `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`
733
+ )
734
+ blockedCollections.add(name)
735
+ collectionsToHandle.delete(name)
736
+ } else if ((0, Utils_1.isMissingKeyError)(error)) {
737
+ logger.info(
738
+ logData,
739
+ `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`
740
+ )
741
+ forceSnapshotCollections.add(name)
742
+ } else if ((0, Utils_1.isAppStateSyncIrrecoverable)(error, attemptsMap[name])) {
743
+ logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`)
744
+ collectionsToHandle.delete(name)
745
+ } else {
746
+ logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`)
747
+ forceSnapshotCollections.add(name)
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }, authState?.creds?.me?.id || 'resync-app-state')
753
+ const { onMutation } = newAppStateChunkHandler(isInitialSync)
754
+ for (const key in globalMutationMap) {
755
+ onMutation(globalMutationMap[key])
756
+ }
757
+ })
758
+ /**
759
+ * fetch the profile picture of a user/group
760
+ * type = "preview" for a low res picture
761
+ * type = "image" for the high res picture URL (adds query="url")
762
+ * type = "avatar" for the avatar variant (no query attr, confirmed from interop logs)
763
+ */
764
+ const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
765
+ const picAttrs = type === 'image' ? { type, query: 'url' } : { type }
766
+ const baseContent = [{ tag: 'picture', attrs: picAttrs }]
767
+ // WA Web only includes tctoken for user JIDs (not groups/newsletters)
768
+ // and never for own profile pic (Chat model for self has no tcToken).
769
+ // Including tctoken for own JID causes the server to never respond.
770
+ const normalizedJid = (0, WABinary_1.jidNormalizedUser)(jid)
771
+ const isUserJid = (0, WABinary_1.isPnUser)(normalizedJid) || (0, WABinary_1.isLidUser)(normalizedJid)
772
+ const me = authState.creds.me
773
+ const isSelf =
774
+ me &&
775
+ (normalizedJid === (0, WABinary_1.jidNormalizedUser)(me.id) ||
776
+ (me.lid && normalizedJid === (0, WABinary_1.jidNormalizedUser)(me.lid)))
777
+ let content = baseContent
778
+ if (serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
779
+ content = await (0, tc_token_utils_1.buildTcTokenFromJid)({
780
+ authState,
781
+ jid: normalizedJid,
782
+ baseContent,
783
+ getLIDForPN
784
+ })
785
+ }
786
+ jid = (0, WABinary_1.jidNormalizedUser)(jid)
787
+ const result = await query(
788
+ {
789
+ tag: 'iq',
790
+ attrs: {
791
+ target: jid,
792
+ to: WABinary_1.S_WHATSAPP_NET,
793
+ type: 'get',
794
+ xmlns: 'w:profile:picture'
795
+ },
796
+ content
797
+ },
798
+ timeoutMs
799
+ )
800
+ const child = (0, WABinary_1.getBinaryNodeChild)(result, 'picture')
801
+ return child?.attrs?.url
802
+ }
803
+ const createCallLink = async (type, event, timeoutMs) => {
804
+ const result = await query(
805
+ {
806
+ tag: 'call',
807
+ attrs: {
808
+ id: generateMessageTag(),
809
+ to: '@call'
810
+ },
811
+ content: [
812
+ {
813
+ tag: 'link_create',
814
+ attrs: { media: type },
815
+ content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined
816
+ }
817
+ ]
818
+ },
819
+ timeoutMs
820
+ )
821
+ const child = (0, WABinary_1.getBinaryNodeChild)(result, 'link_create')
822
+ return child?.attrs?.token
823
+ }
824
+ /**
825
+ * Toggle the waiting room on an existing call link.
826
+ * Ported from WhatsApp Web's WASmaxVoipWaitingRoomToggleCallLinkRPC.
827
+ * @param {string} linkToken the call link token (from createCallLink)
828
+ * @param {boolean} enabled
829
+ * @param {'audio' | 'video'} [media]
830
+ */
831
+ const toggleCallLinkWaitingRoom = async (linkToken, enabled, media = 'audio') => {
832
+ const result = await query({
833
+ tag: 'call',
834
+ attrs: { id: generateMessageTag(), to: '@call' },
835
+ content: [
836
+ {
837
+ tag: 'waiting_room_toggle',
838
+ attrs: { enabled: enabled ? '1' : '0', 'link-token': linkToken, media }
839
+ }
840
+ ]
841
+ })
842
+ const child = (0, WABinary_1.getBinaryNodeChild)(result, 'waiting_room_toggle')
843
+ return child?.attrs
844
+ }
845
+ const sendPresenceUpdate = async (type, toJid) => {
846
+ const me = authState.creds.me
847
+ const isAvailableType = type === 'available'
848
+ if (isAvailableType || type === 'unavailable') {
849
+ if (!me.name) {
850
+ logger.warn('no name present, ignoring presence update request...')
851
+ return
852
+ }
853
+ ev.emit('connection.update', { isOnline: isAvailableType })
854
+ if (isAvailableType) {
855
+ void sendUnifiedSession()
856
+ }
857
+ await sendNode({
858
+ tag: 'presence',
859
+ attrs: {
860
+ name: me.name.replace(/@/g, ''),
861
+ type
862
+ }
863
+ })
864
+ } else {
865
+ const { server } = (0, WABinary_1.jidDecode)(toJid)
866
+ const isLid = server === 'lid'
867
+ await sendNode({
868
+ tag: 'chatstate',
869
+ attrs: {
870
+ from: isLid ? me.lid : me.id,
871
+ to: toJid
872
+ },
873
+ content: [
874
+ {
875
+ tag: type === 'recording' ? 'composing' : type,
876
+ attrs: type === 'recording' ? { media: 'audio' } : {}
877
+ }
878
+ ]
879
+ })
880
+ }
881
+ }
882
+ /**
883
+ * @param toJid the jid to subscribe to
884
+ * @param tcToken token for subscription, use if present
885
+ */
886
+ const presenceSubscribe = async toJid => {
887
+ // Only include tctoken for user JIDs — groups/newsletters don't use tctokens
888
+ const normalizedToJid = (0, WABinary_1.jidNormalizedUser)(toJid)
889
+ const isUserJid = (0, WABinary_1.isPnUser)(normalizedToJid) || (0, WABinary_1.isLidUser)(normalizedToJid)
890
+ const tcTokenContent = isUserJid
891
+ ? await (0, tc_token_utils_1.buildTcTokenFromJid)({ authState, jid: normalizedToJid, getLIDForPN })
892
+ : undefined
893
+ return sendNode({
894
+ tag: 'presence',
895
+ attrs: {
896
+ to: toJid,
897
+ id: generateMessageTag(),
898
+ type: 'subscribe'
899
+ },
900
+ content: tcTokenContent
901
+ })
902
+ }
903
+ const handlePresenceUpdate = ({ tag, attrs, content }) => {
904
+ let presence
905
+ const jid = attrs.from
906
+ const participant = attrs.participant || attrs.from
907
+ if (shouldIgnoreJid(jid) && jid !== WABinary_1.S_WHATSAPP_NET) {
908
+ return
909
+ }
910
+ if (tag === 'presence') {
911
+ presence = {
912
+ lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
913
+ lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
914
+ }
915
+ } else if (Array.isArray(content)) {
916
+ const [firstChild] = content
917
+ let type = firstChild.tag
918
+ if (type === 'paused') {
919
+ type = 'available'
920
+ }
921
+ if (firstChild.attrs?.media === 'audio') {
922
+ type = 'recording'
923
+ }
924
+ presence = { lastKnownPresence: type }
925
+ } else {
926
+ logger.error({ tag, attrs, content }, 'recv invalid presence node')
927
+ }
928
+ if (presence) {
929
+ ev.emit('presence.update', { id: jid, presences: { [participant]: presence } })
930
+ }
931
+ }
932
+ const appPatch = async patchCreate => {
933
+ const name = patchCreate.type
934
+ const myAppStateKeyId = authState.creds.myAppStateKeyId
935
+ if (!myAppStateKeyId) {
936
+ throw new boom_1.Boom('App state key not present!', { statusCode: 400 })
937
+ }
938
+ let initial
939
+ let encodeResult
940
+ await appStatePatchMutex.mutex(async () => {
941
+ await authState.keys.transaction(async () => {
942
+ logger.debug({ patch: patchCreate }, 'applying app patch')
943
+ await resyncAppState([name], false)
944
+ const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name])
945
+ initial = currentSyncVersion || (0, Utils_1.newLTHashState)()
946
+ encodeResult = await (0, Utils_1.encodeSyncdPatch)(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey)
947
+ const { patch, state } = encodeResult
948
+ const node = {
949
+ tag: 'iq',
950
+ attrs: {
951
+ to: WABinary_1.S_WHATSAPP_NET,
952
+ type: 'set',
953
+ xmlns: 'w:sync:app:state'
954
+ },
955
+ content: [
956
+ {
957
+ tag: 'sync',
958
+ attrs: {},
959
+ content: [
960
+ {
961
+ tag: 'collection',
962
+ attrs: {
963
+ name,
964
+ version: (state.version - 1).toString(),
965
+ return_snapshot: 'false'
966
+ },
967
+ content: [
968
+ {
969
+ tag: 'patch',
970
+ attrs: {},
971
+ content: index_js_1.proto.SyncdPatch.encode(patch).finish()
972
+ }
973
+ ]
974
+ }
975
+ ]
976
+ }
977
+ ]
978
+ }
979
+ await query(node)
980
+ await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
981
+ }, authState?.creds?.me?.id || 'app-patch')
982
+ })
983
+ if (config.emitOwnEvents) {
984
+ const { onMutation } = newAppStateChunkHandler(false)
985
+ const { mutationMap } = await (0, Utils_1.decodePatches)(
986
+ name,
987
+ [{ ...encodeResult.patch, version: { version: encodeResult.state.version } }],
988
+ initial,
989
+ getAppStateSyncKey,
990
+ config.options,
991
+ undefined,
992
+ logger
993
+ )
994
+ for (const key in mutationMap) {
995
+ onMutation(mutationMap[key])
996
+ }
997
+ }
998
+ }
999
+ /** sending non-abt props may fix QR scan fail if server expects */
1000
+ const fetchProps = async () => {
1001
+ //TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
1002
+ const resultNode = await query({
1003
+ tag: 'iq',
1004
+ attrs: {
1005
+ to: WABinary_1.S_WHATSAPP_NET,
1006
+ xmlns: 'w',
1007
+ type: 'get'
1008
+ },
1009
+ content: [
1010
+ {
1011
+ tag: 'props',
1012
+ attrs: {
1013
+ protocol: '2',
1014
+ hash: authState?.creds?.lastPropHash || ''
1015
+ }
1016
+ }
1017
+ ]
1018
+ })
1019
+ const propsNode = (0, WABinary_1.getBinaryNodeChild)(resultNode, 'props')
1020
+ let props = {}
1021
+ if (propsNode) {
1022
+ if (propsNode.attrs?.hash) {
1023
+ // on some clients, the hash is returning as undefined
1024
+ authState.creds.lastPropHash = propsNode?.attrs?.hash
1025
+ ev.emit('creds.update', authState.creds)
1026
+ }
1027
+ props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1028
+ }
1029
+ // Extract protocol-relevant AB props (only the ones we need)
1030
+ const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages']
1031
+ if (privacyTokenProp !== undefined) {
1032
+ serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1'
1033
+ }
1034
+ const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq']
1035
+ if (profilePicProp !== undefined) {
1036
+ serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1'
1037
+ }
1038
+ const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid']
1039
+ if (lidIssueProp !== undefined) {
1040
+ serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1'
1041
+ }
1042
+ logger.debug({ serverProps }, 'fetched props')
1043
+ return props
1044
+ }
1045
+ /**
1046
+ * modify a chat -- mark unread, read etc.
1047
+ * lastMessages must be sorted in reverse chronologically
1048
+ * requires the last messages till the last message received; required for archive & unread
1049
+ */
1050
+ const chatModify = (mod, jid) => {
1051
+ const patch = (0, Utils_1.chatModificationToAppPatch)(mod, jid)
1052
+ return appPatch(patch)
1053
+ }
1054
+ /**
1055
+ * Enable/Disable link preview privacy, not related to baileys link preview generation
1056
+ */
1057
+ const updateDisableLinkPreviewsPrivacy = isPreviewsDisabled => {
1058
+ return chatModify(
1059
+ {
1060
+ disableLinkPreviews: { isPreviewsDisabled }
1061
+ },
1062
+ ''
1063
+ )
1064
+ }
1065
+ /**
1066
+ * Star or Unstar a message
1067
+ */
1068
+ const star = (jid, messages, star) => {
1069
+ return chatModify(
1070
+ {
1071
+ star: {
1072
+ messages,
1073
+ star
1074
+ }
1075
+ },
1076
+ jid
1077
+ )
1078
+ }
1079
+ /**
1080
+ * Add or Edit Contact
1081
+ */
1082
+ const addOrEditContact = (jid, contact) => {
1083
+ return chatModify(
1084
+ {
1085
+ contact
1086
+ },
1087
+ jid
1088
+ )
1089
+ }
1090
+ /**
1091
+ * Remove Contact
1092
+ */
1093
+ const removeContact = jid => {
1094
+ return chatModify(
1095
+ {
1096
+ contact: null
1097
+ },
1098
+ jid
1099
+ )
1100
+ }
1101
+ /**
1102
+ * Adds label
1103
+ */
1104
+ const addLabel = (jid, labels) => {
1105
+ return chatModify(
1106
+ {
1107
+ addLabel: {
1108
+ ...labels
1109
+ }
1110
+ },
1111
+ jid
1112
+ )
1113
+ }
1114
+ /**
1115
+ * Adds label for the chats
1116
+ */
1117
+ const addChatLabel = (jid, labelId) => {
1118
+ return chatModify(
1119
+ {
1120
+ addChatLabel: {
1121
+ labelId
1122
+ }
1123
+ },
1124
+ jid
1125
+ )
1126
+ }
1127
+ /**
1128
+ * Removes label for the chat
1129
+ */
1130
+ const removeChatLabel = (jid, labelId) => {
1131
+ return chatModify(
1132
+ {
1133
+ removeChatLabel: {
1134
+ labelId
1135
+ }
1136
+ },
1137
+ jid
1138
+ )
1139
+ }
1140
+ /**
1141
+ * Adds label for the message
1142
+ */
1143
+ const addMessageLabel = (jid, messageId, labelId) => {
1144
+ return chatModify(
1145
+ {
1146
+ addMessageLabel: {
1147
+ messageId,
1148
+ labelId
1149
+ }
1150
+ },
1151
+ jid
1152
+ )
1153
+ }
1154
+ /**
1155
+ * Removes label for the message
1156
+ */
1157
+ const removeMessageLabel = (jid, messageId, labelId) => {
1158
+ return chatModify(
1159
+ {
1160
+ removeMessageLabel: {
1161
+ messageId,
1162
+ labelId
1163
+ }
1164
+ },
1165
+ jid
1166
+ )
1167
+ }
1168
+ /**
1169
+ * Add or Edit Quick Reply
1170
+ */
1171
+ const addOrEditQuickReply = quickReply => {
1172
+ return chatModify(
1173
+ {
1174
+ quickReply
1175
+ },
1176
+ ''
1177
+ )
1178
+ }
1179
+ /**
1180
+ * Remove Quick Reply
1181
+ */
1182
+ const removeQuickReply = timestamp => {
1183
+ return chatModify(
1184
+ {
1185
+ quickReply: { timestamp, deleted: true }
1186
+ },
1187
+ ''
1188
+ )
1189
+ }
1190
+ /**
1191
+ * Rename an AI chat thread
1192
+ */
1193
+ const renameAIThread = (jid, title) => {
1194
+ return chatModify({ aiThreadRename: { title } }, jid)
1195
+ }
1196
+ /**
1197
+ * Pin or unpin a message in a thread/AI chat
1198
+ */
1199
+ const pinThreadMessage = (jid, messageId, pinned = true) => {
1200
+ return chatModify({ threadPin: { pinned, messageId } }, jid)
1201
+ }
1202
+ /**
1203
+ * Toggle AI private processing (end-to-end encrypted AI processing)
1204
+ */
1205
+ const updatePrivateProcessingSetting = enabled => {
1206
+ return chatModify({ privateProcessingSetting: enabled ? 'enabled' : 'disabled' }, '')
1207
+ }
1208
+ /**
1209
+ * Update bio/about privacy (who can see your About text)
1210
+ * @param value 'all' | 'contacts' | 'contact_blacklist' | 'none'
1211
+ */
1212
+ const updateBioPrivacy = async value => {
1213
+ await privacyQuery('about', value)
1214
+ }
1215
+ /**
1216
+ * Block a bot JID
1217
+ */
1218
+ const blockBot = async botJid => {
1219
+ await query({
1220
+ tag: 'iq',
1221
+ attrs: {
1222
+ xmlns: 'disappearing_mode',
1223
+ to: WABinary_1.S_WHATSAPP_NET,
1224
+ type: 'set'
1225
+ },
1226
+ content: [
1227
+ {
1228
+ tag: 'block',
1229
+ attrs: { jid: botJid }
1230
+ }
1231
+ ]
1232
+ })
1233
+ }
1234
+ /**
1235
+ * Unblock a bot JID
1236
+ */
1237
+ const unblockBot = async botJid => {
1238
+ await updateBlockStatus(botJid, 'unblock')
1239
+ }
1240
+ /**
1241
+ * Mute or unmute a contact's status updates
1242
+ */
1243
+ const muteContactStatus = (jid, muted = true) => {
1244
+ return chatModify({ muteStatus: { muted } }, jid)
1245
+ }
1246
+ /**
1247
+ * Add or remove a contact from favorites
1248
+ */
1249
+ const toggleFavorite = (jid, isFavorite = true) => {
1250
+ return chatModify({ favorite: { isFavorite } }, jid)
1251
+ }
1252
+ /**
1253
+ * Reorder labels
1254
+ * @param sortedLabelIds ordered array of label IDs
1255
+ */
1256
+ const reorderLabels = sortedLabelIds => {
1257
+ return chatModify({ reorderLabel: { sortedLabelIds } }, '')
1258
+ }
1259
+ /**
1260
+ * Delete an individual call log entry
1261
+ */
1262
+ const deleteCallLog = (callId, jid = '') => {
1263
+ return chatModify({ deleteCallLog: { callId } }, jid)
1264
+ }
1265
+ /**
1266
+ * Create or update a note/draft for a chat
1267
+ */
1268
+ const setChatNote = (jid, note) => {
1269
+ return chatModify({ noteEdit: { note } }, jid)
1270
+ }
1271
+ /**
1272
+ * Delete the note/draft for a chat
1273
+ */
1274
+ const deleteChatNote = jid => {
1275
+ return chatModify({ noteEdit: { note: '', deleted: true } }, jid)
1276
+ }
1277
+ /**
1278
+ * Explicitly mark a chat as unread (shows unread dot even if read)
1279
+ */
1280
+ const markChatAsUnread = (jid, lastMessages) => {
1281
+ return chatModify({ markAsUnread: true, lastMessages }, jid)
1282
+ }
1283
+ /**
1284
+ * Set per-chat ephemeral message duration
1285
+ * @param duration seconds (0 = off, 86400 = 1d, 604800 = 7d, 7776000 = 90d)
1286
+ */
1287
+ const setChatEphemeral = (jid, duration) => {
1288
+ return chatModify({ setChatEphemeral: duration }, jid)
1289
+ }
1290
+ /**
1291
+ * Silence a chat (mute without timestamp = permanent, or provide until timestamp)
1292
+ */
1293
+ const silenceChat = (jid, silent = true, until = null) => {
1294
+ return chatModify({ silenceChat: { silent, until } }, jid)
1295
+ }
1296
+ /**
1297
+ * Fetch bot profiles for a list of JIDs using USyncBotProfileProtocol
1298
+ */
1299
+ const fetchBotProfiles = async jids => {
1300
+ const { USyncQuery, USyncUser, USyncBotProfileProtocol } = require('../WAUSync')
1301
+ const q = new USyncQuery()
1302
+ q.protocols.push(new USyncBotProfileProtocol())
1303
+ for (const jid of jids) {
1304
+ q.withUser(new USyncUser().withId(jid))
1305
+ }
1306
+ const result = await sock.executeUSyncQuery(q)
1307
+ return result?.list || []
1308
+ }
1309
+ /**
1310
+ * Lock or unlock a chat with an optional secret code
1311
+ */
1312
+ const updateChatLock = (jid, locked) => {
1313
+ return chatModify({ chatLock: { locked } }, jid)
1314
+ }
1315
+ /**
1316
+ * Set a custom wallpaper for a chat
1317
+ * @param jid the chat JID
1318
+ * @param wallpaper wallpaper data or null to remove
1319
+ */
1320
+ const updateChatWallpaper = (jid, wallpaper) => {
1321
+ if (!wallpaper) {
1322
+ return chatModify({ wallpaper: { remove: true } }, jid)
1323
+ }
1324
+ return chatModify({ wallpaper }, jid)
1325
+ }
1326
+ /**
1327
+ * Set media visibility (auto-download) for a chat
1328
+ * @param jid the chat JID
1329
+ * @param visibility 'default' | 'on' | 'off'
1330
+ */
1331
+ const updateChatMediaVisibility = (jid, visibility) => {
1332
+ return chatModify({ mediaVisibility: visibility }, jid)
1333
+ }
1334
+ /**
1335
+ * Fetch details for a specific bot by JID
1336
+ */
1337
+ const getBotProfile = async botJid => {
1338
+ const resp = await query({
1339
+ tag: 'iq',
1340
+ attrs: {
1341
+ xmlns: 'bot',
1342
+ to: WABinary_1.S_WHATSAPP_NET,
1343
+ type: 'get'
1344
+ },
1345
+ content: [
1346
+ {
1347
+ tag: 'bot',
1348
+ attrs: { v: '2', jid: botJid }
1349
+ }
1350
+ ]
1351
+ })
1352
+ const botNode = (0, WABinary_1.getBinaryNodeChild)(resp, 'bot')
1353
+ if (!botNode) return null
1354
+ return {
1355
+ jid: botNode.attrs.jid,
1356
+ personaId: botNode.attrs['persona_id'],
1357
+ name: botNode.attrs.name,
1358
+ description: botNode.attrs.description
1359
+ }
1360
+ }
1361
+ /**
1362
+ * Fetch AB-test (abt) props from server.
1363
+ * Mirrors ABPropsProtocolHelper — protocol 1 or 2, optional hash/refresh_id/group.
1364
+ */
1365
+ const fetchABProps = async (protocol = '2', hash = '', refreshId = null, group = null) => {
1366
+ const propAttrs = { protocol }
1367
+ if (hash) propAttrs.hash = hash
1368
+ if (refreshId != null) propAttrs.refresh_id = String(refreshId)
1369
+ if (group != null) propAttrs.group = String(group)
1370
+ const result = await query({
1371
+ tag: 'iq',
1372
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'abt', type: 'get' },
1373
+ content: [{ tag: 'props', attrs: propAttrs }]
1374
+ })
1375
+ const propsNode = (0, WABinary_1.getBinaryNodeChild)(result, 'props')
1376
+ if (!propsNode) return {}
1377
+ return (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1378
+ }
1379
+ /**
1380
+ * Remove a companion (linked) device from the account.
1381
+ * Mirrors CompanionDeviceRemovalJob — xmlns="md", child tag "remove-companion-device".
1382
+ * reason: "user_initiated" | "server_initiated" | any WA-defined reason string.
1383
+ */
1384
+ const removeCompanionDevice = async (keyIndex, reason = 'user_initiated') => {
1385
+ await query({
1386
+ tag: 'iq',
1387
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'md', type: 'set' },
1388
+ content: [
1389
+ {
1390
+ tag: 'remove-companion-device',
1391
+ attrs: { platform: 'true', reason, id: String(keyIndex) }
1392
+ }
1393
+ ]
1394
+ })
1395
+ }
1396
+ /**
1397
+ * Push an updated key-index-list to the server (multi-device key announcement).
1398
+ * Mirrors KeyIndexListJob — xmlns="md", child tag "key-index-list" with a binary proto body.
1399
+ * ts: unix timestamp seconds (string or number).
1400
+ * content: Buffer — serialized proto KeyIndexList.
1401
+ */
1402
+ const updateKeyIndexList = async (ts, content) => {
1403
+ await query({
1404
+ tag: 'iq',
1405
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'md', type: 'set' },
1406
+ content: [
1407
+ {
1408
+ tag: 'key-index-list',
1409
+ attrs: { ts: String(ts) },
1410
+ content
1411
+ }
1412
+ ]
1413
+ })
1414
+ }
1415
+ /**
1416
+ * Request a media upload connection token from the server.
1417
+ * Mirrors MediaConnFetcher — xmlns="w:m", type="set", optional last_id.
1418
+ * Returns the raw media_conn node.
1419
+ */
1420
+ const fetchMediaConn = async (lastId = null) => {
1421
+ const attrs = {}
1422
+ if (lastId != null) attrs.last_id = String(lastId)
1423
+ const result = await query({
1424
+ tag: 'iq',
1425
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'w:m', type: 'set' },
1426
+ content: [{ tag: 'media_conn', attrs }]
1427
+ })
1428
+ return (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn') || null
1429
+ }
1430
+ /**
1431
+ * Delete a broadcast list by ID.
1432
+ * Mirrors BroadcastListDeleteJob — xmlns="w:b", wraps <delete><list id="..."/></delete>.
1433
+ */
1434
+ const deleteBroadcastList = async listId => {
1435
+ await query({
1436
+ tag: 'iq',
1437
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'w:b', type: 'set' },
1438
+ content: [
1439
+ {
1440
+ tag: 'delete',
1441
+ attrs: {},
1442
+ content: [{ tag: 'list', attrs: { id: String(listId) } }]
1443
+ }
1444
+ ]
1445
+ })
1446
+ }
1447
+ /**
1448
+ * Fetch a QR code from the server (e.g. for linked-device linking).
1449
+ * Mirrors QRCodeFetcher — xmlns="w:qr", type="get".
1450
+ * addressingMode: "lid" | undefined — set to "lid" for LID-addressed QR.
1451
+ */
1452
+ const fetchQRCode = async (code, addressingMode = null) => {
1453
+ const attrs = { code }
1454
+ if (addressingMode) attrs.addressing_mode = addressingMode
1455
+ const result = await query({
1456
+ tag: 'iq',
1457
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'w:qr', type: 'get' },
1458
+ content: [{ tag: 'qr', attrs }]
1459
+ })
1460
+ return (0, WABinary_1.getBinaryNodeChild)(result, 'qr') || null
1461
+ }
1462
+ /**
1463
+ * Confirm or deny a device-logout request from the server.
1464
+ * Mirrors AccountDefenceDeviceLogoutJob — xmlns="w:account_defence", smax_id=87.
1465
+ * approve: true to confirm the logout, false to deny.
1466
+ */
1467
+ const confirmDeviceLogout = async (id, approve = true) => {
1468
+ await query({
1469
+ tag: 'iq',
1470
+ attrs: {
1471
+ to: WABinary_1.S_WHATSAPP_NET,
1472
+ xmlns: 'w:account_defence',
1473
+ type: 'set',
1474
+ smax_id: '87'
1475
+ },
1476
+ content: [
1477
+ {
1478
+ tag: 'device_logout',
1479
+ attrs: { approve: approve ? 'true' : 'false', id: String(id) }
1480
+ }
1481
+ ]
1482
+ })
1483
+ }
1484
+ /**
1485
+ * queries need to be fired on connection open
1486
+ * help ensure parity with WA Web
1487
+ * */
1488
+ const executeInitQueries = async () => {
1489
+ await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings(), initInterop()])
1490
+ }
1491
+ const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
1492
+ ev.emit('messages.upsert', { messages: [msg], type })
1493
+ if (!!msg.pushName) {
1494
+ let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
1495
+ jid = (0, WABinary_1.jidNormalizedUser)(jid)
1496
+ if (!msg.key.fromMe) {
1497
+ ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
1498
+ }
1499
+ // update our pushname too
1500
+ if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
1501
+ ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
1502
+ }
1503
+ }
1504
+ const historyMsg = (0, Utils_1.getHistoryMsg)(msg.message)
1505
+ const shouldProcessHistoryMsg = historyMsg
1506
+ ? shouldSyncHistoryMessage(historyMsg) && Defaults_1.PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
1507
+ : false
1508
+ if (historyMsg && shouldProcessHistoryMsg) {
1509
+ const syncType = historyMsg.syncType
1510
+ // INITIAL_BOOTSTRAP — fire immediately, no progress check
1511
+ if (
1512
+ syncType === index_js_1.proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
1513
+ !historySyncStatus.initialBootstrapComplete
1514
+ ) {
1515
+ historySyncStatus.initialBootstrapComplete = true
1516
+ ev.emit('messaging-history.status', {
1517
+ syncType,
1518
+ status: 'complete',
1519
+ explicit: true
1520
+ })
1521
+ }
1522
+ // RECENT with progress === 100 — explicit completion
1523
+ if (
1524
+ syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT &&
1525
+ historyMsg.progress === 100 &&
1526
+ !historySyncStatus.recentSyncComplete
1527
+ ) {
1528
+ historySyncStatus.recentSyncComplete = true
1529
+ clearTimeout(historySyncPausedTimeout)
1530
+ historySyncPausedTimeout = undefined
1531
+ ev.emit('messaging-history.status', {
1532
+ syncType,
1533
+ status: 'complete',
1534
+ explicit: true
1535
+ })
1536
+ }
1537
+ // Reset 120s paused timeout on any RECENT chunk
1538
+ if (syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
1539
+ clearTimeout(historySyncPausedTimeout)
1540
+ historySyncPausedTimeout = setTimeout(() => {
1541
+ if (!historySyncStatus.recentSyncComplete) {
1542
+ historySyncStatus.recentSyncComplete = true
1543
+ ev.emit('messaging-history.status', {
1544
+ syncType: index_js_1.proto.HistorySync.HistorySyncType.RECENT,
1545
+ status: 'paused',
1546
+ explicit: false
1547
+ })
1548
+ }
1549
+ historySyncPausedTimeout = undefined
1550
+ }, Defaults_1.HISTORY_SYNC_PAUSED_TIMEOUT_MS)
1551
+ }
1552
+ }
1553
+ // State machine: decide on sync and flush
1554
+ if (historyMsg && syncState === State_1.SyncState.AwaitingInitialSync) {
1555
+ if (awaitingSyncTimeout) {
1556
+ clearTimeout(awaitingSyncTimeout)
1557
+ awaitingSyncTimeout = undefined
1558
+ }
1559
+ if (shouldProcessHistoryMsg) {
1560
+ syncState = State_1.SyncState.Syncing
1561
+ logger.info('Transitioned to Syncing state')
1562
+ // Let doAppStateSync handle the final flush after it's done
1563
+ } else {
1564
+ syncState = State_1.SyncState.Online
1565
+ logger.info('History sync skipped, transitioning to Online state and flushing buffer')
1566
+ ev.flush()
1567
+ }
1568
+ }
1569
+ const doAppStateSync = async () => {
1570
+ if (syncState === State_1.SyncState.Syncing) {
1571
+ // All collections will be synced, so clear any blocked ones
1572
+ blockedCollections.clear()
1573
+ logger.info('Doing app state sync')
1574
+ await resyncAppState(Types_1.ALL_WA_PATCH_NAMES, true)
1575
+ // Sync is complete, go online and flush everything
1576
+ syncState = State_1.SyncState.Online
1577
+ logger.info('App state sync complete, transitioning to Online state and flushing buffer')
1578
+ ev.flush()
1579
+ const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1
1580
+ ev.emit('creds.update', { accountSyncCounter })
1581
+ }
1582
+ }
1583
+ await Promise.all([
1584
+ (async () => {
1585
+ if (shouldProcessHistoryMsg) {
1586
+ await doAppStateSync()
1587
+ }
1588
+ })(),
1589
+ (0, process_message_1.default)(msg, {
1590
+ signalRepository,
1591
+ shouldProcessHistoryMsg,
1592
+ placeholderResendCache,
1593
+ ev,
1594
+ creds: authState.creds,
1595
+ keyStore: authState.keys,
1596
+ logger,
1597
+ options: config.options,
1598
+ getMessage
1599
+ })
1600
+ ])
1601
+ // If the app state key arrives and we are waiting to sync, trigger the sync now.
1602
+ if (msg.message?.protocolMessage?.appStateSyncKeyShare && syncState === State_1.SyncState.Syncing) {
1603
+ logger.info('App state sync key arrived, triggering app state sync')
1604
+ await doAppStateSync()
1605
+ }
1606
+ })
1607
+ ws.on('CB:presence', handlePresenceUpdate)
1608
+ ws.on('CB:chatstate', handlePresenceUpdate)
1609
+ ws.on('CB:ib,,dirty', async node => {
1610
+ const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty')
1611
+ const type = attrs.type
1612
+ switch (type) {
1613
+ case 'account_sync':
1614
+ if (attrs.timestamp) {
1615
+ let { lastAccountSyncTimestamp } = authState.creds
1616
+ if (lastAccountSyncTimestamp) {
1617
+ await cleanDirtyBits('account_sync', lastAccountSyncTimestamp)
1618
+ }
1619
+ lastAccountSyncTimestamp = +attrs.timestamp
1620
+ ev.emit('creds.update', { lastAccountSyncTimestamp })
1621
+ }
1622
+ break
1623
+ case 'groups':
1624
+ // handled in groups.ts
1625
+ break
1626
+ default:
1627
+ logger.info({ node }, 'received unknown sync')
1628
+ break
1629
+ }
1630
+ })
1631
+ ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
1632
+ if (connection === 'close') {
1633
+ blockedCollections.clear()
1634
+ clearTimeout(historySyncPausedTimeout)
1635
+ historySyncPausedTimeout = undefined
1636
+ }
1637
+ if (connection === 'open') {
1638
+ if (fireInitQueries) {
1639
+ executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'))
1640
+ }
1641
+ sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error =>
1642
+ onUnexpectedError(error, 'presence update requests')
1643
+ )
1644
+ }
1645
+ if (!receivedPendingNotifications || syncState !== State_1.SyncState.Connecting) {
1646
+ return
1647
+ }
1648
+ historySyncStatus.initialBootstrapComplete = false
1649
+ historySyncStatus.recentSyncComplete = false
1650
+ clearTimeout(historySyncPausedTimeout)
1651
+ historySyncPausedTimeout = undefined
1652
+ syncState = State_1.SyncState.AwaitingInitialSync
1653
+ logger.info('Connection is now AwaitingInitialSync, buffering events')
1654
+ ev.buffer()
1655
+ const willSyncHistory = shouldSyncHistoryMessage(
1656
+ index_js_1.proto.Message.HistorySyncNotification.create({
1657
+ syncType: index_js_1.proto.HistorySync.HistorySyncType.RECENT
1658
+ })
1659
+ )
1660
+ if (!willSyncHistory) {
1661
+ logger.info('History sync is disabled by config, not waiting for notification. Transitioning to Online.')
1662
+ syncState = State_1.SyncState.Online
1663
+ setTimeout(() => ev.flush(), 0)
1664
+ return
1665
+ }
1666
+ // On reconnection (accountSyncCounter > 0), the server does not push
1667
+ // history sync notifications — the device already has its data.
1668
+ // Skip the 20s wait and go online immediately.
1669
+ if (authState.creds.accountSyncCounter > 0) {
1670
+ logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
1671
+ syncState = State_1.SyncState.Online
1672
+ setTimeout(() => ev.flush(), 0)
1673
+ return
1674
+ }
1675
+ logger.info('First connection, awaiting history sync notification with a 20s timeout.')
1676
+ if (awaitingSyncTimeout) {
1677
+ clearTimeout(awaitingSyncTimeout)
1678
+ }
1679
+ awaitingSyncTimeout = setTimeout(() => {
1680
+ if (syncState === State_1.SyncState.AwaitingInitialSync) {
1681
+ logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer')
1682
+ syncState = State_1.SyncState.Online
1683
+ ev.flush()
1684
+ // Increment so subsequent reconnections skip the 20s wait.
1685
+ const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1
1686
+ ev.emit('creds.update', { accountSyncCounter })
1687
+ }
1688
+ }, 20000)
1689
+ })
1690
+ // When an app state sync key arrives and there are collections blocked on a missing key, re-sync them.
1691
+ ev.on('creds.update', ({ myAppStateKeyId }) => {
1692
+ if (!myAppStateKeyId || blockedCollections.size === 0) {
1693
+ return
1694
+ }
1695
+ // If we're in the middle of a full sync, doAppStateSync handles all collections
1696
+ if (syncState === State_1.SyncState.Syncing) {
1697
+ blockedCollections.clear()
1698
+ return
1699
+ }
1700
+ const collections = [...blockedCollections]
1701
+ blockedCollections.clear()
1702
+ logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections')
1703
+ resyncAppState(collections, false).catch(error => onUnexpectedError(error, 'blocked collections resync'))
1704
+ })
1705
+ ev.on('lid-mapping.update', async ({ lid, pn }) => {
1706
+ try {
1707
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }])
1708
+ } catch (error) {
1709
+ logger.warn({ lid, pn, error }, 'Failed to store LID-PN mapping')
1710
+ }
1711
+ })
1712
+ return {
1713
+ ...sock,
1714
+ serverProps,
1715
+ createCallLink,
1716
+ toggleCallLinkWaitingRoom,
1717
+ getBotListV2,
1718
+ getChatBlockingStatus,
1719
+ updateChatBlockingStatus,
1720
+ getUserDisclosures,
1721
+ getOptOutList,
1722
+ getPushConfig,
1723
+ setPushConfig,
1724
+ messageMutex,
1725
+ receiptMutex,
1726
+ appStatePatchMutex,
1727
+ notificationMutex,
1728
+ fetchPrivacySettings,
1729
+ upsertMessage,
1730
+ appPatch,
1731
+ sendPresenceUpdate,
1732
+ presenceSubscribe,
1733
+ profilePictureUrl,
1734
+ fetchBlocklist,
1735
+ fetchStatus,
1736
+ fetchDisappearingDuration,
1737
+ updateProfilePicture,
1738
+ removeProfilePicture,
1739
+ updateProfileStatus,
1740
+ updateProfileName,
1741
+ updateBlockStatus,
1742
+ updateDisableLinkPreviewsPrivacy,
1743
+ updateCallPrivacy,
1744
+ updateMessagesPrivacy,
1745
+ updateLastSeenPrivacy,
1746
+ updateOnlinePrivacy,
1747
+ updateProfilePicturePrivacy,
1748
+ updateStatusPrivacy,
1749
+ getStatusPrivacy,
1750
+ setStatusPrivacy,
1751
+ updateReadReceiptsPrivacy,
1752
+ updateGroupsAddPrivacy,
1753
+ updateDefaultDisappearingMode,
1754
+ fetchBroadcastListQuota,
1755
+ getBusinessProfile,
1756
+ resyncAppState,
1757
+ chatModify,
1758
+ cleanDirtyBits,
1759
+ addOrEditContact,
1760
+ removeContact,
1761
+ addLabel,
1762
+ addChatLabel,
1763
+ removeChatLabel,
1764
+ addMessageLabel,
1765
+ removeMessageLabel,
1766
+ star,
1767
+ addOrEditQuickReply,
1768
+ removeQuickReply,
1769
+ renameAIThread,
1770
+ pinThreadMessage,
1771
+ updatePrivateProcessingSetting,
1772
+ updateBioPrivacy,
1773
+ blockBot,
1774
+ unblockBot,
1775
+ getBotProfile,
1776
+ muteContactStatus,
1777
+ toggleFavorite,
1778
+ reorderLabels,
1779
+ deleteCallLog,
1780
+ setChatNote,
1781
+ deleteChatNote,
1782
+ markChatAsUnread,
1783
+ setChatEphemeral,
1784
+ silenceChat,
1785
+ fetchBotProfiles,
1786
+ updateChatLock,
1787
+ updateChatWallpaper,
1788
+ updateChatMediaVisibility,
1789
+ fetchIntegrators,
1790
+ acceptInteropTOS,
1791
+ optInIntegrators,
1792
+ optOutIntegrators,
1793
+ resolveInteropUser,
1794
+ resolveInteropUsers,
1795
+ getReachabilitySettings,
1796
+ setReachabilitySettings,
1797
+ blockInteropUser,
1798
+ unblockInteropUser,
1799
+ reportInteropSpam,
1800
+ trustInteropContact,
1801
+ initInterop,
1802
+ createInteropGroup,
1803
+ leaveInteropGroup,
1804
+ getInteropGroupAddPrivacy,
1805
+ INTEGRATOR_BIRDYCHAT,
1806
+ INTEGRATOR_HAIKET,
1807
+ fetchABProps,
1808
+ removeCompanionDevice,
1809
+ updateKeyIndexList,
1810
+ fetchMediaConn,
1811
+ deleteBroadcastList,
1812
+ fetchQRCode,
1813
+ confirmDeviceLogout
1814
+ }
1815
+ }
1816
+ exports.makeChatsSocket = makeChatsSocket