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:
parent
796a0a2367
commit
e470c82237
2 changed files with 235 additions and 227 deletions
439
src/Client.js
439
src/Client.js
|
|
@ -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,26 +142,21 @@ 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';
|
/**
|
||||||
});
|
* Emitted when a QR code is received
|
||||||
if (!injected) {
|
* @event Client#qr
|
||||||
await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => {
|
* @param {string} qr QR Code
|
||||||
/**
|
*/
|
||||||
* Emitted when a QR code is received
|
this.emit(Events.QR_RECEIVED, qr);
|
||||||
* @event Client#qr
|
if (this.options.qrMaxRetries > 0) {
|
||||||
* @param {string} qr QR Code
|
qrRetries++;
|
||||||
*/
|
if (qrRetries > this.options.qrMaxRetries) {
|
||||||
this.emit(Events.QR_RECEIVED, qr);
|
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
|
||||||
if (this.options.qrMaxRetries > 0) {
|
await this.destroy();
|
||||||
qrRetries++;
|
|
||||||
if (qrRetries > this.options.qrMaxRetries) {
|
|
||||||
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
|
|
||||||
await this.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
|
||||||
await this.pupPage.evaluate(async () => {
|
await this.pupPage.evaluate(async () => {
|
||||||
|
|
@ -178,86 +173,78 @@ 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
|
||||||
* @event Client#authenticated
|
* @event Client#authenticated
|
||||||
*/
|
*/
|
||||||
this.emit(Events.AUTHENTICATED, authEventPayload);
|
this.emit(Events.AUTHENTICATED, authEventPayload);
|
||||||
|
|
||||||
const injected = await this.pupPage.evaluate(async () => {
|
const injected = await this.pupPage.evaluate(async () => {
|
||||||
return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
|
return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!injected) {
|
if (!injected) {
|
||||||
if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
|
if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
|
||||||
const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
|
const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
|
||||||
const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
|
const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
|
||||||
|
|
||||||
await webCache.persist(this.currentIndexHtml, version);
|
await webCache.persist(this.currentIndexHtml, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCometOrAbove) {
|
if (isCometOrAbove) {
|
||||||
await this.pupPage.evaluate(ExposeStore);
|
await this.pupPage.evaluate(ExposeStore);
|
||||||
} else {
|
} else {
|
||||||
// make sure all modules are ready before injection
|
// make sure all modules are ready before injection
|
||||||
// 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
|
// 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
await this.pupPage.evaluate(ExposeLegacyStore);
|
await this.pupPage.evaluate(ExposeLegacyStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check window.Store Injection
|
// Check window.Store Injection
|
||||||
await this.pupPage.waitForFunction('window.Store != undefined');
|
await this.pupPage.waitForFunction('window.Store != undefined');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current connection information
|
* Current connection information
|
||||||
* @type {ClientInfo}
|
* @type {ClientInfo}
|
||||||
*/
|
*/
|
||||||
this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
|
this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
|
||||||
return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
|
return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.interface = new InterfaceController(this);
|
this.interface = new InterfaceController(this);
|
||||||
|
|
||||||
//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.
|
||||||
* @event Client#ready
|
* @event Client#ready
|
||||||
*/
|
*/
|
||||||
this.emit(Events.READY);
|
this.emit(Events.READY);
|
||||||
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(() => {
|
this.lastLoggedOut = true;
|
||||||
return typeof window.onLogoutEvent !== 'undefined';
|
await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
|
||||||
});
|
});
|
||||||
if (!logoutCatchInjected) {
|
|
||||||
await this.pupPage.exposeFunction('onLogoutEvent', async () => {
|
|
||||||
this.lastLoggedOut = true;
|
|
||||||
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,34 +359,33 @@ 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)) {
|
/**
|
||||||
/**
|
|
||||||
* Emitted when a user joins the chat via invite link or is added by an admin.
|
* Emitted when a user joins the chat via invite link or is added by an admin.
|
||||||
* @event Client#group_join
|
* @event Client#group_join
|
||||||
* @param {GroupNotification} notification GroupNotification with more information about the action
|
* @param {GroupNotification} notification GroupNotification with more information about the action
|
||||||
*/
|
*/
|
||||||
this.emit(Events.GROUP_JOIN, notification);
|
this.emit(Events.GROUP_JOIN, notification);
|
||||||
} else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
|
} else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
|
||||||
/**
|
/**
|
||||||
* Emitted when a user leaves the chat or is removed by an admin.
|
* Emitted when a user leaves the chat or is removed by an admin.
|
||||||
* @event Client#group_leave
|
* @event Client#group_leave
|
||||||
* @param {GroupNotification} notification GroupNotification with more information about the action
|
* @param {GroupNotification} notification GroupNotification with more information about the action
|
||||||
*/
|
*/
|
||||||
this.emit(Events.GROUP_LEAVE, notification);
|
this.emit(Events.GROUP_LEAVE, notification);
|
||||||
} else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
|
} else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
|
||||||
/**
|
/**
|
||||||
* Emitted when a current user is promoted to an admin or demoted to a regular user.
|
* Emitted when a current user is promoted to an admin or demoted to a regular user.
|
||||||
* @event Client#group_admin_changed
|
* @event Client#group_admin_changed
|
||||||
* @param {GroupNotification} notification GroupNotification with more information about the action
|
* @param {GroupNotification} notification GroupNotification with more information about the action
|
||||||
*/
|
*/
|
||||||
this.emit(Events.GROUP_ADMIN_CHANGED, notification);
|
this.emit(Events.GROUP_ADMIN_CHANGED, notification);
|
||||||
} else if (msg.subtype === 'membership_approval_request') {
|
} else if (msg.subtype === 'membership_approval_request') {
|
||||||
/**
|
/**
|
||||||
* Emitted when some user requested to join the group
|
* Emitted when some user requested to join the group
|
||||||
* that has the membership approval mode turned on
|
* that has the membership approval mode turned on
|
||||||
* @event Client#group_membership_request
|
* @event Client#group_membership_request
|
||||||
|
|
@ -408,86 +394,86 @@ class Client extends EventEmitter {
|
||||||
* @param {string} notification.author The user ID that made a request
|
* @param {string} notification.author The user ID that made a request
|
||||||
* @param {number} notification.timestamp The timestamp the request was made at
|
* @param {number} notification.timestamp The timestamp the request was made at
|
||||||
*/
|
*/
|
||||||
this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
|
this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* Emitted when group settings are updated, such as subject, description or picture.
|
* Emitted when group settings are updated, such as subject, description or picture.
|
||||||
* @event Client#group_update
|
* @event Client#group_update
|
||||||
* @param {GroupNotification} notification GroupNotification with more information about the action
|
* @param {GroupNotification} notification GroupNotification with more information about the action
|
||||||
*/
|
*/
|
||||||
this.emit(Events.GROUP_UPDATE, notification);
|
this.emit(Events.GROUP_UPDATE, notification);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const message = new Message(this, msg);
|
const message = new Message(this, msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a new message is created, which may include the current user's own messages.
|
* Emitted when a new message is created, which may include the current user's own messages.
|
||||||
* @event Client#message_create
|
* @event Client#message_create
|
||||||
* @param {Message} message The message that was created
|
* @param {Message} message The message that was created
|
||||||
*/
|
*/
|
||||||
this.emit(Events.MESSAGE_CREATE, message);
|
this.emit(Events.MESSAGE_CREATE, message);
|
||||||
|
|
||||||
if (msg.id.fromMe) return;
|
if (msg.id.fromMe) return;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a new message is received.
|
* Emitted when a new message is received.
|
||||||
* @event Client#message
|
* @event Client#message
|
||||||
* @param {Message} message The message that was received
|
* @param {Message} message The message that was received
|
||||||
*/
|
*/
|
||||||
this.emit(Events.MESSAGE_RECEIVED, message);
|
this.emit(Events.MESSAGE_RECEIVED, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
let revoked_msg;
|
let revoked_msg;
|
||||||
if (last_message && msg.id.id === last_message.id.id) {
|
if (last_message && msg.id.id === last_message.id.id) {
|
||||||
revoked_msg = new Message(this, last_message);
|
revoked_msg = new Message(this, last_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a message is deleted for everyone in the chat.
|
* Emitted when a message is deleted for everyone in the chat.
|
||||||
* @event Client#message_revoke_everyone
|
* @event Client#message_revoke_everyone
|
||||||
* @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
|
* @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
|
||||||
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
|
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
|
||||||
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
|
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
|
||||||
*/
|
*/
|
||||||
this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
|
this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The event notification that is received when one of
|
* The event notification that is received when one of
|
||||||
* the group participants changes their phone number.
|
* the group participants changes their phone number.
|
||||||
*/
|
*/
|
||||||
const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
|
const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The event notification that is received when one of
|
* The event notification that is received when one of
|
||||||
* the contacts changes their phone number.
|
* the contacts changes their phone number.
|
||||||
*/
|
*/
|
||||||
const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
|
const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
|
||||||
|
|
||||||
if (isParticipant || isContact) {
|
if (isParticipant || isContact) {
|
||||||
/** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
|
/** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
|
||||||
const message = new Message(this, msg);
|
const message = new Message(this, msg);
|
||||||
|
|
||||||
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
||||||
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a contact or a group participant changes their phone number.
|
* Emitted when a contact or a group participant changes their phone number.
|
||||||
* @event Client#contact_changed
|
* @event Client#contact_changed
|
||||||
* @param {Message} message Message with more information about the event.
|
* @param {Message} message Message with more information about the event.
|
||||||
|
|
@ -496,98 +482,98 @@ class Client extends EventEmitter {
|
||||||
* @param {String} newId The user's new id after the change.
|
* @param {String} newId The user's new id after the change.
|
||||||
* @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
|
* @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
|
||||||
*/
|
*/
|
||||||
this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
|
this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
|
await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => {
|
||||||
|
|
||||||
if (!msg.isNewMsg) return;
|
if (!msg.isNewMsg) return;
|
||||||
|
|
||||||
const message = new Message(this, msg);
|
const message = new Message(this, msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a message is deleted by the current user.
|
* Emitted when a message is deleted by the current user.
|
||||||
* @event Client#message_revoke_me
|
* @event Client#message_revoke_me
|
||||||
* @param {Message} message The message that was revoked
|
* @param {Message} message The message that was revoked
|
||||||
*/
|
*/
|
||||||
this.emit(Events.MESSAGE_REVOKED_ME, message);
|
this.emit(Events.MESSAGE_REVOKED_ME, message);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when an ack event occurrs on message type.
|
* Emitted when an ack event occurrs on message type.
|
||||||
* @event Client#message_ack
|
* @event Client#message_ack
|
||||||
* @param {Message} message The message that was affected
|
* @param {Message} message The message that was affected
|
||||||
* @param {MessageAck} ack The new ACK value
|
* @param {MessageAck} ack The new ACK value
|
||||||
*/
|
*/
|
||||||
this.emit(Events.MESSAGE_ACK, message, ack);
|
this.emit(Events.MESSAGE_ACK, message, ack);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the chat unread count changes
|
* Emitted when the chat unread count changes
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when media has been uploaded for a message sent by the client.
|
* Emitted when media has been uploaded for a message sent by the client.
|
||||||
* @event Client#media_uploaded
|
* @event Client#media_uploaded
|
||||||
* @param {Message} message The message with media that was uploaded
|
* @param {Message} message The message with media that was uploaded
|
||||||
*/
|
*/
|
||||||
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
|
||||||
* @param {WAState} state the new connection state
|
* @param {WAState} state the new connection state
|
||||||
*/
|
*/
|
||||||
this.emit(Events.STATE_CHANGED, state);
|
this.emit(Events.STATE_CHANGED, state);
|
||||||
|
|
||||||
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
|
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
|
||||||
|
|
||||||
if (this.options.takeoverOnConflict) {
|
if (this.options.takeoverOnConflict) {
|
||||||
ACCEPTED_STATES.push(WAState.CONFLICT);
|
ACCEPTED_STATES.push(WAState.CONFLICT);
|
||||||
|
|
||||||
if (state === WAState.CONFLICT) {
|
if (state === WAState.CONFLICT) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
||||||
}, this.options.takeoverTimeoutMs);
|
}, this.options.takeoverTimeoutMs);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ACCEPTED_STATES.includes(state)) {
|
if (!ACCEPTED_STATES.includes(state)) {
|
||||||
/**
|
/**
|
||||||
* Emitted when the client has been disconnected
|
* Emitted when the client has been disconnected
|
||||||
* @event Client#disconnected
|
* @event Client#disconnected
|
||||||
* @param {WAState|"LOGOUT"} reason reason that caused the disconnect
|
* @param {WAState|"LOGOUT"} reason reason that caused the disconnect
|
||||||
*/
|
*/
|
||||||
await this.authStrategy.disconnect();
|
await this.authStrategy.disconnect();
|
||||||
this.emit(Events.DISCONNECTED, state);
|
this.emit(Events.DISCONNECTED, state);
|
||||||
this.destroy();
|
this.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
|
* Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
|
||||||
* @event Client#change_battery
|
* @event Client#change_battery
|
||||||
* @param {object} batteryInfo
|
* @param {object} batteryInfo
|
||||||
|
|
@ -595,11 +581,11 @@ class Client extends EventEmitter {
|
||||||
* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
|
* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
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
|
||||||
* @param {object} call
|
* @param {object} call
|
||||||
|
|
@ -612,13 +598,13 @@ class Client extends EventEmitter {
|
||||||
* @param {boolean} call.webClientShouldHandle - If Waweb should handle
|
* @param {boolean} call.webClientShouldHandle - If Waweb should handle
|
||||||
* @param {object} call.participants - Participants
|
* @param {object} call.participants - Participants
|
||||||
*/
|
*/
|
||||||
const cll = new Call(this, call);
|
const cll = new Call(this, call);
|
||||||
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
|
||||||
* @event Client#message_reaction
|
* @event Client#message_reaction
|
||||||
* @param {object} reaction
|
* @param {object} reaction
|
||||||
|
|
@ -633,69 +619,68 @@ class Client extends EventEmitter {
|
||||||
* @param {?number} reaction.ack - Ack
|
* @param {?number} reaction.ack - Ack
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a chat is removed
|
* Emitted when a chat is removed
|
||||||
* @event Client#chat_removed
|
* @event Client#chat_removed
|
||||||
* @param {Chat} chat
|
* @param {Chat} chat
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when a chat is archived/unarchived
|
* Emitted when a chat is archived/unarchived
|
||||||
* @event Client#chat_archived
|
* @event Client#chat_archived
|
||||||
* @param {Chat} chat
|
* @param {Chat} chat
|
||||||
* @param {boolean} currState
|
* @param {boolean} currState
|
||||||
* @param {boolean} prevState
|
* @param {boolean} prevState
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Emitted when messages are edited
|
* Emitted when messages are edited
|
||||||
* @event Client#message_edit
|
* @event Client#message_edit
|
||||||
* @param {Message} message
|
* @param {Message} message
|
||||||
* @param {string} newBody
|
* @param {string} newBody
|
||||||
* @param {string} prevBody
|
* @param {string} prevBody
|
||||||
*/
|
*/
|
||||||
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
|
||||||
* @event Client#message_ciphertext
|
* @event Client#message_ciphertext
|
||||||
* @param {Message} message
|
* @param {Message} message
|
||||||
*/
|
*/
|
||||||
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,
|
||||||
* shows a user's current selected option(s) on the poll
|
* shows a user's current selected option(s) on the poll
|
||||||
* @event Client#vote_update
|
* @event Client#vote_update
|
||||||
*/
|
*/
|
||||||
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
23
src/util/Puppeteer.js
Normal 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};
|
||||||
Loading…
Reference in a new issue