Added reconnect mechanism

This commit is contained in:
Adhiraj
2020-08-25 00:26:47 +05:30
parent e2372bf60b
commit 68b64b9707
5 changed files with 38 additions and 21 deletions

View File

@@ -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})
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) */

View File

@@ -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,