whatsapp-web.js 1.25.0 → 1.26.1-alpha.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <p>
8
8
  <a href="https://www.npmjs.com/package/whatsapp-web.js"><img src="https://img.shields.io/npm/v/whatsapp-web.js.svg" alt="npm" /></a>
9
9
  <a href="https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765"><img src="https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg" alt="Depfu" /></a>
10
- <img src="https://img.shields.io/badge/WhatsApp_Web-2.2346.52-brightgreen.svg" alt="WhatsApp_Web 2.2346.52" />
10
+ <img src="https://img.shields.io/badge/WhatsApp_Web-2.3000.1016590837-brightgreen.svg" alt="WhatsApp_Web 2.2346.52" />
11
11
  <a href="https://discord.gg/H7DqQs4"><img src="https://img.shields.io/discord/698610475432411196.svg?logo=discord" alt="Discord server" /></a>
12
12
  </p>
13
13
  <br />
package/example.js CHANGED
@@ -450,6 +450,21 @@ client.on('message', async msg => {
450
450
  */
451
451
  const result = await msg.pin(60); // Will pin a message for 1 minute
452
452
  console.log(result); // True if the operation completed successfully, false otherwise
453
+ } else if (msg.body === '!howManyConnections') {
454
+ /**
455
+ * Get user device count by ID
456
+ * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
457
+ * So for a non-enterprise user with one WaWeb connection it should return "2"
458
+ */
459
+ let deviceCount = await client.getContactDeviceCount(msg.from);
460
+ await msg.reply(`You have *${deviceCount}* devices connected`);
461
+ } else if (msg.body === '!syncHistory') {
462
+ const isSynced = await client.syncHistory(msg.from);
463
+ // Or through the Chat object:
464
+ // const chat = await client.getChatById(msg.from);
465
+ // const isSynced = await chat.syncHistory();
466
+
467
+ await msg.reply(isSynced ? 'Historical chat is syncing..' : 'There is no historical chat to sync.');
453
468
  }
454
469
  });
455
470
 
package/index.d.ts CHANGED
@@ -176,7 +176,18 @@ declare namespace WAWebJS {
176
176
  * @param flag true/false on or off
177
177
  */
178
178
  setAutoDownloadVideos(flag: boolean): Promise<void>
179
-
179
+
180
+ /**
181
+ * Get user device count by ID
182
+ * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
183
+ * So for a non-enterprise user with one WaWeb connection it should return "2"
184
+ * @param {string} contactId
185
+ */
186
+ getContactDeviceCount(userId: string): Promise<number>
187
+
188
+ /** Sync history conversation of the Chat */
189
+ syncHistory(chatId: string): Promise<boolean>
190
+
180
191
  /** Changes and returns the archive state of the Chat */
181
192
  unarchiveChat(chatId: string): Promise<boolean>
182
193
 
@@ -1432,6 +1443,8 @@ declare namespace WAWebJS {
1432
1443
  getLabels: () => Promise<Label[]>,
1433
1444
  /** Add or remove labels to this Chat */
1434
1445
  changeLabels: (labelIds: Array<string | number>) => Promise<void>
1446
+ /** Sync history conversation of the Chat */
1447
+ syncHistory: () => Promise<boolean>
1435
1448
  }
1436
1449
 
1437
1450
  export interface MessageSearchOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.25.0",
3
+ "version": "1.26.1-alpha.0",
4
4
  "description": "Library for interacting with the WhatsApp Web API ",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
package/src/Client.js CHANGED
@@ -17,6 +17,7 @@ const ContactFactory = require('./factories/ContactFactory');
17
17
  const WebCacheFactory = require('./webCache/WebCacheFactory');
18
18
  const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
19
19
  const NoAuth = require('./authStrategies/NoAuth');
20
+ const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
20
21
 
21
22
  /**
22
23
  * Starting point for interacting with the WhatsApp Web API
@@ -90,9 +91,8 @@ class Client extends EventEmitter {
90
91
  /**
91
92
  * Injection logic
92
93
  * Private function
93
- * @property {boolean} reinject is this a reinject?
94
94
  */
95
- async inject(reinject = false) {
95
+ async inject() {
96
96
  await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
97
97
 
98
98
  const version = await this.getWWebVersion();
@@ -142,26 +142,21 @@ class Client extends EventEmitter {
142
142
 
143
143
  // Register qr events
144
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
- }
145
+ await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
146
+ /**
147
+ * Emitted when a QR code is received
148
+ * @event Client#qr
149
+ * @param {string} qr QR Code
150
+ */
151
+ this.emit(Events.QR_RECEIVED, qr);
152
+ if (this.options.qrMaxRetries > 0) {
153
+ qrRetries++;
154
+ if (qrRetries > this.options.qrMaxRetries) {
155
+ this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
156
+ await this.destroy();
162
157
  }
163
- });
164
- }
158
+ }
159
+ });
165
160
 
166
161
 
167
162
  await this.pupPage.evaluate(async () => {
@@ -178,86 +173,78 @@ class Client extends EventEmitter {
178
173
  });
179
174
  }
180
175
 
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
- });
176
+ await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => {
177
+ if (state == 'UNPAIRED_IDLE') {
178
+ // refresh qr code
179
+ window.Store.Cmd.refreshQR();
180
+ }
181
+ });
188
182
 
189
- await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => {
190
- const authEventPayload = await this.authStrategy.getAuthEventPayload();
191
- /**
183
+ await exposeFunctionIfAbsent(this.pupPage, 'onAppStateHasSyncedEvent', async () => {
184
+ const authEventPayload = await this.authStrategy.getAuthEventPayload();
185
+ /**
192
186
  * Emitted when authentication is successful
193
187
  * @event Client#authenticated
194
188
  */
195
- this.emit(Events.AUTHENTICATED, authEventPayload);
189
+ this.emit(Events.AUTHENTICATED, authEventPayload);
196
190
 
197
- const injected = await this.pupPage.evaluate(async () => {
198
- return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
199
- });
191
+ const injected = await this.pupPage.evaluate(async () => {
192
+ return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
193
+ });
200
194
 
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);
195
+ if (!injected) {
196
+ if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
197
+ const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
198
+ const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
205
199
 
206
- await webCache.persist(this.currentIndexHtml, version);
207
- }
200
+ await webCache.persist(this.currentIndexHtml, version);
201
+ }
208
202
 
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
- }
203
+ if (isCometOrAbove) {
204
+ await this.pupPage.evaluate(ExposeStore);
205
+ } else {
206
+ // make sure all modules are ready before injection
207
+ // 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
208
+ await new Promise(r => setTimeout(r, 2000));
209
+ await this.pupPage.evaluate(ExposeLegacyStore);
210
+ }
217
211
 
218
- // Check window.Store Injection
219
- await this.pupPage.waitForFunction('window.Store != undefined');
212
+ // Check window.Store Injection
213
+ await this.pupPage.waitForFunction('window.Store != undefined');
220
214
 
221
- /**
215
+ /**
222
216
  * Current connection information
223
217
  * @type {ClientInfo}
224
218
  */
225
- this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
226
- return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
227
- }));
219
+ this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
220
+ return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
221
+ }));
228
222
 
229
- this.interface = new InterfaceController(this);
223
+ this.interface = new InterfaceController(this);
230
224
 
231
- //Load util functions (serializers, helper functions)
232
- await this.pupPage.evaluate(LoadUtils);
225
+ //Load util functions (serializers, helper functions)
226
+ await this.pupPage.evaluate(LoadUtils);
233
227
 
234
- await this.attachEventListeners(reinject);
235
- reinject = true;
236
- }
237
- /**
228
+ await this.attachEventListeners();
229
+ }
230
+ /**
238
231
  * Emitted when the client has initialized and is ready to receive messages.
239
232
  * @event Client#ready
240
233
  */
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';
234
+ this.emit(Events.READY);
235
+ this.authStrategy.afterAuthReady();
236
+ });
237
+ let lastPercent = null;
238
+ await exposeFunctionIfAbsent(this.pupPage, 'onOfflineProgressUpdateEvent', async (percent) => {
239
+ if (lastPercent !== percent) {
240
+ lastPercent = percent;
241
+ this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
242
+ }
243
+ });
244
+ await exposeFunctionIfAbsent(this.pupPage, 'onLogoutEvent', async () => {
245
+ this.lastLoggedOut = true;
246
+ await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
254
247
  });
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
248
  await this.pupPage.evaluate(() => {
262
249
  window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
263
250
  window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
@@ -323,6 +310,7 @@ class Client extends EventEmitter {
323
310
  // remove after 2.3000.x hard release
324
311
  await page.evaluateOnNewDocument(() => {
325
312
  const originalError = Error;
313
+ window.originalError = originalError;
326
314
  //eslint-disable-next-line no-global-assign
327
315
  Error = function (message) {
328
316
  const error = new originalError(message);
@@ -348,7 +336,7 @@ class Client extends EventEmitter {
348
336
  await this.authStrategy.afterBrowserInitialized();
349
337
  this.lastLoggedOut = false;
350
338
  }
351
- await this.inject(true);
339
+ await this.inject();
352
340
  });
353
341
  }
354
342
 
@@ -371,34 +359,33 @@ class Client extends EventEmitter {
371
359
  * Private function
372
360
  * @property {boolean} reinject is this a reinject?
373
361
  */
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
- /**
362
+ async attachEventListeners() {
363
+ await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageEvent', msg => {
364
+ if (msg.type === 'gp2') {
365
+ const notification = new GroupNotification(this, msg);
366
+ if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
367
+ /**
381
368
  * Emitted when a user joins the chat via invite link or is added by an admin.
382
369
  * @event Client#group_join
383
370
  * @param {GroupNotification} notification GroupNotification with more information about the action
384
371
  */
385
- this.emit(Events.GROUP_JOIN, notification);
386
- } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
387
- /**
372
+ this.emit(Events.GROUP_JOIN, notification);
373
+ } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
374
+ /**
388
375
  * Emitted when a user leaves the chat or is removed by an admin.
389
376
  * @event Client#group_leave
390
377
  * @param {GroupNotification} notification GroupNotification with more information about the action
391
378
  */
392
- this.emit(Events.GROUP_LEAVE, notification);
393
- } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
394
- /**
379
+ this.emit(Events.GROUP_LEAVE, notification);
380
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
381
+ /**
395
382
  * Emitted when a current user is promoted to an admin or demoted to a regular user.
396
383
  * @event Client#group_admin_changed
397
384
  * @param {GroupNotification} notification GroupNotification with more information about the action
398
385
  */
399
- this.emit(Events.GROUP_ADMIN_CHANGED, notification);
400
- } else if (msg.subtype === 'membership_approval_request') {
401
- /**
386
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
387
+ } else if (msg.subtype === 'membership_approval_request') {
388
+ /**
402
389
  * Emitted when some user requested to join the group
403
390
  * that has the membership approval mode turned on
404
391
  * @event Client#group_membership_request
@@ -407,86 +394,86 @@ class Client extends EventEmitter {
407
394
  * @param {string} notification.author The user ID that made a request
408
395
  * @param {number} notification.timestamp The timestamp the request was made at
409
396
  */
410
- this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
411
- } else {
412
- /**
397
+ this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
398
+ } else {
399
+ /**
413
400
  * Emitted when group settings are updated, such as subject, description or picture.
414
401
  * @event Client#group_update
415
402
  * @param {GroupNotification} notification GroupNotification with more information about the action
416
403
  */
417
- this.emit(Events.GROUP_UPDATE, notification);
418
- }
419
- return;
404
+ this.emit(Events.GROUP_UPDATE, notification);
420
405
  }
406
+ return;
407
+ }
421
408
 
422
- const message = new Message(this, msg);
409
+ const message = new Message(this, msg);
423
410
 
424
- /**
411
+ /**
425
412
  * Emitted when a new message is created, which may include the current user's own messages.
426
413
  * @event Client#message_create
427
414
  * @param {Message} message The message that was created
428
415
  */
429
- this.emit(Events.MESSAGE_CREATE, message);
416
+ this.emit(Events.MESSAGE_CREATE, message);
430
417
 
431
- if (msg.id.fromMe) return;
418
+ if (msg.id.fromMe) return;
432
419
 
433
- /**
420
+ /**
434
421
  * Emitted when a new message is received.
435
422
  * @event Client#message
436
423
  * @param {Message} message The message that was received
437
424
  */
438
- this.emit(Events.MESSAGE_RECEIVED, message);
439
- });
425
+ this.emit(Events.MESSAGE_RECEIVED, message);
426
+ });
440
427
 
441
- let last_message;
428
+ let last_message;
442
429
 
443
- await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
430
+ await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => {
444
431
 
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
- }
432
+ if (msg.type === 'revoked') {
433
+ const message = new Message(this, msg);
434
+ let revoked_msg;
435
+ if (last_message && msg.id.id === last_message.id.id) {
436
+ revoked_msg = new Message(this, last_message);
437
+ }
451
438
 
452
- /**
439
+ /**
453
440
  * Emitted when a message is deleted for everyone in the chat.
454
441
  * @event Client#message_revoke_everyone
455
442
  * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
456
443
  * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
457
444
  * Note that due to the way this data is captured, it may be possible that this param will be undefined.
458
445
  */
459
- this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
460
- }
446
+ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
447
+ }
461
448
 
462
- });
449
+ });
463
450
 
464
- await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
451
+ await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => {
465
452
 
466
- if (msg.type !== 'revoked') {
467
- last_message = msg;
468
- }
453
+ if (msg.type !== 'revoked') {
454
+ last_message = msg;
455
+ }
469
456
 
470
- /**
457
+ /**
471
458
  * The event notification that is received when one of
472
459
  * the group participants changes their phone number.
473
460
  */
474
- const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
461
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
475
462
 
476
- /**
463
+ /**
477
464
  * The event notification that is received when one of
478
465
  * the contacts changes their phone number.
479
466
  */
480
- const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
467
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
481
468
 
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);
469
+ if (isParticipant || isContact) {
470
+ /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
471
+ const message = new Message(this, msg);
485
472
 
486
- const newId = isParticipant ? msg.recipients[0] : msg.to;
487
- const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
473
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
474
+ const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
488
475
 
489
- /**
476
+ /**
490
477
  * Emitted when a contact or a group participant changes their phone number.
491
478
  * @event Client#contact_changed
492
479
  * @param {Message} message Message with more information about the event.
@@ -495,98 +482,98 @@ class Client extends EventEmitter {
495
482
  * @param {String} newId The user's new id after the change.
496
483
  * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
497
484
  */
498
- this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
499
- }
500
- });
485
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
486
+ }
487
+ });
501
488
 
502
- await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
489
+ await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => {
503
490
 
504
- if (!msg.isNewMsg) return;
491
+ if (!msg.isNewMsg) return;
505
492
 
506
- const message = new Message(this, msg);
493
+ const message = new Message(this, msg);
507
494
 
508
- /**
495
+ /**
509
496
  * Emitted when a message is deleted by the current user.
510
497
  * @event Client#message_revoke_me
511
498
  * @param {Message} message The message that was revoked
512
499
  */
513
- this.emit(Events.MESSAGE_REVOKED_ME, message);
500
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
514
501
 
515
- });
502
+ });
516
503
 
517
- await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
504
+ await exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => {
518
505
 
519
- const message = new Message(this, msg);
506
+ const message = new Message(this, msg);
520
507
 
521
- /**
508
+ /**
522
509
  * Emitted when an ack event occurrs on message type.
523
510
  * @event Client#message_ack
524
511
  * @param {Message} message The message that was affected
525
512
  * @param {MessageAck} ack The new ACK value
526
513
  */
527
- this.emit(Events.MESSAGE_ACK, message, ack);
514
+ this.emit(Events.MESSAGE_ACK, message, ack);
528
515
 
529
- });
516
+ });
530
517
 
531
- await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
532
- const chat = await this.getChatById(data.id);
518
+ await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{
519
+ const chat = await this.getChatById(data.id);
533
520
 
534
- /**
521
+ /**
535
522
  * Emitted when the chat unread count changes
536
523
  */
537
- this.emit(Events.UNREAD_COUNT, chat);
538
- });
524
+ this.emit(Events.UNREAD_COUNT, chat);
525
+ });
539
526
 
540
- await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
527
+ await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => {
541
528
 
542
- const message = new Message(this, msg);
529
+ const message = new Message(this, msg);
543
530
 
544
- /**
531
+ /**
545
532
  * Emitted when media has been uploaded for a message sent by the client.
546
533
  * @event Client#media_uploaded
547
534
  * @param {Message} message The message with media that was uploaded
548
535
  */
549
- this.emit(Events.MEDIA_UPLOADED, message);
550
- });
536
+ this.emit(Events.MEDIA_UPLOADED, message);
537
+ });
551
538
 
552
- await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
553
- /**
539
+ await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => {
540
+ /**
554
541
  * Emitted when the connection state changes
555
542
  * @event Client#change_state
556
543
  * @param {WAState} state the new connection state
557
544
  */
558
- this.emit(Events.STATE_CHANGED, state);
545
+ this.emit(Events.STATE_CHANGED, state);
559
546
 
560
- const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
547
+ const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
561
548
 
562
- if (this.options.takeoverOnConflict) {
563
- ACCEPTED_STATES.push(WAState.CONFLICT);
549
+ if (this.options.takeoverOnConflict) {
550
+ ACCEPTED_STATES.push(WAState.CONFLICT);
564
551
 
565
- if (state === WAState.CONFLICT) {
566
- setTimeout(() => {
567
- this.pupPage.evaluate(() => window.Store.AppState.takeover());
568
- }, this.options.takeoverTimeoutMs);
569
- }
552
+ if (state === WAState.CONFLICT) {
553
+ setTimeout(() => {
554
+ this.pupPage.evaluate(() => window.Store.AppState.takeover());
555
+ }, this.options.takeoverTimeoutMs);
570
556
  }
557
+ }
571
558
 
572
- if (!ACCEPTED_STATES.includes(state)) {
573
- /**
559
+ if (!ACCEPTED_STATES.includes(state)) {
560
+ /**
574
561
  * Emitted when the client has been disconnected
575
562
  * @event Client#disconnected
576
563
  * @param {WAState|"LOGOUT"} reason reason that caused the disconnect
577
564
  */
578
- await this.authStrategy.disconnect();
579
- this.emit(Events.DISCONNECTED, state);
580
- this.destroy();
581
- }
582
- });
565
+ await this.authStrategy.disconnect();
566
+ this.emit(Events.DISCONNECTED, state);
567
+ this.destroy();
568
+ }
569
+ });
583
570
 
584
- await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
585
- const { battery, plugged } = state;
571
+ await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => {
572
+ const { battery, plugged } = state;
586
573
 
587
- if (battery === undefined) return;
574
+ if (battery === undefined) return;
588
575
 
589
- /**
576
+ /**
590
577
  * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
591
578
  * @event Client#change_battery
592
579
  * @param {object} batteryInfo
@@ -594,11 +581,11 @@ class Client extends EventEmitter {
594
581
  * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
595
582
  * @deprecated
596
583
  */
597
- this.emit(Events.BATTERY_CHANGED, { battery, plugged });
598
- });
584
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
585
+ });
599
586
 
600
- await this.pupPage.exposeFunction('onIncomingCall', (call) => {
601
- /**
587
+ await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => {
588
+ /**
602
589
  * Emitted when a call is received
603
590
  * @event Client#incoming_call
604
591
  * @param {object} call
@@ -611,13 +598,13 @@ class Client extends EventEmitter {
611
598
  * @param {boolean} call.webClientShouldHandle - If Waweb should handle
612
599
  * @param {object} call.participants - Participants
613
600
  */
614
- const cll = new Call(this, call);
615
- this.emit(Events.INCOMING_CALL, cll);
616
- });
601
+ const cll = new Call(this, call);
602
+ this.emit(Events.INCOMING_CALL, cll);
603
+ });
617
604
 
618
- await this.pupPage.exposeFunction('onReaction', (reactions) => {
619
- for (const reaction of reactions) {
620
- /**
605
+ await exposeFunctionIfAbsent(this.pupPage, 'onReaction', (reactions) => {
606
+ for (const reaction of reactions) {
607
+ /**
621
608
  * Emitted when a reaction is sent, received, updated or removed
622
609
  * @event Client#message_reaction
623
610
  * @param {object} reaction
@@ -632,61 +619,60 @@ class Client extends EventEmitter {
632
619
  * @param {?number} reaction.ack - Ack
633
620
  */
634
621
 
635
- this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
636
- }
637
- });
622
+ this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
623
+ }
624
+ });
638
625
 
639
- await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
640
- const _chat = await this.getChatById(chat.id);
626
+ await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => {
627
+ const _chat = await this.getChatById(chat.id);
641
628
 
642
- /**
629
+ /**
643
630
  * Emitted when a chat is removed
644
631
  * @event Client#chat_removed
645
632
  * @param {Chat} chat
646
633
  */
647
- this.emit(Events.CHAT_REMOVED, _chat);
648
- });
634
+ this.emit(Events.CHAT_REMOVED, _chat);
635
+ });
649
636
 
650
- await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
651
- const _chat = await this.getChatById(chat.id);
637
+ await exposeFunctionIfAbsent(this.pupPage, 'onArchiveChatEvent', async (chat, currState, prevState) => {
638
+ const _chat = await this.getChatById(chat.id);
652
639
 
653
- /**
640
+ /**
654
641
  * Emitted when a chat is archived/unarchived
655
642
  * @event Client#chat_archived
656
643
  * @param {Chat} chat
657
644
  * @param {boolean} currState
658
645
  * @param {boolean} prevState
659
646
  */
660
- this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
661
- });
647
+ this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
648
+ });
662
649
 
663
- await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
650
+ await exposeFunctionIfAbsent(this.pupPage, 'onEditMessageEvent', (msg, newBody, prevBody) => {
664
651
 
665
- if(msg.type === 'revoked'){
666
- return;
667
- }
668
- /**
652
+ if(msg.type === 'revoked'){
653
+ return;
654
+ }
655
+ /**
669
656
  * Emitted when messages are edited
670
657
  * @event Client#message_edit
671
658
  * @param {Message} message
672
659
  * @param {string} newBody
673
660
  * @param {string} prevBody
674
661
  */
675
- this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
676
- });
662
+ this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
663
+ });
677
664
 
678
- await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
665
+ await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => {
679
666
 
680
- /**
667
+ /**
681
668
  * Emitted when messages are edited
682
669
  * @event Client#message_ciphertext
683
670
  * @param {Message} message
684
671
  */
685
- this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
686
- });
687
- }
672
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
673
+ });
688
674
 
689
- await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
675
+ await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (vote) => {
690
676
  const _vote = new PollVote(this, vote);
691
677
  /**
692
678
  * Emitted when some poll option is selected or deselected,
@@ -720,8 +706,8 @@ class Client extends EventEmitter {
720
706
  }
721
707
  });
722
708
  window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
723
- window.Store.PollVote.on('add', (vote) => {
724
- const pollVoteModel = window.WWebJS.getPollVoteModel(vote);
709
+ window.Store.PollVote.on('add', async (vote) => {
710
+ const pollVoteModel = await window.WWebJS.getPollVoteModel(vote);
725
711
  pollVoteModel && window.onPollVoteEvent(pollVoteModel);
726
712
  });
727
713
 
@@ -802,7 +788,9 @@ class Client extends EventEmitter {
802
788
  */
803
789
  async logout() {
804
790
  await this.pupPage.evaluate(() => {
805
- return window.Store.AppState.logout();
791
+ if (window.Store && window.Store.AppState && typeof window.Store.AppState.logout === 'function') {
792
+ return window.Store.AppState.logout();
793
+ }
806
794
  });
807
795
  await this.pupBrowser.close();
808
796
 
@@ -1034,7 +1022,7 @@ class Client extends EventEmitter {
1034
1022
  if(msg) return window.WWebJS.getMessageModel(msg);
1035
1023
 
1036
1024
  const params = messageId.split('_');
1037
- if(params.length !== 3) throw new Error('Invalid serialized message id specified');
1025
+ if (params.length !== 3 && params.length !== 4) throw new Error('Invalid serialized message id specified');
1038
1026
 
1039
1027
  let messagesObject = await window.Store.Msg.getMessagesById([messageId]);
1040
1028
  if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0];
@@ -1614,8 +1602,8 @@ class Client extends EventEmitter {
1614
1602
  * @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests
1615
1603
  */
1616
1604
  async getGroupMembershipRequests(groupId) {
1617
- return await this.pupPage.evaluate(async (gropId) => {
1618
- const groupWid = window.Store.WidFactory.createWid(gropId);
1605
+ return await this.pupPage.evaluate(async (groupId) => {
1606
+ const groupWid = window.Store.WidFactory.createWid(groupId);
1619
1607
  return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid);
1620
1608
  }, groupId);
1621
1609
  }
@@ -1721,6 +1709,41 @@ class Client extends EventEmitter {
1721
1709
  return flag;
1722
1710
  }, flag);
1723
1711
  }
1712
+
1713
+ /**
1714
+ * Get user device count by ID
1715
+ * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
1716
+ * So for a non-enterprise user with one WaWeb connection it should return "2"
1717
+ * @param {string} userId
1718
+ * @returns {Promise<number>}
1719
+ */
1720
+ async getContactDeviceCount(userId) {
1721
+ return await this.pupPage.evaluate(async (userId) => {
1722
+ const devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(userId)]);
1723
+ if (devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object') {
1724
+ return devices[0].devices.length;
1725
+ }
1726
+ return 0;
1727
+ }, userId);
1728
+ }
1729
+
1730
+ /**
1731
+ * Sync chat history conversation
1732
+ * @param {string} chatId
1733
+ * @return {Promise<boolean>} True if operation completed successfully, false otherwise.
1734
+ */
1735
+ async syncHistory(chatId) {
1736
+ return await this.pupPage.evaluate(async (chatId) => {
1737
+ const chat = await window.WWebJS.getChat(chatId);
1738
+ if (chat.endOfHistoryTransferType === 0) {
1739
+ await window.Store.HistorySync.sendPeerDataOperationRequest(3, {
1740
+ chatId: chat.id
1741
+ });
1742
+ return true;
1743
+ }
1744
+ return false;
1745
+ }, chatId);
1746
+ }
1724
1747
  }
1725
1748
 
1726
1749
  module.exports = Client;
@@ -270,6 +270,14 @@ class Chat extends Base {
270
270
  async changeLabels(labelIds) {
271
271
  return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]);
272
272
  }
273
+
274
+ /**
275
+ * Sync chat history conversation
276
+ * @return {Promise<boolean>} True if operation completed successfully, false otherwise.
277
+ */
278
+ async syncHistory() {
279
+ return this.client.syncHistory(this.id._serialized);
280
+ }
273
281
  }
274
282
 
275
283
  module.exports = Chat;
@@ -182,8 +182,8 @@ class Message extends Base {
182
182
  inviteCodeExp: data.inviteCodeExp,
183
183
  groupId: data.inviteGrp,
184
184
  groupName: data.inviteGrpName,
185
- fromId: '_serialized' in data.from ? data.from._serialized : data.from,
186
- toId: '_serialized' in data.to ? data.to._serialized : data.to
185
+ fromId: typeof data.from === 'object' && '_serialized' in data.from ? data.from._serialized : data.from,
186
+ toId: typeof data.to === 'object' && '_serialized' in data.to ? data.to._serialized : data.to
187
187
  } : undefined;
188
188
 
189
189
  /**
@@ -311,9 +311,9 @@ class Message extends Base {
311
311
  * @returns {Promise<Message>}
312
312
  */
313
313
  async reload() {
314
- const newData = await this.client.pupPage.evaluate((msgId) => {
315
- const msg = window.Store.Msg.get(msgId);
316
- if(!msg) return null;
314
+ const newData = await this.client.pupPage.evaluate(async (msgId) => {
315
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
316
+ if (!msg) return null;
317
317
  return window.WWebJS.getMessageModel(msg);
318
318
  }, this.id._serialized);
319
319
 
@@ -370,8 +370,8 @@ class Message extends Base {
370
370
  async getQuotedMessage() {
371
371
  if (!this.hasQuotedMsg) return undefined;
372
372
 
373
- const quotedMsg = await this.client.pupPage.evaluate((msgId) => {
374
- const msg = window.Store.Msg.get(msgId);
373
+ const quotedMsg = await this.client.pupPage.evaluate(async (msgId) => {
374
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
375
375
  const quotedMsg = window.Store.QuotedMsg.getQuotedMsgObj(msg);
376
376
  return window.WWebJS.getMessageModel(quotedMsg);
377
377
  }, this.id._serialized);
@@ -409,9 +409,10 @@ class Message extends Base {
409
409
  */
410
410
  async react(reaction){
411
411
  await this.client.pupPage.evaluate(async (messageId, reaction) => {
412
- if (!messageId) { return undefined; }
413
-
414
- const msg = await window.Store.Msg.get(messageId);
412
+ if (!messageId) return null;
413
+ const msg =
414
+ window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0];
415
+ if(!msg) return null;
415
416
  await window.Store.sendReactionToMsg(msg, reaction);
416
417
  }, this.id._serialized, reaction);
417
418
  }
@@ -448,9 +449,9 @@ class Message extends Base {
448
449
  }
449
450
 
450
451
  const result = await this.client.pupPage.evaluate(async (msgId) => {
451
- const msg = window.Store.Msg.get(msgId);
452
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
452
453
  if (!msg || !msg.mediaData) {
453
- return undefined;
454
+ return null;
454
455
  }
455
456
  if (msg.mediaData.mediaStage != 'RESOLVED') {
456
457
  // try to resolve media
@@ -500,7 +501,7 @@ class Message extends Base {
500
501
  */
501
502
  async delete(everyone) {
502
503
  await this.client.pupPage.evaluate(async (msgId, everyone) => {
503
- let msg = window.Store.Msg.get(msgId);
504
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
504
505
  let chat = await window.Store.Chat.find(msg.id.remote);
505
506
 
506
507
  const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg);
@@ -521,8 +522,7 @@ class Message extends Base {
521
522
  */
522
523
  async star() {
523
524
  await this.client.pupPage.evaluate(async (msgId) => {
524
- let msg = window.Store.Msg.get(msgId);
525
-
525
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
526
526
  if (window.Store.MsgActionChecks.canStarMsg(msg)) {
527
527
  let chat = await window.Store.Chat.find(msg.id.remote);
528
528
  return window.Store.Cmd.sendStarMsgs(chat, [msg], false);
@@ -535,8 +535,7 @@ class Message extends Base {
535
535
  */
536
536
  async unstar() {
537
537
  await this.client.pupPage.evaluate(async (msgId) => {
538
- let msg = window.Store.Msg.get(msgId);
539
-
538
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
540
539
  if (window.Store.MsgActionChecks.canStarMsg(msg)) {
541
540
  let chat = await window.Store.Chat.find(msg.id.remote);
542
541
  return window.Store.Cmd.sendUnstarMsgs(chat, [msg], false);
@@ -583,7 +582,7 @@ class Message extends Base {
583
582
  */
584
583
  async getInfo() {
585
584
  const info = await this.client.pupPage.evaluate(async (msgId) => {
586
- const msg = window.Store.Msg.get(msgId);
585
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
587
586
  if (!msg || !msg.id.fromMe) return null;
588
587
 
589
588
  return new Promise((resolve) => {
@@ -617,7 +616,7 @@ class Message extends Base {
617
616
  async getPayment() {
618
617
  if (this.type === MessageTypes.PAYMENT) {
619
618
  const msg = await this.client.pupPage.evaluate(async (msgId) => {
620
- const msg = window.Store.Msg.get(msgId);
619
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
621
620
  if(!msg) return null;
622
621
  return msg.serialize();
623
622
  }, this.id._serialized);
@@ -692,11 +691,11 @@ class Message extends Base {
692
691
  return null;
693
692
  }
694
693
  const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => {
695
- let msg = window.Store.Msg.get(msgId);
694
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
696
695
  if (!msg) return null;
697
696
 
698
- let catEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg);
699
- if (catEdit) {
697
+ let canEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg);
698
+ if (canEdit) {
700
699
  const msgEdit = await window.WWebJS.editMessage(msg, message, options);
701
700
  return msgEdit.serialize();
702
701
  }
@@ -7,7 +7,7 @@ exports.DefaultOptions = {
7
7
  headless: true,
8
8
  defaultViewport: null
9
9
  },
10
- webVersion: '2.2346.52',
10
+ webVersion: '2.3000.1016590837',
11
11
  webVersionCache: {
12
12
  type: 'local',
13
13
  },
@@ -97,6 +97,8 @@ exports.ExposeStore = () => {
97
97
  window.Store.Settings = window.require('WAWebUserPrefsGeneral');
98
98
  window.Store.BotSecret = window.require('WAWebBotMessageSecret');
99
99
  window.Store.BotProfiles = window.require('WAWebBotProfileCollection');
100
+ window.Store.DeviceList = window.require('WAWebApiDeviceList');
101
+ window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
100
102
  if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620'))
101
103
  window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
102
104
 
@@ -4,7 +4,7 @@ exports.LoadUtils = () => {
4
4
  window.WWebJS = {};
5
5
 
6
6
  window.WWebJS.forwardMessage = async (chatId, msgId) => {
7
- let msg = window.Store.Msg.get(msgId);
7
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
8
8
  let chat = window.Store.Chat.get(chatId);
9
9
 
10
10
  if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) {
@@ -44,7 +44,13 @@ exports.LoadUtils = () => {
44
44
  }
45
45
  let quotedMsgOptions = {};
46
46
  if (options.quotedMessageId) {
47
- let quotedMessage = window.Store.Msg.get(options.quotedMessageId);
47
+ let quotedMessage = await window.Store.Msg.getMessagesById([options.quotedMessageId]);
48
+
49
+ if (quotedMessage['messages'].length != 1) {
50
+ throw new Error('Could not get the quoted message.');
51
+ }
52
+
53
+ quotedMessage = quotedMessage['messages'][0];
48
54
 
49
55
  // TODO remove .canReply() once all clients are updated to >= v2.2241.6
50
56
  const canReply = window.Store.ReplyUtils ?
@@ -433,14 +439,13 @@ exports.LoadUtils = () => {
433
439
  return msg;
434
440
  };
435
441
 
436
- window.WWebJS.getPollVoteModel = (vote) => {
442
+ window.WWebJS.getPollVoteModel = async (vote) => {
437
443
  const _vote = vote.serialize();
438
- if (vote.parentMsgKey) {
439
- const msg = window.Store.Msg.get(vote.parentMsgKey);
440
- msg && (_vote.parentMessage = window.WWebJS.getMessageModel(msg));
441
- return _vote;
442
- }
443
- return null;
444
+ if (!vote.parentMsgKey) return null;
445
+ const msg =
446
+ window.Store.Msg.get(vote.parentMsgKey) || (await window.Store.Msg.getMessagesById([vote.parentMsgKey]))?.messages?.[0];
447
+ msg && (_vote.parentMessage = window.WWebJS.getMessageModel(msg));
448
+ return _vote;
444
449
  };
445
450
 
446
451
  window.WWebJS.getChatModel = async chat => {
@@ -458,7 +463,9 @@ exports.LoadUtils = () => {
458
463
 
459
464
  res.lastMessage = null;
460
465
  if (res.msgs && res.msgs.length) {
461
- const lastMessage = chat.lastReceivedKey ? window.Store.Msg.get(chat.lastReceivedKey._serialized) : null;
466
+ const lastMessage = chat.lastReceivedKey
467
+ ? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0]
468
+ : null;
462
469
  if (lastMessage) {
463
470
  res.lastMessage = window.WWebJS.getMessageModel(lastMessage);
464
471
  }
@@ -1001,11 +1008,10 @@ exports.LoadUtils = () => {
1001
1008
  };
1002
1009
 
1003
1010
  window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => {
1004
- const message = window.Store.Msg.get(msgId);
1011
+ const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
1005
1012
  if (!message) return false;
1006
1013
  const response = await window.Store.pinUnpinMsg(message, action, duration);
1007
- if (response.messageSendResult === 'OK') return true;
1008
- return false;
1014
+ return response.messageSendResult === 'OK';
1009
1015
  };
1010
1016
 
1011
1017
  };
@@ -15,9 +15,9 @@ class InterfaceController {
15
15
  */
16
16
  async openChatWindow(chatId) {
17
17
  await this.pupPage.evaluate(async chatId => {
18
- let chatWid = window.Store.WidFactory.createWid(chatId);
19
- let chat = await window.Store.Chat.find(chatWid);
20
- await window.Store.Cmd.openChatAt(chat);
18
+ const chatWid = window.Store.WidFactory.createWid(chatId);
19
+ const chat = window.Store.Chat.get(chat) || await window.Store.Chat.find(chatWid);
20
+ await window.Store.Cmd.openChatBottom(chat);
21
21
  }, chatId);
22
22
  }
23
23
 
@@ -49,7 +49,7 @@ class InterfaceController {
49
49
  */
50
50
  async openChatWindowAt(msgId) {
51
51
  await this.pupPage.evaluate(async msgId => {
52
- let msg = await window.Store.Msg.get(msgId);
52
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
53
53
  let chat = await window.Store.Chat.find(msg.id.remote);
54
54
  let searchContext = await window.Store.SearchContext(chat,msg);
55
55
  await window.Store.Cmd.openChatAt(chat, searchContext);
@@ -62,7 +62,7 @@ class InterfaceController {
62
62
  */
63
63
  async openMessageDrawer(msgId) {
64
64
  await this.pupPage.evaluate(async msgId => {
65
- let msg = await window.Store.Msg.get(msgId);
65
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
66
66
  await window.Store.Cmd.msgInfoDrawer(msg);
67
67
  }, msgId);
68
68
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Expose a function to the page if it does not exist
3
+ *
4
+ * NOTE:
5
+ * Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher
6
+ * using page.removeExposedFunction
7
+ * https://pptr.dev/api/puppeteer.page.removeExposedFunction
8
+ *
9
+ * @param {import(puppeteer).Page} page
10
+ * @param {string} name
11
+ * @param {Function} fn
12
+ */
13
+ async function exposeFunctionIfAbsent(page, name, fn) {
14
+ const exist = await page.evaluate((name) => {
15
+ return !!window[name];
16
+ }, name);
17
+ if (exist) {
18
+ return;
19
+ }
20
+ await page.exposeFunction(name, fn);
21
+ }
22
+
23
+ module.exports = {exposeFunctionIfAbsent};