feat(signal): better parity with whatsapp web (#1967)
This commit is contained in:
@@ -108,6 +108,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
useClones: false
|
useClones: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Debounce identity-change session refreshes per JID to avoid bursts
|
||||||
|
const identityAssertDebounce = new NodeCache<boolean>({ stdTTL: 5, useClones: false })
|
||||||
|
|
||||||
let sendActiveReceipts = false
|
let sendActiveReceipts = false
|
||||||
|
|
||||||
const fetchMessageHistory = async (
|
const fetchMessageHistory = async (
|
||||||
@@ -546,8 +549,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
const identityNode = getBinaryNodeChild(node, 'identity')
|
const identityNode = getBinaryNodeChild(node, 'identity')
|
||||||
if (identityNode) {
|
if (identityNode) {
|
||||||
logger.info({ jid: from }, 'identity changed')
|
logger.info({ jid: from }, 'identity changed')
|
||||||
// not handling right now
|
if (identityAssertDebounce.get(from!)) {
|
||||||
// signal will override new identity anyway
|
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 {
|
} else {
|
||||||
logger.info({ node }, 'unknown encrypt notification')
|
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)) {
|
if (isJidGroup(remoteJid)) {
|
||||||
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
|
||||||
|
|||||||
@@ -299,6 +299,16 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
if (lidResults.length > 0) {
|
if (lidResults.length > 0) {
|
||||||
logger.trace('Storing LID maps from device call')
|
logger.trace('Storing LID maps from device call')
|
||||||
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid as string, pn: a.id })))
|
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(
|
const extracted = extractDeviceJids(
|
||||||
@@ -373,7 +383,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
return deviceResults
|
return deviceResults
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertSessions = async (jids: string[]) => {
|
const assertSessions = async (jids: string[], force?: boolean) => {
|
||||||
let didFetchNewSession = false
|
let didFetchNewSession = false
|
||||||
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
|
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
|
||||||
const jidsRequiringFetch: string[] = []
|
const jidsRequiringFetch: string[] = []
|
||||||
@@ -385,14 +395,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
||||||
const cachedSession = peerSessionsCache.get(signalId)
|
const cachedSession = peerSessionsCache.get(signalId)
|
||||||
if (cachedSession !== undefined) {
|
if (cachedSession !== undefined) {
|
||||||
if (cachedSession) {
|
if (cachedSession && !force) {
|
||||||
continue // Session exists in cache
|
continue // Session exists in cache
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const sessionValidation = await signalRepository.validateSession(jid)
|
const sessionValidation = await signalRepository.validateSession(jid)
|
||||||
const hasSession = sessionValidation.exists
|
const hasSession = sessionValidation.exists
|
||||||
peerSessionsCache.set(signalId, hasSession)
|
peerSessionsCache.set(signalId, hasSession)
|
||||||
if (hasSession) {
|
if (hasSession && !force) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,10 +433,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
{
|
{
|
||||||
tag: 'key',
|
tag: 'key',
|
||||||
attrs: {},
|
attrs: {},
|
||||||
content: wireJids.map(jid => ({
|
content: wireJids.map(jid => {
|
||||||
tag: 'user',
|
const attrs: { [key: string]: string } = { jid }
|
||||||
attrs: { jid }
|
if (force) attrs.reason = 'identity'
|
||||||
}))
|
return { tag: 'user', attrs }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
+45
-1
@@ -30,7 +30,9 @@ import {
|
|||||||
getNextPreKeysNode,
|
getNextPreKeysNode,
|
||||||
makeEventBuffer,
|
makeEventBuffer,
|
||||||
makeNoiseHandler,
|
makeNoiseHandler,
|
||||||
promiseTimeout
|
promiseTimeout,
|
||||||
|
signedKeyPair,
|
||||||
|
xmppSignedPreKey
|
||||||
} from '../Utils'
|
} from '../Utils'
|
||||||
import { getPlatformId } from '../Utils/browser-utils'
|
import { getPlatformId } from '../Utils/browser-utils'
|
||||||
import {
|
import {
|
||||||
@@ -204,6 +206,39 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
return result
|
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) => {
|
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
|
||||||
if (usyncQuery.protocols.length === 0) {
|
if (usyncQuery.protocols.length === 0) {
|
||||||
throw new Boom('USyncQuery must have at least one protocol')
|
throw new Boom('USyncQuery must have at least one protocol')
|
||||||
@@ -869,6 +904,13 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
try {
|
try {
|
||||||
await uploadPreKeysToServerIfRequired()
|
await uploadPreKeysToServerIfRequired()
|
||||||
await sendPassiveIq('active')
|
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) {
|
} catch (err) {
|
||||||
logger.warn({ err }, 'failed to send initial passive iq')
|
logger.warn({ err }, 'failed to send initial passive iq')
|
||||||
}
|
}
|
||||||
@@ -1007,6 +1049,8 @@ export const makeSocket = (config: SocketConfig) => {
|
|||||||
onUnexpectedError,
|
onUnexpectedError,
|
||||||
uploadPreKeys,
|
uploadPreKeys,
|
||||||
uploadPreKeysToServerIfRequired,
|
uploadPreKeysToServerIfRequired,
|
||||||
|
digestKeyBundle,
|
||||||
|
rotateSignedPreKey,
|
||||||
requestPairingCode,
|
requestPairingCode,
|
||||||
wamBuffer: publicWAMBuffer,
|
wamBuffer: publicWAMBuffer,
|
||||||
/** Waits for the connection to WA to reach a state */
|
/** Waits for the connection to WA to reach a state */
|
||||||
|
|||||||
Reference in New Issue
Block a user