From dfaeef0db18f3bc1b1f62e392387b33180fdf8d0 Mon Sep 17 00:00:00 2001 From: Adhiraj Singh Date: Thu, 4 Feb 2021 19:04:50 +0530 Subject: [PATCH] Fix initial-data-received called multiple times The latest android version has messages with the last: true flag set on all messages, causing incorrect events being fired off. This update creates a timeout that fires off the initial-data-received 2500ms after the last messages received. This may not be as accurate in production but will be better than the current version --- .gitignore | 3 ++- Example/example.ts | 3 +++ src/WAConnection/0.Base.ts | 25 ++++++++------------ src/WAConnection/1.Validation.ts | 6 ++--- src/WAConnection/3.Connect.ts | 2 +- src/WAConnection/4.Events.ts | 39 ++++++++++++++++---------------- src/WAConnection/Utils.ts | 20 +++++++++++++++- 7 files changed, 57 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index f219caf..529e08a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ decoded-ws.json lib docs browser-token.json -Proxy \ No newline at end of file +Proxy +messages*.json \ No newline at end of file diff --git a/Example/example.ts b/Example/example.ts index d07e208..80cd713 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -25,6 +25,9 @@ async function example() { conn.on('contacts-received', () => { console.log(`you have ${Object.keys(conn.contacts).length} contacts`) }) + conn.on('initial-data-received', () => { + console.log('received all initial messages') + }) // loads the auth file credentials if present /* Note: one can take this auth_info.json file and login again from any computer without having to scan the QR code, diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index 7c0e974..a3b0044 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -32,7 +32,7 @@ const logger = pino({ prettyPrint: { levelFirst: true, ignore: 'hostname', trans export class WAConnection extends EventEmitter { /** The version of WhatsApp Web we're telling the servers we are */ - version: [number, number, number] = [2, 2047, 10] + version: [number, number, number] = [2, 2102, 9] /** The Browser we're telling the WhatsApp Web servers we are */ browserDescription: [string, string, string] = Utils.Browsers.baileys ('Chrome') /** Metadata like WhatsApp id, name set on WhatsApp etc. */ @@ -94,8 +94,11 @@ export class WAConnection extends EventEmitter { protected lastDisconnectReason: DisconnectReason protected mediaConn: MediaConnInfo - protected debounceTimeout: NodeJS.Timeout - + protected connectionDebounceTimeout = Utils.debouncedTimeout( + 1000, + () => this.endConnection(DisconnectReason.timedOut) + ) + protected messagesDebounceTimeout = Utils.debouncedTimeout(2000) /** * Connect to WhatsAppWeb * @param options the connect options @@ -247,7 +250,7 @@ export class WAConnection extends EventEmitter { ) } if (startDebouncedTimeout) { - this.startDebouncedTimeout() + this.connectionDebounceTimeout.start() } return response } catch (error) { @@ -344,17 +347,6 @@ export class WAConnection extends EventEmitter { await this.send(buff) // send it off return tag } - protected startDebouncedTimeout () { - this.stopDebouncedTimeout () - this.debounceTimeout = setTimeout ( - () => this.endConnection(DisconnectReason.timedOut), - this.connectOptions.maxIdleTimeMs - ) - } - protected stopDebouncedTimeout () { - this.debounceTimeout && clearTimeout (this.debounceTimeout) - this.debounceTimeout = null - } /** * Send a plain JSON message to the WhatsApp servers * @param json the message to send @@ -438,7 +430,8 @@ export class WAConnection extends EventEmitter { this.conn?.removeAllListeners ('message') this.initTimeout && clearTimeout (this.initTimeout) - this.debounceTimeout && clearTimeout (this.debounceTimeout) + this.connectionDebounceTimeout.cancel() + this.messagesDebounceTimeout.cancel() this.keepAliveReq && clearInterval(this.keepAliveReq) this.phoneCheckListeners = 0 this.clearPhoneCheckInterval () diff --git a/src/WAConnection/1.Validation.ts b/src/WAConnection/1.Validation.ts index 3d29c25..9faa527 100644 --- a/src/WAConnection/1.Validation.ts +++ b/src/WAConnection/1.Validation.ts @@ -15,7 +15,7 @@ export class WAConnection extends Base { const canLogin = this.canLogin() this.referenceDate = new Date () // refresh reference date - this.startDebouncedTimeout () + this.connectionDebounceTimeout.start() const initQuery = (async () => { const {ref, ttl} = await this.query({ @@ -28,7 +28,7 @@ export class WAConnection extends Base { }) as WAInitResponse if (!canLogin) { - this.stopDebouncedTimeout () // stop the debounced timeout for QR gen + this.connectionDebounceTimeout.cancel() // stop the debounced timeout for QR gen this.generateKeysForAuth (ref, ttl) } })(); @@ -73,7 +73,7 @@ export class WAConnection extends Base { ] .filter(Boolean) ) - this.startDebouncedTimeout() + this.connectionDebounceTimeout.start() this.initTimeout && clearTimeout (this.initTimeout) this.initTimeout = null diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index 9c3a0bc..147bb2d 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -99,7 +99,7 @@ export class WAConnection extends Base { this.conn .removeAllListeners('error') .removeAllListeners('close') - this.stopDebouncedTimeout() + this.connectionDebounceTimeout.start(this.connectOptions.maxIdleTimeMs) resolve(authResult as WAOpenResult) } catch (error) { reject(error) diff --git a/src/WAConnection/4.Events.ts b/src/WAConnection/4.Events.ts index 52130d6..76d2b2d 100644 --- a/src/WAConnection/4.Events.ts +++ b/src/WAConnection/4.Events.ts @@ -77,12 +77,31 @@ export class WAConnection extends Base { // if there are no overlaps of messages and we had messages present, we clear the previous messages // this prevents missing messages in conversations let overlaps: { [_: string]: { requiresOverlap: boolean, didOverlap?: boolean } } = {} + const onLastBatchOfDataReceived = () => { + // find which chats had missing messages + // list out all the jids, and how many messages we've cached now + const chatsWithMissingMessages = Object.keys(overlaps).map(jid => { + // if there was no overlap, delete previous messages + if (!overlaps[jid].didOverlap && overlaps[jid].requiresOverlap) { + this.logger.debug(`received messages for ${jid}, but did not overlap with previous messages, clearing...`) + const chat = this.chats.get(jid) + if (chat) { + const message = chat.messages.get(lastMessages[jid]) + const remainingMessages = chat.messages.paginatedByValue(message, this.maxCachedMessages, undefined, 'after') + chat.messages = newMessagesDB([message, ...remainingMessages]) + return { jid, count: chat.messages.length } // return number of messages we've left + } + } + }).filter(Boolean) + this.emit('initial-data-received', { chatsWithMissingMessages }) + } // messages received const messagesUpdate = (json, style: 'previous' | 'last') => { + //console.log('msg ', json[1]) + this.messagesDebounceTimeout.start(undefined, onLastBatchOfDataReceived) if (style === 'last') { overlaps = {} } - const isLast = json[1].last const messages = json[2] as WANode[] if (messages) { const updates: { [k: string]: KeyedDB } = {} @@ -121,24 +140,6 @@ export class WAConnection extends Base { ) } } - if (isLast) { - // find which chats had missing messages - // list out all the jids, and how many messages we've cached now - const chatsWithMissingMessages = Object.keys(overlaps).map(jid => { - // if there was no overlap, delete previous messages - if (!overlaps[jid].didOverlap && overlaps[jid].requiresOverlap) { - this.logger.debug(`received messages for ${jid}, but did not overlap with previous messages, clearing...`) - const chat = this.chats.get(jid) - if (chat) { - const message = chat.messages.get(lastMessages[jid]) - const remainingMessages = chat.messages.paginatedByValue(message, this.maxCachedMessages, undefined, 'after') - chat.messages = newMessagesDB([message, ...remainingMessages]) - return { jid, count: chat.messages.length } // return number of messages we've left - } - } - }).filter(Boolean) - this.emit('initial-data-received', { chatsWithMissingMessages }) - } } this.on('CB:action,add:last', json => messagesUpdate(json, 'last')) this.on('CB:action,add:before', json => messagesUpdate(json, 'previous')) diff --git a/src/WAConnection/Utils.ts b/src/WAConnection/Utils.ts index 51b9e88..b7945a6 100644 --- a/src/WAConnection/Utils.ts +++ b/src/WAConnection/Utils.ts @@ -15,7 +15,6 @@ import got, { Options, Response } from 'got' import { join } from 'path' import { IAudioMetadata } from 'music-metadata' - const platformMap = { 'aix': 'AIX', 'darwin': 'Mac OS', @@ -105,6 +104,25 @@ export function randomBytes(length) { /** 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 + return { + start: (newIntervalMs?: number, newTask?: () => void) => { + task = newTask || task + intervalMs = newIntervalMs || intervalMs + timeout && clearTimeout(timeout) + timeout = setTimeout(task, intervalMs) + }, + cancel: () => { + timeout && clearTimeout(timeout) + timeout = undefined + }, + setTask: (newTask: () => void) => task = newTask, + setInterval: (newInterval: number) => intervalMs = newInterval + } +} + export const delay = (ms: number) => delayCancellable (ms).delay export const delayCancellable = (ms: number) => { const stack = new Error().stack