diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index 3e9c45e..7318d77 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -19,10 +19,11 @@ export const makeChatsSocket = (config: SocketConfig) => { sendNode, query, fetchPrivacySettings, + onUnexpectedError, } = sock const mutationMutex = makeMutex() - + /// helper function to fetch an app state sync key const getAppStateSyncKey = async(keyId: string) => { const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]) return key @@ -176,35 +177,30 @@ export const makeChatsSocket = (config: SocketConfig) => { }] }] }) - const profiles = getBinaryNodeChild(getBinaryNodeChild(results, 'business_profile'), 'profile') - if(!profiles) { - // if not bussines - if(logger.level === 'trace') { - logger.trace({ jid }, 'Not bussines') - } - return + const profileNode = getBinaryNodeChild(results, 'business_profile') + const profiles = getBinaryNodeChild(profileNode, 'profile') + if(profiles) { + const address = getBinaryNodeChild(profiles, 'address') + const description = getBinaryNodeChild(profiles, 'description') + const website = getBinaryNodeChild(profiles, 'website') + const email = getBinaryNodeChild(profiles, 'email') + 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') + 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(), + business_hours: { + timezone: business_hours?.attrs?.timezone, + business_config: business_hours_config?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) + } + } as unknown as WABusinessProfile } - - const address = getBinaryNodeChild(profiles, 'address') - const description = getBinaryNodeChild(profiles, 'description') - const website = getBinaryNodeChild(profiles, 'website') - const email = getBinaryNodeChild(profiles, 'email') - 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') - 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(), - business_hours: { - timezone: business_hours?.attrs?.timezone, - business_config: business_hours_config?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) - } - } as unknown as WABusinessProfile } const updateAccountSyncTimestamp = async(fromTimestamp: number | string) => { @@ -431,7 +427,6 @@ export const makeChatsSocket = (config: SocketConfig) => { } const resyncMainAppState = async() => { - logger.debug('resyncing main app state') await ( @@ -445,7 +440,7 @@ export const makeChatsSocket = (config: SocketConfig) => { ]) ) .catch(err => ( - logger.warn({ trace: err.stack }, 'failed to sync app state') + onUnexpectedError(err, 'main app sync') )) ) } @@ -480,7 +475,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } else if(action?.pushNameSetting) { const me = { ...authState.creds.me!, - name: action?.pushNameSetting?.name! + name: action?.pushNameSetting?.name! } ev.emit('creds.update', { me }) } else if(action?.pinAction) { @@ -640,6 +635,20 @@ export const makeChatsSocket = (config: SocketConfig) => { return appPatch(patch) } + /** + * queries need to be fired on connection open + * help ensure parity with WA Web + * */ + const fireInitQueries = () => ( + Promise.all([ + fetchAbt(), + fetchProps(), + fetchBlocklist(), + fetchPrivacySettings(), + sendPresenceUpdate('available') + ]) + ) + ws.on('CB:presence', handlePresenceUpdate) ws.on('CB:chatstate', handlePresenceUpdate) @@ -680,12 +689,10 @@ export const makeChatsSocket = (config: SocketConfig) => { ev.on('connection.update', ({ connection }) => { if(connection === 'open') { - fetchAbt() - fetchProps() - fetchBlocklist() - fetchPrivacySettings() - - sendPresenceUpdate('available') + fireInitQueries() + .catch( + error => onUnexpectedError(error, 'connection open requests') + ) } }) diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 7501930..e1e29ad 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -1,7 +1,7 @@ import { proto } from '../../WAProto' import { KEY_BUNDLE_TYPE } from '../Defaults' -import { Chat, GroupMetadata, MessageUserReceipt, ParticipantAction, SocketConfig, WAMessageStubType } from '../Types' +import { BaileysEventMap, Chat, GroupMetadata, MessageUserReceipt, ParticipantAction, SocketConfig, WAMessageStubType } from '../Types' import { decodeMessageStanza, downloadAndProcessHistorySyncNotification, encodeBigEndian, generateSignalPubKey, normalizeMessageContent, toNumber, xmppPreKey, xmppSignedPreKey } from '../Utils' import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex' import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getAllBinaryNodeChildren, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser } from '../WABinary' @@ -15,6 +15,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { ev, authState, ws, + onUnexpectedError, assertSessions, assertingPreKeys, sendNode, @@ -94,6 +95,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } ] } + if(node.attrs.recipient) { receipt.attrs.recipient = node.attrs.recipient } @@ -103,9 +105,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } if(retryCount > 1) { - const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1); - - (receipt.content! as BinaryNode[]).push({ + const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1) + const content = receipt.content! as BinaryNode[] + content.push({ tag: 'keys', attrs: { }, content: [ @@ -357,10 +359,13 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' }) } ) + .catch( + error => onUnexpectedError(error, 'processing message') + ) }) ws.on('CB:ack,class:message', async(node: BinaryNode) => { - await sendNode({ + sendNode({ tag: 'ack', attrs: { class: 'receipt', @@ -368,6 +373,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { from: node.attrs.from } }) + .catch(err => onUnexpectedError(err, 'ack message receipt')) logger.debug({ attrs: node.attrs }, 'sending receipt for ack') }) @@ -376,7 +382,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const [child] = getAllBinaryNodeChildren(node) if(!!child?.tag) { - await sendMessageAck(node, { class: 'call', type: child.tag }) + sendMessageAck(node, { class: 'call', type: child.tag }) + .catch( + error => onUnexpectedError(error, 'ack call') + ) } }) @@ -489,11 +498,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { ) } - ws.on('CB:receipt', handleReceipt) - - ws.on('CB:notification', async(node: BinaryNode) => { + const handleNotification = async(node: BinaryNode) => { const remoteJid = node.attrs.from - processingMutex.mutex( + await processingMutex.mutex( remoteJid, () => { const msg = processNotification(node) @@ -516,15 +523,19 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { ) await sendMessageAck(node, { class: 'notification', type: node.attrs.type }) - }) + } - ev.on('messages.upsert', async({ messages, type }) => { + const handleUpsertedMessages = async({ messages, type }: BaileysEventMap['messages.upsert']) => { if(type === 'notify' || type === 'append') { - const chat: Partial = { id: messages[0].key.remoteJid } + const chatId = jidNormalizedUser(messages[0].key.remoteJid) + const chat: Partial = { id: chatId } const contactNameUpdates: { [_: string]: string } = { } for(const msg of messages) { + const normalizedChatId = jidNormalizedUser(msg.key.remoteJid) if(!!msg.pushName) { - const jid = msg.key.fromMe ? jidNormalizedUser(authState.creds.me!.id) : (msg.key.participant || msg.key.remoteJid) + let jid = msg.key.fromMe ? authState.creds.me!.id : (msg.key.participant || msg.key.remoteJid) + jid = jidNormalizedUser(jid) + contactNameUpdates[jid] = msg.pushName // update our pushname too if(msg.key.fromMe && authState.creds.me?.name !== msg.pushName) { @@ -533,7 +544,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } await processingMutex.mutex( - 'p-' + chat.id!, + 'p-' + normalizedChatId, () => processMessage(msg, chat) ) @@ -555,6 +566,29 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { )) } } + } + + ws.on('CB:receipt', node => { + handleReceipt(node) + .catch( + error => onUnexpectedError(error, 'handling receipt') + ) + }) + + ws.on('CB:notification', async(node: BinaryNode) => { + handleNotification(node) + .catch( + error => { + onUnexpectedError(error, 'handling notification') + } + ) + }) + + ev.on('messages.upsert', data => { + handleUpsertedMessages(data) + .catch( + error => onUnexpectedError(error, 'handling upserted messages') + ) }) return { ...sock, processMessage, sendMessageAck, sendRetryRequest } diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index 6303eb7..6f3ba60 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -84,6 +84,14 @@ export const makeSocket = ({ return sendRawMessage(buff) } + /** log & process any unexpected errors */ + const onUnexpectedError = (error: Error, msg: string) => { + logger.error( + { trace: error.stack, output: (error as any).output }, + `unexpected error in '${msg}'` + ) + } + /** await the next incoming message */ const awaitNextMessage = async(sendMsg?: Uint8Array) => { if(ws.readyState !== ws.OPEN) { @@ -200,7 +208,11 @@ export const makeSocket = ({ startKeepAliveRequest() } - /** get some pre-keys and do something with them */ + /** + * get some pre-keys and do something with them + * @param range how many pre-keys to get + * @param execute what to do with them + */ const assertingPreKeys = async(range: number, execute: (keys: { [_: number]: any }) => Promise) => { const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range) @@ -558,6 +570,7 @@ export const makeSocket = ({ sendNode, logout, end, + onUnexpectedError, /** Waits for the connection to WA to reach a state */ waitForConnectionUpdate: bindWaitForConnectionUpdate(ev) }