whatsapp-web.js 1.24.0 → 1.26.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 +1 -1
- package/example.js +37 -4
- package/index.d.ts +24 -2
- package/index.js +0 -1
- package/package.json +1 -1
- package/src/Client.js +560 -536
- package/src/authStrategies/LocalAuth.js +4 -1
- package/src/structures/GroupChat.js +12 -4
- package/src/structures/Message.js +27 -27
- package/src/util/Constants.js +1 -1
- package/src/util/Injected/AuthStore/AuthStore.js +17 -0
- package/src/util/Injected/AuthStore/LegacyAuthStore.js +22 -0
- package/src/util/Injected/LegacyStore.js +146 -0
- package/src/util/Injected/Store.js +167 -0
- package/src/util/{Injected.js → Injected/Utils.js} +49 -166
- package/src/util/InterfaceController.js +2 -2
- package/src/webCache/LocalWebCache.js +3 -6
- package/src/authStrategies/LegacySessionAuth.js +0 -72
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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,9 +319,11 @@ class Client extends EventEmitter {
|
|
|
126
319
|
await this.authStrategy.afterBrowserInitialized();
|
|
127
320
|
await this.initWebVersionCache();
|
|
128
321
|
|
|
129
|
-
//
|
|
322
|
+
// ocVersion (isOfficialClient patch)
|
|
323
|
+
// remove after 2.3000.x hard release
|
|
130
324
|
await page.evaluateOnNewDocument(() => {
|
|
131
325
|
const originalError = Error;
|
|
326
|
+
window.originalError = originalError;
|
|
132
327
|
//eslint-disable-next-line no-global-assign
|
|
133
328
|
Error = function (message) {
|
|
134
329
|
const error = new originalError(message);
|
|
@@ -144,549 +339,355 @@ class Client extends EventEmitter {
|
|
|
144
339
|
referer: 'https://whatsapp.com/'
|
|
145
340
|
});
|
|
146
341
|
|
|
147
|
-
await
|
|
148
|
-
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
149
|
-
}`);
|
|
342
|
+
await this.inject();
|
|
150
343
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.
|
|
157
|
-
|
|
158
|
-
lastPercentMessage = message;
|
|
344
|
+
this.pupPage.on('framenavigated', async (frame) => {
|
|
345
|
+
if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
|
|
346
|
+
this.emit(Events.DISCONNECTED, 'LOGOUT');
|
|
347
|
+
await this.authStrategy.logout();
|
|
348
|
+
await this.authStrategy.beforeBrowserInitialized();
|
|
349
|
+
await this.authStrategy.afterBrowserInitialized();
|
|
350
|
+
this.lastLoggedOut = false;
|
|
159
351
|
}
|
|
352
|
+
await this.inject(true);
|
|
160
353
|
});
|
|
354
|
+
}
|
|
161
355
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Request authentication via pairing code instead of QR code
|
|
358
|
+
* @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
|
|
359
|
+
* @param {boolean} showNotification - Show notification to pair on phone number
|
|
360
|
+
* @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
|
|
361
|
+
*/
|
|
362
|
+
async requestPairingCode(phoneNumber, showNotification = true) {
|
|
363
|
+
return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
|
|
364
|
+
window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
|
|
365
|
+
await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
|
|
366
|
+
return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
|
|
367
|
+
}, phoneNumber, showNotification);
|
|
368
|
+
}
|
|
171
369
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Attach event listeners to WA Web
|
|
372
|
+
* Private function
|
|
373
|
+
* @property {boolean} reinject is this a reinject?
|
|
374
|
+
*/
|
|
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
|
+
/**
|
|
382
|
+
* Emitted when a user joins the chat via invite link or is added by an admin.
|
|
383
|
+
* @event Client#group_join
|
|
384
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
385
|
+
*/
|
|
386
|
+
this.emit(Events.GROUP_JOIN, notification);
|
|
387
|
+
} else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
|
|
388
|
+
/**
|
|
389
|
+
* Emitted when a user leaves the chat or is removed by an admin.
|
|
390
|
+
* @event Client#group_leave
|
|
391
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
392
|
+
*/
|
|
393
|
+
this.emit(Events.GROUP_LEAVE, notification);
|
|
394
|
+
} else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
|
|
395
|
+
/**
|
|
396
|
+
* Emitted when a current user is promoted to an admin or demoted to a regular user.
|
|
397
|
+
* @event Client#group_admin_changed
|
|
398
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
399
|
+
*/
|
|
400
|
+
this.emit(Events.GROUP_ADMIN_CHANGED, notification);
|
|
401
|
+
} else if (msg.subtype === 'membership_approval_request') {
|
|
402
|
+
/**
|
|
403
|
+
* Emitted when some user requested to join the group
|
|
404
|
+
* that has the membership approval mode turned on
|
|
405
|
+
* @event Client#group_membership_request
|
|
406
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
407
|
+
* @param {string} notification.chatId The group ID the request was made for
|
|
408
|
+
* @param {string} notification.author The user ID that made a request
|
|
409
|
+
* @param {number} notification.timestamp The timestamp the request was made at
|
|
410
|
+
*/
|
|
411
|
+
this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
|
|
412
|
+
} else {
|
|
413
|
+
/**
|
|
414
|
+
* Emitted when group settings are updated, such as subject, description or picture.
|
|
415
|
+
* @event Client#group_update
|
|
416
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
417
|
+
*/
|
|
418
|
+
this.emit(Events.GROUP_UPDATE, notification);
|
|
177
419
|
}
|
|
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();
|
|
420
|
+
return;
|
|
227
421
|
}
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
422
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
let qrRetries = 0;
|
|
234
|
-
await page.exposeFunction('qrChanged', async (qr) => {
|
|
423
|
+
const message = new Message(this, msg);
|
|
424
|
+
|
|
235
425
|
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.emit(Events.
|
|
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
|
-
});
|
|
426
|
+
* Emitted when a new message is created, which may include the current user's own messages.
|
|
427
|
+
* @event Client#message_create
|
|
428
|
+
* @param {Message} message The message that was created
|
|
429
|
+
*/
|
|
430
|
+
this.emit(Events.MESSAGE_CREATE, message);
|
|
249
431
|
|
|
250
|
-
|
|
251
|
-
const qr_container = document.querySelector(selectors.QR_CONTAINER);
|
|
252
|
-
window.qrChanged(qr_container.dataset.ref);
|
|
432
|
+
if (msg.id.fromMe) return;
|
|
253
433
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
434
|
+
/**
|
|
435
|
+
* Emitted when a new message is received.
|
|
436
|
+
* @event Client#message
|
|
437
|
+
* @param {Message} message The message that was received
|
|
438
|
+
*/
|
|
439
|
+
this.emit(Events.MESSAGE_RECEIVED, message);
|
|
276
440
|
});
|
|
277
441
|
|
|
278
|
-
|
|
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
|
-
}
|
|
295
|
-
|
|
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');
|
|
442
|
+
let last_message;
|
|
309
443
|
|
|
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
|
-
}
|
|
444
|
+
await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
|
|
316
445
|
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
}
|
|
319
452
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
453
|
+
/**
|
|
454
|
+
* Emitted when a message is deleted for everyone in the chat.
|
|
455
|
+
* @event Client#message_revoke_everyone
|
|
456
|
+
* @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
|
|
457
|
+
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
|
|
458
|
+
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
|
|
459
|
+
*/
|
|
460
|
+
this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
|
|
324
461
|
}
|
|
325
462
|
|
|
326
|
-
|
|
327
|
-
rOperand = Number(rOperand.replace(/\./g, ''));
|
|
463
|
+
});
|
|
328
464
|
|
|
329
|
-
|
|
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
|
-
});
|
|
465
|
+
await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
|
|
339
466
|
|
|
340
|
-
|
|
341
|
-
|
|
467
|
+
if (msg.type !== 'revoked') {
|
|
468
|
+
last_message = msg;
|
|
469
|
+
}
|
|
342
470
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
471
|
+
/**
|
|
472
|
+
* The event notification that is received when one of
|
|
473
|
+
* the group participants changes their phone number.
|
|
474
|
+
*/
|
|
475
|
+
const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
|
|
348
476
|
|
|
349
|
-
|
|
350
|
-
|
|
477
|
+
/**
|
|
478
|
+
* The event notification that is received when one of
|
|
479
|
+
* the contacts changes their phone number.
|
|
480
|
+
*/
|
|
481
|
+
const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
|
|
351
482
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
for (let registration of registrations) {
|
|
356
|
-
registration.unregister();
|
|
357
|
-
}
|
|
358
|
-
});
|
|
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);
|
|
359
486
|
|
|
360
|
-
|
|
361
|
-
|
|
487
|
+
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
|
488
|
+
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
|
362
489
|
|
|
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
|
-
/**
|
|
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
490
|
/**
|
|
413
|
-
* Emitted when
|
|
414
|
-
* @event Client#
|
|
415
|
-
* @param {
|
|
491
|
+
* Emitted when a contact or a group participant changes their phone number.
|
|
492
|
+
* @event Client#contact_changed
|
|
493
|
+
* @param {Message} message Message with more information about the event.
|
|
494
|
+
* @param {String} oldId The user's id (an old one) who changed their phone number
|
|
495
|
+
* and who triggered the notification.
|
|
496
|
+
* @param {String} newId The user's new id after the change.
|
|
497
|
+
* @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
|
|
416
498
|
*/
|
|
417
|
-
this.emit(Events.
|
|
499
|
+
this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
|
|
418
500
|
}
|
|
419
|
-
|
|
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
|
-
});
|
|
501
|
+
});
|
|
440
502
|
|
|
441
|
-
|
|
503
|
+
await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
|
|
442
504
|
|
|
443
|
-
|
|
505
|
+
if (!msg.isNewMsg) return;
|
|
444
506
|
|
|
445
|
-
if (msg.type === 'revoked') {
|
|
446
507
|
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
508
|
|
|
452
509
|
/**
|
|
453
|
-
* Emitted when a message is deleted
|
|
454
|
-
* @event Client#
|
|
455
|
-
* @param {Message} message The message that was revoked
|
|
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.
|
|
510
|
+
* Emitted when a message is deleted by the current user.
|
|
511
|
+
* @event Client#message_revoke_me
|
|
512
|
+
* @param {Message} message The message that was revoked
|
|
458
513
|
*/
|
|
459
|
-
this.emit(Events.
|
|
460
|
-
}
|
|
514
|
+
this.emit(Events.MESSAGE_REVOKED_ME, message);
|
|
461
515
|
|
|
462
|
-
|
|
463
|
-
|
|
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';
|
|
516
|
+
});
|
|
475
517
|
|
|
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';
|
|
518
|
+
await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
|
|
481
519
|
|
|
482
|
-
if (isParticipant || isContact) {
|
|
483
|
-
/** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
|
|
484
520
|
const message = new Message(this, msg);
|
|
485
521
|
|
|
486
|
-
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
|
487
|
-
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
|
488
|
-
|
|
489
522
|
/**
|
|
490
|
-
* Emitted when
|
|
491
|
-
* @event Client#
|
|
492
|
-
* @param {Message} message
|
|
493
|
-
* @param {
|
|
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.
|
|
523
|
+
* Emitted when an ack event occurrs on message type.
|
|
524
|
+
* @event Client#message_ack
|
|
525
|
+
* @param {Message} message The message that was affected
|
|
526
|
+
* @param {MessageAck} ack The new ACK value
|
|
497
527
|
*/
|
|
498
|
-
this.emit(Events.
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
await page.exposeFunction('onRemoveMessageEvent', (msg) => {
|
|
503
|
-
|
|
504
|
-
if (!msg.isNewMsg) return;
|
|
505
|
-
|
|
506
|
-
const message = new Message(this, msg);
|
|
507
|
-
|
|
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) => {
|
|
528
|
+
this.emit(Events.MESSAGE_ACK, message, ack);
|
|
518
529
|
|
|
519
|
-
|
|
530
|
+
});
|
|
520
531
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
532
|
+
await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
|
|
533
|
+
const chat = await this.getChatById(data.id);
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Emitted when the chat unread count changes
|
|
537
|
+
*/
|
|
538
|
+
this.emit(Events.UNREAD_COUNT, chat);
|
|
539
|
+
});
|
|
528
540
|
|
|
529
|
-
|
|
541
|
+
await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
|
|
530
542
|
|
|
531
|
-
|
|
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
|
-
});
|
|
543
|
+
const message = new Message(this, msg);
|
|
539
544
|
|
|
540
|
-
|
|
545
|
+
/**
|
|
546
|
+
* Emitted when media has been uploaded for a message sent by the client.
|
|
547
|
+
* @event Client#media_uploaded
|
|
548
|
+
* @param {Message} message The message with media that was uploaded
|
|
549
|
+
*/
|
|
550
|
+
this.emit(Events.MEDIA_UPLOADED, message);
|
|
551
|
+
});
|
|
541
552
|
|
|
542
|
-
|
|
553
|
+
await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
|
|
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);
|
|
543
560
|
|
|
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
|
-
});
|
|
561
|
+
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
|
|
551
562
|
|
|
552
|
-
|
|
563
|
+
if (this.options.takeoverOnConflict) {
|
|
564
|
+
ACCEPTED_STATES.push(WAState.CONFLICT);
|
|
553
565
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
566
|
+
if (state === WAState.CONFLICT) {
|
|
567
|
+
setTimeout(() => {
|
|
568
|
+
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
|
569
|
+
}, this.options.takeoverTimeoutMs);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
560
572
|
|
|
561
|
-
|
|
573
|
+
if (!ACCEPTED_STATES.includes(state)) {
|
|
574
|
+
/**
|
|
575
|
+
* Emitted when the client has been disconnected
|
|
576
|
+
* @event Client#disconnected
|
|
577
|
+
* @param {WAState|"LOGOUT"} reason reason that caused the disconnect
|
|
578
|
+
*/
|
|
579
|
+
await this.authStrategy.disconnect();
|
|
580
|
+
this.emit(Events.DISCONNECTED, state);
|
|
581
|
+
this.destroy();
|
|
582
|
+
}
|
|
583
|
+
});
|
|
562
584
|
|
|
563
|
-
|
|
564
|
-
|
|
585
|
+
await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
|
|
586
|
+
const { battery, plugged } = state;
|
|
565
587
|
|
|
566
|
-
if (
|
|
567
|
-
setTimeout(() => {
|
|
568
|
-
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
|
569
|
-
}, this.options.takeoverTimeoutMs);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
588
|
+
if (battery === undefined) return;
|
|
572
589
|
|
|
573
|
-
if (!ACCEPTED_STATES.includes(state)) {
|
|
574
590
|
/**
|
|
575
|
-
* Emitted when the
|
|
576
|
-
* @event Client#
|
|
577
|
-
* @param {
|
|
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
|
|
578
597
|
*/
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
this.destroy();
|
|
582
|
-
}
|
|
583
|
-
});
|
|
598
|
+
this.emit(Events.BATTERY_CHANGED, { battery, plugged });
|
|
599
|
+
});
|
|
584
600
|
|
|
585
|
-
|
|
586
|
-
|
|
601
|
+
await this.pupPage.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
|
+
});
|
|
587
618
|
|
|
588
|
-
|
|
619
|
+
await this.pupPage.exposeFunction('onReaction', (reactions) => {
|
|
620
|
+
for (const reaction of reactions) {
|
|
621
|
+
/**
|
|
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
|
|
634
|
+
*/
|
|
589
635
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
});
|
|
636
|
+
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
|
637
|
+
}
|
|
638
|
+
});
|
|
600
639
|
|
|
601
|
-
|
|
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
|
-
});
|
|
640
|
+
await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
|
|
641
|
+
const _chat = await this.getChatById(chat.id);
|
|
618
642
|
|
|
619
|
-
await page.exposeFunction('onReaction', (reactions) => {
|
|
620
|
-
for (const reaction of reactions) {
|
|
621
643
|
/**
|
|
622
|
-
* Emitted when a
|
|
623
|
-
* @event Client#
|
|
624
|
-
* @param {
|
|
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
|
|
644
|
+
* Emitted when a chat is removed
|
|
645
|
+
* @event Client#chat_removed
|
|
646
|
+
* @param {Chat} chat
|
|
634
647
|
*/
|
|
635
|
-
|
|
636
|
-
|
|
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);
|
|
648
|
+
this.emit(Events.CHAT_REMOVED, _chat);
|
|
649
|
+
});
|
|
653
650
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
651
|
+
await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
|
|
652
|
+
const _chat = await this.getChatById(chat.id);
|
|
653
|
+
|
|
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
|
+
});
|
|
663
663
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
await page.exposeFunction('onAddMessageCiphertextEvent', msg => {
|
|
664
|
+
await this.pupPage.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
|
+
});
|
|
680
678
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
679
|
+
await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
|
|
680
|
+
|
|
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
|
+
});
|
|
688
|
+
}
|
|
688
689
|
|
|
689
|
-
await
|
|
690
|
+
await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
|
|
690
691
|
const _vote = new PollVote(this, vote);
|
|
691
692
|
/**
|
|
692
693
|
* Emitted when some poll option is selected or deselected,
|
|
@@ -696,7 +697,7 @@ class Client extends EventEmitter {
|
|
|
696
697
|
this.emit(Events.VOTE_UPDATE, _vote);
|
|
697
698
|
});
|
|
698
699
|
|
|
699
|
-
await
|
|
700
|
+
await this.pupPage.evaluate(() => {
|
|
700
701
|
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
|
|
701
702
|
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
|
|
702
703
|
window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
|
|
@@ -720,12 +721,28 @@ class Client extends EventEmitter {
|
|
|
720
721
|
}
|
|
721
722
|
});
|
|
722
723
|
window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
|
|
723
|
-
window.Store.PollVote.on('add', (vote) => {
|
|
724
|
-
const pollVoteModel = window.WWebJS.getPollVoteModel(vote);
|
|
724
|
+
window.Store.PollVote.on('add', async (vote) => {
|
|
725
|
+
const pollVoteModel = await window.WWebJS.getPollVoteModel(vote);
|
|
725
726
|
pollVoteModel && window.onPollVoteEvent(pollVoteModel);
|
|
726
727
|
});
|
|
727
728
|
|
|
728
|
-
{
|
|
729
|
+
if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
|
|
730
|
+
const module = window.Store.AddonReactionTable;
|
|
731
|
+
const ogMethod = module.bulkUpsert;
|
|
732
|
+
module.bulkUpsert = ((...args) => {
|
|
733
|
+
window.onReaction(args[0].map(reaction => {
|
|
734
|
+
const msgKey = reaction.id;
|
|
735
|
+
const parentMsgKey = reaction.reactionParentKey;
|
|
736
|
+
const timestamp = reaction.reactionTimestamp / 1000;
|
|
737
|
+
const sender = reaction.author ?? reaction.from;
|
|
738
|
+
const senderUserJid = sender._serialized;
|
|
739
|
+
|
|
740
|
+
return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
|
|
741
|
+
}));
|
|
742
|
+
|
|
743
|
+
return ogMethod(...args);
|
|
744
|
+
}).bind(module);
|
|
745
|
+
} else {
|
|
729
746
|
const module = window.Store.createOrUpdateReactionsModule;
|
|
730
747
|
const ogMethod = module.createOrUpdateReactions;
|
|
731
748
|
module.createOrUpdateReactions = ((...args) => {
|
|
@@ -741,24 +758,7 @@ class Client extends EventEmitter {
|
|
|
741
758
|
}).bind(module);
|
|
742
759
|
}
|
|
743
760
|
});
|
|
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
|
-
}
|
|
761
|
+
}
|
|
762
762
|
|
|
763
763
|
async initWebVersionCache() {
|
|
764
764
|
const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
|
|
@@ -783,7 +783,8 @@ class Client extends EventEmitter {
|
|
|
783
783
|
} else {
|
|
784
784
|
this.pupPage.on('response', async (res) => {
|
|
785
785
|
if(res.ok() && res.url() === WhatsWebURL) {
|
|
786
|
-
|
|
786
|
+
const indexHtml = await res.text();
|
|
787
|
+
this.currentIndexHtml = indexHtml;
|
|
787
788
|
}
|
|
788
789
|
});
|
|
789
790
|
}
|
|
@@ -802,7 +803,9 @@ class Client extends EventEmitter {
|
|
|
802
803
|
*/
|
|
803
804
|
async logout() {
|
|
804
805
|
await this.pupPage.evaluate(() => {
|
|
805
|
-
|
|
806
|
+
if (window.Store && window.Store.AppState && typeof window.Store.AppState.logout === 'function') {
|
|
807
|
+
return window.Store.AppState.logout();
|
|
808
|
+
}
|
|
806
809
|
});
|
|
807
810
|
await this.pupBrowser.close();
|
|
808
811
|
|
|
@@ -861,6 +864,7 @@ class Client extends EventEmitter {
|
|
|
861
864
|
* @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions
|
|
862
865
|
* @property {string[]} [mentions] - User IDs to mention in the message
|
|
863
866
|
* @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
|
|
867
|
+
* @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI
|
|
864
868
|
* @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
|
|
865
869
|
* @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
|
|
866
870
|
* @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
|
|
@@ -894,9 +898,10 @@ class Client extends EventEmitter {
|
|
|
894
898
|
sendMediaAsDocument: options.sendMediaAsDocument,
|
|
895
899
|
caption: options.caption,
|
|
896
900
|
quotedMessageId: options.quotedMessageId,
|
|
897
|
-
parseVCards: options.parseVCards
|
|
901
|
+
parseVCards: options.parseVCards !== false,
|
|
898
902
|
mentionedJidList: options.mentions || [],
|
|
899
903
|
groupMentions: options.groupMentions,
|
|
904
|
+
invokedBotWid: options.invokedBotWid,
|
|
900
905
|
extraOptions: options.extra
|
|
901
906
|
};
|
|
902
907
|
|
|
@@ -1032,7 +1037,7 @@ class Client extends EventEmitter {
|
|
|
1032
1037
|
if(msg) return window.WWebJS.getMessageModel(msg);
|
|
1033
1038
|
|
|
1034
1039
|
const params = messageId.split('_');
|
|
1035
|
-
if(params.length !== 3) throw new Error('Invalid serialized message id specified');
|
|
1040
|
+
if (params.length !== 3 && params.length !== 4) throw new Error('Invalid serialized message id specified');
|
|
1036
1041
|
|
|
1037
1042
|
let messagesObject = await window.Store.Msg.getMessagesById([messageId]);
|
|
1038
1043
|
if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0];
|
|
@@ -1102,14 +1107,8 @@ class Client extends EventEmitter {
|
|
|
1102
1107
|
async setDisplayName(displayName) {
|
|
1103
1108
|
const couldSet = await this.pupPage.evaluate(async displayName => {
|
|
1104
1109
|
if(!window.Store.Conn.canSetMyPushname()) return false;
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
-
}
|
|
1110
|
+
await window.Store.Settings.setPushname(displayName);
|
|
1111
|
+
return true;
|
|
1113
1112
|
}, displayName);
|
|
1114
1113
|
|
|
1115
1114
|
return couldSet;
|
|
@@ -1250,7 +1249,9 @@ class Client extends EventEmitter {
|
|
|
1250
1249
|
const profilePic = await this.pupPage.evaluate(async contactId => {
|
|
1251
1250
|
try {
|
|
1252
1251
|
const chatWid = window.Store.WidFactory.createWid(contactId);
|
|
1253
|
-
return
|
|
1252
|
+
return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
|
|
1253
|
+
? await window.Store.ProfilePic.profilePicFind(chatWid)
|
|
1254
|
+
: await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
|
|
1254
1255
|
} catch (err) {
|
|
1255
1256
|
if(err.name === 'ServerStatusCodeError') return undefined;
|
|
1256
1257
|
throw err;
|
|
@@ -1416,10 +1417,18 @@ class Client extends EventEmitter {
|
|
|
1416
1417
|
|
|
1417
1418
|
try {
|
|
1418
1419
|
createGroupResult = await window.Store.GroupUtils.createGroup(
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1420
|
+
{
|
|
1421
|
+
'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
|
|
1422
|
+
'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
|
|
1423
|
+
'announce': options.announce === undefined ? true : options.announce,
|
|
1424
|
+
'ephemeralDuration': messageTimer,
|
|
1425
|
+
'full': undefined,
|
|
1426
|
+
'parentGroupId': parentGroupWid,
|
|
1427
|
+
'restrict': options.restrict === undefined ? true : options.restrict,
|
|
1428
|
+
'thumb': undefined,
|
|
1429
|
+
'title': title,
|
|
1430
|
+
},
|
|
1431
|
+
participantWids
|
|
1423
1432
|
);
|
|
1424
1433
|
} catch (err) {
|
|
1425
1434
|
return 'CreateGroupError: An unknown error occupied while creating a group';
|
|
@@ -1431,7 +1440,7 @@ class Client extends EventEmitter {
|
|
|
1431
1440
|
const statusCode = participant.error ?? 200;
|
|
1432
1441
|
|
|
1433
1442
|
if (autoSendInviteV4 && statusCode === 403) {
|
|
1434
|
-
window.Store.
|
|
1443
|
+
window.Store.Contact.gadd(participant.wid, { silent: true });
|
|
1435
1444
|
const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
|
|
1436
1445
|
await window.Store.Chat.find(participant.wid),
|
|
1437
1446
|
createGroupResult.wid._serialized,
|
|
@@ -1608,8 +1617,8 @@ class Client extends EventEmitter {
|
|
|
1608
1617
|
* @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests
|
|
1609
1618
|
*/
|
|
1610
1619
|
async getGroupMembershipRequests(groupId) {
|
|
1611
|
-
return await this.pupPage.evaluate(async (
|
|
1612
|
-
const groupWid = window.Store.WidFactory.createWid(
|
|
1620
|
+
return await this.pupPage.evaluate(async (groupId) => {
|
|
1621
|
+
const groupWid = window.Store.WidFactory.createWid(groupId);
|
|
1613
1622
|
return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid);
|
|
1614
1623
|
}, groupId);
|
|
1615
1624
|
}
|
|
@@ -1715,6 +1724,21 @@ class Client extends EventEmitter {
|
|
|
1715
1724
|
return flag;
|
|
1716
1725
|
}, flag);
|
|
1717
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Get user device count by ID
|
|
1730
|
+
* Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
|
|
1731
|
+
* So for a non-enterprise user with one WaWeb connection it should return "2"
|
|
1732
|
+
* @param {string} contactId
|
|
1733
|
+
* @returns {number}
|
|
1734
|
+
*/
|
|
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;
|
|
1741
|
+
}
|
|
1718
1742
|
}
|
|
1719
1743
|
|
|
1720
|
-
module.exports = Client;
|
|
1744
|
+
module.exports = Client;
|