Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 4 additions & 25 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,6 @@ declare namespace WAWebJS {

/** Emitted when authentication is successful */
on(event: 'authenticated', listener: (
/**
* Object containing session information, when using LegacySessionAuth. Can be used to restore the session
*/
session?: ClientSession
) => void): this

/**
Expand Down Expand Up @@ -567,23 +563,15 @@ declare namespace WAWebJS {
evalOnNewDoc?: Function,
/** Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ */
puppeteer?: puppeteer.PuppeteerNodeLaunchOptions & puppeteer.ConnectOptions
/** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */
/** Determines how to save and restore sessions. Otherwise, NoAuth will be used. */
authStrategy?: AuthStrategy,
/** The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved. */
webVersion?: string,
/** Determines how to retrieve the WhatsApp Web version specified in options.webVersion. */
webVersionCache?: WebCacheOptions,
/** How many times should the qrcode be refreshed before giving up
* @default 0 (disabled) */
qrMaxRetries?: number,
/**
* @deprecated This option should be set directly on the LegacySessionAuth
*/
restartOnAuthFail?: boolean
/**
* @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
*/
session?: ClientSession
qrMaxRetries?: number
/** If another whatsapp web session is detected (another browser), take over the session in the current browser
* @default false */
takeoverOnConflict?: boolean,
Expand Down Expand Up @@ -700,17 +688,6 @@ declare namespace WAWebJS {
extract: (options: { session: string, path: string }) => Promise<any> | any,
}

/**
* Legacy session auth strategy
* Not compatible with multi-device accounts.
*/
export class LegacySessionAuth extends AuthStrategy {
constructor(options?: {
session?: ClientSession,
restartOnAuthFail?: boolean,
})
}

/**
* Represents a WhatsApp client session
*/
Expand Down Expand Up @@ -1717,6 +1694,8 @@ declare namespace WAWebJS {
lastMessage: Message,
/** Indicates if the Chat is pinned */
pinned: boolean,
/** Indicates if the Chat is locked */
isLocked: boolean,

/** Archives this chat */
archive: () => Promise<void>,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
},
"homepage": "https://wwebjs.dev/",
"dependencies": {
"@pedroslopez/moduleraid": "^5.0.2",
"fluent-ffmpeg": "2.1.3",
"mime": "^3.0.0",
"node-fetch": "^2.6.9",
Expand Down
197 changes: 69 additions & 128 deletions src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@

const EventEmitter = require('events');
const puppeteer = require('puppeteer');
const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');

const Util = require('./util/Util');
const InterfaceController = require('./util/InterfaceController');
const { WhatsWebURL, DefaultOptions, Events, WAState, MessageTypes } = require('./util/Constants');
const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
const { ExposeStore } = require('./util/Injected/Store');
const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore');
const { ExposeLegacyStore } = require('./util/Injected/LegacyStore');
const { LoadUtils } = require('./util/Injected/Utils');
const ChatFactory = require('./factories/ChatFactory');
const ContactFactory = require('./factories/ContactFactory');
Expand All @@ -23,15 +20,13 @@ const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
* Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter}
* @param {object} options - Client options
* @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
* @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Otherwise, NoAuth will be used.
* @param {string} options.webVersion - The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved.
* @param {object} options.webVersionCache - Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found.
* @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
* @param {function} options.evalOnNewDoc - function to eval on new doc
* @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
* @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
* @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth.
* @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
* @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
* @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
* @param {string} options.userAgent - User agent to use in puppeteer
Expand Down Expand Up @@ -96,30 +91,16 @@ class Client extends EventEmitter {
* Private function
*/
async inject() {
if(this.options.authTimeoutMs === undefined || this.options.authTimeoutMs==0){
this.options.authTimeoutMs = 30000;
}
let start = Date.now();
let timeout = this.options.authTimeoutMs;
let res = false;
while(start > (Date.now() - timeout)){
res = await this.pupPage.evaluate('window.Debug?.VERSION != undefined');
if(res){break;}
await new Promise(r => setTimeout(r, 200));
}
if(!res){
throw 'auth timeout';
}
const authTimeout = this.options.authTimeoutMs || 30000;
await this.pupPage.waitForFunction(
'window.Debug?.VERSION != undefined',
{ timeout: authTimeout }
).catch(() => { throw 'auth timeout'; });
await this.setDeviceName(this.options.deviceName, this.options.browserName);
const pairWithPhoneNumber = this.options.pairWithPhoneNumber;
const version = await this.getWWebVersion();
const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;

if (isCometOrAbove) {
await this.pupPage.evaluate(ExposeAuthStore);
} else {
await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString());
}
await this.pupPage.evaluate(ExposeAuthStore);

const needAuthentication = await this.pupPage.evaluate(async () => {
let state = window.AuthStore.AppState.state;
Expand Down Expand Up @@ -231,25 +212,13 @@ class Client extends EventEmitter {
await webCache.persist(this.currentIndexHtml, version);
}

if (isCometOrAbove) {
await this.pupPage.evaluate(ExposeStore);
} else {
// make sure all modules are ready before injection
// 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
await new Promise(r => setTimeout(r, 2000));
await this.pupPage.evaluate(ExposeLegacyStore);
}
let start = Date.now();
let res = false;
while(start > (Date.now() - 30000)){
// Check window.Store Injection
res = await this.pupPage.evaluate('window.Store != undefined');
if(res){break;}
await new Promise(r => setTimeout(r, 200));
}
if(!res){
throw 'ready timeout';
}
await this.pupPage.evaluate(ExposeStore);

// Check window.Store Injection
await this.pupPage.waitForFunction(
'window.Store != undefined',
{ timeout: 30000 }
).catch(() => { throw 'ready timeout'; });

/**
* Current connection information
Expand Down Expand Up @@ -287,12 +256,15 @@ class Client extends EventEmitter {
await this.pupPage.evaluate(() => {
window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
window.AuthStore.Cmd.on('offline_progress_update', () => {
window.AuthStore.Cmd.on('offline_progress_update_from_bridge', () => {
window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress());
});
window.AuthStore.Cmd.on('logout', async () => {
await window.onLogoutEvent();
});
window.AuthStore.Cmd.on('logout_from_bridge', async () => {
await window.onLogoutEvent();
});
});
}

Expand Down Expand Up @@ -350,28 +322,12 @@ class Client extends EventEmitter {
await page.evaluateOnNewDocument(this.options.evalOnNewDoc);
}

// ocVersion (isOfficialClient patch)
// remove after 2.3000.x hard release
await page.evaluateOnNewDocument(() => {
const originalError = Error;
window.originalError = originalError;
//eslint-disable-next-line no-global-assign
Error = function (message) {
const error = new originalError(message);
const originalStack = error.stack;
if (error.stack.includes('moduleRaid')) error.stack = originalStack + '\n at https://web.whatsapp.com/vendors~lazy_loaded_low_priority_components.05e98054dbd60f980427.js:2:44';
return error;
};
});

await page.goto(WhatsWebURL, {
waitUntil: 'load',
timeout: 0,
referer: 'https://whatsapp.com/'
});

await this.inject();

this.pupPage.on('framenavigated', async (frame) => {
if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
this.emit(Events.DISCONNECTED, 'LOGOUT');
Expand All @@ -382,6 +338,8 @@ class Client extends EventEmitter {
}
await this.inject();
});

await this.inject();
}

/**
Expand Down Expand Up @@ -775,70 +733,54 @@ class Client extends EventEmitter {
});
window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});

if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
const module = window.Store.AddonReactionTable;
const ogMethod = module.bulkUpsert;
module.bulkUpsert = ((...args) => {
window.onReaction(args[0].map(reaction => {
const msgKey = reaction.id;
const parentMsgKey = reaction.reactionParentKey;
const timestamp = reaction.reactionTimestamp / 1000;
const sender = reaction.author ?? reaction.from;
const senderUserJid = sender._serialized;

return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
}));

return ogMethod(...args);
}).bind(module);

const pollVoteModule = window.Store.AddonPollVoteTable;
const ogPollVoteMethod = pollVoteModule.bulkUpsert;

pollVoteModule.bulkUpsert = (async (...args) => {
const votes = await Promise.all(args[0].map(async vote => {
const msgKey = vote.id;
const parentMsgKey = vote.pollUpdateParentKey;
const timestamp = vote.t / 1000;
const sender = vote.author ?? vote.from;
const senderUserJid = sender._serialized;

let parentMessage = window.Store.Msg.get(parentMsgKey._serialized);
if (!parentMessage) {
const fetched = await window.Store.Msg.getMessagesById([parentMsgKey._serialized]);
parentMessage = fetched?.messages?.[0] || null;
}
const module = window.Store.AddonReactionTable;
const ogMethod = module.bulkUpsert;
module.bulkUpsert = ((...args) => {
window.onReaction(args[0].map(reaction => {
const msgKey = reaction.id;
const parentMsgKey = reaction.reactionParentKey;
const timestamp = reaction.reactionTimestamp / 1000;
const sender = reaction.author ?? reaction.from;
const senderUserJid = sender._serialized;

return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
}));

return {
...vote,
msgKey,
sender,
parentMsgKey,
senderUserJid,
timestamp,
parentMessage
};
}));

window.onPollVoteEvent(votes);

return ogPollVoteMethod.apply(pollVoteModule, args);
}).bind(pollVoteModule);
} else {
const module = window.Store.createOrUpdateReactionsModule;
const ogMethod = module.createOrUpdateReactions;
module.createOrUpdateReactions = ((...args) => {
window.onReaction(args[0].map(reaction => {
const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey);
const timestamp = reaction.timestamp / 1000;

return {...reaction, msgKey, parentMsgKey, timestamp };
}));

return ogMethod(...args);
}).bind(module);
}
return ogMethod(...args);
}).bind(module);

const pollVoteModule = window.Store.AddonPollVoteTable;
const ogPollVoteMethod = pollVoteModule.bulkUpsert;

pollVoteModule.bulkUpsert = (async (...args) => {
const votes = await Promise.all(args[0].map(async vote => {
const msgKey = vote.id;
const parentMsgKey = vote.pollUpdateParentKey;
const timestamp = vote.t / 1000;
const sender = vote.author ?? vote.from;
const senderUserJid = sender._serialized;

let parentMessage = window.Store.Msg.get(parentMsgKey._serialized);
if (!parentMessage) {
const fetched = await window.Store.Msg.getMessagesById([parentMsgKey._serialized]);
parentMessage = fetched?.messages?.[0] || null;
}

return {
...vote,
msgKey,
sender,
parentMsgKey,
senderUserJid,
timestamp,
parentMessage
};
}));

window.onPollVoteEvent(votes);

return ogPollVoteMethod.apply(pollVoteModule, args);
}).bind(pollVoteModule);
});
}

Expand Down Expand Up @@ -1023,6 +965,7 @@ class Client extends EventEmitter {
sendMediaAsDocument: options.sendMediaAsDocument,
sendMediaAsHd: options.sendMediaAsHd,
caption: options.caption,
isCaptionByUser: options.caption ? true : false,
quotedMessageId: options.quotedMessageId,
parseVCards: options.parseVCards !== false,
mentionedJidList: options.mentions || [],
Expand Down Expand Up @@ -1560,9 +1503,7 @@ class Client extends EventEmitter {
const profilePic = await this.pupPage.evaluate(async contactId => {
try {
const chatWid = window.Store.WidFactory.createWid(contactId);
return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
? await window.Store.ProfilePic.profilePicFind(chatWid)
: await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
return await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
} catch (err) {
if(err.name === 'ServerStatusCodeError') return undefined;
throw err;
Expand Down
6 changes: 6 additions & 0 deletions src/structures/Chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class Chat extends Base {
*/
this.pinned = !!data.pin;

/**
* Indicates if the Chat is locked
* @type {boolean}
*/
this.isLocked = data.isLocked;

/**
* Indicates if the chat is muted or not
* @type {boolean}
Expand Down
5 changes: 1 addition & 4 deletions src/structures/GroupChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,11 +389,8 @@ class GroupChat extends Chat {
*/
async getInviteCode() {
const codeRes = await this.client.pupPage.evaluate(async chatId => {
const chatWid = window.Store.WidFactory.createWid(chatId);
try {
return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1020730154')
? await window.Store.GroupInvite.fetchMexGroupInviteCode(chatId)
: await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true);
return await window.Store.GroupInvite.fetchMexGroupInviteCode(chatId);
}
catch (err) {
if(err.name === 'ServerStatusCodeError') return undefined;
Expand Down
Loading