@nexustechpro/baileys 2.1.0 → 2.1.3
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.
- package/README.md +1 -1
- package/lib/Defaults/index.js +1 -0
- package/lib/Socket/chats.js +180 -272
- package/lib/Socket/messages-recv.js +8 -5
- package/lib/Socket/messages-send.js +16 -15
- package/lib/Utils/chat-utils.js +318 -619
- package/lib/Utils/key-store.js +10 -5
- package/lib/Utils/lt-hash.js +2 -43
- package/lib/Utils/messages.js +89 -64
- package/lib/Utils/process-message.js +234 -215
- package/lib/Utils/tc-token-utils.js +38 -73
- package/lib/Utils/use-multi-file-auth-state.js +18 -8
- package/lib/index.js +1 -1
- package/package.json +2 -2
package/lib/Socket/chats.js
CHANGED
|
@@ -1,82 +1,64 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache'
|
|
2
2
|
import { Boom } from '@hapi/boom'
|
|
3
3
|
import { proto } from '../../WAProto/index.js'
|
|
4
|
-
import { DEFAULT_CACHE_TTLS,
|
|
4
|
+
import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js'
|
|
5
5
|
import { ALL_WA_PATCH_NAMES } from '../Types/index.js'
|
|
6
6
|
import { SyncState } from '../Types/State.js'
|
|
7
|
-
import {
|
|
8
|
-
chatModificationToAppPatch,
|
|
9
|
-
decodePatches,
|
|
10
|
-
decodeSyncdSnapshot,
|
|
11
|
-
encodeSyncdPatch,
|
|
12
|
-
ensureLTHashStateVersion,
|
|
13
|
-
extractSyncdPatches,
|
|
14
|
-
generateProfilePicture,
|
|
15
|
-
getHistoryMsg,
|
|
16
|
-
newLTHashState,
|
|
17
|
-
processSyncAction
|
|
18
|
-
} from '../Utils/index.js'
|
|
7
|
+
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js'
|
|
19
8
|
import { makeMutex } from '../Utils/make-mutex.js'
|
|
20
9
|
import processMessage from '../Utils/process-message.js'
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
getBinaryNodeChildren,
|
|
24
|
-
isLidUser,
|
|
25
|
-
jidDecode,
|
|
26
|
-
jidNormalizedUser,
|
|
27
|
-
reduceBinaryNodeToDictionary,
|
|
28
|
-
S_WHATSAPP_NET,
|
|
29
|
-
VIOLATION_TYPES
|
|
30
|
-
} from '../WABinary/index.js'
|
|
10
|
+
import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js'
|
|
11
|
+
import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET, VIOLATION_TYPES } from '../WABinary/index.js'
|
|
31
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js'
|
|
32
13
|
import { makeSocket } from './socket.js'
|
|
33
14
|
|
|
34
|
-
const MAX_SYNC_ATTEMPTS = 2
|
|
35
|
-
const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120_000
|
|
36
15
|
const APP_STATE_RESYNC_COOLDOWN_MS = 60_000
|
|
37
16
|
|
|
38
17
|
export const makeChatsSocket = (config) => {
|
|
39
|
-
const {
|
|
40
|
-
logger,
|
|
41
|
-
markOnlineOnConnect,
|
|
42
|
-
fireInitQueries,
|
|
43
|
-
appStateMacVerification,
|
|
44
|
-
shouldIgnoreJid,
|
|
45
|
-
shouldSyncHistoryMessage,
|
|
46
|
-
getMessage
|
|
47
|
-
} = config
|
|
18
|
+
const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config
|
|
48
19
|
|
|
49
20
|
const sock = makeSocket(config)
|
|
50
|
-
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock
|
|
21
|
+
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession, registerSocketEndHandler } = sock
|
|
22
|
+
|
|
23
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
|
|
51
24
|
|
|
52
25
|
let privacySettings
|
|
26
|
+
/** Server-assigned AB props for protocol behavior. */
|
|
27
|
+
const serverProps = {
|
|
28
|
+
/** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
|
|
29
|
+
privacyTokenOn1to1: true,
|
|
30
|
+
/** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
|
|
31
|
+
profilePicPrivacyToken: true,
|
|
32
|
+
/** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
|
|
33
|
+
lidTrustedTokenIssueToLid: false
|
|
34
|
+
}
|
|
35
|
+
|
|
53
36
|
let syncState = SyncState.Connecting
|
|
54
37
|
let awaitingSyncTimeout
|
|
55
38
|
let historySyncPausedTimeout
|
|
56
39
|
|
|
57
40
|
const historySyncStatus = { initialBootstrapComplete: false, recentSyncComplete: false }
|
|
58
41
|
const blockedCollections = new Set()
|
|
42
|
+
const appStateResyncCooldown = new Map()
|
|
59
43
|
|
|
44
|
+
/** this mutex ensures that processing happens in order */
|
|
60
45
|
const processingMutex = makeMutex()
|
|
46
|
+
/** this mutex ensures that messages are processed in order */
|
|
61
47
|
const messageMutex = makeMutex()
|
|
48
|
+
/** this mutex ensures that receipts are processed in order */
|
|
62
49
|
const receiptMutex = makeMutex()
|
|
50
|
+
/** this mutex ensures that app state patches are processed in order */
|
|
63
51
|
const appStatePatchMutex = makeMutex()
|
|
52
|
+
/** this mutex ensures that notifications are processed in order */
|
|
64
53
|
const notificationMutex = makeMutex()
|
|
65
54
|
|
|
66
|
-
const placeholderResendCache = config.placeholderResendCache || new NodeCache({
|
|
67
|
-
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
68
|
-
useClones: false
|
|
69
|
-
})
|
|
55
|
+
const placeholderResendCache = config.placeholderResendCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, useClones: false })
|
|
70
56
|
if (!config.placeholderResendCache) config.placeholderResendCache = placeholderResendCache
|
|
71
57
|
|
|
72
|
-
const profilePictureUrlCache = config.profilePictureUrlCache || new NodeCache({
|
|
73
|
-
stdTTL: DEFAULT_CACHE_TTLS.PROFILE_PIC,
|
|
74
|
-
useClones: false
|
|
75
|
-
})
|
|
58
|
+
const profilePictureUrlCache = config.profilePictureUrlCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.PROFILE_PIC, useClones: false })
|
|
76
59
|
if (!config.profilePictureUrlCache) config.profilePictureUrlCache = profilePictureUrlCache
|
|
77
60
|
|
|
78
61
|
const inFlightProfilePictureUrl = new Map()
|
|
79
|
-
const appStateResyncCooldown = new Map()
|
|
80
62
|
|
|
81
63
|
// ─── Key helpers ────────────────────────────────────────────────────────────
|
|
82
64
|
|
|
@@ -85,45 +67,26 @@ export const makeChatsSocket = (config) => {
|
|
|
85
67
|
return key
|
|
86
68
|
}
|
|
87
69
|
|
|
88
|
-
|
|
89
70
|
const interactiveQuery = async (userNodes, queryNode) => {
|
|
90
|
-
const result = await query({
|
|
91
|
-
tag: 'iq',
|
|
92
|
-
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
|
|
93
|
-
content: [{
|
|
94
|
-
tag: 'usync',
|
|
95
|
-
attrs: { sid: generateMessageTag(), mode: 'query', last: 'true', index: '0', context: 'interactive' },
|
|
96
|
-
content: [
|
|
97
|
-
{ tag: 'query', attrs: {}, content: [queryNode] },
|
|
98
|
-
{ tag: 'list', attrs: {}, content: userNodes }
|
|
99
|
-
]
|
|
100
|
-
}]
|
|
101
|
-
})
|
|
71
|
+
const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' }, content: [{ tag: 'usync', attrs: { sid: generateMessageTag(), mode: 'query', last: 'true', index: '0', context: 'interactive' }, content: [{ tag: 'query', attrs: {}, content: [queryNode] }, { tag: 'list', attrs: {}, content: userNodes }] }] })
|
|
102
72
|
const usyncNode = getBinaryNodeChild(result, 'usync')
|
|
103
73
|
const listNode = getBinaryNodeChild(usyncNode, 'list')
|
|
104
74
|
return getBinaryNodeChildren(listNode, 'user')
|
|
105
75
|
}
|
|
106
76
|
|
|
107
|
-
// ─── Privacy
|
|
77
|
+
// ─── Privacy ──────────────────────────────────────────────────────────────
|
|
108
78
|
|
|
109
79
|
const fetchPrivacySettings = async (force = false) => {
|
|
110
80
|
if (!privacySettings || force) {
|
|
111
|
-
const { content } = await query({
|
|
112
|
-
tag: 'iq',
|
|
113
|
-
attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'get' },
|
|
114
|
-
content: [{ tag: 'privacy', attrs: {} }]
|
|
115
|
-
})
|
|
81
|
+
const { content } = await query({ tag: 'iq', attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'get' }, content: [{ tag: 'privacy', attrs: {} }] })
|
|
116
82
|
privacySettings = reduceBinaryNodeToDictionary(content?.[0], 'category')
|
|
117
83
|
}
|
|
118
84
|
return privacySettings
|
|
119
85
|
}
|
|
120
86
|
|
|
87
|
+
/** helper function to run a privacy IQ query */
|
|
121
88
|
const privacyQuery = async (name, value) => {
|
|
122
|
-
await query({
|
|
123
|
-
tag: 'iq',
|
|
124
|
-
attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'set' },
|
|
125
|
-
content: [{ tag: 'privacy', attrs: {}, content: [{ tag: 'category', attrs: { name, value } }] }]
|
|
126
|
-
})
|
|
89
|
+
await query({ tag: 'iq', attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'privacy', attrs: {}, content: [{ tag: 'category', attrs: { name, value } }] }] })
|
|
127
90
|
}
|
|
128
91
|
|
|
129
92
|
const updateMessagesPrivacy = (value) => privacyQuery('messages', value)
|
|
@@ -136,21 +99,13 @@ export const makeChatsSocket = (config) => {
|
|
|
136
99
|
const updateGroupsAddPrivacy = (value) => privacyQuery('groupadd', value)
|
|
137
100
|
|
|
138
101
|
const updateDefaultDisappearingMode = async (duration) => {
|
|
139
|
-
await query({
|
|
140
|
-
tag: 'iq',
|
|
141
|
-
attrs: { xmlns: 'disappearing_mode', to: S_WHATSAPP_NET, type: 'set' },
|
|
142
|
-
content: [{ tag: 'disappearing_mode', attrs: { duration: duration.toString() } }]
|
|
143
|
-
})
|
|
102
|
+
await query({ tag: 'iq', attrs: { xmlns: 'disappearing_mode', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'disappearing_mode', attrs: { duration: duration.toString() } }] })
|
|
144
103
|
}
|
|
145
104
|
|
|
146
|
-
// ─── Queries
|
|
105
|
+
// ─── Queries ──────────────────────────────────────────────────────────────
|
|
147
106
|
|
|
148
107
|
const getBotListV2 = async () => {
|
|
149
|
-
const resp = await query({
|
|
150
|
-
tag: 'iq',
|
|
151
|
-
attrs: { xmlns: 'bot', to: S_WHATSAPP_NET, type: 'get' },
|
|
152
|
-
content: [{ tag: 'bot', attrs: { v: '2' } }]
|
|
153
|
-
})
|
|
108
|
+
const resp = await query({ tag: 'iq', attrs: { xmlns: 'bot', to: S_WHATSAPP_NET, type: 'get' }, content: [{ tag: 'bot', attrs: { v: '2' } }] })
|
|
154
109
|
const botNode = getBinaryNodeChild(resp, 'bot')
|
|
155
110
|
const botList = []
|
|
156
111
|
for (const section of getBinaryNodeChildren(botNode, 'section')) {
|
|
@@ -180,34 +135,24 @@ export const makeChatsSocket = (config) => {
|
|
|
180
135
|
const onWhatsApp = async (...jids) => {
|
|
181
136
|
const usyncQuery = new USyncQuery()
|
|
182
137
|
let contactEnabled = false
|
|
183
|
-
|
|
184
138
|
for (let jid of jids) {
|
|
185
139
|
if (isLidUser(jid)) {
|
|
186
140
|
const pn = await signalRepository.lidMapping.getPNForLID(jid)
|
|
187
141
|
if (pn) {
|
|
188
142
|
jid = pn
|
|
189
143
|
} else {
|
|
190
|
-
if (!contactEnabled) {
|
|
191
|
-
contactEnabled = true
|
|
192
|
-
usyncQuery.withContactProtocol().withLIDProtocol()
|
|
193
|
-
}
|
|
144
|
+
if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
|
|
194
145
|
usyncQuery.withUser(new USyncUser().withId(jid))
|
|
195
146
|
continue
|
|
196
147
|
}
|
|
197
148
|
}
|
|
198
|
-
if (!contactEnabled) {
|
|
199
|
-
contactEnabled = true
|
|
200
|
-
usyncQuery.withContactProtocol().withLIDProtocol()
|
|
201
|
-
}
|
|
149
|
+
if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
|
|
202
150
|
const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}`
|
|
203
151
|
usyncQuery.withUser(new USyncUser().withPhone(phone))
|
|
204
152
|
}
|
|
205
|
-
|
|
206
153
|
if (usyncQuery.users.length === 0) return []
|
|
207
|
-
|
|
208
154
|
const results = await sock.executeUSyncQuery(usyncQuery)
|
|
209
155
|
if (!results) return []
|
|
210
|
-
|
|
211
156
|
return Promise.all(
|
|
212
157
|
results.list
|
|
213
158
|
.filter(a => a.contact === true && a.id && a.id !== 'undefined')
|
|
@@ -227,53 +172,47 @@ export const makeChatsSocket = (config) => {
|
|
|
227
172
|
)
|
|
228
173
|
}
|
|
229
174
|
|
|
230
|
-
// ─── Profile
|
|
175
|
+
// ─── Profile ──────────────────────────────────────────────────────────────
|
|
231
176
|
|
|
232
|
-
|
|
177
|
+
/** update the profile picture for yourself or a group */
|
|
178
|
+
const updateProfilePicture = async (jid, content, dimensions) => {
|
|
233
179
|
if (!jid) throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
await query({
|
|
238
|
-
tag: 'iq',
|
|
239
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) },
|
|
240
|
-
content: [{ tag: 'picture', attrs: { type: 'image' }, content: img }]
|
|
241
|
-
})
|
|
180
|
+
const targetJid = jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id) ? jidNormalizedUser(jid) : undefined
|
|
181
|
+
const { img } = await generateProfilePicture(content, dimensions)
|
|
182
|
+
await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) }, content: [{ tag: 'picture', attrs: { type: 'image' }, content: img }] })
|
|
242
183
|
}
|
|
243
184
|
|
|
185
|
+
/** remove the profile picture for yourself or a group */
|
|
244
186
|
const removeProfilePicture = async (jid) => {
|
|
245
187
|
if (!jid) throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await query({
|
|
249
|
-
tag: 'iq',
|
|
250
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) }
|
|
251
|
-
})
|
|
188
|
+
const targetJid = jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id) ? jidNormalizedUser(jid) : undefined
|
|
189
|
+
await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) } })
|
|
252
190
|
}
|
|
253
191
|
|
|
192
|
+
/** update the profile status for yourself */
|
|
254
193
|
const updateProfileStatus = async (status) => {
|
|
255
|
-
await query({
|
|
256
|
-
tag: 'iq',
|
|
257
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' },
|
|
258
|
-
content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }]
|
|
259
|
-
})
|
|
194
|
+
await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' }, content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }] })
|
|
260
195
|
}
|
|
261
196
|
|
|
262
197
|
const updateProfileName = (name) => chatModify({ pushNameSetting: name }, '')
|
|
263
198
|
|
|
264
199
|
const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
|
|
265
|
-
|
|
266
|
-
const cacheKey = `${
|
|
200
|
+
const normalizedJid = jidNormalizedUser(jid)
|
|
201
|
+
const cacheKey = `${normalizedJid}:${type}`
|
|
267
202
|
const cached = profilePictureUrlCache.get(cacheKey)
|
|
268
203
|
if (typeof cached !== 'undefined') return cached || undefined
|
|
269
204
|
const inFlight = inFlightProfilePictureUrl.get(cacheKey)
|
|
270
205
|
if (inFlight) return inFlight
|
|
271
206
|
const fetchPromise = (async () => {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
207
|
+
const baseContent = [{ tag: 'picture', attrs: { type, query: 'url' } }]
|
|
208
|
+
const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid)
|
|
209
|
+
const me = authState.creds.me
|
|
210
|
+
const isSelf = me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)))
|
|
211
|
+
let content = baseContent
|
|
212
|
+
if (serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
|
|
213
|
+
content = await buildTcTokenFromJid({ authState, jid: normalizedJid, baseContent, getLIDForPN })
|
|
214
|
+
}
|
|
215
|
+
const result = await query({ tag: 'iq', attrs: { target: normalizedJid, to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' }, content }, timeoutMs)
|
|
277
216
|
const child = getBinaryNodeChild(result, 'picture')
|
|
278
217
|
const url = child?.attrs?.url
|
|
279
218
|
profilePictureUrlCache.set(cacheKey, url || null)
|
|
@@ -287,7 +226,7 @@ export const makeChatsSocket = (config) => {
|
|
|
287
226
|
}
|
|
288
227
|
}
|
|
289
228
|
|
|
290
|
-
// ─── Blocklist
|
|
229
|
+
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
291
230
|
|
|
292
231
|
const fetchBlocklist = async () => {
|
|
293
232
|
const result = await query({ tag: 'iq', attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'get' } })
|
|
@@ -296,21 +235,36 @@ export const makeChatsSocket = (config) => {
|
|
|
296
235
|
}
|
|
297
236
|
|
|
298
237
|
const updateBlockStatus = async (jid, action) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
238
|
+
const normalizedJid = jidNormalizedUser(jid)
|
|
239
|
+
let lid
|
|
240
|
+
let pn_jid
|
|
241
|
+
if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
|
|
242
|
+
lid = normalizedJid
|
|
243
|
+
if (action === 'block') {
|
|
244
|
+
const pn = await signalRepository.lidMapping.getPNForLID(normalizedJid)
|
|
245
|
+
if (!pn) throw new Boom(`Unable to resolve PN JID for LID: ${jid}`, { statusCode: 400 })
|
|
246
|
+
pn_jid = jidNormalizedUser(pn)
|
|
247
|
+
}
|
|
248
|
+
} else if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
|
|
249
|
+
const mapped = await signalRepository.lidMapping.getLIDForPN(normalizedJid)
|
|
250
|
+
if (!mapped) throw new Boom(`Unable to resolve LID for PN JID: ${jid}`, { statusCode: 400 })
|
|
251
|
+
lid = mapped
|
|
252
|
+
if (action === 'block') pn_jid = jidNormalizedUser(normalizedJid)
|
|
253
|
+
} else {
|
|
254
|
+
throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 })
|
|
255
|
+
}
|
|
256
|
+
const itemAttrs = { action, jid: lid }
|
|
257
|
+
if (action === 'block') {
|
|
258
|
+
if (!pn_jid) throw new Boom(`pn_jid required for block: ${jid}`, { statusCode: 400 })
|
|
259
|
+
itemAttrs.pn_jid = pn_jid
|
|
260
|
+
}
|
|
261
|
+
await query({ tag: 'iq', attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'item', attrs: itemAttrs }] })
|
|
304
262
|
}
|
|
305
263
|
|
|
306
|
-
// ─── Business
|
|
264
|
+
// ─── Business ─────────────────────────────────────────────────────────────
|
|
307
265
|
|
|
308
266
|
const getBusinessProfile = async (jid) => {
|
|
309
|
-
const results = await query({
|
|
310
|
-
tag: 'iq',
|
|
311
|
-
attrs: { to: 's.whatsapp.net', xmlns: 'w:biz', type: 'get' },
|
|
312
|
-
content: [{ tag: 'business_profile', attrs: { v: '244' }, content: [{ tag: 'profile', attrs: { jid } }] }]
|
|
313
|
-
})
|
|
267
|
+
const results = await query({ tag: 'iq', attrs: { to: 's.whatsapp.net', xmlns: 'w:biz', type: 'get' }, content: [{ tag: 'business_profile', attrs: { v: '244' }, content: [{ tag: 'profile', attrs: { jid } }] }] })
|
|
314
268
|
const profileNode = getBinaryNodeChild(results, 'business_profile')
|
|
315
269
|
const profiles = getBinaryNodeChild(profileNode, 'profile')
|
|
316
270
|
if (profiles) {
|
|
@@ -334,15 +288,11 @@ export const makeChatsSocket = (config) => {
|
|
|
334
288
|
}
|
|
335
289
|
}
|
|
336
290
|
|
|
337
|
-
// ─── App state
|
|
291
|
+
// ─── App state ────────────────────────────────────────────────────────────
|
|
338
292
|
|
|
339
293
|
const cleanDirtyBits = async (type, fromTimestamp) => {
|
|
340
294
|
logger.info({ fromTimestamp }, 'clean dirty bits ' + type)
|
|
341
|
-
await sendNode({
|
|
342
|
-
tag: 'iq',
|
|
343
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', id: generateMessageTag() },
|
|
344
|
-
content: [{ tag: 'clean', attrs: { type, ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } }]
|
|
345
|
-
})
|
|
295
|
+
await sendNode({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', id: generateMessageTag() }, content: [{ tag: 'clean', attrs: { type, ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } }] })
|
|
346
296
|
}
|
|
347
297
|
|
|
348
298
|
const newAppStateChunkHandler = (isInitialSync) => ({
|
|
@@ -356,7 +306,6 @@ export const makeChatsSocket = (config) => {
|
|
|
356
306
|
const collectionsToSync = collections.filter(name => (appStateResyncCooldown.get(name) || 0) <= now)
|
|
357
307
|
if (!collectionsToSync.length) return
|
|
358
308
|
|
|
359
|
-
// Per-invocation cache to avoid redundant key fetches within a single resync
|
|
360
309
|
const appStateSyncKeyCache = new Map()
|
|
361
310
|
const getCachedAppStateSyncKey = async (keyId) => {
|
|
362
311
|
if (appStateSyncKeyCache.has(keyId)) return appStateSyncKeyCache.get(keyId) ?? undefined
|
|
@@ -387,24 +336,15 @@ export const makeChatsSocket = (config) => {
|
|
|
387
336
|
state = newLTHashState()
|
|
388
337
|
}
|
|
389
338
|
states[name] = state
|
|
390
|
-
|
|
391
339
|
const shouldForceSnapshot = forceSnapshotCollections.has(name)
|
|
392
340
|
if (shouldForceSnapshot) forceSnapshotCollections.delete(name)
|
|
393
|
-
|
|
394
341
|
logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`)
|
|
395
|
-
nodes.push({
|
|
396
|
-
tag: 'collection',
|
|
397
|
-
attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() }
|
|
398
|
-
})
|
|
342
|
+
nodes.push({ tag: 'collection', attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() } })
|
|
399
343
|
}
|
|
400
344
|
|
|
401
|
-
const result = await query({
|
|
402
|
-
tag: 'iq',
|
|
403
|
-
attrs: { to: S_WHATSAPP_NET, xmlns: 'w:sync:app:state', type: 'set' },
|
|
404
|
-
content: [{ tag: 'sync', attrs: {}, content: nodes }]
|
|
405
|
-
})
|
|
406
|
-
|
|
345
|
+
const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'w:sync:app:state', type: 'set' }, content: [{ tag: 'sync', attrs: {}, content: nodes }] })
|
|
407
346
|
const decoded = await extractSyncdPatches(result, config?.options)
|
|
347
|
+
|
|
408
348
|
for (const key in decoded) {
|
|
409
349
|
const name = key
|
|
410
350
|
const { patches, hasMorePatches, snapshot } = decoded[name]
|
|
@@ -430,25 +370,20 @@ export const makeChatsSocket = (config) => {
|
|
|
430
370
|
}
|
|
431
371
|
} catch (error) {
|
|
432
372
|
attemptsMap[name] = (attemptsMap[name] || 0) + 1
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
logger.info({ name, error: error.stack, attempt: attemptsMap[name] }, `failed to sync ${name} from v${states[name].version}`)
|
|
437
|
-
if (!isMissingKey) {
|
|
438
|
-
await authState.keys.set({ 'app-state-sync-version': { [name]: null } })
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (isMissingKey) appStateResyncCooldown.set(name, Date.now() + APP_STATE_RESYNC_COOLDOWN_MS)
|
|
442
|
-
|
|
443
|
-
if (isMissingKey && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
|
|
444
|
-
logger.warn({ name }, `${name} blocked on missing key, parking until key arrives`)
|
|
373
|
+
const logData = { name, attempt: attemptsMap[name], version: states[name].version, statusCode: error.output?.statusCode, errorType: error.name, error: error.stack }
|
|
374
|
+
if (isMissingKeyError(error) && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
|
|
375
|
+
logger.warn(logData, `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`)
|
|
445
376
|
blockedCollections.add(name)
|
|
446
377
|
collectionsToHandle.delete(name)
|
|
447
|
-
|
|
378
|
+
appStateResyncCooldown.set(name, Date.now() + APP_STATE_RESYNC_COOLDOWN_MS)
|
|
379
|
+
} else if (isMissingKeyError(error)) {
|
|
380
|
+
logger.info(logData, `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`)
|
|
448
381
|
forceSnapshotCollections.add(name)
|
|
449
|
-
} else if (
|
|
382
|
+
} else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
|
|
383
|
+
logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`)
|
|
450
384
|
collectionsToHandle.delete(name)
|
|
451
385
|
} else {
|
|
386
|
+
logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`)
|
|
452
387
|
forceSnapshotCollections.add(name)
|
|
453
388
|
}
|
|
454
389
|
}
|
|
@@ -460,33 +395,33 @@ export const makeChatsSocket = (config) => {
|
|
|
460
395
|
for (const key in globalMutationMap) onMutation(globalMutationMap[key])
|
|
461
396
|
})
|
|
462
397
|
|
|
463
|
-
// ─── Presence
|
|
398
|
+
// ─── Presence ─────────────────────────────────────────────────────────────
|
|
464
399
|
|
|
465
400
|
const sendPresenceUpdate = async (type, toJid) => {
|
|
466
401
|
const me = authState.creds.me
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
ev.emit('connection.update', { isOnline: type === 'available' })
|
|
402
|
+
const isAvailableType = type === 'available'
|
|
403
|
+
if (isAvailableType || type === 'unavailable') {
|
|
404
|
+
if (!me.name) { logger.warn('no name present, ignoring presence update request...'); return }
|
|
405
|
+
ev.emit('connection.update', { isOnline: isAvailableType })
|
|
406
|
+
if (isAvailableType) void sendUnifiedSession()
|
|
473
407
|
await sendNode({ tag: 'presence', attrs: { name: me.name.replace(/@/g, ''), type } })
|
|
474
408
|
} else {
|
|
475
409
|
const { server } = jidDecode(toJid)
|
|
476
410
|
const isLid = server === 'lid'
|
|
477
|
-
await sendNode({
|
|
478
|
-
tag: 'chatstate',
|
|
479
|
-
attrs: { from: isLid ? me.lid : me.id, to: toJid },
|
|
480
|
-
content: [{ tag: type === 'recording' ? 'composing' : type, attrs: type === 'recording' ? { media: 'audio' } : {} }]
|
|
481
|
-
})
|
|
411
|
+
await sendNode({ tag: 'chatstate', attrs: { from: isLid ? me.lid : me.id, to: toJid }, content: [{ tag: type === 'recording' ? 'composing' : type, attrs: type === 'recording' ? { media: 'audio' } : {} }] })
|
|
482
412
|
}
|
|
483
413
|
}
|
|
484
414
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
415
|
+
/**
|
|
416
|
+
* @param toJid the jid to subscribe to
|
|
417
|
+
* @param tcToken token for subscription, use if present
|
|
418
|
+
*/
|
|
419
|
+
const presenceSubscribe = async (toJid) => {
|
|
420
|
+
const normalizedToJid = jidNormalizedUser(toJid)
|
|
421
|
+
const isUserJid = isPnUser(normalizedToJid) || isLidUser(normalizedToJid)
|
|
422
|
+
const tcTokenContent = isUserJid ? await buildTcTokenFromJid({ authState, jid: normalizedToJid, getLIDForPN }) : undefined
|
|
423
|
+
return sendNode({ tag: 'presence', attrs: { to: toJid, id: generateMessageTag(), type: 'subscribe' }, content: tcTokenContent })
|
|
424
|
+
}
|
|
490
425
|
|
|
491
426
|
const handlePresenceUpdate = ({ tag, attrs, content }) => {
|
|
492
427
|
let presence
|
|
@@ -494,10 +429,7 @@ export const makeChatsSocket = (config) => {
|
|
|
494
429
|
const participant = attrs.participant || attrs.from
|
|
495
430
|
if (shouldIgnoreJid(jid) && jid !== S_WHATSAPP_NET) return
|
|
496
431
|
if (tag === 'presence') {
|
|
497
|
-
presence = {
|
|
498
|
-
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
|
|
499
|
-
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
|
|
500
|
-
}
|
|
432
|
+
presence = { lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available', lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined, groupOnlineCount: attrs.count ? +attrs.count : undefined }
|
|
501
433
|
} else if (Array.isArray(content)) {
|
|
502
434
|
const [firstChild] = content
|
|
503
435
|
let type = firstChild.tag
|
|
@@ -510,7 +442,7 @@ export const makeChatsSocket = (config) => {
|
|
|
510
442
|
if (presence) ev.emit('presence.update', { id: jid, presences: { [participant]: presence } })
|
|
511
443
|
}
|
|
512
444
|
|
|
513
|
-
// ─── App patch
|
|
445
|
+
// ─── App patch ────────────────────────────────────────────────────────────
|
|
514
446
|
|
|
515
447
|
const appPatch = async (patchCreate) => {
|
|
516
448
|
const name = patchCreate.type
|
|
@@ -528,17 +460,7 @@ export const makeChatsSocket = (config) => {
|
|
|
528
460
|
initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState()
|
|
529
461
|
encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey)
|
|
530
462
|
const { patch, state } = encodeResult
|
|
531
|
-
await query({
|
|
532
|
-
tag: 'iq',
|
|
533
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:sync:app:state' },
|
|
534
|
-
content: [{
|
|
535
|
-
tag: 'sync', attrs: {}, content: [{
|
|
536
|
-
tag: 'collection',
|
|
537
|
-
attrs: { name, version: (state.version - 1).toString(), return_snapshot: 'false' },
|
|
538
|
-
content: [{ tag: 'patch', attrs: {}, content: proto.SyncdPatch.encode(patch).finish() }]
|
|
539
|
-
}]
|
|
540
|
-
}]
|
|
541
|
-
})
|
|
463
|
+
await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:sync:app:state' }, content: [{ tag: 'sync', attrs: {}, content: [{ tag: 'collection', attrs: { name, version: (state.version - 1).toString(), return_snapshot: 'false' }, content: [{ tag: 'patch', attrs: {}, content: proto.SyncdPatch.encode(patch).finish() }] }] }] })
|
|
542
464
|
await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
|
|
543
465
|
}, authState?.creds?.me?.id || 'app-patch')
|
|
544
466
|
})
|
|
@@ -550,14 +472,11 @@ export const makeChatsSocket = (config) => {
|
|
|
550
472
|
}
|
|
551
473
|
}
|
|
552
474
|
|
|
553
|
-
// ─── Props
|
|
475
|
+
// ─── Props ────────────────────────────────────────────────────────────────
|
|
554
476
|
|
|
477
|
+
/** fetch AB props */
|
|
555
478
|
const fetchProps = async () => {
|
|
556
|
-
const resultNode = await query({
|
|
557
|
-
tag: 'iq',
|
|
558
|
-
attrs: { to: S_WHATSAPP_NET, xmlns: 'w', type: 'get' },
|
|
559
|
-
content: [{ tag: 'props', attrs: { protocol: '2', hash: authState?.creds?.lastPropHash || '' } }]
|
|
560
|
-
})
|
|
479
|
+
const resultNode = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'abt', type: 'get' }, content: [{ tag: 'props', attrs: { protocol: '1', ...(authState?.creds?.lastPropHash ? { hash: authState.creds.lastPropHash } : {}) } }] })
|
|
561
480
|
const propsNode = getBinaryNodeChild(resultNode, 'props')
|
|
562
481
|
let props = {}
|
|
563
482
|
if (propsNode) {
|
|
@@ -567,41 +486,63 @@ export const makeChatsSocket = (config) => {
|
|
|
567
486
|
}
|
|
568
487
|
props = reduceBinaryNodeToDictionary(propsNode, 'prop')
|
|
569
488
|
}
|
|
570
|
-
|
|
489
|
+
const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages']
|
|
490
|
+
if (privacyTokenProp !== undefined) serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1'
|
|
491
|
+
const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq']
|
|
492
|
+
if (profilePicProp !== undefined) serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1'
|
|
493
|
+
const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid']
|
|
494
|
+
if (lidIssueProp !== undefined) serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1'
|
|
495
|
+
logger.debug({ serverProps }, 'fetched props')
|
|
571
496
|
return props
|
|
572
497
|
}
|
|
573
498
|
|
|
574
|
-
// ─── Chat modification helpers
|
|
499
|
+
// ─── Chat modification helpers ────────────────────────────────────────────
|
|
575
500
|
|
|
501
|
+
/**
|
|
502
|
+
* modify a chat -- mark unread, read etc.
|
|
503
|
+
* lastMessages must be sorted in reverse chronologically
|
|
504
|
+
* requires the last messages till the last message received; required for archive & unread
|
|
505
|
+
*/
|
|
576
506
|
const chatModify = (mod, jid) => appPatch(chatModificationToAppPatch(mod, jid))
|
|
507
|
+
/** Enable/Disable link preview privacy, not related to baileys link preview generation */
|
|
577
508
|
const updateDisableLinkPreviewsPrivacy = (isPreviewsDisabled) => chatModify({ disableLinkPreviews: { isPreviewsDisabled } }, '')
|
|
509
|
+
/** Star or Unstar a message */
|
|
578
510
|
const star = (jid, messages, star) => chatModify({ star: { messages, star } }, jid)
|
|
511
|
+
/** Add or Edit Contact */
|
|
579
512
|
const addOrEditContact = (jid, contact) => chatModify({ contact }, jid)
|
|
513
|
+
/** Remove Contact */
|
|
580
514
|
const removeContact = (jid) => chatModify({ contact: null }, jid)
|
|
515
|
+
/** Adds label */
|
|
581
516
|
const addLabel = (jid, labels) => chatModify({ addLabel: { ...labels } }, jid)
|
|
517
|
+
/** Adds label for the chats */
|
|
582
518
|
const addChatLabel = (jid, labelId) => chatModify({ addChatLabel: { labelId } }, jid)
|
|
519
|
+
/** Removes label for the chat */
|
|
583
520
|
const removeChatLabel = (jid, labelId) => chatModify({ removeChatLabel: { labelId } }, jid)
|
|
521
|
+
/** Adds label for the message */
|
|
584
522
|
const addMessageLabel = (jid, messageId, labelId) => chatModify({ addMessageLabel: { messageId, labelId } }, jid)
|
|
523
|
+
/** Removes label for the message */
|
|
585
524
|
const removeMessageLabel = (jid, messageId, labelId) => chatModify({ removeMessageLabel: { messageId, labelId } }, jid)
|
|
525
|
+
/** Add or Edit Quick Reply */
|
|
586
526
|
const addOrEditQuickReply = (quickReply) => chatModify({ quickReply }, '')
|
|
527
|
+
/** Remove Quick Reply */
|
|
587
528
|
const removeQuickReply = (timestamp) => chatModify({ quickReply: { timestamp, deleted: true } }, '')
|
|
588
529
|
|
|
589
|
-
// ─── Call link
|
|
530
|
+
// ─── Call link ────────────────────────────────────────────────────────────
|
|
590
531
|
|
|
591
532
|
const createCallLink = async (type, event, timeoutMs) => {
|
|
592
|
-
const result = await query({
|
|
593
|
-
tag: 'call',
|
|
594
|
-
attrs: { id: generateMessageTag(), to: '@call' },
|
|
595
|
-
content: [{ tag: 'link_create', attrs: { media: type }, content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined }]
|
|
596
|
-
}, timeoutMs)
|
|
533
|
+
const result = await query({ tag: 'call', attrs: { id: generateMessageTag(), to: '@call' }, content: [{ tag: 'link_create', attrs: { media: type }, content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined }] }, timeoutMs)
|
|
597
534
|
return getBinaryNodeChild(result, 'link_create')?.attrs?.token
|
|
598
535
|
}
|
|
599
536
|
|
|
600
|
-
// ─── Init
|
|
537
|
+
// ─── Init ─────────────────────────────────────────────────────────────────
|
|
601
538
|
|
|
539
|
+
/**
|
|
540
|
+
* queries need to be fired on connection open
|
|
541
|
+
* help ensure parity with WA Web
|
|
542
|
+
*/
|
|
602
543
|
const executeInitQueries = () => Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()])
|
|
603
544
|
|
|
604
|
-
// ─── Message upsert
|
|
545
|
+
// ─── Message upsert ───────────────────────────────────────────────────────
|
|
605
546
|
|
|
606
547
|
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
|
|
607
548
|
ev.emit('messages.upsert', { messages: [msg], type })
|
|
@@ -609,37 +550,27 @@ export const makeChatsSocket = (config) => {
|
|
|
609
550
|
if (msg.pushName) {
|
|
610
551
|
let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
|
|
611
552
|
jid = jidNormalizedUser(jid)
|
|
612
|
-
if (!msg.key.fromMe) {
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
|
|
616
|
-
ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
|
|
617
|
-
}
|
|
553
|
+
if (!msg.key.fromMe) ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
|
|
554
|
+
if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
|
|
618
555
|
}
|
|
619
556
|
|
|
620
557
|
const historyMsg = getHistoryMsg(msg.message)
|
|
621
558
|
const shouldProcessHistoryMsg = historyMsg
|
|
622
|
-
? !!historyMsg.mediaKey?.length
|
|
623
|
-
&& shouldSyncHistoryMessage(historyMsg)
|
|
624
|
-
&& PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
559
|
+
? !!historyMsg.mediaKey?.length && shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
625
560
|
: false
|
|
626
561
|
|
|
627
|
-
// History sync progress tracking
|
|
628
562
|
if (historyMsg && shouldProcessHistoryMsg) {
|
|
629
563
|
const syncType = historyMsg.syncType
|
|
630
|
-
|
|
631
564
|
if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP && !historySyncStatus.initialBootstrapComplete) {
|
|
632
565
|
historySyncStatus.initialBootstrapComplete = true
|
|
633
566
|
ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
|
|
634
567
|
}
|
|
635
|
-
|
|
636
568
|
if (syncType === proto.HistorySync.HistorySyncType.RECENT && historyMsg.progress === 100 && !historySyncStatus.recentSyncComplete) {
|
|
637
569
|
historySyncStatus.recentSyncComplete = true
|
|
638
570
|
clearTimeout(historySyncPausedTimeout)
|
|
639
571
|
historySyncPausedTimeout = undefined
|
|
640
572
|
ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
|
|
641
573
|
}
|
|
642
|
-
|
|
643
574
|
if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
|
|
644
575
|
clearTimeout(historySyncPausedTimeout)
|
|
645
576
|
historySyncPausedTimeout = setTimeout(() => {
|
|
@@ -652,12 +583,8 @@ export const makeChatsSocket = (config) => {
|
|
|
652
583
|
}
|
|
653
584
|
}
|
|
654
585
|
|
|
655
|
-
// SyncState machine
|
|
656
586
|
if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
|
|
657
|
-
if (awaitingSyncTimeout) {
|
|
658
|
-
clearTimeout(awaitingSyncTimeout)
|
|
659
|
-
awaitingSyncTimeout = undefined
|
|
660
|
-
}
|
|
587
|
+
if (awaitingSyncTimeout) { clearTimeout(awaitingSyncTimeout); awaitingSyncTimeout = undefined }
|
|
661
588
|
if (shouldProcessHistoryMsg) {
|
|
662
589
|
syncState = SyncState.Syncing
|
|
663
590
|
logger.info('Transitioned to Syncing state')
|
|
@@ -683,17 +610,7 @@ export const makeChatsSocket = (config) => {
|
|
|
683
610
|
|
|
684
611
|
await Promise.all([
|
|
685
612
|
(async () => { if (shouldProcessHistoryMsg) await doAppStateSync() })(),
|
|
686
|
-
processMessage(msg, {
|
|
687
|
-
signalRepository,
|
|
688
|
-
shouldProcessHistoryMsg,
|
|
689
|
-
placeholderResendCache,
|
|
690
|
-
ev,
|
|
691
|
-
creds: authState.creds,
|
|
692
|
-
keyStore: authState.keys,
|
|
693
|
-
logger,
|
|
694
|
-
options: config.options,
|
|
695
|
-
getMessage
|
|
696
|
-
})
|
|
613
|
+
processMessage(msg, { signalRepository, shouldProcessHistoryMsg, placeholderResendCache, ev, creds: authState.creds, keyStore: authState.keys, logger, options: config.options, getMessage })
|
|
697
614
|
])
|
|
698
615
|
|
|
699
616
|
if (msg.message?.protocolMessage?.appStateSyncKeyShare) {
|
|
@@ -709,7 +626,7 @@ export const makeChatsSocket = (config) => {
|
|
|
709
626
|
}
|
|
710
627
|
})
|
|
711
628
|
|
|
712
|
-
// ─── WS handlers
|
|
629
|
+
// ─── WS handlers ──────────────────────────────────────────────────────────
|
|
713
630
|
|
|
714
631
|
ws.on('CB:presence', handlePresenceUpdate)
|
|
715
632
|
ws.on('CB:chatstate', handlePresenceUpdate)
|
|
@@ -734,7 +651,7 @@ export const makeChatsSocket = (config) => {
|
|
|
734
651
|
}
|
|
735
652
|
})
|
|
736
653
|
|
|
737
|
-
// ─── Event listeners
|
|
654
|
+
// ─── Event listeners ──────────────────────────────────────────────────────
|
|
738
655
|
|
|
739
656
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
|
740
657
|
if (connection === 'close') {
|
|
@@ -759,9 +676,7 @@ export const makeChatsSocket = (config) => {
|
|
|
759
676
|
logger.info('Connection is now AwaitingInitialSync, buffering events')
|
|
760
677
|
ev.buffer()
|
|
761
678
|
|
|
762
|
-
const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({
|
|
763
|
-
syncType: proto.HistorySync.HistorySyncType.RECENT
|
|
764
|
-
}))
|
|
679
|
+
const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({ syncType: proto.HistorySync.HistorySyncType.RECENT }))
|
|
765
680
|
|
|
766
681
|
if (!willSyncHistory) {
|
|
767
682
|
logger.info('History sync is disabled by config, not waiting for notification. Transitioning to Online.')
|
|
@@ -770,7 +685,6 @@ export const makeChatsSocket = (config) => {
|
|
|
770
685
|
return
|
|
771
686
|
}
|
|
772
687
|
|
|
773
|
-
// On reconnection the server won't push history sync again — skip the wait
|
|
774
688
|
if (authState.creds.accountSyncCounter > 0) {
|
|
775
689
|
logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
|
|
776
690
|
syncState = SyncState.Online
|
|
@@ -791,13 +705,9 @@ export const makeChatsSocket = (config) => {
|
|
|
791
705
|
}, 20_000)
|
|
792
706
|
})
|
|
793
707
|
|
|
794
|
-
// Re-sync collections that were parked due to a missing app state key
|
|
795
708
|
ev.on('creds.update', ({ myAppStateKeyId }) => {
|
|
796
709
|
if (!myAppStateKeyId || blockedCollections.size === 0) return
|
|
797
|
-
if (syncState === SyncState.Syncing) {
|
|
798
|
-
blockedCollections.clear()
|
|
799
|
-
return
|
|
800
|
-
}
|
|
710
|
+
if (syncState === SyncState.Syncing) { blockedCollections.clear(); return }
|
|
801
711
|
const collections = [...blockedCollections]
|
|
802
712
|
blockedCollections.clear()
|
|
803
713
|
logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections')
|
|
@@ -812,10 +722,18 @@ export const makeChatsSocket = (config) => {
|
|
|
812
722
|
}
|
|
813
723
|
})
|
|
814
724
|
|
|
815
|
-
|
|
725
|
+
registerSocketEndHandler(() => {
|
|
726
|
+
if (awaitingSyncTimeout) { clearTimeout(awaitingSyncTimeout); awaitingSyncTimeout = undefined }
|
|
727
|
+
if (!config.placeholderResendCache && placeholderResendCache.close) placeholderResendCache.close()
|
|
728
|
+
syncState = SyncState.Connecting
|
|
729
|
+
privacySettings = undefined
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
// ─── Return ───────────────────────────────────────────────────────────────
|
|
816
733
|
|
|
817
734
|
return {
|
|
818
735
|
...sock,
|
|
736
|
+
serverProps,
|
|
819
737
|
createCallLink,
|
|
820
738
|
getBotListV2,
|
|
821
739
|
processingMutex,
|
|
@@ -853,9 +771,9 @@ export const makeChatsSocket = (config) => {
|
|
|
853
771
|
cleanDirtyBits,
|
|
854
772
|
addOrEditContact,
|
|
855
773
|
removeContact,
|
|
774
|
+
placeholderResendCache,
|
|
856
775
|
addLabel,
|
|
857
776
|
onWhatsApp,
|
|
858
|
-
checkStatusWA,
|
|
859
777
|
addChatLabel,
|
|
860
778
|
removeChatLabel,
|
|
861
779
|
addMessageLabel,
|
|
@@ -865,6 +783,7 @@ export const makeChatsSocket = (config) => {
|
|
|
865
783
|
removeQuickReply
|
|
866
784
|
}
|
|
867
785
|
}
|
|
786
|
+
|
|
868
787
|
export const checkStatusWA = async (phoneNumber) => {
|
|
869
788
|
if (!phoneNumber) throw new Error('Please provide a phone number')
|
|
870
789
|
|
|
@@ -883,40 +802,29 @@ export const checkStatusWA = async (phoneNumber) => {
|
|
|
883
802
|
|
|
884
803
|
const state = {
|
|
885
804
|
creds: initAuthCreds(),
|
|
886
|
-
keys: {
|
|
887
|
-
get: async () => ({}),
|
|
888
|
-
set: async () => { },
|
|
889
|
-
transaction: async (fn) => fn(),
|
|
890
|
-
}
|
|
805
|
+
keys: { get: async () => ({}), set: async () => { }, transaction: async (fn) => fn() }
|
|
891
806
|
}
|
|
892
807
|
|
|
893
|
-
const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({
|
|
894
|
-
number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo
|
|
895
|
-
})
|
|
808
|
+
const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({ number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo })
|
|
896
809
|
|
|
897
810
|
try {
|
|
898
|
-
await mobileRegisterExists({
|
|
899
|
-
...state.creds,
|
|
900
|
-
phoneNumberCountryCode: countryCode,
|
|
901
|
-
phoneNumberNationalNumber: nationalNumber,
|
|
902
|
-
})
|
|
811
|
+
await mobileRegisterExists({ ...state.creds, phoneNumberCountryCode: countryCode, phoneNumberNationalNumber: nationalNumber })
|
|
903
812
|
return build('active', false, false)
|
|
904
813
|
} catch (err) {
|
|
905
814
|
if (err?.appeal_token) {
|
|
906
815
|
const banDetails = await getBanDetails(err.appeal_token)
|
|
907
816
|
const appealStatus = banDetails?.status || null
|
|
908
817
|
const banType = appealStatus === 'BANNED' ? 'permanent' : 'temporary'
|
|
909
|
-
|
|
910
818
|
return build('banned', true, false, {
|
|
911
819
|
banType,
|
|
912
820
|
violationType: err.violation_type || null,
|
|
913
821
|
violationReason: err.violation_type ? `Type ${err.violation_type}` : 'Unknown',
|
|
914
|
-
canAppeal: banType !== 'permanent',
|
|
822
|
+
canAppeal: banType !== 'permanent',
|
|
915
823
|
appealToken: err.appeal_token,
|
|
916
824
|
banTime: banDetails?.ban_time || null,
|
|
917
825
|
banDate: banDetails?.ban_time ? new Date(banDetails.ban_time * 1000).toISOString() : null,
|
|
918
826
|
appealStatus,
|
|
919
|
-
appealCreatedAt: banDetails?.appeal_creation_time ? new Date(banDetails.appeal_creation_time * 1000).toISOString() : null
|
|
827
|
+
appealCreatedAt: banDetails?.appeal_creation_time ? new Date(banDetails.appeal_creation_time * 1000).toISOString() : null
|
|
920
828
|
})
|
|
921
829
|
}
|
|
922
830
|
if (err?.custom_block_screen) return build('blocked', false, true)
|
|
@@ -924,4 +832,4 @@ export const checkStatusWA = async (phoneNumber) => {
|
|
|
924
832
|
if (err?.reason === 'temporarily_unavailable') return build('rate_limited', false, false)
|
|
925
833
|
return build('error', false, false)
|
|
926
834
|
}
|
|
927
|
-
}
|
|
835
|
+
}
|