fix: revert remaining ?? '' patterns across Utils, WABinary, Signal, and WAUSync files

Completes the comprehensive revert of defensive null coalescing patterns
introduced in PRs 124-129. These patterns (e.g. `value ?? ''`) silently
produced empty strings instead of failing fast, causing subtle bugs like
malformed JIDs and broken Signal session lookups.

Files: libsignal.ts, messages-recv.ts, chat-utils.ts, generics.ts,
history.ts, messages-media.ts, messages.ts, process-message.ts,
validate-connection.ts, jid-utils.ts, UsyncBotProfileProtocol.ts

https://claude.ai/code/session_01XaA7GwNaB6azTHFYQ8WEpB
This commit is contained in:
Claude
2026-02-09 13:56:33 +00:00
parent 48b63565b6
commit 2a9d9a0a52
11 changed files with 61 additions and 64 deletions
+8 -8
View File
@@ -470,7 +470,7 @@ export const decodeSyncdSnapshot = async (
areMutationsRequired
? mutation => {
const index = mutation.syncAction.index?.toString()
mutationMap[index ?? ''] = mutation
mutationMap[index!] = mutation
}
: () => {},
validateMacs
@@ -558,7 +558,7 @@ export const decodePatches = async (
shouldMutate
? mutation => {
const index = mutation.syncAction.index?.toString()
mutationMap[index ?? ''] = mutation
mutationMap[index!] = mutation
}
: () => {},
true
@@ -689,7 +689,7 @@ export const chatModificationToAppPatch = (mod: ChatModification, jid: string) =
messageTimestamp: timestamp
}
},
index: ['deleteMessageForMe', jid, key.id ?? '', key.fromMe ? '1' : '0', '0'],
index: ['deleteMessageForMe', jid, key.id!, key.fromMe ? '1' : '0', '0'],
type: 'regular_high',
apiVersion: 3,
operation: OP.SET
@@ -896,7 +896,7 @@ export const processSyncAction = (
{
id,
muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null,
conditional: getChatUpdateConditional(id ?? '', undefined)
conditional: getChatUpdateConditional(id!, undefined)
}
])
} else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
@@ -925,7 +925,7 @@ export const processSyncAction = (
{
id,
archived: isArchived,
conditional: getChatUpdateConditional(id ?? '', msgRange)
conditional: getChatUpdateConditional(id!, msgRange)
}
])
} else if (action?.markChatAsReadAction) {
@@ -939,7 +939,7 @@ export const processSyncAction = (
{
id,
unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
conditional: getChatUpdateConditional(id ?? '', markReadAction?.messageRange)
conditional: getChatUpdateConditional(id!, markReadAction?.messageRange)
}
])
} else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
@@ -965,7 +965,7 @@ export const processSyncAction = (
{
id,
pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
conditional: getChatUpdateConditional(id ?? '', undefined)
conditional: getChatUpdateConditional(id!, undefined)
}
])
} else if (action?.unarchiveChatsSetting) {
@@ -990,7 +990,7 @@ export const processSyncAction = (
])
} else if (action?.deleteChatAction || type === 'deleteChat') {
if (!isInitialSync) {
ev.emit('chats.delete', [id ?? ''])
ev.emit('chats.delete', [id!])
}
} else if (action?.labelEditAction) {
const { name, color, deleted, predefinedId } = action.labelEditAction
+1 -1
View File
@@ -343,7 +343,7 @@ const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = {
* @param type type from receipt
*/
export const getStatusFromReceiptType = (type: string | undefined) => {
const status = STATUS_MAP[type ?? '']
const status = STATUS_MAP[type!]
if (typeof type === 'undefined') {
return proto.WebMessageInfo.Status.DELIVERY_ACK
}
+2 -2
View File
@@ -294,7 +294,7 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
case proto.HistorySync.HistorySyncType.FULL:
case proto.HistorySync.HistorySyncType.ON_DEMAND:
for (const chat of (item.conversations ?? []) as Chat[]) {
const chatId = chat.id ?? ''
const chatId = chat.id!
// Source 2: Extract LID-PN mapping from conversation object
// This handles cases where the mapping isn't in phoneNumberToLidMappings
@@ -371,7 +371,7 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
break
case proto.HistorySync.HistorySyncType.PUSH_NAME:
for (const c of (item.pushnames ?? [])) {
contacts.push({ id: c.id ?? '', notify: c.pushname ?? '' })
contacts.push({ id: c.id!, notify: c.pushname! })
}
break
+5 -5
View File
@@ -317,7 +317,7 @@ export const getStream = async (item: WAMediaUpload, opts?: RequestInit & { maxC
const urlStr = item.url.toString()
if (urlStr.startsWith('data:')) {
const buffer = Buffer.from(urlStr.split(',')[1] ?? '', 'base64')
const buffer = Buffer.from(urlStr.split(',')[1]!, 'base64')
return { stream: toReadable(buffer), type: 'buffer' } as const
}
@@ -532,7 +532,7 @@ export const downloadContentFromMessage = async (
opts: MediaDownloadOptions = {}
) => {
const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/')
const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath ?? '')
const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath!)
if (!downloadUrl) {
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 })
}
@@ -656,7 +656,7 @@ export function extensionForMediaMessage(message: WAMessageContent) {
extension = '.jpeg'
} else {
const messageContent = message[type] as WAGenericMediaMessage
extension = getExtension(messageContent.mimetype ?? '') ?? ''
extension = getExtension(messageContent.mimetype!)!
}
return extension
@@ -864,8 +864,8 @@ export const getWAUploadToServer = (
if (result?.url || result?.direct_path) {
urls = {
mediaUrl: result.url ?? '',
directPath: result.direct_path ?? '',
mediaUrl: result.url!,
directPath: result.direct_path!,
meta_hmac: result.meta_hmac,
fbid: result.fbid,
ts: result.ts
+1 -1
View File
@@ -1770,7 +1770,7 @@ export const generateWAMessageFromContent = (
const innerContent = innerMessage[key as Exclude<keyof proto.IMessage, 'conversation'>]
const contextInfo: proto.IContextInfo =
(innerContent && typeof innerContent === 'object' && 'contextInfo' in innerContent && (innerContent as Record<string, unknown>).contextInfo as proto.IContextInfo) || {}
contextInfo.participant = jidNormalizedUser(participant ?? '')
contextInfo.participant = jidNormalizedUser(participant!)
contextInfo.stanzaId = quoted.key.id
contextInfo.quotedMessage = quotedMsg
+19 -22
View File
@@ -59,24 +59,22 @@ const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_
/** Cleans a received message to further processing */
export const cleanMessage = (message: WAMessage, meId: string, meLid: string) => {
// ensure remoteJid and participant doesn't have device or agent in it
const remoteJid = message.key.remoteJid ?? ''
if (isHostedPnUser(remoteJid) || isHostedLidUser(remoteJid)) {
if (isHostedPnUser(message.key.remoteJid!) || isHostedLidUser(message.key.remoteJid!)) {
message.key.remoteJid = jidEncode(
jidDecode(remoteJid)?.user ?? '',
isHostedPnUser(remoteJid) ? 's.whatsapp.net' : 'lid'
jidDecode(message.key?.remoteJid!)?.user!,
isHostedPnUser(message.key.remoteJid!) ? 's.whatsapp.net' : 'lid'
)
} else {
message.key.remoteJid = jidNormalizedUser(remoteJid)
message.key.remoteJid = jidNormalizedUser(message.key.remoteJid!)
}
const participantJid = message.key.participant ?? ''
if (isHostedPnUser(participantJid) || isHostedLidUser(participantJid)) {
if (isHostedPnUser(message.key.participant!) || isHostedLidUser(message.key.participant!)) {
message.key.participant = jidEncode(
jidDecode(participantJid)?.user ?? '',
isHostedPnUser(participantJid) ? 's.whatsapp.net' : 'lid'
jidDecode(message.key.participant!)?.user!,
isHostedPnUser(message.key.participant!) ? 's.whatsapp.net' : 'lid'
)
} else {
message.key.participant = jidNormalizedUser(participantJid)
message.key.participant = jidNormalizedUser(message.key.participant!)
}
const content = normalizeMessageContent(message.message)
@@ -102,8 +100,8 @@ export const cleanMessage = (message: WAMessage, meId: string, meLid: string) =>
// if the sender believed the message being reacted to is not from them
// we've to correct the key to be from them, or some other participant
msgKey.fromMe = !msgKey.fromMe
? areJidsSameUser(msgKey.participant || (msgKey.remoteJid ?? ''), meId) ||
areJidsSameUser(msgKey.participant || (msgKey.remoteJid ?? ''), meLid)
? areJidsSameUser(msgKey.participant || (msgKey.remoteJid!), meId) ||
areJidsSameUser(msgKey.participant || (msgKey.remoteJid!), meLid)
: // if the message being reacted to, was from them
// fromMe automatically becomes false
false
@@ -177,12 +175,11 @@ export const shouldIncrementChatUnread = (message: WAMessage) => !message.key.fr
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
*/
export const getChatId = ({ remoteJid, participant, fromMe }: WAMessageKey) => {
const jid = remoteJid ?? ''
if (isJidBroadcast(jid) && !isJidStatusBroadcast(jid) && !fromMe) {
return participant ?? ''
if (isJidBroadcast(remoteJid!) && !isJidStatusBroadcast(remoteJid!) && !fromMe) {
return participant!
}
return jid
return remoteJid!
}
type PollContext = {
@@ -478,7 +475,7 @@ const processMessage = async (
ev.emit('messages.upsert', {
messages: [webMessageInfo as WAMessage],
type: 'notify',
requestId: response.stanzaId ?? ''
requestId: response.stanzaId!
})
}
}
@@ -517,10 +514,10 @@ const processMessage = async (
const labelAssociationMsg = protocolMsg.memberLabel
if (labelAssociationMsg?.label) {
ev.emit('group.member-tag.update', {
groupId: chat.id ?? '',
groupId: chat.id!,
label: labelAssociationMsg.label,
participant: message.key.participant ?? '',
participantAlt: message.key.participantAlt ?? '',
participant: message.key.participant!,
participantAlt: message.key.participantAlt!,
messageTimestamp: Number(message.messageTimestamp)
})
}
@@ -578,7 +575,7 @@ const processMessage = async (
const meIdNormalised = jidNormalizedUser(meId)
// all jids need to be PN
const eventCreatorKey = creationMsgKey.participant || (creationMsgKey.remoteJid ?? '')
const eventCreatorKey = creationMsgKey.participant || (creationMsgKey.remoteJid!)
const eventCreatorPn = isLidUser(eventCreatorKey)
? await signalRepository.lidMapping.getPNForLID(eventCreatorKey)
: eventCreatorKey
@@ -600,7 +597,7 @@ const processMessage = async (
const responseMsg = decryptEventResponse(encEventResponse, {
eventEncKey,
eventCreatorJid,
eventMsgId: creationMsgKey.id ?? '',
eventMsgId: creationMsgKey.id!,
responderJid
})
+1 -1
View File
@@ -237,7 +237,7 @@ export const configureSuccessfulPairing = (
content: [
{
tag: 'device-identity',
attrs: { 'key-index': String(deviceIdentity.keyIndex ?? '') },
attrs: { 'key-index': deviceIdentity.keyIndex!.toString() },
content: accountEnc
}
]