whatsapp-web.js 1.26.0 → 1.26.1-alpha.1

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/example.js CHANGED
@@ -450,7 +450,7 @@ 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'){
453
+ } else if (msg.body === '!howManyConnections') {
454
454
  /**
455
455
  * Get user device count by ID
456
456
  * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
@@ -458,6 +458,13 @@ client.on('message', async msg => {
458
458
  */
459
459
  let deviceCount = await client.getContactDeviceCount(msg.from);
460
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.');
461
468
  }
462
469
  });
463
470
 
package/index.d.ts CHANGED
@@ -183,7 +183,10 @@ declare namespace WAWebJS {
183
183
  * So for a non-enterprise user with one WaWeb connection it should return "2"
184
184
  * @param {string} contactId
185
185
  */
186
- getContactDeviceCount(contactId: string): Promise<Number>
186
+ getContactDeviceCount(userId: string): Promise<number>
187
+
188
+ /** Sync history conversation of the Chat */
189
+ syncHistory(chatId: string): Promise<boolean>
187
190
 
188
191
  /** Changes and returns the archive state of the Chat */
189
192
  unarchiveChat(chatId: string): Promise<boolean>
@@ -1440,6 +1443,8 @@ declare namespace WAWebJS {
1440
1443
  getLabels: () => Promise<Label[]>,
1441
1444
  /** Add or remove labels to this Chat */
1442
1445
  changeLabels: (labelIds: Array<string | number>) => Promise<void>
1446
+ /** Sync history conversation of the Chat */
1447
+ syncHistory: () => Promise<boolean>
1443
1448
  }
1444
1449
 
1445
1450
  export interface MessageSearchOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.26.0",
3
+ "version": "1.26.1-alpha.1",
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(); });
@@ -349,7 +336,7 @@ class Client extends EventEmitter {
349
336
  await this.authStrategy.afterBrowserInitialized();
350
337
  this.lastLoggedOut = false;
351
338
  }
352
- await this.inject(true);
339
+ await this.inject();
353
340
  });
354
341
  }
355
342
 
@@ -372,34 +359,33 @@ class Client extends EventEmitter {
372
359
  * Private function
373
360
  * @property {boolean} reinject is this a reinject?
374
361
  */
375
- async attachEventListeners(reinject = false) {
376
- if (!reinject) {
377
- await this.pupPage.exposeFunction('onAddMessageEvent', msg => {
378
- if (msg.type === 'gp2') {
379
- const notification = new GroupNotification(this, msg);
380
- if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
381
- /**
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
+ /**
382
368
  * Emitted when a user joins the chat via invite link or is added by an admin.
383
369
  * @event Client#group_join
384
370
  * @param {GroupNotification} notification GroupNotification with more information about the action
385
371
  */
386
- this.emit(Events.GROUP_JOIN, notification);
387
- } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
388
- /**
372
+ this.emit(Events.GROUP_JOIN, notification);
373
+ } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
374
+ /**
389
375
  * Emitted when a user leaves the chat or is removed by an admin.
390
376
  * @event Client#group_leave
391
377
  * @param {GroupNotification} notification GroupNotification with more information about the action
392
378
  */
393
- this.emit(Events.GROUP_LEAVE, notification);
394
- } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
395
- /**
379
+ this.emit(Events.GROUP_LEAVE, notification);
380
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
381
+ /**
396
382
  * Emitted when a current user is promoted to an admin or demoted to a regular user.
397
383
  * @event Client#group_admin_changed
398
384
  * @param {GroupNotification} notification GroupNotification with more information about the action
399
385
  */
400
- this.emit(Events.GROUP_ADMIN_CHANGED, notification);
401
- } else if (msg.subtype === 'membership_approval_request') {
402
- /**
386
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
387
+ } else if (msg.subtype === 'membership_approval_request') {
388
+ /**
403
389
  * Emitted when some user requested to join the group
404
390
  * that has the membership approval mode turned on
405
391
  * @event Client#group_membership_request
@@ -408,86 +394,86 @@ class Client extends EventEmitter {
408
394
  * @param {string} notification.author The user ID that made a request
409
395
  * @param {number} notification.timestamp The timestamp the request was made at
410
396
  */
411
- this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
412
- } else {
413
- /**
397
+ this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
398
+ } else {
399
+ /**
414
400
  * Emitted when group settings are updated, such as subject, description or picture.
415
401
  * @event Client#group_update
416
402
  * @param {GroupNotification} notification GroupNotification with more information about the action
417
403
  */
418
- this.emit(Events.GROUP_UPDATE, notification);
419
- }
420
- return;
404
+ this.emit(Events.GROUP_UPDATE, notification);
421
405
  }
406
+ return;
407
+ }
422
408
 
423
- const message = new Message(this, msg);
409
+ const message = new Message(this, msg);
424
410
 
425
- /**
411
+ /**
426
412
  * Emitted when a new message is created, which may include the current user's own messages.
427
413
  * @event Client#message_create
428
414
  * @param {Message} message The message that was created
429
415
  */
430
- this.emit(Events.MESSAGE_CREATE, message);
416
+ this.emit(Events.MESSAGE_CREATE, message);
431
417
 
432
- if (msg.id.fromMe) return;
418
+ if (msg.id.fromMe) return;
433
419
 
434
- /**
420
+ /**
435
421
  * Emitted when a new message is received.
436
422
  * @event Client#message
437
423
  * @param {Message} message The message that was received
438
424
  */
439
- this.emit(Events.MESSAGE_RECEIVED, message);
440
- });
425
+ this.emit(Events.MESSAGE_RECEIVED, message);
426
+ });
441
427
 
442
- let last_message;
428
+ let last_message;
443
429
 
444
- await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
430
+ await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => {
445
431
 
446
- if (msg.type === 'revoked') {
447
- const message = new Message(this, msg);
448
- let revoked_msg;
449
- if (last_message && msg.id.id === last_message.id.id) {
450
- revoked_msg = new Message(this, last_message);
451
- }
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
+ }
452
438
 
453
- /**
439
+ /**
454
440
  * Emitted when a message is deleted for everyone in the chat.
455
441
  * @event Client#message_revoke_everyone
456
442
  * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
457
443
  * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
458
444
  * Note that due to the way this data is captured, it may be possible that this param will be undefined.
459
445
  */
460
- this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
461
- }
446
+ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
447
+ }
462
448
 
463
- });
449
+ });
464
450
 
465
- await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
451
+ await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => {
466
452
 
467
- if (msg.type !== 'revoked') {
468
- last_message = msg;
469
- }
453
+ if (msg.type !== 'revoked') {
454
+ last_message = msg;
455
+ }
470
456
 
471
- /**
457
+ /**
472
458
  * The event notification that is received when one of
473
459
  * the group participants changes their phone number.
474
460
  */
475
- const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
461
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
476
462
 
477
- /**
463
+ /**
478
464
  * The event notification that is received when one of
479
465
  * the contacts changes their phone number.
480
466
  */
481
- const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
467
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
482
468
 
483
- if (isParticipant || isContact) {
484
- /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
485
- 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);
486
472
 
487
- const newId = isParticipant ? msg.recipients[0] : msg.to;
488
- 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);
489
475
 
490
- /**
476
+ /**
491
477
  * Emitted when a contact or a group participant changes their phone number.
492
478
  * @event Client#contact_changed
493
479
  * @param {Message} message Message with more information about the event.
@@ -496,98 +482,98 @@ class Client extends EventEmitter {
496
482
  * @param {String} newId The user's new id after the change.
497
483
  * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
498
484
  */
499
- this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
500
- }
501
- });
485
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
486
+ }
487
+ });
502
488
 
503
- await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
489
+ await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => {
504
490
 
505
- if (!msg.isNewMsg) return;
491
+ if (!msg.isNewMsg) return;
506
492
 
507
- const message = new Message(this, msg);
493
+ const message = new Message(this, msg);
508
494
 
509
- /**
495
+ /**
510
496
  * Emitted when a message is deleted by the current user.
511
497
  * @event Client#message_revoke_me
512
498
  * @param {Message} message The message that was revoked
513
499
  */
514
- this.emit(Events.MESSAGE_REVOKED_ME, message);
500
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
515
501
 
516
- });
502
+ });
517
503
 
518
- await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
504
+ await exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => {
519
505
 
520
- const message = new Message(this, msg);
506
+ const message = new Message(this, msg);
521
507
 
522
- /**
508
+ /**
523
509
  * Emitted when an ack event occurrs on message type.
524
510
  * @event Client#message_ack
525
511
  * @param {Message} message The message that was affected
526
512
  * @param {MessageAck} ack The new ACK value
527
513
  */
528
- this.emit(Events.MESSAGE_ACK, message, ack);
514
+ this.emit(Events.MESSAGE_ACK, message, ack);
529
515
 
530
- });
516
+ });
531
517
 
532
- await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
533
- const chat = await this.getChatById(data.id);
518
+ await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{
519
+ const chat = await this.getChatById(data.id);
534
520
 
535
- /**
521
+ /**
536
522
  * Emitted when the chat unread count changes
537
523
  */
538
- this.emit(Events.UNREAD_COUNT, chat);
539
- });
524
+ this.emit(Events.UNREAD_COUNT, chat);
525
+ });
540
526
 
541
- await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
527
+ await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => {
542
528
 
543
- const message = new Message(this, msg);
529
+ const message = new Message(this, msg);
544
530
 
545
- /**
531
+ /**
546
532
  * Emitted when media has been uploaded for a message sent by the client.
547
533
  * @event Client#media_uploaded
548
534
  * @param {Message} message The message with media that was uploaded
549
535
  */
550
- this.emit(Events.MEDIA_UPLOADED, message);
551
- });
536
+ this.emit(Events.MEDIA_UPLOADED, message);
537
+ });
552
538
 
553
- await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
554
- /**
539
+ await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => {
540
+ /**
555
541
  * Emitted when the connection state changes
556
542
  * @event Client#change_state
557
543
  * @param {WAState} state the new connection state
558
544
  */
559
- this.emit(Events.STATE_CHANGED, state);
545
+ this.emit(Events.STATE_CHANGED, state);
560
546
 
561
- const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
547
+ const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
562
548
 
563
- if (this.options.takeoverOnConflict) {
564
- ACCEPTED_STATES.push(WAState.CONFLICT);
549
+ if (this.options.takeoverOnConflict) {
550
+ ACCEPTED_STATES.push(WAState.CONFLICT);
565
551
 
566
- if (state === WAState.CONFLICT) {
567
- setTimeout(() => {
568
- this.pupPage.evaluate(() => window.Store.AppState.takeover());
569
- }, this.options.takeoverTimeoutMs);
570
- }
552
+ if (state === WAState.CONFLICT) {
553
+ setTimeout(() => {
554
+ this.pupPage.evaluate(() => window.Store.AppState.takeover());
555
+ }, this.options.takeoverTimeoutMs);
571
556
  }
557
+ }
572
558
 
573
- if (!ACCEPTED_STATES.includes(state)) {
574
- /**
559
+ if (!ACCEPTED_STATES.includes(state)) {
560
+ /**
575
561
  * Emitted when the client has been disconnected
576
562
  * @event Client#disconnected
577
563
  * @param {WAState|"LOGOUT"} reason reason that caused the disconnect
578
564
  */
579
- await this.authStrategy.disconnect();
580
- this.emit(Events.DISCONNECTED, state);
581
- this.destroy();
582
- }
583
- });
565
+ await this.authStrategy.disconnect();
566
+ this.emit(Events.DISCONNECTED, state);
567
+ this.destroy();
568
+ }
569
+ });
584
570
 
585
- await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
586
- const { battery, plugged } = state;
571
+ await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => {
572
+ const { battery, plugged } = state;
587
573
 
588
- if (battery === undefined) return;
574
+ if (battery === undefined) return;
589
575
 
590
- /**
576
+ /**
591
577
  * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
592
578
  * @event Client#change_battery
593
579
  * @param {object} batteryInfo
@@ -595,11 +581,11 @@ class Client extends EventEmitter {
595
581
  * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
596
582
  * @deprecated
597
583
  */
598
- this.emit(Events.BATTERY_CHANGED, { battery, plugged });
599
- });
584
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
585
+ });
600
586
 
601
- await this.pupPage.exposeFunction('onIncomingCall', (call) => {
602
- /**
587
+ await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => {
588
+ /**
603
589
  * Emitted when a call is received
604
590
  * @event Client#incoming_call
605
591
  * @param {object} call
@@ -612,13 +598,13 @@ class Client extends EventEmitter {
612
598
  * @param {boolean} call.webClientShouldHandle - If Waweb should handle
613
599
  * @param {object} call.participants - Participants
614
600
  */
615
- const cll = new Call(this, call);
616
- this.emit(Events.INCOMING_CALL, cll);
617
- });
601
+ const cll = new Call(this, call);
602
+ this.emit(Events.INCOMING_CALL, cll);
603
+ });
618
604
 
619
- await this.pupPage.exposeFunction('onReaction', (reactions) => {
620
- for (const reaction of reactions) {
621
- /**
605
+ await exposeFunctionIfAbsent(this.pupPage, 'onReaction', (reactions) => {
606
+ for (const reaction of reactions) {
607
+ /**
622
608
  * Emitted when a reaction is sent, received, updated or removed
623
609
  * @event Client#message_reaction
624
610
  * @param {object} reaction
@@ -633,61 +619,60 @@ class Client extends EventEmitter {
633
619
  * @param {?number} reaction.ack - Ack
634
620
  */
635
621
 
636
- this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
637
- }
638
- });
622
+ this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
623
+ }
624
+ });
639
625
 
640
- await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
641
- const _chat = await this.getChatById(chat.id);
626
+ await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => {
627
+ const _chat = await this.getChatById(chat.id);
642
628
 
643
- /**
629
+ /**
644
630
  * Emitted when a chat is removed
645
631
  * @event Client#chat_removed
646
632
  * @param {Chat} chat
647
633
  */
648
- this.emit(Events.CHAT_REMOVED, _chat);
649
- });
634
+ this.emit(Events.CHAT_REMOVED, _chat);
635
+ });
650
636
 
651
- await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
652
- 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);
653
639
 
654
- /**
640
+ /**
655
641
  * Emitted when a chat is archived/unarchived
656
642
  * @event Client#chat_archived
657
643
  * @param {Chat} chat
658
644
  * @param {boolean} currState
659
645
  * @param {boolean} prevState
660
646
  */
661
- this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
662
- });
647
+ this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
648
+ });
663
649
 
664
- await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
650
+ await exposeFunctionIfAbsent(this.pupPage, 'onEditMessageEvent', (msg, newBody, prevBody) => {
665
651
 
666
- if(msg.type === 'revoked'){
667
- return;
668
- }
669
- /**
652
+ if(msg.type === 'revoked'){
653
+ return;
654
+ }
655
+ /**
670
656
  * Emitted when messages are edited
671
657
  * @event Client#message_edit
672
658
  * @param {Message} message
673
659
  * @param {string} newBody
674
660
  * @param {string} prevBody
675
661
  */
676
- this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
677
- });
662
+ this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
663
+ });
678
664
 
679
- await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
665
+ await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => {
680
666
 
681
- /**
667
+ /**
682
668
  * Emitted when messages are edited
683
669
  * @event Client#message_ciphertext
684
670
  * @param {Message} message
685
671
  */
686
- this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
687
- });
688
- }
672
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
673
+ });
689
674
 
690
- await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
675
+ await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (vote) => {
691
676
  const _vote = new PollVote(this, vote);
692
677
  /**
693
678
  * Emitted when some poll option is selected or deselected,
@@ -1729,16 +1714,36 @@ class Client extends EventEmitter {
1729
1714
  * Get user device count by ID
1730
1715
  * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
1731
1716
  * So for a non-enterprise user with one WaWeb connection it should return "2"
1732
- * @param {string} contactId
1733
- * @returns {number}
1717
+ * @param {string} userId
1718
+ * @returns {Promise<number>}
1734
1719
  */
1735
- async getContactDeviceCount(contactId) {
1736
- let devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(contactId)]);
1737
- if(devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object'){
1738
- return devices[0].devices.length;
1739
- }
1740
- return 0;
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);
1741
1746
  }
1742
1747
  }
1743
1748
 
1744
- module.exports = Client;
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;
@@ -98,6 +98,7 @@ exports.ExposeStore = () => {
98
98
  window.Store.BotSecret = window.require('WAWebBotMessageSecret');
99
99
  window.Store.BotProfiles = window.require('WAWebBotProfileCollection');
100
100
  window.Store.DeviceList = window.require('WAWebApiDeviceList');
101
+ window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
101
102
  if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620'))
102
103
  window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
103
104
 
@@ -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(chatWid) || await window.Store.Chat.find(chatWid);
20
+ await window.Store.Cmd.openChatBottom(chat);
21
21
  }, chatId);
22
22
  }
23
23
 
@@ -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};