From 40a1e268aaf4c89b0c08f7dc64725dda397cdfcc Mon Sep 17 00:00:00 2001 From: Adhiraj Singh Date: Fri, 8 Jul 2022 10:38:25 +0530 Subject: [PATCH] feat: add "strictNullChecks" --- src/Defaults/index.ts | 3 +- src/LegacySocket/auth.ts | 4 +- src/LegacySocket/chats.ts | 27 +++++++------- src/LegacySocket/groups.ts | 19 +++++----- src/LegacySocket/messages.ts | 44 +++++++++++----------- src/LegacySocket/socket.ts | 10 ++--- src/Socket/business.ts | 12 +++--- src/Socket/chats.ts | 41 +++++++++++---------- src/Socket/groups.ts | 18 +++++---- src/Socket/messages-recv.ts | 33 +++++++++-------- src/Socket/messages-send.ts | 30 +++++++-------- src/Socket/socket.ts | 12 +++--- src/Store/make-in-memory-store.ts | 51 +++++++++++++++----------- src/Store/make-ordered-dictionary.ts | 2 +- src/Types/GroupMetadata.ts | 2 +- src/Types/Legacy.ts | 3 +- src/Types/Message.ts | 6 +-- src/Types/Socket.ts | 4 +- src/Types/State.ts | 2 +- src/Types/index.ts | 12 +++--- src/Utils/auth-utils.ts | 3 +- src/Utils/business.ts | 44 +++++++++++----------- src/Utils/chat-utils.ts | 37 ++++++++++--------- src/Utils/decode-wa-message.ts | 4 ++ src/Utils/event-buffer.ts | 24 +++++++----- src/Utils/generics.ts | 33 ++++------------- src/Utils/history.ts | 13 ++++--- src/Utils/legacy-msgs.ts | 2 +- src/Utils/messages-media.ts | 46 ++++++++++++----------- src/Utils/messages.ts | 50 +++++++++++++------------ src/Utils/noise-handler.ts | 2 +- src/Utils/process-message.ts | 24 ++++++------ src/Utils/signal.ts | 16 ++++---- src/Utils/use-multi-file-auth-state.ts | 6 +-- src/Utils/validate-connection.ts | 4 +- src/WABinary/Legacy/constants.ts | 2 +- src/WABinary/constants.ts | 4 +- src/WABinary/decode.ts | 4 +- src/WABinary/encode.ts | 4 +- src/WABinary/generic-utils.ts | 16 ++++---- src/WABinary/jid-utils.ts | 15 +++++--- tsconfig.json | 1 + 42 files changed, 350 insertions(+), 339 deletions(-) diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index 306b664..62c9647 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -25,7 +25,7 @@ export const WA_CERT_DETAILS = { SERIAL: 0, } -const BASE_CONNECTION_CONFIG: CommonSocketConfig = { +const BASE_CONNECTION_CONFIG: CommonSocketConfig = { version: version as any, browser: Browsers.baileys('Chrome'), @@ -42,6 +42,7 @@ const BASE_CONNECTION_CONFIG: CommonSocketConfig = { export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { ...BASE_CONNECTION_CONFIG, + auth: undefined as any, downloadHistory: true, markOnlineOnConnect: true, linkPreviewImageThumbnailWidth: 192, diff --git a/src/LegacySocket/auth.ts b/src/LegacySocket/auth.ts index e9016c2..6f6c92d 100644 --- a/src/LegacySocket/auth.ts +++ b/src/LegacySocket/auth.ts @@ -27,7 +27,7 @@ const makeAuthSocket = (config: LegacySocketConfig) => { const socket = makeSocket(config) const { ws } = socket let curveKeys: CurveKeyPair - let initTimeout: NodeJS.Timeout + let initTimeout: NodeJS.Timeout | undefined ws.on('phone-connection', ({ value: phoneConnected }) => { updateState({ legacy: { ...state.legacy, phoneConnected } }) @@ -133,7 +133,7 @@ const makeAuthSocket = (config: LegacySocketConfig) => { generateKeysForAuth(ref, ttl) } })() - let loginTag: string + let loginTag: string | undefined if(canDoLogin) { updateEncKeys() // if we have the info to restore a closed session diff --git a/src/LegacySocket/chats.ts b/src/LegacySocket/chats.ts index 5d1c4dd..7318d86 100644 --- a/src/LegacySocket/chats.ts +++ b/src/LegacySocket/chats.ts @@ -114,9 +114,9 @@ const makeChatsSocket = (config: LegacySocketConfig) => { { tag: 'read', attrs: { - jid: fromMessage.remoteJid, + jid: fromMessage.remoteJid!, count: count.toString(), - index: fromMessage.id, + index: fromMessage.id!, owner: fromMessage.fromMe ? 'true' : 'false' } } @@ -124,7 +124,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => { [ WAMetric.read, WAFlag.ignore ] ) if(config.emitOwnEvents) { - ev.emit('chats.update', [{ id: fromMessage.remoteJid, unreadCount: count < 0 ? -1 : 0 }]) + ev.emit('chats.update', [{ id: fromMessage.remoteJid!, unreadCount: count < 0 ? -1 : 0 }]) } } @@ -225,10 +225,11 @@ const makeChatsSocket = (config: LegacySocketConfig) => { }) // User Profile Name Updates socketEvents.on('CB:Conn,pushname', json => { - const { legacy: { user }, connection } = state - if(connection === 'open' && json[1].pushname !== user.name) { - user.name = json[1].pushname - ev.emit('connection.update', { legacy: { ...state.legacy, user } }) + const { legacy, connection } = state + const { user } = legacy! + if(connection === 'open' && json[1].pushname !== user!.name) { + user!.name = json[1].pushname + ev.emit('connection.update', { legacy: { ...legacy!, user } }) } }) // read updates @@ -354,7 +355,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => { } )) } else if('markRead' in modification) { - const indexKey = getIndexKey(modification.lastMessages) + const indexKey = getIndexKey(modification.lastMessages)! return chatRead(indexKey, modification.markRead ? 0 : -1) } else if('delete' in modification) { chatAttrs.type = 'delete' @@ -363,7 +364,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => { if('lastMessages' in modification) { const indexKey = getIndexKey(modification.lastMessages) if(indexKey) { - chatAttrs.index = indexKey.id + chatAttrs.index = indexKey.id! chatAttrs.owner = indexKey.fromMe ? 'true' : 'false' } } @@ -409,7 +410,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => { content: [ { tag: 'presence', - attrs: { type: type, to: toJid } + attrs: { type: type, to: toJid! } } ] } @@ -463,7 +464,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => { if(config.emitOwnEvents) { const user = { ...state.legacy!.user!, name } ev.emit('connection.update', { legacy: { - ...state.legacy, user + ...state.legacy!, user } }) ev.emit('contacts.update', [{ id: user.id, name }]) } @@ -493,11 +494,11 @@ const makeChatsSocket = (config: LegacySocketConfig) => { const { eurl } = await this.setQuery ([query], [WAMetric.picture, 136], tag) as { eurl: string, status: number } if(config.emitOwnEvents) { - if(jid === user.id) { + if(jid === user?.id) { user.imgUrl = eurl ev.emit('connection.update', { legacy: { - ...state.legacy, + ...state.legacy!, user } }) diff --git a/src/LegacySocket/groups.ts b/src/LegacySocket/groups.ts index b6bbc9b..0bc757a 100644 --- a/src/LegacySocket/groups.ts +++ b/src/LegacySocket/groups.ts @@ -23,11 +23,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { { tag: 'group', attrs: { - author: state.legacy?.user?.id, + author: state.legacy?.user?.id!, id: tag, type: type, - jid: jid, - subject: subject, + jid: jid!, + subject: subject!, }, content: participants ? participants.map(jid => ( @@ -96,7 +96,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { id: jid, owner: attrs?.creator, creation: +attrs?.create, - subject: null, + subject: '', desc, participants } @@ -146,8 +146,8 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { * @param participants people to include in the group */ groupCreate: async(title: string, participants: string[]) => { - const response = await groupQuery('create', null, title, participants) as WAGroupCreateResponse - const gid = response.gid + const response = await groupQuery('create', undefined, title, participants) as WAGroupCreateResponse + const gid = response.gid! let metadata: GroupMetadata try { metadata = await groupMetadataFull(gid) @@ -199,11 +199,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { const metadata = await groupMetadataFull(jid) const node: BinaryNode = { tag: 'description', - attrs: { id: generateMessageID(), prev: metadata?.descId }, + attrs: { id: generateMessageID(), prev: metadata?.descId! }, content: Buffer.from(description, 'utf-8') } - const response = await groupQuery ('description', jid, null, null, [node]) + const response = await groupQuery('description', jid, undefined, undefined, [node]) ev.emit('groups.update', [ { id: jid, desc: description } ]) return response }, @@ -213,7 +213,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { * @param participants the people to add */ groupParticipantsUpdate: async(id: string, participants: string[], action: ParticipantAction) => { - const result: GroupModificationResponse = await groupQuery(action, id, null, participants) + const result: GroupModificationResponse = await groupQuery(action, id, undefined, participants) const jids = Object.keys(result.participants || {}) ev.emit('group-participants.update', { id, participants: jids, action }) return Object.keys(result.participants || {}).map( @@ -237,7 +237,6 @@ const makeGroupsSocket = (config: LegacySocketConfig) => { const metadata: GroupMetadata = { subject: result.name, id: jid, - creation: undefined, owner: state.legacy?.user?.id, participants: result.recipients!.map(({ id }) => ( { id: jidNormalizedUser(id), isAdmin: false, isSuperAdmin: false } diff --git a/src/LegacySocket/messages.ts b/src/LegacySocket/messages.ts index dee6076..6cf5c84 100644 --- a/src/LegacySocket/messages.ts +++ b/src/LegacySocket/messages.ts @@ -47,7 +47,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { count: number, cursor?: WAMessageCursor ) => { - let key: WAMessageKey + let key: WAMessageKey | undefined if(cursor) { key = 'before' in cursor ? cursor.before : cursor.after } @@ -61,7 +61,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { jid: jid, kind: !cursor || 'before' in cursor ? 'before' : 'after', count: count.toString(), - index: key?.id, + index: key?.id!, owner: key?.fromMe === false ? 'false' : 'true', } }, @@ -84,9 +84,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { tag: 'query', attrs: { type: 'media', - index: message.key.id, + index: message.key.id!, owner: message.key.fromMe ? 'true' : 'false', - jid: message.key.remoteJid, + jid: message.key.remoteJid!, epoch: currentEpoch().toString() } }, @@ -158,7 +158,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { { // the key of the deleted message is updated update: { message: null, key: message.key, messageStubType }, - key + key: key! } ]) return @@ -167,7 +167,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { chatUpdate.ephemeralExpiration = protocolMessage.ephemeralExpiration if(isJidGroup(jid)) { - emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || null }) + emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || 0 }) } break @@ -188,18 +188,18 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { switch (message.messageStubType) { case WAMessageStubType.CHANGE_EPHEMERAL_SETTING: chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp - chatUpdate.ephemeralExpiration = +message.messageStubParameters[0] + chatUpdate.ephemeralExpiration = +message.messageStubParameters![0] if(isJidGroup(jid)) { - emitGroupUpdate({ ephemeralDuration: +message.messageStubParameters[0] || null }) + emitGroupUpdate({ ephemeralDuration: +(message.messageStubParameters?.[0] || 0) }) } break case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: - participants = message.messageStubParameters.map (jidNormalizedUser) + participants = message.messageStubParameters!.map (jidNormalizedUser) emitParticipantsUpdate('remove') // mark the chat read only if you left the group - if(participants.includes(user.id)) { + if(participants.includes(user!.id)) { chatUpdate.readOnly = true } @@ -207,24 +207,24 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { case WAMessageStubType.GROUP_PARTICIPANT_ADD: case WAMessageStubType.GROUP_PARTICIPANT_INVITE: case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: - participants = message.messageStubParameters.map (jidNormalizedUser) - if(participants.includes(user.id)) { + participants = message.messageStubParameters!.map (jidNormalizedUser) + if(participants.includes(user!.id)) { chatUpdate.readOnly = null } emitParticipantsUpdate('add') break case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: - const announce = message.messageStubParameters[0] === 'on' + const announce = message.messageStubParameters?.[0] === 'on' emitGroupUpdate({ announce }) break case WAMessageStubType.GROUP_CHANGE_RESTRICT: - const restrict = message.messageStubParameters[0] === 'on' + const restrict = message.messageStubParameters?.[0] === 'on' emitGroupUpdate({ restrict }) break case WAMessageStubType.GROUP_CHANGE_SUBJECT: case WAMessageStubType.GROUP_CREATE: - chatUpdate.name = message.messageStubParameters[0] + chatUpdate.name = message.messageStubParameters?.[0] emitGroupUpdate({ subject: chatUpdate.name }) break } @@ -275,9 +275,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { } ] } - const isMsgToMe = areJidsSameUser(message.key.remoteJid, state.legacy.user?.id || '') + const isMsgToMe = areJidsSameUser(message.key.remoteJid!, state.legacy?.user?.id || '') const flag = isMsgToMe ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself - const mID = message.key.id + const mID = message.key.id! const finalState = isMsgToMe ? WAMessageStatus.READ : WAMessageStatus.SERVER_ACK message.status = WAMessageStatus.PENDING @@ -498,7 +498,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { search: txt, count: count.toString(), page: page.toString(), - jid: inJid + jid: inJid! } }, binaryTag: [24, WAFlag.ignore], @@ -515,7 +515,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { content: AnyMessageContent, options: MiscMessageGenerationOptions & { waitForAck?: boolean } = { waitForAck: true } ) => { - const userJid = state.legacy.user?.id + const userJid = state.legacy?.user?.id if( typeof content === 'object' && 'disappearingMessagesInChat' in content && @@ -530,7 +530,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { await setQuery([ { tag: 'group', - attrs: { id: tag, jid, type: 'prop', author: userJid }, + attrs: { id: tag, jid, type: 'prop', author: userJid! }, content: [ { tag: 'ephemeral', attrs: { value: value.toString() } } ] @@ -542,7 +542,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { content, { logger, - userJid: userJid, + userJid: userJid!, getUrlInfo: generateUrlInfo, upload: waUploadToServer, mediaCache: config.mediaCache, @@ -550,7 +550,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => { } ) - await relayMessage(msg, { waitForAck: options.waitForAck }) + await relayMessage(msg, { waitForAck: !!options.waitForAck }) return msg } } diff --git a/src/LegacySocket/socket.ts b/src/LegacySocket/socket.ts index 18f0024..8f6eab8 100644 --- a/src/LegacySocket/socket.ts +++ b/src/LegacySocket/socket.ts @@ -43,7 +43,7 @@ export const makeSocket = ({ let authInfo: { encKey: Buffer, macKey: Buffer } let keepAliveReq: NodeJS.Timeout - let phoneCheckInterval: NodeJS.Timeout + let phoneCheckInterval: NodeJS.Timeout | undefined let phoneCheckListeners = 0 const phoneConnectionChanged = (value: boolean) => { @@ -267,10 +267,10 @@ export const makeSocket = ({ return result as any } finally { requiresPhoneConnection && clearPhoneCheckInterval() - cancelPhoneChecker && cancelPhoneChecker() + cancelPhoneChecker! && cancelPhoneChecker!() - ws.off(`TAG:${tag}`, onRecv) - ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message + ws.off(`TAG:${tag}`, onRecv!) + ws.off('ws-close', onErr!) // if the socket closes, you'll never receive the message } })(), cancelToken: () => { @@ -290,7 +290,7 @@ export const makeSocket = ({ { json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }: SocketQueryOptions ) => { tag = tag || generateMessageTag(longTag) - const { promise, cancelToken } = waitForMessage(tag, requiresPhoneConnection, timeoutMs) + const { promise, cancelToken } = waitForMessage(tag, !!requiresPhoneConnection, timeoutMs) try { await sendNode({ json, tag, binaryTag }) } catch(error) { diff --git a/src/Socket/business.ts b/src/Socket/business.ts index 0201c57..21838f3 100644 --- a/src/Socket/business.ts +++ b/src/Socket/business.ts @@ -13,7 +13,8 @@ export const makeBusinessSocket = (config: SocketConfig) => { } = sock const getCatalog = async(jid?: string, limit = 10) => { - jid = jidNormalizedUser(jid || authState.creds.me?.id) + jid = jid || authState.creds.me?.id + jid = jidNormalizedUser(jid!) const result = await query({ tag: 'iq', attrs: { @@ -52,7 +53,8 @@ export const makeBusinessSocket = (config: SocketConfig) => { } const getCollections = async(jid?: string, limit = 51) => { - jid = jidNormalizedUser(jid || authState.creds.me?.id) + jid = jid || authState.creds.me?.id + jid = jidNormalizedUser(jid!) const result = await query({ tag: 'iq', attrs: { @@ -165,7 +167,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { const productCatalogEditNode = getBinaryNodeChild(result, 'product_catalog_edit') const productNode = getBinaryNodeChild(productCatalogEditNode, 'product') - return parseProductNode(productNode) + return parseProductNode(productNode!) } const productCreate = async(create: ProductCreate) => { @@ -191,7 +193,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { const productCatalogAddNode = getBinaryNodeChild(result, 'product_catalog_add') const productNode = getBinaryNodeChild(productCatalogAddNode, 'product') - return parseProductNode(productNode) + return parseProductNode(productNode!) } const productDelete = async(productIds: string[]) => { @@ -225,7 +227,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { const productCatalogDelNode = getBinaryNodeChild(result, 'product_catalog_delete') return { - deleted: +productCatalogDelNode.attrs.deleted_count + deleted: +(productCatalogDelNode?.attrs.deleted_count || 0) } } diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index f97eeb8..bb85708 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -67,7 +67,7 @@ export const makeChatsSocket = (config: SocketConfig) => { { tag: 'privacy', attrs: { } } ] }) - privacySettings = reduceBinaryNodeToDictionary(content[0] as BinaryNode, 'category') + privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category') } return privacySettings @@ -135,7 +135,7 @@ export const makeChatsSocket = (config: SocketConfig) => { return results.map(user => { const contact = getBinaryNodeChild(user, 'contact') - return { exists: contact.attrs.type === 'in', jid: user.attrs.jid } + return { exists: contact?.attrs.type === 'in', jid: user.attrs.jid } }).filter(item => item.exists) } @@ -147,8 +147,8 @@ export const makeChatsSocket = (config: SocketConfig) => { if(result) { const status = getBinaryNodeChild(result, 'status') return { - status: status.content!.toString(), - setAt: new Date(+status.attrs.t * 1000) + status: status?.content!.toString(), + setAt: new Date(+(status?.attrs.t || 0) * 1000) } } } @@ -253,18 +253,19 @@ export const makeChatsSocket = (config: SocketConfig) => { const category = getBinaryNodeChild(getBinaryNodeChild(profiles, 'categories'), 'category') const business_hours = getBinaryNodeChild(profiles, 'business_hours') const business_hours_config = business_hours && getBinaryNodeChildren(business_hours, 'business_hours_config') + const websiteStr = website?.content?.toString() return { wid: profiles.attrs?.jid, - address: address?.content.toString(), - description: description?.content.toString(), - website: [website?.content.toString()], - email: email?.content.toString(), - category: category?.content.toString(), + address: address?.content?.toString(), + description: description?.content?.toString() || '', + website: websiteStr ? [websiteStr] : [], + email: email?.content?.toString(), + category: category?.content?.toString(), business_hours: { timezone: business_hours?.attrs?.timezone, business_config: business_hours_config?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) } - } as unknown as WABusinessProfile + } } } @@ -296,7 +297,7 @@ export const makeChatsSocket = (config: SocketConfig) => { processSyncAction( mutation, ev, - authState.creds.me, + authState.creds.me!, recvChats ? { recvChats, accountSettings: authState.creds.accountSettings } : undefined, logger ) @@ -402,7 +403,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } catch(error) { // if retry attempts overshoot // or key not found - const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS || error.output?.statusCode === 404 + const isIrrecoverableError = attemptsMap[name]! >= MAX_SYNC_ATTEMPTS || error.output?.statusCode === 404 logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`) await authState.keys.set({ 'app-state-sync-version': { [name]: null } }) // increment number of retries @@ -468,7 +469,7 @@ export const makeChatsSocket = (config: SocketConfig) => { tag: 'chatstate', attrs: { from: me!.id!, - to: toJid, + to: toJid!, }, content: [ { @@ -492,7 +493,7 @@ export const makeChatsSocket = (config: SocketConfig) => { ) const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => { - let presence: PresenceData + let presence: PresenceData | undefined const jid = attrs.from const participant = attrs.participant || attrs.from if(tag === 'presence') { @@ -606,8 +607,8 @@ export const makeChatsSocket = (config: SocketConfig) => { const { onMutation } = newAppStateChunkHandler(undefined) await decodePatches( name, - [{ ...encodeResult.patch, version: { version: encodeResult.state.version }, }], - initial, + [{ ...encodeResult!.patch, version: { version: encodeResult!.state.version }, }], + initial!, getAppStateSyncKey, onMutation, undefined, @@ -698,10 +699,10 @@ export const makeChatsSocket = (config: SocketConfig) => { if(!!msg.pushName) { let jid = msg.key.fromMe ? authState.creds.me!.id : (msg.key.participant || msg.key.remoteJid) - jid = jidNormalizedUser(jid) + jid = jidNormalizedUser(jid!) if(!msg.key.fromMe) { - ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]) + ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName! }]) } // update our pushname too @@ -724,7 +725,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } ) - const isAnyHistoryMsg = isHistoryMsg(msg.message) + const isAnyHistoryMsg = isHistoryMsg(msg.message!) if(isAnyHistoryMsg) { // we only want to sync app state once we've all the history // restart the app state sync timeout @@ -741,7 +742,7 @@ export const makeChatsSocket = (config: SocketConfig) => { ws.on('CB:chatstate', handlePresenceUpdate) ws.on('CB:ib,,dirty', async(node: BinaryNode) => { - const { attrs } = getBinaryNodeChild(node, 'dirty') + const { attrs } = getBinaryNodeChild(node, 'dirty')! const type = attrs.type switch (type) { case 'account_sync': diff --git a/src/Socket/groups.ts b/src/Socket/groups.ts index c86859d..c82aced 100644 --- a/src/Socket/groups.ts +++ b/src/Socket/groups.ts @@ -120,7 +120,9 @@ export const makeGroupsSocket = (config: SocketConfig) => { ...(description ? { id: generateMessageID() } : { delete: 'true' }), ...(prev ? { prev } : {}) }, - content: description ? [{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }] : null + content: description ? [ + { tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') } + ] : undefined } ] ) @@ -128,17 +130,17 @@ export const makeGroupsSocket = (config: SocketConfig) => { groupInviteCode: async(jid: string) => { const result = await groupQuery(jid, 'get', [{ tag: 'invite', attrs: {} }]) const inviteNode = getBinaryNodeChild(result, 'invite') - return inviteNode.attrs.code + return inviteNode?.attrs.code }, groupRevokeInvite: async(jid: string) => { const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }]) const inviteNode = getBinaryNodeChild(result, 'invite') - return inviteNode.attrs.code + return inviteNode?.attrs.code }, groupAcceptInvite: async(code: string) => { const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }]) const result = getBinaryNodeChild(results, 'group') - return result.attrs.jid + return result?.attrs.jid }, /** * accept a GroupInviteMessage @@ -147,11 +149,11 @@ export const makeGroupsSocket = (config: SocketConfig) => { */ groupAcceptInviteV4: async(key: string | WAMessageKey, inviteMessage: proto.IGroupInviteMessage) => { key = typeof key === 'string' ? { remoteJid: key } : key - const results = await groupQuery(inviteMessage.groupJid, 'set', [{ + const results = await groupQuery(inviteMessage.groupJid!, 'set', [{ tag: 'accept', attrs: { - code: inviteMessage.inviteCode, - expiration: inviteMessage.inviteExpiration.toString(), + code: inviteMessage.inviteCode!, + expiration: inviteMessage.inviteExpiration!.toString(), admin: key.remoteJid! } }]) @@ -254,7 +256,7 @@ export const makeGroupsSocket = (config: SocketConfig) => { export const extractGroupMetadata = (result: BinaryNode) => { - const group = getBinaryNodeChild(result, 'group') + const group = getBinaryNodeChild(result, 'group')! const descChild = getBinaryNodeChild(group, 'description') let desc: string | undefined let descId: string | undefined diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 9eabfa8..6b86337 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -76,7 +76,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const isGroup = !!node.attrs.participant const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds - const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish() + const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account!).finish() await authState.keys.transaction( async() => { const { update, preKeys } = await getNextPreKeys(authState, 1) @@ -147,7 +147,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const from = node.attrs.from if(from === S_WHATSAPP_NET) { const countChild = getBinaryNodeChild(node, 'count') - const count = +countChild.attrs.value + const count = +countChild!.attrs.value const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count') @@ -270,22 +270,23 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => { const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id }))) - - const participant = key.participant || key.remoteJid + const remoteJid = key.remoteJid! + const participant = key.participant || remoteJid // if it's the primary jid sending the request // just re-send the message to everyone // prevents the first message decryption failure - const sendToAll = !jidDecode(participant).device + const sendToAll = !jidDecode(participant)?.device await assertSessions([participant], true) - if(isJidGroup(key.remoteJid)) { - await authState.keys.set({ 'sender-key-memory': { [key.remoteJid]: null } }) + if(isJidGroup(remoteJid)) { + await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } }) } logger.debug({ participant, sendToAll }, 'forced new session for retry recp') for(let i = 0; i < msgs.length;i++) { - if(msgs[i]) { + const msg = msgs[i] + if(msg) { updateSendMessageAgainCount(ids[i], participant) const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] } @@ -295,7 +296,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { msgRelayOpts.participant = participant } - await relayMessage(key.remoteJid, msgs[i], msgRelayOpts) + await relayMessage(key.remoteJid!, msg, msgRelayOpts) } else { logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available') } @@ -443,21 +444,21 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } else if(msg.key.fromMe) { // message was sent by us from a different device type = 'sender' // need to specially handle this case - if(isJidUser(msg.key.remoteJid)) { + if(isJidUser(msg.key.remoteJid!)) { participant = author } } else if(!sendActiveReceipts) { type = 'inactive' } - await sendReceipt(msg.key.remoteJid!, participant, [msg.key.id!], type) + await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type) // send ack for history message - const isAnyHistoryMsg = isHistoryMsg(msg.message) + const isAnyHistoryMsg = isHistoryMsg(msg.message!) if(isAnyHistoryMsg) { - const jid = jidEncode(jidDecode(msg.key.remoteJid!).user, 'c.us') - await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync') + const jid = jidNormalizedUser(msg.key.remoteJid!) + await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync') } } @@ -516,7 +517,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id } const msg = await getMessage(key) if(msg) { - await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false }) + await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false }) } else { logger.warn({ attrs }, 'could not send message again, as it was not found') } @@ -539,7 +540,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { // called when all offline notifs are handled ws.on('CB:ib,,offline', async(node: BinaryNode) => { const child = getBinaryNodeChild(node, 'offline') - const offlineNotifs = +child.attrs.count + const offlineNotifs = +(child?.attrs.count || 0) logger.info(`handled ${offlineNotifs} offline messages/notifications`) await ev.flush() diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 83be76b..73dbc6f 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -48,8 +48,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { hosts: getBinaryNodeChildren(mediaConnNode, 'host').map( item => item.attrs as any ), - auth: mediaConnNode.attrs.auth, - ttl: +mediaConnNode.attrs.ttl, + auth: mediaConnNode!.attrs.auth, + ttl: +mediaConnNode!.attrs.ttl, fetchDate: new Date() } logger.debug('fetched media conn') @@ -78,7 +78,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { if(type === 'sender' && isJidUser(jid)) { node.attrs.recipient = jid - node.attrs.to = participant + node.attrs.to = participant! } else { node.attrs.to = jid if(participant) { @@ -134,10 +134,10 @@ export const makeMessagesSocket = (config: SocketConfig) => { const users: BinaryNode[] = [] jids = Array.from(new Set(jids)) for(let jid of jids) { - const user = jidDecode(jid).user + const user = jidDecode(jid)?.user jid = jidNormalizedUser(jid) - if(userDevicesCache.has(user) && useCache) { - const devices: JidWithDevice[] = userDevicesCache.get(user) + if(userDevicesCache.has(user!) && useCache) { + const devices = userDevicesCache.get(user!)! deviceResults.push(...devices) logger.trace({ user }, 'using cache for devices') @@ -278,7 +278,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { let shouldIncludeDeviceIdentity = false - const { user, server } = jidDecode(jid) + const { user, server } = jidDecode(jid)! const isGroup = server === 'g.us' msgId = msgId || generateMessageID() useUserDevicesCache = useUserDevicesCache !== false @@ -299,7 +299,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { additionalAttributes = { ...additionalAttributes, device_fanout: 'false' } } - const { user, device } = jidDecode(participant) + const { user, device } = jidDecode(participant)! devices.push({ user, device }) } @@ -333,7 +333,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { if(!participant) { const participantsList = groupData.participants.map(p => p.id) - const additionalDevices = await getUSyncDevices(participantsList, useUserDevicesCache, false) + const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false) devices.push(...additionalDevices) } @@ -376,7 +376,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) } else { - const { user: meUser } = jidDecode(meId) + const { user: meUser } = jidDecode(meId)! const encodedMeMsg = encodeWAMessage({ deviceSentMessage: { @@ -389,7 +389,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { devices.push({ user }) devices.push({ user: meUser }) - const additionalDevices = await getUSyncDevices([ meId, jid ], useUserDevicesCache, true) + const additionalDevices = await getUSyncDevices([ meId, jid ], !!useUserDevicesCache, true) devices.push(...additionalDevices) } @@ -434,7 +434,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const stanza: BinaryNode = { tag: 'message', attrs: { - id: msgId, + id: msgId!, type: 'text', ...(additionalAttributes || {}) }, @@ -461,7 +461,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { (stanza.content as BinaryNode[]).push({ tag: 'device-identity', attrs: { }, - content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish() + content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account!).finish() }) logger.debug({ jid }, 'adding device identity') @@ -538,7 +538,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { error = result.error } else { try { - const media = decryptMediaRetryData(result.media!, mediaKey, result.key.id) + const media = decryptMediaRetryData(result.media!, mediaKey, result.key.id!) if(media.result !== proto.MediaRetryNotification.MediaRetryNotificationResultType.SUCCESS) { const resultStr = proto.MediaRetryNotification.MediaRetryNotificationResultType[media.result] throw new Boom( @@ -612,7 +612,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { additionalAttributes.edit = '7' } - await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes }) + await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes }) if(config.emitOwnEvents) { process.nextTick(() => { upsertMessage(fullMsg, 'append') diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index 91abcce..a91151f 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -104,7 +104,7 @@ export const makeSocket = ({ }) if(sendMsg) { - sendRawMessage(sendMsg).catch(onClose) + sendRawMessage(sendMsg).catch(onClose!) } return result @@ -134,9 +134,9 @@ export const makeSocket = ({ ) return result as any } finally { - ws.off(`TAG:${msgId}`, onRecv) - ws.off('close', onErr) // if the socket closes, you'll never receive the message - ws.off('error', onErr) + ws.off(`TAG:${msgId}`, onRecv!) + ws.off('close', onErr!) // if the socket closes, you'll never receive the message + ws.off('error', onErr!) } } @@ -215,7 +215,7 @@ export const makeSocket = ({ ] }) const countChild = getBinaryNodeChild(result, 'count') - return +countChild.attrs.value + return +countChild!.attrs.value } /** generates and uploads a set of pre-keys to the server */ @@ -525,7 +525,7 @@ export const makeSocket = ({ logger.info({ name }, 'updated pushName') sendNode({ tag: 'presence', - attrs: { name } + attrs: { name: name! } }) .catch(err => { logger.warn({ trace: err.stack }, 'error in sending presence update on name change') diff --git a/src/Store/make-in-memory-store.ts b/src/Store/make-in-memory-store.ts index 362a153..4cf4aa8 100644 --- a/src/Store/make-in-memory-store.ts +++ b/src/Store/make-in-memory-store.ts @@ -18,7 +18,7 @@ export const waChatKey = (pin: boolean) => ({ compare: (k1: string, k2: string) => k2.localeCompare (k1) }) -export const waMessageID = (m: WAMessage) => m.key.id +export const waMessageID = (m: WAMessage) => m.key.id || '' export type BaileysInMemoryStoreConfig = { chatKey?: Comparable @@ -28,9 +28,9 @@ export type BaileysInMemoryStoreConfig = { const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID) export default ( - { logger, chatKey }: BaileysInMemoryStoreConfig + { logger: _logger, chatKey }: BaileysInMemoryStoreConfig ) => { - logger = logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) + const logger = _logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) chatKey = chatKey || waChatKey(true) const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB @@ -118,9 +118,9 @@ export default ( ev.on('chats.update', updates => { for(let update of updates) { const result = chats.update(update.id!, chat => { - if(update.unreadCount > 0) { + if(update.unreadCount! > 0) { update = { ...update } - update.unreadCount = chat.unreadCount + update.unreadCount + update.unreadCount = (chat.unreadCount || 0) + update.unreadCount! } Object.assign(chat, update) @@ -166,8 +166,8 @@ export default ( }) ev.on('messages.update', updates => { for(const { update, key } of updates) { - const list = assertMessageList(key.remoteJid) - const result = list.updateAssign(key.id, update) + const list = assertMessageList(key.remoteJid!) + const result = list.updateAssign(key.id!, update) if(!result) { logger.debug({ update }, 'got update for non-existent message') } @@ -178,7 +178,7 @@ export default ( const list = messages[item.jid] list?.clear() } else { - const jid = item.keys[0].remoteJid + const jid = item.keys[0].remoteJid! const list = messages[jid] if(list) { const idSet = new Set(item.keys.map(k => k.id)) @@ -189,8 +189,9 @@ export default ( ev.on('groups.update', updates => { for(const update of updates) { - if(groupMetadata[update.id]) { - Object.assign(groupMetadata[update.id!], update) + const id = update.id! + if(groupMetadata[id]) { + Object.assign(groupMetadata[id], update) } else { logger.debug({ update }, 'got update for non-existant group metadata') } @@ -223,7 +224,7 @@ export default ( ev.on('message-receipt.update', updates => { for(const { key, receipt } of updates) { const obj = messages[key.remoteJid!] - const msg = obj?.get(key.id) + const msg = obj?.get(key.id!) if(msg) { updateMessageWithReceipt(msg, receipt) } @@ -267,12 +268,12 @@ export default ( const mode = !cursor || 'before' in cursor ? 'before' : 'after' const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined - const cursorValue = cursorKey ? list.get(cursorKey.id) : undefined + const cursorValue = cursorKey ? list.get(cursorKey.id!) : undefined let messages: WAMessage[] if(list && mode === 'before' && (!cursorKey || cursorValue)) { if(cursorValue) { - const msgIdx = list.array.findIndex(m => m.key.id === cursorKey.id) + const msgIdx = list.array.findIndex(m => m.key.id === cursorKey?.id) messages = list.array.slice(0, msgIdx) } else { messages = list.array @@ -307,10 +308,10 @@ export default ( return message }, mostRecentMessage: async(jid: string, sock: LegacyWASocket | undefined) => { - let message = messages[jid]?.array.slice(-1)[0] + let message: WAMessage | undefined = messages[jid]?.array.slice(-1)[0] if(!message) { - const [result] = await sock?.fetchMessagesFromWA(jid, 1, undefined) - message = result + const items = await sock?.fetchMessagesFromWA(jid, 1, undefined) + message = items?.[0] } return message @@ -329,24 +330,30 @@ export default ( }, fetchGroupMetadata: async(jid: string, sock: AnyWASocket | undefined) => { if(!groupMetadata[jid]) { - groupMetadata[jid] = await sock?.groupMetadata(jid) + const metadata = await sock?.groupMetadata(jid) + if(metadata) { + groupMetadata[jid] = metadata + } } return groupMetadata[jid] }, fetchBroadcastListInfo: async(jid: string, sock: LegacyWASocket | undefined) => { if(!groupMetadata[jid]) { - groupMetadata[jid] = await sock?.getBroadcastListInfo(jid) + const metadata = await sock?.getBroadcastListInfo(jid) + if(metadata) { + groupMetadata[jid] = metadata + } } return groupMetadata[jid] }, fetchMessageReceipts: async({ remoteJid, id }: WAMessageKey, sock: LegacyWASocket | undefined) => { - const list = messages[remoteJid] - const msg = list?.get(id) - let receipts = msg.userReceipt + const list = messages[remoteJid!] + const msg = list?.get(id!) + let receipts = msg?.userReceipt if(!receipts) { - receipts = await sock?.messageInfo(remoteJid, id) + receipts = await sock?.messageInfo(remoteJid!, id!) if(msg) { msg.userReceipt = receipts } diff --git a/src/Store/make-ordered-dictionary.ts b/src/Store/make-ordered-dictionary.ts index e312320..83b8f51 100644 --- a/src/Store/make-ordered-dictionary.ts +++ b/src/Store/make-ordered-dictionary.ts @@ -2,7 +2,7 @@ function makeOrderedDictionary(idGetter: (item: T) => string) { const array: T[] = [] const dict: { [_: string]: T } = { } - const get = (id: string) => dict[id] + const get = (id: string): T | undefined => dict[id] const update = (item: T) => { const id = idGetter(item) diff --git a/src/Types/GroupMetadata.ts b/src/Types/GroupMetadata.ts index f09e09a..6c23c40 100644 --- a/src/Types/GroupMetadata.ts +++ b/src/Types/GroupMetadata.ts @@ -12,7 +12,7 @@ export interface GroupMetadata { subjectOwner?: string /** group subject modification date */ subjectTime?: number - creation: number + creation?: number desc?: string descOwner?: string descId?: string diff --git a/src/Types/Legacy.ts b/src/Types/Legacy.ts index ad86ca7..b475070 100644 --- a/src/Types/Legacy.ts +++ b/src/Types/Legacy.ts @@ -71,7 +71,8 @@ export type SocketQueryOptions = SocketSendMessageOptions & { requiresPhoneConnection?: boolean } -export type LegacySocketConfig = CommonSocketConfig & { +export type LegacySocketConfig = CommonSocketConfig & { + auth?: LegacyAuthenticationCreds /** max time for the phone to respond to a connectivity test */ phoneResponseTimeMs: number /** max time for WA server to respond before error with 422 */ diff --git a/src/Types/Message.ts b/src/Types/Message.ts index 0577027..14a4eb6 100644 --- a/src/Types/Message.ts +++ b/src/Types/Message.ts @@ -22,7 +22,7 @@ export type WAMediaUpload = Buffer | { url: URL | string } | { stream: Readable /** Set of message types that are supported by the library */ export type MessageType = keyof proto.Message -export type DownloadableMessage = { mediaKey?: Uint8Array, directPath?: string, url?: string } +export type DownloadableMessage = { mediaKey?: Uint8Array | null, directPath?: string | null, url?: string | null } export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | undefined @@ -37,7 +37,7 @@ export interface WAUrlInfo { 'canonical-url': string 'matched-text': string title: string - description: string + description?: string jpegThumbnail?: Buffer } @@ -182,7 +182,7 @@ export type MediaGenerationOptions = { mediaUploadTimeoutMs?: number } export type MessageContentGenerationOptions = MediaGenerationOptions & { - getUrlInfo?: (text: string) => Promise + getUrlInfo?: (text: string) => Promise } export type MessageGenerationOptions = MessageContentGenerationOptions & MessageGenerationOptionsFromContent diff --git a/src/Types/Socket.ts b/src/Types/Socket.ts index 59aa841..95e178b 100644 --- a/src/Types/Socket.ts +++ b/src/Types/Socket.ts @@ -8,9 +8,7 @@ import { MediaConnInfo } from './Message' export type WAVersion = [number, number, number] export type WABrowserDescription = [string, string, string] -export type CommonSocketConfig = { - /** provide an auth state object to maintain the auth state */ - auth?: T +export type CommonSocketConfig = { /** the WS url to connect to WA */ waWebSocketUrl: string | URL /** Fails the connection if the socket times out in this interval */ diff --git a/src/Types/State.ts b/src/Types/State.ts index 5d9784a..53c39b6 100644 --- a/src/Types/State.ts +++ b/src/Types/State.ts @@ -7,7 +7,7 @@ export type ConnectionState = { connection: WAConnectionState /** the error that caused the connection to close */ lastDisconnect?: { - error: Error + error: Error | undefined date: Date } /** is this a new login */ diff --git a/src/Types/index.ts b/src/Types/index.ts index e8aceed..41503af 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -17,7 +17,9 @@ import { CommonSocketConfig } from './Socket' export type MessageRetryMap = { [msgId: string]: number } -export type SocketConfig = CommonSocketConfig & { +export type SocketConfig = CommonSocketConfig & { + /** provide an auth state object to maintain the auth state */ + auth: AuthenticationState /** By default true, should history messages be downloaded and processed */ downloadHistory: boolean /** transaction capability options for SignalKeyStore */ @@ -67,18 +69,16 @@ export type WABusinessHoursConfig = { export type WABusinessProfile = { description: string - email: string + email: string | undefined business_hours: { timezone?: string config?: WABusinessHoursConfig[] business_config?: WABusinessHoursConfig[] } website: string[] - categories: { - id: string - localized_display_name: string - }[] + category?: string wid?: string + address?: string } diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index 76209be..31d0455 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -36,8 +36,7 @@ export const addTransactionCapability = (state: SignalKeyStore, logger: Logger, dbQueriesInTransaction += 1 const result = await state.get(type, idsRequiringFetch) - transactionCache[type] = transactionCache[type] || { } - Object.assign(transactionCache[type], result) + transactionCache[type] = Object.assign(transactionCache[type] || { }, result) } } diff --git a/src/Utils/business.ts b/src/Utils/business.ts index caca656..a79bfb1 100644 --- a/src/Utils/business.ts +++ b/src/Utils/business.ts @@ -14,8 +14,8 @@ export const parseCollectionsNode = (node: BinaryNode) => { const collectionsNode = getBinaryNodeChild(node, 'collections') const collections = getBinaryNodeChildren(collectionsNode, 'collection').map( collectionNode => { - const id = getBinaryNodeChildString(collectionNode, 'id') - const name = getBinaryNodeChildString(collectionNode, 'name') + const id = getBinaryNodeChildString(collectionNode, 'id')! + const name = getBinaryNodeChildString(collectionNode, 'name')! const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode) return { @@ -36,14 +36,14 @@ export const parseOrderDetailsNode = (node: BinaryNode) => { const orderNode = getBinaryNodeChild(node, 'order') const products = getBinaryNodeChildren(orderNode, 'product').map( productNode => { - const imageNode = getBinaryNodeChild(productNode, 'image') + const imageNode = getBinaryNodeChild(productNode, 'image')! return { - id: getBinaryNodeChildString(productNode, 'id'), - name: getBinaryNodeChildString(productNode, 'name'), - imageUrl: getBinaryNodeChildString(imageNode, 'url'), - price: +getBinaryNodeChildString(productNode, 'price'), - currency: getBinaryNodeChildString(productNode, 'currency'), - quantity: +getBinaryNodeChildString(productNode, 'quantity') + id: getBinaryNodeChildString(productNode, 'id')!, + name: getBinaryNodeChildString(productNode, 'name')!, + imageUrl: getBinaryNodeChildString(imageNode, 'url')!, + price: +getBinaryNodeChildString(productNode, 'price')!, + currency: getBinaryNodeChildString(productNode, 'currency')!, + quantity: +getBinaryNodeChildString(productNode, 'quantity')! } } ) @@ -52,8 +52,8 @@ export const parseOrderDetailsNode = (node: BinaryNode) => { const orderDetails: OrderDetails = { price: { - total: +getBinaryNodeChildString(priceNode, 'total'), - currency: getBinaryNodeChildString(priceNode, 'currency'), + total: +getBinaryNodeChildString(priceNode, 'total')!, + currency: getBinaryNodeChildString(priceNode, 'currency')!, }, products } @@ -172,24 +172,24 @@ export const toProductNode = (productId: string | undefined, product: ProductCre export const parseProductNode = (productNode: BinaryNode) => { const isHidden = productNode.attrs.is_hidden === 'true' - const id = getBinaryNodeChildString(productNode, 'id') + const id = getBinaryNodeChildString(productNode, 'id')! - const mediaNode = getBinaryNodeChild(productNode, 'media') - const statusInfoNode = getBinaryNodeChild(productNode, 'status_info') + const mediaNode = getBinaryNodeChild(productNode, 'media')! + const statusInfoNode = getBinaryNodeChild(productNode, 'status_info')! const product: Product = { id, imageUrls: parseImageUrls(mediaNode), reviewStatus: { - whatsapp: getBinaryNodeChildString(statusInfoNode, 'status'), + whatsapp: getBinaryNodeChildString(statusInfoNode, 'status')!, }, availability: 'in stock', - name: getBinaryNodeChildString(productNode, 'name'), + name: getBinaryNodeChildString(productNode, 'name')!, retailerId: getBinaryNodeChildString(productNode, 'retailer_id'), url: getBinaryNodeChildString(productNode, 'url'), - description: getBinaryNodeChildString(productNode, 'description'), - price: +getBinaryNodeChildString(productNode, 'price'), - currency: getBinaryNodeChildString(productNode, 'currency'), + description: getBinaryNodeChildString(productNode, 'description')!, + price: +getBinaryNodeChildString(productNode, 'price')!, + currency: getBinaryNodeChildString(productNode, 'currency')!, isHidden, } @@ -246,15 +246,15 @@ export const uploadingNecessaryImages = async(images: WAMediaUpload[], waUploadT const parseImageUrls = (mediaNode: BinaryNode) => { const imgNode = getBinaryNodeChild(mediaNode, 'image') return { - requested: getBinaryNodeChildString(imgNode, 'request_image_url'), - original: getBinaryNodeChildString(imgNode, 'original_image_url') + requested: getBinaryNodeChildString(imgNode, 'request_image_url')!, + original: getBinaryNodeChildString(imgNode, 'original_image_url')! } } const parseStatusInfo = (mediaNode: BinaryNode): CatalogStatus => { const node = getBinaryNodeChild(mediaNode, 'status_info') return { - status: getBinaryNodeChildString(node, 'status'), + status: getBinaryNodeChildString(node, 'status')!, canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true', } } \ No newline at end of file diff --git a/src/Utils/chat-utils.ts b/src/Utils/chat-utils.ts index 4677520..f41c1c4 100644 --- a/src/Utils/chat-utils.ts +++ b/src/Utils/chat-utils.ts @@ -220,7 +220,7 @@ export const decodeSyncdMutations = async( const encContent = content.slice(0, -32) const ogValueMac = content.slice(-32) if(validateMacs) { - const contentHmac = generateMac(operation, encContent, record.keyId!.id!, key.valueMacKey) + const contentHmac = generateMac(operation!, encContent, record.keyId!.id!, key.valueMacKey) if(Buffer.compare(contentHmac, ogValueMac) !== 0) { throw new Boom('HMAC content verification failed') } @@ -231,7 +231,7 @@ export const decodeSyncdMutations = async( if(validateMacs) { const hmac = hmacSign(syncAction.index, key.indexKey) - if(Buffer.compare(hmac, record.index!.blob) !== 0) { + if(Buffer.compare(hmac, record.index!.blob!) !== 0) { throw new Boom('HMAC index verification failed') } } @@ -242,7 +242,7 @@ export const decodeSyncdMutations = async( ltGenerator.mix({ indexMac: record.index!.blob!, valueMac: ogValueMac, - operation: operation + operation: operation! }) } @@ -258,13 +258,13 @@ export const decodeSyncdPatch = async( validateMacs: boolean ) => { if(validateMacs) { - const base64Key = Buffer.from(msg.keyId!.id).toString('base64') + const base64Key = Buffer.from(msg.keyId!.id!).toString('base64') const mainKeyObj = await getAppStateSyncKey(base64Key) const mainKey = mutationKeys(mainKeyObj.keyData!) const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32)) - const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey) - if(Buffer.compare(patchMac, msg.patchMac) !== 0) { + const patchMac = generatePatchMac(msg.snapshotMac!, mutationmacs, toNumber(msg.version!.version!), name, mainKey.patchMacKey) + if(Buffer.compare(patchMac, msg.patchMac!) !== 0) { throw new Boom('Invalid patch mac') } } @@ -418,10 +418,10 @@ export const decodePatches = async( logger?.trace({ name, version }, 'downloading external patch') const ref = await downloadExternalPatch(syncd.externalMutations) logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch') - syncd.mutations.push(...ref.mutations) + syncd.mutations?.push(...ref.mutations) } - const patchVersion = toNumber(version.version!) + const patchVersion = toNumber(version!.version!) newState.version = patchVersion const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber @@ -439,7 +439,7 @@ export const decodePatches = async( const result = mutationKeys(keyEnc.keyData!) const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey) - if(Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) { + if(Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) { throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`) } } @@ -482,7 +482,6 @@ export const chatModificationToAppPatch = ( } if(m.key.participant) { - m.key = { ...m.key } m.key.participant = jidNormalizedUser(m.key.participant) } @@ -627,7 +626,7 @@ export const processSyncAction = ( if( isValidPatchBasedOnMessageRange(id, archiveAction.messageRange) || !isInitialSync - || !accountSettings.unarchiveChats + || !accountSettings?.unarchiveChats ) { // basically we don't need to fire an "archive" update if the chat is being marked unarchvied // this only applies for the initial sync @@ -661,19 +660,21 @@ export const processSyncAction = ( } ] }) } else if(action?.contactAction) { - ev.emit('contacts.upsert', [{ id, name: action.contactAction!.fullName }]) + ev.emit('contacts.upsert', [{ id, name: action.contactAction!.fullName! }]) } else if(action?.pushNameSetting) { if(me?.name !== action?.pushNameSetting) { ev.emit('creds.update', { me: { ...me, name: action?.pushNameSetting?.name! } }) } } else if(action?.pinAction) { - ev.emit('chats.update', [{ id, pin: action.pinAction?.pinned ? toNumber(action.timestamp) : null }]) + ev.emit('chats.update', [{ id, pin: action.pinAction?.pinned ? toNumber(action.timestamp!) : null }]) } else if(action?.unarchiveChatsSetting) { const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats ev.emit('creds.update', { accountSettings: { unarchiveChats } }) - logger.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`) - accountSettings.unarchiveChats = unarchiveChats + logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`) + if(accountSettings) { + accountSettings.unarchiveChats = unarchiveChats + } } else if(action?.starAction) { ev.emit('messages.update', [ { @@ -689,12 +690,12 @@ export const processSyncAction = ( ev.emit('chats.delete', [id]) } } else { - logger.warn({ syncAction, id }, 'unprocessable update') + logger?.warn({ syncAction, id }, 'unprocessable update') } - function isValidPatchBasedOnMessageRange(id: string, msgRange: proto.ISyncActionMessageRange) { + function isValidPatchBasedOnMessageRange(id: string, msgRange: proto.ISyncActionMessageRange | null | undefined) { const chat = recvChats?.[id] - const lastMsgTimestamp = msgRange.lastMessageTimestamp || msgRange.lastSystemMessageTimestamp || 0 + const lastMsgTimestamp = msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0 const chatLastMsgTimestamp = chat?.lastMsgRecvTimestamp || 0 return lastMsgTimestamp >= chatLastMsgTimestamp } diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index ef4aa6f..a4e1cfc 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -56,6 +56,8 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat chatId = from author = participant + } else { + throw new Boom('Unknown message type', { data: stanza }) } const sender = msgType === 'chat' ? author : chatId @@ -117,6 +119,8 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat const user = isJidUser(sender) ? sender : author msgBuffer = await decryptSignalProto(user, e2eType, content as Buffer, auth) break + default: + throw new Error(`Unknown e2e type: ${e2eType}`) } let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer)) diff --git a/src/Utils/event-buffer.ts b/src/Utils/event-buffer.ts index 97b4aef..b8d5f97 100644 --- a/src/Utils/event-buffer.ts +++ b/src/Utils/event-buffer.ts @@ -96,6 +96,8 @@ export const makeEventBuffer = (logger: Logger): BaileysBufferableEventEmitter = isBuffering = true return true } + + return false }, async flush() { if(!isBuffering) { @@ -209,12 +211,13 @@ function append( case 'contacts.update': const contactUpdates = eventData as BaileysEventMap['contacts.update'] for(const update of contactUpdates) { + const id = update.id! const upsert = data.contactUpserts[update.id!] if(upsert) { Object.assign(upsert, update) } else { - const contactUpdate = data.contactUpdates[update.id] || { } - data.contactUpdates[update.id] = Object.assign(contactUpdate, update) + const contactUpdate = data.contactUpdates[id] || { } + data.contactUpdates[id] = Object.assign(contactUpdate, update) } } @@ -312,8 +315,9 @@ function append( case 'groups.update': const groupUpdates = eventData as BaileysEventMap['groups.update'] for(const update of groupUpdates) { - const groupUpdate = data.groupUpdates[update.id] || { } - data.groupUpdates[update.id] = Object.assign(groupUpdate, update) + const id = update.id! + const groupUpdate = data.groupUpdates[id] || { } + data.groupUpdates[id] = Object.assign(groupUpdate, update) } break @@ -324,12 +328,12 @@ 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! const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId] if( isRealMessage(message) && shouldIncrementChatUnread(message) - && typeof chat?.unreadCount !== 'undefined' + && typeof chat?.unreadCount === 'number' && chat.unreadCount > 0 ) { logger.debug({ chatId: chat.id }, 'decrementing chat counter') @@ -413,16 +417,16 @@ function consolidateEvents(data: BufferedEventData) { function concatChats>(a: C, b: C) { if(b.unreadCount === null) { // neutralize unread counter - if(a.unreadCount < 0) { + if(a.unreadCount! < 0) { a.unreadCount = undefined b.unreadCount = undefined } } - if(typeof a.unreadCount !== 'undefined' && typeof b.unreadCount !== 'undefined') { + 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) { + b.unreadCount = Math.max(b.unreadCount!, 0) + Math.max(a.unreadCount, 0) } } diff --git a/src/Utils/generics.ts b/src/Utils/generics.ts index e5f317d..b59da12 100644 --- a/src/Utils/generics.ts +++ b/src/Utils/generics.ts @@ -86,40 +86,21 @@ export const encodeBigEndian = (e: number, t = 4) => { return a } -export const toNumber = (t: Long | number): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as any).low) : t) - -export function shallowChanges (old: T, current: T, { lookForDeletedKeys }: {lookForDeletedKeys: boolean}): Partial { - const changes: Partial = {} - for(const key in current) { - if(old[key] !== current[key]) { - changes[key] = current[key] || null - } - } - - if(lookForDeletedKeys) { - for(const key in old) { - if(!changes[key] && old[key] !== current[key]) { - changes[key] = current[key] || null - } - } - } - - return changes -} +export const toNumber = (t: Long | number | null | undefined): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as any).low) : t) /** unix timestamp of a date in seconds */ export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime() / 1000) export type DebouncedTimeout = ReturnType -export const debouncedTimeout = (intervalMs: number = 1000, task: () => void = undefined) => { - let timeout: NodeJS.Timeout +export const debouncedTimeout = (intervalMs: number = 1000, task?: () => void) => { + let timeout: NodeJS.Timeout | undefined return { start: (newIntervalMs?: number, newTask?: () => void) => { task = newTask || task intervalMs = newIntervalMs || intervalMs timeout && clearTimeout(timeout) - timeout = setTimeout(task, intervalMs) + timeout = setTimeout(() => task?.(), intervalMs) }, cancel: () => { timeout && clearTimeout(timeout) @@ -155,7 +136,7 @@ export const delayCancellable = (ms: number) => { return { delay, cancel } } -export async function promiseTimeout(ms: number, promise: (resolve: (v?: T)=>void, reject: (error) => void) => void) { +export async function promiseTimeout(ms: number | undefined, promise: (resolve: (v?: T)=>void, reject: (error) => void) => void) { if(!ms) { return new Promise (promise) } @@ -185,7 +166,7 @@ export async function promiseTimeout(ms: number, promise: (resolve: (v?: T)=> export const generateMessageID = () => 'BAE5' + randomBytes(6).toString('hex').toUpperCase() export function bindWaitForEvent>(ev: CommonBaileysEventEmitter, event: T) { - return async(check: (u: BaileysEventMap[T]) => boolean, timeoutMs?: number) => { + return async(check: (u: BaileysEventMap[T]) => boolean | undefined, timeoutMs?: number) => { let listener: (item: BaileysEventMap[T]) => void let closeListener: any await ( @@ -291,7 +272,7 @@ const STATUS_MAP: { [_: string]: proto.WebMessageInfo.WebMessageInfoStatus } = { * @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.WebMessageInfoStatus.DELIVERY_ACK } diff --git a/src/Utils/history.ts b/src/Utils/history.ts index ac7d495..c3249bc 100644 --- a/src/Utils/history.ts +++ b/src/Utils/history.ts @@ -35,7 +35,7 @@ export const processHistoryMessage = ( switch (item.syncType) { case proto.HistorySync.HistorySyncHistorySyncType.INITIAL_BOOTSTRAP: case proto.HistorySync.HistorySyncHistorySyncType.RECENT: - for(const chat of item.conversations) { + for(const chat of item.conversations!) { const contactId = `c:${chat.id}` if(chat.name && !historyCache.has(contactId)) { contacts.push({ id: chat.id, name: chat.name }) @@ -43,15 +43,16 @@ export const processHistoryMessage = ( } const msgs = chat.messages || [] - for(const { message } of msgs) { + for(const item of msgs) { + const message = item.message! const uqId = `${message.key.remoteJid}:${message.key.id}` if(!historyCache.has(uqId)) { messages.push(message) - const curItem = recvChats[message.key.remoteJid] + const curItem = recvChats[message.key.remoteJid!] const timestamp = toNumber(message.messageTimestamp) if(!message.key.fromMe && (!curItem || timestamp > curItem.lastMsgRecvTimestamp)) { - recvChats[message.key.remoteJid] = { lastMsgRecvTimestamp: timestamp } + recvChats[message.key.remoteJid!] = { lastMsgRecvTimestamp: timestamp } // keep only the most recent message in the chat array chat.messages = [{ message }] } @@ -72,10 +73,10 @@ export const processHistoryMessage = ( break case proto.HistorySync.HistorySyncHistorySyncType.PUSH_NAME: - for(const c of item.pushnames) { + for(const c of item.pushnames!) { const contactId = `c:${c.id}` if(!historyCache.has(contactId)) { - contacts.push({ notify: c.pushname, id: c.id }) + contacts.push({ notify: c.pushname!, id: c.id! }) historyCache.add(contactId) } } diff --git a/src/Utils/legacy-msgs.ts b/src/Utils/legacy-msgs.ts index c728e94..7dfd0d5 100644 --- a/src/Utils/legacy-msgs.ts +++ b/src/Utils/legacy-msgs.ts @@ -29,7 +29,7 @@ export const decodeWAMessage = ( // If a query was done, the server will respond with the same message tag we sent the query with const messageTag: string = message.slice(0, commaIndex).toString() let json: any - let tags: WATag + let tags: WATag | undefined if(data.length) { const possiblyEnc = (data.length > 32 && data.length % 16 === 0) if(typeof data === 'string' || !possiblyEnc) { diff --git a/src/Utils/messages-media.ts b/src/Utils/messages-media.ts index 2fb9394..45b438d 100644 --- a/src/Utils/messages-media.ts +++ b/src/Utils/messages-media.ts @@ -62,7 +62,11 @@ export const hkdfInfoKey = (type: MediaType) => { } /** generates all the keys required to encrypt/decrypt & sign a media message */ -export function getMediaKeys(buffer: Uint8Array | string, mediaType: MediaType): MediaDecryptionKeyInfo { +export function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): MediaDecryptionKeyInfo { + if(!buffer) { + throw new Boom('Cannot derive from empty media key') + } + if(typeof buffer === 'string') { buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64') } @@ -163,13 +167,13 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) { const musicMetadata = await import('music-metadata') let metadata: IAudioMetadata if(Buffer.isBuffer(buffer)) { - metadata = await musicMetadata.parseBuffer(buffer, null, { duration: true }) + metadata = await musicMetadata.parseBuffer(buffer, undefined, { duration: true }) } else if(typeof buffer === 'string') { const rStream = createReadStream(buffer) - metadata = await musicMetadata.parseStream(rStream, null, { duration: true }) + metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true }) rStream.close() } else { - metadata = await musicMetadata.parseStream(buffer, null, { duration: true }) + metadata = await musicMetadata.parseStream(buffer, undefined, { duration: true }) } return metadata.format.duration @@ -216,7 +220,7 @@ export async function generateThumbnail( logger?: Logger } ) { - let thumbnail: string + let thumbnail: string | undefined if(mediaType === 'image') { const buff = await extractImageThumb(file) thumbnail = buff.toString('base64') @@ -259,8 +263,8 @@ export const encryptedStream = async( // const encWriteStream = createWriteStream(encBodyPath) const encWriteStream = new Readable({ read: () => {} }) - let bodyPath: string - let writeStream: WriteStream + let bodyPath: string | undefined + let writeStream: WriteStream | undefined let didSaveToTmpPath = false if(type === 'file') { bodyPath = (media as any).url @@ -272,7 +276,7 @@ export const encryptedStream = async( let fileLength = 0 const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv) - let hmac = Crypto.createHmac('sha256', macKey).update(iv) + let hmac = Crypto.createHmac('sha256', macKey!).update(iv) let sha256Plain = Crypto.createHash('sha256') let sha256Enc = Crypto.createHash('sha256') @@ -323,7 +327,7 @@ export const encryptedStream = async( } } catch(error) { encWriteStream.destroy(error) - writeStream.destroy(error) + writeStream?.destroy(error) aes.destroy(error) hmac.destroy(error) sha256Plain.destroy(error) @@ -353,7 +357,7 @@ export const downloadContentFromMessage = ( type: MediaType, opts: MediaDownloadOptions = { } ) => { - const downloadUrl = url || getUrlFromDirectPath(directPath) + const downloadUrl = url || getUrlFromDirectPath(directPath!) const keys = getMediaKeys(mediaKey, type) return downloadEncryptedContent(downloadUrl, keys, opts) @@ -410,8 +414,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! ? undefined : Math.max(startByte! - bytesFetched, 0) + const end = bytesFetched + bytes.length < endByte! ? undefined : Math.max(endByte! - bytesFetched, 0) push(bytes.slice(start, end)) @@ -486,13 +490,13 @@ export function extensionForMediaMessage(message: WAMessageContent) { return extension } -export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: CommonSocketConfig, refreshMediaConn: (force: boolean) => Promise): WAMediaUploadFunction => { +export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: CommonSocketConfig, refreshMediaConn: (force: boolean) => Promise): WAMediaUploadFunction => { return async(stream, { mediaType, fileEncSha256B64, timeoutMs }) => { const { default: axios } = await import('axios') // send a query JSON to obtain the url & auth token to upload our media let uploadInfo = await refreshMediaConn(false) - let urls: { mediaUrl: string, directPath: string } + let urls: { mediaUrl: string, directPath: string } | undefined const hosts = [ ...customUploadHosts, ...uploadInfo.hosts ] const chunks: Buffer[] = [] @@ -500,7 +504,7 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C chunks.push(chunk) } - let reqBody = Buffer.concat(chunks) + const reqBody = Buffer.concat(chunks) for(const { hostname, maxContentLengthBytes } of hosts) { logger.debug(`uploading to "${hostname}"`) @@ -550,9 +554,6 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C } } - // clear buffer just to be sure we're releasing the memory - reqBody = undefined - if(!urls) { throw new Boom( 'Media upload failed on all hosts', @@ -583,12 +584,12 @@ export const encryptMediaRetryRequest = ( 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' }, @@ -607,8 +608,9 @@ export const encryptMediaRetryRequest = ( { tag: 'rmr', attrs: { - jid: key.remoteJid, + jid: key.remoteJid!, from_me: (!!key.fromMe).toString(), + // @ts-ignore participant: key.participant || undefined } } @@ -619,7 +621,7 @@ export const encryptMediaRetryRequest = ( } export const decodeMediaRetryNode = (node: BinaryNode) => { - const rmrNode = getBinaryNodeChild(node, 'rmr') + const rmrNode = getBinaryNodeChild(node, 'rmr')! const event: BaileysEventMap['messages.media-update'][number] = { key: { diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 54f973a..39c668a 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -75,13 +75,17 @@ export const prepareWAMessageMedia = async( ) => { const logger = options.logger - let mediaType: typeof MEDIA_KEYS[number] + let mediaType: typeof MEDIA_KEYS[number] | undefined for(const key of MEDIA_KEYS) { if(key in message) { mediaType = key } } + if(!mediaType) { + throw new Boom('Invalid media type', { statusCode: 400 }) + } + const uploadData: MediaUploadData = { ...message, media: message[mediaType] @@ -106,15 +110,14 @@ export const prepareWAMessageMedia = async( // check for cache hit if(cacheableKey) { - const mediaBuff: Buffer = options.mediaCache!.get(cacheableKey) + const mediaBuff = options.mediaCache!.get(cacheableKey) if(mediaBuff) { logger?.debug({ cacheableKey }, 'got media cache hit') const obj = WAProto.Message.decode(mediaBuff) const key = `${mediaType}Message` - delete uploadData.media - Object.assign(obj[key], { ...uploadData }) + Object.assign(obj[key], { ...uploadData, media: undefined }) return obj } @@ -153,12 +156,12 @@ export const prepareWAMessageMedia = async( (async() => { try { if(requiresThumbnailComputation) { - uploadData.jpegThumbnail = await generateThumbnail(bodyPath, mediaType as any, options) + uploadData.jpegThumbnail = await generateThumbnail(bodyPath!, mediaType as any, options) logger?.debug('generated thumbnail') } if(requiresDurationComputation) { - uploadData.seconds = await getAudioDuration(bodyPath) + uploadData.seconds = await getAudioDuration(bodyPath!) logger?.debug('computed audio duration') } } catch(error) { @@ -177,8 +180,6 @@ export const prepareWAMessageMedia = async( } ) - delete uploadData.media - const obj = WAProto.Message.fromObject({ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject( { @@ -189,13 +190,14 @@ export const prepareWAMessageMedia = async( fileSha256, fileLength, mediaKeyTimestamp: unixTimestampSeconds(), - ...uploadData + ...uploadData, + media: undefined } ) }) if(cacheableKey) { - logger.debug({ cacheableKey }, 'set cache') + logger?.debug({ cacheableKey }, 'set cache') options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish()) } @@ -232,8 +234,8 @@ export const generateForwardMessageContent = ( } // hacky copy - content = normalizeMessageContent(message.message) - content = proto.Message.decode(proto.Message.encode(content).finish()) + content = normalizeMessageContent(content) + content = proto.Message.decode(proto.Message.encode(content!).finish()) let key = Object.keys(content)[0] as MessageType @@ -428,8 +430,8 @@ export const generateWAMessageFromContent = ( if(quoted) { const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid) - let quotedMsg = normalizeMessageContent(quoted.message) - const msgType = getContentType(quotedMsg) + let quotedMsg = normalizeMessageContent(quoted.message)! + const msgType = getContentType(quotedMsg)! // strip any redundant properties quotedMsg = proto.Message.fromObject({ [msgType]: quotedMsg[msgType] }) @@ -439,7 +441,7 @@ export const generateWAMessageFromContent = ( } const contextInfo: proto.IContextInfo = message[key].contextInfo || { } - contextInfo.participant = jidNormalizedUser(participant) + contextInfo.participant = jidNormalizedUser(participant!) contextInfo.stanzaId = quoted.key.id contextInfo.quotedMessage = quotedMsg @@ -521,7 +523,7 @@ export const getContentType = (content: WAProto.IMessage | undefined) => { * @param content * @returns */ -export const normalizeMessageContent = (content: WAMessageContent | undefined): WAMessageContent => { +export const normalizeMessageContent = (content: WAMessageContent | null | undefined): WAMessageContent | undefined => { content = content?.ephemeralMessage?.message?.viewOnceMessage?.message || content?.ephemeralMessage?.message || content?.viewOnceMessage?.message || @@ -614,13 +616,13 @@ export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => { const uqKey = `${remoteJid}:${participant || ''}` if(!keyMap[uqKey]) { keyMap[uqKey] = { - jid: remoteJid, - participant, + jid: remoteJid!, + participant: participant!, messageIds: [] } } - keyMap[uqKey].messageIds.push(id) + keyMap[uqKey].messageIds.push(id!) } } @@ -650,7 +652,7 @@ export const downloadMediaMessage = async( if(ctx) { if(axios.isAxiosError(error)) { // check if the message requires a reupload - if(REUPLOAD_REQUIRED_STATUS.includes(error.response?.status)) { + if(REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!)) { ctx.logger.info({ key: message.key }, 'sending reupload media request...') // request reupload message = await ctx.reuploadRequest(message) @@ -670,9 +672,9 @@ export const downloadMediaMessage = async( } const contentType = getContentType(mContent) - const mediaType = contentType.replace('Message', '') as MediaType - const media = mContent[contentType] - if(typeof media !== 'object' || !('url' in media)) { + const mediaType = contentType?.replace('Message', '') as MediaType + const media = mContent[contentType!] + if(!media || typeof media !== 'object' || !('url' in media)) { throw new Boom(`"${contentType}" message is not a media message`) } @@ -691,7 +693,7 @@ export const downloadMediaMessage = async( } /** Checks whether the given message is a media message; if it is returns the inner content */ -export const assertMediaContent = (content: proto.IMessage) => { +export const assertMediaContent = (content: proto.IMessage | null | undefined) => { content = extractMessageContent(content) const mediaContent = content?.documentMessage || content?.imageMessage diff --git a/src/Utils/noise-handler.ts b/src/Utils/noise-handler.ts index 68b6167..ee9de0b 100644 --- a/src/Utils/noise-handler.ts +++ b/src/Utils/noise-handler.ts @@ -97,7 +97,7 @@ export const makeNoiseHandler = ( finishInit, processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => { authenticate(serverHello!.ephemeral!) - mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral!)) + mixIntoKey(Curve.sharedKey(privateKey, serverHello!.ephemeral!)) const decStaticContent = decrypt(serverHello!.static!) mixIntoKey(Curve.sharedKey(privateKey, decStaticContent)) diff --git a/src/Utils/process-message.ts b/src/Utils/process-message.ts index b5f2e02..3943a79 100644 --- a/src/Utils/process-message.ts +++ b/src/Utils/process-message.ts @@ -42,7 +42,7 @@ export const isRealMessage = (message: proto.IWebMessageInfo) => { const normalizedContent = normalizeMessageContent(message.message) return ( !!normalizedContent - || MSG_MISSED_CALL_TYPES.has(message.messageStubType) + || MSG_MISSED_CALL_TYPES.has(message.messageStubType!) ) && !normalizedContent?.protocolMessage && !normalizedContent?.reactionMessage @@ -59,7 +59,7 @@ const processMessage = async( const meId = creds.me!.id const { accountSettings } = creds - const chat: Partial = { id: jidNormalizedUser(message.key.remoteJid) } + const chat: Partial = { id: jidNormalizedUser(message.key.remoteJid!) } if(isRealMessage(message)) { chat.conversationTimestamp = toNumber(message.messageTimestamp) @@ -79,7 +79,7 @@ const processMessage = async( if(protocolMsg) { switch (protocolMsg.type) { case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION: - const histNotification = protocolMsg!.historySyncNotification + const histNotification = protocolMsg!.historySyncNotification! logger?.info({ histNotification, id: message.key.id }, 'got history notification') @@ -117,10 +117,10 @@ const processMessage = async( await keyStore.transaction( async() => { for(const { keyData, keyId } of keys) { - const strKeyId = Buffer.from(keyId.keyId!).toString('base64') + const strKeyId = Buffer.from(keyId!.keyId!).toString('base64') logger?.info({ strKeyId }, 'injecting new app state sync key') - await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData } }) + await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData! } }) newAppStateSyncKeyId = strKeyId } @@ -176,7 +176,7 @@ const processMessage = async( switch (message.messageStubType) { case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: - participants = message.messageStubParameters + participants = message.messageStubParameters || [] emitParticipantsUpdate('remove') // mark the chat read only if you left the group if(participantsIncludesMe()) { @@ -187,7 +187,7 @@ const processMessage = async( case WAMessageStubType.GROUP_PARTICIPANT_ADD: case WAMessageStubType.GROUP_PARTICIPANT_INVITE: case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: - participants = message.messageStubParameters + participants = message.messageStubParameters || [] if(participantsIncludesMe()) { chat.readOnly = false } @@ -195,23 +195,23 @@ const processMessage = async( emitParticipantsUpdate('add') break case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE: - participants = message.messageStubParameters + participants = message.messageStubParameters || [] emitParticipantsUpdate('demote') break case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE: - participants = message.messageStubParameters + participants = message.messageStubParameters || [] emitParticipantsUpdate('promote') break case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: - const announceValue = message.messageStubParameters[0] + const announceValue = message.messageStubParameters?.[0] emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' }) break case WAMessageStubType.GROUP_CHANGE_RESTRICT: - const restrictValue = message.messageStubParameters[0] + const restrictValue = message.messageStubParameters?.[0] emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' }) break case WAMessageStubType.GROUP_CHANGE_SUBJECT: - const name = message.messageStubParameters[0] + const name = message.messageStubParameters?.[0] chat.name = name emitGroupUpdate({ subject: name }) break diff --git a/src/Utils/signal.ts b/src/Utils/signal.ts index 4a76034..a4a7e3d 100644 --- a/src/Utils/signal.ts +++ b/src/Utils/signal.ts @@ -142,7 +142,7 @@ export const processSenderKeyMessage = async( auth: SignalAuthState ) => { const builder = new GroupSessionBuilder(signalStorage(auth)) - const senderName = jidToSignalSenderKeyName(item.groupId, authorJid) + const senderName = jidToSignalSenderKeyName(item.groupId!, authorJid) const senderMsg = new SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage) const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName]) @@ -203,9 +203,7 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut const extractKey = (key: BinaryNode) => ( key ? ({ keyId: getBinaryNodeChildUInt(key, 'id', 3), - publicKey: generateSignalPubKey( - getBinaryNodeChildBuffer(key, 'value') - ), + publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!), signature: getBinaryNodeChildBuffer(key, 'signature'), }) : undefined ) @@ -217,9 +215,9 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut await Promise.all( nodes.map( async node => { - const signedKey = getBinaryNodeChild(node, 'skey') - const key = getBinaryNodeChild(node, 'key') - const identity = getBinaryNodeChildBuffer(node, 'identity') + const signedKey = getBinaryNodeChild(node, 'skey')! + const key = getBinaryNodeChild(node, 'key')! + const identity = getBinaryNodeChildBuffer(node, 'identity')! const jid = node.attrs.jid const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) @@ -237,13 +235,13 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut } export const extractDeviceJids = (result: BinaryNode, myJid: string, excludeZeroDevices: boolean) => { - const { user: myUser, device: myDevice } = jidDecode(myJid) + const { user: myUser, device: myDevice } = jidDecode(myJid)! const extracted: JidWithDevice[] = [] for(const node of result.content as BinaryNode[]) { const list = getBinaryNodeChild(node, 'list')?.content if(list && Array.isArray(list)) { for(const item of list) { - const { user } = jidDecode(item.attrs.jid) + const { user } = jidDecode(item.attrs.jid)! const devicesNode = getBinaryNodeChild(item, 'devices') const deviceListNode = getBinaryNodeChild(devicesNode, 'device-list') if(Array.isArray(deviceListNode?.content)) { diff --git a/src/Utils/use-multi-file-auth-state.ts b/src/Utils/use-multi-file-auth-state.ts index 69f909b..085c9ff 100644 --- a/src/Utils/use-multi-file-auth-state.ts +++ b/src/Utils/use-multi-file-auth-state.ts @@ -15,12 +15,12 @@ import { BufferJSON } from './generics' export const useMultiFileAuthState = async(folder: string): Promise<{ state: AuthenticationState, saveCreds: () => Promise }> => { const writeData = (data: any, file: string) => { - return writeFile(join(folder, fixFileName(file)), JSON.stringify(data, BufferJSON.replacer)) + return writeFile(join(folder, fixFileName(file)!), JSON.stringify(data, BufferJSON.replacer)) } const readData = async(file: string) => { try { - const data = await readFile(join(folder, fixFileName(file)), { encoding: 'utf-8' }) + const data = await readFile(join(folder, fixFileName(file)!), { encoding: 'utf-8' }) return JSON.parse(data, BufferJSON.reviver) } catch(error) { return null @@ -29,7 +29,7 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut const removeData = async(file: string) => { try { - await unlink(fixFileName(file)) + await unlink(fixFileName(file)!) } catch{ } diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index c6fd515..3d64b3f 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -45,7 +45,7 @@ const getClientPayload = (config: ClientPayloadConfig): proto.IClientPayload => } export const generateLoginNode = (userJid: string, config: ClientPayloadConfig): proto.IClientPayload => { - const { user, device } = jidDecode(userJid) + const { user, device } = jidDecode(userJid)! const payload: proto.IClientPayload = { ...getClientPayload(config), passive: true, @@ -137,7 +137,7 @@ export const configureSuccessfulPairing = ( const deviceMsg = Buffer.concat([ Buffer.from([6, 1]), deviceDetails, signedIdentityKey.public, accountSignatureKey ]) account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg) // do not provide the "accountSignatureKey" back - account.accountSignatureKey = null + account.accountSignatureKey = Buffer.alloc(0) const identity = createSignalIdentity(jid, accountSignatureKey) const accountEnc = proto.ADVSignedDeviceIdentity.encode(account).finish() diff --git a/src/WABinary/Legacy/constants.ts b/src/WABinary/Legacy/constants.ts index ad9e943..79e4b38 100644 --- a/src/WABinary/Legacy/constants.ts +++ b/src/WABinary/Legacy/constants.ts @@ -201,5 +201,5 @@ export const SINGLE_BYTE_TOKENS = [ export const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { } for(let i = 0;i < SINGLE_BYTE_TOKENS.length;i++) { - TOKEN_MAP[SINGLE_BYTE_TOKENS[i]] = { index: i } + TOKEN_MAP[SINGLE_BYTE_TOKENS[i]!] = { index: i } } \ No newline at end of file diff --git a/src/WABinary/constants.ts b/src/WABinary/constants.ts index d6028ae..0e4c439 100644 --- a/src/WABinary/constants.ts +++ b/src/WABinary/constants.ts @@ -25,14 +25,14 @@ export const DOUBLE_BYTE_TOKENS = [ ['contact_blacklist', 'Asia/Jakarta', 'media.fepa10-1.fna.whatsapp.net', 'media.fmex10-3.fna.whatsapp.net', 'disorder_prefetching_start_when_empty', 'America/Bogota', 'use_local_probing_rx_bitrate', 'America/Argentina/Buenos_Aires', 'cross_post', 'media.fabb1-1.fna.whatsapp.net', 'media.fbog4-2.fna.whatsapp.net', 'media.fcgk9-1.fna.whatsapp.net', 'media.fcmn2-1.fna.whatsapp.net', 'media.fdel3-1.fna.whatsapp.net', 'media.ffjr1-1.fna.whatsapp.net', 'media.fgdl5-1.fna.whatsapp.net', 'media.flpb1-2.fna.whatsapp.net', 'media.fmex2-1.fna.whatsapp.net', 'media.frba2-2.fna.whatsapp.net', 'media.fros2-2.fna.whatsapp.net', 'media.fruh2-1.fna.whatsapp.net', 'media.fybz2-2.fna.whatsapp.net', 'options', '20', 'a', '017', '018', 'mute_always', 'user_notice', 'Asia/Kolkata', 'gif_provider', 'locked', 'media-gua1-1.cdn.whatsapp.net', 'piggyback_exclude_force_flush', '24', 'media.frec39-1.fna.whatsapp.net', 'user_remove', 'file_max_size', 'cond_packet_loss_pct_ema_alpha', 'media.facc1-1.fna.whatsapp.net', 'media.fadb2-1.fna.whatsapp.net', 'media.faly3-1.fna.whatsapp.net', 'media.fbdo6-2.fna.whatsapp.net', 'media.fcmn2-2.fna.whatsapp.net', 'media.fctg1-3.fna.whatsapp.net', 'media.ffez1-2.fna.whatsapp.net', 'media.fist1-3.fna.whatsapp.net', 'media.fist2-2.fna.whatsapp.net', 'media.flim2-2.fna.whatsapp.net', 'media.fmct2-3.fna.whatsapp.net', 'media.fpei3-1.fna.whatsapp.net', 'media.frba3-1.fna.whatsapp.net', 'media.fsdu8-2.fna.whatsapp.net', 'media.fstu2-1.fna.whatsapp.net', 'media_type', 'receipt_agg', '016', 'enable_pli_for_crc_mismatch', 'live', 'enc_rekey', 'frskmsg', 'd', 'media.fdel11-2.fna.whatsapp.net', 'proto', '2250', 'audio_piggyback_enable_cache', 'skip_nack_if_ltrp_sent', 'mark_dtx_jb_frames', 'web_service_delay', '7282', 'catalog_send_all', 'outgoing', '360', '30', 'LIMITED', '019', 'audio_picker', 'bpv2_phase', 'media.fada1-7.fna.whatsapp.net', 'media.faep7-1.fna.whatsapp.net', 'media.fbko1-2.fna.whatsapp.net', 'media.fbni1-2.fna.whatsapp.net', 'media.fbtz1-1.fna.whatsapp.net', 'media.fbtz1-8.fna.whatsapp.net', 'media.fcjs3-1.fna.whatsapp.net', 'media.fesb3-2.fna.whatsapp.net', 'media.fgdl5-4.fna.whatsapp.net', 'media.fist2-1.fna.whatsapp.net', 'media.flhe2-2.fna.whatsapp.net', 'media.flim2-1.fna.whatsapp.net', 'media.fmex1-1.fna.whatsapp.net', 'media.fpat3-2.fna.whatsapp.net', 'media.fpat3-3.fna.whatsapp.net', 'media.fros2-1.fna.whatsapp.net', 'media.fsdu8-1.fna.whatsapp.net', 'media.fsub3-2.fna.whatsapp.net', 'payments_chat_plugin', 'cond_congestion_no_rtcp_thr', 'green_alert', 'not-a-biz', '..', 'shops_pdp_urls_config', 'source', 'media-dus1-1.cdn.whatsapp.net', 'mute_video', '01b', 'currency', 'max_keys', 'resume_check', 'contact_array', 'qr_scanning', '23', 'b', 'media.fbfh15-1.fna.whatsapp.net', 'media.flim22-1.fna.whatsapp.net', 'media.fsdu11-1.fna.whatsapp.net', 'media.fsdu15-1.fna.whatsapp.net', 'Chrome', 'fts_version', '60', 'media.fada1-6.fna.whatsapp.net', 'media.faep4-2.fna.whatsapp.net', 'media.fbaq5-1.fna.whatsapp.net', 'media.fbni1-1.fna.whatsapp.net', 'media.fcai3-2.fna.whatsapp.net', 'media.fdel3-2.fna.whatsapp.net', 'media.fdmm3-2.fna.whatsapp.net', 'media.fhex3-1.fna.whatsapp.net', 'media.fisb4-2.fna.whatsapp.net', 'media.fkhi5-2.fna.whatsapp.net', 'media.flos2-1.fna.whatsapp.net', 'media.fmct2-1.fna.whatsapp.net', 'media.fntr7-1.fna.whatsapp.net', 'media.frak3-1.fna.whatsapp.net', 'media.fruh5-2.fna.whatsapp.net', 'media.fsub6-1.fna.whatsapp.net', 'media.fuab1-2.fna.whatsapp.net', 'media.fuio1-1.fna.whatsapp.net', 'media.fver1-1.fna.whatsapp.net', 'media.fymy1-1.fna.whatsapp.net', 'product_catalog', '1380', 'audio_oob_fec_max_pkts', '22', '254', 'media-ort2-2.cdn.whatsapp.net', 'media-sjc3-1.cdn.whatsapp.net', '1600', '01a', '01c', '405', 'key_frame_interval', 'body', 'media.fcgh20-1.fna.whatsapp.net', 'media.fesb10-2.fna.whatsapp.net', '125', '2000', 'media.fbsb1-1.fna.whatsapp.net', 'media.fcmn3-2.fna.whatsapp.net', 'media.fcpq1-1.fna.whatsapp.net', 'media.fdel1-2.fna.whatsapp.net', 'media.ffor2-1.fna.whatsapp.net', 'media.fgdl1-4.fna.whatsapp.net', 'media.fhex2-1.fna.whatsapp.net', 'media.fist1-2.fna.whatsapp.net', 'media.fjed5-2.fna.whatsapp.net', 'media.flim6-4.fna.whatsapp.net', 'media.flos2-2.fna.whatsapp.net', 'media.fntr6-2.fna.whatsapp.net', 'media.fpku3-2.fna.whatsapp.net', 'media.fros8-1.fna.whatsapp.net', 'media.fymy1-2.fna.whatsapp.net', 'ul_bw', 'ltrp_qp_offset', 'request', 'nack', 'dtx_delay_state_reset', 'timeoffline', '28', '01f', '32', 'enable_ltr_pool', 'wa_msys_crypto', '01d', '58', 'dtx_freeze_hg_update', 'nack_if_rpsi_throttled', '253', '840', 'media.famd15-1.fna.whatsapp.net', 'media.fbog17-2.fna.whatsapp.net', 'media.fcai19-2.fna.whatsapp.net', 'media.fcai21-4.fna.whatsapp.net', 'media.fesb10-4.fna.whatsapp.net', 'media.fesb10-5.fna.whatsapp.net', 'media.fmaa12-1.fna.whatsapp.net', 'media.fmex11-3.fna.whatsapp.net', 'media.fpoa33-1.fna.whatsapp.net', '1050', '021', 'clean', 'cond_range_ema_packet_loss_pct', 'media.fadb6-5.fna.whatsapp.net', 'media.faqp4-1.fna.whatsapp.net', 'media.fbaq3-1.fna.whatsapp.net', 'media.fbel2-1.fna.whatsapp.net', 'media.fblr4-2.fna.whatsapp.net', 'media.fclo8-1.fna.whatsapp.net', 'media.fcoo1-2.fna.whatsapp.net', 'media.ffjr1-4.fna.whatsapp.net', 'media.ffor9-1.fna.whatsapp.net', 'media.fisb3-1.fna.whatsapp.net', 'media.fkhi2-2.fna.whatsapp.net', 'media.fkhi4-1.fna.whatsapp.net', 'media.fpbc1-2.fna.whatsapp.net', 'media.fruh2-2.fna.whatsapp.net', 'media.fruh5-1.fna.whatsapp.net', 'media.fsub3-1.fna.whatsapp.net', 'payments_transaction_limit', '252', '27', '29', 'tintagel', '01e', '237', '780', 'callee_updated_payload', '020', '257', 'price', '025', '239', 'payments_cs_phone_number', 'mediaretry', 'w:auth:backup:token', 'Glass.caf', 'max_bitrate', '240', '251', '660', 'media.fbog16-1.fna.whatsapp.net', 'media.fcgh21-1.fna.whatsapp.net', 'media.fkul19-2.fna.whatsapp.net', 'media.flim21-2.fna.whatsapp.net', 'media.fmex10-4.fna.whatsapp.net', '64', '33', '34', '35', 'interruption', 'media.fabv3-1.fna.whatsapp.net', 'media.fadb6-1.fna.whatsapp.net', 'media.fagr1-1.fna.whatsapp.net', 'media.famd1-1.fna.whatsapp.net', 'media.famm6-1.fna.whatsapp.net', 'media.faqp2-3.fna.whatsapp.net'], ] -export const SINGLE_BYTE_TOKENS = [ +export const SINGLE_BYTE_TOKENS: (string | null)[] = [ '', 'xmlstreamstart', 'xmlstreamend', 's.whatsapp.net', 'type', 'participant', 'from', 'receipt', 'id', 'broadcast', 'status', 'message', 'notification', 'notify', 'to', 'jid', 'user', 'class', 'offline', 'g.us', 'result', 'mediatype', 'enc', 'skmsg', 'off_cnt', 'xmlns', 'presence', 'participants', 'ack', 't', 'iq', 'device_hash', 'read', 'value', 'media', 'picture', 'chatstate', 'unavailable', 'text', 'urn:xmpp:whatsapp:push', 'devices', 'verified_name', 'contact', 'composing', 'edge_routing', 'routing_info', 'item', 'image', 'verified_level', 'get', 'fallback_hostname', '2', 'media_conn', '1', 'v', 'handshake', 'fallback_class', 'count', 'config', 'offline_preview', 'download_buckets', 'w:profile:picture', 'set', 'creation', 'location', 'fallback_ip4', 'msg', 'urn:xmpp:ping', 'fallback_ip6', 'call-creator', 'relaylatency', 'success', 'subscribe', 'video', 'business_hours_config', 'platform', 'hostname', 'version', 'unknown', '0', 'ping', 'hash', 'edit', 'subject', 'max_buckets', 'download', 'delivery', 'props', 'sticker', 'name', 'last', 'contacts', 'business', 'primary', 'preview', 'w:p', 'pkmsg', 'call-id', 'retry', 'prop', 'call', 'auth_ttl', 'available', 'relay_id', 'last_id', 'day_of_week', 'w', 'host', 'seen', 'bits', 'list', 'atn', 'upload', 'is_new', 'w:stats', 'key', 'paused', 'specific_hours', 'multicast', 'stream:error', 'mmg.whatsapp.net', 'code', 'deny', 'played', 'profile', 'fna', 'device-list', 'close_time', 'latency', 'gcm', 'pop', 'audio', '26', 'w:web', 'open_time', 'error', 'auth', 'ip4', 'update', 'profile_options', 'config_value', 'category', 'catalog_not_created', '00', 'config_code', 'mode', 'catalog_status', 'ip6', 'blocklist', 'registration', '7', 'web', 'fail', 'w:m', 'cart_enabled', 'ttl', 'gif', '300', 'device_orientation', 'identity', 'query', '401', 'media-gig2-1.cdn.whatsapp.net', 'in', '3', 'te2', 'add', 'fallback', 'categories', 'ptt', 'encrypt', 'notice', 'thumbnail-document', 'item-not-found', '12', 'thumbnail-image', 'stage', 'thumbnail-link', 'usync', 'out', 'thumbnail-video', '8', '01', 'context', 'sidelist', 'thumbnail-gif', 'terminate', 'not-authorized', 'orientation', 'dhash', 'capability', 'side_list', 'md-app-state', 'description', 'serial', 'readreceipts', 'te', 'business_hours', 'md-msg-hist', 'tag', 'attribute_padding', 'document', 'open_24h', 'delete', 'expiration', 'active', 'prev_v_id', 'true', 'passive', 'index', '4', 'conflict', 'remove', 'w:gp2', 'config_expo_key', 'screen_height', 'replaced', '02', 'screen_width', 'uploadfieldstat', '2:47DEQpj8', 'media-bog1-1.cdn.whatsapp.net', 'encopt', 'url', 'catalog_exists', 'keygen', 'rate', 'offer', 'opus', 'media-mia3-1.cdn.whatsapp.net', 'privacy', 'media-mia3-2.cdn.whatsapp.net', 'signature', 'preaccept', 'token_id', 'media-eze1-1.cdn.whatsapp.net' ] export const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { } for(let i = 0;i < SINGLE_BYTE_TOKENS.length;i++) { - TOKEN_MAP[SINGLE_BYTE_TOKENS[i]] = { index: i } + TOKEN_MAP[SINGLE_BYTE_TOKENS[i]!] = { index: i } } for(let i = 0;i < DOUBLE_BYTE_TOKENS.length;i++) { diff --git a/src/WABinary/decode.ts b/src/WABinary/decode.ts index a119af4..6095c55 100644 --- a/src/WABinary/decode.ts +++ b/src/WABinary/decode.ts @@ -153,7 +153,7 @@ export const decodeDecompressedBinaryNode = ( const readString = (tag: number): string => { if(tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) { - return SINGLE_BYTE_TOKENS[tag] + return SINGLE_BYTE_TOKENS[tag] || '' } switch (tag) { @@ -163,7 +163,7 @@ export const decodeDecompressedBinaryNode = ( case TAGS.DICTIONARY_3: return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte()) case TAGS.LIST_EMPTY: - return null + return '' case TAGS.BINARY_8: return readStringFromChars(readByte()) case TAGS.BINARY_20: diff --git a/src/WABinary/encode.ts b/src/WABinary/encode.ts index a2209bb..695365f 100644 --- a/src/WABinary/encode.ts +++ b/src/WABinary/encode.ts @@ -1,6 +1,6 @@ import * as constants from './constants' -import { jidDecode } from './jid-utils' +import { FullJid, jidDecode } from './jid-utils' import type { BinaryNode, BinaryNodeCodingOptions } from './types' export const encodeBinaryNode = ( @@ -52,7 +52,7 @@ export const encodeBinaryNode = ( pushBytes(bytes) } - const writeJid = ({ agent, device, user, server }: ReturnType) => { + const writeJid = ({ agent, device, user, server }: FullJid) => { if(typeof agent !== 'undefined' || typeof device !== 'undefined') { pushByte(TAGS.AD_JID) pushByte(agent || 0) diff --git a/src/WABinary/generic-utils.ts b/src/WABinary/generic-utils.ts index ebe994a..dd38620 100644 --- a/src/WABinary/generic-utils.ts +++ b/src/WABinary/generic-utils.ts @@ -4,9 +4,9 @@ import { BinaryNode } from './types' // some extra useful utilities -export const getBinaryNodeChildren = ({ content }: BinaryNode, childTag: string) => { - if(Array.isArray(content)) { - return content.filter(item => item.tag === childTag) +export const getBinaryNodeChildren = (node: BinaryNode | undefined, childTag: string) => { + if(Array.isArray(node?.content)) { + return node!.content.filter(item => item.tag === childTag) } return [] @@ -20,20 +20,20 @@ export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => { return [] } -export const getBinaryNodeChild = ({ content }: BinaryNode, childTag: string) => { - if(Array.isArray(content)) { - return content.find(item => item.tag === childTag) +export const getBinaryNodeChild = (node: BinaryNode | undefined, childTag: string) => { + if(Array.isArray(node?.content)) { + return node?.content.find(item => item.tag === childTag) } } -export const getBinaryNodeChildBuffer = (node: BinaryNode, childTag: string) => { +export const getBinaryNodeChildBuffer = (node: BinaryNode | undefined, childTag: string) => { const child = getBinaryNodeChild(node, childTag)?.content if(Buffer.isBuffer(child) || child instanceof Uint8Array) { return child } } -export const getBinaryNodeChildString = (node: BinaryNode, childTag: string) => { +export const getBinaryNodeChildString = (node: BinaryNode | undefined, childTag: string) => { const child = getBinaryNodeChild(node, childTag)?.content if(Buffer.isBuffer(child) || child instanceof Uint8Array) { return Buffer.from(child).toString('utf-8') diff --git a/src/WABinary/jid-utils.ts b/src/WABinary/jid-utils.ts index d02c022..cdb60e4 100644 --- a/src/WABinary/jid-utils.ts +++ b/src/WABinary/jid-utils.ts @@ -11,18 +11,23 @@ export type JidWithDevice = { device?: number } +export type FullJid = JidWithDevice & { + server: JidServer | string + agent?: number +} + export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => { return `${user || ''}${!!agent ? `_${agent}` : ''}${!!device ? `:${device}` : ''}@${server}` } -export const jidDecode = (jid: string) => { +export const jidDecode = (jid: string | undefined): FullJid | undefined => { const sepIdx = typeof jid === 'string' ? jid.indexOf('@') : -1 if(sepIdx < 0) { 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('_') @@ -36,7 +41,7 @@ export const jidDecode = (jid: string) => { } /** is the jid a user */ -export const areJidsSameUser = (jid1: string, jid2: string) => ( +export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) => ( jidDecode(jid1)?.user === jidDecode(jid2)?.user ) /** is the jid a user */ @@ -49,6 +54,6 @@ export const isJidGroup = (jid: string) => (jid?.endsWith('@g.us')) export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast' export const jidNormalizedUser = (jid: string) => { - const { user, server } = jidDecode(jid) + const { user, server } = jidDecode(jid)! return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer) } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 756c69e..3f8dd8c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "checkJs": false, "outDir": "lib", "strict": false, + "strictNullChecks": true, "skipLibCheck": true, "noImplicitThis": true, "esModuleInterop": true,