From 1c9fb86cc37456e1cc25216c12aadfaa433bb58d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Feb 2026 00:32:04 +0000 Subject: [PATCH] fix(types): remove non-null assertions in remaining 12 files Files fixed: - messages-media.ts: stream/buffer guards, file path validation - decode-wa-message.ts: message field guards, optional chaining - version-cache.ts: narrowing after null checks - jid-utils.ts: split result guards, user fallbacks - communities.ts: attrs fallbacks with ?? operator - sticker-pack.ts: response guards - business.ts: attrs guards - socket.ts: onClose guard, pairingCode guard, creds.me guard - event-buffer.ts: null guards - signal.ts: null guards - encode.ts (WAM): id null check with continue - upstream history.ts: conversations/pushnames null guards All 26 files across the project are now corrected. Total: ~248 non-null assertions replaced with proper null guards. https://claude.ai/code/session_01E2cfX1N3sJgCJBTvzGazSG --- src/Socket/business.ts | 20 ++++++++-- src/Socket/communities.ts | 25 ++++++++---- src/Socket/socket.ts | 48 ++++++++++++++++++----- src/Utils/decode-wa-message.ts | 32 +++++++++------ src/Utils/event-buffer.ts | 44 +++++++++++++-------- src/Utils/messages-media.ts | 41 +++++++++++++------ src/Utils/signal.ts | 50 +++++++++++++++--------- src/Utils/sticker-pack.ts | 2 +- src/Utils/version-cache.ts | 12 +++--- src/WABinary/jid-utils.ts | 19 +++++---- src/WAM/encode.ts | 6 ++- upstream-baileys-pr/src/Utils/history.ts | 20 +++++++--- 12 files changed, 215 insertions(+), 104 deletions(-) diff --git a/src/Socket/business.ts b/src/Socket/business.ts index 961f8e6c..23392b23 100644 --- a/src/Socket/business.ts +++ b/src/Socket/business.ts @@ -118,14 +118,18 @@ export const makeBusinessSocket = (config: SocketConfig) => { content: [ { tag: 'cover_photo', - attrs: { id: String(fbid), op: 'update', token: meta_hmac!, ts: String(ts) } + attrs: { id: String(fbid), op: 'update', token: meta_hmac ?? '', ts: String(ts) } } ] } ] }) - return fbid! + if (!fbid) { + throw new Error('Cover photo upload failed: missing fbid') + } + + return fbid } const removeCoverPhoto = async (id: string) => { @@ -332,7 +336,11 @@ export const makeBusinessSocket = (config: SocketConfig) => { const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit') const productNode = getBinaryNodeChild(productCatalogEditNode, 'product') - return parseProductNode(productNode!) + if (!productNode) { + throw new Error('Product node not found in catalog edit response') + } + + return parseProductNode(productNode) } const productCreate = async (create: ProductCreate) => { @@ -372,7 +380,11 @@ export const makeBusinessSocket = (config: SocketConfig) => { const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add') const productNode = getBinaryNodeChild(productCatalogAddNode, 'product') - return parseProductNode(productNode!) + if (!productNode) { + throw new Error('Product node not found in catalog add response') + } + + return parseProductNode(productNode) } const productDelete = async (productIds: string[]) => { diff --git a/src/Socket/communities.ts b/src/Socket/communities.ts index 0d99e9ea..9691be1a 100644 --- a/src/Socket/communities.ts +++ b/src/Socket/communities.ts @@ -100,7 +100,12 @@ export const makeCommunitiesSocket = (config: SocketConfig) => { } sock.ws.on('CB:ib,,dirty', async (node: BinaryNode) => { - const { attrs } = getBinaryNodeChild(node, 'dirty')! + const dirtyNode = getBinaryNodeChild(node, 'dirty') + if (!dirtyNode) { + return + } + + const { attrs } = dirtyNode if (attrs.type !== 'communities') { return } @@ -353,13 +358,13 @@ export const makeCommunitiesSocket = (config: SocketConfig) => { communityAcceptInviteV4: ev.createBufferedFunction( async (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => { key = typeof key === 'string' ? { remoteJid: key } : key - const results = await communityQuery(inviteMessage.groupJid!, 'set', [ + const results = await communityQuery(inviteMessage.groupJid ?? '', 'set', [ { tag: 'accept', attrs: { - code: inviteMessage.inviteCode!, - expiration: inviteMessage.inviteExpiration!.toString(), - admin: key.remoteJid! + code: inviteMessage.inviteCode ?? '', + expiration: (inviteMessage.inviteExpiration ?? 0).toString(), + admin: key.remoteJid ?? '' } } ]) @@ -432,7 +437,11 @@ export const makeCommunitiesSocket = (config: SocketConfig) => { } export const extractCommunityMetadata = (result: BinaryNode) => { - const community = getBinaryNodeChild(result, 'community')! + const community = getBinaryNodeChild(result, 'community') + if (!community) { + throw new Error('Missing community node in result') + } + const descChild = getBinaryNodeChild(community, 'description') let desc: string | undefined let descId: string | undefined @@ -466,12 +475,12 @@ export const extractCommunityMetadata = (result: BinaryNode) => { participants: getBinaryNodeChildren(community, 'participant').map(({ attrs }) => { return { // TODO: IMPLEMENT THE PN/LID FIELDS HERE!! - id: attrs.jid!, + id: attrs.jid ?? '', admin: (attrs.type || null) as GroupParticipant['admin'] } }), ephemeralDuration: eph ? +eph : undefined, - addressingMode: getBinaryNodeChildString(community, 'addressing_mode')! as GroupMetadata['addressingMode'] + addressingMode: getBinaryNodeChildString(community, 'addressing_mode') as GroupMetadata['addressingMode'] } return metadata } diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index e0a3c70e..f932eab3 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -552,7 +552,11 @@ export const makeSocket = (config: SocketConfig) => { }) if (sendMsg) { - sendRawMessage(sendMsg).catch(onClose!) + if (!onClose) { + throw new Error('onClose handler not initialized') + } + + sendRawMessage(sendMsg).catch(onClose) } return result @@ -609,8 +613,12 @@ export const makeSocket = (config: SocketConfig) => { }, content: [{ tag: 'count', attrs: {} }] }) - const countChild = getBinaryNodeChild(result, 'count')! - return +countChild.attrs.value! + const countChild = getBinaryNodeChild(result, 'count') + if (!countChild) { + return 0 + } + + return +(countChild.attrs.value ?? 0) } // Pre-key upload state management @@ -885,7 +893,11 @@ export const makeSocket = (config: SocketConfig) => { return } - const duration = Date.now() - sessionStartTime! + if (!sessionStartTime) { + return + } + + const duration = Date.now() - sessionStartTime const durationHours = Math.floor(duration / 1000 / 60 / 60) logger.info(`🕐 Session TTL reached after ${durationHours}h, initiating graceful cleanup`) @@ -1305,7 +1317,11 @@ export const makeSocket = (config: SocketConfig) => { async function generatePairingKey() { const salt = randomBytes(32) const randomIv = randomBytes(16) - const key = await derivePairingCodeKey(authState.creds.pairingCode!, salt) + if (!authState.creds.pairingCode) { + throw new Error('Pairing code not set') + } + + const key = await derivePairingCodeKey(authState.creds.pairingCode, salt) const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv) return Buffer.concat([salt, randomIv, ciphered]) } @@ -1352,7 +1368,7 @@ export const makeSocket = (config: SocketConfig) => { attrs: { to: S_WHATSAPP_NET, type: 'result', - id: stanza.attrs.id! + id: stanza.attrs.id ?? '' } } await sendNode(iq) @@ -1431,7 +1447,9 @@ export const makeSocket = (config: SocketConfig) => { logger.info('opened connection to WA') clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try - ev.emit('creds.update', { me: { ...authState.creds.me!, lid: node.attrs.lid } }) + if (authState.creds.me) { + ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } }) + } ev.emit('connection.update', { connection: 'open' }) @@ -1454,13 +1472,23 @@ export const makeSocket = (config: SocketConfig) => { const myLID = node.attrs.lid process.nextTick(async () => { try { - const myPN = authState.creds.me!.id + const me = authState.creds.me + if (!me) { + return + } + + const myPN = me.id // Store our own LID-PN mapping await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }]) // Create device list for our own user (needed for bulk migration) - const { user, device } = jidDecode(myPN)! + const decoded = jidDecode(myPN) + if (!decoded) { + return + } + + const { user, device } = decoded await authState.keys.set({ 'device-list': { [user]: [device?.toString() || '0'] @@ -1548,7 +1576,7 @@ export const makeSocket = (config: SocketConfig) => { logger.debug({ name }, 'updated pushName') sendNode({ tag: 'presence', - attrs: { name: name! } + attrs: { name: name ?? '' } }).catch(err => { logger.warn({ trace: err.stack }, 'error in sending presence update on name change') }) diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index dc13fa4e..5a3b61c9 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -127,6 +127,10 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin const msgId = stanza.attrs.id const from = stanza.attrs.from + if (!from) { + throw new Boom('Missing from attribute in message', { data: stanza }) + } + const participant: string | undefined = stanza.attrs.participant const recipient: string | undefined = stanza.attrs.recipient @@ -137,21 +141,21 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) { if (recipient && !isJidMetaAI(recipient)) { - if (!isMe(from!) && !isMeLid(from!)) { + if (!isMe(from) && !isMeLid(from)) { throw new Boom('receipient present, but msg not from me', { data: stanza }) } - if (isMe(from!) || isMeLid(from!)) { + if (isMe(from) || isMeLid(from)) { fromMe = true } chatId = recipient } else { - chatId = from! + chatId = from } msgType = 'chat' - author = from! + author = from } else if (isJidGroup(from)) { if (!participant) { throw new Boom('No participant in group message') @@ -163,28 +167,28 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin msgType = 'group' author = participant - chatId = from! + chatId = from } else if (isJidBroadcast(from)) { if (!participant) { throw new Boom('No participant in group message') } const isParticipantMe = isMe(participant) - if (isJidStatusBroadcast(from!)) { + if (isJidStatusBroadcast(from)) { msgType = isParticipantMe ? 'direct_peer_status' : 'other_status' } else { msgType = isParticipantMe ? 'peer_broadcast' : 'other_broadcast' } fromMe = isParticipantMe - chatId = from! + chatId = from author = participant } else if (isJidNewsletter(from)) { msgType = 'newsletter' - chatId = from! - author = from! + chatId = from + author = from - if (isMe(from!) || isMeLid(from!)) { + if (isMe(from) || isMeLid(from)) { fromMe = true } } else { @@ -207,7 +211,7 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin const fullMessage: WAMessage = { key, category: stanza.attrs.category, - messageTimestamp: +stanza.attrs.t!, + messageTimestamp: +(stanza.attrs.t ?? 0), pushName: pushname, broadcast: isJidBroadcast(from) } @@ -241,8 +245,10 @@ export const decryptMessageNode = ( for (const { tag, attrs, content } of stanza.content) { if (tag === 'verified_name' && content instanceof Uint8Array) { const cert = proto.VerifiedNameCertificate.decode(content) - const details = proto.VerifiedNameCertificate.Details.decode(cert.details!) - fullMessage.verifiedBizName = details.verifiedName + if (cert.details) { + const details = proto.VerifiedNameCertificate.Details.decode(cert.details) + fullMessage.verifiedBizName = details.verifiedName + } } if (tag === 'unavailable' && attrs.type === 'view_once') { diff --git a/src/Utils/event-buffer.ts b/src/Utils/event-buffer.ts index 189cb1d1..c18be26e 100644 --- a/src/Utils/event-buffer.ts +++ b/src/Utils/event-buffer.ts @@ -289,8 +289,9 @@ class AdaptiveTimeoutCalculator { return // Not enough data } - const oldest = this.eventTimestamps[0]! - const newest = this.eventTimestamps[this.eventTimestamps.length - 1]! + const oldest = this.eventTimestamps[0] + const newest = this.eventTimestamps[this.eventTimestamps.length - 1] + if (oldest === undefined || newest === undefined) return const timeSpan = newest - oldest if (timeSpan === 0) return @@ -337,8 +338,9 @@ class AdaptiveTimeoutCalculator { if (this.eventTimestamps.length < 2) { return 0 } - const oldest = this.eventTimestamps[0]! - const newest = this.eventTimestamps[this.eventTimestamps.length - 1]! + const oldest = this.eventTimestamps[0] + const newest = this.eventTimestamps[this.eventTimestamps.length - 1] + if (oldest === undefined || newest === undefined) return 0 const timeSpan = newest - oldest if (timeSpan === 0) return 0 return (this.eventTimestamps.length / timeSpan) * 1000 @@ -633,8 +635,11 @@ export const makeEventBuffer = ( for (const update of chatUpdates) { if (update.conditional) { conditionalChatUpdatesLeft += 1 - newData.chatUpdates[update.id!] = update - delete data.chatUpdates[update.id!] + const updateId = update.id + if (updateId) { + newData.chatUpdates[updateId] = update + delete data.chatUpdates[updateId] + } } } @@ -723,7 +728,7 @@ export const makeEventBuffer = ( const { type } = evData as BaileysEventMap['messages.upsert'] const existingUpserts = Object.values(data.messageUpserts) if (existingUpserts.length > 0) { - const bufferedType = existingUpserts[0]!.type + const bufferedType = existingUpserts[0]?.type if (bufferedType !== type) { logger.debug({ bufferedType, newType: type }, 'messages.upsert type mismatch, emitting buffered messages') // Emit the buffered messages with their correct type @@ -973,7 +978,8 @@ function append( break case 'chats.update': for (const update of eventData as ChatUpdate[]) { - const chatId = update.id! + const chatId = update.id + if (!chatId) continue const conditionMatches = update.conditional ? update.conditional(data) : true if (conditionMatches) { delete update.conditional @@ -1039,8 +1045,9 @@ function append( data.contactUpserts[contact.id] = upsert } - if (data.contactUpdates[contact.id]) { - upsert = Object.assign(data.contactUpdates[contact.id]!, trimUndefined(contact)) as Contact + const existingContactUpdate = data.contactUpdates[contact.id] + if (existingContactUpdate) { + upsert = Object.assign(existingContactUpdate, trimUndefined(contact)) as Contact delete data.contactUpdates[contact.id] } } @@ -1049,7 +1056,8 @@ function append( case 'contacts.update': const contactUpdates = eventData as BaileysEventMap['contacts.update'] for (const update of contactUpdates) { - const id = update.id! + const id = update.id + if (!id) continue // merge into prior upsert const upsert = data.historySets.contacts[id] || data.contactUpserts[id] if (upsert) { @@ -1170,7 +1178,8 @@ function append( case 'groups.update': const groupUpdates = eventData as BaileysEventMap['groups.update'] for (const update of groupUpdates) { - const id = update.id! + const id = update.id + if (!id) continue const groupUpdate = data.groupUpdates[id] || {} if (!data.groupUpdates[id]) { data.groupUpdates[id] = Object.assign(groupUpdate, update) @@ -1202,7 +1211,8 @@ function append( function decrementChatReadCounterIfMsgDidUnread(message: WAMessage) { // decrement chat unread counter // if the message has already been marked read by us - const chatId = message.key.remoteJid! + const chatId = message.key.remoteJid + if (!chatId) return const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId] if ( isRealMessage(message) && @@ -1255,7 +1265,7 @@ function consolidateEvents(data: BufferedEventData) { const messageUpsertList = Object.values(data.messageUpserts) if (messageUpsertList.length) { - const type = messageUpsertList[0]!.type + const type = messageUpsertList[0]?.type map['messages.upsert'] = { messages: messageUpsertList.map(m => m.message), type @@ -1311,7 +1321,7 @@ function consolidateEvents(data: BufferedEventData) { function concatChats>(a: C, b: Partial) { if ( b.unreadCount === null && // neutralize unread counter - a.unreadCount! < 0 + (a.unreadCount ?? 0) < 0 ) { a.unreadCount = undefined b.unreadCount = undefined @@ -1319,8 +1329,8 @@ function concatChats>(a: C, b: Partial) { if (typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number') { b = { ...b } - if (b.unreadCount! >= 0) { - b.unreadCount = Math.max(b.unreadCount!, 0) + Math.max(a.unreadCount, 0) + if ((b.unreadCount ?? 0) >= 0) { + b.unreadCount = Math.max(b.unreadCount ?? 0, 0) + Math.max(a.unreadCount, 0) } } diff --git a/src/Utils/messages-media.ts b/src/Utils/messages-media.ts index 98c107fc..6533d7b2 100644 --- a/src/Utils/messages-media.ts +++ b/src/Utils/messages-media.ts @@ -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 } @@ -414,7 +414,11 @@ export const encryptedStream = async ( let fileLength = 0 const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv) - const hmac = Crypto.createHmac('sha256', macKey!).update(iv) + if (!macKey) { + throw new Boom('Failed to derive media mac key') + } + + const hmac = Crypto.createHmac('sha256', macKey).update(iv) const sha256Plain = Crypto.createHash('sha256') const sha256Enc = Crypto.createHash('sha256') @@ -528,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 }) } @@ -591,8 +595,8 @@ export const downloadEncryptedContent = async ( const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => { if (startByte || endByte) { - const start = bytesFetched >= startByte! ? undefined : Math.max(startByte! - bytesFetched, 0) - const end = bytesFetched + bytes.length < endByte! ? undefined : Math.max(endByte! - bytesFetched, 0) + const start = bytesFetched >= (startByte ?? 0) ? undefined : Math.max((startByte ?? 0) - bytesFetched, 0) + const end = bytesFetched + bytes.length < (endByte ?? 0) ? undefined : Math.max((endByte ?? 0) - bytesFetched, 0) push(bytes.slice(start, end)) @@ -652,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 @@ -860,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 @@ -896,17 +900,25 @@ const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => { * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL */ export const encryptMediaRetryRequest = (key: WAMessageKey, mediaKey: Buffer | Uint8Array, meId: string) => { + if (!key.id) { + throw new Boom('Missing message ID for media retry request') + } + + if (!key.remoteJid) { + throw new Boom('Missing remote JID for media retry request') + } + const recp: proto.IServerErrorReceipt = { stanzaId: key.id } const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish() const iv = Crypto.randomBytes(12) const retryKey = getMediaRetryKey(mediaKey) - const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id!)) + const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id)) const req: BinaryNode = { tag: 'receipt', attrs: { - id: key.id!, + id: key.id, to: jidNormalizedUser(meId), type: 'server-error' }, @@ -925,7 +937,7 @@ export const encryptMediaRetryRequest = (key: WAMessageKey, mediaKey: Buffer | U { tag: 'rmr', attrs: { - jid: key.remoteJid!, + jid: key.remoteJid, from_me: (!!key.fromMe).toString(), // @ts-ignore participant: key.participant || undefined @@ -938,7 +950,10 @@ export const encryptMediaRetryRequest = (key: WAMessageKey, mediaKey: Buffer | U } export const decodeMediaRetryNode = (node: BinaryNode) => { - const rmrNode = getBinaryNodeChild(node, 'rmr')! + const rmrNode = getBinaryNodeChild(node, 'rmr') + if (!rmrNode) { + throw new Boom('Missing rmr node in media retry response') + } const event: BaileysEventMap['messages.media-update'][number] = { key: { @@ -951,7 +966,7 @@ export const decodeMediaRetryNode = (node: BinaryNode) => { const errorNode = getBinaryNodeChild(node, 'error') if (errorNode) { - const errorCode = +errorNode.attrs.code! + const errorCode = +(errorNode.attrs.code ?? '0') event.error = new Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) diff --git a/src/Utils/signal.ts b/src/Utils/signal.ts index fcc9d814..6900743b 100644 --- a/src/Utils/signal.ts +++ b/src/Utils/signal.ts @@ -88,14 +88,18 @@ export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => ({ }) export const parseAndInjectE2ESessions = async (node: BinaryNode, repository: SignalRepositoryWithLIDStore) => { - const extractKey = (key: BinaryNode) => - key - ? { - keyId: getBinaryNodeChildUInt(key, 'id', 3)!, - publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!), - signature: getBinaryNodeChildBuffer(key, 'signature')! - } - : undefined + const extractKey = (key: BinaryNode) => { + if (!key) return undefined + const keyId = getBinaryNodeChildUInt(key, 'id', 3) + const publicKeyBuf = getBinaryNodeChildBuffer(key, 'value') + const signature = getBinaryNodeChildBuffer(key, 'signature') + if (keyId === undefined || !publicKeyBuf || !signature) return undefined + return { + keyId, + publicKey: generateSignalPubKey(publicKeyBuf), + signature + } + } const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user') for (const node of nodes) { assertNodeErrorFree(node) @@ -111,20 +115,26 @@ export const parseAndInjectE2ESessions = async (node: BinaryNode, repository: Si for (const nodesChunk of chunks) { for (const node of nodesChunk) { - const signedKey = getBinaryNodeChild(node, 'skey')! - const key = getBinaryNodeChild(node, 'key')! - const identity = getBinaryNodeChildBuffer(node, 'identity')! - const jid = node.attrs.jid! + const signedKey = getBinaryNodeChild(node, 'skey') + const key = getBinaryNodeChild(node, 'key') + const identity = getBinaryNodeChildBuffer(node, 'identity') + const jid = node.attrs.jid + if (!signedKey || !key || !identity || !jid) continue const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) + if (registrationId === undefined) continue + + const signedPreKey = extractKey(signedKey) + const preKey = extractKey(key) + if (!signedPreKey || !preKey) continue await repository.injectE2ESession({ jid, session: { - registrationId: registrationId!, + registrationId, identityKey: generateSignalPubKey(identity), - signedPreKey: extractKey(signedKey)!, - preKey: extractKey(key)! + signedPreKey, + preKey } }) } @@ -137,14 +147,18 @@ export const extractDeviceJids = ( myLid: string, excludeZeroDevices: boolean ) => { - const { user: myUser, device: myDevice } = jidDecode(myJid)! + const myJidDecoded = jidDecode(myJid) + if (!myJidDecoded) return [] + + const { user: myUser, device: myDevice } = myJidDecoded const extracted: FullJid[] = [] for (const userResult of result) { const { devices, id } = userResult as { devices: ParsedDeviceInfo; id: string } - const decoded = jidDecode(id)!, - { user, server } = decoded + const decoded = jidDecode(id) + if (!decoded) continue + const { user, server } = decoded let { domainType } = decoded const deviceList = devices?.deviceList as DeviceListData[] if (!Array.isArray(deviceList)) continue diff --git a/src/Utils/sticker-pack.ts b/src/Utils/sticker-pack.ts index 0be27536..8360228b 100644 --- a/src/Utils/sticker-pack.ts +++ b/src/Utils/sticker-pack.ts @@ -575,7 +575,7 @@ export const prepareStickerPackMessage = async ( duplicateCount++ // Merge emojis (deduplicate) - const mergedEmojis = Array.from(new Set([...existingMetadata.emojis!, ...emojis])) + const mergedEmojis = Array.from(new Set([...(existingMetadata.emojis ?? []), ...emojis])) existingMetadata.emojis = mergedEmojis // Merge accessibility labels (concatenate with separator if both exist) diff --git a/src/Utils/version-cache.ts b/src/Utils/version-cache.ts index a5cd9679..97b345de 100644 --- a/src/Utils/version-cache.ts +++ b/src/Utils/version-cache.ts @@ -165,19 +165,19 @@ export async function getCachedVersion( } = config // 1. Check memory cache first (fastest) - if (isCacheValid(memoryCache, cacheTtlMs)) { - const age = Date.now() - memoryCache!.fetchedAt + if (isCacheValid(memoryCache, cacheTtlMs) && memoryCache) { + const age = Date.now() - memoryCache.fetchedAt logger?.debug({ age: Math.round(age / 1000) + 's' }, 'Using memory cached version') - return { version: memoryCache!.version, fromCache: true, age, source: 'memory' } + return { version: memoryCache.version, fromCache: true, age, source: 'memory' } } // 2. Check file cache (survives restarts) const fileCache = await readCacheFile(cacheFilePath) - if (isCacheValid(fileCache, cacheTtlMs)) { + if (isCacheValid(fileCache, cacheTtlMs) && fileCache) { memoryCache = fileCache // Update memory cache - const age = Date.now() - fileCache!.fetchedAt + const age = Date.now() - fileCache.fetchedAt logger?.debug({ age: Math.round(age / 1000) + 's' }, 'Using file cached version') - return { version: fileCache!.version, fromCache: true, age, source: 'file' } + return { version: fileCache.version, fromCache: true, age, source: 'file' } } // 3. Need to fetch - but deduplicate concurrent requests diff --git a/src/WABinary/jid-utils.ts b/src/WABinary/jid-utils.ts index bcfa988f..34dd447c 100644 --- a/src/WABinary/jid-utils.ts +++ b/src/WABinary/jid-utils.ts @@ -55,15 +55,15 @@ export const jidEncode = (user: string | number | null, server: JidServer, devic export const jidDecode = (jid: string | undefined): FullJid | undefined => { // todo: investigate how to implement hosted ids in this case const sepIdx = typeof jid === 'string' ? jid.indexOf('@') : -1 - if (sepIdx < 0) { + if (sepIdx < 0 || !jid) { return undefined } - const server = jid!.slice(sepIdx + 1) - const userCombined = jid!.slice(0, sepIdx) + const server = jid.slice(sepIdx + 1) + const userCombined = jid.slice(0, sepIdx) const [userAgent, device] = userCombined.split(':') - const [user, agent] = userAgent!.split('_') + const [user, agent] = (userAgent ?? '').split('_') let domainType = WAJIDDomains.WHATSAPP if (server === 'lid') { @@ -78,7 +78,7 @@ export const jidDecode = (jid: string | undefined): FullJid | undefined => { return { server: server as JidServer, - user: user!, + user: user ?? '', domainType, device: device ? +device : undefined } @@ -114,7 +114,7 @@ export const isAnyPnUser = (jid: string | undefined) => const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/ -export const isJidBot = (jid: string | undefined) => jid && botRegexp.test(jid.split('@')[0]!) && jid.endsWith('@c.us') +export const isJidBot = (jid: string | undefined) => jid && botRegexp.test(jid.split('@')[0] ?? '') && jid.endsWith('@c.us') export const jidNormalizedUser = (jid: string | undefined) => { const result = jidDecode(jid) @@ -129,6 +129,11 @@ export const jidNormalizedUser = (jid: string | undefined) => { export const transferDevice = (fromJid: string, toJid: string) => { const fromDecoded = jidDecode(fromJid) const deviceId = fromDecoded?.device || 0 - const { server, user } = jidDecode(toJid)! + const toDecoded = jidDecode(toJid) + if (!toDecoded) { + return '' + } + + const { server, user } = toDecoded return jidEncode(user, server, deviceId) } diff --git a/src/WAM/encode.ts b/src/WAM/encode.ts index ebb0420a..d0b566ae 100644 --- a/src/WAM/encode.ts +++ b/src/WAM/encode.ts @@ -78,7 +78,11 @@ function encodeEvents(binaryInfo: BinaryInfo) { } const fieldFlag = extended ? FLAG_EVENT : FLAG_FIELD | FLAG_EXTENDED - binaryInfo.buffer.push(serializeData(id!, value, fieldFlag)) + if (id == null) { + continue + } + + binaryInfo.buffer.push(serializeData(id, value, fieldFlag)) } } } diff --git a/upstream-baileys-pr/src/Utils/history.ts b/upstream-baileys-pr/src/Utils/history.ts index 689cf8f2..801b136d 100644 --- a/upstream-baileys-pr/src/Utils/history.ts +++ b/upstream-baileys-pr/src/Utils/history.ts @@ -194,9 +194,13 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { case proto.HistorySync.HistorySyncType.RECENT: case proto.HistorySync.HistorySyncType.FULL: case proto.HistorySync.HistorySyncType.ON_DEMAND: - for (const chat of item.conversations! as Chat[]) { + for (const chat of (item.conversations ?? []) as Chat[]) { + if (!chat.id) { + continue + } + contacts.push({ - id: chat.id!, + id: chat.id, name: chat.name || undefined, lid: chat.lidJid || undefined, phoneNumber: chat.pnJid || undefined @@ -204,7 +208,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { // Source 2: Extract from conversation metadata addLidPnMapping( - extractLidPnFromConversation(chat.id!, chat.lidJid, chat.pnJid) + extractLidPnFromConversation(chat.id, chat.lidJid, chat.pnJid) ) const msgs = chat.messages || [] @@ -242,7 +246,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { message.messageStubParameters?.[0] ) { contacts.push({ - id: message.key.participant || message.key.remoteJid!, + id: message.key.participant || message.key.remoteJid || '', verifiedName: message.messageStubParameters?.[0] }) } @@ -253,8 +257,12 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { break case proto.HistorySync.HistorySyncType.PUSH_NAME: - for (const c of item.pushnames!) { - contacts.push({ id: c.id!, notify: c.pushname! }) + for (const c of item.pushnames ?? []) { + if (!c.id) { + continue + } + + contacts.push({ id: c.id, notify: c.pushname ?? '' }) } break