Skip to content

Commit a70e4da

Browse files
authored
feat(signal): better parity with whatsapp web (#1967)
1 parent 3be21f8 commit a70e4da

File tree

3 files changed

+78
-11
lines changed

3 files changed

+78
-11
lines changed

src/Socket/messages-recv.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
108108
useClones: false
109109
})
110110

111+
// Debounce identity-change session refreshes per JID to avoid bursts
112+
const identityAssertDebounce = new NodeCache<boolean>({ stdTTL: 5, useClones: false })
113+
111114
let sendActiveReceipts = false
112115

113116
const fetchMessageHistory = async (
@@ -546,8 +549,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
546549
const identityNode = getBinaryNodeChild(node, 'identity')
547550
if (identityNode) {
548551
logger.info({ jid: from }, 'identity changed')
549-
// not handling right now
550-
// signal will override new identity anyway
552+
if (identityAssertDebounce.get(from!)) {
553+
logger.debug({ jid: from }, 'skipping identity assert (debounced)')
554+
return
555+
}
556+
557+
identityAssertDebounce.set(from!, true)
558+
try {
559+
await assertSessions([from!], true)
560+
} catch (error) {
561+
logger.warn({ error, jid: from }, 'failed to assert sessions after identity change')
562+
}
551563
} else {
552564
logger.info({ node }, 'unknown encrypt notification')
553565
}
@@ -1007,7 +1019,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
10071019
}
10081020
}
10091021

1010-
await assertSessions([participant])
1022+
await assertSessions([participant], true)
10111023

10121024
if (isJidGroup(remoteJid)) {
10131025
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })

src/Socket/messages-send.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
299299
if (lidResults.length > 0) {
300300
logger.trace('Storing LID maps from device call')
301301
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid as string, pn: a.id })))
302+
303+
// Force-refresh sessions for newly mapped LIDs to align identity addressing
304+
try {
305+
const lids = lidResults.map(a => a.lid as string)
306+
if (lids.length) {
307+
await assertSessions(lids, true)
308+
}
309+
} catch (e) {
310+
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
311+
}
302312
}
303313

304314
const extracted = extractDeviceJids(
@@ -373,7 +383,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
373383
return deviceResults
374384
}
375385

376-
const assertSessions = async (jids: string[]) => {
386+
const assertSessions = async (jids: string[], force?: boolean) => {
377387
let didFetchNewSession = false
378388
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
379389
const jidsRequiringFetch: string[] = []
@@ -385,14 +395,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
385395
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
386396
const cachedSession = peerSessionsCache.get(signalId)
387397
if (cachedSession !== undefined) {
388-
if (cachedSession) {
398+
if (cachedSession && !force) {
389399
continue // Session exists in cache
390400
}
391401
} else {
392402
const sessionValidation = await signalRepository.validateSession(jid)
393403
const hasSession = sessionValidation.exists
394404
peerSessionsCache.set(signalId, hasSession)
395-
if (hasSession) {
405+
if (hasSession && !force) {
396406
continue
397407
}
398408
}
@@ -423,10 +433,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
423433
{
424434
tag: 'key',
425435
attrs: {},
426-
content: wireJids.map(jid => ({
427-
tag: 'user',
428-
attrs: { jid }
429-
}))
436+
content: wireJids.map(jid => {
437+
const attrs: { [key: string]: string } = { jid }
438+
if (force) attrs.reason = 'identity'
439+
return { tag: 'user', attrs }
440+
})
430441
}
431442
]
432443
})

src/Socket/socket.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import {
3030
getNextPreKeysNode,
3131
makeEventBuffer,
3232
makeNoiseHandler,
33-
promiseTimeout
33+
promiseTimeout,
34+
signedKeyPair,
35+
xmppSignedPreKey
3436
} from '../Utils'
3537
import { getPlatformId } from '../Utils/browser-utils'
3638
import {
@@ -204,6 +206,39 @@ export const makeSocket = (config: SocketConfig) => {
204206
return result
205207
}
206208

209+
// Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
210+
const digestKeyBundle = async (): Promise<void> => {
211+
const res = await query({
212+
tag: 'iq',
213+
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'encrypt' },
214+
content: [{ tag: 'digest', attrs: {} }]
215+
})
216+
const digestNode = getBinaryNodeChild(res, 'digest')
217+
if (!digestNode) {
218+
await uploadPreKeys()
219+
throw new Error('encrypt/get digest returned no digest node')
220+
}
221+
}
222+
223+
// Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
224+
const rotateSignedPreKey = async (): Promise<void> => {
225+
const newId = (creds.signedPreKey.keyId || 0) + 1
226+
const skey = await signedKeyPair(creds.signedIdentityKey, newId)
227+
await query({
228+
tag: 'iq',
229+
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'encrypt' },
230+
content: [
231+
{
232+
tag: 'rotate',
233+
attrs: {},
234+
content: [xmppSignedPreKey(skey)]
235+
}
236+
]
237+
})
238+
// Persist new signed pre-key in creds
239+
ev.emit('creds.update', { signedPreKey: skey })
240+
}
241+
207242
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
208243
if (usyncQuery.protocols.length === 0) {
209244
throw new Boom('USyncQuery must have at least one protocol')
@@ -869,6 +904,13 @@ export const makeSocket = (config: SocketConfig) => {
869904
try {
870905
await uploadPreKeysToServerIfRequired()
871906
await sendPassiveIq('active')
907+
908+
// After successful login, validate our key-bundle against server
909+
try {
910+
await digestKeyBundle()
911+
} catch (e) {
912+
logger.warn({ e }, 'failed to run digest after login')
913+
}
872914
} catch (err) {
873915
logger.warn({ err }, 'failed to send initial passive iq')
874916
}
@@ -1007,6 +1049,8 @@ export const makeSocket = (config: SocketConfig) => {
10071049
onUnexpectedError,
10081050
uploadPreKeys,
10091051
uploadPreKeysToServerIfRequired,
1052+
digestKeyBundle,
1053+
rotateSignedPreKey,
10101054
requestPairingCode,
10111055
wamBuffer: publicWAMBuffer,
10121056
/** Waits for the connection to WA to reach a state */

0 commit comments

Comments
 (0)