diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index 3cc507b..8ab70b5 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -65,20 +65,21 @@ export class WAConnection extends EventEmitter { protected qrTimeout: NodeJS.Timeout protected phoneCheck: NodeJS.Timeout + protected lastDisconnectReason: DisconnectReason protected cancelledReconnect = false protected cancelReconnect: () => void constructor () { super () - this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind)) + this.registerCallback (['Cmd', 'type:disconnect'], json => this.unexpectedDisconnect(json[1].kind || 'unknown')) } - async unexpectedDisconnect (error?: DisconnectReason) { + async unexpectedDisconnect (error: DisconnectReason) { const willReconnect = (this.autoReconnect === ReconnectMode.onAllErrors || (this.autoReconnect === ReconnectMode.onConnectionLost && (error !== 'replaced'))) && error !== 'invalid_session' - this.log (`got disconnected, reason ${error || 'unknown'}${willReconnect ? ', reconnecting in a few seconds...' : ''}`, MessageLogLevel.info) + this.log (`got disconnected, reason ${error}${willReconnect ? ', reconnecting in a few seconds...' : ''}`, MessageLogLevel.info) this.closeInternal(error, willReconnect) willReconnect && !this.cancelReconnect && this.reconnectLoop () @@ -215,6 +216,11 @@ export class WAConnection extends EventEmitter { const response = await this.waitForMessage(tag, json, timeoutMs) if (expect200 && response.status && Math.floor(+response.status / 100) !== 2) { + if (response.status >= 500) { + this.unexpectedDisconnect ('bad_session') + const response = await this.query ({json, binaryTags, tag, timeoutMs, expect200, waitForOpen}) + return response + } throw new BaileysError(`Unexpected status code in '${json[0] || 'generic query'}': ${response.status}`, {query: json}) } return response @@ -282,11 +288,7 @@ export class WAConnection extends EventEmitter { /** Close the connection to WhatsApp Web */ close () { this.closeInternal ('intentional') - this.cancelReconnect && this.cancelReconnect () - - this.pendingRequests.forEach (({reject}) => reject(new Error('close'))) - this.pendingRequests = [] } protected closeInternal (reason?: DisconnectReason, isReconnecting: boolean=false) { this.qrTimeout && clearTimeout (this.qrTimeout) @@ -298,6 +300,12 @@ export class WAConnection extends EventEmitter { this.conn?.close() this.conn = null this.phoneConnected = false + this.lastDisconnectReason = reason + + if (reason === 'invalid_session' || reason === 'intentional') { + this.pendingRequests.forEach (({reject}) => reject(new Error('close'))) + this.pendingRequests = [] + } Object.keys(this.callbacks).forEach(key => { if (!key.includes('function:')) { @@ -307,6 +315,7 @@ export class WAConnection extends EventEmitter { } }) if (this.keepAliveReq) clearInterval(this.keepAliveReq) + // reconnecting if the timeout is active for the reconnect loop this.emit ('close', { reason, isReconnecting: this.cancelReconnect || isReconnecting}) } diff --git a/src/WAConnection/1.Validation.ts b/src/WAConnection/1.Validation.ts index c3c66e9..e2ee615 100644 --- a/src/WAConnection/1.Validation.ts +++ b/src/WAConnection/1.Validation.ts @@ -6,7 +6,7 @@ import { MessageLogLevel, WAMetric, WAFlag, BaileysError, Presence } from './Con export class WAConnection extends Base { /** Authenticate the connection */ - protected async authenticate() { + protected async authenticate (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) { @@ -27,8 +27,9 @@ export class WAConnection extends Base { this.authInfo?.clientToken, this.authInfo?.serverToken, this.authInfo?.clientID, - 'takeover', ] + if (reconnect) json.push(...['reconnect', reconnect.replace('@s.whatsapp.net', '@c.us')]) + else json.push ('takeover') return this.query({ json, tag: 's1', waitForOpen: false }) // wait for response with tag "s1" } return this.generateKeysForAuth(json.ref) // generate keys which will in turn be the QR diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index 8b67f06..b83bc76 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -20,8 +20,8 @@ export class WAConnection extends Base { ws .then (conn => this.conn = conn) .then (() => this.conn.on('message', data => this.onMessageRecieved(data as any))) - .then (() => this.log('connected to WhatsApp Web server, authenticating...', MessageLogLevel.info)) - .then (() => this.authenticate()) + .then (() => this.log(`connected to WhatsApp Web server, authenticating via ${options.reconnectID ? 'reconnect' : 'takeover'}`, MessageLogLevel.info)) + .then (() => this.authenticate(options?.reconnectID)) .then (() => { this.startKeepAliveRequest() this.conn.removeAllListeners ('error') @@ -261,7 +261,8 @@ export class WAConnection extends Base { await delay try { - await this.connect ({ timeoutMs: 30000, retryOnNetworkErrors: true }) + const reconnectID = this.lastDisconnectReason !== 'replaced' && this.user ? this.user.id.replace ('@s.whatsapp.net', '@c.us') : null + await this.connect ({ timeoutMs: 30000, retryOnNetworkErrors: true, reconnectID }) this.cancelReconnect = null break } catch (error) { diff --git a/src/WAConnection/5.User.ts b/src/WAConnection/5.User.ts index b5c07b6..e5ebe4e 100644 --- a/src/WAConnection/5.User.ts +++ b/src/WAConnection/5.User.ts @@ -18,14 +18,18 @@ export class WAConnection extends Base { * @param jid the ID of the person/group who you are updating * @param type your presence */ - async updatePresence(jid: string | null, type: Presence) { - const json = [ - 'action', - { epoch: this.msgCount.toString(), type: 'set' }, - [['presence', { type: type, to: jid }, null]], - ] - return this.query({json, binaryTags: [WAMetric.group, WAFlag.acknowledge]}) as Promise<{ status: number }> - } + updatePresence = (jid: string | null, type: Presence) => + this.query( + { + json: [ + 'action', + { epoch: this.msgCount.toString(), type: 'set' }, + [['presence', { type: type, to: jid }, null]], + ], + binaryTags: [WAMetric.group, WAFlag.acknowledge], + expect200: true + } + ) as Promise<{status: number}> /** Request an update on the presence of a user */ requestPresenceUpdate = async (jid: string) => this.query({json: ['action', 'presence', 'subscribe', jid]}) /** Query the status of the person (see groupMetadata() for groups) */ diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index 2aa13c2..30f0315 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -62,10 +62,12 @@ export type WAConnectOptions = { waitForChats?: boolean /** retry on network errors while connecting */ retryOnNetworkErrors?: boolean + /** use the 'reconnect' tag to reconnect instead of the 'takeover' tag */ + reconnectID?: string } export type WAConnectionState = 'open' | 'connecting' | 'close' -export type DisconnectReason = 'close' | 'lost' | 'replaced' | 'intentional' | 'invalid_session' +export type DisconnectReason = 'close' | 'lost' | 'replaced' | 'intentional' | 'invalid_session' | 'unknown' | 'bad_session' export enum MessageLogLevel { none=0, info=1,