chore: add missing types in JSDoc/TSDoc (ClientOptions, ClientInfo, MessageSendOptions, Session) (#286)

* chore: add ClientOptions and ClientInfo in JSDoc

* chore: add MessageSendOptions type

* chore: add ClientSession type in JSDoc

* chore: remove @todo from createGroup (was already solved)

Co-authored-by: Pedro Lopez <pedroslopez@me.com>
This commit is contained in:
stefanfuchs
2020-08-03 03:52:37 -03:00
committed by GitHub
parent bfea74f567
commit 5c84a1651d
4 changed files with 124 additions and 74 deletions

67
index.d.ts vendored
View File

@@ -7,10 +7,7 @@ declare namespace WAWebJS {
export class Client extends EventEmitter { export class Client extends EventEmitter {
constructor(options: ClientOptions) constructor(options: ClientOptions)
/** /** Current connection information */
* Current connection information
* @todo add this in the official docs
*/
public info: ClientInfo public info: ClientInfo
/**Accepts an invitation to join a group */ /**Accepts an invitation to join a group */
@@ -26,8 +23,6 @@ declare namespace WAWebJS {
* Create a new group * Create a new group
* @param name group title * @param name group title
* @param participants an array of Contacts or contact IDs to add to the group * @param participants an array of Contacts or contact IDs to add to the group
*
* @todo improve return type in the official docs
*/ */
createGroup(name: string, participants: Contact[] | string[]): Promise<CreateGroupResult> createGroup(name: string, participants: Contact[] | string[]): Promise<CreateGroupResult>
@@ -223,25 +218,32 @@ declare namespace WAWebJS {
os_build_number: string os_build_number: string
} }
/** /** Options for initializing the whatsapp client */
* Options for initializing the whatsapp client
* @todo add these in the official docs
*/
export interface ClientOptions { export interface ClientOptions {
puppeteer?: puppeteer.LaunchOptions /** Timeout for authentication selector in puppeteer
/** Whatsapp session to restore. If not set, will start a new session */ * @default 45000 */
session?: ClientSession,
/** @default 45000 */
qrTimeoutMs?: number,
/** @default 20000 */
qrRefreshIntervalMs?: number,
/** @default 45000 */
authTimeoutMs?: number, authTimeoutMs?: number,
/** @default false */ /** Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ */
puppeteer?: puppeteer.LaunchOptions
/** Refresh interval for qr code (how much time to wait before checking if the qr code has changed)
* @default 20000 */
qrRefreshIntervalMs?: number
/** Timeout for qr code selector in puppeteer
* @default 45000 */
qrTimeoutMs?: number,
/** Restart client with a new session (i.e. use null 'session' var) if authentication fails
* @default false */
restartOnAuthFail?: boolean
/** Whatsapp session to restore. If not set, will start a new session */
session?: ClientSession
/** If another whatsapp web session is detected (another browser), take over the session in the current browser
* @default false */
takeoverOnConflict?: boolean, takeoverOnConflict?: boolean,
/** @default 0 */ /** How much time to wait before taking over the session
* @default 0 */
takeoverTimeoutMs?: number, takeoverTimeoutMs?: number,
/** @default 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36' */ /** User agent to use in puppeteer.
* @default 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36' */
userAgent?: string userAgent?: string
} }
@@ -451,7 +453,7 @@ declare namespace WAWebJS {
*/ */
to: string, to: string,
/** Message type */ /** Message type */
type: string, type: MessageTypes,
/** Deletes the message from the chat */ /** Deletes the message from the chat */
delete: (everyone?: boolean) => Promise<void>, delete: (everyone?: boolean) => Promise<void>,
@@ -487,10 +489,23 @@ declare namespace WAWebJS {
longitude: string, longitude: string,
} }
/** /** Options for sending a message */
* Options for sending a message export interface MessageSendOptions {
* @todo add more specific type for the object */ /** Show links preview */
export type MessageSendOptions = object linkPreview?: boolean
/** Send audio as voice message */
sendAudioAsVoice?: boolean
/** Image or videos caption */
caption?: string
/** Id of the message that is being quoted (or replied to) */
quotedMessageId?: string
/** Contacts that are being mentioned in the message */
mentions?: Contact[]
/** Send 'seen' status */
sendSeen?: boolean
/** Media to be sent */
media?: MessageMedia
}
export interface MessageMedia { export interface MessageMedia {
data: string, data: string,

View File

@@ -15,7 +15,21 @@ const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification
/** /**
* Starting point for interacting with the WhatsApp Web API * Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter} * @extends {EventEmitter}
* @param {object} options * @param {object} options - Client options
* @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
* @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
* @param {number} options.qrRefreshIntervalMs - Refresh interval for qr code (how much time to wait before checking if the qr code has changed)
* @param {number} options.qrTimeoutMs - Timeout for qr code selector in puppeteer
* @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails
* @param {object} options.session - Whatsapp session to restore. If not set, will start a new session
* @param {string} options.session.WABrowserId
* @param {string} options.session.WASecretBundle
* @param {string} options.session.WAToken1
* @param {string} options.session.WAToken2
* @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
*
* @fires Client#qr * @fires Client#qr
* @fires Client#authenticated * @fires Client#authenticated
* @fires Client#auth_failure * @fires Client#auth_failure
@@ -53,7 +67,7 @@ class Client extends EventEmitter {
this.pupBrowser = browser; this.pupBrowser = browser;
this.pupPage = page; this.pupPage = page;
if (this.options.session) { if (this.options.session) {
await page.evaluateOnNewDocument( await page.evaluateOnNewDocument(
session => { session => {
@@ -69,7 +83,7 @@ class Client extends EventEmitter {
waitUntil: 'load', waitUntil: 'load',
timeout: 0, timeout: 0,
}); });
const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-asset-intro-image-light="true"]'; const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-asset-intro-image-light="true"]';
if (this.options.session) { if (this.options.session) {
@@ -146,6 +160,10 @@ class Client extends EventEmitter {
* Emitted when authentication is successful * Emitted when authentication is successful
* @event Client#authenticated * @event Client#authenticated
* @param {object} session Object containing session information. Can be used to restore the session. * @param {object} session Object containing session information. Can be used to restore the session.
* @param {string} session.WABrowserId
* @param {string} session.WASecretBundle
* @param {string} session.WAToken1
* @param {string} session.WAToken2
*/ */
this.emit(Events.AUTHENTICATED, session); this.emit(Events.AUTHENTICATED, session);
@@ -156,6 +174,10 @@ class Client extends EventEmitter {
await page.evaluate(LoadUtils); await page.evaluate(LoadUtils);
// Expose client info // Expose client info
/**
* Current connection information
* @type {ClientInfo}
*/
this.info = new ClientInfo(this, await page.evaluate(() => { this.info = new ClientInfo(this, await page.evaluate(() => {
return window.Store.Conn.serialize(); return window.Store.Conn.serialize();
})); }));
@@ -193,7 +215,7 @@ class Client extends EventEmitter {
} }
return; return;
} }
const message = new Message(this, msg); const message = new Message(this, msg);
/** /**
@@ -262,7 +284,7 @@ class Client extends EventEmitter {
await page.exposeFunction('onMessageAckEvent', (msg, ack) => { await page.exposeFunction('onMessageAckEvent', (msg, ack) => {
const message = new Message(this, msg); const message = new Message(this, msg);
/** /**
* Emitted when an ack event occurrs on message type. * Emitted when an ack event occurrs on message type.
* @event Client#message_ack * @event Client#message_ack
@@ -276,7 +298,7 @@ class Client extends EventEmitter {
await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => { await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
const message = new Message(this, msg); const message = new Message(this, msg);
/** /**
* Emitted when media has been uploaded for a message sent by the client. * Emitted when media has been uploaded for a message sent by the client.
* @event Client#media_uploaded * @event Client#media_uploaded
@@ -296,10 +318,10 @@ class Client extends EventEmitter {
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT]; const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
if(this.options.takeoverOnConflict) { if (this.options.takeoverOnConflict) {
ACCEPTED_STATES.push(WAState.CONFLICT); ACCEPTED_STATES.push(WAState.CONFLICT);
if(state === WAState.CONFLICT) { if (state === WAState.CONFLICT) {
setTimeout(() => { setTimeout(() => {
this.pupPage.evaluate(() => window.Store.AppState.takeover()); this.pupPage.evaluate(() => window.Store.AppState.takeover());
}, this.options.takeoverTimeoutMs); }, this.options.takeoverTimeoutMs);
@@ -320,7 +342,7 @@ class Client extends EventEmitter {
await page.exposeFunction('onBatteryStateChangedEvent', (state) => { await page.exposeFunction('onBatteryStateChangedEvent', (state) => {
const { battery, plugged } = state; const { battery, plugged } = state;
if(battery === undefined) return; if (battery === undefined) return;
/** /**
* Emitted when the battery percentage for the attached device changes * Emitted when the battery percentage for the attached device changes
@@ -333,12 +355,12 @@ class Client extends EventEmitter {
}); });
await page.evaluate(() => { await page.evaluate(() => {
window.Store.Msg.on('add', (msg) => { if(msg.isNewMsg) window.onAddMessageEvent(msg); }); window.Store.Msg.on('add', (msg) => { if (msg.isNewMsg) window.onAddMessageEvent(msg); });
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(msg); }); window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(msg); });
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(msg); }); window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(msg); });
window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(msg, ack); }); window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(msg, ack); });
window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if(msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(msg); }); window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(msg); });
window.Store.Msg.on('remove', (msg) => { if(msg.isNewMsg) window.onRemoveMessageEvent(msg); }); window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(msg); });
window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); }); window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); }); window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
}); });
@@ -393,11 +415,24 @@ class Client extends EventEmitter {
return result; return result;
} }
/**
* Message options.
* @typedef {Object} MessageSendOptions
* @property {boolean} [linkPreview=true] - Show links preview
* @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message
* @property {string} [caption] - Image or video caption
* @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
* @property {Contact[]} [mentions] - Contacts that are being mentioned in the message
* @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
* @property {boolean} [media] - Media to be sent
*/
/** /**
* Send a message to a specific chatId * Send a message to a specific chatId
* @param {string} chatId * @param {string} chatId
* @param {string|MessageMedia|Location} content * @param {string|MessageMedia|Location} content
* @param {object} options * @param {MessageSendOptions} [options] - Options used when sending the message
*
* @returns {Promise<Message>} Message that was just sent * @returns {Promise<Message>} Message that was just sent
*/ */
async sendMessage(chatId, content, options = {}) { async sendMessage(chatId, content, options = {}) {
@@ -408,7 +443,7 @@ class Client extends EventEmitter {
quotedMessageId: options.quotedMessageId, quotedMessageId: options.quotedMessageId,
mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : [] mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : []
}; };
const sendSeen = typeof options.sendSeen === 'undefined' ? true : options.sendSeen; const sendSeen = typeof options.sendSeen === 'undefined' ? true : options.sendSeen;
if (content instanceof MessageMedia) { if (content instanceof MessageMedia) {
@@ -427,7 +462,7 @@ class Client extends EventEmitter {
const chatWid = window.Store.WidFactory.createWid(chatId); const chatWid = window.Store.WidFactory.createWid(chatId);
const chat = await window.Store.Chat.find(chatWid); const chat = await window.Store.Chat.find(chatWid);
if(sendSeen) { if (sendSeen) {
window.WWebJS.sendSeen(chatId); window.WWebJS.sendSeen(chatId);
} }
@@ -575,7 +610,7 @@ class Client extends EventEmitter {
await chat.mute.mute(timestamp, !0); await chat.mute.mute(timestamp, !0);
}, chatId, unmuteDate.getTime() / 1000); }, chatId, unmuteDate.getTime() / 1000);
} }
/** /**
* Unmutes the Chat * Unmutes the Chat
* @param {string} chatId ID of the chat that will be unmuted * @param {string} chatId ID of the chat that will be unmuted
@@ -586,7 +621,7 @@ class Client extends EventEmitter {
await window.Store.Cmd.muteChat(chat, false); await window.Store.Cmd.muteChat(chat, false);
}, chatId); }, chatId);
} }
/** /**
* Returns the contact ID's profile picture URL, if privacy settings allow it * Returns the contact ID's profile picture URL, if privacy settings allow it
* @param {string} contactId the whatsapp user's ID * @param {string} contactId the whatsapp user's ID
@@ -603,7 +638,7 @@ class Client extends EventEmitter {
/** /**
* Force reset of connection state for the client * Force reset of connection state for the client
*/ */
async resetState(){ async resetState() {
await this.pupPage.evaluate(() => { await this.pupPage.evaluate(() => {
window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow(); window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow();
}); });
@@ -630,18 +665,18 @@ class Client extends EventEmitter {
* @returns {Object.<string,string>} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups. * @returns {Object.<string,string>} createRes.missingParticipants - participants that were not added to the group. Keys represent the ID for participant that was not added and its value is a status code that represents the reason why participant could not be added. This is usually 403 if the user's privacy settings don't allow you to add them to groups.
*/ */
async createGroup(name, participants) { async createGroup(name, participants) {
if(!Array.isArray(participants) || participants.length == 0) { if (!Array.isArray(participants) || participants.length == 0) {
throw 'You need to add at least one other participant to the group'; throw 'You need to add at least one other participant to the group';
} }
if(participants.every(c => c instanceof Contact)) { if (participants.every(c => c instanceof Contact)) {
participants = participants.map(c => c.id._serialized); participants = participants.map(c => c.id._serialized);
} }
const createRes = await this.pupPage.evaluate(async (name, participantIds) => { const createRes = await this.pupPage.evaluate(async (name, participantIds) => {
const res = await window.Store.Wap.createGroup(name, participantIds); const res = await window.Store.Wap.createGroup(name, participantIds);
console.log(res); console.log(res);
if(!res.status === 200) { if (!res.status === 200) {
throw 'An error occurred while creating the group!'; throw 'An error occurred while creating the group!';
} }
@@ -651,11 +686,11 @@ class Client extends EventEmitter {
const missingParticipants = createRes.participants.reduce(((missing, c) => { const missingParticipants = createRes.participants.reduce(((missing, c) => {
const id = Object.keys(c)[0]; const id = Object.keys(c)[0];
const statusCode = c[id].code; const statusCode = c[id].code;
if(statusCode != 200) return Object.assign(missing, {[id]: statusCode}); if (statusCode != 200) return Object.assign(missing, { [id]: statusCode });
return missing; return missing;
}), {}); }), {});
return { gid: createRes.gid, missingParticipants}; return { gid: createRes.gid, missingParticipants };
} }
} }

View File

@@ -63,7 +63,7 @@ class Chat extends Base {
/** /**
* Send a message to this chat * Send a message to this chat
* @param {string|MessageMedia|Location} content * @param {string|MessageMedia|Location} content
* @param {object} options * @param {MessageSendOptions} [options]
* @returns {Promise<Message>} Message that was just sent * @returns {Promise<Message>} Message that was just sent
*/ */
async sendMessage(content, options) { async sendMessage(content, options) {
@@ -119,7 +119,7 @@ class Chat extends Base {
async mute(unmuteDate) { async mute(unmuteDate) {
return this.client.muteChat(this.id._serialized, unmuteDate); return this.client.muteChat(this.id._serialized, unmuteDate);
} }
/** /**
* Unmutes this chat * Unmutes this chat
*/ */
@@ -134,18 +134,18 @@ class Chat extends Base {
* @returns {Promise<Array<Message>>} * @returns {Promise<Array<Message>>}
*/ */
async fetchMessages(searchOptions) { async fetchMessages(searchOptions) {
if(!searchOptions || !searchOptions.limit) { if (!searchOptions || !searchOptions.limit) {
searchOptions = {limit: 50}; searchOptions = { limit: 50 };
} }
let messages = await this.client.pupPage.evaluate(async (chatId, limit) => { let messages = await this.client.pupPage.evaluate(async (chatId, limit) => {
const msgFilter = m => !m.isNotification; // dont include notification messages const msgFilter = m => !m.isNotification; // dont include notification messages
const chat = window.Store.Chat.get(chatId); const chat = window.Store.Chat.get(chatId);
let msgs = chat.msgs.models.filter(msgFilter); let msgs = chat.msgs.models.filter(msgFilter);
while(msgs.length < limit) { while (msgs.length < limit) {
const loadedMessages = await chat.loadEarlierMsgs(); const loadedMessages = await chat.loadEarlierMsgs();
if(!loadedMessages) break; if (!loadedMessages) break;
msgs = [...loadedMessages.filter(msgFilter), ...msgs]; msgs = [...loadedMessages.filter(msgFilter), ...msgs];
} }
@@ -156,7 +156,7 @@ class Chat extends Base {
return messages.map(m => new Message(this.client, m)); return messages.map(m => new Message(this.client, m));
} }
/** /**
* Simulate typing in chat. This will last for 25 seconds. * Simulate typing in chat. This will last for 25 seconds.
*/ */
@@ -166,7 +166,7 @@ class Chat extends Base {
return true; return true;
}, this.id._serialized); }, this.id._serialized);
} }
/** /**
* Simulate recording audio in chat. This will last for 25 seconds. * Simulate recording audio in chat. This will last for 25 seconds.
*/ */
@@ -176,7 +176,7 @@ class Chat extends Base {
return true; return true;
}, this.id._serialized); }, this.id._serialized);
} }
/** /**
* Stops typing or recording in chat immediately. * Stops typing or recording in chat immediately.
*/ */

View File

@@ -13,7 +13,7 @@ class Message extends Base {
constructor(client, data) { constructor(client, data) {
super(client); super(client);
if(data) this._patch(data); if (data) this._patch(data);
} }
_patch(data) { _patch(data) {
@@ -98,7 +98,7 @@ class Message extends Base {
* @type {boolean} * @type {boolean}
*/ */
this.fromMe = data.id.fromMe; this.fromMe = data.id.fromMe;
/** /**
* Indicates if the message was sent as a reply to another message. * Indicates if the message was sent as a reply to another message.
* @type {boolean} * @type {boolean}
@@ -173,11 +173,11 @@ class Message extends Base {
* in the same Chat as the original message was sent. * in the same Chat as the original message was sent.
* *
* @param {string|MessageMedia|Location} content * @param {string|MessageMedia|Location} content
* @param {?string} chatId * @param {string} [chatId]
* @param {object} options * @param {MessageSendOptions} [options]
* @returns {Promise<Message>} * @returns {Promise<Message>}
*/ */
async reply(content, chatId, options={}) { async reply(content, chatId, options = {}) {
if (!chatId) { if (!chatId) {
chatId = this._getChatId(); chatId = this._getChatId();
} }
@@ -201,13 +201,13 @@ class Message extends Base {
const result = await this.client.pupPage.evaluate(async (msgId) => { const result = await this.client.pupPage.evaluate(async (msgId) => {
const msg = window.Store.Msg.get(msgId); const msg = window.Store.Msg.get(msgId);
if(msg.mediaData.mediaStage != 'RESOLVED') { if (msg.mediaData.mediaStage != 'RESOLVED') {
// try to resolve media // try to resolve media
await msg.downloadMedia(true, 1); await msg.downloadMedia(true, 1);
} }
if(msg.mediaData.mediaStage.includes('ERROR')) { if (msg.mediaData.mediaStage.includes('ERROR')) {
// media could not be downloaded // media could not be downloaded
return undefined; return undefined;
} }
@@ -215,7 +215,7 @@ class Message extends Base {
const buffer = await window.WWebJS.downloadBuffer(msg.clientUrl); const buffer = await window.WWebJS.downloadBuffer(msg.clientUrl);
const decrypted = await window.Store.CryptoLib.decryptE2EMedia(msg.type, buffer, msg.mediaKey, msg.mimetype); const decrypted = await window.Store.CryptoLib.decryptE2EMedia(msg.type, buffer, msg.mediaKey, msg.mimetype);
const data = await window.WWebJS.readBlobAsync(decrypted._blob); const data = await window.WWebJS.readBlobAsync(decrypted._blob);
return { return {
data: data.split(',')[1], data: data.split(',')[1],
mimetype: msg.mimetype, mimetype: msg.mimetype,
@@ -224,7 +224,7 @@ class Message extends Base {
}, this.id._serialized); }, this.id._serialized);
if(!result) return undefined; if (!result) return undefined;
return new MessageMedia(result.mimetype, result.data, result.filename); return new MessageMedia(result.mimetype, result.data, result.filename);
} }
@@ -236,10 +236,10 @@ class Message extends Base {
await this.client.pupPage.evaluate((msgId, everyone) => { await this.client.pupPage.evaluate((msgId, everyone) => {
let msg = window.Store.Msg.get(msgId); let msg = window.Store.Msg.get(msgId);
if(everyone && msg.id.fromMe && msg.canRevoke()) { if (everyone && msg.id.fromMe && msg.canRevoke()) {
return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], true); return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], true);
} }
return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true); return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true);
}, this.id._serialized, everyone); }, this.id._serialized, everyone);
} }