diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index 3deaa22..a5ddb7f 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -45,6 +45,7 @@ export class WAConnection extends EventEmitter { regenerateQRIntervalMs = 30*1000 connectOptions: WAConnectOptions = { timeoutMs: 60*1000, + maxIdleTimeMs: 10*1000, waitForChats: true, maxRetries: 5, connectCooldownMs: 2250, @@ -80,6 +81,7 @@ export class WAConnection extends EventEmitter { protected lastDisconnectReason: DisconnectReason protected mediaConn: MediaConnInfo + protected debounceTimeout: NodeJS.Timeout constructor () { super () @@ -101,7 +103,10 @@ export class WAConnection extends EventEmitter { error !== DisconnectReason.invalidSession // do not reconnect if credentials have been invalidated this.closeInternal(error, willReconnect) - willReconnect && this.connect () + willReconnect && ( + this.connect () + .catch(err => {}) // prevent unhandled exeception + ) } /** * base 64 encode the authentication credentials and return them @@ -316,6 +321,7 @@ export class WAConnection extends EventEmitter { this.qrTimeout && clearTimeout (this.qrTimeout) this.keepAliveReq && clearInterval(this.keepAliveReq) + this.debounceTimeout && clearTimeout (this.debounceTimeout) this.state = 'close' this.msgCount = 0 diff --git a/src/WAConnection/1.Validation.ts b/src/WAConnection/1.Validation.ts index 0205265..26d0cc4 100644 --- a/src/WAConnection/1.Validation.ts +++ b/src/WAConnection/1.Validation.ts @@ -6,7 +6,7 @@ import { MessageLogLevel, WAMetric, WAFlag, BaileysError, Presence, WAUser } fro export class WAConnection extends Base { /** Authenticate the connection */ - protected async authenticate (reconnect?: string) { + protected async authenticate (onConnectionValidated: () => void, reconnect?: string) { // if no auth info is present, that is, a new session has to be established // generate a client ID if (!this.authInfo?.clientID) { @@ -56,9 +56,9 @@ export class WAConnection extends Base { } const validationJSON = (await Promise.all (initQueries)).slice(-1)[0] // get the last result - this.user = await this.validateNewConnection(validationJSON[1]) // validate the connection + onConnectionValidated () this.log('validated connection successfully', MessageLogLevel.info) const response = await this.query({ json: ['query', 'ProfilePicThumb', this.user.jid], waitForOpen: false, expect200: false }) diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index 93a80d8..35be5a7 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -68,13 +68,18 @@ export class WAConnection extends Base { let cancel: () => void const task = Utils.promiseTimeout(timeoutMs, (resolve, reject) => { let task: Promise = Promise.resolve () + const checkIdleTime = () => { + this.debounceTimeout && clearTimeout (this.debounceTimeout) + this.debounceTimeout = setTimeout (() => rejectSafe (TimedOutError()), this.connectOptions.maxIdleTimeMs) + } + const debouncedTimeout = () => this.connectOptions.maxIdleTimeMs && this.conn.addEventListener ('message', checkIdleTime) + // add wait for chats promise if required if (typeof options?.waitForChats === 'undefined' ? true : options?.waitForChats) { const {waitForChats, cancelChats} = this.receiveChatsAndContacts() task = waitForChats cancel = cancelChats } - // determine whether reconnect should be used or not const shouldUseReconnect = this.lastDisconnectReason !== DisconnectReason.replaced && this.lastDisconnectReason !== DisconnectReason.unknown && @@ -83,16 +88,17 @@ export class WAConnection extends Base { const reconnectID = shouldUseReconnect ? this.user.jid.replace ('@s.whatsapp.net', '@c.us') : null - this.conn = new WS(WS_URL, null, { origin: DEFAULT_ORIGIN, timeout: timeoutMs, agent: options.agent }) - this.conn.on('message', data => this.onMessageRecieved(data as any)) - + this.conn = new WS(WS_URL, null, { origin: DEFAULT_ORIGIN, timeout: this.connectOptions.maxIdleTimeMs, agent: options.agent }) + this.conn.addEventListener('message', ({data}) => this.onMessageRecieved(data as any)) + this.conn.on ('open', async () => { this.log(`connected to WhatsApp Web server, authenticating via ${reconnectID ? 'reconnect' : 'takeover'}`, MessageLogLevel.info) try { task = Promise.all ([ task, - this.authenticate (reconnectID) + // debounce the timeout once validated + this.authenticate (debouncedTimeout, reconnectID) .then ( () => { this.conn @@ -102,12 +108,14 @@ export class WAConnection extends Base { ) ]) const [result] = await task + + this.conn.removeEventListener ('message', checkIdleTime) + resolve (result) } catch (error) { reject (error) } }) - const rejectSafe = error => { task = task.catch (() => {}) reject (error) @@ -138,7 +146,7 @@ export class WAConnection extends Base { const result = connect () cancellations.push (result.cancel) - const final = await result.promise + const final = await result.promise return final } catch (error) { this.endConnection () diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index e98928f..1a106a4 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -67,6 +67,8 @@ export enum ReconnectMode { export type WAConnectOptions = { /** timeout after which the connect attempt will fail, set to null for default timeout value */ timeoutMs?: number + /** */ + maxIdleTimeMs?: number /** maximum attempts to connect */ maxRetries?: number /** should the chats be waited for */