From a6c8fee81481ad500a7f3688baa9edcefa38faf8 Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Wed, 19 Aug 2020 16:06:20 +0530 Subject: [PATCH] Reconnect fixes --- README.md | 2 + src/Tests/Tests.Connect.ts | 82 ++++++++++++++++++++++++++++++++++- src/WAConnection/0.Base.ts | 12 ++--- src/WAConnection/3.Connect.ts | 12 +++-- src/WAConnection/4.Events.ts | 4 +- src/WAConnection/8.Groups.ts | 2 +- 6 files changed, 100 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ddc6918..6ea16e1 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ conn.regenerateQRIntervalMs = 20000 // QR regen every 20 seconds Baileys now uses the EventEmitter syntax for events. They're all nicely typed up, so you shouldn't have any issues with an Intellisense editor like VS Code. +Also, these events are fired regardless of whether they are initiated by the Baileys client or are relayed from your phone. + ``` ts /** when the connection has opened successfully */ diff --git a/src/Tests/Tests.Connect.ts b/src/Tests/Tests.Connect.ts index 2c8bdf5..8deb6fe 100644 --- a/src/Tests/Tests.Connect.ts +++ b/src/Tests/Tests.Connect.ts @@ -1,7 +1,8 @@ import * as assert from 'assert' import {WAConnection} from '../WAConnection/WAConnection' -import { AuthenticationCredentialsBase64, BaileysError, MessageLogLevel } from '../WAConnection/Constants' +import { AuthenticationCredentialsBase64, BaileysError, MessageLogLevel, ReconnectMode } from '../WAConnection/Constants' import { delay, promiseTimeout } from '../WAConnection/Utils' +import { close } from 'fs' describe('QR Generation', () => { it('should generate QR', async () => { @@ -72,7 +73,84 @@ describe('Test Connect', () => { .finally (() => conn.close()) }) }) -describe ('Pending Requests', async () => { +describe ('Reconnects', () => { + it ('should disconnect & reconnect phone', async () => { + const conn = new WAConnection () + await conn.loadAuthInfo('./auth_info.json').connect () + assert.equal (conn.phoneConnected, true) + + try { + const waitForEvent = expect => new Promise (resolve => { + conn.on ('connection-phone-change', ({connected}) => { + assert.equal (connected, expect) + conn.removeAllListeners ('connection-phone-change') + resolve () + }) + }) + + console.log ('disconnect your phone from the internet') + await waitForEvent (false) + console.log ('reconnect your phone to the internet') + await waitForEvent (true) + } finally { + conn.close () + } + }) + it ('should reconnect connection', async () => { + const conn = new WAConnection () + conn.autoReconnect = ReconnectMode.onConnectionLost + + await conn.loadAuthInfo('./auth_info.json').connect () + assert.equal (conn.phoneConnected, true) + + try { + const closeConn = () => conn['conn']?.terminate () + + const task = new Promise (resolve => { + let closes = 0 + conn.on ('closed', ({reason, isReconnecting}) => { + console.log (`closed: ${reason}`) + assert.ok (reason) + assert.ok (isReconnecting) + closes += 1 + + // let it fail reconnect a few times + if (closes > 4) { + console.log ('here') + conn.removeAllListeners ('closed') + conn.removeAllListeners ('connecting') + resolve () + } + }) + conn.on ('connecting', () => { + // close again + delay (500).then (closeConn) + }) + }) + + closeConn () + await task + + await new Promise (resolve => { + conn.on ('open', () => { + conn.removeAllListeners ('open') + resolve () + }) + }) + + conn.close () + + conn.on ('connecting', () => assert.fail('should not connect')) + await delay (2000) + } finally { + conn.removeAllListeners ('connecting') + conn.removeAllListeners ('closed') + conn.removeAllListeners ('open') + conn.close () + } + }) +}) +describe ('Pending Requests', () => { it('should queue requests when closed', async () => { const conn = new WAConnection () conn.pendingRequestTimeoutMs = null diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index 6861837..a33adc0 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -73,12 +73,12 @@ export class WAConnection extends EventEmitter { this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind)) } async unexpectedDisconnect (error?: DisconnectReason) { - const willReconnect = this.autoReconnect === ReconnectMode.onAllErrors || (this.autoReconnect === ReconnectMode.onConnectionLost && (error === 'lost' || error === 'closed')) + const willReconnect = this.autoReconnect === ReconnectMode.onAllErrors || (this.autoReconnect === ReconnectMode.onConnectionLost && (error !== 'replaced')) this.log (`got disconnected, reason ${error || 'unknown'}${willReconnect ? ', reconnecting in a few seconds...' : ''}`, MessageLogLevel.info) - this.closeInternal(error, willReconnect) - willReconnect && this.reconnectLoop () + + willReconnect && !this.cancelReconnect && this.reconnectLoop () } /** * base 64 encode the authentication credentials and return them @@ -285,7 +285,7 @@ export class WAConnection extends EventEmitter { this.pendingRequests.forEach (({reject}) => reject(new Error('closed'))) this.pendingRequests = [] } - protected closeInternal (reason?: DisconnectReason, isReconnecting: boolean = false) { + protected closeInternal (reason?: DisconnectReason, isReconnecting: boolean=false) { this.qrTimeout && clearTimeout (this.qrTimeout) this.phoneCheck && clearTimeout (this.phoneCheck) @@ -303,8 +303,8 @@ export class WAConnection extends EventEmitter { } }) if (this.keepAliveReq) clearInterval(this.keepAliveReq) - - this.emit ('closed', { reason, isReconnecting }) + // reconnecting if the timeout is active for the reconnect loop + this.emit ('closed', { reason, isReconnecting: this.cancelReconnect || isReconnecting }) } protected async reconnectLoop () { diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index 67f67b8..dee6fec 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -28,6 +28,7 @@ export class WAConnection extends Base { this.startKeepAliveRequest() this.conn.removeAllListeners ('error') + this.conn.removeAllListeners ('close') this.conn.on ('close', () => this.unexpectedDisconnect ('closed')) this.state = 'open' @@ -37,7 +38,8 @@ export class WAConnection extends Base { }) this.conn.on('message', m => this.onMessageRecieved(m)) // if there was an error in the WebSocket - this.conn.on('error', error => { this.closeInternal(error.message as any); reject(error) }) + this.conn.on('error', reject) + this.conn.on('close', () => reject(new Error('closed'))) }) try { @@ -77,7 +79,6 @@ export class WAConnection extends Base { let receivedMessages = false let convoResolve: () => void - this.log('waiting for chats & contacts', MessageLogLevel.info) // wait for the message with chats const waitForConvos = () => Utils.promiseTimeout(timeoutMs, resolve => { convoResolve = () => { @@ -91,7 +92,7 @@ export class WAConnection extends Base { } const chatUpdate = json => { receivedMessages = true - const isLast = json[1].last || (json[1].add === 'last' && stopAfterMostRecentMessage) + const isLast = json[1].last || stopAfterMostRecentMessage const messages = json[2] as WANode[] if (messages) { @@ -133,6 +134,8 @@ export class WAConnection extends Base { this.deregisterCallback(['response', 'type:chat']) + this.log ('received chats list', MessageLogLevel.info) + if (this.chats.all().length > 0) waitForConvos().then (resolve) else resolve () }) @@ -157,6 +160,8 @@ export class WAConnection extends Base { resolve () this.deregisterCallback(['response', 'type:contacts']) + + this.log ('received contacts list', MessageLogLevel.info) }) }) ) @@ -259,6 +264,7 @@ export class WAConnection extends Base { try { await this.connect () this.cancelReconnect = null + break } catch (error) { this.log (`error in reconnecting: ${error}, reconnecting...`, MessageLogLevel.info) } diff --git a/src/WAConnection/4.Events.ts b/src/WAConnection/4.Events.ts index 7d19ab2..a0c9697 100644 --- a/src/WAConnection/4.Events.ts +++ b/src/WAConnection/4.Events.ts @@ -261,7 +261,7 @@ export class WAConnection extends Base { } protected registerPhoneConnectionPoll () { this.phoneCheck = setInterval (() => { - this.checkPhoneConnection (7500) // 7500 ms for timeout + this.checkPhoneConnection (5000) // 5000 ms for timeout .then (connected => { if (this.phoneConnected != connected) { this.emit ('connection-phone-change', {connected}) @@ -269,7 +269,7 @@ export class WAConnection extends Base { this.phoneConnected = connected }) .catch (error => this.log(`error in getting phone connection: ${error}`, MessageLogLevel.info)) - }, 20000) + }, 15000) } // Add all event types diff --git a/src/WAConnection/8.Groups.ts b/src/WAConnection/8.Groups.ts index 610aa2a..5d65d26 100644 --- a/src/WAConnection/8.Groups.ts +++ b/src/WAConnection/8.Groups.ts @@ -134,7 +134,7 @@ export class WAConnection extends Base { /** Get the invite link of the given group */ async groupInviteCode(jid: string) { const json = ['query', 'inviteCode', jid] - const response = await this.query({json}) + const response = await this.query({json, expect200: true}) return response.code as string } } \ No newline at end of file