From 0d13a159048f9172b352eaf983be0e252550a430 Mon Sep 17 00:00:00 2001 From: Adhiraj Date: Fri, 2 Oct 2020 14:19:04 +0530 Subject: [PATCH] Handle updating of credentials --- Example/example.ts | 10 +++++++--- README.md | 21 ++++++++++++++++----- src/Tests/Tests.Connect.ts | 13 +++++++++++-- src/WAConnection/1.Validation.ts | 16 +++++++++++++++- src/WAConnection/4.Events.ts | 4 +++- src/WAConnection/Constants.ts | 3 ++- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/Example/example.ts b/Example/example.ts index 094407b..e653c3f 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -22,6 +22,13 @@ async function example() { // attempt to reconnect at most 10 times conn.connectOptions.maxRetries = 10 + conn.on ('credentials-updated', () => { + // save credentials whenever updated + console.log (`credentials updated`) + const authInfo = conn.base64EncodedAuthInfo() // get all the auth info we need to restore this session + fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file + }) + // loads the auth file credentials if present fs.existsSync('./auth_info.json') && conn.loadAuthInfo ('./auth_info.json') // uncomment the following line to proxy the connection; some random proxy I got off of: https://proxyscrape.com/free-proxy-list @@ -34,9 +41,6 @@ async function example() { console.log('you have ' + conn.chats.length + ' chats & ' + Object.keys(conn.contacts).length + ' contacts') console.log ('you have ' + unread.length + ' unread messages') - const authInfo = conn.base64EncodedAuthInfo() // get all the auth info we need to restore this session - fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file - /* Note: one can take this auth_info.json file and login again from any computer without having to scan the QR code, and get full access to one's WhatsApp. Despite the convenience, be careful with this file */ conn.on ('user-presence-update', json => console.log(json.id + ' presence is ' + json.type)) diff --git a/README.md b/README.md index eae967e..bab09ed 100644 --- a/README.md +++ b/README.md @@ -84,14 +84,19 @@ console.log ("oh hello " + conn.user.name + "! You connected via a proxy") You obviously don't want to keep scanning the QR code every time you want to connect. -So, do the following every time you open a new connection: +So, you can save the credentials to log back in via: ``` ts import * as fs from 'fs' const conn = new WAConnection() -await conn.connect() // connect first -const creds = conn.base64EncodedAuthInfo () // contains all the keys you need to restore a session -fs.writeFileSync('./auth_info.json', JSON.stringify(creds, null, '\t')) // save JSON to file +// this will be called as soon as the credentials are updated +conn.on ('credentials-updated', () => { + // save credentials whenever updated + console.log (`credentials updated!`) + const authInfo = conn.base64EncodedAuthInfo() // get all the auth info we need to restore this session + fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file +}) +await conn.connect() // connect ``` Then, to restore a session: @@ -108,11 +113,13 @@ await conn.connect() If you're considering switching from a Chromium/Puppeteer based library, you can use WhatsApp Web's Browser credentials to restore sessions too: ``` ts -conn.loadAuthInfo ('./auth_info_browser.json') // use loaded credentials & timeout in 20s +conn.loadAuthInfo ('./auth_info_browser.json') await conn.connect() // works the same ``` See the browser credentials type in the docs. +**Note**: Upon every successive connection, WA can update part of the stored credentials. Whenever that happens, the `credentials-updated` event is triggered, and you should probably update your saved credentials upon receiving that event. Not doing so *may* lead WA to log you out after a few weeks with a 419 error code. + ## QR Callback If you want to do some custom processing with the QR code used to authenticate, you can register for the following event: @@ -143,6 +150,10 @@ on (event: 'open', listener: (result: WAOpenResult) => void): this on (event: 'connecting', listener: () => void): this /** when the connection has closed */ on (event: 'close', listener: (err: {reason?: DisconnectReason | string, isReconnecting: boolean}) => void): this +/** when the connection has closed */ +on (event: 'intermediate-close', listener: (err: {reason?: DisconnectReason | string}) => void): this +/** when WA updates the credentials */ +on (event: 'credentials-updated', listener: (auth: AuthenticationCredentials) => void): this /** when a new QR is generated, ready for scanning */ on (event: 'qr', listener: (qr: string) => void): this /** when the connection to the phone changes */ diff --git a/src/Tests/Tests.Connect.ts b/src/Tests/Tests.Connect.ts index b607056..cad1c8b 100644 --- a/src/Tests/Tests.Connect.ts +++ b/src/Tests/Tests.Connect.ts @@ -33,10 +33,15 @@ describe('Test Connect', () => { console.log('please be ready to scan with your phone') const conn = new WAConnection() + + let credentialsUpdateCalled = false + conn.on ('credentials-updated', () => credentialsUpdateCalled = true) + await conn.connect () assert.ok(conn.user?.jid) assert.ok(conn.user?.phone) assert.ok (conn.user?.imgUrl || conn.user.imgUrl === '') + assert.ok (credentialsUpdateCalled) assertChatDBIntegrity (conn) @@ -45,15 +50,19 @@ describe('Test Connect', () => { }) it('should reconnect', async () => { const conn = new WAConnection() - + + let credentialsUpdateCalled = false + conn.on ('credentials-updated', () => credentialsUpdateCalled = true) + await conn.loadAuthInfo (auth).connect () assert.ok(conn.user) assert.ok(conn.user.jid) + assert.ok (credentialsUpdateCalled) assertChatDBIntegrity (conn) await conn.logout() conn.loadAuthInfo(auth) - + await conn.connect() .then (() => assert.fail('should not have reconnected')) .catch (err => { diff --git a/src/WAConnection/1.Validation.ts b/src/WAConnection/1.Validation.ts index 67c3f12..3114784 100644 --- a/src/WAConnection/1.Validation.ts +++ b/src/WAConnection/1.Validation.ts @@ -112,10 +112,22 @@ export class WAConnection extends Base { }) as WAUser if (!json.secret) { + let credsChanged = false // if we didn't get a secret, we don't need it, we're validated + if (json.clientToken && json.clientToken !== this.authInfo.clientToken) { + console.log (`change: ${this.authInfo.clientToken}, ${json.clientToken}`) + this.authInfo = { ...this.authInfo, clientToken: json.clientToken } + credsChanged = true + } + if (json.serverToken && json.serverToken !== this.authInfo.serverToken) { + this.authInfo = { ...this.authInfo, serverToken: json.serverToken } + credsChanged = true + } + if (credsChanged) { + this.emit ('credentials-updated', this.authInfo) + } return onValidationSuccess() } - const secret = Buffer.from(json.secret, 'base64') if (secret.length !== 144) { throw new Error ('incorrect secret length received: ' + secret.length) @@ -153,6 +165,8 @@ export class WAConnection extends Base { serverToken: json.serverToken, clientID: this.authInfo.clientID, } + + this.emit ('credentials-updated', this.authInfo) return onValidationSuccess() } /** diff --git a/src/WAConnection/4.Events.ts b/src/WAConnection/4.Events.ts index c49c4f4..919d958 100644 --- a/src/WAConnection/4.Events.ts +++ b/src/WAConnection/4.Events.ts @@ -1,6 +1,6 @@ import * as QR from 'qrcode-terminal' import { WAConnection as Base } from './3.Connect' -import { WAMessageStatusUpdate, WAMessage, WAContact, WAChat, WAMessageProto, WA_MESSAGE_STUB_TYPE, WA_MESSAGE_STATUS_TYPE, MessageLogLevel, PresenceUpdate, BaileysEvent, DisconnectReason, WANode, WAOpenResult, Presence } from './Constants' +import { WAMessageStatusUpdate, WAMessage, WAContact, WAChat, WAMessageProto, WA_MESSAGE_STUB_TYPE, WA_MESSAGE_STATUS_TYPE, MessageLogLevel, PresenceUpdate, BaileysEvent, DisconnectReason, WANode, WAOpenResult, Presence, AuthenticationCredentials } from './Constants' import { whatsappID, unixTimestampSeconds, isGroupID, toNumber, GET_MESSAGE_ID, WA_MESSAGE_ID, WA_MESSAGE_KEY } from './Utils' import KeyedDB from '@adiwajshing/keyed-db' import { Mutex } from './Mutex' @@ -322,6 +322,8 @@ export class WAConnection extends Base { on (event: 'close', listener: (err: {reason?: DisconnectReason | string, isReconnecting: boolean}) => void): this /** when the connection has closed */ on (event: 'intermediate-close', listener: (err: {reason?: DisconnectReason | string}) => void): this + /** when WA updates the credentials */ + on (event: 'credentials-updated', listener: (auth: AuthenticationCredentials) => void): this /** when a new QR is generated, ready for scanning */ on (event: 'qr', listener: (qr: string) => void): this /** when the connection to the phone changes */ diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index 5c88a36..01c4325 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -431,4 +431,5 @@ export type BaileysEvent = 'group-participants-demote' | 'group-settings-update' | 'group-description-update' | - 'received-pong' \ No newline at end of file + 'received-pong' | + 'credentials-updated' \ No newline at end of file