feat(signal): better parity with whatsapp web (#1967)

This commit is contained in:
vini
2025-11-19 10:20:49 -03:00
committed by GitHub
parent 3be21f8b99
commit a70e4da1c4
3 changed files with 78 additions and 11 deletions
+15 -3
View File
@@ -108,6 +108,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
useClones: false
})
// Debounce identity-change session refreshes per JID to avoid bursts
const identityAssertDebounce = new NodeCache<boolean>({ stdTTL: 5, useClones: false })
let sendActiveReceipts = false
const fetchMessageHistory = async (
@@ -546,8 +549,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const identityNode = getBinaryNodeChild(node, 'identity')
if (identityNode) {
logger.info({ jid: from }, 'identity changed')
// not handling right now
// signal will override new identity anyway
if (identityAssertDebounce.get(from!)) {
logger.debug({ jid: from }, 'skipping identity assert (debounced)')
return
}
identityAssertDebounce.set(from!, true)
try {
await assertSessions([from!], true)
} catch (error) {
logger.warn({ error, jid: from }, 'failed to assert sessions after identity change')
}
} else {
logger.info({ node }, 'unknown encrypt notification')
}
@@ -1007,7 +1019,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
}
}
await assertSessions([participant])
await assertSessions([participant], true)
if (isJidGroup(remoteJid)) {
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
+18 -7
View File
@@ -299,6 +299,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if (lidResults.length > 0) {
logger.trace('Storing LID maps from device call')
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid as string, pn: a.id })))
// Force-refresh sessions for newly mapped LIDs to align identity addressing
try {
const lids = lidResults.map(a => a.lid as string)
if (lids.length) {
await assertSessions(lids, true)
}
} catch (e) {
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
}
}
const extracted = extractDeviceJids(
@@ -373,7 +383,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
return deviceResults
}
const assertSessions = async (jids: string[]) => {
const assertSessions = async (jids: string[], force?: boolean) => {
let didFetchNewSession = false
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
const jidsRequiringFetch: string[] = []
@@ -385,14 +395,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
const cachedSession = peerSessionsCache.get(signalId)
if (cachedSession !== undefined) {
if (cachedSession) {
if (cachedSession && !force) {
continue // Session exists in cache
}
} else {
const sessionValidation = await signalRepository.validateSession(jid)
const hasSession = sessionValidation.exists
peerSessionsCache.set(signalId, hasSession)
if (hasSession) {
if (hasSession && !force) {
continue
}
}
@@ -423,10 +433,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
{
tag: 'key',
attrs: {},
content: wireJids.map(jid => ({
tag: 'user',
attrs: { jid }
}))
content: wireJids.map(jid => {
const attrs: { [key: string]: string } = { jid }
if (force) attrs.reason = 'identity'
return { tag: 'user', attrs }
})
}
]
})
+45 -1
View File
@@ -30,7 +30,9 @@ import {
getNextPreKeysNode,
makeEventBuffer,
makeNoiseHandler,
promiseTimeout
promiseTimeout,
signedKeyPair,
xmppSignedPreKey
} from '../Utils'
import { getPlatformId } from '../Utils/browser-utils'
import {
@@ -204,6 +206,39 @@ export const makeSocket = (config: SocketConfig) => {
return result
}
// Validate current key-bundle on server; on failure, trigger pre-key upload and rethrow
const digestKeyBundle = async (): Promise<void> => {
const res = await query({
tag: 'iq',
attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'encrypt' },
content: [{ tag: 'digest', attrs: {} }]
})
const digestNode = getBinaryNodeChild(res, 'digest')
if (!digestNode) {
await uploadPreKeys()
throw new Error('encrypt/get digest returned no digest node')
}
}
// Rotate our signed pre-key on server; on failure, run digest as fallback and rethrow
const rotateSignedPreKey = async (): Promise<void> => {
const newId = (creds.signedPreKey.keyId || 0) + 1
const skey = await signedKeyPair(creds.signedIdentityKey, newId)
await query({
tag: 'iq',
attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'encrypt' },
content: [
{
tag: 'rotate',
attrs: {},
content: [xmppSignedPreKey(skey)]
}
]
})
// Persist new signed pre-key in creds
ev.emit('creds.update', { signedPreKey: skey })
}
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
if (usyncQuery.protocols.length === 0) {
throw new Boom('USyncQuery must have at least one protocol')
@@ -869,6 +904,13 @@ export const makeSocket = (config: SocketConfig) => {
try {
await uploadPreKeysToServerIfRequired()
await sendPassiveIq('active')
// After successful login, validate our key-bundle against server
try {
await digestKeyBundle()
} catch (e) {
logger.warn({ e }, 'failed to run digest after login')
}
} catch (err) {
logger.warn({ err }, 'failed to send initial passive iq')
}
@@ -1007,6 +1049,8 @@ export const makeSocket = (config: SocketConfig) => {
onUnexpectedError,
uploadPreKeys,
uploadPreKeysToServerIfRequired,
digestKeyBundle,
rotateSignedPreKey,
requestPairingCode,
wamBuffer: publicWAMBuffer,
/** Waits for the connection to WA to reach a state */