@nexustechpro/baileys 2.0.6 → 2.1.2
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 +72 -1
- package/lib/Defaults/index.js +3 -2
- package/lib/Signal/libsignal.js +552 -358
- package/lib/Socket/chats.js +192 -273
- package/lib/Socket/messages-recv.js +4 -2
- package/lib/Socket/messages-send.js +42 -18
- package/lib/Socket/newsletter.js +87 -36
- package/lib/Socket/nexus-handler.js +44 -42
- package/lib/Socket/socket.js +23 -10
- package/lib/Store/make-in-memory-store.js +29 -20
- package/lib/Utils/auth-utils.js +2 -2
- package/lib/Utils/chat-utils.js +319 -620
- package/lib/Utils/decode-wa-message.js +11 -26
- package/lib/Utils/key-store.js +1 -1
- package/lib/Utils/link-preview.js +134 -71
- package/lib/Utils/messages-media.js +97 -26
- package/lib/Utils/messages.js +723 -820
- package/lib/Utils/use-multi-file-auth-state.js +183 -90
- package/lib/index.js +1 -1
- package/package.json +10 -8
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,91 +135,84 @@ export const makeChatsSocket = (config) => {
|
|
|
180
135
|
const onWhatsApp = async (...jids) => {
|
|
181
136
|
const usyncQuery = new USyncQuery()
|
|
182
137
|
let contactEnabled = false
|
|
183
|
-
|
|
184
|
-
for (const jid of jids) {
|
|
138
|
+
for (let jid of jids) {
|
|
185
139
|
if (isLidUser(jid)) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
140
|
+
const pn = await signalRepository.lidMapping.getPNForLID(jid)
|
|
141
|
+
if (pn) {
|
|
142
|
+
jid = pn
|
|
143
|
+
} else {
|
|
144
|
+
if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
|
|
145
|
+
usyncQuery.withUser(new USyncUser().withId(jid))
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
192
148
|
}
|
|
149
|
+
if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
|
|
193
150
|
const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}`
|
|
194
151
|
usyncQuery.withUser(new USyncUser().withPhone(phone))
|
|
195
152
|
}
|
|
196
|
-
|
|
197
153
|
if (usyncQuery.users.length === 0) return []
|
|
198
|
-
|
|
199
154
|
const results = await sock.executeUSyncQuery(usyncQuery)
|
|
200
155
|
if (!results) return []
|
|
201
|
-
|
|
202
156
|
return Promise.all(
|
|
203
157
|
results.list
|
|
204
|
-
.filter(a => a.contact === true)
|
|
158
|
+
.filter(a => a.contact === true && a.id && a.id !== 'undefined')
|
|
205
159
|
.map(async ({ id, lid }) => {
|
|
206
160
|
try {
|
|
207
161
|
const businessProfile = await getBusinessProfile(id)
|
|
208
162
|
const isBusiness = businessProfile && Object.keys(businessProfile).length > 0
|
|
209
163
|
if (isBusiness) {
|
|
210
164
|
const { wid, ...businessInfo } = businessProfile
|
|
211
|
-
return { jid: id, exists: true, lid, status: 'business', businessInfo }
|
|
165
|
+
return { jid: id, exists: true, lid: lid || id, status: 'business', businessInfo }
|
|
212
166
|
}
|
|
213
|
-
return { jid: id, exists: true, lid, status: 'regular' }
|
|
167
|
+
return { jid: id, exists: true, lid: lid || id, status: 'regular' }
|
|
214
168
|
} catch (error) {
|
|
215
|
-
return { jid: id, exists: true, lid, status: 'error', error: error?.message }
|
|
169
|
+
return { jid: id, exists: true, lid: lid || id, status: 'error', error: error?.message }
|
|
216
170
|
}
|
|
217
171
|
})
|
|
218
172
|
)
|
|
219
173
|
}
|
|
220
174
|
|
|
221
|
-
// ─── Profile
|
|
175
|
+
// ─── Profile ──────────────────────────────────────────────────────────────
|
|
222
176
|
|
|
223
|
-
|
|
177
|
+
/** update the profile picture for yourself or a group */
|
|
178
|
+
const updateProfilePicture = async (jid, content, dimensions) => {
|
|
224
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')
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await query({
|
|
229
|
-
tag: 'iq',
|
|
230
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) },
|
|
231
|
-
content: [{ tag: 'picture', attrs: { type: 'image' }, content: img }]
|
|
232
|
-
})
|
|
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 }] })
|
|
233
183
|
}
|
|
234
184
|
|
|
185
|
+
/** remove the profile picture for yourself or a group */
|
|
235
186
|
const removeProfilePicture = async (jid) => {
|
|
236
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')
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await query({
|
|
240
|
-
tag: 'iq',
|
|
241
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) }
|
|
242
|
-
})
|
|
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 } : {}) } })
|
|
243
190
|
}
|
|
244
191
|
|
|
192
|
+
/** update the profile status for yourself */
|
|
245
193
|
const updateProfileStatus = async (status) => {
|
|
246
|
-
await query({
|
|
247
|
-
tag: 'iq',
|
|
248
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' },
|
|
249
|
-
content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }]
|
|
250
|
-
})
|
|
194
|
+
await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' }, content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }] })
|
|
251
195
|
}
|
|
252
196
|
|
|
253
197
|
const updateProfileName = (name) => chatModify({ pushNameSetting: name }, '')
|
|
254
198
|
|
|
255
199
|
const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
|
|
256
|
-
|
|
257
|
-
const cacheKey = `${
|
|
200
|
+
const normalizedJid = jidNormalizedUser(jid)
|
|
201
|
+
const cacheKey = `${normalizedJid}:${type}`
|
|
258
202
|
const cached = profilePictureUrlCache.get(cacheKey)
|
|
259
203
|
if (typeof cached !== 'undefined') return cached || undefined
|
|
260
204
|
const inFlight = inFlightProfilePictureUrl.get(cacheKey)
|
|
261
205
|
if (inFlight) return inFlight
|
|
262
206
|
const fetchPromise = (async () => {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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)
|
|
268
216
|
const child = getBinaryNodeChild(result, 'picture')
|
|
269
217
|
const url = child?.attrs?.url
|
|
270
218
|
profilePictureUrlCache.set(cacheKey, url || null)
|
|
@@ -278,7 +226,7 @@ export const makeChatsSocket = (config) => {
|
|
|
278
226
|
}
|
|
279
227
|
}
|
|
280
228
|
|
|
281
|
-
// ─── Blocklist
|
|
229
|
+
// ─── Blocklist ────────────────────────────────────────────────────────────
|
|
282
230
|
|
|
283
231
|
const fetchBlocklist = async () => {
|
|
284
232
|
const result = await query({ tag: 'iq', attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'get' } })
|
|
@@ -287,21 +235,36 @@ export const makeChatsSocket = (config) => {
|
|
|
287
235
|
}
|
|
288
236
|
|
|
289
237
|
const updateBlockStatus = async (jid, action) => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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 }] })
|
|
295
262
|
}
|
|
296
263
|
|
|
297
|
-
// ─── Business
|
|
264
|
+
// ─── Business ─────────────────────────────────────────────────────────────
|
|
298
265
|
|
|
299
266
|
const getBusinessProfile = async (jid) => {
|
|
300
|
-
const results = await query({
|
|
301
|
-
tag: 'iq',
|
|
302
|
-
attrs: { to: 's.whatsapp.net', xmlns: 'w:biz', type: 'get' },
|
|
303
|
-
content: [{ tag: 'business_profile', attrs: { v: '244' }, content: [{ tag: 'profile', attrs: { jid } }] }]
|
|
304
|
-
})
|
|
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 } }] }] })
|
|
305
268
|
const profileNode = getBinaryNodeChild(results, 'business_profile')
|
|
306
269
|
const profiles = getBinaryNodeChild(profileNode, 'profile')
|
|
307
270
|
if (profiles) {
|
|
@@ -325,15 +288,11 @@ export const makeChatsSocket = (config) => {
|
|
|
325
288
|
}
|
|
326
289
|
}
|
|
327
290
|
|
|
328
|
-
// ─── App state
|
|
291
|
+
// ─── App state ────────────────────────────────────────────────────────────
|
|
329
292
|
|
|
330
293
|
const cleanDirtyBits = async (type, fromTimestamp) => {
|
|
331
294
|
logger.info({ fromTimestamp }, 'clean dirty bits ' + type)
|
|
332
|
-
await sendNode({
|
|
333
|
-
tag: 'iq',
|
|
334
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', id: generateMessageTag() },
|
|
335
|
-
content: [{ tag: 'clean', attrs: { type, ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } }]
|
|
336
|
-
})
|
|
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) } }] })
|
|
337
296
|
}
|
|
338
297
|
|
|
339
298
|
const newAppStateChunkHandler = (isInitialSync) => ({
|
|
@@ -347,7 +306,6 @@ export const makeChatsSocket = (config) => {
|
|
|
347
306
|
const collectionsToSync = collections.filter(name => (appStateResyncCooldown.get(name) || 0) <= now)
|
|
348
307
|
if (!collectionsToSync.length) return
|
|
349
308
|
|
|
350
|
-
// Per-invocation cache to avoid redundant key fetches within a single resync
|
|
351
309
|
const appStateSyncKeyCache = new Map()
|
|
352
310
|
const getCachedAppStateSyncKey = async (keyId) => {
|
|
353
311
|
if (appStateSyncKeyCache.has(keyId)) return appStateSyncKeyCache.get(keyId) ?? undefined
|
|
@@ -378,24 +336,15 @@ export const makeChatsSocket = (config) => {
|
|
|
378
336
|
state = newLTHashState()
|
|
379
337
|
}
|
|
380
338
|
states[name] = state
|
|
381
|
-
|
|
382
339
|
const shouldForceSnapshot = forceSnapshotCollections.has(name)
|
|
383
340
|
if (shouldForceSnapshot) forceSnapshotCollections.delete(name)
|
|
384
|
-
|
|
385
341
|
logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`)
|
|
386
|
-
nodes.push({
|
|
387
|
-
tag: 'collection',
|
|
388
|
-
attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() }
|
|
389
|
-
})
|
|
342
|
+
nodes.push({ tag: 'collection', attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() } })
|
|
390
343
|
}
|
|
391
344
|
|
|
392
|
-
const result = await query({
|
|
393
|
-
tag: 'iq',
|
|
394
|
-
attrs: { to: S_WHATSAPP_NET, xmlns: 'w:sync:app:state', type: 'set' },
|
|
395
|
-
content: [{ tag: 'sync', attrs: {}, content: nodes }]
|
|
396
|
-
})
|
|
397
|
-
|
|
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 }] })
|
|
398
346
|
const decoded = await extractSyncdPatches(result, config?.options)
|
|
347
|
+
|
|
399
348
|
for (const key in decoded) {
|
|
400
349
|
const name = key
|
|
401
350
|
const { patches, hasMorePatches, snapshot } = decoded[name]
|
|
@@ -421,25 +370,20 @@ export const makeChatsSocket = (config) => {
|
|
|
421
370
|
}
|
|
422
371
|
} catch (error) {
|
|
423
372
|
attemptsMap[name] = (attemptsMap[name] || 0) + 1
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
logger.info({ name, error: error.stack, attempt: attemptsMap[name] }, `failed to sync ${name} from v${states[name].version}`)
|
|
428
|
-
if (!isMissingKey) {
|
|
429
|
-
await authState.keys.set({ 'app-state-sync-version': { [name]: null } })
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (isMissingKey) appStateResyncCooldown.set(name, Date.now() + APP_STATE_RESYNC_COOLDOWN_MS)
|
|
433
|
-
|
|
434
|
-
if (isMissingKey && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
|
|
435
|
-
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`)
|
|
436
376
|
blockedCollections.add(name)
|
|
437
377
|
collectionsToHandle.delete(name)
|
|
438
|
-
|
|
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`)
|
|
439
381
|
forceSnapshotCollections.add(name)
|
|
440
|
-
} else if (
|
|
382
|
+
} else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
|
|
383
|
+
logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`)
|
|
441
384
|
collectionsToHandle.delete(name)
|
|
442
385
|
} else {
|
|
386
|
+
logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`)
|
|
443
387
|
forceSnapshotCollections.add(name)
|
|
444
388
|
}
|
|
445
389
|
}
|
|
@@ -451,33 +395,33 @@ export const makeChatsSocket = (config) => {
|
|
|
451
395
|
for (const key in globalMutationMap) onMutation(globalMutationMap[key])
|
|
452
396
|
})
|
|
453
397
|
|
|
454
|
-
// ─── Presence
|
|
398
|
+
// ─── Presence ─────────────────────────────────────────────────────────────
|
|
455
399
|
|
|
456
400
|
const sendPresenceUpdate = async (type, toJid) => {
|
|
457
401
|
const me = authState.creds.me
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
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()
|
|
464
407
|
await sendNode({ tag: 'presence', attrs: { name: me.name.replace(/@/g, ''), type } })
|
|
465
408
|
} else {
|
|
466
409
|
const { server } = jidDecode(toJid)
|
|
467
410
|
const isLid = server === 'lid'
|
|
468
|
-
await sendNode({
|
|
469
|
-
tag: 'chatstate',
|
|
470
|
-
attrs: { from: isLid ? me.lid : me.id, to: toJid },
|
|
471
|
-
content: [{ tag: type === 'recording' ? 'composing' : type, attrs: type === 'recording' ? { media: 'audio' } : {} }]
|
|
472
|
-
})
|
|
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' } : {} }] })
|
|
473
412
|
}
|
|
474
413
|
}
|
|
475
414
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
+
}
|
|
481
425
|
|
|
482
426
|
const handlePresenceUpdate = ({ tag, attrs, content }) => {
|
|
483
427
|
let presence
|
|
@@ -485,10 +429,7 @@ export const makeChatsSocket = (config) => {
|
|
|
485
429
|
const participant = attrs.participant || attrs.from
|
|
486
430
|
if (shouldIgnoreJid(jid) && jid !== S_WHATSAPP_NET) return
|
|
487
431
|
if (tag === 'presence') {
|
|
488
|
-
presence = {
|
|
489
|
-
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
|
|
490
|
-
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
|
|
491
|
-
}
|
|
432
|
+
presence = { lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available', lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined, groupOnlineCount: attrs.count ? +attrs.count : undefined }
|
|
492
433
|
} else if (Array.isArray(content)) {
|
|
493
434
|
const [firstChild] = content
|
|
494
435
|
let type = firstChild.tag
|
|
@@ -501,7 +442,7 @@ export const makeChatsSocket = (config) => {
|
|
|
501
442
|
if (presence) ev.emit('presence.update', { id: jid, presences: { [participant]: presence } })
|
|
502
443
|
}
|
|
503
444
|
|
|
504
|
-
// ─── App patch
|
|
445
|
+
// ─── App patch ────────────────────────────────────────────────────────────
|
|
505
446
|
|
|
506
447
|
const appPatch = async (patchCreate) => {
|
|
507
448
|
const name = patchCreate.type
|
|
@@ -519,17 +460,7 @@ export const makeChatsSocket = (config) => {
|
|
|
519
460
|
initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState()
|
|
520
461
|
encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey)
|
|
521
462
|
const { patch, state } = encodeResult
|
|
522
|
-
await query({
|
|
523
|
-
tag: 'iq',
|
|
524
|
-
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:sync:app:state' },
|
|
525
|
-
content: [{
|
|
526
|
-
tag: 'sync', attrs: {}, content: [{
|
|
527
|
-
tag: 'collection',
|
|
528
|
-
attrs: { name, version: (state.version - 1).toString(), return_snapshot: 'false' },
|
|
529
|
-
content: [{ tag: 'patch', attrs: {}, content: proto.SyncdPatch.encode(patch).finish() }]
|
|
530
|
-
}]
|
|
531
|
-
}]
|
|
532
|
-
})
|
|
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() }] }] }] })
|
|
533
464
|
await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
|
|
534
465
|
}, authState?.creds?.me?.id || 'app-patch')
|
|
535
466
|
})
|
|
@@ -541,14 +472,11 @@ export const makeChatsSocket = (config) => {
|
|
|
541
472
|
}
|
|
542
473
|
}
|
|
543
474
|
|
|
544
|
-
// ─── Props
|
|
475
|
+
// ─── Props ────────────────────────────────────────────────────────────────
|
|
545
476
|
|
|
477
|
+
/** fetch AB props */
|
|
546
478
|
const fetchProps = async () => {
|
|
547
|
-
const resultNode = await query({
|
|
548
|
-
tag: 'iq',
|
|
549
|
-
attrs: { to: S_WHATSAPP_NET, xmlns: 'w', type: 'get' },
|
|
550
|
-
content: [{ tag: 'props', attrs: { protocol: '2', hash: authState?.creds?.lastPropHash || '' } }]
|
|
551
|
-
})
|
|
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 } : {}) } }] })
|
|
552
480
|
const propsNode = getBinaryNodeChild(resultNode, 'props')
|
|
553
481
|
let props = {}
|
|
554
482
|
if (propsNode) {
|
|
@@ -558,41 +486,63 @@ export const makeChatsSocket = (config) => {
|
|
|
558
486
|
}
|
|
559
487
|
props = reduceBinaryNodeToDictionary(propsNode, 'prop')
|
|
560
488
|
}
|
|
561
|
-
|
|
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')
|
|
562
496
|
return props
|
|
563
497
|
}
|
|
564
498
|
|
|
565
|
-
// ─── Chat modification helpers
|
|
499
|
+
// ─── Chat modification helpers ────────────────────────────────────────────
|
|
566
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
|
+
*/
|
|
567
506
|
const chatModify = (mod, jid) => appPatch(chatModificationToAppPatch(mod, jid))
|
|
507
|
+
/** Enable/Disable link preview privacy, not related to baileys link preview generation */
|
|
568
508
|
const updateDisableLinkPreviewsPrivacy = (isPreviewsDisabled) => chatModify({ disableLinkPreviews: { isPreviewsDisabled } }, '')
|
|
509
|
+
/** Star or Unstar a message */
|
|
569
510
|
const star = (jid, messages, star) => chatModify({ star: { messages, star } }, jid)
|
|
511
|
+
/** Add or Edit Contact */
|
|
570
512
|
const addOrEditContact = (jid, contact) => chatModify({ contact }, jid)
|
|
513
|
+
/** Remove Contact */
|
|
571
514
|
const removeContact = (jid) => chatModify({ contact: null }, jid)
|
|
515
|
+
/** Adds label */
|
|
572
516
|
const addLabel = (jid, labels) => chatModify({ addLabel: { ...labels } }, jid)
|
|
517
|
+
/** Adds label for the chats */
|
|
573
518
|
const addChatLabel = (jid, labelId) => chatModify({ addChatLabel: { labelId } }, jid)
|
|
519
|
+
/** Removes label for the chat */
|
|
574
520
|
const removeChatLabel = (jid, labelId) => chatModify({ removeChatLabel: { labelId } }, jid)
|
|
521
|
+
/** Adds label for the message */
|
|
575
522
|
const addMessageLabel = (jid, messageId, labelId) => chatModify({ addMessageLabel: { messageId, labelId } }, jid)
|
|
523
|
+
/** Removes label for the message */
|
|
576
524
|
const removeMessageLabel = (jid, messageId, labelId) => chatModify({ removeMessageLabel: { messageId, labelId } }, jid)
|
|
525
|
+
/** Add or Edit Quick Reply */
|
|
577
526
|
const addOrEditQuickReply = (quickReply) => chatModify({ quickReply }, '')
|
|
527
|
+
/** Remove Quick Reply */
|
|
578
528
|
const removeQuickReply = (timestamp) => chatModify({ quickReply: { timestamp, deleted: true } }, '')
|
|
579
529
|
|
|
580
|
-
// ─── Call link
|
|
530
|
+
// ─── Call link ────────────────────────────────────────────────────────────
|
|
581
531
|
|
|
582
532
|
const createCallLink = async (type, event, timeoutMs) => {
|
|
583
|
-
const result = await query({
|
|
584
|
-
tag: 'call',
|
|
585
|
-
attrs: { id: generateMessageTag(), to: '@call' },
|
|
586
|
-
content: [{ tag: 'link_create', attrs: { media: type }, content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined }]
|
|
587
|
-
}, 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)
|
|
588
534
|
return getBinaryNodeChild(result, 'link_create')?.attrs?.token
|
|
589
535
|
}
|
|
590
536
|
|
|
591
|
-
// ─── Init
|
|
537
|
+
// ─── Init ─────────────────────────────────────────────────────────────────
|
|
592
538
|
|
|
539
|
+
/**
|
|
540
|
+
* queries need to be fired on connection open
|
|
541
|
+
* help ensure parity with WA Web
|
|
542
|
+
*/
|
|
593
543
|
const executeInitQueries = () => Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()])
|
|
594
544
|
|
|
595
|
-
// ─── Message upsert
|
|
545
|
+
// ─── Message upsert ───────────────────────────────────────────────────────
|
|
596
546
|
|
|
597
547
|
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
|
|
598
548
|
ev.emit('messages.upsert', { messages: [msg], type })
|
|
@@ -600,35 +550,27 @@ export const makeChatsSocket = (config) => {
|
|
|
600
550
|
if (msg.pushName) {
|
|
601
551
|
let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
|
|
602
552
|
jid = jidNormalizedUser(jid)
|
|
603
|
-
if (!msg.key.fromMe) {
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
|
|
607
|
-
ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
|
|
608
|
-
}
|
|
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 } })
|
|
609
555
|
}
|
|
610
556
|
|
|
611
557
|
const historyMsg = getHistoryMsg(msg.message)
|
|
612
558
|
const shouldProcessHistoryMsg = historyMsg
|
|
613
|
-
? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
559
|
+
? !!historyMsg.mediaKey?.length && shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
614
560
|
: false
|
|
615
561
|
|
|
616
|
-
// History sync progress tracking
|
|
617
562
|
if (historyMsg && shouldProcessHistoryMsg) {
|
|
618
563
|
const syncType = historyMsg.syncType
|
|
619
|
-
|
|
620
564
|
if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP && !historySyncStatus.initialBootstrapComplete) {
|
|
621
565
|
historySyncStatus.initialBootstrapComplete = true
|
|
622
566
|
ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
|
|
623
567
|
}
|
|
624
|
-
|
|
625
568
|
if (syncType === proto.HistorySync.HistorySyncType.RECENT && historyMsg.progress === 100 && !historySyncStatus.recentSyncComplete) {
|
|
626
569
|
historySyncStatus.recentSyncComplete = true
|
|
627
570
|
clearTimeout(historySyncPausedTimeout)
|
|
628
571
|
historySyncPausedTimeout = undefined
|
|
629
572
|
ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
|
|
630
573
|
}
|
|
631
|
-
|
|
632
574
|
if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
|
|
633
575
|
clearTimeout(historySyncPausedTimeout)
|
|
634
576
|
historySyncPausedTimeout = setTimeout(() => {
|
|
@@ -641,12 +583,8 @@ export const makeChatsSocket = (config) => {
|
|
|
641
583
|
}
|
|
642
584
|
}
|
|
643
585
|
|
|
644
|
-
// SyncState machine
|
|
645
586
|
if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
|
|
646
|
-
if (awaitingSyncTimeout) {
|
|
647
|
-
clearTimeout(awaitingSyncTimeout)
|
|
648
|
-
awaitingSyncTimeout = undefined
|
|
649
|
-
}
|
|
587
|
+
if (awaitingSyncTimeout) { clearTimeout(awaitingSyncTimeout); awaitingSyncTimeout = undefined }
|
|
650
588
|
if (shouldProcessHistoryMsg) {
|
|
651
589
|
syncState = SyncState.Syncing
|
|
652
590
|
logger.info('Transitioned to Syncing state')
|
|
@@ -672,17 +610,7 @@ export const makeChatsSocket = (config) => {
|
|
|
672
610
|
|
|
673
611
|
await Promise.all([
|
|
674
612
|
(async () => { if (shouldProcessHistoryMsg) await doAppStateSync() })(),
|
|
675
|
-
processMessage(msg, {
|
|
676
|
-
signalRepository,
|
|
677
|
-
shouldProcessHistoryMsg,
|
|
678
|
-
placeholderResendCache,
|
|
679
|
-
ev,
|
|
680
|
-
creds: authState.creds,
|
|
681
|
-
keyStore: authState.keys,
|
|
682
|
-
logger,
|
|
683
|
-
options: config.options,
|
|
684
|
-
getMessage
|
|
685
|
-
})
|
|
613
|
+
processMessage(msg, { signalRepository, shouldProcessHistoryMsg, placeholderResendCache, ev, creds: authState.creds, keyStore: authState.keys, logger, options: config.options, getMessage })
|
|
686
614
|
])
|
|
687
615
|
|
|
688
616
|
if (msg.message?.protocolMessage?.appStateSyncKeyShare) {
|
|
@@ -698,7 +626,7 @@ export const makeChatsSocket = (config) => {
|
|
|
698
626
|
}
|
|
699
627
|
})
|
|
700
628
|
|
|
701
|
-
// ─── WS handlers
|
|
629
|
+
// ─── WS handlers ──────────────────────────────────────────────────────────
|
|
702
630
|
|
|
703
631
|
ws.on('CB:presence', handlePresenceUpdate)
|
|
704
632
|
ws.on('CB:chatstate', handlePresenceUpdate)
|
|
@@ -723,7 +651,7 @@ export const makeChatsSocket = (config) => {
|
|
|
723
651
|
}
|
|
724
652
|
})
|
|
725
653
|
|
|
726
|
-
// ─── Event listeners
|
|
654
|
+
// ─── Event listeners ──────────────────────────────────────────────────────
|
|
727
655
|
|
|
728
656
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
|
729
657
|
if (connection === 'close') {
|
|
@@ -748,9 +676,7 @@ export const makeChatsSocket = (config) => {
|
|
|
748
676
|
logger.info('Connection is now AwaitingInitialSync, buffering events')
|
|
749
677
|
ev.buffer()
|
|
750
678
|
|
|
751
|
-
const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({
|
|
752
|
-
syncType: proto.HistorySync.HistorySyncType.RECENT
|
|
753
|
-
}))
|
|
679
|
+
const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({ syncType: proto.HistorySync.HistorySyncType.RECENT }))
|
|
754
680
|
|
|
755
681
|
if (!willSyncHistory) {
|
|
756
682
|
logger.info('History sync is disabled by config, not waiting for notification. Transitioning to Online.')
|
|
@@ -759,7 +685,6 @@ export const makeChatsSocket = (config) => {
|
|
|
759
685
|
return
|
|
760
686
|
}
|
|
761
687
|
|
|
762
|
-
// On reconnection the server won't push history sync again — skip the wait
|
|
763
688
|
if (authState.creds.accountSyncCounter > 0) {
|
|
764
689
|
logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
|
|
765
690
|
syncState = SyncState.Online
|
|
@@ -780,13 +705,9 @@ export const makeChatsSocket = (config) => {
|
|
|
780
705
|
}, 20_000)
|
|
781
706
|
})
|
|
782
707
|
|
|
783
|
-
// Re-sync collections that were parked due to a missing app state key
|
|
784
708
|
ev.on('creds.update', ({ myAppStateKeyId }) => {
|
|
785
709
|
if (!myAppStateKeyId || blockedCollections.size === 0) return
|
|
786
|
-
if (syncState === SyncState.Syncing) {
|
|
787
|
-
blockedCollections.clear()
|
|
788
|
-
return
|
|
789
|
-
}
|
|
710
|
+
if (syncState === SyncState.Syncing) { blockedCollections.clear(); return }
|
|
790
711
|
const collections = [...blockedCollections]
|
|
791
712
|
blockedCollections.clear()
|
|
792
713
|
logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections')
|
|
@@ -801,10 +722,18 @@ export const makeChatsSocket = (config) => {
|
|
|
801
722
|
}
|
|
802
723
|
})
|
|
803
724
|
|
|
804
|
-
|
|
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 ───────────────────────────────────────────────────────────────
|
|
805
733
|
|
|
806
734
|
return {
|
|
807
735
|
...sock,
|
|
736
|
+
serverProps,
|
|
808
737
|
createCallLink,
|
|
809
738
|
getBotListV2,
|
|
810
739
|
processingMutex,
|
|
@@ -842,9 +771,9 @@ export const makeChatsSocket = (config) => {
|
|
|
842
771
|
cleanDirtyBits,
|
|
843
772
|
addOrEditContact,
|
|
844
773
|
removeContact,
|
|
774
|
+
placeholderResendCache,
|
|
845
775
|
addLabel,
|
|
846
776
|
onWhatsApp,
|
|
847
|
-
checkStatusWA,
|
|
848
777
|
addChatLabel,
|
|
849
778
|
removeChatLabel,
|
|
850
779
|
addMessageLabel,
|
|
@@ -854,6 +783,7 @@ export const makeChatsSocket = (config) => {
|
|
|
854
783
|
removeQuickReply
|
|
855
784
|
}
|
|
856
785
|
}
|
|
786
|
+
|
|
857
787
|
export const checkStatusWA = async (phoneNumber) => {
|
|
858
788
|
if (!phoneNumber) throw new Error('Please provide a phone number')
|
|
859
789
|
|
|
@@ -872,40 +802,29 @@ export const checkStatusWA = async (phoneNumber) => {
|
|
|
872
802
|
|
|
873
803
|
const state = {
|
|
874
804
|
creds: initAuthCreds(),
|
|
875
|
-
keys: {
|
|
876
|
-
get: async () => ({}),
|
|
877
|
-
set: async () => { },
|
|
878
|
-
transaction: async (fn) => fn(),
|
|
879
|
-
}
|
|
805
|
+
keys: { get: async () => ({}), set: async () => { }, transaction: async (fn) => fn() }
|
|
880
806
|
}
|
|
881
807
|
|
|
882
|
-
const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({
|
|
883
|
-
number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo
|
|
884
|
-
})
|
|
808
|
+
const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({ number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo })
|
|
885
809
|
|
|
886
810
|
try {
|
|
887
|
-
await mobileRegisterExists({
|
|
888
|
-
...state.creds,
|
|
889
|
-
phoneNumberCountryCode: countryCode,
|
|
890
|
-
phoneNumberNationalNumber: nationalNumber,
|
|
891
|
-
})
|
|
811
|
+
await mobileRegisterExists({ ...state.creds, phoneNumberCountryCode: countryCode, phoneNumberNationalNumber: nationalNumber })
|
|
892
812
|
return build('active', false, false)
|
|
893
813
|
} catch (err) {
|
|
894
814
|
if (err?.appeal_token) {
|
|
895
815
|
const banDetails = await getBanDetails(err.appeal_token)
|
|
896
816
|
const appealStatus = banDetails?.status || null
|
|
897
817
|
const banType = appealStatus === 'BANNED' ? 'permanent' : 'temporary'
|
|
898
|
-
|
|
899
818
|
return build('banned', true, false, {
|
|
900
819
|
banType,
|
|
901
820
|
violationType: err.violation_type || null,
|
|
902
821
|
violationReason: err.violation_type ? `Type ${err.violation_type}` : 'Unknown',
|
|
903
|
-
canAppeal: banType !== 'permanent',
|
|
822
|
+
canAppeal: banType !== 'permanent',
|
|
904
823
|
appealToken: err.appeal_token,
|
|
905
824
|
banTime: banDetails?.ban_time || null,
|
|
906
825
|
banDate: banDetails?.ban_time ? new Date(banDetails.ban_time * 1000).toISOString() : null,
|
|
907
826
|
appealStatus,
|
|
908
|
-
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
|
|
909
828
|
})
|
|
910
829
|
}
|
|
911
830
|
if (err?.custom_block_screen) return build('blocked', false, true)
|
|
@@ -913,4 +832,4 @@ export const checkStatusWA = async (phoneNumber) => {
|
|
|
913
832
|
if (err?.reason === 'temporarily_unavailable') return build('rate_limited', false, false)
|
|
914
833
|
return build('error', false, false)
|
|
915
834
|
}
|
|
916
|
-
}
|
|
835
|
+
}
|