mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
More reliable debounced timeouts
This commit is contained in:
@@ -116,16 +116,20 @@ describe('Test Connect', () => {
|
|||||||
describe ('Reconnects', () => {
|
describe ('Reconnects', () => {
|
||||||
const verifyConnectionOpen = async (conn: WAConnection) => {
|
const verifyConnectionOpen = async (conn: WAConnection) => {
|
||||||
assert.ok (conn.user.jid)
|
assert.ok (conn.user.jid)
|
||||||
|
let failed = false
|
||||||
// check that the connection stays open
|
// check that the connection stays open
|
||||||
conn.on ('close', ({reason}) => (
|
conn.on ('close', ({reason}) => {
|
||||||
reason !== DisconnectReason.intentional && assert.fail ('should not have closed again')
|
if(reason !== DisconnectReason.intentional) failed = true
|
||||||
))
|
})
|
||||||
await delay (60*1000)
|
await delay (60*1000)
|
||||||
|
|
||||||
const status = await conn.getStatus ()
|
const status = await conn.getStatus ()
|
||||||
assert.ok (status)
|
assert.ok (status)
|
||||||
|
assert.ok (!conn['debounceTimeout']) // this should be null
|
||||||
|
|
||||||
conn.close ()
|
conn.close ()
|
||||||
|
|
||||||
|
if (failed) assert.fail ('should not have closed again')
|
||||||
}
|
}
|
||||||
it('should dispose correctly on bad_session', async () => {
|
it('should dispose correctly on bad_session', async () => {
|
||||||
const conn = new WAConnection()
|
const conn = new WAConnection()
|
||||||
@@ -185,6 +189,7 @@ describe ('Reconnects', () => {
|
|||||||
*/
|
*/
|
||||||
it('should disrupt connect loop', async () => {
|
it('should disrupt connect loop', async () => {
|
||||||
const conn = new WAConnection()
|
const conn = new WAConnection()
|
||||||
|
|
||||||
conn.autoReconnect = ReconnectMode.onAllErrors
|
conn.autoReconnect = ReconnectMode.onAllErrors
|
||||||
conn.loadAuthInfo ('./auth_info.json')
|
conn.loadAuthInfo ('./auth_info.json')
|
||||||
|
|
||||||
@@ -194,7 +199,7 @@ describe ('Reconnects', () => {
|
|||||||
while (!conn['conn']) {
|
while (!conn['conn']) {
|
||||||
await delay(100)
|
await delay(100)
|
||||||
}
|
}
|
||||||
conn['conn'].terminate ()
|
conn['conn'].close ()
|
||||||
|
|
||||||
while (conn['conn']) {
|
while (conn['conn']) {
|
||||||
await delay(100)
|
await delay(100)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const logger = pino({ prettyPrint: { levelFirst: true, ignore: 'hostname', trans
|
|||||||
|
|
||||||
export class WAConnection extends EventEmitter {
|
export class WAConnection extends EventEmitter {
|
||||||
/** The version of WhatsApp Web we're telling the servers we are */
|
/** The version of WhatsApp Web we're telling the servers we are */
|
||||||
version: [number, number, number] = [2, 2041, 6]
|
version: [number, number, number] = [2, 2043, 8]
|
||||||
/** The Browser we're telling the WhatsApp Web servers we are */
|
/** The Browser we're telling the WhatsApp Web servers we are */
|
||||||
browserDescription: [string, string, string] = Utils.Browsers.baileys ('Chrome')
|
browserDescription: [string, string, string] = Utils.Browsers.baileys ('Chrome')
|
||||||
/** Metadata like WhatsApp id, name set on WhatsApp etc. */
|
/** Metadata like WhatsApp id, name set on WhatsApp etc. */
|
||||||
@@ -46,8 +46,8 @@ export class WAConnection extends EventEmitter {
|
|||||||
maxIdleTimeMs: 15_000,
|
maxIdleTimeMs: 15_000,
|
||||||
waitOnlyForLastMessage: false,
|
waitOnlyForLastMessage: false,
|
||||||
waitForChats: true,
|
waitForChats: true,
|
||||||
maxRetries: 5,
|
maxRetries: 10,
|
||||||
connectCooldownMs: 3000,
|
connectCooldownMs: 4000,
|
||||||
phoneResponseTime: 10_000,
|
phoneResponseTime: 10_000,
|
||||||
alwaysUseTakeover: true
|
alwaysUseTakeover: true
|
||||||
}
|
}
|
||||||
@@ -93,11 +93,12 @@ export class WAConnection extends EventEmitter {
|
|||||||
|
|
||||||
protected mediaConn: MediaConnInfo
|
protected mediaConn: MediaConnInfo
|
||||||
protected debounceTimeout: NodeJS.Timeout
|
protected debounceTimeout: NodeJS.Timeout
|
||||||
|
protected onDebounceTimeout = () => {}
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super ()
|
super ()
|
||||||
this.registerCallback (['Cmd', 'type:disconnect'], json => (
|
this.registerCallback (['Cmd', 'type:disconnect'], json => (
|
||||||
this.unexpectedDisconnect(json[1].kind || 'unknown')
|
this.state === 'open' && this.unexpectedDisconnect(json[1].kind || 'unknown')
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +247,7 @@ export class WAConnection extends EventEmitter {
|
|||||||
* @param tag the tag to attach to the message
|
* @param tag the tag to attach to the message
|
||||||
*/
|
*/
|
||||||
async query(q: WAQuery) {
|
async query(q: WAQuery) {
|
||||||
let {json, binaryTags, tag, timeoutMs, expect200, waitForOpen, longTag, requiresPhoneConnection} = q
|
let {json, binaryTags, tag, timeoutMs, expect200, waitForOpen, longTag, requiresPhoneConnection, startDebouncedTimeout} = q
|
||||||
requiresPhoneConnection = requiresPhoneConnection !== false
|
requiresPhoneConnection = requiresPhoneConnection !== false
|
||||||
waitForOpen = waitForOpen !== false
|
waitForOpen = waitForOpen !== false
|
||||||
if (waitForOpen) await this.waitForConnection()
|
if (waitForOpen) await this.waitForConnection()
|
||||||
@@ -273,6 +274,7 @@ export class WAConnection extends EventEmitter {
|
|||||||
{query: json, message, status: response.status}
|
{query: json, message, status: response.status}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (startDebouncedTimeout) this.stopDebouncedTimeout ()
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
/** interval is started when a query takes too long to respond */
|
/** interval is started when a query takes too long to respond */
|
||||||
@@ -320,6 +322,14 @@ export class WAConnection extends EventEmitter {
|
|||||||
await this.send(buff) // send it off
|
await this.send(buff) // send it off
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
protected startDebouncedTimeout () {
|
||||||
|
this.stopDebouncedTimeout ()
|
||||||
|
this.debounceTimeout = setTimeout (() => this.onDebounceTimeout(), this.connectOptions.maxIdleTimeMs)
|
||||||
|
}
|
||||||
|
protected stopDebouncedTimeout () {
|
||||||
|
this.debounceTimeout && clearTimeout (this.debounceTimeout)
|
||||||
|
this.debounceTimeout = null
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Send a plain JSON message to the WhatsApp servers
|
* Send a plain JSON message to the WhatsApp servers
|
||||||
* @param json the message to send
|
* @param json the message to send
|
||||||
@@ -390,10 +400,9 @@ export class WAConnection extends EventEmitter {
|
|||||||
this.keepAliveReq && clearInterval(this.keepAliveReq)
|
this.keepAliveReq && clearInterval(this.keepAliveReq)
|
||||||
this.clearPhoneCheckInterval ()
|
this.clearPhoneCheckInterval ()
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.conn?.close()
|
this.conn?.close()
|
||||||
this.conn?.terminate()
|
//this.conn?.terminate()
|
||||||
} catch {
|
} catch {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { WAMetric, WAFlag, BaileysError, Presence, WAUser } from './Constants'
|
|||||||
export class WAConnection extends Base {
|
export class WAConnection extends Base {
|
||||||
|
|
||||||
/** Authenticate the connection */
|
/** Authenticate the connection */
|
||||||
protected async authenticate (startDebouncedTimeout: () => void, stopDebouncedTimeout: () => void, reconnect?: string) {
|
protected async authenticate (reconnect?: string) {
|
||||||
// if no auth info is present, that is, a new session has to be established
|
// if no auth info is present, that is, a new session has to be established
|
||||||
// generate a client ID
|
// generate a client ID
|
||||||
if (!this.authInfo?.clientID) {
|
if (!this.authInfo?.clientID) {
|
||||||
@@ -16,7 +16,7 @@ export class WAConnection extends Base {
|
|||||||
const canLogin = this.authInfo?.encKey && this.authInfo?.macKey
|
const canLogin = this.authInfo?.encKey && this.authInfo?.macKey
|
||||||
this.referenceDate = new Date () // refresh reference date
|
this.referenceDate = new Date () // refresh reference date
|
||||||
|
|
||||||
startDebouncedTimeout ()
|
this.startDebouncedTimeout ()
|
||||||
|
|
||||||
const initQueries = [
|
const initQueries = [
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -25,12 +25,13 @@ export class WAConnection extends Base {
|
|||||||
expect200: true,
|
expect200: true,
|
||||||
waitForOpen: false,
|
waitForOpen: false,
|
||||||
longTag: true,
|
longTag: true,
|
||||||
requiresPhoneConnection: false
|
requiresPhoneConnection: false,
|
||||||
|
startDebouncedTimeout: true
|
||||||
})
|
})
|
||||||
if (!canLogin) {
|
if (!canLogin) {
|
||||||
stopDebouncedTimeout () // stop the debounced timeout for QR gen
|
this.stopDebouncedTimeout () // stop the debounced timeout for QR gen
|
||||||
const result = await this.generateKeysForAuth (ref)
|
const result = await this.generateKeysForAuth (ref)
|
||||||
startDebouncedTimeout () // restart debounced timeout
|
this.startDebouncedTimeout () // restart debounced timeout
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -49,7 +50,15 @@ export class WAConnection extends Base {
|
|||||||
if (reconnect) json.push(...['reconnect', reconnect.replace('@s.whatsapp.net', '@c.us')])
|
if (reconnect) json.push(...['reconnect', reconnect.replace('@s.whatsapp.net', '@c.us')])
|
||||||
else json.push ('takeover')
|
else json.push ('takeover')
|
||||||
|
|
||||||
let response = await this.query({ json, tag: 's1', waitForOpen: false, expect200: true, longTag: true, requiresPhoneConnection: false }) // wait for response with tag "s1"
|
let response = await this.query({
|
||||||
|
json,
|
||||||
|
tag: 's1',
|
||||||
|
waitForOpen: false,
|
||||||
|
expect200: true,
|
||||||
|
longTag: true,
|
||||||
|
requiresPhoneConnection: false,
|
||||||
|
startDebouncedTimeout: true
|
||||||
|
}) // wait for response with tag "s1"
|
||||||
// if its a challenge request (we get it when logging in)
|
// if its a challenge request (we get it when logging in)
|
||||||
if (response[1]?.challenge) {
|
if (response[1]?.challenge) {
|
||||||
await this.respondToChallenge(response[1].challenge)
|
await this.respondToChallenge(response[1].challenge)
|
||||||
@@ -66,8 +75,10 @@ export class WAConnection extends Base {
|
|||||||
this.logger.info('validated connection successfully')
|
this.logger.info('validated connection successfully')
|
||||||
this.emit ('connection-validated', this.user)
|
this.emit ('connection-validated', this.user)
|
||||||
|
|
||||||
const response = await this.query({ json: ['query', 'ProfilePicThumb', this.user.jid], waitForOpen: false, expect200: false, requiresPhoneConnection: false })
|
if (this.loadProfilePicturesForChatsAutomatically) {
|
||||||
this.user.imgUrl = response?.eurl || ''
|
const response = await this.query({ json: ['query', 'ProfilePicThumb', this.user.jid], waitForOpen: false, expect200: false, requiresPhoneConnection: false, startDebouncedTimeout: true })
|
||||||
|
this.user.imgUrl = response?.eurl || ''
|
||||||
|
}
|
||||||
|
|
||||||
this.sendPostConnectQueries ()
|
this.sendPostConnectQueries ()
|
||||||
|
|
||||||
@@ -181,7 +192,7 @@ export class WAConnection extends Base {
|
|||||||
const json = ['admin', 'challenge', signed, this.authInfo.serverToken, this.authInfo.clientID] // prepare to send this signed string with the serverToken & clientID
|
const json = ['admin', 'challenge', signed, this.authInfo.serverToken, this.authInfo.clientID] // prepare to send this signed string with the serverToken & clientID
|
||||||
|
|
||||||
this.logger.info('resolving login challenge')
|
this.logger.info('resolving login challenge')
|
||||||
return this.query({json, expect200: true, waitForOpen: false})
|
return this.query({json, expect200: true, waitForOpen: false, startDebouncedTimeout: true})
|
||||||
}
|
}
|
||||||
/** When starting a new session, generate a QR code by generating a private/public key pair & the keys the server sends */
|
/** When starting a new session, generate a QR code by generating a private/public key pair & the keys the server sends */
|
||||||
protected async generateKeysForAuth(ref: string) {
|
protected async generateKeysForAuth(ref: string) {
|
||||||
@@ -194,21 +205,21 @@ export class WAConnection extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const regenQR = () => {
|
const regenQR = () => {
|
||||||
this.qrTimeout = setTimeout (() => {
|
this.qrTimeout = setTimeout (async () => {
|
||||||
if (this.state === 'open') return
|
if (this.state === 'open') return
|
||||||
|
|
||||||
this.logger.debug ('regenerated QR')
|
this.logger.debug ('regenerated QR')
|
||||||
|
try {
|
||||||
this.generateNewQRCodeRef ()
|
const newRef = await this.generateNewQRCodeRef ()
|
||||||
.then (newRef => ref = newRef)
|
ref = newRef
|
||||||
.then (emitQR)
|
emitQR ()
|
||||||
.then (regenQR)
|
regenQR ()
|
||||||
.catch (error => {
|
} catch (error) {
|
||||||
this.logger.error ({ error }, `error in QR gen`)
|
this.logger.error ({ error }, `error in QR gen`)
|
||||||
if (error.status === 429) { // too many QR requests
|
if (error.status === 429) { // too many QR requests
|
||||||
this.endConnection ()
|
this.endConnection ()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}, this.connectOptions.regenerateQRIntervalMs)
|
}, this.connectOptions.regenerateQRIntervalMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,16 +66,6 @@ export class WAConnection extends Base {
|
|||||||
let cancel: () => void
|
let cancel: () => void
|
||||||
const task = new Promise((resolve, reject) => {
|
const task = new Promise((resolve, reject) => {
|
||||||
cancel = () => reject (CancelledError())
|
cancel = () => reject (CancelledError())
|
||||||
|
|
||||||
const checkIdleTime = () => {
|
|
||||||
this.debounceTimeout && clearTimeout (this.debounceTimeout)
|
|
||||||
this.debounceTimeout = setTimeout (() => rejectSafe (TimedOutError()), this.connectOptions.maxIdleTimeMs)
|
|
||||||
}
|
|
||||||
const startDebouncedTimeout = () => this.connectOptions.maxIdleTimeMs && this.conn.addEventListener ('message', checkIdleTime)
|
|
||||||
const stopDebouncedTimeout = () => {
|
|
||||||
clearTimeout (this.debounceTimeout)
|
|
||||||
this.conn.removeEventListener ('message', checkIdleTime)
|
|
||||||
}
|
|
||||||
// determine whether reconnect should be used or not
|
// determine whether reconnect should be used or not
|
||||||
const shouldUseReconnect = (this.lastDisconnectReason === DisconnectReason.close ||
|
const shouldUseReconnect = (this.lastDisconnectReason === DisconnectReason.close ||
|
||||||
this.lastDisconnectReason === DisconnectReason.lost) &&
|
this.lastDisconnectReason === DisconnectReason.lost) &&
|
||||||
@@ -110,7 +100,8 @@ export class WAConnection extends Base {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.authenticate (startDebouncedTimeout, stopDebouncedTimeout, reconnectID)
|
this.onDebounceTimeout = () => rejectSafe(TimedOutError())
|
||||||
|
await this.authenticate (reconnectID)
|
||||||
|
|
||||||
this.startKeepAliveRequest()
|
this.startKeepAliveRequest()
|
||||||
|
|
||||||
@@ -118,7 +109,9 @@ export class WAConnection extends Base {
|
|||||||
.removeAllListeners ('error')
|
.removeAllListeners ('error')
|
||||||
.removeAllListeners ('close')
|
.removeAllListeners ('close')
|
||||||
const result = waitForChats && (await waitForChats)
|
const result = waitForChats && (await waitForChats)
|
||||||
this.conn.removeEventListener ('message', checkIdleTime)
|
|
||||||
|
this.stopDebouncedTimeout ()
|
||||||
|
|
||||||
resolve (result)
|
resolve (result)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject (error)
|
reject (error)
|
||||||
@@ -177,10 +170,8 @@ export class WAConnection extends Base {
|
|||||||
const deregisterCallbacks = () => {
|
const deregisterCallbacks = () => {
|
||||||
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
|
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
|
||||||
this.deregisterCallback(['action', 'add:last'])
|
this.deregisterCallback(['action', 'add:last'])
|
||||||
if (!waitOnlyForLast) {
|
this.deregisterCallback(['action', 'add:before'])
|
||||||
this.deregisterCallback(['action', 'add:before'])
|
this.deregisterCallback(['action', 'add:unread'])
|
||||||
this.deregisterCallback(['action', 'add:unread'])
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deregisterCallback(['response', 'type:chat'])
|
this.deregisterCallback(['response', 'type:chat'])
|
||||||
this.deregisterCallback(['response', 'type:contacts'])
|
this.deregisterCallback(['response', 'type:contacts'])
|
||||||
@@ -189,13 +180,13 @@ export class WAConnection extends Base {
|
|||||||
|
|
||||||
// wait for messages to load
|
// wait for messages to load
|
||||||
const chatUpdate = json => {
|
const chatUpdate = json => {
|
||||||
|
this.startDebouncedTimeout () // restart debounced timeout
|
||||||
receivedMessages = true
|
receivedMessages = true
|
||||||
|
|
||||||
const isLast = json[1].last || waitOnlyForLast
|
const isLast = json[1].last || waitOnlyForLast
|
||||||
const messages = json[2] as WANode[]
|
const messages = json[2] as WANode[]
|
||||||
|
|
||||||
if (messages) {
|
if (messages) {
|
||||||
|
|
||||||
messages.reverse().forEach (([,, message]: ['message', null, WAMessage]) => {
|
messages.reverse().forEach (([,, message]: ['message', null, WAMessage]) => {
|
||||||
const jid = message.key.remoteJid
|
const jid = message.key.remoteJid
|
||||||
const chat = chats.get(jid)
|
const chat = chats.get(jid)
|
||||||
@@ -207,7 +198,6 @@ export class WAConnection extends Base {
|
|||||||
message['epoch'] = prevEpoch-1
|
message['epoch'] = prevEpoch-1
|
||||||
chat.messages.insert (message)
|
chat.messages.insert (message)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// if received contacts before messages
|
// if received contacts before messages
|
||||||
@@ -222,6 +212,7 @@ export class WAConnection extends Base {
|
|||||||
// get chats
|
// get chats
|
||||||
this.registerCallback(['response', 'type:chat'], json => {
|
this.registerCallback(['response', 'type:chat'], json => {
|
||||||
if (json[1].duplicate || !json[2]) return
|
if (json[1].duplicate || !json[2]) return
|
||||||
|
this.startDebouncedTimeout () // restart debounced timeout
|
||||||
|
|
||||||
json[2]
|
json[2]
|
||||||
.forEach(([item, chat]: [any, WAChat]) => {
|
.forEach(([item, chat]: [any, WAChat]) => {
|
||||||
@@ -248,6 +239,7 @@ export class WAConnection extends Base {
|
|||||||
// get contacts
|
// get contacts
|
||||||
this.registerCallback(['response', 'type:contacts'], json => {
|
this.registerCallback(['response', 'type:contacts'], json => {
|
||||||
if (json[1].duplicate || !json[2]) return
|
if (json[1].duplicate || !json[2]) return
|
||||||
|
this.startDebouncedTimeout () // restart debounced timeout
|
||||||
|
|
||||||
receivedContacts = true
|
receivedContacts = true
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export interface WAQuery {
|
|||||||
waitForOpen?: boolean
|
waitForOpen?: boolean
|
||||||
longTag?: boolean
|
longTag?: boolean
|
||||||
requiresPhoneConnection?: boolean
|
requiresPhoneConnection?: boolean
|
||||||
|
startDebouncedTimeout?: boolean
|
||||||
}
|
}
|
||||||
export enum ReconnectMode {
|
export enum ReconnectMode {
|
||||||
/** does not reconnect */
|
/** does not reconnect */
|
||||||
|
|||||||
Reference in New Issue
Block a user