feat: Switch between QR or phone number pairing method (#3180)
* fix(webpack-exodus): Added the new patch * fix(webpack-exodus): Fixed v2.3000.x and v2.24 * chore(webpack-exodus): eslint * fix(webpack-exodus): Fixed Store * fix(webpack-exodus): slight store fix * chore(webpack-exodus): fix example so that I don't crash on every load * chore: eslint fix * fix(webpack-exodus): prevent confusion with build systems * fix(webpack-exodus): Add proper groups module * fix(webpack-exodus): Fix order queries * fix(webpack-exodus): Bad export name * feat(webpack-exodus): Re-injection, No more selectors, Pairing Code auth * fix(webpack-exodus): Eslint and better login/logout handling * fix(webpack-exodus): 2.24 compatibility * revert testing options * chore(webpack-exodus): ESLint * chore(webpack-exodus): final eslint tweak (did not commit with last commit for some reason) * style: fix broken link in readme file * fix(webpack-exodus): Fix forwarding messages * fix(webpack-exodus): finish forwarding function * Making the ESLint god happy * Fix window.Store.ProfilePic.profilePicFind is not a function error (#3001) * Change window.Store.ProfilePic.profilePicFind to window.Store.ProfilePic.requestProfilePicFromServer * Prevent breaking v2.2x * refactor: make it more readable --------- Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> * Sanitize, improves and fixes * make ESList god happy * fix getInviteCode for group (#3007) (#3029) * fix getInviteCode for group (#3007) * move changes to proper getInviteCode method. * style fix * refactor: add try-catch in a case the user is not a group admin * refactor: handle promise properly --------- Co-authored-by: Sven Neumann <s.neumann@websix.de> Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> * fixes * Update LocalWebCache.js * fix delete on 2.3000 (#3048) * fix delete on 2.3000 * fix compareWwebVersions * fix: use proper comparison operator --------- Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> * Implement bot invoking capabilities (#3009) * Implement bot invoking capabilities * Update index.d.ts * Eslint * Webpack exodus fix message_reaction event (#3099) * exposed WAWebAddonReactionTableMode * capture reaction using WAWebAddonReactionTableMode * move compareWwebVersions and check version before trying to expose new store * message_reaction on example.js (#3102) * handle message_reaction event on example.js * log wweb version on ready * fix reactions on pvt chats (#3104) * New group function * new group settings * Update Client.js * Switch pairing method between QR and phone number * Remove existing pairing code interval * Modify example.js * Update docs * Small improvements in inject function * Fix ESLint * example.js correction * docs: fix docs for 'requestPairingCode' method Co-authored-by: Leandro Matilla <30844484+wis-dev@users.noreply.github.com> * Update Utils.js getChatModel because Meta chat object affected Group Chats * Ajust to set isGroup as false as default * Remove the need for custom interval * Fix typo * Fix intermittent response bug #9864 Co-authored-by: Mob Code 100 <66469454+MobCode100@users.noreply.github.com> * fix: eslint fix * Revert "Remove the need for custom interval" This reverts commit c5d5e5626684742cadbf58f28739c1c2c9dcc582. * Add wait for PairingCodeLinkUtils on requestPairingCode * Remove unused vars WhatsWebStaticURL Co-authored-by: Mob Code 100 <66469454+MobCode100@users.noreply.github.com> * style: format code --------- Co-authored-by: Rajeh Taher <rajeh@reforward.dev> Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com> Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com> Co-authored-by: Seow Zhen Jun <56989187+seowzhenjun0126@users.noreply.github.com> Co-authored-by: Sven <sven2neumann@gmail.com> Co-authored-by: Sven Neumann <s.neumann@websix.de> Co-authored-by: Jorge Rocha Gualtieri <jorge@jrg.com.br> Co-authored-by: Mateus Mercer <accounts@matbm.net> Co-authored-by: Leandro Matilla <30844484+wis-dev@users.noreply.github.com> Co-authored-by: BenyFilho <168232825+BenyFilho@users.noreply.github.com> Co-authored-by: Shir Serlui <70711723+shirser121@users.noreply.github.com>
This commit is contained in:
parent
f9db14d185
commit
b5adf47664
4 changed files with 103 additions and 50 deletions
19
example.js
19
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: <COUNTRY_CODE><PHONE_NUMBER>)
|
||||
// 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', () => {
|
||||
|
|
|
|||
21
index.d.ts
vendored
21
index.d.ts
vendored
|
|
@ -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<string>} - Returns a pairing code in format "ABCDEFGH"
|
||||
*/
|
||||
requestPairingCode(phoneNumber: string, showNotification?: boolean): Promise<string>
|
||||
requestPairingCode(phoneNumber: string, showNotification?: boolean, intervalMs?: number): Promise<string>
|
||||
|
||||
/** Force reset of connection state for the client */
|
||||
resetState(): Promise<void>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
105
src/Client.js
105
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<string>} - 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue