feat(signal): better parity with whatsapp web (#1967)
This commit is contained in:
@@ -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 } })
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user