Use idempotent exposeFunctionIfAbsent (no reinject flag) (#3233)

* Add exposeFunctionIfAbsent function

* use exposeFunctionIfAbsent function instead of original exposeFunction

* Remove "reinject" flag

* exposeFunctionIfAbsent - onQRChangedEvent

* exposeFunctionIfAbsent - onLogoutEvent
This commit is contained in:
devlikeapro 2024-09-25 22:31:35 +07:00 • committed by GitHub
commit e470c82237
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 235 additions and 227 deletions

View file

@ -17,6 +17,7 @@ const ContactFactory = require('./factories/ContactFactory');
const WebCacheFactory = require('./webCache/WebCacheFactory'); const WebCacheFactory = require('./webCache/WebCacheFactory');
const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
const NoAuth = require('./authStrategies/NoAuth'); const NoAuth = require('./authStrategies/NoAuth');
const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
/** /**
* Starting point for interacting with the WhatsApp Web API * Starting point for interacting with the WhatsApp Web API
@ -90,9 +91,8 @@ class Client extends EventEmitter {
/** /**
* Injection logic * Injection logic
* Private function * Private function
* @property {boolean} reinject is this a reinject?
*/ */
async inject(reinject = false) { async inject() {
await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs}); await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
const version = await this.getWWebVersion(); const version = await this.getWWebVersion();
@ -142,11 +142,7 @@ class Client extends EventEmitter {
// Register qr events // Register qr events
let qrRetries = 0; let qrRetries = 0;
const injected = await this.pupPage.evaluate(() => { await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
return typeof window.onQRChangedEvent !== 'undefined';
});
if (!injected) {
await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => {
/** /**
* Emitted when a QR code is received * Emitted when a QR code is received
* @event Client#qr * @event Client#qr
@ -161,7 +157,6 @@ class Client extends EventEmitter {
} }
} }
}); });
}
await this.pupPage.evaluate(async () => { await this.pupPage.evaluate(async () => {
@ -178,15 +173,14 @@ class Client extends EventEmitter {
}); });
} }
if (!reinject) { await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => {
await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => {
if (state == 'UNPAIRED_IDLE') { if (state == 'UNPAIRED_IDLE') {
// refresh qr code // refresh qr code
window.Store.Cmd.refreshQR(); window.Store.Cmd.refreshQR();
} }
}); });
await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => { await exposeFunctionIfAbsent(this.pupPage, 'onAppStateHasSyncedEvent', async () => {
const authEventPayload = await this.authStrategy.getAuthEventPayload(); const authEventPayload = await this.authStrategy.getAuthEventPayload();
/** /**
* Emitted when authentication is successful * Emitted when authentication is successful
@ -231,8 +225,7 @@ class Client extends EventEmitter {
//Load util functions (serializers, helper functions) //Load util functions (serializers, helper functions)
await this.pupPage.evaluate(LoadUtils); await this.pupPage.evaluate(LoadUtils);
await this.attachEventListeners(reinject); await this.attachEventListeners();
reinject = true;
} }
/** /**
* Emitted when the client has initialized and is ready to receive messages. * Emitted when the client has initialized and is ready to receive messages.
@ -242,22 +235,16 @@ class Client extends EventEmitter {
this.authStrategy.afterAuthReady(); this.authStrategy.afterAuthReady();
}); });
let lastPercent = null; let lastPercent = null;
await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => { await exposeFunctionIfAbsent(this.pupPage, 'onOfflineProgressUpdateEvent', async (percent) => {
if (lastPercent !== percent) { if (lastPercent !== percent) {
lastPercent = percent; lastPercent = percent;
this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
} }
}); });
} await exposeFunctionIfAbsent(this.pupPage, 'onLogoutEvent', async () => {
const logoutCatchInjected = await this.pupPage.evaluate(() => {
return typeof window.onLogoutEvent !== 'undefined';
});
if (!logoutCatchInjected) {
await this.pupPage.exposeFunction('onLogoutEvent', async () => {
this.lastLoggedOut = true; this.lastLoggedOut = true;
await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _); await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
}); });
}
await this.pupPage.evaluate(() => { await this.pupPage.evaluate(() => {
window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); }); window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); }); window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
@ -349,7 +336,7 @@ class Client extends EventEmitter {
await this.authStrategy.afterBrowserInitialized(); await this.authStrategy.afterBrowserInitialized();
this.lastLoggedOut = false; this.lastLoggedOut = false;
} }
await this.inject(true); await this.inject();
}); });
} }
@ -372,9 +359,8 @@ class Client extends EventEmitter {
* Private function * Private function
* @property {boolean} reinject is this a reinject? * @property {boolean} reinject is this a reinject?
*/ */
async attachEventListeners(reinject = false) { async attachEventListeners() {
if (!reinject) { await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageEvent', msg => {
await this.pupPage.exposeFunction('onAddMessageEvent', msg => {
if (msg.type === 'gp2') { if (msg.type === 'gp2') {
const notification = new GroupNotification(this, msg); const notification = new GroupNotification(this, msg);
if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) { if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
@ -441,7 +427,7 @@ class Client extends EventEmitter {
let last_message; let last_message;
await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => { await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => {
if (msg.type === 'revoked') { if (msg.type === 'revoked') {
const message = new Message(this, msg); const message = new Message(this, msg);
@ -462,7 +448,7 @@ class Client extends EventEmitter {
}); });
await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => { await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => {
if (msg.type !== 'revoked') { if (msg.type !== 'revoked') {
last_message = msg; last_message = msg;
@ -500,7 +486,7 @@ class Client extends EventEmitter {
} }
}); });
await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => { await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => {
if (!msg.isNewMsg) return; if (!msg.isNewMsg) return;
@ -515,7 +501,7 @@ class Client extends EventEmitter {
}); });
await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => { await exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => {
const message = new Message(this, msg); const message = new Message(this, msg);
@ -529,7 +515,7 @@ class Client extends EventEmitter {
}); });
await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{ await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{
const chat = await this.getChatById(data.id); const chat = await this.getChatById(data.id);
/** /**
@ -538,7 +524,7 @@ class Client extends EventEmitter {
this.emit(Events.UNREAD_COUNT, chat); this.emit(Events.UNREAD_COUNT, chat);
}); });
await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => { await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => {
const message = new Message(this, msg); const message = new Message(this, msg);
@ -550,7 +536,7 @@ class Client extends EventEmitter {
this.emit(Events.MEDIA_UPLOADED, message); this.emit(Events.MEDIA_UPLOADED, message);
}); });
await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => { await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => {
/** /**
* Emitted when the connection state changes * Emitted when the connection state changes
* @event Client#change_state * @event Client#change_state
@ -582,7 +568,7 @@ class Client extends EventEmitter {
} }
}); });
await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => { await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => {
const { battery, plugged } = state; const { battery, plugged } = state;
if (battery === undefined) return; if (battery === undefined) return;
@ -598,7 +584,7 @@ class Client extends EventEmitter {
this.emit(Events.BATTERY_CHANGED, { battery, plugged }); this.emit(Events.BATTERY_CHANGED, { battery, plugged });
}); });
await this.pupPage.exposeFunction('onIncomingCall', (call) => { await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => {
/** /**
* Emitted when a call is received * Emitted when a call is received
* @event Client#incoming_call * @event Client#incoming_call
@ -616,7 +602,7 @@ class Client extends EventEmitter {
this.emit(Events.INCOMING_CALL, cll); this.emit(Events.INCOMING_CALL, cll);
}); });
await this.pupPage.exposeFunction('onReaction', (reactions) => { await exposeFunctionIfAbsent(this.pupPage, 'onReaction', (reactions) => {
for (const reaction of reactions) { for (const reaction of reactions) {
/** /**
* Emitted when a reaction is sent, received, updated or removed * Emitted when a reaction is sent, received, updated or removed
@ -637,7 +623,7 @@ class Client extends EventEmitter {
} }
}); });
await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => { await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => {
const _chat = await this.getChatById(chat.id); const _chat = await this.getChatById(chat.id);
/** /**
@ -648,7 +634,7 @@ class Client extends EventEmitter {
this.emit(Events.CHAT_REMOVED, _chat); this.emit(Events.CHAT_REMOVED, _chat);
}); });
await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => { await exposeFunctionIfAbsent(this.pupPage, 'onArchiveChatEvent', async (chat, currState, prevState) => {
const _chat = await this.getChatById(chat.id); const _chat = await this.getChatById(chat.id);
/** /**
@ -661,7 +647,7 @@ class Client extends EventEmitter {
this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState); this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
}); });
await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { await exposeFunctionIfAbsent(this.pupPage, 'onEditMessageEvent', (msg, newBody, prevBody) => {
if(msg.type === 'revoked'){ if(msg.type === 'revoked'){
return; return;
@ -676,7 +662,7 @@ class Client extends EventEmitter {
this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
}); });
await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => { await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => {
/** /**
* Emitted when messages are edited * Emitted when messages are edited
@ -686,7 +672,7 @@ class Client extends EventEmitter {
this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
}); });
await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => { await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (vote) => {
const _vote = new PollVote(this, vote); const _vote = new PollVote(this, vote);
/** /**
* Emitted when some poll option is selected or deselected, * Emitted when some poll option is selected or deselected,
@ -695,7 +681,6 @@ class Client extends EventEmitter {
*/ */
this.emit(Events.VOTE_UPDATE, _vote); this.emit(Events.VOTE_UPDATE, _vote);
}); });
}
await this.pupPage.evaluate(() => { await this.pupPage.evaluate(() => {
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });

23
src/util/Puppeteer.js Normal file
View file

@ -0,0 +1,23 @@
/**
* Expose a function to the page if it does not exist
*
* NOTE:
* Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher
* using page.removeExposedFunction
* https://pptr.dev/api/puppeteer.page.removeExposedFunction
*
* @param {import(puppeteer).Page} page
* @param {string} name
* @param {Function} fn
*/
async function exposeFunctionIfAbsent(page, name, fn) {
const exist = await page.evaluate((name) => {
return !!window[name];
}, name);
if (exist) {
return;
}
await page.exposeFunction(name, fn);
}
module.exports = {exposeFunctionIfAbsent};