'use strict';
+
+/* Require Optional Dependencies */
+try {
+ var fs = require('fs-extra');
+ var unzipper = require('unzipper');
+ var archiver = require('archiver');
+} catch {
+ fs = undefined;
+ unzipper = undefined;
+ archiver = undefined;
+}
+
+const path = require('path');
+const { Events } = require('./../util/Constants');
+const BaseAuthStrategy = require('./BaseAuthStrategy');
+
+/**
+ * Remote-based authentication
+ * @param {object} options - options
+ * @param {object} options.store - Remote database store instance
+ * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
+ * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
+ * @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}
+ */
+class RemoteAuth extends BaseAuthStrategy {
+ constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) {
+ if (!fs && !unzipper && !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag');
+ super();
+
+ const idRegex = /^[-_\w]+$/i;
+ if (clientId && !idRegex.test(clientId)) {
+ throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
+ }
+ if (!backupSyncIntervalMs || backupSyncIntervalMs < 60000) {
+ throw new Error('Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.');
+ }
+ if(!store) throw new Error('Remote database store is required.');
+
+ this.store = store;
+ this.clientId = clientId;
+ this.backupSyncIntervalMs = backupSyncIntervalMs;
+ this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
+ this.tempDir = `${this.dataPath}/wwebjs_temp_session`;
+ this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */
+ }
+
+ async beforeBrowserInitialized() {
+ const puppeteerOpts = this.client.options.puppeteer;
+ const sessionDirName = this.clientId ? `RemoteAuth-${this.clientId}` : 'RemoteAuth';
+ const dirPath = path.join(this.dataPath, sessionDirName);
+
+ if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) {
+ throw new Error('RemoteAuth is not compatible with a user-supplied userDataDir.');
+ }
+
+ this.userDataDir = dirPath;
+ this.sessionName = sessionDirName;
+
+ await this.extractRemoteSession();
+
+ this.client.options.puppeteer = {
+ ...puppeteerOpts,
+ userDataDir: dirPath
+ };
+ }
+
+ async logout() {
+ await this.disconnect();
+ }
+
+ async destroy() {
+ clearInterval(this.backupSync);
+ }
+
+ async disconnect() {
+ await this.deleteRemoteSession();
+
+ let pathExists = await this.isValidPath(this.userDataDir);
+ if (pathExists) {
+ await fs.promises.rm(this.userDataDir, {
+ recursive: true,
+ force: true
+ }).catch(() => {});
+ }
+ clearInterval(this.backupSync);
+ }
+
+ async afterAuthReady() {
+ const sessionExists = await this.store.sessionExists({session: this.sessionName});
+ if(!sessionExists) {
+ await this.delay(60000); /* Initial delay sync required for session to be stable enough to recover */
+ await this.storeRemoteSession({emit: true});
+ }
+ var self = this;
+ this.backupSync = setInterval(async function () {
+ await self.storeRemoteSession();
+ }, this.backupSyncIntervalMs);
+ }
+
+ async storeRemoteSession(options) {
+ /* Compress & Store Session */
+ const pathExists = await this.isValidPath(this.userDataDir);
+ if (pathExists) {
+ await this.compressSession();
+ await this.store.save({session: this.sessionName});
+ await fs.promises.unlink(`${this.sessionName}.zip`);
+ await fs.promises.rm(`${this.tempDir}`, {
+ recursive: true,
+ force: true
+ }).catch(() => {});
+ if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED);
+ }
+ }
+
+ async extractRemoteSession() {
+ const pathExists = await this.isValidPath(this.userDataDir);
+ const compressedSessionPath = `${this.sessionName}.zip`;
+ const sessionExists = await this.store.sessionExists({session: this.sessionName});
+ if (pathExists) {
+ await fs.promises.rm(this.userDataDir, {
+ recursive: true,
+ force: true
+ }).catch(() => {});
+ }
+ if (sessionExists) {
+ await this.store.extract({session: this.sessionName, path: compressedSessionPath});
+ await this.unCompressSession(compressedSessionPath);
+ } else {
+ fs.mkdirSync(this.userDataDir, { recursive: true });
+ }
+ }
+
+ async deleteRemoteSession() {
+ const sessionExists = await this.store.sessionExists({session: this.sessionName});
+ if (sessionExists) await this.store.delete({session: this.sessionName});
+ }
+
+ async compressSession() {
+ const archive = archiver('zip');
+ const stream = fs.createWriteStream(`${this.sessionName}.zip`);
+
+ await fs.copy(this.userDataDir, this.tempDir).catch(() => {});
+ await this.deleteMetadata();
+ return new Promise((resolve, reject) => {
+ archive
+ .directory(this.tempDir, false)
+ .on('error', err => reject(err))
+ .pipe(stream);
+
+ stream.on('close', () => resolve());
+ archive.finalize();
+ });
+ }
+
+ async unCompressSession(compressedSessionPath) {
+ var stream = fs.createReadStream(compressedSessionPath);
+ await new Promise((resolve, reject) => {
+ stream.pipe(unzipper.Extract({
+ path: this.userDataDir
+ }))
+ .on('error', err => reject(err))
+ .on('finish', () => resolve());
+ });
+ await fs.promises.unlink(compressedSessionPath);
+ }
+
+ async deleteMetadata() {
+ const sessionDirs = [this.tempDir, path.join(this.tempDir, 'Default')];
+ for (const dir of sessionDirs) {
+ const sessionFiles = await fs.promises.readdir(dir);
+ for (const element of sessionFiles) {
+ if (!this.requiredDirs.includes(element)) {
+ const dirElement = path.join(dir, element);
+ const stats = await fs.promises.lstat(dirElement);
+
+ if (stats.isDirectory()) {
+ await fs.promises.rm(dirElement, {
+ recursive: true,
+ force: true
+ }).catch(() => {});
+ } else {
+ await fs.promises.unlink(dirElement).catch(() => {});
+ }
+ }
+ }
+ }
+ }
+
+ async isValidPath(path) {
+ try {
+ await fs.promises.access(path);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ async delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+}
+
+module.exports = RemoteAuth;
+
+ Reaction
+Source: structures/
Represents a Reaction on WhatsApp
+-
+
new Reaction()
+-
+
- Extends +
- Base +
Properties
+ack + nullable number
+ACK
+-
+
id + object
+Reaction ID
+-
+
msgId + object
+Message ID
+-
+
orphan + number
+Orphan
+-
+
orphanReason + nullable string
+Orphan reason
+-
+
reaction + string
+Reaction
+-
+
read + boolean
+Read
+-
+
senderId + string
+Sender ID
+-
+
timestamp + number
+Unix timestamp for when the reaction was created
+-
+