diff --git a/example.js b/example.js index 42c1e27..b7e5dc6 100644 --- a/example.js +++ b/example.js @@ -17,7 +17,12 @@ const client = new Client({ puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], headless: false, - } + }, + // pairWithPhoneNumber: { + // phoneNumber: '96170100100' // Pair with phone number (format: ) + // showNotification: true, + // intervalMs: 180000 // Time to renew pairing code in milliseconds, defaults to 3 minutes + // } }); // client initialize does not finish at ready now. @@ -27,19 +32,13 @@ client.on('loading_screen', (percent, message) => { console.log('LOADING SCREEN', percent, message); }); -// Pairing code only needs to be requested once -let pairingCodeRequested = false; client.on('qr', async (qr) => { // NOTE: This event will not be fired if a session is specified. console.log('QR RECEIVED', qr); +}); - // paiuting code example - const pairingCodeEnabled = false; - if (pairingCodeEnabled && !pairingCodeRequested) { - const pairingCode = await client.requestPairingCode('96170100100'); // enter the target phone number - console.log('Pairing code enabled, code: '+ pairingCode); - pairingCodeRequested = true; - } +client.on('code', (code) => { + console.log('Pairing code:',code); }); client.on('authenticated', () => { diff --git a/index.d.ts b/index.d.ts index f977403..b46483f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -160,10 +160,11 @@ declare namespace WAWebJS { /** * Request authentication via pairing code instead of QR code * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) - * @param showNotification - Show notification to pair on phone number + * @param showNotification - Show notification to pair on phone number. Defaults to `true` + * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes). Defaults to `180000` * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - requestPairingCode(phoneNumber: string, showNotification?: boolean): Promise + requestPairingCode(phoneNumber: string, showNotification?: boolean, intervalMs?: number): Promise /** Force reset of connection state for the client */ resetState(): Promise @@ -460,6 +461,13 @@ declare namespace WAWebJS { qr: string ) => void): this + /** Emitted when the phone number pairing code is received */ + on(event: 'code', listener: ( + /** pairing code string + * @example `8W2WZ3TS` */ + code: string + ) => void): this + /** Emitted when a call is received */ on(event: 'call', listener: ( /** The call that started */ @@ -569,6 +577,15 @@ declare namespace WAWebJS { browserName?: string, /** Object with proxy autentication requirements @default: undefined */ proxyAuthentication?: {username: string, password: string} | undefined + /** Phone number pairing configuration. Refer the requestPairingCode function of Client. + * @default + * { + * phoneNumber: "", + * showNotification: true, + * intervalMs: 180000, + * } + */ + pairWithPhoneNumber?: {phoneNumber: string, showNotification?: boolean, intervalMs?: number} } export interface LocalWebCacheOptions { diff --git a/src/Client.js b/src/Client.js index 90e7cfe..f1fe15b 100644 --- a/src/Client.js +++ b/src/Client.js @@ -96,8 +96,8 @@ class Client extends EventEmitter { */ async inject() { await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); - await this.setDeviceName(this.options.deviceName, this.options.browserName); + const pairWithPhoneNumber = this.options.pairWithPhoneNumber; const version = await this.getWWebVersion(); const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000; @@ -143,41 +143,55 @@ class Client extends EventEmitter { return; } - // Register qr events - let qrRetries = 0; - await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => { - /** - * Emitted when a QR code is received - * @event Client#qr - * @param {string} qr QR Code - */ - this.emit(Events.QR_RECEIVED, qr); - if (this.options.qrMaxRetries > 0) { - qrRetries++; - if (qrRetries > this.options.qrMaxRetries) { - this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); - await this.destroy(); + // Register qr/code events + if (pairWithPhoneNumber.phoneNumber) { + await exposeFunctionIfAbsent(this.pupPage, 'onCodeReceivedEvent', async (code) => { + /** + * Emitted when a pairing code is received + * @event Client#code + * @param {string} code Code + * @returns {string} Code that was just received + */ + this.emit(Events.CODE_RECEIVED, code); + return code; + }); + this.requestPairingCode(pairWithPhoneNumber.phoneNumber, pairWithPhoneNumber.showNotification, pairWithPhoneNumber.intervalMs); + } else { + let qrRetries = 0; + await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => { + /** + * Emitted when a QR code is received + * @event Client#qr + * @param {string} qr QR Code + */ + this.emit(Events.QR_RECEIVED, qr); + if (this.options.qrMaxRetries > 0) { + qrRetries++; + if (qrRetries > this.options.qrMaxRetries) { + this.emit(Events.DISCONNECTED, 'Max qrcode retries reached'); + await this.destroy(); + } } - } - }); + }); - await this.pupPage.evaluate(async () => { - const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); - const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); - const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); - const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); - const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); - const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; - const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; - - window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr - window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes - }); + await this.pupPage.evaluate(async () => { + const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo(); + const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get(); + const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey); + const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey); + const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey(); + const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; + const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform; + + window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr + window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes + }); + } } await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => { - if (state == 'UNPAIRED_IDLE') { + if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) { // refresh qr code window.Store.Cmd.refreshQR(); } @@ -346,15 +360,32 @@ class Client extends EventEmitter { /** * Request authentication via pairing code instead of QR code * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil) - * @param {boolean} showNotification - Show notification to pair on phone number + * @param {boolean} [showNotification = true] - Show notification to pair on phone number + * @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes) * @returns {Promise} - Returns a pairing code in format "ABCDEFGH" */ - async requestPairingCode(phoneNumber, showNotification = true) { - return await this.pupPage.evaluate(async (phoneNumber, showNotification) => { - window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); - await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); - return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); - }, phoneNumber, showNotification); + async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) { + return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => { + const getCode = async () => { + while (!window.AuthStore.PairingCodeLinkUtils) { + await new Promise(resolve => setTimeout(resolve, 250)); + } + window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING'); + await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking(); + return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification); + }; + if (window.codeInterval) { + clearInterval(window.codeInterval); // remove existing interval + } + window.codeInterval = setInterval(async () => { + if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') { + clearInterval(window.codeInterval); + return; + } + window.onCodeReceivedEvent(await getCode()); + }, intervalMs); + return window.onCodeReceivedEvent(await getCode()); + }, phoneNumber, showNotification, intervalMs); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index 5231220..611b36c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -18,7 +18,12 @@ exports.DefaultOptions = { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36', ffmpegPath: 'ffmpeg', bypassCSP: false, - proxyAuthentication: undefined + proxyAuthentication: undefined, + pairWithPhoneNumber: { + phoneNumber: '', + showNotification: true, + intervalMs: 180000, + }, }; /** @@ -60,6 +65,7 @@ exports.Events = { GROUP_MEMBERSHIP_REQUEST: 'group_membership_request', GROUP_UPDATE: 'group_update', QR_RECEIVED: 'qr', + CODE_RECEIVED: 'code', LOADING_SCREEN: 'loading_screen', DISCONNECTED: 'disconnected', STATE_CHANGED: 'change_state',