Fix: groups pn messages-send (#1829)
* fix: centralize wireJid at encryption layer * fix: wirejid mess * fix: use signalrepository validate session in assertsessions * fix: wirejid conversion before fetch session * fix: lint
This commit is contained in:
+62
-98
@@ -200,37 +200,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
await sendReceipts(keys, readType)
|
||||
}
|
||||
|
||||
/** Device info with wire JID format for envelope addressing */
|
||||
type DeviceWithWireJid = JidWithDevice & {
|
||||
wireJid: string
|
||||
}
|
||||
|
||||
const resolveSessionJids = async (jids: string[]): Promise<Map<string, string>> => {
|
||||
const uniquePnJids = Array.from(new Set(jids.filter(isPnUser)))
|
||||
if (!uniquePnJids.length) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
const lookups = await Promise.all(
|
||||
uniquePnJids.map(async pnJid => {
|
||||
try {
|
||||
const resolved = await signalRepository.lidMapping.getLIDForPN(pnJid)
|
||||
return resolved ? ([pnJid, resolved] as const) : [pnJid, pnJid]
|
||||
} catch (error) {
|
||||
logger.warn({ pnJid, error }, 'Failed to resolve LID mapping for PN JID')
|
||||
return [pnJid, pnJid]
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const sessionMap = new Map<string, string>()
|
||||
for (const entry of lookups) {
|
||||
if (entry) {
|
||||
sessionMap.set(entry[0], entry[1])
|
||||
}
|
||||
}
|
||||
|
||||
return sessionMap
|
||||
/** Device info with wire JID */
|
||||
type DeviceWithJid = JidWithDevice & {
|
||||
jid: string
|
||||
}
|
||||
|
||||
/** Fetch all the devices we've to send a message to */
|
||||
@@ -238,8 +210,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
jids: string[],
|
||||
useCache: boolean,
|
||||
ignoreZeroDevices: boolean
|
||||
): Promise<DeviceWithWireJid[]> => {
|
||||
const deviceResults: DeviceWithWireJid[] = []
|
||||
): Promise<DeviceWithJid[]> => {
|
||||
const deviceResults: DeviceWithJid[] = []
|
||||
|
||||
if (!useCache) {
|
||||
logger.debug('not using cache for devices')
|
||||
@@ -258,7 +230,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
deviceResults.push({
|
||||
user,
|
||||
device,
|
||||
wireJid: jid // again this makes no sense
|
||||
jid
|
||||
})
|
||||
return null
|
||||
}
|
||||
@@ -282,11 +254,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
(userDevicesCache.mget ? undefined : ((await userDevicesCache.get(user!)) as JidWithDevice[]))
|
||||
if (devices) {
|
||||
const isLidJid = jid.includes('@lid')
|
||||
const devicesWithWire = devices.map(d => ({
|
||||
const devicesWithJid = devices.map(d => ({
|
||||
...d,
|
||||
wireJid: isLidJid ? jidEncode(d.user, 'lid', d.device) : jidEncode(d.user, 's.whatsapp.net', d.device)
|
||||
jid: isLidJid ? jidEncode(d.user, 'lid', d.device) : jidEncode(d.user, 's.whatsapp.net', d.device)
|
||||
}))
|
||||
deviceResults.push(...devicesWithWire)
|
||||
deviceResults.push(...devicesWithJid)
|
||||
|
||||
logger.trace({ user }, 'using cache for devices')
|
||||
} else {
|
||||
@@ -332,20 +304,20 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
|
||||
// Process all devices for this user
|
||||
for (const item of userDevices) {
|
||||
const finalWireJid = isLidUser
|
||||
const finalJid = isLidUser
|
||||
? jidEncode(user, 'lid', item.device)
|
||||
: jidEncode(item.user, 's.whatsapp.net', item.device)
|
||||
|
||||
deviceResults.push({
|
||||
...item,
|
||||
wireJid: finalWireJid
|
||||
jid: finalJid
|
||||
})
|
||||
|
||||
logger.debug(
|
||||
{
|
||||
user: item.user,
|
||||
device: item.device,
|
||||
finalWireJid,
|
||||
finalJid,
|
||||
usedLid: isLidUser
|
||||
},
|
||||
'Processed device with LID priority'
|
||||
@@ -390,7 +362,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
|
||||
const jidsRequiringFetch: string[] = []
|
||||
|
||||
// Check peerSessionsCache and authState.keys
|
||||
// Check peerSessionsCache and validate sessions using libsignal loadSession
|
||||
for (const jid of uniqueJids) {
|
||||
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
||||
const cachedSession = peerSessionsCache.get(signalId)
|
||||
@@ -399,8 +371,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
continue // Session exists in cache
|
||||
}
|
||||
} else {
|
||||
const sessions = await authState.keys.get('session', [signalId])
|
||||
const hasSession = !!sessions[signalId]
|
||||
const sessionValidation = await signalRepository.validateSession(jid)
|
||||
const hasSession = sessionValidation.exists
|
||||
peerSessionsCache.set(signalId, hasSession)
|
||||
if (hasSession) {
|
||||
continue
|
||||
@@ -411,7 +383,19 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
}
|
||||
|
||||
if (jidsRequiringFetch.length) {
|
||||
logger.debug({ jidsRequiringFetch }, 'fetching sessions')
|
||||
// LID if mapped, otherwise original
|
||||
const wireJids = await Promise.all(
|
||||
jidsRequiringFetch.map(async jid => {
|
||||
if (jid.includes('@s.whatsapp.net')) {
|
||||
const lid = await signalRepository.lidMapping.getLIDForPN(jid)
|
||||
return lid ? lid : jid
|
||||
}
|
||||
|
||||
return jid
|
||||
})
|
||||
)
|
||||
|
||||
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions')
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
@@ -423,7 +407,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
{
|
||||
tag: 'key',
|
||||
attrs: {},
|
||||
content: jidsRequiringFetch.map(jid => ({
|
||||
content: wireJids.map(jid => ({
|
||||
tag: 'user',
|
||||
attrs: { jid }
|
||||
}))
|
||||
@@ -433,9 +417,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
await parseAndInjectE2ESessions(result, signalRepository)
|
||||
didFetchNewSession = true
|
||||
|
||||
// Cache fetched sessions
|
||||
for (const jid of jidsRequiringFetch) {
|
||||
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
||||
// Cache fetched sessions using wire JIDs
|
||||
for (const wireJid of wireJids) {
|
||||
const signalId = signalRepository.jidToSignalProtocolAddress(wireJid)
|
||||
peerSessionsCache.set(signalId, true)
|
||||
}
|
||||
}
|
||||
@@ -471,21 +455,20 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
return msgId
|
||||
}
|
||||
|
||||
const createParticipantNodesWithSessionMap = async (
|
||||
recipientWireJids: string[],
|
||||
sessionMap: Map<string, string>,
|
||||
const createParticipantNodes = async (
|
||||
recipientJids: string[],
|
||||
message: proto.IMessage,
|
||||
extraAttrs?: BinaryNode['attrs'],
|
||||
dsmMessage?: proto.IMessage
|
||||
) => {
|
||||
if (!recipientWireJids.length) {
|
||||
if (!recipientJids.length) {
|
||||
return { nodes: [] as BinaryNode[], shouldIncludeDeviceIdentity: false }
|
||||
}
|
||||
|
||||
const patched = await patchMessageBeforeSending(message, recipientWireJids)
|
||||
const patched = await patchMessageBeforeSending(message, recipientJids)
|
||||
const patchedMessages = Array.isArray(patched)
|
||||
? patched
|
||||
: recipientWireJids.map(jid => ({ recipientJid: jid, message: patched }))
|
||||
: recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
|
||||
|
||||
let shouldIncludeDeviceIdentity = false
|
||||
const meId = authState.creds.me!.id
|
||||
@@ -493,27 +476,26 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const meLidUser = meLid ? jidDecode(meLid)?.user : null
|
||||
|
||||
const encryptionPromises = (patchedMessages as any).map(
|
||||
async ({ recipientJid: wireJid, message: patchedMessage }: any) => {
|
||||
if (!wireJid) return null
|
||||
wireJid = sessionMap.get(wireJid) ?? wireJid
|
||||
async ({ recipientJid: jid, message: patchedMessage }: any) => {
|
||||
if (!jid) return null
|
||||
let msgToEncrypt = patchedMessage
|
||||
if (dsmMessage) {
|
||||
const { user: targetUser } = jidDecode(wireJid)!
|
||||
const { user: targetUser } = jidDecode(jid)!
|
||||
const { user: ownPnUser } = jidDecode(meId)!
|
||||
const ownLidUser = meLidUser
|
||||
const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
|
||||
const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid)
|
||||
const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
|
||||
if (isOwnUser && !isExactSenderDevice) {
|
||||
msgToEncrypt = dsmMessage
|
||||
logger.debug({ wireJid, targetUser }, 'Using DSM for own device')
|
||||
logger.debug({ jid, targetUser }, 'Using DSM for own device')
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = encodeWAMessage(msgToEncrypt)
|
||||
const mutexKey = wireJid
|
||||
const mutexKey = jid
|
||||
const node = await encryptionMutex.mutex(mutexKey, async () => {
|
||||
const { type, ciphertext } = await signalRepository.encryptMessage({
|
||||
jid: wireJid,
|
||||
jid,
|
||||
data: bytes
|
||||
})
|
||||
if (type === 'pkmsg') {
|
||||
@@ -522,7 +504,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
|
||||
return {
|
||||
tag: 'to',
|
||||
attrs: { jid: wireJid },
|
||||
attrs: { jid },
|
||||
content: [
|
||||
{
|
||||
tag: 'enc',
|
||||
@@ -544,16 +526,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
return { nodes, shouldIncludeDeviceIdentity }
|
||||
}
|
||||
|
||||
const createParticipantNodes = async (
|
||||
recipientWireJids: string[],
|
||||
message: proto.IMessage,
|
||||
extraAttrs?: BinaryNode['attrs'],
|
||||
dsmMessage?: proto.IMessage
|
||||
) => {
|
||||
const sessionMap = await resolveSessionJids(recipientWireJids)
|
||||
return createParticipantNodesWithSessionMap(recipientWireJids, sessionMap, message, extraAttrs, dsmMessage)
|
||||
}
|
||||
|
||||
const relayMessage = async (
|
||||
jid: string,
|
||||
message: proto.IMessage,
|
||||
@@ -597,7 +569,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const participants: BinaryNode[] = []
|
||||
const destinationJid = !isStatus ? finalJid : statusJid
|
||||
const binaryNodeContent: BinaryNode[] = []
|
||||
const devices: DeviceWithWireJid[] = []
|
||||
const devices: DeviceWithJid[] = []
|
||||
|
||||
const meMsg: proto.IMessage = {
|
||||
deviceSentMessage: {
|
||||
@@ -618,7 +590,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
devices.push({
|
||||
user,
|
||||
device,
|
||||
wireJid: participant.jid
|
||||
jid: participant.jid
|
||||
})
|
||||
}
|
||||
|
||||
@@ -718,10 +690,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
meId: groupSenderIdentity
|
||||
})
|
||||
|
||||
const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid))
|
||||
const senderKeyRecipients: string[] = []
|
||||
for (const device of devices) {
|
||||
const deviceJid = device.wireJid
|
||||
const deviceJid = device.jid
|
||||
const hasKey = !!senderKeyMap[deviceJid]
|
||||
if (!hasKey && !isRetryResend) {
|
||||
senderKeyRecipients.push(deviceJid)
|
||||
@@ -739,15 +710,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
}
|
||||
}
|
||||
|
||||
const senderKeySessionTargets = senderKeyRecipients.map(jid => deviceSessionMap.get(jid) ?? jid)
|
||||
const senderKeySessionTargets = senderKeyRecipients
|
||||
await assertSessions(senderKeySessionTargets)
|
||||
|
||||
const result = await createParticipantNodesWithSessionMap(
|
||||
senderKeyRecipients,
|
||||
deviceSessionMap,
|
||||
senderKeyMsg,
|
||||
extraAttrs
|
||||
)
|
||||
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
|
||||
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
|
||||
|
||||
participants.push(...result.nodes)
|
||||
@@ -785,7 +751,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
devices.push({
|
||||
user,
|
||||
device: 0,
|
||||
wireJid: jidEncode(user, targetUserServer, 0)
|
||||
jid: jidEncode(user, targetUserServer, 0)
|
||||
})
|
||||
|
||||
// Own user matches conversation addressing mode
|
||||
@@ -796,7 +762,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
devices.push({
|
||||
user: ownUserForAddressing,
|
||||
device: 0,
|
||||
wireJid: jidEncode(ownUserForAddressing, ownUserServer, 0)
|
||||
jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -817,7 +783,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
logger.debug(
|
||||
{
|
||||
deviceCount: devices.length,
|
||||
devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.wireJid)?.server}`)
|
||||
devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
|
||||
},
|
||||
'Device enumeration complete with unified addressing'
|
||||
)
|
||||
@@ -830,10 +796,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const { user: mePnUser } = jidDecode(meId)!
|
||||
const { user: meLidUser } = meLid ? jidDecode(meLid)! : { user: null }
|
||||
|
||||
for (const { user, wireJid } of devices) {
|
||||
const isExactSenderDevice = wireJid === meId || (meLid && wireJid === meLid)
|
||||
for (const { user, jid } of devices) {
|
||||
const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
|
||||
if (isExactSenderDevice) {
|
||||
logger.debug({ wireJid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
|
||||
logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -841,25 +807,23 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const isMe = user === mePnUser || (meLidUser && user === meLidUser)
|
||||
|
||||
if (isMe) {
|
||||
meRecipients.push(wireJid)
|
||||
meRecipients.push(jid)
|
||||
} else {
|
||||
otherRecipients.push(wireJid)
|
||||
otherRecipients.push(jid)
|
||||
}
|
||||
|
||||
allRecipients.push(wireJid)
|
||||
allRecipients.push(jid)
|
||||
}
|
||||
|
||||
const deviceSessionMap = await resolveSessionJids(devices.map(d => d.wireJid))
|
||||
const sessionTargets = allRecipients.map(jid => deviceSessionMap.get(jid) ?? jid)
|
||||
await assertSessions(sessionTargets)
|
||||
await assertSessions(allRecipients)
|
||||
|
||||
const [
|
||||
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
||||
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
||||
] = await Promise.all([
|
||||
// For own devices: use DSM if available (1:1 chats only)
|
||||
createParticipantNodesWithSessionMap(meRecipients, deviceSessionMap, meMsg || message, extraAttrs),
|
||||
createParticipantNodesWithSessionMap(otherRecipients, deviceSessionMap, message, extraAttrs, meMsg)
|
||||
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
||||
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
||||
])
|
||||
participants.push(...meNodes)
|
||||
participants.push(...otherNodes)
|
||||
|
||||
Reference in New Issue
Block a user