whatsapp-web.js 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Client.js CHANGED
@@ -7,12 +7,15 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
7
7
  const Util = require('./util/Util');
8
8
  const InterfaceController = require('./util/InterfaceController');
9
9
  const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants');
10
- const { ExposeStore, LoadUtils } = require('./util/Injected');
10
+ const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
11
+ const { ExposeStore } = require('./util/Injected/Store');
12
+ const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore');
13
+ const { ExposeLegacyStore } = require('./util/Injected/LegacyStore');
14
+ const { LoadUtils } = require('./util/Injected/Utils');
11
15
  const ChatFactory = require('./factories/ChatFactory');
12
16
  const ContactFactory = require('./factories/ContactFactory');
13
17
  const WebCacheFactory = require('./webCache/WebCacheFactory');
14
18
  const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
15
- const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
16
19
  const NoAuth = require('./authStrategies/NoAuth');
17
20
 
18
21
  /**
@@ -63,37 +66,227 @@ class Client extends EventEmitter {
63
66
  this.options = Util.mergeDefault(DefaultOptions, options);
64
67
 
65
68
  if(!this.options.authStrategy) {
66
- if(Object.prototype.hasOwnProperty.call(this.options, 'session')) {
67
- process.emitWarning(
68
- 'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' +
69
- 'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).',
70
- 'DeprecationWarning'
71
- );
72
-
73
- this.authStrategy = new LegacySessionAuth({
74
- session: this.options.session,
75
- restartOnAuthFail: this.options.restartOnAuthFail
76
- });
77
- } else {
78
- this.authStrategy = new NoAuth();
79
- }
69
+ this.authStrategy = new NoAuth();
80
70
  } else {
81
71
  this.authStrategy = this.options.authStrategy;
82
72
  }
83
73
 
84
74
  this.authStrategy.setup(this);
85
75
 
76
+ /**
77
+ * @type {puppeteer.Browser}
78
+ */
86
79
  this.pupBrowser = null;
80
+ /**
81
+ * @type {puppeteer.Page}
82
+ */
87
83
  this.pupPage = null;
88
84
 
85
+ this.currentIndexHtml = null;
86
+ this.lastLoggedOut = false;
87
+
89
88
  Util.setFfmpegPath(this.options.ffmpegPath);
90
89
  }
90
+ /**
91
+ * Injection logic
92
+ * Private function
93
+ * @property {boolean} reinject is this a reinject?
94
+ */
95
+ async inject(reinject = false) {
96
+ await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
97
+
98
+ const version = await this.getWWebVersion();
99
+ const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;
100
+
101
+ if (isCometOrAbove) {
102
+ await this.pupPage.evaluate(ExposeAuthStore);
103
+ } else {
104
+ await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString());
105
+ }
106
+
107
+ const needAuthentication = await this.pupPage.evaluate(async () => {
108
+ let state = window.AuthStore.AppState.state;
109
+
110
+ if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') {
111
+ // wait till state changes
112
+ await new Promise(r => {
113
+ window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) {
114
+ if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') {
115
+ window.AuthStore.AppState.off('change:state', waitTillInit);
116
+ r();
117
+ }
118
+ });
119
+ });
120
+ }
121
+ state = window.AuthStore.AppState.state;
122
+ return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE';
123
+ });
124
+
125
+ if (needAuthentication) {
126
+ const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
127
+
128
+ if(failed) {
129
+ /**
130
+ * Emitted when there has been an error while trying to restore an existing session
131
+ * @event Client#auth_failure
132
+ * @param {string} message
133
+ */
134
+ this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
135
+ await this.destroy();
136
+ if (restart) {
137
+ // session restore failed so try again but without session to force new authentication
138
+ return this.initialize();
139
+ }
140
+ return;
141
+ }
142
+
143
+ // Register qr events
144
+ let qrRetries = 0;
145
+ const injected = await this.pupPage.evaluate(() => {
146
+ return typeof window.onQRChangedEvent !== 'undefined';
147
+ });
148
+ if (!injected) {
149
+ await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => {
150
+ /**
151
+ * Emitted when a QR code is received
152
+ * @event Client#qr
153
+ * @param {string} qr QR Code
154
+ */
155
+ this.emit(Events.QR_RECEIVED, qr);
156
+ if (this.options.qrMaxRetries > 0) {
157
+ qrRetries++;
158
+ if (qrRetries > this.options.qrMaxRetries) {
159
+ this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
160
+ await this.destroy();
161
+ }
162
+ }
163
+ });
164
+ }
165
+
166
+
167
+ await this.pupPage.evaluate(async () => {
168
+ const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
169
+ const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
170
+ const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
171
+ const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
172
+ const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
173
+ const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
174
+ const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
175
+
176
+ window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
177
+ window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
178
+ });
179
+ }
180
+
181
+ if (!reinject) {
182
+ await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => {
183
+ if (state == 'UNPAIRED_IDLE') {
184
+ // refresh qr code
185
+ window.Store.Cmd.refreshQR();
186
+ }
187
+ });
188
+
189
+ await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => {
190
+ const authEventPayload = await this.authStrategy.getAuthEventPayload();
191
+ /**
192
+ * Emitted when authentication is successful
193
+ * @event Client#authenticated
194
+ */
195
+ this.emit(Events.AUTHENTICATED, authEventPayload);
196
+
197
+ const injected = await this.pupPage.evaluate(async () => {
198
+ return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
199
+ });
200
+
201
+ if (!injected) {
202
+ if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
203
+ const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
204
+ const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
205
+
206
+ await webCache.persist(this.currentIndexHtml, version);
207
+ }
208
+
209
+ if (isCometOrAbove) {
210
+ await this.pupPage.evaluate(ExposeStore);
211
+ } else {
212
+ // make sure all modules are ready before injection
213
+ // 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
214
+ await new Promise(r => setTimeout(r, 2000));
215
+ await this.pupPage.evaluate(ExposeLegacyStore);
216
+ }
217
+
218
+ // Check window.Store Injection
219
+ await this.pupPage.waitForFunction('window.Store != undefined');
220
+
221
+ /**
222
+ * Current connection information
223
+ * @type {ClientInfo}
224
+ */
225
+ this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
226
+ return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
227
+ }));
228
+
229
+ this.interface = new InterfaceController(this);
230
+
231
+ //Load util functions (serializers, helper functions)
232
+ await this.pupPage.evaluate(LoadUtils);
233
+
234
+ await this.attachEventListeners(reinject);
235
+ reinject = true;
236
+ }
237
+ /**
238
+ * Emitted when the client has initialized and is ready to receive messages.
239
+ * @event Client#ready
240
+ */
241
+ this.emit(Events.READY);
242
+ this.authStrategy.afterAuthReady();
243
+ });
244
+ let lastPercent = null;
245
+ await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => {
246
+ if (lastPercent !== percent) {
247
+ lastPercent = percent;
248
+ this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
249
+ }
250
+ });
251
+ }
252
+ const logoutCatchInjected = await this.pupPage.evaluate(() => {
253
+ return typeof window.onLogoutEvent !== 'undefined';
254
+ });
255
+ if (!logoutCatchInjected) {
256
+ await this.pupPage.exposeFunction('onLogoutEvent', async () => {
257
+ this.lastLoggedOut = true;
258
+ await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
259
+ });
260
+ }
261
+ await this.pupPage.evaluate(() => {
262
+ window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
263
+ window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
264
+ window.AuthStore.Cmd.on('offline_progress_update', () => {
265
+ window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress());
266
+ });
267
+ window.AuthStore.Cmd.on('logout', async () => {
268
+ await window.onLogoutEvent();
269
+ });
270
+ });
271
+ }
91
272
 
92
273
  /**
93
274
  * Sets up events and requirements, kicks off authentication request
94
275
  */
95
276
  async initialize() {
96
- let [browser, page] = [null, null];
277
+
278
+ let
279
+ /**
280
+ * @type {puppeteer.Browser}
281
+ */
282
+ browser,
283
+ /**
284
+ * @type {puppeteer.Page}
285
+ */
286
+ page;
287
+
288
+ browser = null;
289
+ page = null;
97
290
 
98
291
  await this.authStrategy.beforeBrowserInitialized();
99
292
 
@@ -126,7 +319,8 @@ class Client extends EventEmitter {
126
319
  await this.authStrategy.afterBrowserInitialized();
127
320
  await this.initWebVersionCache();
128
321
 
129
- // ocVesion (isOfficialClient patch)
322
+ // ocVersion (isOfficialClient patch)
323
+ // remove after 2.3000.x hard release
130
324
  await page.evaluateOnNewDocument(() => {
131
325
  const originalError = Error;
132
326
  //eslint-disable-next-line no-global-assign
@@ -144,549 +338,355 @@ class Client extends EventEmitter {
144
338
  referer: 'https://whatsapp.com/'
145
339
  });
146
340
 
147
- await page.evaluate(`function getElementByXpath(path) {
148
- return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
149
- }`);
150
-
151
- let lastPercent = null,
152
- lastPercentMessage = null;
341
+ await this.inject();
153
342
 
154
- await page.exposeFunction('loadingScreen', async (percent, message) => {
155
- if (lastPercent !== percent || lastPercentMessage !== message) {
156
- this.emit(Events.LOADING_SCREEN, percent, message);
157
- lastPercent = percent;
158
- lastPercentMessage = message;
343
+ this.pupPage.on('framenavigated', async (frame) => {
344
+ if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
345
+ this.emit(Events.DISCONNECTED, 'LOGOUT');
346
+ await this.authStrategy.logout();
347
+ await this.authStrategy.beforeBrowserInitialized();
348
+ await this.authStrategy.afterBrowserInitialized();
349
+ this.lastLoggedOut = false;
159
350
  }
351
+ await this.inject(true);
160
352
  });
353
+ }
161
354
 
162
- await page.evaluate(
163
- async function (selectors) {
164
- var observer = new MutationObserver(function () {
165
- let progressBar = window.getElementByXpath(
166
- selectors.PROGRESS
167
- );
168
- let progressMessage = window.getElementByXpath(
169
- selectors.PROGRESS_MESSAGE
170
- );
355
+ /**
356
+ * Request authentication via pairing code instead of QR code
357
+ * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
358
+ * @param {boolean} showNotification - Show notification to pair on phone number
359
+ * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
360
+ */
361
+ async requestPairingCode(phoneNumber, showNotification = true) {
362
+ return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
363
+ window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
364
+ await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
365
+ return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
366
+ }, phoneNumber, showNotification);
367
+ }
171
368
 
172
- if (progressBar) {
173
- window.loadingScreen(
174
- progressBar.value,
175
- progressMessage.innerText
176
- );
369
+ /**
370
+ * Attach event listeners to WA Web
371
+ * Private function
372
+ * @property {boolean} reinject is this a reinject?
373
+ */
374
+ async attachEventListeners(reinject = false) {
375
+ if (!reinject) {
376
+ await this.pupPage.exposeFunction('onAddMessageEvent', msg => {
377
+ if (msg.type === 'gp2') {
378
+ const notification = new GroupNotification(this, msg);
379
+ if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
380
+ /**
381
+ * Emitted when a user joins the chat via invite link or is added by an admin.
382
+ * @event Client#group_join
383
+ * @param {GroupNotification} notification GroupNotification with more information about the action
384
+ */
385
+ this.emit(Events.GROUP_JOIN, notification);
386
+ } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
387
+ /**
388
+ * Emitted when a user leaves the chat or is removed by an admin.
389
+ * @event Client#group_leave
390
+ * @param {GroupNotification} notification GroupNotification with more information about the action
391
+ */
392
+ this.emit(Events.GROUP_LEAVE, notification);
393
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
394
+ /**
395
+ * Emitted when a current user is promoted to an admin or demoted to a regular user.
396
+ * @event Client#group_admin_changed
397
+ * @param {GroupNotification} notification GroupNotification with more information about the action
398
+ */
399
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
400
+ } else if (msg.subtype === 'membership_approval_request') {
401
+ /**
402
+ * Emitted when some user requested to join the group
403
+ * that has the membership approval mode turned on
404
+ * @event Client#group_membership_request
405
+ * @param {GroupNotification} notification GroupNotification with more information about the action
406
+ * @param {string} notification.chatId The group ID the request was made for
407
+ * @param {string} notification.author The user ID that made a request
408
+ * @param {number} notification.timestamp The timestamp the request was made at
409
+ */
410
+ this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
411
+ } else {
412
+ /**
413
+ * Emitted when group settings are updated, such as subject, description or picture.
414
+ * @event Client#group_update
415
+ * @param {GroupNotification} notification GroupNotification with more information about the action
416
+ */
417
+ this.emit(Events.GROUP_UPDATE, notification);
177
418
  }
178
- });
179
-
180
- observer.observe(document, {
181
- attributes: true,
182
- childList: true,
183
- characterData: true,
184
- subtree: true,
185
- });
186
- },
187
- {
188
- PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress',
189
- PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]',
190
- }
191
- );
192
-
193
- const INTRO_IMG_SELECTOR = '[data-icon=\'search\']';
194
- const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas';
195
-
196
- // Checks which selector appears first
197
- const needAuthentication = await Promise.race([
198
- new Promise(resolve => {
199
- page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs })
200
- .then(() => resolve(false))
201
- .catch((err) => resolve(err));
202
- }),
203
- new Promise(resolve => {
204
- page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs })
205
- .then(() => resolve(true))
206
- .catch((err) => resolve(err));
207
- })
208
- ]);
209
-
210
- // Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race;
211
- if (needAuthentication instanceof Error) throw needAuthentication;
212
-
213
- // Scan-qrcode selector was found. Needs authentication
214
- if (needAuthentication) {
215
- const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
216
- if(failed) {
217
- /**
218
- * Emitted when there has been an error while trying to restore an existing session
219
- * @event Client#auth_failure
220
- * @param {string} message
221
- */
222
- this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
223
- await this.destroy();
224
- if (restart) {
225
- // session restore failed so try again but without session to force new authentication
226
- return this.initialize();
419
+ return;
227
420
  }
228
- return;
229
- }
230
421
 
231
- const QR_CONTAINER = 'div[data-ref]';
232
- const QR_RETRY_BUTTON = 'div[data-ref] > span > button';
233
- let qrRetries = 0;
234
- await page.exposeFunction('qrChanged', async (qr) => {
422
+ const message = new Message(this, msg);
423
+
235
424
  /**
236
- * Emitted when a QR code is received
237
- * @event Client#qr
238
- * @param {string} qr QR Code
239
- */
240
- this.emit(Events.QR_RECEIVED, qr);
241
- if (this.options.qrMaxRetries > 0) {
242
- qrRetries++;
243
- if (qrRetries > this.options.qrMaxRetries) {
244
- this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
245
- await this.destroy();
246
- }
247
- }
248
- });
425
+ * Emitted when a new message is created, which may include the current user's own messages.
426
+ * @event Client#message_create
427
+ * @param {Message} message The message that was created
428
+ */
429
+ this.emit(Events.MESSAGE_CREATE, message);
249
430
 
250
- await page.evaluate(function (selectors) {
251
- const qr_container = document.querySelector(selectors.QR_CONTAINER);
252
- window.qrChanged(qr_container.dataset.ref);
431
+ if (msg.id.fromMe) return;
253
432
 
254
- const obs = new MutationObserver((muts) => {
255
- muts.forEach(mut => {
256
- // Listens to qr token change
257
- if (mut.type === 'attributes' && mut.attributeName === 'data-ref') {
258
- window.qrChanged(mut.target.dataset.ref);
259
- }
260
- // Listens to retry button, when found, click it
261
- else if (mut.type === 'childList') {
262
- const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON);
263
- if (retry_button) retry_button.click();
264
- }
265
- });
266
- });
267
- obs.observe(qr_container.parentElement, {
268
- subtree: true,
269
- childList: true,
270
- attributes: true,
271
- attributeFilter: ['data-ref'],
272
- });
273
- }, {
274
- QR_CONTAINER,
275
- QR_RETRY_BUTTON
433
+ /**
434
+ * Emitted when a new message is received.
435
+ * @event Client#message
436
+ * @param {Message} message The message that was received
437
+ */
438
+ this.emit(Events.MESSAGE_RECEIVED, message);
276
439
  });
277
440
 
278
- // Wait for code scan
279
- try {
280
- await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 });
281
- } catch(error) {
282
- if (
283
- error.name === 'ProtocolError' &&
284
- error.message &&
285
- error.message.match(/Target closed/)
286
- ) {
287
- // something has called .destroy() while waiting
288
- return;
289
- }
290
-
291
- throw error;
292
- }
293
-
294
- }
441
+ let last_message;
295
442
 
296
- await page.evaluate(() => {
297
- /**
298
- * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
299
- * @param {string} lOperand The left operand for the WWeb version string to compare with
300
- * @param {string} operator The comparison operator
301
- * @param {string} rOperand The right operand for the WWeb version string to compare with
302
- * @returns {boolean} Boolean value that indicates the result of the comparison
303
- */
304
- window.compareWwebVersions = (lOperand, operator, rOperand) => {
305
- if (!['>', '>=', '<', '<=', '='].includes(operator)) {
306
- throw new class _ extends Error {
307
- constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
308
- }('Invalid comparison operator is provided');
443
+ await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
309
444
 
310
- }
311
- if (typeof lOperand !== 'string' || typeof rOperand !== 'string') {
312
- throw new class _ extends Error {
313
- constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
314
- }('A non-string WWeb version type is provided');
315
- }
316
-
317
- lOperand = lOperand.replace(/-beta$/, '');
318
- rOperand = rOperand.replace(/-beta$/, '');
445
+ if (msg.type === 'revoked') {
446
+ const message = new Message(this, msg);
447
+ let revoked_msg;
448
+ if (last_message && msg.id.id === last_message.id.id) {
449
+ revoked_msg = new Message(this, last_message);
450
+ }
319
451
 
320
- while (lOperand.length !== rOperand.length) {
321
- lOperand.length > rOperand.length
322
- ? rOperand = rOperand.concat('0')
323
- : lOperand = lOperand.concat('0');
452
+ /**
453
+ * Emitted when a message is deleted for everyone in the chat.
454
+ * @event Client#message_revoke_everyone
455
+ * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
456
+ * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
457
+ * Note that due to the way this data is captured, it may be possible that this param will be undefined.
458
+ */
459
+ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
324
460
  }
325
461
 
326
- lOperand = Number(lOperand.replace(/\./g, ''));
327
- rOperand = Number(rOperand.replace(/\./g, ''));
462
+ });
328
463
 
329
- return (
330
- operator === '>' ? lOperand > rOperand :
331
- operator === '>=' ? lOperand >= rOperand :
332
- operator === '<' ? lOperand < rOperand :
333
- operator === '<=' ? lOperand <= rOperand :
334
- operator === '=' ? lOperand === rOperand :
335
- false
336
- );
337
- };
338
- });
464
+ await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
339
465
 
340
- await page.evaluate(ExposeStore, moduleRaid.toString());
341
- const authEventPayload = await this.authStrategy.getAuthEventPayload();
466
+ if (msg.type !== 'revoked') {
467
+ last_message = msg;
468
+ }
342
469
 
343
- /**
344
- * Emitted when authentication is successful
345
- * @event Client#authenticated
346
- */
347
- this.emit(Events.AUTHENTICATED, authEventPayload);
470
+ /**
471
+ * The event notification that is received when one of
472
+ * the group participants changes their phone number.
473
+ */
474
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
348
475
 
349
- // Check window.Store Injection
350
- await page.waitForFunction('window.Store != undefined');
476
+ /**
477
+ * The event notification that is received when one of
478
+ * the contacts changes their phone number.
479
+ */
480
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
351
481
 
352
- await page.evaluate(async () => {
353
- // safely unregister service workers
354
- const registrations = await navigator.serviceWorker.getRegistrations();
355
- for (let registration of registrations) {
356
- registration.unregister();
357
- }
358
- });
482
+ if (isParticipant || isContact) {
483
+ /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
484
+ const message = new Message(this, msg);
359
485
 
360
- //Load util functions (serializers, helper functions)
361
- await page.evaluate(LoadUtils);
486
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
487
+ const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
362
488
 
363
- // Expose client info
364
- /**
365
- * Current connection information
366
- * @type {ClientInfo}
367
- */
368
- this.info = new ClientInfo(this, await page.evaluate(() => {
369
- return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
370
- }));
371
-
372
- // Add InterfaceController
373
- this.interface = new InterfaceController(this);
374
-
375
- // Register events
376
- await page.exposeFunction('onAddMessageEvent', msg => {
377
- if (msg.type === 'gp2') {
378
- const notification = new GroupNotification(this, msg);
379
- if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
380
489
  /**
381
- * Emitted when a user joins the chat via invite link or is added by an admin.
382
- * @event Client#group_join
383
- * @param {GroupNotification} notification GroupNotification with more information about the action
490
+ * Emitted when a contact or a group participant changes their phone number.
491
+ * @event Client#contact_changed
492
+ * @param {Message} message Message with more information about the event.
493
+ * @param {String} oldId The user's id (an old one) who changed their phone number
494
+ * and who triggered the notification.
495
+ * @param {String} newId The user's new id after the change.
496
+ * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
384
497
  */
385
- this.emit(Events.GROUP_JOIN, notification);
386
- } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
387
- /**
388
- * Emitted when a user leaves the chat or is removed by an admin.
389
- * @event Client#group_leave
390
- * @param {GroupNotification} notification GroupNotification with more information about the action
391
- */
392
- this.emit(Events.GROUP_LEAVE, notification);
393
- } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
394
- /**
395
- * Emitted when a current user is promoted to an admin or demoted to a regular user.
396
- * @event Client#group_admin_changed
397
- * @param {GroupNotification} notification GroupNotification with more information about the action
398
- */
399
- this.emit(Events.GROUP_ADMIN_CHANGED, notification);
400
- } else if (msg.subtype === 'membership_approval_request') {
401
- /**
402
- * Emitted when some user requested to join the group
403
- * that has the membership approval mode turned on
404
- * @event Client#group_membership_request
405
- * @param {GroupNotification} notification GroupNotification with more information about the action
406
- * @param {string} notification.chatId The group ID the request was made for
407
- * @param {string} notification.author The user ID that made a request
408
- * @param {number} notification.timestamp The timestamp the request was made at
409
- */
410
- this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
411
- } else {
412
- /**
413
- * Emitted when group settings are updated, such as subject, description or picture.
414
- * @event Client#group_update
415
- * @param {GroupNotification} notification GroupNotification with more information about the action
416
- */
417
- this.emit(Events.GROUP_UPDATE, notification);
498
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
418
499
  }
419
- return;
420
- }
421
-
422
- const message = new Message(this, msg);
423
-
424
- /**
425
- * Emitted when a new message is created, which may include the current user's own messages.
426
- * @event Client#message_create
427
- * @param {Message} message The message that was created
428
- */
429
- this.emit(Events.MESSAGE_CREATE, message);
430
-
431
- if (msg.id.fromMe) return;
432
-
433
- /**
434
- * Emitted when a new message is received.
435
- * @event Client#message
436
- * @param {Message} message The message that was received
437
- */
438
- this.emit(Events.MESSAGE_RECEIVED, message);
439
- });
500
+ });
440
501
 
441
- let last_message;
502
+ await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
442
503
 
443
- await page.exposeFunction('onChangeMessageTypeEvent', (msg) => {
504
+ if (!msg.isNewMsg) return;
444
505
 
445
- if (msg.type === 'revoked') {
446
506
  const message = new Message(this, msg);
447
- let revoked_msg;
448
- if (last_message && msg.id.id === last_message.id.id) {
449
- revoked_msg = new Message(this, last_message);
450
- }
451
507
 
452
508
  /**
453
- * Emitted when a message is deleted for everyone in the chat.
454
- * @event Client#message_revoke_everyone
455
- * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
456
- * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
457
- * Note that due to the way this data is captured, it may be possible that this param will be undefined.
509
+ * Emitted when a message is deleted by the current user.
510
+ * @event Client#message_revoke_me
511
+ * @param {Message} message The message that was revoked
458
512
  */
459
- this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
460
- }
461
-
462
- });
513
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
463
514
 
464
- await page.exposeFunction('onChangeMessageEvent', (msg) => {
465
-
466
- if (msg.type !== 'revoked') {
467
- last_message = msg;
468
- }
469
-
470
- /**
471
- * The event notification that is received when one of
472
- * the group participants changes their phone number.
473
- */
474
- const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
515
+ });
475
516
 
476
- /**
477
- * The event notification that is received when one of
478
- * the contacts changes their phone number.
479
- */
480
- const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
517
+ await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
481
518
 
482
- if (isParticipant || isContact) {
483
- /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
484
519
  const message = new Message(this, msg);
485
520
 
486
- const newId = isParticipant ? msg.recipients[0] : msg.to;
487
- const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
488
-
489
521
  /**
490
- * Emitted when a contact or a group participant changes their phone number.
491
- * @event Client#contact_changed
492
- * @param {Message} message Message with more information about the event.
493
- * @param {String} oldId The user's id (an old one) who changed their phone number
494
- * and who triggered the notification.
495
- * @param {String} newId The user's new id after the change.
496
- * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
522
+ * Emitted when an ack event occurrs on message type.
523
+ * @event Client#message_ack
524
+ * @param {Message} message The message that was affected
525
+ * @param {MessageAck} ack The new ACK value
497
526
  */
498
- this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
499
- }
500
- });
501
-
502
- await page.exposeFunction('onRemoveMessageEvent', (msg) => {
503
-
504
- if (!msg.isNewMsg) return;
505
-
506
- const message = new Message(this, msg);
527
+ this.emit(Events.MESSAGE_ACK, message, ack);
507
528
 
508
- /**
509
- * Emitted when a message is deleted by the current user.
510
- * @event Client#message_revoke_me
511
- * @param {Message} message The message that was revoked
512
- */
513
- this.emit(Events.MESSAGE_REVOKED_ME, message);
514
-
515
- });
516
-
517
- await page.exposeFunction('onMessageAckEvent', (msg, ack) => {
518
-
519
- const message = new Message(this, msg);
529
+ });
520
530
 
521
- /**
522
- * Emitted when an ack event occurrs on message type.
523
- * @event Client#message_ack
524
- * @param {Message} message The message that was affected
525
- * @param {MessageAck} ack The new ACK value
526
- */
527
- this.emit(Events.MESSAGE_ACK, message, ack);
531
+ await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
532
+ const chat = await this.getChatById(data.id);
533
+
534
+ /**
535
+ * Emitted when the chat unread count changes
536
+ */
537
+ this.emit(Events.UNREAD_COUNT, chat);
538
+ });
528
539
 
529
- });
540
+ await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
530
541
 
531
- await page.exposeFunction('onChatUnreadCountEvent', async (data) =>{
532
- const chat = await this.getChatById(data.id);
533
-
534
- /**
535
- * Emitted when the chat unread count changes
536
- */
537
- this.emit(Events.UNREAD_COUNT, chat);
538
- });
542
+ const message = new Message(this, msg);
539
543
 
540
- await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
544
+ /**
545
+ * Emitted when media has been uploaded for a message sent by the client.
546
+ * @event Client#media_uploaded
547
+ * @param {Message} message The message with media that was uploaded
548
+ */
549
+ this.emit(Events.MEDIA_UPLOADED, message);
550
+ });
541
551
 
542
- const message = new Message(this, msg);
552
+ await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
553
+ /**
554
+ * Emitted when the connection state changes
555
+ * @event Client#change_state
556
+ * @param {WAState} state the new connection state
557
+ */
558
+ this.emit(Events.STATE_CHANGED, state);
543
559
 
544
- /**
545
- * Emitted when media has been uploaded for a message sent by the client.
546
- * @event Client#media_uploaded
547
- * @param {Message} message The message with media that was uploaded
548
- */
549
- this.emit(Events.MEDIA_UPLOADED, message);
550
- });
560
+ const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
551
561
 
552
- await page.exposeFunction('onAppStateChangedEvent', async (state) => {
562
+ if (this.options.takeoverOnConflict) {
563
+ ACCEPTED_STATES.push(WAState.CONFLICT);
553
564
 
554
- /**
555
- * Emitted when the connection state changes
556
- * @event Client#change_state
557
- * @param {WAState} state the new connection state
558
- */
559
- this.emit(Events.STATE_CHANGED, state);
565
+ if (state === WAState.CONFLICT) {
566
+ setTimeout(() => {
567
+ this.pupPage.evaluate(() => window.Store.AppState.takeover());
568
+ }, this.options.takeoverTimeoutMs);
569
+ }
570
+ }
560
571
 
561
- const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
572
+ if (!ACCEPTED_STATES.includes(state)) {
573
+ /**
574
+ * Emitted when the client has been disconnected
575
+ * @event Client#disconnected
576
+ * @param {WAState|"LOGOUT"} reason reason that caused the disconnect
577
+ */
578
+ await this.authStrategy.disconnect();
579
+ this.emit(Events.DISCONNECTED, state);
580
+ this.destroy();
581
+ }
582
+ });
562
583
 
563
- if (this.options.takeoverOnConflict) {
564
- ACCEPTED_STATES.push(WAState.CONFLICT);
584
+ await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
585
+ const { battery, plugged } = state;
565
586
 
566
- if (state === WAState.CONFLICT) {
567
- setTimeout(() => {
568
- this.pupPage.evaluate(() => window.Store.AppState.takeover());
569
- }, this.options.takeoverTimeoutMs);
570
- }
571
- }
587
+ if (battery === undefined) return;
572
588
 
573
- if (!ACCEPTED_STATES.includes(state)) {
574
589
  /**
575
- * Emitted when the client has been disconnected
576
- * @event Client#disconnected
577
- * @param {WAState|"NAVIGATION"} reason reason that caused the disconnect
590
+ * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
591
+ * @event Client#change_battery
592
+ * @param {object} batteryInfo
593
+ * @param {number} batteryInfo.battery - The current battery percentage
594
+ * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
595
+ * @deprecated
578
596
  */
579
- await this.authStrategy.disconnect();
580
- this.emit(Events.DISCONNECTED, state);
581
- this.destroy();
582
- }
583
- });
597
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
598
+ });
584
599
 
585
- await page.exposeFunction('onBatteryStateChangedEvent', (state) => {
586
- const { battery, plugged } = state;
600
+ await this.pupPage.exposeFunction('onIncomingCall', (call) => {
601
+ /**
602
+ * Emitted when a call is received
603
+ * @event Client#incoming_call
604
+ * @param {object} call
605
+ * @param {number} call.id - Call id
606
+ * @param {string} call.peerJid - Who called
607
+ * @param {boolean} call.isVideo - if is video
608
+ * @param {boolean} call.isGroup - if is group
609
+ * @param {boolean} call.canHandleLocally - if we can handle in waweb
610
+ * @param {boolean} call.outgoing - if is outgoing
611
+ * @param {boolean} call.webClientShouldHandle - If Waweb should handle
612
+ * @param {object} call.participants - Participants
613
+ */
614
+ const cll = new Call(this, call);
615
+ this.emit(Events.INCOMING_CALL, cll);
616
+ });
587
617
 
588
- if (battery === undefined) return;
618
+ await this.pupPage.exposeFunction('onReaction', (reactions) => {
619
+ for (const reaction of reactions) {
620
+ /**
621
+ * Emitted when a reaction is sent, received, updated or removed
622
+ * @event Client#message_reaction
623
+ * @param {object} reaction
624
+ * @param {object} reaction.id - Reaction id
625
+ * @param {number} reaction.orphan - Orphan
626
+ * @param {?string} reaction.orphanReason - Orphan reason
627
+ * @param {number} reaction.timestamp - Timestamp
628
+ * @param {string} reaction.reaction - Reaction
629
+ * @param {boolean} reaction.read - Read
630
+ * @param {object} reaction.msgId - Parent message id
631
+ * @param {string} reaction.senderId - Sender id
632
+ * @param {?number} reaction.ack - Ack
633
+ */
589
634
 
590
- /**
591
- * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
592
- * @event Client#change_battery
593
- * @param {object} batteryInfo
594
- * @param {number} batteryInfo.battery - The current battery percentage
595
- * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
596
- * @deprecated
597
- */
598
- this.emit(Events.BATTERY_CHANGED, { battery, plugged });
599
- });
635
+ this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
636
+ }
637
+ });
600
638
 
601
- await page.exposeFunction('onIncomingCall', (call) => {
602
- /**
603
- * Emitted when a call is received
604
- * @event Client#incoming_call
605
- * @param {object} call
606
- * @param {number} call.id - Call id
607
- * @param {string} call.peerJid - Who called
608
- * @param {boolean} call.isVideo - if is video
609
- * @param {boolean} call.isGroup - if is group
610
- * @param {boolean} call.canHandleLocally - if we can handle in waweb
611
- * @param {boolean} call.outgoing - if is outgoing
612
- * @param {boolean} call.webClientShouldHandle - If Waweb should handle
613
- * @param {object} call.participants - Participants
614
- */
615
- const cll = new Call(this, call);
616
- this.emit(Events.INCOMING_CALL, cll);
617
- });
639
+ await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
640
+ const _chat = await this.getChatById(chat.id);
618
641
 
619
- await page.exposeFunction('onReaction', (reactions) => {
620
- for (const reaction of reactions) {
621
642
  /**
622
- * Emitted when a reaction is sent, received, updated or removed
623
- * @event Client#message_reaction
624
- * @param {object} reaction
625
- * @param {object} reaction.id - Reaction id
626
- * @param {number} reaction.orphan - Orphan
627
- * @param {?string} reaction.orphanReason - Orphan reason
628
- * @param {number} reaction.timestamp - Timestamp
629
- * @param {string} reaction.reaction - Reaction
630
- * @param {boolean} reaction.read - Read
631
- * @param {object} reaction.msgId - Parent message id
632
- * @param {string} reaction.senderId - Sender id
633
- * @param {?number} reaction.ack - Ack
643
+ * Emitted when a chat is removed
644
+ * @event Client#chat_removed
645
+ * @param {Chat} chat
634
646
  */
635
-
636
- this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
637
- }
638
- });
639
-
640
- await page.exposeFunction('onRemoveChatEvent', async (chat) => {
641
- const _chat = await this.getChatById(chat.id);
642
-
643
- /**
644
- * Emitted when a chat is removed
645
- * @event Client#chat_removed
646
- * @param {Chat} chat
647
- */
648
- this.emit(Events.CHAT_REMOVED, _chat);
649
- });
650
-
651
- await page.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
652
- const _chat = await this.getChatById(chat.id);
647
+ this.emit(Events.CHAT_REMOVED, _chat);
648
+ });
653
649
 
654
- /**
655
- * Emitted when a chat is archived/unarchived
656
- * @event Client#chat_archived
657
- * @param {Chat} chat
658
- * @param {boolean} currState
659
- * @param {boolean} prevState
660
- */
661
- this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
662
- });
650
+ await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
651
+ const _chat = await this.getChatById(chat.id);
652
+
653
+ /**
654
+ * Emitted when a chat is archived/unarchived
655
+ * @event Client#chat_archived
656
+ * @param {Chat} chat
657
+ * @param {boolean} currState
658
+ * @param {boolean} prevState
659
+ */
660
+ this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
661
+ });
663
662
 
664
- await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
665
-
666
- if(msg.type === 'revoked'){
667
- return;
668
- }
669
- /**
670
- * Emitted when messages are edited
671
- * @event Client#message_edit
672
- * @param {Message} message
673
- * @param {string} newBody
674
- * @param {string} prevBody
675
- */
676
- this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
677
- });
678
-
679
- await page.exposeFunction('onAddMessageCiphertextEvent', msg => {
663
+ await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
664
+
665
+ if(msg.type === 'revoked'){
666
+ return;
667
+ }
668
+ /**
669
+ * Emitted when messages are edited
670
+ * @event Client#message_edit
671
+ * @param {Message} message
672
+ * @param {string} newBody
673
+ * @param {string} prevBody
674
+ */
675
+ this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
676
+ });
680
677
 
681
- /**
682
- * Emitted when messages are edited
683
- * @event Client#message_ciphertext
684
- * @param {Message} message
685
- */
686
- this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
687
- });
678
+ await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
679
+
680
+ /**
681
+ * Emitted when messages are edited
682
+ * @event Client#message_ciphertext
683
+ * @param {Message} message
684
+ */
685
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
686
+ });
687
+ }
688
688
 
689
- await page.exposeFunction('onPollVoteEvent', (vote) => {
689
+ await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
690
690
  const _vote = new PollVote(this, vote);
691
691
  /**
692
692
  * Emitted when some poll option is selected or deselected,
@@ -696,7 +696,7 @@ class Client extends EventEmitter {
696
696
  this.emit(Events.VOTE_UPDATE, _vote);
697
697
  });
698
698
 
699
- await page.evaluate(() => {
699
+ await this.pupPage.evaluate(() => {
700
700
  window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
701
701
  window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
702
702
  window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
@@ -725,7 +725,23 @@ class Client extends EventEmitter {
725
725
  pollVoteModel && window.onPollVoteEvent(pollVoteModel);
726
726
  });
727
727
 
728
- {
728
+ if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
729
+ const module = window.Store.AddonReactionTable;
730
+ const ogMethod = module.bulkUpsert;
731
+ module.bulkUpsert = ((...args) => {
732
+ window.onReaction(args[0].map(reaction => {
733
+ const msgKey = reaction.id;
734
+ const parentMsgKey = reaction.reactionParentKey;
735
+ const timestamp = reaction.reactionTimestamp / 1000;
736
+ const sender = reaction.author ?? reaction.from;
737
+ const senderUserJid = sender._serialized;
738
+
739
+ return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
740
+ }));
741
+
742
+ return ogMethod(...args);
743
+ }).bind(module);
744
+ } else {
729
745
  const module = window.Store.createOrUpdateReactionsModule;
730
746
  const ogMethod = module.createOrUpdateReactions;
731
747
  module.createOrUpdateReactions = ((...args) => {
@@ -741,24 +757,7 @@ class Client extends EventEmitter {
741
757
  }).bind(module);
742
758
  }
743
759
  });
744
-
745
- /**
746
- * Emitted when the client has initialized and is ready to receive messages.
747
- * @event Client#ready
748
- */
749
- this.emit(Events.READY);
750
- this.authStrategy.afterAuthReady();
751
-
752
- // Disconnect when navigating away when in PAIRING state (detect logout)
753
- this.pupPage.on('framenavigated', async () => {
754
- const appState = await this.getState();
755
- if(!appState || appState === WAState.PAIRING) {
756
- await this.authStrategy.disconnect();
757
- this.emit(Events.DISCONNECTED, 'NAVIGATION');
758
- await this.destroy();
759
- }
760
- });
761
- }
760
+ }
762
761
 
763
762
  async initWebVersionCache() {
764
763
  const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
@@ -783,7 +782,8 @@ class Client extends EventEmitter {
783
782
  } else {
784
783
  this.pupPage.on('response', async (res) => {
785
784
  if(res.ok() && res.url() === WhatsWebURL) {
786
- await webCache.persist(await res.text());
785
+ const indexHtml = await res.text();
786
+ this.currentIndexHtml = indexHtml;
787
787
  }
788
788
  });
789
789
  }
@@ -861,6 +861,7 @@ class Client extends EventEmitter {
861
861
  * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions
862
862
  * @property {string[]} [mentions] - User IDs to mention in the message
863
863
  * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
864
+ * @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI
864
865
  * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
865
866
  * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
866
867
  * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
@@ -894,9 +895,10 @@ class Client extends EventEmitter {
894
895
  sendMediaAsDocument: options.sendMediaAsDocument,
895
896
  caption: options.caption,
896
897
  quotedMessageId: options.quotedMessageId,
897
- parseVCards: options.parseVCards === false ? false : true,
898
+ parseVCards: options.parseVCards !== false,
898
899
  mentionedJidList: options.mentions || [],
899
900
  groupMentions: options.groupMentions,
901
+ invokedBotWid: options.invokedBotWid,
900
902
  extraOptions: options.extra
901
903
  };
902
904
 
@@ -1102,14 +1104,8 @@ class Client extends EventEmitter {
1102
1104
  async setDisplayName(displayName) {
1103
1105
  const couldSet = await this.pupPage.evaluate(async displayName => {
1104
1106
  if(!window.Store.Conn.canSetMyPushname()) return false;
1105
-
1106
- if(window.Store.MDBackend) {
1107
- await window.Store.Settings.setPushname(displayName);
1108
- return true;
1109
- } else {
1110
- const res = await window.Store.Wap.setPushname(displayName);
1111
- return !res.status || res.status === 200;
1112
- }
1107
+ await window.Store.Settings.setPushname(displayName);
1108
+ return true;
1113
1109
  }, displayName);
1114
1110
 
1115
1111
  return couldSet;
@@ -1250,7 +1246,9 @@ class Client extends EventEmitter {
1250
1246
  const profilePic = await this.pupPage.evaluate(async contactId => {
1251
1247
  try {
1252
1248
  const chatWid = window.Store.WidFactory.createWid(contactId);
1253
- return await window.Store.ProfilePic.profilePicFind(chatWid);
1249
+ return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
1250
+ ? await window.Store.ProfilePic.profilePicFind(chatWid)
1251
+ : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
1254
1252
  } catch (err) {
1255
1253
  if(err.name === 'ServerStatusCodeError') return undefined;
1256
1254
  throw err;
@@ -1416,10 +1414,18 @@ class Client extends EventEmitter {
1416
1414
 
1417
1415
  try {
1418
1416
  createGroupResult = await window.Store.GroupUtils.createGroup(
1419
- title,
1420
- participantWids,
1421
- messageTimer,
1422
- parentGroupWid
1417
+ {
1418
+ 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
1419
+ 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
1420
+ 'announce': options.announce === undefined ? true : options.announce,
1421
+ 'ephemeralDuration': messageTimer,
1422
+ 'full': undefined,
1423
+ 'parentGroupId': parentGroupWid,
1424
+ 'restrict': options.restrict === undefined ? true : options.restrict,
1425
+ 'thumb': undefined,
1426
+ 'title': title,
1427
+ },
1428
+ participantWids
1423
1429
  );
1424
1430
  } catch (err) {
1425
1431
  return 'CreateGroupError: An unknown error occupied while creating a group';
@@ -1431,7 +1437,7 @@ class Client extends EventEmitter {
1431
1437
  const statusCode = participant.error ?? 200;
1432
1438
 
1433
1439
  if (autoSendInviteV4 && statusCode === 403) {
1434
- window.Store.ContactCollection.gadd(participant.wid, { silent: true });
1440
+ window.Store.Contact.gadd(participant.wid, { silent: true });
1435
1441
  const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
1436
1442
  await window.Store.Chat.find(participant.wid),
1437
1443
  createGroupResult.wid._serialized,