From 9041272b5cfe3c01e4e9e124ba6d08dda616043e Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Sat, 5 Sep 2020 18:31:56 +0530 Subject: [PATCH] updates --- src/Tests/Tests.Connect.ts | 6 +++- src/WAConnection/0.Base.ts | 19 +++++------ src/WAConnection/3.Connect.ts | 64 +++++++++++++++++------------------ src/WAConnection/5.User.ts | 4 +-- src/WAConnection/Utils.ts | 39 --------------------- 5 files changed, 47 insertions(+), 85 deletions(-) diff --git a/src/Tests/Tests.Connect.ts b/src/Tests/Tests.Connect.ts index 95cafa5..2a0700f 100644 --- a/src/Tests/Tests.Connect.ts +++ b/src/Tests/Tests.Connect.ts @@ -94,6 +94,10 @@ describe ('Reconnects', () => { reason !== DisconnectReason.intentional && assert.fail ('should not have closed again') )) await delay (60*1000) + + const status = await conn.getStatus () + assert.ok (status) + conn.close () } /** @@ -252,7 +256,7 @@ describe ('Pending Requests', () => { }) it('should queue requests when closed', async () => { const conn = new WAConnection () - conn.pendingRequestTimeoutMs = null + //conn.pendingRequestTimeoutMs = null await conn.loadAuthInfo('./auth_info.json').connect () diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index 473b6c5..6a5b42b 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -221,7 +221,7 @@ export class WAConnection extends EventEmitter { */ async query({json, binaryTags, tag, timeoutMs, expect200, waitForOpen}: WAQuery) { waitForOpen = typeof waitForOpen === 'undefined' ? true : waitForOpen - await this.waitForConnection (waitForOpen) + if (waitForOpen) await this.waitForConnection () if (binaryTags) tag = await this.sendBinary(json as WANode, binaryTags, tag) else tag = await this.sendJSON(json, tag) @@ -276,15 +276,12 @@ export class WAConnection extends EventEmitter { this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages return this.conn.send(m) } - protected async waitForConnection (waitForOpen: boolean=true) { - if (!waitForOpen || this.state === 'open') return + protected async waitForConnection () { + if (this.state === 'open') return - const timeout = this.pendingRequestTimeoutMs - try { - await Utils.promiseTimeout (timeout, (resolve, reject) => this.pendingRequests.push({resolve, reject})) - } catch { - throw new Error('cannot send message, disconnected from WhatsApp') - } + await Utils.promiseTimeout ( + this.pendingRequestTimeoutMs, + (resolve, reject) => this.pendingRequests.push({resolve, reject})) } /** * Disconnect from the phone. Your auth credentials become invalid after sending a disconnect request. @@ -318,7 +315,7 @@ export class WAConnection extends EventEmitter { this.endConnection () if (reason === 'invalid_session' || reason === 'intentional') { - this.pendingRequests.forEach (({reject}) => reject(new Error('close'))) + this.pendingRequests.forEach (({reject}) => reject(new Error(reason))) this.pendingRequests = [] } // reconnecting if the timeout is active for the reconnect loop @@ -327,7 +324,7 @@ export class WAConnection extends EventEmitter { protected endConnection () { this.conn?.removeAllListeners ('close') this.conn?.removeAllListeners ('message') - this.conn?.close () + //this.conn?.close () this.conn?.terminate() this.conn = null this.lastSeen = null diff --git a/src/WAConnection/3.Connect.ts b/src/WAConnection/3.Connect.ts index aa5cde3..c23efa9 100644 --- a/src/WAConnection/3.Connect.ts +++ b/src/WAConnection/3.Connect.ts @@ -2,6 +2,7 @@ import * as Utils from './Utils' import { WAMessage, WAChat, MessageLogLevel, WANode, KEEP_ALIVE_INTERVAL_MS, BaileysError, WAConnectOptions, DisconnectReason, UNAUTHORIZED_CODES, WAContact, TimedOutError, CancelledError, WAOpenResult } from './Constants' import {WAConnection as Base} from './1.Validation' import Decoder from '../Binary/Decoder' +import WS from 'ws' export class WAConnection extends Base { /** Connect to WhatsApp Web */ @@ -62,48 +63,44 @@ export class WAConnection extends Base { // actual connect const connect = () => { const timeoutMs = options?.timeoutMs || 60*1000 - - const { ws, cancel } = Utils.openWebSocketConnection (5000, false) + let task = Utils.promiseTimeout(timeoutMs, (resolve, reject) => { + // determine whether reconnect should be used or not + const shouldUseReconnect = this.lastDisconnectReason !== DisconnectReason.replaced && + this.lastDisconnectReason !== DisconnectReason.unknown && + this.lastDisconnectReason !== DisconnectReason.intentional && + this.user?.jid + + const reconnectID = shouldUseReconnect ? this.user.jid.replace ('@s.whatsapp.net', '@c.us') : null - let task: Promise }> = ws - .then (conn => this.conn = conn) + this.conn = new WS('wss://web.whatsapp.com/ws', null, { origin: 'https://web.whatsapp.com', timeout: timeoutMs }) + this.conn.on('message', data => this.onMessageRecieved(data as any)) + + this.conn.on ('open', () => { + this.log(`connected to WhatsApp Web server, authenticating via ${reconnectID ? 'reconnect' : 'takeover'}`, MessageLogLevel.info) + + this.authenticate(reconnectID) + .then (resolve) .then (() => ( - this.conn.on('message', data => this.onMessageRecieved(data as any)) - )) - .then (() => ( - this.log(`connected to WhatsApp Web server, authenticating via ${reconnectID ? 'reconnect' : 'takeover'}`, MessageLogLevel.info) - )) - .then (() => this.authenticate(reconnectID)) - .then (() => { this.conn .removeAllListeners ('error') .removeAllListeners ('close') - }) + )) + .catch (reject) + }) + this.conn.on('error', reject) + this.conn.on('close', () => reject(new Error('close'))) + }) - let cancelTask: () => void + let cancel: () => void if (typeof options?.waitForChats === 'undefined' ? true : options?.waitForChats) { const {waitForChats, cancelChats} = this.receiveChatsAndContacts() - task = Promise.all ([task, waitForChats]).then (([_, updates]) => updates) - cancelTask = () => { cancelChats(); cancel() } - } else cancelTask = cancel + cancel = cancelChats + } + + task = task as Promise }> - // determine whether reconnect should be used or not - const shouldUseReconnect = this.lastDisconnectReason !== DisconnectReason.replaced && - this.lastDisconnectReason !== DisconnectReason.unknown && - this.lastDisconnectReason !== DisconnectReason.intentional && - this.user?.jid - const reconnectID = shouldUseReconnect ? this.user.jid.replace ('@s.whatsapp.net', '@c.us') : null - - const promise = Utils.promiseTimeout(timeoutMs, (resolve, reject) => ( - task.then (resolve).catch (reject) - )) - .catch (err => { - this.endConnection () - throw err - }) as Promise }> - - return { promise, cancel: cancelTask } + return { promise: task, cancel } } let promise = Promise.resolve () @@ -127,6 +124,9 @@ export class WAConnection extends Base { const final = await result.promise return final + } catch (error) { + this.endConnection () + throw error } finally { cancel() this.off('close', cancel) diff --git a/src/WAConnection/5.User.ts b/src/WAConnection/5.User.ts index 1bc91ca..bc10209 100644 --- a/src/WAConnection/5.User.ts +++ b/src/WAConnection/5.User.ts @@ -31,10 +31,10 @@ export class WAConnection extends Base { } ) as Promise<{status: number}> /** Request an update on the presence of a user */ - requestPresenceUpdate = async (jid: string) => this.query({json: ['action', 'presence', 'subscribe', jid]}) + requestPresenceUpdate = async (jid: string) => this.query({ json: ['action', 'presence', 'subscribe', jid] }) /** Query the status of the person (see groupMetadata() for groups) */ async getStatus (jid?: string) { - const status: { status: string } = await this.query({json: ['query', 'Status', jid || this.user.jid], expect200: true}) + const status: { status: string } = await this.query({ json: ['query', 'Status', jid || this.user.jid] }) return status } async setStatus (status: string) { diff --git a/src/WAConnection/Utils.ts b/src/WAConnection/Utils.ts index 30dd484..45dfc87 100644 --- a/src/WAConnection/Utils.ts +++ b/src/WAConnection/Utils.ts @@ -5,7 +5,6 @@ import {promises as fs} from 'fs' import fetch from 'node-fetch' import { exec } from 'child_process' import {platform, release} from 'os' -import WS from 'ws' import Decoder from '../Binary/Decoder' import { MessageType, HKDFInfoKeys, MessageOptions, WAChat, WAMessageContent, BaileysError, WAMessageProto, TimedOutError, CancelledError } from './Constants' @@ -115,44 +114,6 @@ export async function promiseTimeout(ms: number, promise: (resolve: (v?: T)=> .finally (cancel) return p as Promise } - -export const openWebSocketConnection = (timeoutMs: number, retryOnNetworkError: boolean) => { - const newWS = async () => { - const conn = new WS('wss://web.whatsapp.com/ws', null, { origin: 'https://web.whatsapp.com', timeout: timeoutMs }) - await new Promise ((resolve, reject) => { - conn.on('open', () => { - conn.removeAllListeners ('error') - conn.removeAllListeners ('close') - conn.removeAllListeners ('open') - - resolve () - }) - // if there was an error in the WebSocket - conn.on('error', reject) - conn.on('close', () => reject(new Error('close'))) - }) - return conn - } - let cancelled = false - const connect = async () => { - while (!cancelled) { - try { - const ws = await newWS() - if (cancelled) { - ws.terminate () - break - } else return ws - } catch (error) { - if (!retryOnNetworkError) throw error - await delay (1000) - } - } - throw CancelledError() - } - const cancel = () => cancelled = true - return { ws: connect(), cancel } -} - // whatsapp requires a message tag for every message, we just use the timestamp as one export function generateMessageTag(epoch?: number) { let tag = unixTimestampSeconds().toString()