diff --git a/.prettierignore b/.prettierignore index 9af4195..a0737aa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ lib coverage *.lock +*.json src/WABinary/index.ts WAProto WASignalGroup diff --git a/package.json b/package.json index f7fa475..975da4e 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", "example": "node --inspect -r ts-node/register Example/example.ts", "gen:protobuf": "sh WAProto/GenerateStatics.sh", + "format": "prettier --write \"src/**/*.{ts,js,json,md}\"", "lint": "eslint src --ext .js,.ts", - "lint:fix": "yarn lint --fix", + "lint:fix": "yarn format && yarn lint --fix", "prepack": "tsc", "prepare": "tsc", "preinstall": "node ./engine-requirements.js", diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index e6792d6..f190094 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -17,14 +17,12 @@ export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60 export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0' export const DICT_VERSION = 2 export const KEY_BUNDLE_TYPE = Buffer.from([5]) -export const NOISE_WA_HEADER = Buffer.from( - [ 87, 65, 6, DICT_VERSION ] -) // last is "DICT_VERSION" +export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]) // last is "DICT_VERSION" /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */ export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g export const WA_CERT_DETAILS = { - SERIAL: 0, + SERIAL: 0 } export const PROCESSABLE_HISTORY_TYPES = [ @@ -32,7 +30,7 @@ export const PROCESSABLE_HISTORY_TYPES = [ proto.Message.HistorySyncNotification.HistorySyncType.PUSH_NAME, proto.Message.HistorySyncNotification.HistorySyncType.RECENT, proto.Message.HistorySyncNotification.HistorySyncType.FULL, - proto.Message.HistorySyncNotification.HistorySyncType.ON_DEMAND, + proto.Message.HistorySyncNotification.HistorySyncType.ON_DEMAND ] export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { @@ -57,14 +55,14 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { linkPreviewImageThumbnailWidth: 192, transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, generateHighQualityLinkPreview: false, - options: { }, + options: {}, appStateMacVerification: { patch: false, - snapshot: false, + snapshot: false }, countryCode: 'US', - getMessage: async() => undefined, - cachedGroupMetadata: async() => undefined, + getMessage: async () => undefined, + cachedGroupMetadata: async () => undefined, makeSignalRepository: makeLibSignalRepository } @@ -77,19 +75,19 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = { 'thumbnail-link': '/mms/image', 'product-catalog-image': '/product/image', 'md-app-state': '', - 'md-msg-hist': '/mms/md-app-state', + 'md-msg-hist': '/mms/md-app-state' } export const MEDIA_HKDF_KEY_MAPPING = { - 'audio': 'Audio', - 'document': 'Document', - 'gif': 'Video', - 'image': 'Image', - 'ppic': '', - 'product': 'Image', - 'ptt': 'Audio', - 'sticker': 'Image', - 'video': 'Video', + audio: 'Audio', + document: 'Document', + gif: 'Video', + image: 'Image', + ppic: '', + product: 'Image', + ptt: 'Audio', + sticker: 'Image', + video: 'Video', 'thumbnail-document': 'Document Thumbnail', 'thumbnail-image': 'Image Thumbnail', 'thumbnail-video': 'Video Thumbnail', @@ -98,7 +96,7 @@ export const MEDIA_HKDF_KEY_MAPPING = { 'md-app-state': 'App State', 'product-catalog-image': '', 'payment-bg-image': 'Payment Background', - 'ptv': 'Video' + ptv: 'Video' } export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[] @@ -111,5 +109,5 @@ export const DEFAULT_CACHE_TTLS = { SIGNAL_STORE: 5 * 60, // 5 minutes MSG_RETRY: 60 * 60, // 1 hour CALL_OFFER: 5 * 60, // 5 minutes - USER_DEVICES: 5 * 60, // 5 minutes + USER_DEVICES: 5 * 60 // 5 minutes } diff --git a/src/Signal/libsignal.ts b/src/Signal/libsignal.ts index 367cdb8..a80b08b 100644 --- a/src/Signal/libsignal.ts +++ b/src/Signal/libsignal.ts @@ -1,5 +1,11 @@ import * as libsignal from 'libsignal' -import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage, SenderKeyName, SenderKeyRecord } from '../../WASignalGroup' +import { + GroupCipher, + GroupSessionBuilder, + SenderKeyDistributionMessage, + SenderKeyName, + SenderKeyRecord +} from '../../WASignalGroup' import { SignalAuthState } from '../Types' import { SignalRepository } from '../Types/Signal' import { generateSignalPubKey } from '../Utils' @@ -18,9 +24,15 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository const builder = new GroupSessionBuilder(storage) const senderName = jidToSignalSenderKeyName(item.groupId!, authorJid) - const senderMsg = new SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage) + const senderMsg = new SenderKeyDistributionMessage( + null, + null, + null, + null, + item.axolotlSenderKeyDistributionMessage + ) const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName]) - if(!senderKey) { + if (!senderKey) { await storage.storeSenderKey(senderName, new SenderKeyRecord()) } @@ -31,12 +43,12 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository const session = new libsignal.SessionCipher(storage, addr) let result: Buffer switch (type) { - case 'pkmsg': - result = await session.decryptPreKeyWhisperMessage(ciphertext) - break - case 'msg': - result = await session.decryptWhisperMessage(ciphertext) - break + case 'pkmsg': + result = await session.decryptPreKeyWhisperMessage(ciphertext) + break + case 'msg': + result = await session.decryptWhisperMessage(ciphertext) + break } return result @@ -54,7 +66,7 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository const builder = new GroupSessionBuilder(storage) const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName]) - if(!senderKey) { + if (!senderKey) { await storage.storeSenderKey(senderName, new SenderKeyRecord()) } @@ -64,7 +76,7 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository return { ciphertext, - senderKeyDistributionMessage: senderKeyDistributionMessage.serialize(), + senderKeyDistributionMessage: senderKeyDistributionMessage.serialize() } }, async injectE2ESession({ jid, session }) { @@ -73,7 +85,7 @@ export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository }, jidToSignalProtocolAddress(jid) { return jidToSignalProtocolAddress(jid).toString() - }, + } } } @@ -88,22 +100,22 @@ const jidToSignalSenderKeyName = (group: string, user: string): string => { function signalStorage({ creds, keys }: SignalAuthState) { return { - loadSession: async(id: string) => { + loadSession: async (id: string) => { const { [id]: sess } = await keys.get('session', [id]) - if(sess) { + if (sess) { return libsignal.SessionRecord.deserialize(sess) } }, - storeSession: async(id, session) => { - await keys.set({ 'session': { [id]: session.serialize() } }) + storeSession: async (id, session) => { + await keys.set({ session: { [id]: session.serialize() } }) }, isTrustedIdentity: () => { return true }, - loadPreKey: async(id: number | string) => { + loadPreKey: async (id: number | string) => { const keyId = id.toString() const { [keyId]: key } = await keys.get('pre-key', [keyId]) - if(key) { + if (key) { return { privKey: Buffer.from(key.private), pubKey: Buffer.from(key.public) @@ -118,24 +130,22 @@ function signalStorage({ creds, keys }: SignalAuthState) { pubKey: Buffer.from(key.keyPair.public) } }, - loadSenderKey: async(keyId: string) => { + loadSenderKey: async (keyId: string) => { const { [keyId]: key } = await keys.get('sender-key', [keyId]) - if(key) { + if (key) { return new SenderKeyRecord(key) } }, - storeSenderKey: async(keyId, key) => { + storeSenderKey: async (keyId, key) => { await keys.set({ 'sender-key': { [keyId]: key.serialize() } }) }, - getOurRegistrationId: () => ( - creds.registrationId - ), + getOurRegistrationId: () => creds.registrationId, getOurIdentity: () => { const { signedIdentityKey } = creds return { privKey: Buffer.from(signedIdentityKey.private), - pubKey: generateSignalPubKey(signedIdentityKey.public), + pubKey: generateSignalPubKey(signedIdentityKey.public) } } } -} \ No newline at end of file +} diff --git a/src/Socket/Client/index.ts b/src/Socket/Client/index.ts index d5e782c..fb75601 100644 --- a/src/Socket/Client/index.ts +++ b/src/Socket/Client/index.ts @@ -1,2 +1,2 @@ export * from './types' -export * from './websocket' \ No newline at end of file +export * from './websocket' diff --git a/src/Socket/Client/types.ts b/src/Socket/Client/types.ts index 5d78298..8b411d3 100644 --- a/src/Socket/Client/types.ts +++ b/src/Socket/Client/types.ts @@ -8,12 +8,15 @@ export abstract class AbstractSocketClient extends EventEmitter { abstract get isClosing(): boolean abstract get isConnecting(): boolean - constructor(public url: URL, public config: SocketConfig) { + constructor( + public url: URL, + public config: SocketConfig + ) { super() this.setMaxListeners(0) } abstract connect(): Promise abstract close(): Promise - abstract send(str: Uint8Array | string, cb?: (err?: Error) => void): boolean; -} \ No newline at end of file + abstract send(str: Uint8Array | string, cb?: (err?: Error) => void): boolean +} diff --git a/src/Socket/Client/websocket.ts b/src/Socket/Client/websocket.ts index ea8ddfd..ec131d8 100644 --- a/src/Socket/Client/websocket.ts +++ b/src/Socket/Client/websocket.ts @@ -3,7 +3,6 @@ import { DEFAULT_ORIGIN } from '../../Defaults' import { AbstractSocketClient } from './types' export class WebSocketClient extends AbstractSocketClient { - protected socket: WebSocket | null = null get isOpen(): boolean { @@ -20,7 +19,7 @@ export class WebSocketClient extends AbstractSocketClient { } async connect(): Promise { - if(this.socket) { + if (this.socket) { return } @@ -29,20 +28,20 @@ export class WebSocketClient extends AbstractSocketClient { headers: this.config.options?.headers as {}, handshakeTimeout: this.config.connectTimeoutMs, timeout: this.config.connectTimeoutMs, - agent: this.config.agent, + agent: this.config.agent }) this.socket.setMaxListeners(0) const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response'] - for(const event of events) { + for (const event of events) { this.socket?.on(event, (...args: any[]) => this.emit(event, ...args)) } } async close(): Promise { - if(!this.socket) { + if (!this.socket) { return } diff --git a/src/Socket/business.ts b/src/Socket/business.ts index c9dfdb1..f410636 100644 --- a/src/Socket/business.ts +++ b/src/Socket/business.ts @@ -1,43 +1,46 @@ import { GetCatalogOptions, ProductCreate, ProductUpdate, SocketConfig } from '../Types' -import { parseCatalogNode, parseCollectionsNode, parseOrderDetailsNode, parseProductNode, toProductNode, uploadingNecessaryImagesOfProduct } from '../Utils/business' +import { + parseCatalogNode, + parseCollectionsNode, + parseOrderDetailsNode, + parseProductNode, + toProductNode, + uploadingNecessaryImagesOfProduct +} from '../Utils/business' import { BinaryNode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary' import { getBinaryNodeChild } from '../WABinary/generic-utils' import { makeMessagesRecvSocket } from './messages-recv' export const makeBusinessSocket = (config: SocketConfig) => { const sock = makeMessagesRecvSocket(config) - const { - authState, - query, - waUploadToServer - } = sock + const { authState, query, waUploadToServer } = sock - const getCatalog = async({ jid, limit, cursor }: GetCatalogOptions) => { + const getCatalog = async ({ jid, limit, cursor }: GetCatalogOptions) => { jid = jid || authState.creds.me?.id jid = jidNormalizedUser(jid) const queryParamNodes: BinaryNode[] = [ { tag: 'limit', - attrs: { }, + attrs: {}, content: Buffer.from((limit || 10).toString()) }, { tag: 'width', - attrs: { }, + attrs: {}, content: Buffer.from('100') }, { tag: 'height', - attrs: { }, + attrs: {}, content: Buffer.from('100') - }, + } ] - if(cursor) { + if (cursor) { queryParamNodes.push({ tag: 'after', - attrs: { }, + attrs: {}, content: cursor }) } @@ -54,7 +57,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { tag: 'product_catalog', attrs: { jid, - 'allow_shop_source': 'true' + allow_shop_source: 'true' }, content: queryParamNodes } @@ -63,7 +66,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { return parseCatalogNode(result) } - const getCollections = async(jid?: string, limit = 51) => { + const getCollections = async (jid?: string, limit = 51) => { jid = jid || authState.creds.me?.id jid = jidNormalizedUser(jid) const result = await query({ @@ -72,33 +75,33 @@ export const makeBusinessSocket = (config: SocketConfig) => { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:biz:catalog', - 'smax_id': '35' + smax_id: '35' }, content: [ { tag: 'collections', attrs: { - 'biz_jid': jid, + biz_jid: jid }, content: [ { tag: 'collection_limit', - attrs: { }, + attrs: {}, content: Buffer.from(limit.toString()) }, { tag: 'item_limit', - attrs: { }, + attrs: {}, content: Buffer.from(limit.toString()) }, { tag: 'width', - attrs: { }, + attrs: {}, content: Buffer.from('100') }, { tag: 'height', - attrs: { }, + attrs: {}, content: Buffer.from('100') } ] @@ -109,14 +112,14 @@ export const makeBusinessSocket = (config: SocketConfig) => { return parseCollectionsNode(result) } - const getOrderDetails = async(orderId: string, tokenBase64: string) => { + const getOrderDetails = async (orderId: string, tokenBase64: string) => { const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'fb:thrift_iq', - 'smax_id': '5' + smax_id: '5' }, content: [ { @@ -128,23 +131,23 @@ export const makeBusinessSocket = (config: SocketConfig) => { content: [ { tag: 'image_dimensions', - attrs: { }, + attrs: {}, content: [ { tag: 'width', - attrs: { }, + attrs: {}, content: Buffer.from('100') }, { tag: 'height', - attrs: { }, + attrs: {}, content: Buffer.from('100') } ] }, { tag: 'token', - attrs: { }, + attrs: {}, content: Buffer.from(tokenBase64) } ] @@ -155,7 +158,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { return parseOrderDetailsNode(result) } - const productUpdate = async(productId: string, update: ProductUpdate) => { + const productUpdate = async (productId: string, update: ProductUpdate) => { update = await uploadingNecessaryImagesOfProduct(update, waUploadToServer) const editNode = toProductNode(productId, update) @@ -174,12 +177,12 @@ export const makeBusinessSocket = (config: SocketConfig) => { editNode, { tag: 'width', - attrs: { }, + attrs: {}, content: '100' }, { tag: 'height', - attrs: { }, + attrs: {}, content: '100' } ] @@ -193,7 +196,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { return parseProductNode(productNode!) } - const productCreate = async(create: ProductCreate) => { + const productCreate = async (create: ProductCreate) => { // ensure isHidden is defined create.isHidden = !!create.isHidden create = await uploadingNecessaryImagesOfProduct(create, waUploadToServer) @@ -214,12 +217,12 @@ export const makeBusinessSocket = (config: SocketConfig) => { createNode, { tag: 'width', - attrs: { }, + attrs: {}, content: '100' }, { tag: 'height', - attrs: { }, + attrs: {}, content: '100' } ] @@ -233,7 +236,7 @@ export const makeBusinessSocket = (config: SocketConfig) => { return parseProductNode(productNode!) } - const productDelete = async(productIds: string[]) => { + const productDelete = async (productIds: string[]) => { const result = await query({ tag: 'iq', attrs: { @@ -245,19 +248,17 @@ export const makeBusinessSocket = (config: SocketConfig) => { { tag: 'product_catalog_delete', attrs: { v: '1' }, - content: productIds.map( - id => ({ - tag: 'product', - attrs: { }, - content: [ - { - tag: 'id', - attrs: { }, - content: Buffer.from(id) - } - ] - }) - ) + content: productIds.map(id => ({ + tag: 'product', + attrs: {}, + content: [ + { + tag: 'id', + attrs: {}, + content: Buffer.from(id) + } + ] + })) } ] }) diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index a5f8283..363f6d9 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -2,12 +2,52 @@ import NodeCache from '@cacheable/node-cache' import { Boom } from '@hapi/boom' import { proto } from '../../WAProto' import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults' -import { ALL_WA_PATCH_NAMES, BotListInfo, ChatModification, ChatMutation, LTHashState, MessageUpsertType, PresenceData, SocketConfig, WABusinessHoursConfig, WABusinessProfile, WAMediaUpload, WAMessage, WAPatchCreate, WAPatchName, WAPresence, WAPrivacyCallValue, WAPrivacyGroupAddValue, WAPrivacyMessagesValue, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '../Types' +import { + ALL_WA_PATCH_NAMES, + BotListInfo, + ChatModification, + ChatMutation, + LTHashState, + MessageUpsertType, + PresenceData, + SocketConfig, + WABusinessHoursConfig, + WABusinessProfile, + WAMediaUpload, + WAMessage, + WAPatchCreate, + WAPatchName, + WAPresence, + WAPrivacyCallValue, + WAPrivacyGroupAddValue, + WAPrivacyMessagesValue, + WAPrivacyOnlineValue, + WAPrivacyValue, + WAReadReceiptsValue +} from '../Types' import { LabelActionBody } from '../Types/Label' -import { chatModificationToAppPatch, ChatMutationMap, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, extractSyncdPatches, generateProfilePicture, getHistoryMsg, newLTHashState, processSyncAction } from '../Utils' +import { + chatModificationToAppPatch, + ChatMutationMap, + decodePatches, + decodeSyncdSnapshot, + encodeSyncdPatch, + extractSyncdPatches, + generateProfilePicture, + getHistoryMsg, + newLTHashState, + processSyncAction +} from '../Utils' import { makeMutex } from '../Utils/make-mutex' import processMessage from '../Utils/process-message' -import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary' +import { + BinaryNode, + getBinaryNodeChild, + getBinaryNodeChildren, + jidNormalizedUser, + reduceBinaryNodeToDictionary, + S_WHATSAPP_NET +} from '../WABinary' import { USyncQuery, USyncUser } from '../WAUSync' import { makeUSyncSocket } from './usync' const MAX_SYNC_ATTEMPTS = 2 @@ -19,18 +59,10 @@ export const makeChatsSocket = (config: SocketConfig) => { fireInitQueries, appStateMacVerification, shouldIgnoreJid, - shouldSyncHistoryMessage, + shouldSyncHistoryMessage } = config const sock = makeUSyncSocket(config) - const { - ev, - ws, - authState, - generateMessageTag, - sendNode, - query, - onUnexpectedError, - } = sock + const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError } = sock let privacySettings: { [_: string]: string } | undefined let needToFlushWithAppStateSync = false @@ -38,23 +70,25 @@ export const makeChatsSocket = (config: SocketConfig) => { /** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */ const processingMutex = makeMutex() - const placeholderResendCache = config.placeholderResendCache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour - useClones: false - }) + const placeholderResendCache = + config.placeholderResendCache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour + useClones: false + }) - if(!config.placeholderResendCache) { + if (!config.placeholderResendCache) { config.placeholderResendCache = placeholderResendCache } /** helper function to fetch the given app state sync key */ - const getAppStateSyncKey = async(keyId: string) => { + const getAppStateSyncKey = async (keyId: string) => { const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]) return key } - const fetchPrivacySettings = async(force = false) => { - if(!privacySettings || force) { + const fetchPrivacySettings = async (force = false) => { + if (!privacySettings || force) { const { content } = await query({ tag: 'iq', attrs: { @@ -62,9 +96,7 @@ export const makeChatsSocket = (config: SocketConfig) => { to: S_WHATSAPP_NET, type: 'get' }, - content: [ - { tag: 'privacy', attrs: {} } - ] + content: [{ tag: 'privacy', attrs: {} }] }) privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category') } @@ -73,7 +105,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } /** helper function to run a privacy IQ query */ - const privacyQuery = async(name: string, value: string) => { + const privacyQuery = async (name: string, value: string) => { await query({ tag: 'iq', attrs: { @@ -81,52 +113,54 @@ export const makeChatsSocket = (config: SocketConfig) => { to: S_WHATSAPP_NET, type: 'set' }, - content: [{ - tag: 'privacy', - attrs: {}, - content: [ - { - tag: 'category', - attrs: { name, value } - } - ] - }] + content: [ + { + tag: 'privacy', + attrs: {}, + content: [ + { + tag: 'category', + attrs: { name, value } + } + ] + } + ] }) } - const updateMessagesPrivacy = async(value: WAPrivacyMessagesValue) => { + const updateMessagesPrivacy = async (value: WAPrivacyMessagesValue) => { await privacyQuery('messages', value) } - const updateCallPrivacy = async(value: WAPrivacyCallValue) => { + const updateCallPrivacy = async (value: WAPrivacyCallValue) => { await privacyQuery('calladd', value) } - const updateLastSeenPrivacy = async(value: WAPrivacyValue) => { + const updateLastSeenPrivacy = async (value: WAPrivacyValue) => { await privacyQuery('last', value) } - const updateOnlinePrivacy = async(value: WAPrivacyOnlineValue) => { + const updateOnlinePrivacy = async (value: WAPrivacyOnlineValue) => { await privacyQuery('online', value) } - const updateProfilePicturePrivacy = async(value: WAPrivacyValue) => { + const updateProfilePicturePrivacy = async (value: WAPrivacyValue) => { await privacyQuery('profile', value) } - const updateStatusPrivacy = async(value: WAPrivacyValue) => { + const updateStatusPrivacy = async (value: WAPrivacyValue) => { await privacyQuery('status', value) } - const updateReadReceiptsPrivacy = async(value: WAReadReceiptsValue) => { + const updateReadReceiptsPrivacy = async (value: WAReadReceiptsValue) => { await privacyQuery('readreceipts', value) } - const updateGroupsAddPrivacy = async(value: WAPrivacyGroupAddValue) => { + const updateGroupsAddPrivacy = async (value: WAPrivacyGroupAddValue) => { await privacyQuery('groupadd', value) } - const updateDefaultDisappearingMode = async(duration: number) => { + const updateDefaultDisappearingMode = async (duration: number) => { await query({ tag: 'iq', attrs: { @@ -134,38 +168,42 @@ export const makeChatsSocket = (config: SocketConfig) => { to: S_WHATSAPP_NET, type: 'set' }, - content: [{ - tag: 'disappearing_mode', - attrs: { - duration: duration.toString() + content: [ + { + tag: 'disappearing_mode', + attrs: { + duration: duration.toString() + } } - }] + ] }) } - const getBotListV2 = async() => { - const resp = await query({ - tag: 'iq', - attrs: { - xmlns: 'bot', - to: S_WHATSAPP_NET, - type: 'get' - }, - content: [{ - tag: 'bot', - attrs: { - v: '2' - } - }] - }) + const getBotListV2 = async () => { + const resp = await query({ + tag: 'iq', + attrs: { + xmlns: 'bot', + to: S_WHATSAPP_NET, + type: 'get' + }, + content: [ + { + tag: 'bot', + attrs: { + v: '2' + } + } + ] + }) const botNode = getBinaryNodeChild(resp, 'bot') const botList: BotListInfo[] = [] - for(const section of getBinaryNodeChildren(botNode, 'section')) { - if(section.attrs.type === 'all') { - for(const bot of getBinaryNodeChildren(section, 'bot')) { - botList.push({ + for (const section of getBinaryNodeChildren(botNode, 'section')) { + if (section.attrs.type === 'all') { + for (const bot of getBinaryNodeChildren(section, 'bot')) { + botList.push({ jid: bot.attrs.jid, personaId: bot.attrs['persona_id'] }) @@ -176,59 +214,57 @@ export const makeChatsSocket = (config: SocketConfig) => { return botList } - const onWhatsApp = async(...jids: string[]) => { - const usyncQuery = new USyncQuery() - .withContactProtocol() - .withLIDProtocol() + const onWhatsApp = async (...jids: string[]) => { + const usyncQuery = new USyncQuery().withContactProtocol().withLIDProtocol() - for(const jid of jids) { + for (const jid of jids) { const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}` usyncQuery.withUser(new USyncUser().withPhone(phone)) } const results = await sock.executeUSyncQuery(usyncQuery) - if(results) { - return results.list.filter((a) => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid })) + if (results) { + return results.list.filter(a => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid })) } } - const fetchStatus = async(...jids: string[]) => { - const usyncQuery = new USyncQuery() - .withStatusProtocol() + const fetchStatus = async (...jids: string[]) => { + const usyncQuery = new USyncQuery().withStatusProtocol() - for(const jid of jids) { + for (const jid of jids) { usyncQuery.withUser(new USyncUser().withId(jid)) } const result = await sock.executeUSyncQuery(usyncQuery) - if(result) { + if (result) { return result.list } } - const fetchDisappearingDuration = async(...jids: string[]) => { - const usyncQuery = new USyncQuery() - .withDisappearingModeProtocol() + const fetchDisappearingDuration = async (...jids: string[]) => { + const usyncQuery = new USyncQuery().withDisappearingModeProtocol() - for(const jid of jids) { + for (const jid of jids) { usyncQuery.withUser(new USyncUser().withId(jid)) } const result = await sock.executeUSyncQuery(usyncQuery) - if(result) { + if (result) { return result.list } } /** update the profile picture for yourself or a group */ - const updateProfilePicture = async(jid: string, content: WAMediaUpload) => { + const updateProfilePicture = async (jid: string, content: WAMediaUpload) => { let targetJid - if(!jid) { - throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update') + if (!jid) { + throw new Boom( + 'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update' + ) } - if(jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) { + if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) { targetJid = jidNormalizedUser(jid) // in case it is someone other than us } @@ -252,13 +288,15 @@ export const makeChatsSocket = (config: SocketConfig) => { } /** remove the profile picture for yourself or a group */ - const removeProfilePicture = async(jid: string) => { + const removeProfilePicture = async (jid: string) => { let targetJid - if(!jid) { - throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update') + if (!jid) { + throw new Boom( + 'Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update' + ) } - if(jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) { + if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me!.id)) { targetJid = jidNormalizedUser(jid) // in case it is someone other than us } @@ -274,7 +312,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } /** update the profile status for yourself */ - const updateProfileStatus = async(status: string) => { + const updateProfileStatus = async (status: string) => { await query({ tag: 'iq', attrs: { @@ -292,11 +330,11 @@ export const makeChatsSocket = (config: SocketConfig) => { }) } - const updateProfileName = async(name: string) => { + const updateProfileName = async (name: string) => { await chatModify({ pushNameSetting: name }, '') } - const fetchBlocklist = async() => { + const fetchBlocklist = async () => { const result = await query({ tag: 'iq', attrs: { @@ -307,11 +345,10 @@ export const makeChatsSocket = (config: SocketConfig) => { }) const listNode = getBinaryNodeChild(result, 'list') - return getBinaryNodeChildren(listNode, 'item') - .map(n => n.attrs.jid) + return getBinaryNodeChildren(listNode, 'item').map(n => n.attrs.jid) } - const updateBlockStatus = async(jid: string, action: 'block' | 'unblock') => { + const updateBlockStatus = async (jid: string, action: 'block' | 'unblock') => { await query({ tag: 'iq', attrs: { @@ -331,7 +368,7 @@ export const makeChatsSocket = (config: SocketConfig) => { }) } - const getBusinessProfile = async(jid: string): Promise => { + const getBusinessProfile = async (jid: string): Promise => { const results = await query({ tag: 'iq', attrs: { @@ -339,19 +376,23 @@ export const makeChatsSocket = (config: SocketConfig) => { xmlns: 'w:biz', type: 'get' }, - content: [{ - tag: 'business_profile', - attrs: { v: '244' }, - content: [{ - tag: 'profile', - attrs: { jid } - }] - }] + content: [ + { + tag: 'business_profile', + attrs: { v: '244' }, + content: [ + { + tag: 'profile', + attrs: { jid } + } + ] + } + ] }) const profileNode = getBinaryNodeChild(results, 'business_profile') const profiles = getBinaryNodeChild(profileNode, 'profile') - if(profiles) { + if (profiles) { const address = getBinaryNodeChild(profiles, 'address') const description = getBinaryNodeChild(profiles, 'description') const website = getBinaryNodeChild(profiles, 'website') @@ -369,15 +410,15 @@ export const makeChatsSocket = (config: SocketConfig) => { website: websiteStr ? [websiteStr] : [], email: email?.content?.toString(), category: category?.content?.toString(), - 'business_hours': { + business_hours: { timezone: businessHours?.attrs?.timezone, - 'business_config': businessHoursConfig?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) + business_config: businessHoursConfig?.map(({ attrs }) => attrs as unknown as WABusinessHoursConfig) } } } } - const cleanDirtyBits = async(type: 'account_sync' | 'groups', fromTimestamp?: number | string) => { + const cleanDirtyBits = async (type: 'account_sync' | 'groups', fromTimestamp?: number | string) => { logger.info({ fromTimestamp }, 'clean dirty bits ' + type) await sendNode({ tag: 'iq', @@ -385,14 +426,14 @@ export const makeChatsSocket = (config: SocketConfig) => { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', - id: generateMessageTag(), + id: generateMessageTag() }, content: [ { tag: 'clean', attrs: { type, - ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null), + ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } } ] @@ -413,30 +454,30 @@ export const makeChatsSocket = (config: SocketConfig) => { } } - const resyncAppState = ev.createBufferedFunction(async(collections: readonly WAPatchName[], isInitialSync: boolean) => { - // we use this to determine which events to fire - // otherwise when we resync from scratch -- all notifications will fire - const initialVersionMap: { [T in WAPatchName]?: number } = {} - const globalMutationMap: ChatMutationMap = {} + const resyncAppState = ev.createBufferedFunction( + async (collections: readonly WAPatchName[], isInitialSync: boolean) => { + // we use this to determine which events to fire + // otherwise when we resync from scratch -- all notifications will fire + const initialVersionMap: { [T in WAPatchName]?: number } = {} + const globalMutationMap: ChatMutationMap = {} - await authState.keys.transaction( - async() => { + await authState.keys.transaction(async () => { const collectionsToHandle = new Set(collections) // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from const attemptsMap: { [T in WAPatchName]?: number } = {} // keep executing till all collections are done // sometimes a single patch request will not return all the patches (God knows why) // so we fetch till they're all done (this is determined by the "has_more_patches" flag) - while(collectionsToHandle.size) { + while (collectionsToHandle.size) { const states = {} as { [T in WAPatchName]: LTHashState } const nodes: BinaryNode[] = [] - for(const name of collectionsToHandle) { + for (const name of collectionsToHandle) { const result = await authState.keys.get('app-state-sync-version', [name]) let state = result[name] - if(state) { - if(typeof initialVersionMap[name] === 'undefined') { + if (state) { + if (typeof initialVersionMap[name] === 'undefined') { initialVersionMap[name] = state.version } } else { @@ -453,7 +494,7 @@ export const makeChatsSocket = (config: SocketConfig) => { name, version: state.version.toString(), // return snapshot if being synced from scratch - 'return_snapshot': (!state.version).toString() + return_snapshot: (!state.version).toString() } }) } @@ -476,11 +517,11 @@ export const makeChatsSocket = (config: SocketConfig) => { // extract from binary node const decoded = await extractSyncdPatches(result, config?.options) - for(const key in decoded) { + for (const key in decoded) { const name = key as WAPatchName const { patches, hasMorePatches, snapshot } = decoded[name] try { - if(snapshot) { + if (snapshot) { const { state: newState, mutationMap } = await decodeSyncdSnapshot( name, snapshot, @@ -497,7 +538,7 @@ export const makeChatsSocket = (config: SocketConfig) => { } // only process if there are syncd patches - if(patches.length) { + if (patches.length) { const { state: newState, mutationMap } = await decodePatches( name, patches, @@ -517,17 +558,19 @@ export const makeChatsSocket = (config: SocketConfig) => { Object.assign(globalMutationMap, mutationMap) } - if(hasMorePatches) { + if (hasMorePatches) { logger.info(`${name} has more patches...`) - } else { // collection is done with sync + } else { + // collection is done with sync collectionsToHandle.delete(name) } - } catch(error) { + } catch (error) { // if retry attempts overshoot // or key not found - const isIrrecoverableError = attemptsMap[name]! >= MAX_SYNC_ATTEMPTS - || error.output?.statusCode === 404 - || error.name === 'TypeError' + const isIrrecoverableError = + attemptsMap[name]! >= MAX_SYNC_ATTEMPTS || + error.output?.statusCode === 404 || + error.name === 'TypeError' logger.info( { name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}` @@ -536,49 +579,50 @@ export const makeChatsSocket = (config: SocketConfig) => { // increment number of retries attemptsMap[name] = (attemptsMap[name] || 0) + 1 - if(isIrrecoverableError) { + if (isIrrecoverableError) { // stop retrying collectionsToHandle.delete(name) } } } } - } - ) + }) - const { onMutation } = newAppStateChunkHandler(isInitialSync) - for(const key in globalMutationMap) { - onMutation(globalMutationMap[key]) + const { onMutation } = newAppStateChunkHandler(isInitialSync) + for (const key in globalMutationMap) { + onMutation(globalMutationMap[key]) + } } - }) + ) /** * fetch the profile picture of a user/group * type = "preview" for a low res picture * type = "image for the high res picture" */ - const profilePictureUrl = async(jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => { + const profilePictureUrl = async (jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => { jid = jidNormalizedUser(jid) - const result = await query({ - tag: 'iq', - attrs: { - target: jid, - to: S_WHATSAPP_NET, - type: 'get', - xmlns: 'w:profile:picture' + const result = await query( + { + tag: 'iq', + attrs: { + target: jid, + to: S_WHATSAPP_NET, + type: 'get', + xmlns: 'w:profile:picture' + }, + content: [{ tag: 'picture', attrs: { type, query: 'url' } }] }, - content: [ - { tag: 'picture', attrs: { type, query: 'url' } } - ] - }, timeoutMs) + timeoutMs + ) const child = getBinaryNodeChild(result, 'picture') return child?.attrs?.url } - const sendPresenceUpdate = async(type: WAPresence, toJid?: string) => { + const sendPresenceUpdate = async (type: WAPresence, toJid?: string) => { const me = authState.creds.me! - if(type === 'available' || type === 'unavailable') { - if(!me.name) { + if (type === 'available' || type === 'unavailable') { + if (!me.name) { logger.warn('no name present, ignoring presence update request...') return } @@ -597,7 +641,7 @@ export const makeChatsSocket = (config: SocketConfig) => { tag: 'chatstate', attrs: { from: me.id, - to: toJid!, + to: toJid! }, content: [ { @@ -613,7 +657,7 @@ export const makeChatsSocket = (config: SocketConfig) => { * @param toJid the jid to subscribe to * @param tcToken token for subscription, use if present */ - const presenceSubscribe = (toJid: string, tcToken?: Buffer) => ( + const presenceSubscribe = (toJid: string, tcToken?: Buffer) => sendNode({ tag: 'presence', attrs: { @@ -623,38 +667,37 @@ export const makeChatsSocket = (config: SocketConfig) => { }, content: tcToken ? [ - { - tag: 'tctoken', - attrs: {}, - content: tcToken - } - ] + { + tag: 'tctoken', + attrs: {}, + content: tcToken + } + ] : undefined }) - ) const handlePresenceUpdate = ({ tag, attrs, content }: BinaryNode) => { let presence: PresenceData | undefined const jid = attrs.from const participant = attrs.participant || attrs.from - if(shouldIgnoreJid(jid) && jid !== '@s.whatsapp.net') { + if (shouldIgnoreJid(jid) && jid !== '@s.whatsapp.net') { return } - if(tag === 'presence') { + if (tag === 'presence') { presence = { lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available', lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined } - } else if(Array.isArray(content)) { + } else if (Array.isArray(content)) { const [firstChild] = content let type = firstChild.tag as WAPresence - if(type === 'paused') { + if (type === 'paused') { type = 'available' } - if(firstChild.attrs?.media === 'audio') { + if (firstChild.attrs?.media === 'audio') { type = 'recording' } @@ -663,119 +706,113 @@ export const makeChatsSocket = (config: SocketConfig) => { logger.error({ tag, attrs, content }, 'recv invalid presence node') } - if(presence) { + if (presence) { ev.emit('presence.update', { id: jid, presences: { [participant]: presence } }) } } - const appPatch = async(patchCreate: WAPatchCreate) => { + const appPatch = async (patchCreate: WAPatchCreate) => { const name = patchCreate.type const myAppStateKeyId = authState.creds.myAppStateKeyId - if(!myAppStateKeyId) { + if (!myAppStateKeyId) { throw new Boom('App state key not present!', { statusCode: 400 }) } let initial: LTHashState - let encodeResult: { patch: proto.ISyncdPatch, state: LTHashState } + let encodeResult: { patch: proto.ISyncdPatch; state: LTHashState } - await processingMutex.mutex( - async() => { - await authState.keys.transaction( - async() => { - logger.debug({ patch: patchCreate }, 'applying app patch') + await processingMutex.mutex(async () => { + await authState.keys.transaction(async () => { + logger.debug({ patch: patchCreate }, 'applying app patch') - await resyncAppState([name], false) + await resyncAppState([name], false) - const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]) - initial = currentSyncVersion || newLTHashState() + const { [name]: currentSyncVersion } = await authState.keys.get('app-state-sync-version', [name]) + initial = currentSyncVersion || newLTHashState() - encodeResult = await encodeSyncdPatch( - patchCreate, - myAppStateKeyId, - initial, - getAppStateSyncKey, - ) - const { patch, state } = encodeResult + encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey) + const { patch, state } = encodeResult - const node: BinaryNode = { - tag: 'iq', - attrs: { - to: S_WHATSAPP_NET, - type: 'set', - xmlns: 'w:sync:app:state' - }, + const node: BinaryNode = { + tag: 'iq', + attrs: { + to: S_WHATSAPP_NET, + type: 'set', + xmlns: 'w:sync:app:state' + }, + content: [ + { + tag: 'sync', + attrs: {}, content: [ { - tag: 'sync', - attrs: {}, + tag: 'collection', + attrs: { + name, + version: (state.version - 1).toString(), + return_snapshot: 'false' + }, content: [ { - tag: 'collection', - attrs: { - name, - version: (state.version - 1).toString(), - 'return_snapshot': 'false' - }, - content: [ - { - tag: 'patch', - attrs: {}, - content: proto.SyncdPatch.encode(patch).finish() - } - ] + tag: 'patch', + attrs: {}, + content: proto.SyncdPatch.encode(patch).finish() } ] } ] } - await query(node) + ] + } + await query(node) - await authState.keys.set({ 'app-state-sync-version': { [name]: state } }) - } - ) - } - ) + await authState.keys.set({ 'app-state-sync-version': { [name]: state } }) + }) + }) - if(config.emitOwnEvents) { + if (config.emitOwnEvents) { const { onMutation } = newAppStateChunkHandler(false) const { mutationMap } = await decodePatches( name, - [{ ...encodeResult!.patch, version: { version: encodeResult!.state.version }, }], + [{ ...encodeResult!.patch, version: { version: encodeResult!.state.version } }], initial!, getAppStateSyncKey, config.options, undefined, - logger, + logger ) - for(const key in mutationMap) { + for (const key in mutationMap) { onMutation(mutationMap[key]) } } } /** sending non-abt props may fix QR scan fail if server expects */ - const fetchProps = async() => { + const fetchProps = async () => { const resultNode = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'w', - type: 'get', + type: 'get' }, content: [ - { tag: 'props', attrs: { - protocol: '2', - hash: authState?.creds?.lastPropHash || '' - } } + { + tag: 'props', + attrs: { + protocol: '2', + hash: authState?.creds?.lastPropHash || '' + } + } ] }) const propsNode = getBinaryNodeChild(resultNode, 'props') - let props: { [_: string]: string } = {} - if(propsNode) { - if(propsNode.attrs?.hash) { // on some clients, the hash is returning as undefined + if (propsNode) { + if (propsNode.attrs?.hash) { + // on some clients, the hash is returning as undefined authState.creds.lastPropHash = propsNode?.attrs?.hash ev.emit('creds.update', authState.creds) } @@ -792,7 +829,7 @@ export const makeChatsSocket = (config: SocketConfig) => { * modify a chat -- mark unread, read etc. * lastMessages must be sorted in reverse chronologically * requires the last messages till the last message received; required for archive & unread - */ + */ const chatModify = (mod: ChatModification, jid: string) => { const patch = chatModificationToAppPatch(mod, jid) return appPatch(patch) @@ -801,155 +838,157 @@ export const makeChatsSocket = (config: SocketConfig) => { /** * Star or Unstar a message */ - const star = (jid: string, messages: { id: string, fromMe?: boolean }[], star: boolean) => { - return chatModify({ - star: { - messages, - star - } - }, jid) + const star = (jid: string, messages: { id: string; fromMe?: boolean }[], star: boolean) => { + return chatModify( + { + star: { + messages, + star + } + }, + jid + ) } /** * Adds label */ const addLabel = (jid: string, labels: LabelActionBody) => { - return chatModify({ - addLabel: { - ...labels - } - }, jid) + return chatModify( + { + addLabel: { + ...labels + } + }, + jid + ) } /** * Adds label for the chats */ const addChatLabel = (jid: string, labelId: string) => { - return chatModify({ - addChatLabel: { - labelId - } - }, jid) + return chatModify( + { + addChatLabel: { + labelId + } + }, + jid + ) } /** * Removes label for the chat */ const removeChatLabel = (jid: string, labelId: string) => { - return chatModify({ - removeChatLabel: { - labelId - } - }, jid) + return chatModify( + { + removeChatLabel: { + labelId + } + }, + jid + ) } /** * Adds label for the message */ const addMessageLabel = (jid: string, messageId: string, labelId: string) => { - return chatModify({ - addMessageLabel: { - messageId, - labelId - } - }, jid) + return chatModify( + { + addMessageLabel: { + messageId, + labelId + } + }, + jid + ) } /** * Removes label for the message */ const removeMessageLabel = (jid: string, messageId: string, labelId: string) => { - return chatModify({ - removeMessageLabel: { - messageId, - labelId - } - }, jid) + return chatModify( + { + removeMessageLabel: { + messageId, + labelId + } + }, + jid + ) } /** * queries need to be fired on connection open * help ensure parity with WA Web * */ - const executeInitQueries = async() => { - await Promise.all([ - fetchProps(), - fetchBlocklist(), - fetchPrivacySettings(), - ]) + const executeInitQueries = async () => { + await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()]) } - const upsertMessage = ev.createBufferedFunction(async(msg: WAMessage, type: MessageUpsertType) => { + const upsertMessage = ev.createBufferedFunction(async (msg: WAMessage, type: MessageUpsertType) => { ev.emit('messages.upsert', { messages: [msg], type }) - if(!!msg.pushName) { - let jid = msg.key.fromMe ? authState.creds.me!.id : (msg.key.participant || msg.key.remoteJid) + if (!!msg.pushName) { + let jid = msg.key.fromMe ? authState.creds.me!.id : msg.key.participant || msg.key.remoteJid jid = jidNormalizedUser(jid!) - if(!msg.key.fromMe) { + if (!msg.key.fromMe) { ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName! }]) } // update our pushname too - if(msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) { + if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) { ev.emit('creds.update', { me: { ...authState.creds.me!, name: msg.pushName } }) } } const historyMsg = getHistoryMsg(msg.message!) const shouldProcessHistoryMsg = historyMsg - ? ( - shouldSyncHistoryMessage(historyMsg) - && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType!) - ) + ? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType!) : false - if(historyMsg && !authState.creds.myAppStateKeyId) { + if (historyMsg && !authState.creds.myAppStateKeyId) { logger.warn('skipping app state sync, as myAppStateKeyId is not set') pendingAppStateSync = true } await Promise.all([ - (async() => { - if( - historyMsg - && authState.creds.myAppStateKeyId - ) { + (async () => { + if (historyMsg && authState.creds.myAppStateKeyId) { pendingAppStateSync = false await doAppStateSync() } })(), - processMessage( - msg, - { - shouldProcessHistoryMsg, - placeholderResendCache, - ev, - creds: authState.creds, - keyStore: authState.keys, - logger, - options: config.options, - } - ) + processMessage(msg, { + shouldProcessHistoryMsg, + placeholderResendCache, + ev, + creds: authState.creds, + keyStore: authState.keys, + logger, + options: config.options + }) ]) - if( - msg.message?.protocolMessage?.appStateSyncKeyShare - && pendingAppStateSync - ) { + if (msg.message?.protocolMessage?.appStateSyncKeyShare && pendingAppStateSync) { await doAppStateSync() pendingAppStateSync = false } async function doAppStateSync() { - if(!authState.creds.accountSyncCounter) { + if (!authState.creds.accountSyncCounter) { logger.info('doing initial app state sync') await resyncAppState(ALL_WA_PATCH_NAMES, true) const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1 ev.emit('creds.update', { accountSyncCounter }) - if(needToFlushWithAppStateSync) { + if (needToFlushWithAppStateSync) { logger.debug('flushing with app state sync') ev.flush() } @@ -960,51 +999,49 @@ export const makeChatsSocket = (config: SocketConfig) => { ws.on('CB:presence', handlePresenceUpdate) ws.on('CB:chatstate', handlePresenceUpdate) - ws.on('CB:ib,,dirty', async(node: BinaryNode) => { + ws.on('CB:ib,,dirty', async (node: BinaryNode) => { const { attrs } = getBinaryNodeChild(node, 'dirty')! const type = attrs.type switch (type) { - case 'account_sync': - if(attrs.timestamp) { - let { lastAccountSyncTimestamp } = authState.creds - if(lastAccountSyncTimestamp) { - await cleanDirtyBits('account_sync', lastAccountSyncTimestamp) + case 'account_sync': + if (attrs.timestamp) { + let { lastAccountSyncTimestamp } = authState.creds + if (lastAccountSyncTimestamp) { + await cleanDirtyBits('account_sync', lastAccountSyncTimestamp) + } + + lastAccountSyncTimestamp = +attrs.timestamp + ev.emit('creds.update', { lastAccountSyncTimestamp }) } - lastAccountSyncTimestamp = +attrs.timestamp - ev.emit('creds.update', { lastAccountSyncTimestamp }) - } - - break - case 'groups': - // handled in groups.ts - break - default: - logger.info({ node }, 'received unknown sync') - break + break + case 'groups': + // handled in groups.ts + break + default: + logger.info({ node }, 'received unknown sync') + break } }) ev.on('connection.update', ({ connection, receivedPendingNotifications }) => { - if(connection === 'open') { - if(fireInitQueries) { - executeInitQueries() - .catch( - error => onUnexpectedError(error, 'init queries') - ) + if (connection === 'open') { + if (fireInitQueries) { + executeInitQueries().catch(error => onUnexpectedError(error, 'init queries')) } - sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable') - .catch( - error => onUnexpectedError(error, 'presence update requests') - ) + sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error => + onUnexpectedError(error, 'presence update requests') + ) } - if(receivedPendingNotifications && // if we don't have the app state key + if ( + receivedPendingNotifications && // if we don't have the app state key // we keep buffering events until we finally have // the key and can sync the messages // todo scrutinize - !authState.creds?.myAppStateKeyId) { + !authState.creds?.myAppStateKeyId + ) { ev.buffer() needToFlushWithAppStateSync = true } diff --git a/src/Socket/groups.ts b/src/Socket/groups.ts index 2c576f4..8c9179a 100644 --- a/src/Socket/groups.ts +++ b/src/Socket/groups.ts @@ -1,62 +1,70 @@ import { proto } from '../../WAProto' -import { GroupMetadata, GroupParticipant, ParticipantAction, SocketConfig, WAMessageKey, WAMessageStubType } from '../Types' +import { + GroupMetadata, + GroupParticipant, + ParticipantAction, + SocketConfig, + WAMessageKey, + WAMessageStubType +} from '../Types' import { generateMessageIDV2, unixTimestampSeconds } from '../Utils' -import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, jidEncode, jidNormalizedUser } from '../WABinary' +import { + BinaryNode, + getBinaryNodeChild, + getBinaryNodeChildren, + getBinaryNodeChildString, + jidEncode, + jidNormalizedUser +} from '../WABinary' import { makeChatsSocket } from './chats' export const makeGroupsSocket = (config: SocketConfig) => { const sock = makeChatsSocket(config) const { authState, ev, query, upsertMessage } = sock - const groupQuery = async(jid: string, type: 'get' | 'set', content: BinaryNode[]) => ( + const groupQuery = async (jid: string, type: 'get' | 'set', content: BinaryNode[]) => query({ tag: 'iq', attrs: { type, xmlns: 'w:g2', - to: jid, + to: jid }, content }) - ) - const groupMetadata = async(jid: string) => { - const result = await groupQuery( - jid, - 'get', - [ { tag: 'query', attrs: { request: 'interactive' } } ] - ) + const groupMetadata = async (jid: string) => { + const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]) return extractGroupMetadata(result) } - - const groupFetchAllParticipating = async() => { + const groupFetchAllParticipating = async () => { const result = await query({ tag: 'iq', attrs: { to: '@g.us', xmlns: 'w:g2', - type: 'get', + type: 'get' }, content: [ { tag: 'participating', - attrs: { }, + attrs: {}, content: [ - { tag: 'participants', attrs: { } }, - { tag: 'description', attrs: { } } + { tag: 'participants', attrs: {} }, + { tag: 'description', attrs: {} } ] } ] }) - const data: { [_: string]: GroupMetadata } = { } + const data: { [_: string]: GroupMetadata } = {} const groupsChild = getBinaryNodeChild(result, 'groups') - if(groupsChild) { + if (groupsChild) { const groups = getBinaryNodeChildren(groupsChild, 'group') - for(const groupNode of groups) { + for (const groupNode of groups) { const meta = extractGroupMetadata({ tag: 'result', - attrs: { }, + attrs: {}, content: [groupNode] }) data[meta.id] = meta @@ -68,9 +76,9 @@ export const makeGroupsSocket = (config: SocketConfig) => { return data } - sock.ws.on('CB:ib,,dirty', async(node: BinaryNode) => { + sock.ws.on('CB:ib,,dirty', async (node: BinaryNode) => { const { attrs } = getBinaryNodeChild(node, 'dirty')! - if(attrs.type !== 'groups') { + if (attrs.type !== 'groups') { return } @@ -81,89 +89,69 @@ export const makeGroupsSocket = (config: SocketConfig) => { return { ...sock, groupMetadata, - groupCreate: async(subject: string, participants: string[]) => { + groupCreate: async (subject: string, participants: string[]) => { const key = generateMessageIDV2() - const result = await groupQuery( - '@g.us', - 'set', - [ - { - tag: 'create', - attrs: { - subject, - key - }, - content: participants.map(jid => ({ - tag: 'participant', - attrs: { jid } - })) - } - ] - ) + const result = await groupQuery('@g.us', 'set', [ + { + tag: 'create', + attrs: { + subject, + key + }, + content: participants.map(jid => ({ + tag: 'participant', + attrs: { jid } + })) + } + ]) return extractGroupMetadata(result) }, - groupLeave: async(id: string) => { - await groupQuery( - '@g.us', - 'set', - [ - { - tag: 'leave', - attrs: { }, - content: [ - { tag: 'group', attrs: { id } } - ] - } - ] - ) + groupLeave: async (id: string) => { + await groupQuery('@g.us', 'set', [ + { + tag: 'leave', + attrs: {}, + content: [{ tag: 'group', attrs: { id } }] + } + ]) }, - groupUpdateSubject: async(jid: string, subject: string) => { - await groupQuery( - jid, - 'set', - [ - { - tag: 'subject', - attrs: { }, - content: Buffer.from(subject, 'utf-8') - } - ] - ) + groupUpdateSubject: async (jid: string, subject: string) => { + await groupQuery(jid, 'set', [ + { + tag: 'subject', + attrs: {}, + content: Buffer.from(subject, 'utf-8') + } + ]) }, - groupRequestParticipantsList: async(jid: string) => { - const result = await groupQuery( - jid, - 'get', - [ - { - tag: 'membership_approval_requests', - attrs: {} - } - ] - ) + groupRequestParticipantsList: async (jid: string) => { + const result = await groupQuery(jid, 'get', [ + { + tag: 'membership_approval_requests', + attrs: {} + } + ]) const node = getBinaryNodeChild(result, 'membership_approval_requests') const participants = getBinaryNodeChildren(node, 'membership_approval_request') return participants.map(v => v.attrs) }, - groupRequestParticipantsUpdate: async(jid: string, participants: string[], action: 'approve' | 'reject') => { - const result = await groupQuery( - jid, - 'set', - [{ + groupRequestParticipantsUpdate: async (jid: string, participants: string[], action: 'approve' | 'reject') => { + const result = await groupQuery(jid, 'set', [ + { tag: 'membership_requests_action', attrs: {}, - content: [ + content: [ { tag: action, - attrs: { }, + attrs: {}, content: participants.map(jid => ({ tag: 'participant', attrs: { jid } })) } ] - }] - ) + } + ]) const node = getBinaryNodeChild(result, 'membership_requests_action') const nodeAction = getBinaryNodeChild(node, action) const participantsAffected = getBinaryNodeChildren(nodeAction, 'participant') @@ -171,63 +159,49 @@ export const makeGroupsSocket = (config: SocketConfig) => { return { status: p.attrs.error || '200', jid: p.attrs.jid } }) }, - groupParticipantsUpdate: async( - jid: string, - participants: string[], - action: ParticipantAction - ) => { - const result = await groupQuery( - jid, - 'set', - [ - { - tag: action, - attrs: { }, - content: participants.map(jid => ({ - tag: 'participant', - attrs: { jid } - })) - } - ] - ) + groupParticipantsUpdate: async (jid: string, participants: string[], action: ParticipantAction) => { + const result = await groupQuery(jid, 'set', [ + { + tag: action, + attrs: {}, + content: participants.map(jid => ({ + tag: 'participant', + attrs: { jid } + })) + } + ]) const node = getBinaryNodeChild(result, action) const participantsAffected = getBinaryNodeChildren(node, 'participant') return participantsAffected.map(p => { return { status: p.attrs.error || '200', jid: p.attrs.jid, content: p } }) }, - groupUpdateDescription: async(jid: string, description?: string) => { + groupUpdateDescription: async (jid: string, description?: string) => { const metadata = await groupMetadata(jid) const prev = metadata.descId ?? null - await groupQuery( - jid, - 'set', - [ - { - tag: 'description', - attrs: { - ...(description ? { id: generateMessageIDV2() } : { delete: 'true' }), - ...(prev ? { prev } : {}) - }, - content: description ? [ - { tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') } - ] : undefined - } - ] - ) + await groupQuery(jid, 'set', [ + { + tag: 'description', + attrs: { + ...(description ? { id: generateMessageIDV2() } : { delete: 'true' }), + ...(prev ? { prev } : {}) + }, + content: description ? [{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }] : undefined + } + ]) }, - groupInviteCode: async(jid: string) => { + groupInviteCode: async (jid: string) => { const result = await groupQuery(jid, 'get', [{ tag: 'invite', attrs: {} }]) const inviteNode = getBinaryNodeChild(result, 'invite') return inviteNode?.attrs.code }, - groupRevokeInvite: async(jid: string) => { + groupRevokeInvite: async (jid: string) => { const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }]) const inviteNode = getBinaryNodeChild(result, 'invite') return inviteNode?.attrs.code }, - groupAcceptInvite: async(code: string) => { + groupAcceptInvite: async (code: string) => { const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }]) const result = getBinaryNodeChild(results, 'group') return result?.attrs.jid @@ -239,8 +213,10 @@ export const makeGroupsSocket = (config: SocketConfig) => { * @param invitedJid jid of person you invited * @returns true if successful */ - groupRevokeInviteV4: async(groupJid: string, invitedJid: string) => { - const result = await groupQuery(groupJid, 'set', [{ tag: 'revoke', attrs: {}, content: [{ tag: 'participant', attrs: { jid: invitedJid } }] }]) + groupRevokeInviteV4: async (groupJid: string, invitedJid: string) => { + const result = await groupQuery(groupJid, 'set', [ + { tag: 'revoke', attrs: {}, content: [{ tag: 'participant', attrs: { jid: invitedJid } }] } + ]) return !!result }, @@ -249,87 +225,90 @@ export const makeGroupsSocket = (config: SocketConfig) => { * @param key the key of the invite message, or optionally only provide the jid of the person who sent the invite * @param inviteMessage the message to accept */ - groupAcceptInviteV4: ev.createBufferedFunction(async(key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => { - key = typeof key === 'string' ? { remoteJid: key } : key - const results = await groupQuery(inviteMessage.groupJid!, 'set', [{ - tag: 'accept', - attrs: { - code: inviteMessage.inviteCode!, - expiration: inviteMessage.inviteExpiration!.toString(), - admin: key.remoteJid! - } - }]) - - // if we have the full message key - // update the invite message to be expired - if(key.id) { - // create new invite message that is expired - inviteMessage = proto.Message.GroupInviteMessage.fromObject(inviteMessage) - inviteMessage.inviteExpiration = 0 - inviteMessage.inviteCode = '' - ev.emit('messages.update', [ + groupAcceptInviteV4: ev.createBufferedFunction( + async (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => { + key = typeof key === 'string' ? { remoteJid: key } : key + const results = await groupQuery(inviteMessage.groupJid!, 'set', [ { - key, - update: { - message: { - groupInviteMessage: inviteMessage - } + tag: 'accept', + attrs: { + code: inviteMessage.inviteCode!, + expiration: inviteMessage.inviteExpiration!.toString(), + admin: key.remoteJid! } } ]) - } - // generate the group add message - await upsertMessage( - { - key: { - remoteJid: inviteMessage.groupJid, - id: generateMessageIDV2(sock.user?.id), - fromMe: false, + // if we have the full message key + // update the invite message to be expired + if (key.id) { + // create new invite message that is expired + inviteMessage = proto.Message.GroupInviteMessage.fromObject(inviteMessage) + inviteMessage.inviteExpiration = 0 + inviteMessage.inviteCode = '' + ev.emit('messages.update', [ + { + key, + update: { + message: { + groupInviteMessage: inviteMessage + } + } + } + ]) + } + + // generate the group add message + await upsertMessage( + { + key: { + remoteJid: inviteMessage.groupJid, + id: generateMessageIDV2(sock.user?.id), + fromMe: false, + participant: key.remoteJid + }, + messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD, + messageStubParameters: [authState.creds.me!.id], participant: key.remoteJid, + messageTimestamp: unixTimestampSeconds() }, - messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD, - messageStubParameters: [ - authState.creds.me!.id - ], - participant: key.remoteJid, - messageTimestamp: unixTimestampSeconds() - }, - 'notify' - ) + 'notify' + ) - return results.attrs.from - }), - groupGetInviteInfo: async(code: string) => { + return results.attrs.from + } + ), + groupGetInviteInfo: async (code: string) => { const results = await groupQuery('@g.us', 'get', [{ tag: 'invite', attrs: { code } }]) return extractGroupMetadata(results) }, - groupToggleEphemeral: async(jid: string, ephemeralExpiration: number) => { - const content: BinaryNode = ephemeralExpiration ? - { tag: 'ephemeral', attrs: { expiration: ephemeralExpiration.toString() } } : - { tag: 'not_ephemeral', attrs: { } } + groupToggleEphemeral: async (jid: string, ephemeralExpiration: number) => { + const content: BinaryNode = ephemeralExpiration + ? { tag: 'ephemeral', attrs: { expiration: ephemeralExpiration.toString() } } + : { tag: 'not_ephemeral', attrs: {} } await groupQuery(jid, 'set', [content]) }, - groupSettingUpdate: async(jid: string, setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked') => { - await groupQuery(jid, 'set', [ { tag: setting, attrs: { } } ]) + groupSettingUpdate: async (jid: string, setting: 'announcement' | 'not_announcement' | 'locked' | 'unlocked') => { + await groupQuery(jid, 'set', [{ tag: setting, attrs: {} }]) }, - groupMemberAddMode: async(jid: string, mode: 'admin_add' | 'all_member_add') => { - await groupQuery(jid, 'set', [ { tag: 'member_add_mode', attrs: { }, content: mode } ]) + groupMemberAddMode: async (jid: string, mode: 'admin_add' | 'all_member_add') => { + await groupQuery(jid, 'set', [{ tag: 'member_add_mode', attrs: {}, content: mode }]) }, - groupJoinApprovalMode: async(jid: string, mode: 'on' | 'off') => { - await groupQuery(jid, 'set', [ { tag: 'membership_approval_mode', attrs: { }, content: [ { tag: 'group_join', attrs: { state: mode } } ] } ]) + groupJoinApprovalMode: async (jid: string, mode: 'on' | 'off') => { + await groupQuery(jid, 'set', [ + { tag: 'membership_approval_mode', attrs: {}, content: [{ tag: 'group_join', attrs: { state: mode } }] } + ]) }, groupFetchAllParticipating } } - export const extractGroupMetadata = (result: BinaryNode) => { const group = getBinaryNodeChild(result, 'group')! const descChild = getBinaryNodeChild(group, 'description') let desc: string | undefined let descId: string | undefined - if(descChild) { + if (descChild) { desc = getBinaryNodeChildString(descChild, 'body') descId = descChild.attrs.id } @@ -355,14 +334,12 @@ export const extractGroupMetadata = (result: BinaryNode) => { isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'), joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'), memberAddMode, - participants: getBinaryNodeChildren(group, 'participant').map( - ({ attrs }) => { - return { - id: attrs.jid, - admin: (attrs.type || null) as GroupParticipant['admin'], - } + participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => { + return { + id: attrs.jid, + admin: (attrs.type || null) as GroupParticipant['admin'] } - ), + }), ephemeralDuration: eph ? +eph : undefined } return metadata diff --git a/src/Socket/index.ts b/src/Socket/index.ts index 30d8871..3b07852 100644 --- a/src/Socket/index.ts +++ b/src/Socket/index.ts @@ -3,11 +3,10 @@ import { UserFacingSocketConfig } from '../Types' import { makeBusinessSocket } from './business' // export the last socket layer -const makeWASocket = (config: UserFacingSocketConfig) => ( +const makeWASocket = (config: UserFacingSocketConfig) => makeBusinessSocket({ ...DEFAULT_CONNECTION_CONFIG, ...config }) -) -export default makeWASocket \ No newline at end of file +export default makeWASocket diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index ecda600..2a12118 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -1,11 +1,20 @@ - import NodeCache from '@cacheable/node-cache' import { Boom } from '@hapi/boom' import { randomBytes } from 'crypto' -import Long = require('long'); +import Long = require('long') import { proto } from '../../WAProto' import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults' -import { MessageReceiptType, MessageRelayOptions, MessageUserReceipt, SocketConfig, WACallEvent, WAMessageKey, WAMessageStatus, WAMessageStubType, WAPatchName } from '../Types' +import { + MessageReceiptType, + MessageRelayOptions, + MessageUserReceipt, + SocketConfig, + WACallEvent, + WAMessageKey, + WAMessageStatus, + WAMessageStubType, + WAPatchName +} from '../Types' import { aesDecryptCTR, aesEncryptGCM, @@ -21,7 +30,8 @@ import { getCallStatusFromNode, getHistoryMsg, getNextPreKeys, - getStatusFromReceiptType, hkdf, + getStatusFromReceiptType, + hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, @@ -37,7 +47,8 @@ import { getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, - isJidGroup, isJidStatusBroadcast, + isJidGroup, + isJidStatusBroadcast, isJidUser, jidDecode, jidNormalizedUser, @@ -47,13 +58,7 @@ import { extractGroupMetadata } from './groups' import { makeMessagesSocket } from './messages-send' export const makeMessagesRecvSocket = (config: SocketConfig) => { - const { - logger, - retryRequestDelayMs, - maxMsgRetryCount, - getMessage, - shouldIgnoreJid - } = config + const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config const sock = makeMessagesSocket(config) const { ev, @@ -70,29 +75,35 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { relayMessage, sendReceipt, uploadPreKeys, - sendPeerDataOperationMessage, + sendPeerDataOperationMessage } = sock /** this mutex ensures that each retryRequest will wait for the previous one to finish */ const retryMutex = makeMutex() - const msgRetryCache = config.msgRetryCounterCache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour - useClones: false - }) - const callOfferCache = config.callOfferCache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins - useClones: false - }) + const msgRetryCache = + config.msgRetryCounterCache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour + useClones: false + }) + const callOfferCache = + config.callOfferCache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins + useClones: false + }) - const placeholderResendCache = config.placeholderResendCache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour - useClones: false - }) + const placeholderResendCache = + config.placeholderResendCache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour + useClones: false + }) let sendActiveReceipts = false - const sendMessageAck = async({ tag, attrs, content }: BinaryNode, errorCode?: number) => { + const sendMessageAck = async ({ tag, attrs, content }: BinaryNode, errorCode?: number) => { const stanza: BinaryNode = { tag: 'ack', attrs: { @@ -102,23 +113,26 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } } - if(!!errorCode) { - stanza.attrs.error = errorCode.toString() + if (!!errorCode) { + stanza.attrs.error = errorCode.toString() } - if(!!attrs.participant) { + if (!!attrs.participant) { stanza.attrs.participant = attrs.participant } - if(!!attrs.recipient) { + if (!!attrs.recipient) { stanza.attrs.recipient = attrs.recipient } - if(!!attrs.type && (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) { + if ( + !!attrs.type && + (tag !== 'message' || getBinaryNodeChild({ tag, attrs, content }, 'unavailable') || errorCode !== 0) + ) { stanza.attrs.type = attrs.type } - if(tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) { + if (tag === 'message' && getBinaryNodeChild({ tag, attrs, content }, 'unavailable')) { stanza.attrs.from = authState.creds.me!.id } @@ -126,34 +140,36 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { await sendNode(stanza) } - const rejectCall = async(callId: string, callFrom: string) => { - const stanza: BinaryNode = ({ + const rejectCall = async (callId: string, callFrom: string) => { + const stanza: BinaryNode = { tag: 'call', attrs: { from: authState.creds.me!.id, - to: callFrom, + to: callFrom }, - content: [{ - tag: 'reject', - attrs: { - 'call-id': callId, - 'call-creator': callFrom, - count: '0', - }, - content: undefined, - }], - }) + content: [ + { + tag: 'reject', + attrs: { + 'call-id': callId, + 'call-creator': callFrom, + count: '0' + }, + content: undefined + } + ] + } await query(stanza) } - const sendRetryRequest = async(node: BinaryNode, forceIncludeKeys = false) => { + const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false) => { const { fullMessage } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '') const { key: msgKey } = fullMessage const msgId = msgKey.id! const key = `${msgId}:${msgKey?.participant}` let retryCount = msgRetryCache.get(key) || 0 - if(retryCount >= maxMsgRetryCount) { + if (retryCount >= maxMsgRetryCount) { logger.debug({ retryCount, msgId }, 'reached retry limit, clearing') msgRetryCache.del(key) return @@ -164,91 +180,89 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds - if(retryCount === 1) { + if (retryCount === 1) { //request a resend via phone const msgId = await requestPlaceholderResend(msgKey) logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`) } const deviceIdentity = encodeSignedDeviceIdentity(account!, true) - await authState.keys.transaction( - async() => { - const receipt: BinaryNode = { - tag: 'receipt', - attrs: { - id: msgId, - type: 'retry', - to: node.attrs.from - }, - content: [ - { - tag: 'retry', - attrs: { - count: retryCount.toString(), - id: node.attrs.id, - t: node.attrs.t, - v: '1' - } - }, - { - tag: 'registration', - attrs: { }, - content: encodeBigEndian(authState.creds.registrationId) + await authState.keys.transaction(async () => { + const receipt: BinaryNode = { + tag: 'receipt', + attrs: { + id: msgId, + type: 'retry', + to: node.attrs.from + }, + content: [ + { + tag: 'retry', + attrs: { + count: retryCount.toString(), + id: node.attrs.id, + t: node.attrs.t, + v: '1' } - ] - } - - if(node.attrs.recipient) { - receipt.attrs.recipient = node.attrs.recipient - } - - if(node.attrs.participant) { - receipt.attrs.participant = node.attrs.participant - } - - if(retryCount > 1 || forceIncludeKeys) { - const { update, preKeys } = await getNextPreKeys(authState, 1) - - const [keyId] = Object.keys(preKeys) - const key = preKeys[+keyId] - - const content = receipt.content! as BinaryNode[] - content.push({ - tag: 'keys', - attrs: { }, - content: [ - { tag: 'type', attrs: { }, content: Buffer.from(KEY_BUNDLE_TYPE) }, - { tag: 'identity', attrs: { }, content: identityKey.public }, - xmppPreKey(key, +keyId), - xmppSignedPreKey(signedPreKey), - { tag: 'device-identity', attrs: { }, content: deviceIdentity } - ] - }) - - ev.emit('creds.update', update) - } - - await sendNode(receipt) - - logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt') + }, + { + tag: 'registration', + attrs: {}, + content: encodeBigEndian(authState.creds.registrationId) + } + ] } - ) + + if (node.attrs.recipient) { + receipt.attrs.recipient = node.attrs.recipient + } + + if (node.attrs.participant) { + receipt.attrs.participant = node.attrs.participant + } + + if (retryCount > 1 || forceIncludeKeys) { + const { update, preKeys } = await getNextPreKeys(authState, 1) + + const [keyId] = Object.keys(preKeys) + const key = preKeys[+keyId] + + const content = receipt.content! as BinaryNode[] + content.push({ + tag: 'keys', + attrs: {}, + content: [ + { tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) }, + { tag: 'identity', attrs: {}, content: identityKey.public }, + xmppPreKey(key, +keyId), + xmppSignedPreKey(signedPreKey), + { tag: 'device-identity', attrs: {}, content: deviceIdentity } + ] + }) + + ev.emit('creds.update', update) + } + + await sendNode(receipt) + + logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt') + }) } - const handleEncryptNotification = async(node: BinaryNode) => { + const handleEncryptNotification = async (node: BinaryNode) => { const from = node.attrs.from - if(from === S_WHATSAPP_NET) { + if (from === S_WHATSAPP_NET) { const countChild = getBinaryNodeChild(node, 'count') const count = +countChild!.attrs.value const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count') - if(shouldUploadMorePreKeys) { + if (shouldUploadMorePreKeys) { await uploadPreKeys() } } else { const identityNode = getBinaryNodeChild(node, 'identity') - if(identityNode) { + if (identityNode) { logger.info({ jid: from }, 'identity changed') // not handling right now // signal will override new identity anyway @@ -258,276 +272,289 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } } - const handleGroupNotification = ( - participant: string, - child: BinaryNode, - msg: Partial - ) => { + const handleGroupNotification = (participant: string, child: BinaryNode, msg: Partial) => { const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant switch (child?.tag) { - case 'create': - const metadata = extractGroupMetadata(child) + case 'create': + const metadata = extractGroupMetadata(child) - msg.messageStubType = WAMessageStubType.GROUP_CREATE - msg.messageStubParameters = [metadata.subject] - msg.key = { participant: metadata.owner } + msg.messageStubType = WAMessageStubType.GROUP_CREATE + msg.messageStubParameters = [metadata.subject] + msg.key = { participant: metadata.owner } - ev.emit('chats.upsert', [{ - id: metadata.id, - name: metadata.subject, - conversationTimestamp: metadata.creation, - }]) - ev.emit('groups.upsert', [{ - ...metadata, - author: participant - }]) - break - case 'ephemeral': - case 'not_ephemeral': - msg.message = { - protocolMessage: { - type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING, - ephemeralExpiration: +(child.attrs.expiration || 0) + ev.emit('chats.upsert', [ + { + id: metadata.id, + name: metadata.subject, + conversationTimestamp: metadata.creation + } + ]) + ev.emit('groups.upsert', [ + { + ...metadata, + author: participant + } + ]) + break + case 'ephemeral': + case 'not_ephemeral': + msg.message = { + protocolMessage: { + type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING, + ephemeralExpiration: +(child.attrs.expiration || 0) + } } - } - break - case 'modify': - const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid) - msg.messageStubParameters = oldNumber || [] - msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER - break - case 'promote': - case 'demote': - case 'remove': - case 'add': - case 'leave': - const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}` - msg.messageStubType = WAMessageStubType[stubType] + break + case 'modify': + const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid) + msg.messageStubParameters = oldNumber || [] + msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER + break + case 'promote': + case 'demote': + case 'remove': + case 'add': + case 'leave': + const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}` + msg.messageStubType = WAMessageStubType[stubType] - const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid) - if( - participants.length === 1 && + const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid) + if ( + participants.length === 1 && // if recv. "remove" message and sender removed themselves // mark as left areJidsSameUser(participants[0], participant) && child.tag === 'remove' - ) { - msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE - } + ) { + msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE + } - msg.messageStubParameters = participants - break - case 'subject': - msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT - msg.messageStubParameters = [ child.attrs.subject ] - break - case 'description': - const description = getBinaryNodeChild(child, 'body')?.content?.toString() - msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION - msg.messageStubParameters = description ? [ description ] : undefined - break - case 'announcement': - case 'not_announcement': - msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE - msg.messageStubParameters = [ (child.tag === 'announcement') ? 'on' : 'off' ] - break - case 'locked': - case 'unlocked': - msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT - msg.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ] - break - case 'invite': - msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK - msg.messageStubParameters = [ child.attrs.code ] - break - case 'member_add_mode': - const addMode = child.content - if(addMode) { - msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE - msg.messageStubParameters = [ addMode.toString() ] - } + msg.messageStubParameters = participants + break + case 'subject': + msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT + msg.messageStubParameters = [child.attrs.subject] + break + case 'description': + const description = getBinaryNodeChild(child, 'body')?.content?.toString() + msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION + msg.messageStubParameters = description ? [description] : undefined + break + case 'announcement': + case 'not_announcement': + msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE + msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'] + break + case 'locked': + case 'unlocked': + msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT + msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'] + break + case 'invite': + msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK + msg.messageStubParameters = [child.attrs.code] + break + case 'member_add_mode': + const addMode = child.content + if (addMode) { + msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE + msg.messageStubParameters = [addMode.toString()] + } - break - case 'membership_approval_mode': - const approvalMode = getBinaryNodeChild(child, 'group_join') - if(approvalMode) { - msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE - msg.messageStubParameters = [ approvalMode.attrs.state ] - } + break + case 'membership_approval_mode': + const approvalMode = getBinaryNodeChild(child, 'group_join') + if (approvalMode) { + msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE + msg.messageStubParameters = [approvalMode.attrs.state] + } - break - case 'created_membership_requests': - msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD - msg.messageStubParameters = [ participantJid, 'created', child.attrs.request_method ] - break - case 'revoked_membership_requests': - const isDenied = areJidsSameUser(participantJid, participant) - msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD - msg.messageStubParameters = [ participantJid, isDenied ? 'revoked' : 'rejected' ] - break + break + case 'created_membership_requests': + msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD + msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method] + break + case 'revoked_membership_requests': + const isDenied = areJidsSameUser(participantJid, participant) + msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD + msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'] + break } } - const processNotification = async(node: BinaryNode) => { - const result: Partial = { } + const processNotification = async (node: BinaryNode) => { + const result: Partial = {} const [child] = getAllBinaryNodeChildren(node) const nodeType = node.attrs.type const from = jidNormalizedUser(node.attrs.from) switch (nodeType) { - case 'privacy_token': - const tokenList = getBinaryNodeChildren(child, 'token') - for(const { attrs, content } of tokenList) { - const jid = attrs.jid - ev.emit('chats.update', [ + case 'privacy_token': + const tokenList = getBinaryNodeChildren(child, 'token') + for (const { attrs, content } of tokenList) { + const jid = attrs.jid + ev.emit('chats.update', [ + { + id: jid, + tcToken: content as Buffer + } + ]) + + logger.debug({ jid }, 'got privacy token update') + } + + break + case 'w:gp2': + handleGroupNotification(node.attrs.participant, child, result) + break + case 'mediaretry': + const event = decodeMediaRetryNode(node) + ev.emit('messages.media-update', [event]) + break + case 'encrypt': + await handleEncryptNotification(node) + break + case 'devices': + const devices = getBinaryNodeChildren(child, 'device') + if (areJidsSameUser(child.attrs.jid, authState.creds.me!.id)) { + const deviceJids = devices.map(d => d.attrs.jid) + logger.info({ deviceJids }, 'got my own devices') + } + + break + case 'server_sync': + const update = getBinaryNodeChild(node, 'collection') + if (update) { + const name = update.attrs.name as WAPatchName + await resyncAppState([name], false) + } + + break + case 'picture': + const setPicture = getBinaryNodeChild(node, 'set') + const delPicture = getBinaryNodeChild(node, 'delete') + + ev.emit('contacts.update', [ { - id: jid, - tcToken: content as Buffer + id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '', + imgUrl: setPicture ? 'changed' : 'removed' } ]) - logger.debug({ jid }, 'got privacy token update') - } + if (isJidGroup(from)) { + const node = setPicture || delPicture + result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON - break - case 'w:gp2': - handleGroupNotification(node.attrs.participant, child, result) - break - case 'mediaretry': - const event = decodeMediaRetryNode(node) - ev.emit('messages.media-update', [event]) - break - case 'encrypt': - await handleEncryptNotification(node) - break - case 'devices': - const devices = getBinaryNodeChildren(child, 'device') - if(areJidsSameUser(child.attrs.jid, authState.creds.me!.id)) { - const deviceJids = devices.map(d => d.attrs.jid) - logger.info({ deviceJids }, 'got my own devices') - } - - break - case 'server_sync': - const update = getBinaryNodeChild(node, 'collection') - if(update) { - const name = update.attrs.name as WAPatchName - await resyncAppState([name], false) - } - - break - case 'picture': - const setPicture = getBinaryNodeChild(node, 'set') - const delPicture = getBinaryNodeChild(node, 'delete') - - ev.emit('contacts.update', [{ - id: jidNormalizedUser(node?.attrs?.from) || ((setPicture || delPicture)?.attrs?.hash) || '', - imgUrl: setPicture ? 'changed' : 'removed' - }]) - - if(isJidGroup(from)) { - const node = setPicture || delPicture - result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON - - if(setPicture) { - result.messageStubParameters = [setPicture.attrs.id] - } - - result.participant = node?.attrs.author - result.key = { - ...result.key || {}, - participant: setPicture?.attrs.author - } - } - - break - case 'account_sync': - if(child.tag === 'disappearing_mode') { - const newDuration = +child.attrs.duration - const timestamp = +child.attrs.t - - logger.info({ newDuration }, 'updated account disappearing mode') - - ev.emit('creds.update', { - accountSettings: { - ...authState.creds.accountSettings, - defaultDisappearingMode: { - ephemeralExpiration: newDuration, - ephemeralSettingTimestamp: timestamp, - }, + if (setPicture) { + result.messageStubParameters = [setPicture.attrs.id] } - }) - } else if(child.tag === 'blocklist') { - const blocklists = getBinaryNodeChildren(child, 'item') - for(const { attrs } of blocklists) { - const blocklist = [attrs.jid] - const type = (attrs.action === 'block') ? 'add' : 'remove' - ev.emit('blocklist.update', { blocklist, type }) + result.participant = node?.attrs.author + result.key = { + ...(result.key || {}), + participant: setPicture?.attrs.author + } } - } - break - case 'link_code_companion_reg': - const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg') - const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref')) - const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')) - const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub')) - const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped) - const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey) - const random = randomBytes(32) - const linkCodeSalt = randomBytes(32) - const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, { - salt: linkCodeSalt, - info: 'link_code_pairing_key_bundle_encryption_key' - }) - const encryptPayload = Buffer.concat([Buffer.from(authState.creds.signedIdentityKey.public), primaryIdentityPublicKey, random]) - const encryptIv = randomBytes(12) - const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0)) - const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]) - const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey) - const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]) - authState.creds.advSecretKey = (await hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64') - await query({ - tag: 'iq', - attrs: { - to: S_WHATSAPP_NET, - type: 'set', - id: sock.generateMessageTag(), - xmlns: 'md' - }, - content: [ - { - tag: 'link_code_companion_reg', - attrs: { - jid: authState.creds.me!.id, - stage: 'companion_finish', - }, - content: [ - { - tag: 'link_code_pairing_wrapped_key_bundle', - attrs: {}, - content: encryptedPayload - }, - { - tag: 'companion_identity_public', - attrs: {}, - content: authState.creds.signedIdentityKey.public - }, - { - tag: 'link_code_pairing_ref', - attrs: {}, - content: ref + break + case 'account_sync': + if (child.tag === 'disappearing_mode') { + const newDuration = +child.attrs.duration + const timestamp = +child.attrs.t + + logger.info({ newDuration }, 'updated account disappearing mode') + + ev.emit('creds.update', { + accountSettings: { + ...authState.creds.accountSettings, + defaultDisappearingMode: { + ephemeralExpiration: newDuration, + ephemeralSettingTimestamp: timestamp } - ] + } + }) + } else if (child.tag === 'blocklist') { + const blocklists = getBinaryNodeChildren(child, 'item') + + for (const { attrs } of blocklists) { + const blocklist = [attrs.jid] + const type = attrs.action === 'block' ? 'add' : 'remove' + ev.emit('blocklist.update', { blocklist, type }) } - ] - }) - authState.creds.registered = true - ev.emit('creds.update', authState.creds) + } + + break + case 'link_code_companion_reg': + const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg') + const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref')) + const primaryIdentityPublicKey = toRequiredBuffer( + getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub') + ) + const primaryEphemeralPublicKeyWrapped = toRequiredBuffer( + getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub') + ) + const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped) + const companionSharedKey = Curve.sharedKey( + authState.creds.pairingEphemeralKeyPair.private, + codePairingPublicKey + ) + const random = randomBytes(32) + const linkCodeSalt = randomBytes(32) + const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, { + salt: linkCodeSalt, + info: 'link_code_pairing_key_bundle_encryption_key' + }) + const encryptPayload = Buffer.concat([ + Buffer.from(authState.creds.signedIdentityKey.public), + primaryIdentityPublicKey, + random + ]) + const encryptIv = randomBytes(12) + const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0)) + const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]) + const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey) + const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]) + authState.creds.advSecretKey = (await hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64') + await query({ + tag: 'iq', + attrs: { + to: S_WHATSAPP_NET, + type: 'set', + id: sock.generateMessageTag(), + xmlns: 'md' + }, + content: [ + { + tag: 'link_code_companion_reg', + attrs: { + jid: authState.creds.me!.id, + stage: 'companion_finish' + }, + content: [ + { + tag: 'link_code_pairing_wrapped_key_bundle', + attrs: {}, + content: encryptedPayload + }, + { + tag: 'companion_identity_public', + attrs: {}, + content: authState.creds.signedIdentityKey.public + }, + { + tag: 'link_code_pairing_ref', + attrs: {}, + content: ref + } + ] + } + ] + }) + authState.creds.registered = true + ev.emit('creds.update', authState.creds) } - if(Object.keys(result).length) { + if (Object.keys(result).length) { return result } } @@ -542,7 +569,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } function toRequiredBuffer(data: Uint8Array | Buffer | undefined) { - if(data === undefined) { + if (data === undefined) { throw new Boom('Invalid buffer', { statusCode: 400 }) } @@ -561,12 +588,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { msgRetryCache.set(key, newValue) } - const sendMessagesAgain = async( - key: proto.IMessageKey, - ids: string[], - retryNode: BinaryNode - ) => { - // todo: implement a cache to store the last 256 sent messages (copy whatsmeow) + const sendMessagesAgain = async (key: proto.IMessageKey, ids: string[], retryNode: BinaryNode) => { + // todo: implement a cache to store the last 256 sent messages (copy whatsmeow) const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id }))) const remoteJid = key.remoteJid! const participant = key.participant || remoteJid @@ -576,18 +599,18 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const sendToAll = !jidDecode(participant)?.device await assertSessions([participant], true) - if(isJidGroup(remoteJid)) { + if (isJidGroup(remoteJid)) { await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } }) } logger.debug({ participant, sendToAll }, 'forced new session for retry recp') - for(const [i, msg] of msgs.entries()) { - if(msg) { + for (const [i, msg] of msgs.entries()) { + if (msg) { updateSendMessageAgainCount(ids[i], participant) const msgRelayOpts: MessageRelayOptions = { messageId: ids[i] } - if(sendToAll) { + if (sendToAll) { msgRelayOpts.useUserDevicesCache = false } else { msgRelayOpts.participant = { @@ -603,10 +626,13 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } } - const handleReceipt = async(node: BinaryNode) => { + const handleReceipt = async (node: BinaryNode) => { const { attrs, content } = node const isLid = attrs.from.includes('lid') - const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id) + const isNodeFromMe = areJidsSameUser( + attrs.participant || attrs.from, + isLid ? authState.creds.me?.lid : authState.creds.me?.id + ) const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe) @@ -617,87 +643,83 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { participant: attrs.participant } - if(shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') { + if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') { logger.debug({ remoteJid }, 'ignoring receipt from jid') await sendMessageAck(node) return } const ids = [attrs.id] - if(Array.isArray(content)) { + if (Array.isArray(content)) { const items = getBinaryNodeChildren(content[0], 'item') ids.push(...items.map(i => i.attrs.id)) } try { await Promise.all([ - processingMutex.mutex( - async() => { - const status = getStatusFromReceiptType(attrs.type) - if( - typeof status !== 'undefined' && - ( - // basically, we only want to know when a message from us has been delivered to/read by the other person - // or another device of ours has read some messages - status >= proto.WebMessageInfo.Status.SERVER_ACK || - !isNodeFromMe - ) - ) { - if(isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) { - if(attrs.participant) { - const updateKey: keyof MessageUserReceipt = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp' - ev.emit( - 'message-receipt.update', - ids.map(id => ({ - key: { ...key, id }, - receipt: { - userJid: jidNormalizedUser(attrs.participant), - [updateKey]: +attrs.t - } - })) - ) - } - } else { + processingMutex.mutex(async () => { + const status = getStatusFromReceiptType(attrs.type) + if ( + typeof status !== 'undefined' && + // basically, we only want to know when a message from us has been delivered to/read by the other person + // or another device of ours has read some messages + (status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe) + ) { + if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) { + if (attrs.participant) { + const updateKey: keyof MessageUserReceipt = + status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp' ev.emit( - 'messages.update', + 'message-receipt.update', ids.map(id => ({ key: { ...key, id }, - update: { status } + receipt: { + userJid: jidNormalizedUser(attrs.participant), + [updateKey]: +attrs.t + } })) ) } - } - - if(attrs.type === 'retry') { - // correctly set who is asking for the retry - key.participant = key.participant || attrs.from - const retryNode = getBinaryNodeChild(node, 'retry') - if(willSendMessageAgain(ids[0], key.participant)) { - if(key.fromMe) { - try { - logger.debug({ attrs, key }, 'recv retry request') - await sendMessagesAgain(key, ids, retryNode!) - } catch(error) { - logger.error({ key, ids, trace: error.stack }, 'error in sending message again') - } - } else { - logger.info({ attrs, key }, 'recv retry for not fromMe message') - } - } else { - logger.info({ attrs, key }, 'will not send message again, as sent too many times') - } + } else { + ev.emit( + 'messages.update', + ids.map(id => ({ + key: { ...key, id }, + update: { status } + })) + ) } } - ) + + if (attrs.type === 'retry') { + // correctly set who is asking for the retry + key.participant = key.participant || attrs.from + const retryNode = getBinaryNodeChild(node, 'retry') + if (willSendMessageAgain(ids[0], key.participant)) { + if (key.fromMe) { + try { + logger.debug({ attrs, key }, 'recv retry request') + await sendMessagesAgain(key, ids, retryNode!) + } catch (error) { + logger.error({ key, ids, trace: error.stack }, 'error in sending message again') + } + } else { + logger.info({ attrs, key }, 'recv retry for not fromMe message') + } + } else { + logger.info({ attrs, key }, 'will not send message again, as sent too many times') + } + } + }) ]) } finally { await sendMessageAck(node) } } - const handleNotification = async(node: BinaryNode) => { + const handleNotification = async (node: BinaryNode) => { const remoteJid = node.attrs.from - if(shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') { + if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') { logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification') await sendMessageAck(node) return @@ -705,34 +727,32 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { try { await Promise.all([ - processingMutex.mutex( - async() => { - const msg = await processNotification(node) - if(msg) { - const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id) - msg.key = { - remoteJid, - fromMe, - participant: node.attrs.participant, - id: node.attrs.id, - ...(msg.key || {}) - } - msg.participant ??= node.attrs.participant - msg.messageTimestamp = +node.attrs.t - - const fullMsg = proto.WebMessageInfo.fromObject(msg) - await upsertMessage(fullMsg, 'append') + processingMutex.mutex(async () => { + const msg = await processNotification(node) + if (msg) { + const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id) + msg.key = { + remoteJid, + fromMe, + participant: node.attrs.participant, + id: node.attrs.id, + ...(msg.key || {}) } + msg.participant ??= node.attrs.participant + msg.messageTimestamp = +node.attrs.t + + const fullMsg = proto.WebMessageInfo.fromObject(msg) + await upsertMessage(fullMsg, 'append') } - ) + }) ]) } finally { await sendMessageAck(node) } } - const handleMessage = async(node: BinaryNode) => { - if(shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') { + const handleMessage = async (node: BinaryNode) => { + if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') { logger.debug({ key: node.attrs.key }, 'ignored message') await sendMessageAck(node) return @@ -741,119 +761,118 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const encNode = getBinaryNodeChild(node, 'enc') // TODO: temporary fix for crashes and issues resulting of failed msmsg decryption - if(encNode && encNode.attrs.type === 'msmsg') { - logger.debug({ key: node.attrs.key }, 'ignored msmsg') - await sendMessageAck(node) - return + if (encNode && encNode.attrs.type === 'msmsg') { + logger.debug({ key: node.attrs.key }, 'ignored msmsg') + await sendMessageAck(node) + return } let response: string | undefined - if(getBinaryNodeChild(node, 'unavailable') && !encNode) { + if (getBinaryNodeChild(node, 'unavailable') && !encNode) { await sendMessageAck(node) const { key } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '').fullMessage response = await requestPlaceholderResend(key) - if(response === 'RESOLVED') { + if (response === 'RESOLVED') { return } logger.debug('received unavailable message, acked and requested resend from phone') } else { - if(placeholderResendCache.get(node.attrs.id)) { + if (placeholderResendCache.get(node.attrs.id)) { placeholderResendCache.del(node.attrs.id) } } + const { + fullMessage: msg, + category, + author, + decrypt + } = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger) - const { fullMessage: msg, category, author, decrypt } = decryptMessageNode( - node, - authState.creds.me!.id, - authState.creds.me!.lid || '', - signalRepository, - logger, - ) - - if(response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) { + if (response && msg?.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) { msg.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT, response] } - if(msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) { + if ( + msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && + node.attrs.sender_pn + ) { ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn }) } try { await Promise.all([ - processingMutex.mutex( - async() => { - await decrypt() - // message failed to decrypt - if(msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) { - if(msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) { - return sendMessageAck(node, NACK_REASONS.ParsingError) - } - - retryMutex.mutex( - async() => { - if(ws.isOpen) { - if(getBinaryNodeChild(node, 'unavailable')) { - return - } - - const encNode = getBinaryNodeChild(node, 'enc') - await sendRetryRequest(node, !encNode) - if(retryRequestDelayMs) { - await delay(retryRequestDelayMs) - } - } else { - logger.debug({ node }, 'connection closed, ignoring retry req') - } - } - ) - } else { - // no type in the receipt => message delivered - let type: MessageReceiptType = undefined - let participant = msg.key.participant - if(category === 'peer') { // special peer message - type = 'peer_msg' - } else if(msg.key.fromMe) { // message was sent by us from a different device - type = 'sender' - // need to specially handle this case - if(isJidUser(msg.key.remoteJid!)) { - participant = author - } - } else if(!sendActiveReceipts) { - type = 'inactive' - } - - await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type) - - // send ack for history message - const isAnyHistoryMsg = getHistoryMsg(msg.message!) - if(isAnyHistoryMsg) { - const jid = jidNormalizedUser(msg.key.remoteJid!) - await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync') - } + processingMutex.mutex(async () => { + await decrypt() + // message failed to decrypt + if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT) { + if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) { + return sendMessageAck(node, NACK_REASONS.ParsingError) } - cleanMessage(msg, authState.creds.me!.id) + retryMutex.mutex(async () => { + if (ws.isOpen) { + if (getBinaryNodeChild(node, 'unavailable')) { + return + } - await sendMessageAck(node) + const encNode = getBinaryNodeChild(node, 'enc') + await sendRetryRequest(node, !encNode) + if (retryRequestDelayMs) { + await delay(retryRequestDelayMs) + } + } else { + logger.debug({ node }, 'connection closed, ignoring retry req') + } + }) + } else { + // no type in the receipt => message delivered + let type: MessageReceiptType = undefined + let participant = msg.key.participant + if (category === 'peer') { + // special peer message + type = 'peer_msg' + } else if (msg.key.fromMe) { + // message was sent by us from a different device + type = 'sender' + // need to specially handle this case + if (isJidUser(msg.key.remoteJid!)) { + participant = author + } + } else if (!sendActiveReceipts) { + type = 'inactive' + } - await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify') + await sendReceipt(msg.key.remoteJid!, participant!, [msg.key.id!], type) + + // send ack for history message + const isAnyHistoryMsg = getHistoryMsg(msg.message!) + if (isAnyHistoryMsg) { + const jid = jidNormalizedUser(msg.key.remoteJid!) + await sendReceipt(jid, undefined, [msg.key.id!], 'hist_sync') + } } - ) + + cleanMessage(msg, authState.creds.me!.id) + + await sendMessageAck(node) + + await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify') + }) ]) - } catch(error) { + } catch (error) { logger.error({ error, node }, 'error in handling message') } } - const fetchMessageHistory = async( + const fetchMessageHistory = async ( count: number, oldestMsgKey: WAMessageKey, oldestMsgTimestamp: number | Long ): Promise => { - if(!authState.creds.me?.id) { + if (!authState.creds.me?.id) { throw new Boom('Not authenticated') } @@ -871,12 +890,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { return sendPeerDataOperationMessage(pdoMessage) } - const requestPlaceholderResend = async(messageKey: WAMessageKey): Promise => { - if(!authState.creds.me?.id) { + const requestPlaceholderResend = async (messageKey: WAMessageKey): Promise => { + if (!authState.creds.me?.id) { throw new Boom('Not authenticated') } - if(placeholderResendCache.get(messageKey?.id!)) { + if (placeholderResendCache.get(messageKey?.id!)) { logger.debug({ messageKey }, 'already requested resend') return } else { @@ -885,20 +904,22 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { await delay(5000) - if(!placeholderResendCache.get(messageKey?.id!)) { + if (!placeholderResendCache.get(messageKey?.id!)) { logger.debug({ messageKey }, 'message received while resend requested') return 'RESOLVED' } const pdoMessage = { - placeholderMessageResendRequest: [{ - messageKey - }], + placeholderMessageResendRequest: [ + { + messageKey + } + ], peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND } setTimeout(() => { - if(placeholderResendCache.get(messageKey?.id!)) { + if (placeholderResendCache.get(messageKey?.id!)) { logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline') placeholderResendCache.del(messageKey?.id!) } @@ -907,7 +928,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { return sendPeerDataOperationMessage(pdoMessage) } - const handleCall = async(node: BinaryNode) => { + const handleCall = async (node: BinaryNode) => { const { attrs } = node const [infoChild] = getAllBinaryNodeChildren(node) const callId = infoChild.attrs['call-id'] @@ -919,10 +940,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { id: callId, date: new Date(+attrs.t * 1000), offline: !!attrs.offline, - status, + status } - if(status === 'offer') { + if (status === 'offer') { call.isVideo = !!getBinaryNodeChild(infoChild, 'video') call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'] call.groupJid = infoChild.attrs['group-jid'] @@ -932,13 +953,13 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const existingCall = callOfferCache.get(call.id) // use existing call info to populate this event - if(existingCall) { + if (existingCall) { call.isVideo = existingCall.isVideo call.isGroup = existingCall.isGroup } // delete data once call has ended - if(status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') { + if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') { callOfferCache.del(call.id) } @@ -947,7 +968,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { await sendMessageAck(node) } - const handleBadAck = async({ attrs }: BinaryNode) => { + const handleBadAck = async ({ attrs }: BinaryNode) => { const key: WAMessageKey = { remoteJid: attrs.from, fromMe: true, id: attrs.id } // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP @@ -966,28 +987,23 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { // error in acknowledgement, // device could not display the message - if(attrs.error) { + if (attrs.error) { logger.warn({ attrs }, 'received error in ack') - ev.emit( - 'messages.update', - [ - { - key, - update: { - status: WAMessageStatus.ERROR, - messageStubParameters: [ - attrs.error - ] - } + ev.emit('messages.update', [ + { + key, + update: { + status: WAMessageStatus.ERROR, + messageStubParameters: [attrs.error] } - ] - ) + } + ]) } } /// processes a node with the given function /// and adds the task to the existing buffer if we're buffering events - const processNodeWithBuffer = async( + const processNodeWithBuffer = async ( node: BinaryNode, identifier: string, exec: (node: BinaryNode, offline: boolean) => Promise @@ -997,8 +1013,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { ev.flush() function execTask() { - return exec(node, false) - .catch(err => onUnexpectedError(err, identifier)) + return exec(node, false).catch(err => onUnexpectedError(err, identifier)) } } @@ -1022,23 +1037,20 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const enqueue = (type: MessageType, node: BinaryNode) => { nodes.push({ type, node }) - if(isProcessing) { + if (isProcessing) { return } isProcessing = true - const promise = async() => { - while(nodes.length && ws.isOpen) { + const promise = async () => { + while (nodes.length && ws.isOpen) { const { type, node } = nodes.shift()! const nodeProcessor = nodeProcessorMap.get(type) - if(!nodeProcessor) { - onUnexpectedError( - new Error(`unknown offline node type: ${type}`), - 'processing offline node' - ) + if (!nodeProcessor) { + onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node') continue } @@ -1056,10 +1068,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const offlineNodeProcessor = makeOfflineNodeProcessor() - const processNode = (type: MessageType, node: BinaryNode, identifier: string, exec: (node: BinaryNode) => Promise) => { + const processNode = ( + type: MessageType, + node: BinaryNode, + identifier: string, + exec: (node: BinaryNode) => Promise + ) => { const isOffline = !!node.attrs.offline - if(isOffline) { + if (isOffline) { offlineNodeProcessor.enqueue(type, node) } else { processNodeWithBuffer(node, identifier, exec) @@ -1071,7 +1088,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { processNode('message', node, 'processing message', handleMessage) }) - ws.on('CB:call', async(node: BinaryNode) => { + ws.on('CB:call', async (node: BinaryNode) => { processNode('call', node, 'handling call', handleCall) }) @@ -1079,28 +1096,29 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { processNode('receipt', node, 'handling receipt', handleReceipt) }) - ws.on('CB:notification', async(node: BinaryNode) => { + ws.on('CB:notification', async (node: BinaryNode) => { processNode('notification', node, 'handling notification', handleNotification) }) ws.on('CB:ack,class:message', (node: BinaryNode) => { - handleBadAck(node) - .catch(error => onUnexpectedError(error, 'handling bad ack')) + handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack')) }) - ev.on('call', ([ call ]) => { + ev.on('call', ([call]) => { // missed call + group call notification message generation - if(call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) { + if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) { const msg: proto.IWebMessageInfo = { key: { remoteJid: call.chatId, id: call.id, fromMe: false }, - messageTimestamp: unixTimestampSeconds(call.date), + messageTimestamp: unixTimestampSeconds(call.date) } - if(call.status === 'timeout') { - if(call.isGroup) { - msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO : WAMessageStubType.CALL_MISSED_GROUP_VOICE + if (call.status === 'timeout') { + if (call.isGroup) { + msg.messageStubType = call.isVideo + ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO + : WAMessageStubType.CALL_MISSED_GROUP_VOICE } else { msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE } @@ -1114,7 +1132,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { }) ev.on('connection.update', ({ isOnline }) => { - if(typeof isOnline !== 'undefined') { + if (typeof isOnline !== 'undefined') { sendActiveReceipts = isOnline logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`) } @@ -1126,6 +1144,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { sendRetryRequest, rejectCall, fetchMessageHistory, - requestPlaceholderResend, + requestPlaceholderResend } } diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index e398fc2..4d4af65 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -1,12 +1,49 @@ - import NodeCache from '@cacheable/node-cache' import { Boom } from '@hapi/boom' import { proto } from '../../WAProto' import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults' -import { AnyMessageContent, MediaConnInfo, MessageReceiptType, MessageRelayOptions, MiscMessageGenerationOptions, SocketConfig, WAMessageKey } from '../Types' -import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils' +import { + AnyMessageContent, + MediaConnInfo, + MessageReceiptType, + MessageRelayOptions, + MiscMessageGenerationOptions, + SocketConfig, + WAMessageKey +} from '../Types' +import { + aggregateMessageKeysNotFromMe, + assertMediaContent, + bindWaitForEvent, + decryptMediaRetryData, + encodeSignedDeviceIdentity, + encodeWAMessage, + encryptMediaRetryRequest, + extractDeviceJids, + generateMessageIDV2, + generateWAMessage, + getStatusCodeForMediaRetry, + getUrlFromDirectPath, + getWAUploadToServer, + normalizeMessageContent, + parseAndInjectE2ESessions, + unixTimestampSeconds +} from '../Utils' import { getUrlInfo } from '../Utils/link-preview' -import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, JidWithDevice, S_WHATSAPP_NET } from '../WABinary' +import { + areJidsSameUser, + BinaryNode, + BinaryNodeAttributes, + getBinaryNodeChild, + getBinaryNodeChildren, + isJidGroup, + isJidUser, + jidDecode, + jidEncode, + jidNormalizedUser, + JidWithDevice, + S_WHATSAPP_NET +} from '../WABinary' import { USyncQuery, USyncUser } from '../WAUSync' import { makeGroupsSocket } from './groups' @@ -17,7 +54,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { generateHighQualityLinkPreview, options: axiosOptions, patchMessageBeforeSending, - cachedGroupMetadata, + cachedGroupMetadata } = config const sock = makeGroupsSocket(config) const { @@ -30,36 +67,36 @@ export const makeMessagesSocket = (config: SocketConfig) => { fetchPrivacySettings, sendNode, groupMetadata, - groupToggleEphemeral, + groupToggleEphemeral } = sock - const userDevicesCache = config.userDevicesCache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes - useClones: false - }) + const userDevicesCache = + config.userDevicesCache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes + useClones: false + }) let mediaConn: Promise - const refreshMediaConn = async(forceGet = false) => { + const refreshMediaConn = async (forceGet = false) => { const media = await mediaConn - if(!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) { - mediaConn = (async() => { + if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) { + mediaConn = (async () => { const result = await query({ tag: 'iq', attrs: { type: 'set', xmlns: 'w:m', - to: S_WHATSAPP_NET, + to: S_WHATSAPP_NET }, - content: [ { tag: 'media_conn', attrs: { } } ] + content: [{ tag: 'media_conn', attrs: {} }] }) const mediaConnNode = getBinaryNodeChild(result, 'media_conn') const node: MediaConnInfo = { - hosts: getBinaryNodeChildren(mediaConnNode, 'host').map( - ({ attrs }) => ({ - hostname: attrs.hostname, - maxContentLengthBytes: +attrs.maxContentLengthBytes, - }) - ), + hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({ + hostname: attrs.hostname, + maxContentLengthBytes: +attrs.maxContentLengthBytes + })), auth: mediaConnNode!.attrs.auth, ttl: +mediaConnNode!.attrs.ttl, fetchDate: new Date() @@ -73,41 +110,46 @@ export const makeMessagesSocket = (config: SocketConfig) => { } /** - * generic send receipt function - * used for receipts of phone call, read, delivery etc. - * */ - const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: MessageReceiptType) => { + * generic send receipt function + * used for receipts of phone call, read, delivery etc. + * */ + const sendReceipt = async ( + jid: string, + participant: string | undefined, + messageIds: string[], + type: MessageReceiptType + ) => { const node: BinaryNode = { tag: 'receipt', attrs: { - id: messageIds[0], - }, + id: messageIds[0] + } } const isReadReceipt = type === 'read' || type === 'read-self' - if(isReadReceipt) { + if (isReadReceipt) { node.attrs.t = unixTimestampSeconds().toString() } - if(type === 'sender' && isJidUser(jid)) { + if (type === 'sender' && isJidUser(jid)) { node.attrs.recipient = jid node.attrs.to = participant! } else { node.attrs.to = jid - if(participant) { + if (participant) { node.attrs.participant = participant } } - if(type) { + if (type) { node.attrs.type = type } const remainingMessageIds = messageIds.slice(1) - if(remainingMessageIds.length) { + if (remainingMessageIds.length) { node.content = [ { tag: 'list', - attrs: { }, + attrs: {}, content: remainingMessageIds.map(id => ({ tag: 'item', attrs: { id } @@ -121,38 +163,38 @@ export const makeMessagesSocket = (config: SocketConfig) => { } /** Correctly bulk send receipts to multiple chats, participants */ - const sendReceipts = async(keys: WAMessageKey[], type: MessageReceiptType) => { + const sendReceipts = async (keys: WAMessageKey[], type: MessageReceiptType) => { const recps = aggregateMessageKeysNotFromMe(keys) - for(const { jid, participant, messageIds } of recps) { + for (const { jid, participant, messageIds } of recps) { await sendReceipt(jid, participant, messageIds, type) } } /** Bulk read messages. Keys can be from different chats & participants */ - const readMessages = async(keys: WAMessageKey[]) => { + const readMessages = async (keys: WAMessageKey[]) => { const privacySettings = await fetchPrivacySettings() // based on privacy settings, we have to change the read type const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self' await sendReceipts(keys, readType) - } + } /** Fetch all the devices we've to send a message to */ - const getUSyncDevices = async(jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => { + const getUSyncDevices = async (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => { const deviceResults: JidWithDevice[] = [] - if(!useCache) { + if (!useCache) { logger.debug('not using cache for devices') } const toFetch: string[] = [] jids = Array.from(new Set(jids)) - for(let jid of jids) { + for (let jid of jids) { const user = jidDecode(jid)?.user jid = jidNormalizedUser(jid) - if(useCache) { + if (useCache) { const devices = userDevicesCache.get(user!) - if(devices) { + if (devices) { deviceResults.push(...devices) logger.trace({ user }, 'using cache for devices') @@ -164,32 +206,30 @@ export const makeMessagesSocket = (config: SocketConfig) => { } } - if(!toFetch.length) { + if (!toFetch.length) { return deviceResults } - const query = new USyncQuery() - .withContext('message') - .withDeviceProtocol() + const query = new USyncQuery().withContext('message').withDeviceProtocol() - for(const jid of toFetch) { + for (const jid of toFetch) { query.withUser(new USyncUser().withId(jid)) } const result = await sock.executeUSyncQuery(query) - if(result) { + if (result) { const extracted = extractDeviceJids(result?.list, authState.creds.me!.id, ignoreZeroDevices) const deviceMap: { [_: string]: JidWithDevice[] } = {} - for(const item of extracted) { + for (const item of extracted) { deviceMap[item.user] = deviceMap[item.user] || [] deviceMap[item.user].push(item) deviceResults.push(item) } - for(const key in deviceMap) { + for (const key in deviceMap) { userDevicesCache.set(key, deviceMap[key]) } } @@ -197,45 +237,39 @@ export const makeMessagesSocket = (config: SocketConfig) => { return deviceResults } - const assertSessions = async(jids: string[], force: boolean) => { + const assertSessions = async (jids: string[], force: boolean) => { let didFetchNewSession = false let jidsRequiringFetch: string[] = [] - if(force) { + if (force) { jidsRequiringFetch = jids } else { - const addrs = jids.map(jid => ( - signalRepository - .jidToSignalProtocolAddress(jid) - )) + const addrs = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid)) const sessions = await authState.keys.get('session', addrs) - for(const jid of jids) { - const signalId = signalRepository - .jidToSignalProtocolAddress(jid) - if(!sessions[signalId]) { + for (const jid of jids) { + const signalId = signalRepository.jidToSignalProtocolAddress(jid) + if (!sessions[signalId]) { jidsRequiringFetch.push(jid) } } } - if(jidsRequiringFetch.length) { + if (jidsRequiringFetch.length) { logger.debug({ jidsRequiringFetch }, 'fetching sessions') const result = await query({ tag: 'iq', attrs: { xmlns: 'encrypt', type: 'get', - to: S_WHATSAPP_NET, + to: S_WHATSAPP_NET }, content: [ { tag: 'key', - attrs: { }, - content: jidsRequiringFetch.map( - jid => ({ - tag: 'user', - attrs: { jid }, - }) - ) + attrs: {}, + content: jidsRequiringFetch.map(jid => ({ + tag: 'user', + attrs: { jid } + })) } ] }) @@ -247,11 +281,11 @@ export const makeMessagesSocket = (config: SocketConfig) => { return didFetchNewSession } - const sendPeerDataOperationMessage = async( + const sendPeerDataOperationMessage = async ( pdoMessage: proto.Message.IPeerDataOperationRequestMessage ): Promise => { //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone - if(!authState.creds.me?.id) { + if (!authState.creds.me?.id) { throw new Boom('Not authenticated') } @@ -268,64 +302,67 @@ export const makeMessagesSocket = (config: SocketConfig) => { additionalAttributes: { category: 'peer', // eslint-disable-next-line camelcase - push_priority: 'high_force', - }, + push_priority: 'high_force' + } }) return msgId } - const createParticipantNodes = async( - jids: string[], - message: proto.IMessage, - extraAttrs?: BinaryNode['attrs'] - ) => { + const createParticipantNodes = async (jids: string[], message: proto.IMessage, extraAttrs?: BinaryNode['attrs']) => { let patched = await patchMessageBeforeSending(message, jids) - if(!Array.isArray(patched)) { - patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched] + if (!Array.isArray(patched)) { + patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched] } let shouldIncludeDeviceIdentity = false const nodes = await Promise.all( - patched.map( - async patchedMessageWithJid => { - const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid - if(!jid) { - return {} as BinaryNode - } + patched.map(async patchedMessageWithJid => { + const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid + if (!jid) { + return {} as BinaryNode + } - const bytes = encodeWAMessage(patchedMessage) - const { type, ciphertext } = await signalRepository - .encryptMessage({ jid, data: bytes }) - if(type === 'pkmsg') { - shouldIncludeDeviceIdentity = true - } + const bytes = encodeWAMessage(patchedMessage) + const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes }) + if (type === 'pkmsg') { + shouldIncludeDeviceIdentity = true + } - const node: BinaryNode = { - tag: 'to', - attrs: { jid }, - content: [{ + const node: BinaryNode = { + tag: 'to', + attrs: { jid }, + content: [ + { tag: 'enc', attrs: { v: '2', type, - ...extraAttrs || {} + ...(extraAttrs || {}) }, content: ciphertext - }] - } - return node + } + ] } - ) + return node + }) ) return { nodes, shouldIncludeDeviceIdentity } } - const relayMessage = async( + const relayMessage = async ( jid: string, message: proto.IMessage, - { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }: MessageRelayOptions + { + messageId: msgId, + participant, + additionalAttributes, + additionalNodes, + useUserDevicesCache, + useCachedGroupMetadata, + statusJidList + }: MessageRelayOptions ) => { const meId = authState.creds.me!.id @@ -342,7 +379,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus const participants: BinaryNode[] = [] - const destinationJid = (!isStatus) ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid + const destinationJid = !isStatus ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid const binaryNodeContent: BinaryNode[] = [] const devices: JidWithDevice[] = [] @@ -355,234 +392,233 @@ export const makeMessagesSocket = (config: SocketConfig) => { const extraAttrs = {} - if(participant) { + if (participant) { // when the retry request is not for a group // only send to the specific device that asked for a retry // otherwise the message is sent out to every device that should be a recipient - if(!isGroup && !isStatus) { - additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' } + if (!isGroup && !isStatus) { + additionalAttributes = { ...additionalAttributes, device_fanout: 'false' } } const { user, device } = jidDecode(participant.jid)! devices.push({ user, device }) } - await authState.keys.transaction( - async() => { - const mediaType = getMediaType(message) - if(mediaType) { - extraAttrs['mediatype'] = mediaType - } + await authState.keys.transaction(async () => { + const mediaType = getMediaType(message) + if (mediaType) { + extraAttrs['mediatype'] = mediaType + } - if(normalizeMessageContent(message)?.pinInChatMessage) { - extraAttrs['decrypt-fail'] = 'hide' - } + if (normalizeMessageContent(message)?.pinInChatMessage) { + extraAttrs['decrypt-fail'] = 'hide' + } - if(isGroup || isStatus) { - const [groupData, senderKeyMap] = await Promise.all([ - (async() => { - let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined - if(groupData && Array.isArray(groupData?.participants)) { - logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata') - } else if(!isStatus) { - groupData = await groupMetadata(jid) - } - - return groupData - })(), - (async() => { - if(!participant && !isStatus) { - const result = await authState.keys.get('sender-key-memory', [jid]) - return result[jid] || { } - } - - return { } - })() - ]) - - if(!participant) { - const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : [] - if(isStatus && statusJidList) { - participantsList.push(...statusJidList) + if (isGroup || isStatus) { + const [groupData, senderKeyMap] = await Promise.all([ + (async () => { + let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined + if (groupData && Array.isArray(groupData?.participants)) { + logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata') + } else if (!isStatus) { + groupData = await groupMetadata(jid) } - if(!isStatus) { - additionalAttributes = { - ...additionalAttributes, - addressing_mode: groupData?.addressingMode || 'pn' - } + return groupData + })(), + (async () => { + if (!participant && !isStatus) { + const result = await authState.keys.get('sender-key-memory', [jid]) + return result[jid] || {} } - const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false) + return {} + })() + ]) + + if (!participant) { + const participantsList = groupData && !isStatus ? groupData.participants.map(p => p.id) : [] + if (isStatus && statusJidList) { + participantsList.push(...statusJidList) + } + + if (!isStatus) { + additionalAttributes = { + ...additionalAttributes, + addressing_mode: groupData?.addressingMode || 'pn' + } + } + + const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false) + devices.push(...additionalDevices) + } + + const patched = await patchMessageBeforeSending(message) + + if (Array.isArray(patched)) { + throw new Boom('Per-jid patching is not supported in groups') + } + + const bytes = encodeWAMessage(patched) + + const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ + group: destinationJid, + data: bytes, + meId + }) + + const senderKeyJids: string[] = [] + // ensure a connection is established with every device + for (const { user, device } of devices) { + const jid = jidEncode(user, groupData?.addressingMode === 'lid' ? 'lid' : 's.whatsapp.net', device) + if (!senderKeyMap[jid] || !!participant) { + senderKeyJids.push(jid) + // store that this person has had the sender keys sent to them + senderKeyMap[jid] = true + } + } + + // if there are some participants with whom the session has not been established + // if there are, we re-send the senderkey + if (senderKeyJids.length) { + logger.debug({ senderKeyJids }, 'sending new sender key') + + const senderKeyMsg: proto.IMessage = { + senderKeyDistributionMessage: { + axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, + groupId: destinationJid + } + } + + await assertSessions(senderKeyJids, false) + + const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs) + shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity + + participants.push(...result.nodes) + } + + binaryNodeContent.push({ + tag: 'enc', + attrs: { v: '2', type: 'skmsg' }, + content: ciphertext + }) + + await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) + } else { + const { user: meUser } = jidDecode(meId)! + + if (!participant) { + devices.push({ user }) + if (user !== meUser) { + devices.push({ user: meUser }) + } + + if (additionalAttributes?.['category'] !== 'peer') { + const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true) devices.push(...additionalDevices) } + } - const patched = await patchMessageBeforeSending(message) - - if(Array.isArray(patched)) { - throw new Boom('Per-jid patching is not supported in groups') - } - - const bytes = encodeWAMessage(patched) - - const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage( - { - group: destinationJid, - data: bytes, - meId, - } + const allJids: string[] = [] + const meJids: string[] = [] + const otherJids: string[] = [] + for (const { user, device } of devices) { + const isMe = user === meUser + const jid = jidEncode( + isMe && isLid ? authState.creds?.me?.lid!.split(':')[0] || user : user, + isLid ? 'lid' : 's.whatsapp.net', + device ) - - const senderKeyJids: string[] = [] - // ensure a connection is established with every device - for(const { user, device } of devices) { - const jid = jidEncode(user, groupData?.addressingMode === 'lid' ? 'lid' : 's.whatsapp.net', device) - if(!senderKeyMap[jid] || !!participant) { - senderKeyJids.push(jid) - // store that this person has had the sender keys sent to them - senderKeyMap[jid] = true - } - } - - // if there are some participants with whom the session has not been established - // if there are, we re-send the senderkey - if(senderKeyJids.length) { - logger.debug({ senderKeyJids }, 'sending new sender key') - - const senderKeyMsg: proto.IMessage = { - senderKeyDistributionMessage: { - axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, - groupId: destinationJid - } - } - - await assertSessions(senderKeyJids, false) - - const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs) - shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity - - participants.push(...result.nodes) - } - - binaryNodeContent.push({ - tag: 'enc', - attrs: { v: '2', type: 'skmsg' }, - content: ciphertext - }) - - await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) - } else { - const { user: meUser } = jidDecode(meId)! - - if(!participant) { - devices.push({ user }) - if(user !== meUser) { - devices.push({ user: meUser }) - } - - if(additionalAttributes?.['category'] !== 'peer') { - const additionalDevices = await getUSyncDevices([ meId, jid ], !!useUserDevicesCache, true) - devices.push(...additionalDevices) - } - } - - const allJids: string[] = [] - const meJids: string[] = [] - const otherJids: string[] = [] - for(const { user, device } of devices) { - const isMe = user === meUser - const jid = jidEncode(isMe && isLid ? authState.creds?.me?.lid!.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device) - if(isMe) { - meJids.push(jid) - } else { - otherJids.push(jid) - } - - allJids.push(jid) - } - - await assertSessions(allJids, false) - - const [ - { nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, - { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } - ] = await Promise.all([ - createParticipantNodes(meJids, meMsg, extraAttrs), - createParticipantNodes(otherJids, message, extraAttrs) - ]) - participants.push(...meNodes) - participants.push(...otherNodes) - - shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 - } - - if(participants.length) { - if(additionalAttributes?.['category'] === 'peer') { - const peerNode = participants[0]?.content?.[0] as BinaryNode - if(peerNode) { - binaryNodeContent.push(peerNode) // push only enc - } + if (isMe) { + meJids.push(jid) } else { - binaryNodeContent.push({ - tag: 'participants', - attrs: { }, - content: participants - }) + otherJids.push(jid) } + + allJids.push(jid) } - const stanza: BinaryNode = { - tag: 'message', - attrs: { - id: msgId, - type: getMessageType(message), - ...(additionalAttributes || {}) - }, - content: binaryNodeContent - } - // if the participant to send to is explicitly specified (generally retry recp) - // ensure the message is only sent to that person - // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg - if(participant) { - if(isJidGroup(destinationJid)) { - stanza.attrs.to = destinationJid - stanza.attrs.participant = participant.jid - } else if(areJidsSameUser(participant.jid, meId)) { - stanza.attrs.to = participant.jid - stanza.attrs.recipient = destinationJid - } else { - stanza.attrs.to = participant.jid - } - } else { - stanza.attrs.to = destinationJid - } + await assertSessions(allJids, false) - if(shouldIncludeDeviceIdentity) { - (stanza.content as BinaryNode[]).push({ - tag: 'device-identity', - attrs: { }, - content: encodeSignedDeviceIdentity(authState.creds.account!, true) - }) + const [ + { nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, + { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } + ] = await Promise.all([ + createParticipantNodes(meJids, meMsg, extraAttrs), + createParticipantNodes(otherJids, message, extraAttrs) + ]) + participants.push(...meNodes) + participants.push(...otherNodes) - logger.debug({ jid }, 'adding device identity') - } - - if(additionalNodes && additionalNodes.length > 0) { - (stanza.content as BinaryNode[]).push(...additionalNodes) - } - - logger.debug({ msgId }, `sending message to ${participants.length} devices`) - - await sendNode(stanza) + shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 } - ) + + if (participants.length) { + if (additionalAttributes?.['category'] === 'peer') { + const peerNode = participants[0]?.content?.[0] as BinaryNode + if (peerNode) { + binaryNodeContent.push(peerNode) // push only enc + } + } else { + binaryNodeContent.push({ + tag: 'participants', + attrs: {}, + content: participants + }) + } + } + + const stanza: BinaryNode = { + tag: 'message', + attrs: { + id: msgId, + type: getMessageType(message), + ...(additionalAttributes || {}) + }, + content: binaryNodeContent + } + // if the participant to send to is explicitly specified (generally retry recp) + // ensure the message is only sent to that person + // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg + if (participant) { + if (isJidGroup(destinationJid)) { + stanza.attrs.to = destinationJid + stanza.attrs.participant = participant.jid + } else if (areJidsSameUser(participant.jid, meId)) { + stanza.attrs.to = participant.jid + stanza.attrs.recipient = destinationJid + } else { + stanza.attrs.to = participant.jid + } + } else { + stanza.attrs.to = destinationJid + } + + if (shouldIncludeDeviceIdentity) { + ;(stanza.content as BinaryNode[]).push({ + tag: 'device-identity', + attrs: {}, + content: encodeSignedDeviceIdentity(authState.creds.account!, true) + }) + + logger.debug({ jid }, 'adding device identity') + } + + if (additionalNodes && additionalNodes.length > 0) { + ;(stanza.content as BinaryNode[]).push(...additionalNodes) + } + + logger.debug({ msgId }, `sending message to ${participants.length} devices`) + + await sendNode(stanza) + }) return msgId } - const getMessageType = (message: proto.IMessage) => { - if(message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) { + if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) { return 'poll' } @@ -590,40 +626,40 @@ export const makeMessagesSocket = (config: SocketConfig) => { } const getMediaType = (message: proto.IMessage) => { - if(message.imageMessage) { + if (message.imageMessage) { return 'image' - } else if(message.videoMessage) { + } else if (message.videoMessage) { return message.videoMessage.gifPlayback ? 'gif' : 'video' - } else if(message.audioMessage) { + } else if (message.audioMessage) { return message.audioMessage.ptt ? 'ptt' : 'audio' - } else if(message.contactMessage) { + } else if (message.contactMessage) { return 'vcard' - } else if(message.documentMessage) { + } else if (message.documentMessage) { return 'document' - } else if(message.contactsArrayMessage) { + } else if (message.contactsArrayMessage) { return 'contact_array' - } else if(message.liveLocationMessage) { + } else if (message.liveLocationMessage) { return 'livelocation' - } else if(message.stickerMessage) { + } else if (message.stickerMessage) { return 'sticker' - } else if(message.listMessage) { + } else if (message.listMessage) { return 'list' - } else if(message.listResponseMessage) { + } else if (message.listResponseMessage) { return 'list_response' - } else if(message.buttonsResponseMessage) { + } else if (message.buttonsResponseMessage) { return 'buttons_response' - } else if(message.orderMessage) { + } else if (message.orderMessage) { return 'order' - } else if(message.productMessage) { + } else if (message.productMessage) { return 'product' - } else if(message.interactiveResponseMessage) { + } else if (message.interactiveResponseMessage) { return 'native_flow_response' - } else if(message.groupInviteMessage) { + } else if (message.groupInviteMessage) { return 'url' } } - const getPrivacyTokens = async(jids: string[]) => { + const getPrivacyTokens = async (jids: string[]) => { const t = unixTimestampSeconds().toString() const result = await query({ tag: 'iq', @@ -635,17 +671,15 @@ export const makeMessagesSocket = (config: SocketConfig) => { content: [ { tag: 'tokens', - attrs: { }, - content: jids.map( - jid => ({ - tag: 'token', - attrs: { - jid: jidNormalizedUser(jid), - t, - type: 'trusted_contact' - } - }) - ) + attrs: {}, + content: jids.map(jid => ({ + tag: 'token', + attrs: { + jid: jidNormalizedUser(jid), + t, + type: 'trusted_contact' + } + })) } ] }) @@ -671,141 +705,134 @@ export const makeMessagesSocket = (config: SocketConfig) => { sendPeerDataOperationMessage, createParticipantNodes, getUSyncDevices, - updateMediaMessage: async(message: proto.IWebMessageInfo) => { + updateMediaMessage: async (message: proto.IWebMessageInfo) => { const content = assertMediaContent(message.message) const mediaKey = content.mediaKey! const meId = authState.creds.me!.id const node = await encryptMediaRetryRequest(message.key, mediaKey, meId) let error: Error | undefined = undefined - await Promise.all( - [ - sendNode(node), - waitForMsgMediaUpdate(async(update) => { - const result = update.find(c => c.key.id === message.key.id) - if(result) { - if(result.error) { - error = result.error - } else { - try { - const media = await decryptMediaRetryData(result.media!, mediaKey, result.key.id!) - if(media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) { - const resultStr = proto.MediaRetryNotification.ResultType[media.result!] - throw new Boom( - `Media re-upload failed by device (${resultStr})`, - { data: media, statusCode: getStatusCodeForMediaRetry(media.result!) || 404 } - ) - } - - content.directPath = media.directPath - content.url = getUrlFromDirectPath(content.directPath!) - - logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful') - } catch(err) { - error = err + await Promise.all([ + sendNode(node), + waitForMsgMediaUpdate(async update => { + const result = update.find(c => c.key.id === message.key.id) + if (result) { + if (result.error) { + error = result.error + } else { + try { + const media = await decryptMediaRetryData(result.media!, mediaKey, result.key.id!) + if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) { + const resultStr = proto.MediaRetryNotification.ResultType[media.result!] + throw new Boom(`Media re-upload failed by device (${resultStr})`, { + data: media, + statusCode: getStatusCodeForMediaRetry(media.result!) || 404 + }) } + + content.directPath = media.directPath + content.url = getUrlFromDirectPath(content.directPath!) + + logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful') + } catch (err) { + error = err } - - return true } - }) - ] - ) - if(error) { + return true + } + }) + ]) + + if (error) { throw error } - ev.emit('messages.update', [ - { key: message.key, update: { message: message.message } } - ]) + ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]) return message }, - sendMessage: async( - jid: string, - content: AnyMessageContent, - options: MiscMessageGenerationOptions = { } - ) => { + sendMessage: async (jid: string, content: AnyMessageContent, options: MiscMessageGenerationOptions = {}) => { const userJid = authState.creds.me!.id - if( + if ( typeof content === 'object' && 'disappearingMessagesInChat' in content && typeof content['disappearingMessagesInChat'] !== 'undefined' && isJidGroup(jid) ) { const { disappearingMessagesInChat } = content - const value = typeof disappearingMessagesInChat === 'boolean' ? - (disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) : - disappearingMessagesInChat + const value = + typeof disappearingMessagesInChat === 'boolean' + ? disappearingMessagesInChat + ? WA_DEFAULT_EPHEMERAL + : 0 + : disappearingMessagesInChat await groupToggleEphemeral(jid, value) } else { - const fullMsg = await generateWAMessage( - jid, - content, - { - logger, - userJid, - getUrlInfo: text => getUrlInfo( - text, - { - thumbnailWidth: linkPreviewImageThumbnailWidth, - fetchOpts: { - timeout: 3_000, - ...axiosOptions || { } - }, - logger, - uploadImage: generateHighQualityLinkPreview - ? waUploadToServer - : undefined + const fullMsg = await generateWAMessage(jid, content, { + logger, + userJid, + getUrlInfo: text => + getUrlInfo(text, { + thumbnailWidth: linkPreviewImageThumbnailWidth, + fetchOpts: { + timeout: 3_000, + ...(axiosOptions || {}) }, - ), - //TODO: CACHE - getProfilePicUrl: sock.profilePictureUrl, - upload: waUploadToServer, - mediaCache: config.mediaCache, - options: config.options, - messageId: generateMessageIDV2(sock.user?.id), - ...options, - } - ) + logger, + uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined + }), + //TODO: CACHE + getProfilePicUrl: sock.profilePictureUrl, + upload: waUploadToServer, + mediaCache: config.mediaCache, + options: config.options, + messageId: generateMessageIDV2(sock.user?.id), + ...options + }) const isDeleteMsg = 'delete' in content && !!content.delete const isEditMsg = 'edit' in content && !!content.edit const isPinMsg = 'pin' in content && !!content.pin const isPollMessage = 'poll' in content && !!content.poll - const additionalAttributes: BinaryNodeAttributes = { } + const additionalAttributes: BinaryNodeAttributes = {} const additionalNodes: BinaryNode[] = [] // required for delete - if(isDeleteMsg) { + if (isDeleteMsg) { // if the chat is a group, and I am not the author, then delete the message as an admin - if(isJidGroup(content.delete?.remoteJid as string) && !content.delete?.fromMe) { + if (isJidGroup(content.delete?.remoteJid as string) && !content.delete?.fromMe) { additionalAttributes.edit = '8' } else { additionalAttributes.edit = '7' } - } else if(isEditMsg) { + } else if (isEditMsg) { additionalAttributes.edit = '1' - } else if(isPinMsg) { + } else if (isPinMsg) { additionalAttributes.edit = '2' - } else if(isPollMessage) { + } else if (isPollMessage) { additionalNodes.push({ tag: 'meta', attrs: { polltype: 'creation' - }, + } } as BinaryNode) } - if('cachedGroupMetadata' in options) { - console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.') + if ('cachedGroupMetadata' in options) { + console.warn( + 'cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.' + ) } - await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, useCachedGroupMetadata: options.useCachedGroupMetadata, additionalAttributes, statusJidList: options.statusJidList, additionalNodes }) - if(config.emitOwnEvents) { + await relayMessage(jid, fullMsg.message!, { + messageId: fullMsg.key.id!, + useCachedGroupMetadata: options.useCachedGroupMetadata, + additionalAttributes, + statusJidList: options.statusJidList, + additionalNodes + }) + if (config.emitOwnEvents) { process.nextTick(() => { - processingMutex.mutex(() => ( - upsertMessage(fullMsg, 'append') - )) + processingMutex.mutex(() => upsertMessage(fullMsg, 'append')) }) } diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index 08f86b1..4f7b051 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -28,7 +28,7 @@ import { getPlatformId, makeEventBuffer, makeNoiseHandler, - promiseTimeout, + promiseTimeout } from '../Utils' import { assertNodeErrorFree, @@ -61,21 +61,22 @@ export const makeSocket = (config: SocketConfig) => { defaultQueryTimeoutMs, transactionOpts, qrTimeout, - makeSignalRepository, + makeSignalRepository } = config - if(printQRInTerminal) { - console.warn('⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.') + if (printQRInTerminal) { + console.warn( + '⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.' + ) } const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl - - if(config.mobile || url.protocol === 'tcp:') { + if (config.mobile || url.protocol === 'tcp:') { throw new Boom('Mobile API is not supported anymore', { statusCode: DisconnectReason.loggedOut }) } - if(url.protocol === 'wss' && authState?.creds?.routingInfo) { + if (url.protocol === 'wss' && authState?.creds?.routingInfo) { url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url')) } @@ -110,28 +111,25 @@ export const makeSocket = (config: SocketConfig) => { const sendPromise = promisify(ws.send) /** send a raw buffer */ - const sendRawMessage = async(data: Uint8Array | Buffer) => { - if(!ws.isOpen) { + const sendRawMessage = async (data: Uint8Array | Buffer) => { + if (!ws.isOpen) { throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }) } const bytes = noise.encodeFrame(data) - await promiseTimeout( - connectTimeoutMs, - async(resolve, reject) => { - try { - await sendPromise.call(ws, bytes) - resolve() - } catch(error) { - reject(error) - } + await promiseTimeout(connectTimeoutMs, async (resolve, reject) => { + try { + await sendPromise.call(ws, bytes) + resolve() + } catch (error) { + reject(error) } - ) + }) } /** send a binary node */ const sendNode = (frame: BinaryNode) => { - if(logger.level === 'trace') { + if (logger.level === 'trace') { logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' }) } @@ -141,15 +139,12 @@ export const makeSocket = (config: SocketConfig) => { /** log & process any unexpected errors */ const onUnexpectedError = (err: Error | Boom, msg: string) => { - logger.error( - { err }, - `unexpected error in '${msg}'` - ) + logger.error({ err }, `unexpected error in '${msg}'`) } /** await the next incoming message */ - const awaitNextMessage = async(sendMsg?: Uint8Array) => { - if(!ws.isOpen) { + const awaitNextMessage = async (sendMsg?: Uint8Array) => { + if (!ws.isOpen) { throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }) @@ -164,14 +159,13 @@ export const makeSocket = (config: SocketConfig) => { ws.on('frame', onOpen) ws.on('close', onClose) ws.on('error', onClose) + }).finally(() => { + ws.off('frame', onOpen) + ws.off('close', onClose) + ws.off('error', onClose) }) - .finally(() => { - ws.off('frame', onOpen) - ws.off('close', onClose) - ws.off('error', onClose) - }) - if(sendMsg) { + if (sendMsg) { sendRawMessage(sendMsg).catch(onClose!) } @@ -183,22 +177,20 @@ export const makeSocket = (config: SocketConfig) => { * @param msgId the message tag to await * @param timeoutMs timeout after which the promise will reject */ - const waitForMessage = async(msgId: string, timeoutMs = defaultQueryTimeoutMs) => { + const waitForMessage = async (msgId: string, timeoutMs = defaultQueryTimeoutMs) => { let onRecv: (json) => void let onErr: (err) => void try { - const result = await promiseTimeout(timeoutMs, - (resolve, reject) => { - onRecv = resolve - onErr = err => { - reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })) - } + const result = await promiseTimeout(timeoutMs, (resolve, reject) => { + onRecv = resolve + onErr = err => { + reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })) + } - ws.on(`TAG:${msgId}`, onRecv) - ws.on('close', onErr) // if the socket closes, you'll never receive the message - ws.off('error', onErr) - }, - ) + ws.on(`TAG:${msgId}`, onRecv) + ws.on('close', onErr) // if the socket closes, you'll never receive the message + ws.off('error', onErr) + }) return result as any } finally { @@ -209,19 +201,16 @@ export const makeSocket = (config: SocketConfig) => { } /** send a query, and wait for its response. auto-generates message ID if not provided */ - const query = async(node: BinaryNode, timeoutMs?: number) => { - if(!node.attrs.id) { + const query = async (node: BinaryNode, timeoutMs?: number) => { + if (!node.attrs.id) { node.attrs.id = generateMessageTag() } const msgId = node.attrs.id - const [result] = await Promise.all([ - waitForMessage(msgId, timeoutMs), - sendNode(node) - ]) + const [result] = await Promise.all([waitForMessage(msgId, timeoutMs), sendNode(node)]) - if('tag' in result) { + if ('tag' in result) { assertNodeErrorFree(result) } @@ -229,7 +218,7 @@ export const makeSocket = (config: SocketConfig) => { } /** connection handshake */ - const validateConnection = async() => { + const validateConnection = async () => { let helloMsg: proto.IHandshakeMessage = { clientHello: { ephemeral: ephemeralKeyPair.public } } @@ -247,7 +236,7 @@ export const makeSocket = (config: SocketConfig) => { const keyEnc = await noise.processHandshake(handshake, creds.noiseKey) let node: proto.IClientPayload - if(!creds.me) { + if (!creds.me) { node = generateRegistrationNode(creds, config) logger.info({ node }, 'not logged in, attempting registration...') } else { @@ -255,22 +244,20 @@ export const makeSocket = (config: SocketConfig) => { logger.info({ node }, 'logging in...') } - const payloadEnc = noise.encrypt( - proto.ClientPayload.encode(node).finish() - ) + const payloadEnc = noise.encrypt(proto.ClientPayload.encode(node).finish()) await sendRawMessage( proto.HandshakeMessage.encode({ clientFinish: { static: keyEnc, - payload: payloadEnc, - }, + payload: payloadEnc + } }).finish() ) noise.finishInit() startKeepAliveRequest() } - const getAvailablePreKeysOnServer = async() => { + const getAvailablePreKeysOnServer = async () => { const result = await query({ tag: 'iq', attrs: { @@ -279,33 +266,29 @@ export const makeSocket = (config: SocketConfig) => { type: 'get', to: S_WHATSAPP_NET }, - content: [ - { tag: 'count', attrs: {} } - ] + content: [{ tag: 'count', attrs: {} }] }) const countChild = getBinaryNodeChild(result, 'count') return +countChild!.attrs.value } /** generates and uploads a set of pre-keys to the server */ - const uploadPreKeys = async(count = INITIAL_PREKEY_COUNT) => { - await keys.transaction( - async() => { - logger.info({ count }, 'uploading pre-keys') - const { update, node } = await getNextPreKeysNode({ creds, keys }, count) + const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT) => { + await keys.transaction(async () => { + logger.info({ count }, 'uploading pre-keys') + const { update, node } = await getNextPreKeysNode({ creds, keys }, count) - await query(node) - ev.emit('creds.update', update) + await query(node) + ev.emit('creds.update', update) - logger.info({ count }, 'uploaded pre-keys') - } - ) + logger.info({ count }, 'uploaded pre-keys') + }) } - const uploadPreKeysToServerIfRequired = async() => { + const uploadPreKeysToServerIfRequired = async () => { const preKeyCount = await getAvailablePreKeysOnServer() logger.info(`${preKeyCount} pre-keys found on server`) - if(preKeyCount <= MIN_PREKEY_COUNT) { + if (preKeyCount <= MIN_PREKEY_COUNT) { await uploadPreKeys() } } @@ -319,10 +302,10 @@ export const makeSocket = (config: SocketConfig) => { anyTriggered = ws.emit('frame', frame) // if it's a binary node - if(!(frame instanceof Uint8Array)) { + if (!(frame instanceof Uint8Array)) { const msgId = frame.attrs.id - if(logger.level === 'trace') { + if (logger.level === 'trace') { logger.trace({ xml: binaryNodeToString(frame), msg: 'recv xml' }) } @@ -333,7 +316,7 @@ export const makeSocket = (config: SocketConfig) => { const l1 = frame.attrs || {} const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : '' - for(const key of Object.keys(l1)) { + for (const key of Object.keys(l1)) { anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered @@ -342,7 +325,7 @@ export const makeSocket = (config: SocketConfig) => { anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered - if(!anyTriggered && logger.level === 'debug') { + if (!anyTriggered && logger.level === 'debug') { logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv') } } @@ -350,16 +333,13 @@ export const makeSocket = (config: SocketConfig) => { } const end = (error: Error | undefined) => { - if(closed) { + if (closed) { logger.trace({ trace: error?.stack }, 'connection already closed') return } closed = true - logger.info( - { trace: error?.stack }, - error ? 'connection errored' : 'connection closed' - ) + logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed') clearInterval(keepAliveReq) clearTimeout(qrTimer) @@ -369,10 +349,10 @@ export const makeSocket = (config: SocketConfig) => { ws.removeAllListeners('open') ws.removeAllListeners('message') - if(!ws.isClosed && !ws.isClosing) { + if (!ws.isClosed && !ws.isClosing) { try { ws.close() - } catch{ } + } catch {} } ev.emit('connection.update', { @@ -385,12 +365,12 @@ export const makeSocket = (config: SocketConfig) => { ev.removeAllListeners('connection.update') } - const waitForSocketOpen = async() => { - if(ws.isOpen) { + const waitForSocketOpen = async () => { + if (ws.isOpen) { return } - if(ws.isClosed || ws.isClosing) { + if (ws.isClosed || ws.isClosing) { throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }) } @@ -402,17 +382,16 @@ export const makeSocket = (config: SocketConfig) => { ws.on('open', onOpen) ws.on('close', onClose) ws.on('error', onClose) + }).finally(() => { + ws.off('open', onOpen) + ws.off('close', onClose) + ws.off('error', onClose) }) - .finally(() => { - ws.off('open', onOpen) - ws.off('close', onClose) - ws.off('error', onClose) - }) } - const startKeepAliveRequest = () => ( - keepAliveReq = setInterval(() => { - if(!lastDateRecv) { + const startKeepAliveRequest = () => + (keepAliveReq = setInterval(() => { + if (!lastDateRecv) { lastDateRecv = new Date() } @@ -421,49 +400,42 @@ export const makeSocket = (config: SocketConfig) => { check if it's been a suspicious amount of time since the server responded with our last seen it could be that the network is down */ - if(diff > keepAliveIntervalMs + 5000) { + if (diff > keepAliveIntervalMs + 5000) { end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost })) - } else if(ws.isOpen) { + } else if (ws.isOpen) { // if its all good, send a keep alive request - query( - { - tag: 'iq', - attrs: { - id: generateMessageTag(), - to: S_WHATSAPP_NET, - type: 'get', - xmlns: 'w:p', - }, - content: [{ tag: 'ping', attrs: {} }] - } - ) - .catch(err => { - logger.error({ trace: err.stack }, 'error in sending keep alive') - }) + query({ + tag: 'iq', + attrs: { + id: generateMessageTag(), + to: S_WHATSAPP_NET, + type: 'get', + xmlns: 'w:p' + }, + content: [{ tag: 'ping', attrs: {} }] + }).catch(err => { + logger.error({ trace: err.stack }, 'error in sending keep alive') + }) } else { logger.warn('keep alive called when WS not open') } - }, keepAliveIntervalMs) - ) + }, keepAliveIntervalMs)) /** i have no idea why this exists. pls enlighten me */ - const sendPassiveIq = (tag: 'passive' | 'active') => ( + const sendPassiveIq = (tag: 'passive' | 'active') => query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'passive', - type: 'set', + type: 'set' }, - content: [ - { tag, attrs: {} } - ] + content: [{ tag, attrs: {} }] }) - ) /** logout & invalidate connection */ - const logout = async(msg?: string) => { + const logout = async (msg?: string) => { const jid = authState.creds.me?.id - if(jid) { + if (jid) { await sendNode({ tag: 'iq', attrs: { @@ -487,7 +459,7 @@ export const makeSocket = (config: SocketConfig) => { end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut })) } - const requestPairingCode = async(phoneNumber: string): Promise => { + const requestPairingCode = async (phoneNumber: string): Promise => { authState.creds.pairingCode = bytesToCrockford(randomBytes(5)) authState.creds.me = { id: jidEncode(phoneNumber, 's.whatsapp.net'), @@ -572,10 +544,10 @@ export const makeSocket = (config: SocketConfig) => { ws.on('message', onMessageReceived) - ws.on('open', async() => { + ws.on('open', async () => { try { await validateConnection() - } catch(err) { + } catch (err) { logger.error({ err }, 'error in validating connection') end(err) } @@ -583,15 +555,17 @@ export const makeSocket = (config: SocketConfig) => { ws.on('error', mapWebSocketError(end)) ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed }))) // the server terminated the connection - ws.on('CB:xmlstreamend', () => end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed }))) + ws.on('CB:xmlstreamend', () => + end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed })) + ) // QR gen - ws.on('CB:iq,type:set,pair-device', async(stanza: BinaryNode) => { + ws.on('CB:iq,type:set,pair-device', async (stanza: BinaryNode) => { const iq: BinaryNode = { tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'result', - id: stanza.attrs.id, + id: stanza.attrs.id } } await sendNode(iq) @@ -604,12 +578,12 @@ export const makeSocket = (config: SocketConfig) => { let qrMs = qrTimeout || 60_000 // time to let a QR live const genPairQR = () => { - if(!ws.isOpen) { + if (!ws.isOpen) { return } const refNode = refNodes.shift() - if(!refNode) { + if (!refNode) { end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut })) return } @@ -627,7 +601,7 @@ export const makeSocket = (config: SocketConfig) => { }) // device paired for the first time // if device pairs successfully, the server asks to restart the connection - ws.on('CB:iq,,pair-success', async(stanza: BinaryNode) => { + ws.on('CB:iq,,pair-success', async (stanza: BinaryNode) => { logger.debug('pair success recv') try { const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds) @@ -641,13 +615,13 @@ export const makeSocket = (config: SocketConfig) => { ev.emit('connection.update', { isNewLogin: true, qr: undefined }) await sendNode(reply) - } catch(error) { + } catch (error) { logger.info({ trace: error.stack }, 'error in pairing') end(error) } }) // login complete - ws.on('CB:success', async(node: BinaryNode) => { + ws.on('CB:success', async (node: BinaryNode) => { await uploadPreKeysToServerIfRequired() await sendPassiveIq('active') @@ -677,7 +651,7 @@ export const makeSocket = (config: SocketConfig) => { }) ws.on('CB:ib,,offline_preview', (node: BinaryNode) => { - logger.info('offline preview received', JSON.stringify(node)) + logger.info('offline preview received', JSON.stringify(node)) sendNode({ tag: 'ib', attrs: {}, @@ -688,7 +662,7 @@ export const makeSocket = (config: SocketConfig) => { ws.on('CB:ib,,edge_routing', (node: BinaryNode) => { const edgeRoutingNode = getBinaryNodeChild(node, 'edge_routing') const routingInfo = getBinaryNodeChild(edgeRoutingNode, 'routing_info') - if(routingInfo?.content) { + if (routingInfo?.content) { authState.creds.routingInfo = Buffer.from(routingInfo?.content as Uint8Array) ev.emit('creds.update', authState.creds) } @@ -696,7 +670,7 @@ export const makeSocket = (config: SocketConfig) => { let didStartBuffer = false process.nextTick(() => { - if(creds.me?.id) { + if (creds.me?.id) { // start buffering important events // if we're logged in ev.buffer() @@ -712,7 +686,7 @@ export const makeSocket = (config: SocketConfig) => { const offlineNotifs = +(child?.attrs.count || 0) logger.info(`handled ${offlineNotifs} offline messages/notifications`) - if(didStartBuffer) { + if (didStartBuffer) { ev.flush() logger.trace('flushed events for initial buffer') } @@ -724,21 +698,19 @@ export const makeSocket = (config: SocketConfig) => { ev.on('creds.update', update => { const name = update.me?.name // if name has just been received - if(creds.me?.name !== name) { + if (creds.me?.name !== name) { logger.debug({ name }, 'updated pushName') sendNode({ tag: 'presence', attrs: { name: name! } + }).catch(err => { + logger.warn({ trace: err.stack }, 'error in sending presence update on name change') }) - .catch(err => { - logger.warn({ trace: err.stack }, 'error in sending presence update on name change') - }) } Object.assign(creds, update) }) - return { type: 'md' as 'md', ws, @@ -762,7 +734,7 @@ export const makeSocket = (config: SocketConfig) => { requestPairingCode, /** Waits for the connection to WA to reach a state */ waitForConnectionUpdate: bindWaitForConnectionUpdate(ev), - sendWAMBuffer, + sendWAMBuffer } } @@ -772,11 +744,6 @@ export const makeSocket = (config: SocketConfig) => { * */ function mapWebSocketError(handler: (err: Error) => void) { return (error: Error) => { - handler( - new Boom( - `WebSocket Error (${error?.message})`, - { statusCode: getCodeFromWSError(error), data: error } - ) - ) + handler(new Boom(`WebSocket Error (${error?.message})`, { statusCode: getCodeFromWSError(error), data: error })) } } diff --git a/src/Socket/usync.ts b/src/Socket/usync.ts index efeb90d..4ee110a 100644 --- a/src/Socket/usync.ts +++ b/src/Socket/usync.ts @@ -7,13 +7,10 @@ import { makeSocket } from './socket' export const makeUSyncSocket = (config: SocketConfig) => { const sock = makeSocket(config) - const { - generateMessageTag, - query, - } = sock + const { generateMessageTag, query } = sock - const executeUSyncQuery = async(usyncQuery: USyncQuery) => { - if(usyncQuery.protocols.length === 0) { + const executeUSyncQuery = async (usyncQuery: USyncQuery) => { + if (usyncQuery.protocols.length === 0) { throw new Boom('USyncQuery must have at least one protocol') } @@ -21,15 +18,13 @@ export const makeUSyncSocket = (config: SocketConfig) => { // variable below has only validated users const validUsers = usyncQuery.users - const userNodes = validUsers.map((user) => { + const userNodes = validUsers.map(user => { return { tag: 'user', attrs: { - jid: !user.phone ? user.id : undefined, + jid: !user.phone ? user.id : undefined }, - content: usyncQuery.protocols - .map((a) => a.getUserElement(user)) - .filter(a => a !== null) + content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null) } as BinaryNode }) @@ -42,14 +37,14 @@ export const makeUSyncSocket = (config: SocketConfig) => { const queryNode: BinaryNode = { tag: 'query', attrs: {}, - content: usyncQuery.protocols.map((a) => a.getQueryElement()) + content: usyncQuery.protocols.map(a => a.getQueryElement()) } const iq = { tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', - xmlns: 'usync', + xmlns: 'usync' }, content: [ { @@ -59,14 +54,11 @@ export const makeUSyncSocket = (config: SocketConfig) => { mode: usyncQuery.mode, sid: generateMessageTag(), last: 'true', - index: '0', + index: '0' }, - content: [ - queryNode, - listNode - ] + content: [queryNode, listNode] } - ], + ] } const result = await query(iq) @@ -76,6 +68,6 @@ export const makeUSyncSocket = (config: SocketConfig) => { return { ...sock, - executeUSyncQuery, + executeUSyncQuery } -} \ No newline at end of file +} diff --git a/src/Tests/test.app-state-sync.ts b/src/Tests/test.app-state-sync.ts index 14f925e..d3979d7 100644 --- a/src/Tests/test.app-state-sync.ts +++ b/src/Tests/test.app-state-sync.ts @@ -4,7 +4,6 @@ import { processSyncAction } from '../Utils/chat-utils' import logger from '../Utils/logger' describe('App State Sync Tests', () => { - const me: Contact = { id: randomJid() } // case when initial sync is off it('should return archive=false event', () => { @@ -57,7 +56,7 @@ describe('App State Sync Tests', () => { ] ] - for(const mutations of CASES) { + for (const mutations of CASES) { const events = processSyncAction(mutations, me, undefined, logger) expect(events['chats.update']).toHaveLength(1) const event = events['chats.update']?.[0] @@ -129,7 +128,7 @@ describe('App State Sync Tests', () => { } } } - ], + ] ] const ctx: InitialAppStateSyncOptions = { @@ -139,7 +138,7 @@ describe('App State Sync Tests', () => { accountSettings: { unarchiveChats: true } } - for(const mutations of CASES) { + for (const mutations of CASES) { const events = processSyncActions(mutations, me, ctx, logger) expect(events['chats.update']?.length).toBeFalsy() } @@ -152,7 +151,7 @@ describe('App State Sync Tests', () => { const index = ['archive', jid] const now = unixTimestampSeconds() - const CASES: { settings: AccountSettings, mutations: ChatMutation[] }[] = [ + const CASES: { settings: AccountSettings; mutations: ChatMutation[] }[] = [ { settings: { unarchiveChats: true }, mutations: [ @@ -169,7 +168,7 @@ describe('App State Sync Tests', () => { } } } - ], + ] }, { settings: { unarchiveChats: false }, @@ -187,11 +186,11 @@ describe('App State Sync Tests', () => { } } } - ], + ] } ] - for(const { mutations, settings } of CASES) { + for (const { mutations, settings } of CASES) { const ctx: InitialAppStateSyncOptions = { recvChats: { [jid]: { lastMsgRecvTimestamp: now } @@ -204,4 +203,4 @@ describe('App State Sync Tests', () => { expect(event.archive).toEqual(true) } }) -}) \ No newline at end of file +}) diff --git a/src/Tests/test.event-buffer.ts b/src/Tests/test.event-buffer.ts index e2453e1..2d0b3e1 100644 --- a/src/Tests/test.event-buffer.ts +++ b/src/Tests/test.event-buffer.ts @@ -5,15 +5,14 @@ import logger from '../Utils/logger' import { randomJid } from './utils' describe('Event Buffer Tests', () => { - let ev: ReturnType beforeEach(() => { - const _logger = logger.child({ }) + const _logger = logger.child({}) _logger.level = 'trace' ev = makeEventBuffer(_logger) }) - it('should buffer a chat upsert & update event', async() => { + it('should buffer a chat upsert & update event', async () => { const chatId = randomJid() const chats: Chat[] = [] @@ -23,14 +22,14 @@ describe('Event Buffer Tests', () => { ev.buffer() await Promise.all([ - (async() => { + (async () => { ev.buffer() await delay(100) ev.emit('chats.upsert', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }]) const flushed = ev.flush() expect(flushed).toBeFalsy() })(), - (async() => { + (async () => { ev.buffer() await delay(200) ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }]) @@ -47,7 +46,7 @@ describe('Event Buffer Tests', () => { expect(chats[0].unreadCount).toEqual(2) }) - it('should overwrite a chats.delete event', async() => { + it('should overwrite a chats.delete event', async () => { const chatId = randomJid() const chats: Partial[] = [] @@ -65,7 +64,7 @@ describe('Event Buffer Tests', () => { expect(chats).toHaveLength(1) }) - it('should overwrite a chats.update event', async() => { + it('should overwrite a chats.update event', async () => { const chatId = randomJid() const chatsDeleted: string[] = [] @@ -82,7 +81,7 @@ describe('Event Buffer Tests', () => { expect(chatsDeleted).toHaveLength(1) }) - it('should release a conditional update at the right time', async() => { + it('should release a conditional update at the right time', async () => { const chatId = randomJid() const chatId2 = randomJid() const chatsUpserted: Chat[] = [] @@ -93,41 +92,49 @@ describe('Event Buffer Tests', () => { ev.on('chats.update', () => fail('not should have emitted')) ev.buffer() - ev.emit('chats.update', [{ - id: chatId, - archived: true, - conditional(buff) { - if(buff.chatUpserts[chatId]) { - return true + ev.emit('chats.update', [ + { + id: chatId, + archived: true, + conditional(buff) { + if (buff.chatUpserts[chatId]) { + return true + } } } - }]) - ev.emit('chats.update', [{ - id: chatId2, - archived: true, - conditional(buff) { - if(buff.historySets.chats[chatId2]) { - return true + ]) + ev.emit('chats.update', [ + { + id: chatId2, + archived: true, + conditional(buff) { + if (buff.historySets.chats[chatId2]) { + return true + } } } - }]) + ]) ev.flush() ev.buffer() - ev.emit('chats.upsert', [{ - id: chatId, - conversationTimestamp: 123, - unreadCount: 1, - muteEndTime: 123 - }]) - ev.emit('messaging-history.set', { - chats: [{ - id: chatId2, + ev.emit('chats.upsert', [ + { + id: chatId, conversationTimestamp: 123, unreadCount: 1, muteEndTime: 123 - }], + } + ]) + ev.emit('messaging-history.set', { + chats: [ + { + id: chatId2, + conversationTimestamp: 123, + unreadCount: 1, + muteEndTime: 123 + } + ], contacts: [], messages: [], isLatest: false @@ -144,7 +151,7 @@ describe('Event Buffer Tests', () => { expect(chatsSynced[0].archived).toEqual(true) }) - it('should discard a conditional update', async() => { + it('should discard a conditional update', async () => { const chatId = randomJid() const chatsUpserted: Chat[] = [] @@ -152,21 +159,25 @@ describe('Event Buffer Tests', () => { ev.on('chats.update', () => fail('not should have emitted')) ev.buffer() - ev.emit('chats.update', [{ - id: chatId, - archived: true, - conditional(buff) { - if(buff.chatUpserts[chatId]) { - return false + ev.emit('chats.update', [ + { + id: chatId, + archived: true, + conditional(buff) { + if (buff.chatUpserts[chatId]) { + return false + } } } - }]) - ev.emit('chats.upsert', [{ - id: chatId, - conversationTimestamp: 123, - unreadCount: 1, - muteEndTime: 123 - }]) + ]) + ev.emit('chats.upsert', [ + { + id: chatId, + conversationTimestamp: 123, + unreadCount: 1, + muteEndTime: 123 + } + ]) ev.flush() @@ -174,7 +185,7 @@ describe('Event Buffer Tests', () => { expect(chatsUpserted[0].archived).toBeUndefined() }) - it('should overwrite a chats.update event with a history event', async() => { + it('should overwrite a chats.update event with a history event', async () => { const chatId = randomJid() let chatRecv: Chat | undefined @@ -199,7 +210,7 @@ describe('Event Buffer Tests', () => { expect(chatRecv?.archived).toBeTruthy() }) - it('should buffer message upsert events', async() => { + it('should buffer message upsert events', async () => { const messageTimestamp = unixTimestampSeconds() const msg: proto.IWebMessageInfo = { key: { @@ -235,7 +246,7 @@ describe('Event Buffer Tests', () => { expect(msgs[0].status).toEqual(WAMessageStatus.READ) }) - it('should buffer a message receipt update', async() => { + it('should buffer a message receipt update', async () => { const msg: proto.IWebMessageInfo = { key: { remoteJid: randomJid(), @@ -269,7 +280,7 @@ describe('Event Buffer Tests', () => { expect(msgs[0].userReceipt).toHaveLength(1) }) - it('should buffer multiple status updates', async() => { + it('should buffer multiple status updates', async () => { const key: WAMessageKey = { remoteJid: randomJid(), id: generateMessageID(), @@ -290,7 +301,7 @@ describe('Event Buffer Tests', () => { expect(msgs[0].update.status).toEqual(WAMessageStatus.READ) }) - it('should remove chat unread counter', async() => { + it('should remove chat unread counter', async () => { const msg: proto.IWebMessageInfo = { key: { remoteJid: '12345@s.whatsapp.net', @@ -316,4 +327,4 @@ describe('Event Buffer Tests', () => { expect(chats[0].unreadCount).toBeUndefined() }) -}) \ No newline at end of file +}) diff --git a/src/Tests/test.key-store.ts b/src/Tests/test.key-store.ts index f6b86b2..396b617 100644 --- a/src/Tests/test.key-store.ts +++ b/src/Tests/test.key-store.ts @@ -5,55 +5,44 @@ import { makeMockSignalKeyStore } from './utils' logger.level = 'trace' describe('Key Store w Transaction Tests', () => { - const rawStore = makeMockSignalKeyStore() - const store = addTransactionCapability( - rawStore, - logger, - { - maxCommitRetries: 1, - delayBetweenTriesMs: 10 - } - ) + const store = addTransactionCapability(rawStore, logger, { + maxCommitRetries: 1, + delayBetweenTriesMs: 10 + }) - it('should use transaction cache when mutated', async() => { + it('should use transaction cache when mutated', async () => { const key = '123' const value = new Uint8Array(1) const ogGet = rawStore.get - await store.transaction( - async() => { - await store.set({ 'session': { [key]: value } }) + await store.transaction(async () => { + await store.set({ session: { [key]: value } }) - rawStore.get = () => { - throw new Error('should not have been called') - } - - const { [key]: stored } = await store.get('session', [key]) - expect(stored).toEqual(new Uint8Array(1)) + rawStore.get = () => { + throw new Error('should not have been called') } - ) + + const { [key]: stored } = await store.get('session', [key]) + expect(stored).toEqual(new Uint8Array(1)) + }) rawStore.get = ogGet }) - it('should not commit a failed transaction', async() => { + it('should not commit a failed transaction', async () => { const key = 'abcd' await expect( - store.transaction( - async() => { - await store.set({ 'session': { [key]: new Uint8Array(1) } }) - throw new Error('fail') - } - ) - ).rejects.toThrowError( - 'fail' - ) + store.transaction(async () => { + await store.set({ session: { [key]: new Uint8Array(1) } }) + throw new Error('fail') + }) + ).rejects.toThrowError('fail') const { [key]: stored } = await store.get('session', [key]) expect(stored).toBeUndefined() }) - it('should handle overlapping transactions', async() => { + it('should handle overlapping transactions', async () => { // promise to let transaction 2 // know that transaction 1 has started let promiseResolve: () => void @@ -61,32 +50,28 @@ describe('Key Store w Transaction Tests', () => { promiseResolve = resolve }) - store.transaction( - async() => { - await store.set({ - 'session': { - '1': new Uint8Array(1) - } - }) - // wait for the other transaction to start - await delay(5) - // reolve the promise to let the other transaction continue - promiseResolve() - } - ) + store.transaction(async () => { + await store.set({ + session: { + '1': new Uint8Array(1) + } + }) + // wait for the other transaction to start + await delay(5) + // reolve the promise to let the other transaction continue + promiseResolve() + }) - await store.transaction( - async() => { - await promise - await delay(5) + await store.transaction(async () => { + await promise + await delay(5) - expect(store.isInTransaction()).toBe(true) - } - ) + expect(store.isInTransaction()).toBe(true) + }) expect(store.isInTransaction()).toBe(false) // ensure that the transaction were committed const { ['1']: stored } = await store.get('session', ['1']) expect(stored).toEqual(new Uint8Array(1)) }) -}) \ No newline at end of file +}) diff --git a/src/Tests/test.libsignal.ts b/src/Tests/test.libsignal.ts index a71fd5c..fd24ff8 100644 --- a/src/Tests/test.libsignal.ts +++ b/src/Tests/test.libsignal.ts @@ -3,8 +3,7 @@ import { SignalAuthState, SignalDataTypeMap } from '../Types' import { Curve, generateRegistrationId, generateSignalPubKey, signedKeyPair } from '../Utils' describe('Signal Tests', () => { - - it('should correctly encrypt/decrypt 1 message', async() => { + it('should correctly encrypt/decrypt 1 message', async () => { const user1 = makeUser() const user2 = makeUser() @@ -12,39 +11,31 @@ describe('Signal Tests', () => { await prepareForSendingMessage(user1, user2) - const result = await user1.repository.encryptMessage( - { jid: user2.jid, data: msg } - ) + const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg }) - const dec = await user2.repository.decryptMessage( - { jid: user1.jid, ...result } - ) + const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result }) expect(dec).toEqual(msg) }) - it('should correctly override a session', async() => { + it('should correctly override a session', async () => { const user1 = makeUser() const user2 = makeUser() const msg = Buffer.from('hello there!') - for(let preKeyId = 2; preKeyId <= 3;preKeyId++) { + for (let preKeyId = 2; preKeyId <= 3; preKeyId++) { await prepareForSendingMessage(user1, user2, preKeyId) - const result = await user1.repository.encryptMessage( - { jid: user2.jid, data: msg } - ) + const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg }) - const dec = await user2.repository.decryptMessage( - { jid: user1.jid, ...result } - ) + const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result }) expect(dec).toEqual(msg) } }) - it('should correctly encrypt/decrypt multiple messages', async() => { + it('should correctly encrypt/decrypt multiple messages', async () => { const user1 = makeUser() const user2 = makeUser() @@ -52,56 +43,46 @@ describe('Signal Tests', () => { await prepareForSendingMessage(user1, user2) - for(let i = 0;i < 10;i++) { - const result = await user1.repository.encryptMessage( - { jid: user2.jid, data: msg } - ) + for (let i = 0; i < 10; i++) { + const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg }) - const dec = await user2.repository.decryptMessage( - { jid: user1.jid, ...result } - ) + const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result }) expect(dec).toEqual(msg) } }) - it('should encrypt/decrypt messages from group', async() => { + it('should encrypt/decrypt messages from group', async () => { const groupId = '123456@g.us' const participants = [...Array(5)].map(makeUser) const msg = Buffer.from('hello there!') const sender = participants[0] - const enc = await sender.repository.encryptGroupMessage( - { - group: groupId, - meId: sender.jid, - data: msg - } - ) + const enc = await sender.repository.encryptGroupMessage({ + group: groupId, + meId: sender.jid, + data: msg + }) - for(const participant of participants) { - if(participant === sender) { + for (const participant of participants) { + if (participant === sender) { continue } - await participant.repository.processSenderKeyDistributionMessage( - { - item: { - groupId, - axolotlSenderKeyDistributionMessage: enc.senderKeyDistributionMessage - }, - authorJid: sender.jid - } - ) + await participant.repository.processSenderKeyDistributionMessage({ + item: { + groupId, + axolotlSenderKeyDistributionMessage: enc.senderKeyDistributionMessage + }, + authorJid: sender.jid + }) - const dec = await participant.repository.decryptGroupMessage( - { - group: groupId, - authorJid: sender.jid, - msg: enc.ciphertext - } - ) + const dec = await participant.repository.decryptGroupMessage({ + group: groupId, + authorJid: sender.jid, + msg: enc.ciphertext + }) expect(dec).toEqual(msg) } }) @@ -116,30 +97,24 @@ function makeUser() { return { store, jid, repository } } -async function prepareForSendingMessage( - sender: User, - receiver: User, - preKeyId = 2 -) { +async function prepareForSendingMessage(sender: User, receiver: User, preKeyId = 2) { const preKey = Curve.generateKeyPair() - await sender.repository.injectE2ESession( - { - jid: receiver.jid, - session: { - registrationId: receiver.store.creds.registrationId, - identityKey: generateSignalPubKey(receiver.store.creds.signedIdentityKey.public), - signedPreKey: { - keyId: receiver.store.creds.signedPreKey.keyId, - publicKey: generateSignalPubKey(receiver.store.creds.signedPreKey.keyPair.public), - signature: receiver.store.creds.signedPreKey.signature, - }, - preKey: { - keyId: preKeyId, - publicKey: generateSignalPubKey(preKey.public), - } + await sender.repository.injectE2ESession({ + jid: receiver.jid, + session: { + registrationId: receiver.store.creds.registrationId, + identityKey: generateSignalPubKey(receiver.store.creds.signedIdentityKey.public), + signedPreKey: { + keyId: receiver.store.creds.signedPreKey.keyId, + publicKey: generateSignalPubKey(receiver.store.creds.signedPreKey.keyPair.public), + signature: receiver.store.creds.signedPreKey.signature + }, + preKey: { + keyId: preKeyId, + publicKey: generateSignalPubKey(preKey.public) } } - ) + }) await receiver.store.keys.set({ 'pre-key': { @@ -156,14 +131,14 @@ function makeTestAuthState(): SignalAuthState { creds: { signedIdentityKey: identityKey, registrationId: generateRegistrationId(), - signedPreKey: signedKeyPair(identityKey, 1), + signedPreKey: signedKeyPair(identityKey, 1) }, keys: { get(type, ids) { - const data: { [_: string]: SignalDataTypeMap[typeof type] } = { } - for(const id of ids) { + const data: { [_: string]: SignalDataTypeMap[typeof type] } = {} + for (const id of ids) { const item = store[getUniqueId(type, id)] - if(typeof item !== 'undefined') { + if (typeof item !== 'undefined') { data[id] = item } } @@ -171,16 +146,16 @@ function makeTestAuthState(): SignalAuthState { return data }, set(data) { - for(const type in data) { - for(const id in data[type]) { + for (const type in data) { + for (const id in data[type]) { store[getUniqueId(type, id)] = data[type][id] } } - }, + } } } function getUniqueId(type: string, id: string) { return `${type}.${id}` } -} \ No newline at end of file +} diff --git a/src/Tests/test.media-download.ts b/src/Tests/test.media-download.ts index f00638c..e783d3a 100644 --- a/src/Tests/test.media-download.ts +++ b/src/Tests/test.media-download.ts @@ -31,38 +31,37 @@ const TEST_VECTORS: TestVector[] = [ ) ), plaintext: readFileSync('./Media/icon.png') - }, + } ] describe('Media Download Tests', () => { - - it('should download a full encrypted media correctly', async() => { - for(const { type, message, plaintext } of TEST_VECTORS) { + it('should download a full encrypted media correctly', async () => { + for (const { type, message, plaintext } of TEST_VECTORS) { const readPipe = await downloadContentFromMessage(message, type) let buffer = Buffer.alloc(0) for await (const read of readPipe) { - buffer = Buffer.concat([ buffer, read ]) + buffer = Buffer.concat([buffer, read]) } expect(buffer).toEqual(plaintext) } }) - it('should download an encrypted media correctly piece', async() => { - for(const { type, message, plaintext } of TEST_VECTORS) { + it('should download an encrypted media correctly piece', async () => { + for (const { type, message, plaintext } of TEST_VECTORS) { // check all edge cases const ranges = [ { startByte: 51, endByte: plaintext.length - 100 }, // random numbers { startByte: 1024, endByte: 2038 }, // larger random multiples of 16 { startByte: 1, endByte: plaintext.length - 1 } // borders ] - for(const range of ranges) { + for (const range of ranges) { const readPipe = await downloadContentFromMessage(message, type, range) let buffer = Buffer.alloc(0) for await (const read of readPipe) { - buffer = Buffer.concat([ buffer, read ]) + buffer = Buffer.concat([buffer, read]) } const hex = buffer.toString('hex') @@ -73,4 +72,4 @@ describe('Media Download Tests', () => { } } }) -}) \ No newline at end of file +}) diff --git a/src/Tests/test.messages.ts b/src/Tests/test.messages.ts index 7f51f39..5ebaf6b 100644 --- a/src/Tests/test.messages.ts +++ b/src/Tests/test.messages.ts @@ -2,9 +2,8 @@ import { WAMessageContent } from '../Types' import { normalizeMessageContent } from '../Utils' describe('Messages Tests', () => { - it('should correctly unwrap messages', () => { - const CONTENT = { imageMessage: { } } + const CONTENT = { imageMessage: {} } expectRightContent(CONTENT) expectRightContent({ ephemeralMessage: { message: CONTENT } @@ -29,9 +28,7 @@ describe('Messages Tests', () => { }) function expectRightContent(content: WAMessageContent) { - expect( - normalizeMessageContent(content) - ).toHaveProperty('imageMessage') + expect(normalizeMessageContent(content)).toHaveProperty('imageMessage') } }) -}) \ No newline at end of file +}) diff --git a/src/Tests/utils.ts b/src/Tests/utils.ts index bcd1469..d071efd 100644 --- a/src/Tests/utils.ts +++ b/src/Tests/utils.ts @@ -11,10 +11,10 @@ export function makeMockSignalKeyStore(): SignalKeyStore { return { get(type, ids) { - const data: { [_: string]: SignalDataTypeMap[typeof type] } = { } - for(const id of ids) { + const data: { [_: string]: SignalDataTypeMap[typeof type] } = {} + for (const id of ids) { const item = store[getUniqueId(type, id)] - if(typeof item !== 'undefined') { + if (typeof item !== 'undefined') { data[id] = item } } @@ -22,15 +22,15 @@ export function makeMockSignalKeyStore(): SignalKeyStore { return data }, set(data) { - for(const type in data) { - for(const id in data[type]) { + for (const type in data) { + for (const id in data[type]) { store[getUniqueId(type, id)] = data[type][id] } } - }, + } } function getUniqueId(type: string, id: string) { return `${type}.${id}` } -} \ No newline at end of file +} diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index c0c7ece..75f06d6 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -2,12 +2,12 @@ import type { proto } from '../../WAProto' import type { Contact } from './Contact' import type { MinimalMessage } from './Message' -export type KeyPair = { public: Uint8Array, private: Uint8Array } +export type KeyPair = { public: Uint8Array; private: Uint8Array } export type SignedKeyPair = { - keyPair: KeyPair - signature: Uint8Array - keyId: number - timestampS?: number + keyPair: KeyPair + signature: Uint8Array + keyId: number + timestampS?: number } export type ProtocolAddress = { @@ -20,58 +20,58 @@ export type SignalIdentity = { } export type LTHashState = { - version: number - hash: Buffer - indexValueMap: { - [indexMacBase64: string]: { valueMac: Uint8Array | Buffer } - } + version: number + hash: Buffer + indexValueMap: { + [indexMacBase64: string]: { valueMac: Uint8Array | Buffer } + } } export type SignalCreds = { - readonly signedIdentityKey: KeyPair - readonly signedPreKey: SignedKeyPair - readonly registrationId: number + readonly signedIdentityKey: KeyPair + readonly signedPreKey: SignedKeyPair + readonly registrationId: number } export type AccountSettings = { - /** unarchive chats when a new message is received */ - unarchiveChats: boolean - /** the default mode to start new conversations with */ - defaultDisappearingMode?: Pick + /** unarchive chats when a new message is received */ + unarchiveChats: boolean + /** the default mode to start new conversations with */ + defaultDisappearingMode?: Pick } export type AuthenticationCreds = SignalCreds & { - readonly noiseKey: KeyPair - readonly pairingEphemeralKeyPair: KeyPair - advSecretKey: string + readonly noiseKey: KeyPair + readonly pairingEphemeralKeyPair: KeyPair + advSecretKey: string - me?: Contact - account?: proto.IADVSignedDeviceIdentity - signalIdentities?: SignalIdentity[] - myAppStateKeyId?: string - firstUnuploadedPreKeyId: number - nextPreKeyId: number + me?: Contact + account?: proto.IADVSignedDeviceIdentity + signalIdentities?: SignalIdentity[] + myAppStateKeyId?: string + firstUnuploadedPreKeyId: number + nextPreKeyId: number - lastAccountSyncTimestamp?: number - platform?: string + lastAccountSyncTimestamp?: number + platform?: string - processedHistoryMessages: MinimalMessage[] - /** number of times history & app state has been synced */ - accountSyncCounter: number - accountSettings: AccountSettings - registered: boolean - pairingCode: string | undefined - lastPropHash: string | undefined - routingInfo: Buffer | undefined + processedHistoryMessages: MinimalMessage[] + /** number of times history & app state has been synced */ + accountSyncCounter: number + accountSettings: AccountSettings + registered: boolean + pairingCode: string | undefined + lastPropHash: string | undefined + routingInfo: Buffer | undefined } export type SignalDataTypeMap = { - 'pre-key': KeyPair - 'session': Uint8Array - 'sender-key': Uint8Array - 'sender-key-memory': { [jid: string]: boolean } - 'app-state-sync-key': proto.Message.IAppStateSyncKeyData - 'app-state-sync-version': LTHashState + 'pre-key': KeyPair + session: Uint8Array + 'sender-key': Uint8Array + 'sender-key-memory': { [jid: string]: boolean } + 'app-state-sync-key': proto.Message.IAppStateSyncKeyData + 'app-state-sync-version': LTHashState } export type SignalDataSet = { [T in keyof SignalDataTypeMap]?: { [id: string]: SignalDataTypeMap[T] | null } } @@ -79,15 +79,15 @@ export type SignalDataSet = { [T in keyof SignalDataTypeMap]?: { [id: string]: S type Awaitable = T | Promise export type SignalKeyStore = { - get(type: T, ids: string[]): Awaitable<{ [id: string]: SignalDataTypeMap[T] }> - set(data: SignalDataSet): Awaitable - /** clear all the data in the store */ - clear?(): Awaitable + get(type: T, ids: string[]): Awaitable<{ [id: string]: SignalDataTypeMap[T] }> + set(data: SignalDataSet): Awaitable + /** clear all the data in the store */ + clear?(): Awaitable } export type SignalKeyStoreWithTransaction = SignalKeyStore & { - isInTransaction: () => boolean - transaction(exec: () => Promise): Promise + isInTransaction: () => boolean + transaction(exec: () => Promise): Promise } export type TransactionCapabilityOptions = { @@ -96,11 +96,11 @@ export type TransactionCapabilityOptions = { } export type SignalAuthState = { - creds: SignalCreds - keys: SignalKeyStore | SignalKeyStoreWithTransaction + creds: SignalCreds + keys: SignalKeyStore | SignalKeyStoreWithTransaction } export type AuthenticationState = { - creds: AuthenticationCreds - keys: SignalKeyStore -} \ No newline at end of file + creds: AuthenticationCreds + keys: SignalKeyStore +} diff --git a/src/Types/Call.ts b/src/Types/Call.ts index a5dbe1a..80972f6 100644 --- a/src/Types/Call.ts +++ b/src/Types/Call.ts @@ -1,4 +1,3 @@ - export type WACallUpdateType = 'offer' | 'ringing' | 'timeout' | 'reject' | 'accept' | 'terminate' export type WACallEvent = { diff --git a/src/Types/Chat.ts b/src/Types/Chat.ts index e9b80da..f5d008a 100644 --- a/src/Types/Chat.ts +++ b/src/Types/Chat.ts @@ -22,50 +22,58 @@ export type WAPrivacyMessagesValue = 'all' | 'contacts' /** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */ export type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused' -export const ALL_WA_PATCH_NAMES = ['critical_block', 'critical_unblock_low', 'regular_high', 'regular_low', 'regular'] as const +export const ALL_WA_PATCH_NAMES = [ + 'critical_block', + 'critical_unblock_low', + 'regular_high', + 'regular_low', + 'regular' +] as const -export type WAPatchName = typeof ALL_WA_PATCH_NAMES[number] +export type WAPatchName = (typeof ALL_WA_PATCH_NAMES)[number] export interface PresenceData { - lastKnownPresence: WAPresence - lastSeen?: number + lastKnownPresence: WAPresence + lastSeen?: number } export type BotListInfo = { - jid: string - personaId: string + jid: string + personaId: string } export type ChatMutation = { - syncAction: proto.ISyncActionData - index: string[] + syncAction: proto.ISyncActionData + index: string[] } export type WAPatchCreate = { - syncAction: proto.ISyncActionValue - index: string[] - type: WAPatchName - apiVersion: number - operation: proto.SyncdMutation.SyncdOperation + syncAction: proto.ISyncActionValue + index: string[] + type: WAPatchName + apiVersion: number + operation: proto.SyncdMutation.SyncdOperation } export type Chat = proto.IConversation & { - /** unix timestamp of when the last message was received in the chat */ - lastMessageRecvTimestamp?: number + /** unix timestamp of when the last message was received in the chat */ + lastMessageRecvTimestamp?: number } -export type ChatUpdate = Partial boolean | undefined -}> +export type ChatUpdate = Partial< + Chat & { + /** + * if specified in the update, + * the EV buffer will check if the condition gets fulfilled before applying the update + * Right now, used to determine when to release an app state sync event + * + * @returns true, if the update should be applied; + * false if it can be discarded; + * undefined if the condition is not yet fulfilled + * */ + conditional: (bufferedData: BufferedEventData) => boolean | undefined + } +> /** * the last messages in a chat, sorted reverse-chronologically. That is, the latest message should be first in the chat @@ -74,49 +82,50 @@ export type ChatUpdate = Partial if the profile picture has changed - * null => if the profile picture has not been set (default profile picture) - * any other string => url of the profile picture - */ - imgUrl?: string | null - status?: string -} \ No newline at end of file + id: string + lid?: string + /** name of the contact, you have saved on your WA */ + name?: string + /** name of the contact, the contact has set on their own on WA */ + notify?: string + /** I have no idea */ + verifiedName?: string + // Baileys Added + /** + * Url of the profile picture of the contact + * + * 'changed' => if the profile picture has changed + * null => if the profile picture has not been set (default profile picture) + * any other string => url of the profile picture + */ + imgUrl?: string | null + status?: string +} diff --git a/src/Types/Events.ts b/src/Types/Events.ts index bb13e39..813e5d0 100644 --- a/src/Types/Events.ts +++ b/src/Types/Events.ts @@ -11,91 +11,97 @@ import { MessageUpsertType, MessageUserReceiptUpdate, WAMessage, WAMessageKey, W import { ConnectionState } from './State' export type BaileysEventMap = { - /** connection state has been updated -- WS closed, opened, connecting etc. */ + /** connection state has been updated -- WS closed, opened, connecting etc. */ 'connection.update': Partial - /** credentials updated -- some metadata, keys or something */ - 'creds.update': Partial - /** set chats (history sync), everything is reverse chronologically sorted */ - 'messaging-history.set': { - chats: Chat[] - contacts: Contact[] - messages: WAMessage[] - isLatest?: boolean - progress?: number | null - syncType?: proto.HistorySync.HistorySyncType - peerDataRequestSessionId?: string | null - } - /** upsert chats */ - 'chats.upsert': Chat[] - /** update the given chats */ - 'chats.update': ChatUpdate[] - 'chats.phoneNumberShare': {lid: string, jid: string} - /** delete chats with given ID */ - 'chats.delete': string[] - /** presence of contact in a chat updated */ - 'presence.update': { id: string, presences: { [participant: string]: PresenceData } } + /** credentials updated -- some metadata, keys or something */ + 'creds.update': Partial + /** set chats (history sync), everything is reverse chronologically sorted */ + 'messaging-history.set': { + chats: Chat[] + contacts: Contact[] + messages: WAMessage[] + isLatest?: boolean + progress?: number | null + syncType?: proto.HistorySync.HistorySyncType + peerDataRequestSessionId?: string | null + } + /** upsert chats */ + 'chats.upsert': Chat[] + /** update the given chats */ + 'chats.update': ChatUpdate[] + 'chats.phoneNumberShare': { lid: string; jid: string } + /** delete chats with given ID */ + 'chats.delete': string[] + /** presence of contact in a chat updated */ + 'presence.update': { id: string; presences: { [participant: string]: PresenceData } } - 'contacts.upsert': Contact[] - 'contacts.update': Partial[] + 'contacts.upsert': Contact[] + 'contacts.update': Partial[] - 'messages.delete': { keys: WAMessageKey[] } | { jid: string, all: true } - 'messages.update': WAMessageUpdate[] - 'messages.media-update': { key: WAMessageKey, media?: { ciphertext: Uint8Array, iv: Uint8Array }, error?: Boom }[] - /** - * add/update the given messages. If they were received while the connection was online, - * the update will have type: "notify" - * if requestId is provided, then the messages was received from the phone due to it being unavailable - * */ - 'messages.upsert': { messages: WAMessage[], type: MessageUpsertType, requestId?: string } - /** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */ - 'messages.reaction': { key: WAMessageKey, reaction: proto.IReaction }[] + 'messages.delete': { keys: WAMessageKey[] } | { jid: string; all: true } + 'messages.update': WAMessageUpdate[] + 'messages.media-update': { key: WAMessageKey; media?: { ciphertext: Uint8Array; iv: Uint8Array }; error?: Boom }[] + /** + * add/update the given messages. If they were received while the connection was online, + * the update will have type: "notify" + * if requestId is provided, then the messages was received from the phone due to it being unavailable + * */ + 'messages.upsert': { messages: WAMessage[]; type: MessageUpsertType; requestId?: string } + /** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */ + 'messages.reaction': { key: WAMessageKey; reaction: proto.IReaction }[] - 'message-receipt.update': MessageUserReceiptUpdate[] + 'message-receipt.update': MessageUserReceiptUpdate[] - 'groups.upsert': GroupMetadata[] - 'groups.update': Partial[] - /** apply an action to participants in a group */ - 'group-participants.update': { id: string, author: string, participants: string[], action: ParticipantAction } - 'group.join-request': { id: string, author: string, participant: string, action: RequestJoinAction, method: RequestJoinMethod } + 'groups.upsert': GroupMetadata[] + 'groups.update': Partial[] + /** apply an action to participants in a group */ + 'group-participants.update': { id: string; author: string; participants: string[]; action: ParticipantAction } + 'group.join-request': { + id: string + author: string + participant: string + action: RequestJoinAction + method: RequestJoinMethod + } - 'blocklist.set': { blocklist: string[] } - 'blocklist.update': { blocklist: string[], type: 'add' | 'remove' } + 'blocklist.set': { blocklist: string[] } + 'blocklist.update': { blocklist: string[]; type: 'add' | 'remove' } - /** Receive an update on a call, including when the call was received, rejected, accepted */ - 'call': WACallEvent[] - 'labels.edit': Label - 'labels.association': { association: LabelAssociation, type: 'add' | 'remove' } + /** Receive an update on a call, including when the call was received, rejected, accepted */ + call: WACallEvent[] + 'labels.edit': Label + 'labels.association': { association: LabelAssociation; type: 'add' | 'remove' } } export type BufferedEventData = { - historySets: { - chats: { [jid: string]: Chat } - contacts: { [jid: string]: Contact } - messages: { [uqId: string]: WAMessage } - empty: boolean - isLatest: boolean - progress?: number | null - syncType?: proto.HistorySync.HistorySyncType - peerDataRequestSessionId?: string - } - chatUpserts: { [jid: string]: Chat } - chatUpdates: { [jid: string]: ChatUpdate } - chatDeletes: Set - contactUpserts: { [jid: string]: Contact } - contactUpdates: { [jid: string]: Partial } - messageUpserts: { [key: string]: { type: MessageUpsertType, message: WAMessage } } - messageUpdates: { [key: string]: WAMessageUpdate } - messageDeletes: { [key: string]: WAMessageKey } - messageReactions: { [key: string]: { key: WAMessageKey, reactions: proto.IReaction[] } } - messageReceipts: { [key: string]: { key: WAMessageKey, userReceipt: proto.IUserReceipt[] } } - groupUpdates: { [jid: string]: Partial } + historySets: { + chats: { [jid: string]: Chat } + contacts: { [jid: string]: Contact } + messages: { [uqId: string]: WAMessage } + empty: boolean + isLatest: boolean + progress?: number | null + syncType?: proto.HistorySync.HistorySyncType + peerDataRequestSessionId?: string + } + chatUpserts: { [jid: string]: Chat } + chatUpdates: { [jid: string]: ChatUpdate } + chatDeletes: Set + contactUpserts: { [jid: string]: Contact } + contactUpdates: { [jid: string]: Partial } + messageUpserts: { [key: string]: { type: MessageUpsertType; message: WAMessage } } + messageUpdates: { [key: string]: WAMessageUpdate } + messageDeletes: { [key: string]: WAMessageKey } + messageReactions: { [key: string]: { key: WAMessageKey; reactions: proto.IReaction[] } } + messageReceipts: { [key: string]: { key: WAMessageKey; userReceipt: proto.IUserReceipt[] } } + groupUpdates: { [jid: string]: Partial } } export type BaileysEvent = keyof BaileysEventMap export interface BaileysEventEmitter { on(event: T, listener: (arg: BaileysEventMap[T]) => void): void - off(event: T, listener: (arg: BaileysEventMap[T]) => void): void - removeAllListeners(event: T): void + off(event: T, listener: (arg: BaileysEventMap[T]) => void): void + removeAllListeners(event: T): void emit(event: T, arg: BaileysEventMap[T]): boolean -} \ No newline at end of file +} diff --git a/src/Types/GroupMetadata.ts b/src/Types/GroupMetadata.ts index 5a1ae86..8513f8a 100644 --- a/src/Types/GroupMetadata.ts +++ b/src/Types/GroupMetadata.ts @@ -1,6 +1,10 @@ import { Contact } from './Contact' -export type GroupParticipant = (Contact & { isAdmin?: boolean, isSuperAdmin?: boolean, admin?: 'admin' | 'superadmin' | null }) +export type GroupParticipant = Contact & { + isAdmin?: boolean + isSuperAdmin?: boolean + admin?: 'admin' | 'superadmin' | null +} export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote' | 'modify' @@ -9,51 +13,50 @@ export type RequestJoinAction = 'created' | 'revoked' | 'rejected' export type RequestJoinMethod = 'invite_link' | 'linked_group_join' | 'non_admin_add' | undefined export interface GroupMetadata { - id: string - /** group uses 'lid' or 'pn' to send messages */ - addressingMode: string - owner: string | undefined - subject: string - /** group subject owner */ - subjectOwner?: string - /** group subject modification date */ - subjectTime?: number - creation?: number - desc?: string - descOwner?: string - descId?: string - /** if this group is part of a community, it returns the jid of the community to which it belongs */ - linkedParent?: string - /** is set when the group only allows admins to change group settings */ - restrict?: boolean - /** is set when the group only allows admins to write messages */ - announce?: boolean - /** is set when the group also allows members to add participants */ - memberAddMode?: boolean - /** Request approval to join the group */ - joinApprovalMode?: boolean - /** is this a community */ - isCommunity?: boolean - /** is this the announce of a community */ - isCommunityAnnounce?: boolean - /** number of group participants */ - size?: number - // Baileys modified array - participants: GroupParticipant[] - ephemeralDuration?: number - inviteCode?: string - /** the person who added you to group or changed some setting in group */ - author?: string + id: string + /** group uses 'lid' or 'pn' to send messages */ + addressingMode: string + owner: string | undefined + subject: string + /** group subject owner */ + subjectOwner?: string + /** group subject modification date */ + subjectTime?: number + creation?: number + desc?: string + descOwner?: string + descId?: string + /** if this group is part of a community, it returns the jid of the community to which it belongs */ + linkedParent?: string + /** is set when the group only allows admins to change group settings */ + restrict?: boolean + /** is set when the group only allows admins to write messages */ + announce?: boolean + /** is set when the group also allows members to add participants */ + memberAddMode?: boolean + /** Request approval to join the group */ + joinApprovalMode?: boolean + /** is this a community */ + isCommunity?: boolean + /** is this the announce of a community */ + isCommunityAnnounce?: boolean + /** number of group participants */ + size?: number + // Baileys modified array + participants: GroupParticipant[] + ephemeralDuration?: number + inviteCode?: string + /** the person who added you to group or changed some setting in group */ + author?: string } - export interface WAGroupCreateResponse { - status: number - gid?: string - participants?: [{ [key: string]: {} }] + status: number + gid?: string + participants?: [{ [key: string]: {} }] } export interface GroupModificationResponse { - status: number - participants?: { [key: string]: {} } + status: number + participants?: { [key: string]: {} } } diff --git a/src/Types/Label.ts b/src/Types/Label.ts index d9349e6..86ec10e 100644 --- a/src/Types/Label.ts +++ b/src/Types/Label.ts @@ -1,48 +1,48 @@ export interface Label { - /** Label uniq ID */ - id: string - /** Label name */ - name: string - /** Label color ID */ - color: number - /** Is label has been deleted */ - deleted: boolean - /** WhatsApp has 5 predefined labels (New customer, New order & etc) */ - predefinedId?: string + /** Label uniq ID */ + id: string + /** Label name */ + name: string + /** Label color ID */ + color: number + /** Is label has been deleted */ + deleted: boolean + /** WhatsApp has 5 predefined labels (New customer, New order & etc) */ + predefinedId?: string } export interface LabelActionBody { - id: string - /** Label name */ - name?: string - /** Label color ID */ - color?: number - /** Is label has been deleted */ - deleted?: boolean - /** WhatsApp has 5 predefined labels (New customer, New order & etc) */ - predefinedId?: number + id: string + /** Label name */ + name?: string + /** Label color ID */ + color?: number + /** Is label has been deleted */ + deleted?: boolean + /** WhatsApp has 5 predefined labels (New customer, New order & etc) */ + predefinedId?: number } /** WhatsApp has 20 predefined colors */ export enum LabelColor { - Color1 = 0, - Color2, - Color3, - Color4, - Color5, - Color6, - Color7, - Color8, - Color9, - Color10, - Color11, - Color12, - Color13, - Color14, - Color15, - Color16, - Color17, - Color18, - Color19, - Color20, -} \ No newline at end of file + Color1 = 0, + Color2, + Color3, + Color4, + Color5, + Color6, + Color7, + Color8, + Color9, + Color10, + Color11, + Color12, + Color13, + Color14, + Color15, + Color16, + Color17, + Color18, + Color19, + Color20 +} diff --git a/src/Types/LabelAssociation.ts b/src/Types/LabelAssociation.ts index 8c17cf5..4abcb53 100644 --- a/src/Types/LabelAssociation.ts +++ b/src/Types/LabelAssociation.ts @@ -1,35 +1,35 @@ /** Association type */ export enum LabelAssociationType { - Chat = 'label_jid', - Message = 'label_message' + Chat = 'label_jid', + Message = 'label_message' } export type LabelAssociationTypes = `${LabelAssociationType}` /** Association for chat */ export interface ChatLabelAssociation { - type: LabelAssociationType.Chat - chatId: string - labelId: string + type: LabelAssociationType.Chat + chatId: string + labelId: string } /** Association for message */ export interface MessageLabelAssociation { - type: LabelAssociationType.Message - chatId: string - messageId: string - labelId: string + type: LabelAssociationType.Message + chatId: string + messageId: string + labelId: string } export type LabelAssociation = ChatLabelAssociation | MessageLabelAssociation /** Body for add/remove chat label association action */ export interface ChatLabelAssociationActionBody { - labelId: string + labelId: string } /** body for add/remove message label association action */ export interface MessageLabelAssociationActionBody { - labelId: string - messageId: string -} \ No newline at end of file + labelId: string + messageId: string +} diff --git a/src/Types/Message.ts b/src/Types/Message.ts index b2f6f5f..ab0eead 100644 --- a/src/Types/Message.ts +++ b/src/Types/Message.ts @@ -17,7 +17,12 @@ export type WAMessageKey = proto.IMessageKey export type WATextMessage = proto.Message.IExtendedTextMessage export type WAContextInfo = proto.IContextInfo export type WALocationMessage = proto.Message.ILocationMessage -export type WAGenericMediaMessage = proto.Message.IVideoMessage | proto.Message.IImageMessage | proto.Message.IAudioMessage | proto.Message.IDocumentMessage | proto.Message.IStickerMessage +export type WAGenericMediaMessage = + | proto.Message.IVideoMessage + | proto.Message.IImageMessage + | proto.Message.IAudioMessage + | proto.Message.IDocumentMessage + | proto.Message.IStickerMessage export const WAMessageStubType = proto.WebMessageInfo.StubType export const WAMessageStatus = proto.WebMessageInfo.Status import { ILogger } from '../Utils/logger' @@ -27,235 +32,261 @@ export type WAMediaUpload = Buffer | WAMediaPayloadStream | WAMediaPayloadURL /** Set of message types that are supported by the library */ export type MessageType = keyof proto.Message -export type DownloadableMessage = { mediaKey?: Uint8Array | null, directPath?: string | null, url?: string | null } +export type DownloadableMessage = { mediaKey?: Uint8Array | null; directPath?: string | null; url?: string | null } -export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | 'played' | undefined +export type MessageReceiptType = + | 'read' + | 'read-self' + | 'hist_sync' + | 'peer_msg' + | 'sender' + | 'inactive' + | 'played' + | undefined export type MediaConnInfo = { - auth: string - ttl: number - hosts: { hostname: string, maxContentLengthBytes: number }[] - fetchDate: Date + auth: string + ttl: number + hosts: { hostname: string; maxContentLengthBytes: number }[] + fetchDate: Date } export interface WAUrlInfo { - 'canonical-url': string - 'matched-text': string - title: string - description?: string - jpegThumbnail?: Buffer - highQualityThumbnail?: proto.Message.IImageMessage - originalThumbnailUrl?: string + 'canonical-url': string + 'matched-text': string + title: string + description?: string + jpegThumbnail?: Buffer + highQualityThumbnail?: proto.Message.IImageMessage + originalThumbnailUrl?: string } // types to generate WA messages type Mentionable = { - /** list of jids that are mentioned in the accompanying text */ - mentions?: string[] + /** list of jids that are mentioned in the accompanying text */ + mentions?: string[] } type Contextable = { - /** add contextInfo to the message */ - contextInfo?: proto.IContextInfo + /** add contextInfo to the message */ + contextInfo?: proto.IContextInfo } type ViewOnce = { - viewOnce?: boolean + viewOnce?: boolean } type Editable = { - edit?: WAMessageKey + edit?: WAMessageKey } type WithDimensions = { - width?: number - height?: number + width?: number + height?: number } export type PollMessageOptions = { - name: string - selectableCount?: number - values: string[] - /** 32 byte message secret to encrypt poll selections */ - messageSecret?: Uint8Array - toAnnouncementGroup?: boolean + name: string + selectableCount?: number + values: string[] + /** 32 byte message secret to encrypt poll selections */ + messageSecret?: Uint8Array + toAnnouncementGroup?: boolean } type SharePhoneNumber = { - sharePhoneNumber: boolean + sharePhoneNumber: boolean } type RequestPhoneNumber = { - requestPhoneNumber: boolean + requestPhoneNumber: boolean } export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING export type AnyMediaMessageContent = ( - ({ - image: WAMediaUpload - caption?: string - jpegThumbnail?: string - } & Mentionable & Contextable & WithDimensions) - | ({ - video: WAMediaUpload - caption?: string - gifPlayback?: boolean - jpegThumbnail?: string - /** if set to true, will send as a `video note` */ - ptv?: boolean - } & Mentionable & Contextable & WithDimensions) - | { - audio: WAMediaUpload - /** if set to true, will send as a `voice note` */ - ptt?: boolean - /** optionally tell the duration of the audio */ - seconds?: number - } - | ({ - sticker: WAMediaUpload - isAnimated?: boolean - } & WithDimensions) | ({ - document: WAMediaUpload - mimetype: string - fileName?: string - caption?: string - } & Contextable)) - & { mimetype?: string } & Editable + | ({ + image: WAMediaUpload + caption?: string + jpegThumbnail?: string + } & Mentionable & + Contextable & + WithDimensions) + | ({ + video: WAMediaUpload + caption?: string + gifPlayback?: boolean + jpegThumbnail?: string + /** if set to true, will send as a `video note` */ + ptv?: boolean + } & Mentionable & + Contextable & + WithDimensions) + | { + audio: WAMediaUpload + /** if set to true, will send as a `voice note` */ + ptt?: boolean + /** optionally tell the duration of the audio */ + seconds?: number + } + | ({ + sticker: WAMediaUpload + isAnimated?: boolean + } & WithDimensions) + | ({ + document: WAMediaUpload + mimetype: string + fileName?: string + caption?: string + } & Contextable) +) & { mimetype?: string } & Editable export type ButtonReplyInfo = { - displayText: string - id: string - index: number + displayText: string + id: string + index: number } export type GroupInviteInfo = { - inviteCode: string - inviteExpiration: number - text: string - jid: string - subject: string + inviteCode: string + inviteExpiration: number + text: string + jid: string + subject: string } export type WASendableProduct = Omit & { - productImage: WAMediaUpload + productImage: WAMediaUpload } export type AnyRegularMessageContent = ( - ({ - text: string - linkPreview?: WAUrlInfo | null - } - & Mentionable & Contextable & Editable) - | AnyMediaMessageContent - | ({ - poll: PollMessageOptions - } & Mentionable & Contextable & Editable) - | { - contacts: { - displayName?: string - contacts: proto.Message.IContactMessage[] - } - } - | { - location: WALocationMessage - } - | { react: proto.Message.IReactionMessage } - | { - buttonReply: ButtonReplyInfo - type: 'template' | 'plain' - } - | { - groupInvite: GroupInviteInfo - } - | { - listReply: Omit - } - | { - pin: WAMessageKey - type: proto.PinInChat.Type - /** - * 24 hours, 7 days, 30 days - */ - time?: 86400 | 604800 | 2592000 - } - | { - product: WASendableProduct - businessOwnerJid?: string - body?: string - footer?: string - } | SharePhoneNumber | RequestPhoneNumber -) & ViewOnce + | ({ + text: string + linkPreview?: WAUrlInfo | null + } & Mentionable & + Contextable & + Editable) + | AnyMediaMessageContent + | ({ + poll: PollMessageOptions + } & Mentionable & + Contextable & + Editable) + | { + contacts: { + displayName?: string + contacts: proto.Message.IContactMessage[] + } + } + | { + location: WALocationMessage + } + | { react: proto.Message.IReactionMessage } + | { + buttonReply: ButtonReplyInfo + type: 'template' | 'plain' + } + | { + groupInvite: GroupInviteInfo + } + | { + listReply: Omit + } + | { + pin: WAMessageKey + type: proto.PinInChat.Type + /** + * 24 hours, 7 days, 30 days + */ + time?: 86400 | 604800 | 2592000 + } + | { + product: WASendableProduct + businessOwnerJid?: string + body?: string + footer?: string + } + | SharePhoneNumber + | RequestPhoneNumber +) & + ViewOnce -export type AnyMessageContent = AnyRegularMessageContent | { - forward: WAMessage - force?: boolean -} | { - /** Delete your message or anyone's message in a group (admin required) */ - delete: WAMessageKey -} | { - disappearingMessagesInChat: boolean | number -} +export type AnyMessageContent = + | AnyRegularMessageContent + | { + forward: WAMessage + force?: boolean + } + | { + /** Delete your message or anyone's message in a group (admin required) */ + delete: WAMessageKey + } + | { + disappearingMessagesInChat: boolean | number + } export type GroupMetadataParticipants = Pick type MinimalRelayOptions = { - /** override the message ID with a custom provided string */ - messageId?: string - /** should we use group metadata cache, or fetch afresh from the server; default assumed to be "true" */ - useCachedGroupMetadata?: boolean + /** override the message ID with a custom provided string */ + messageId?: string + /** should we use group metadata cache, or fetch afresh from the server; default assumed to be "true" */ + useCachedGroupMetadata?: boolean } export type MessageRelayOptions = MinimalRelayOptions & { - /** only send to a specific participant; used when a message decryption fails for a single user */ - participant?: { jid: string, count: number } - /** additional attributes to add to the WA binary node */ - additionalAttributes?: { [_: string]: string } - additionalNodes?: BinaryNode[] - /** should we use the devices cache, or fetch afresh from the server; default assumed to be "true" */ - useUserDevicesCache?: boolean - /** jid list of participants for status@broadcast */ - statusJidList?: string[] + /** only send to a specific participant; used when a message decryption fails for a single user */ + participant?: { jid: string; count: number } + /** additional attributes to add to the WA binary node */ + additionalAttributes?: { [_: string]: string } + additionalNodes?: BinaryNode[] + /** should we use the devices cache, or fetch afresh from the server; default assumed to be "true" */ + useUserDevicesCache?: boolean + /** jid list of participants for status@broadcast */ + statusJidList?: string[] } export type MiscMessageGenerationOptions = MinimalRelayOptions & { - /** optional, if you want to manually set the timestamp of the message */ + /** optional, if you want to manually set the timestamp of the message */ timestamp?: Date - /** the message you want to quote */ + /** the message you want to quote */ quoted?: WAMessage - /** disappearing messages settings */ - ephemeralExpiration?: number | string - /** timeout for media upload to WA server */ - mediaUploadTimeoutMs?: number - /** jid list of participants for status@broadcast */ - statusJidList?: string[] - /** backgroundcolor for status */ - backgroundColor?: string - /** font type for status */ - font?: number - /** if it is broadcast */ - broadcast?: boolean + /** disappearing messages settings */ + ephemeralExpiration?: number | string + /** timeout for media upload to WA server */ + mediaUploadTimeoutMs?: number + /** jid list of participants for status@broadcast */ + statusJidList?: string[] + /** backgroundcolor for status */ + backgroundColor?: string + /** font type for status */ + font?: number + /** if it is broadcast */ + broadcast?: boolean } export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & { userJid: string } -export type WAMediaUploadFunction = (readStream: Readable, opts: { fileEncSha256B64: string, mediaType: MediaType, timeoutMs?: number }) => Promise<{ mediaUrl: string, directPath: string }> +export type WAMediaUploadFunction = ( + readStream: Readable, + opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number } +) => Promise<{ mediaUrl: string; directPath: string }> export type MediaGenerationOptions = { logger?: ILogger - mediaTypeOverride?: MediaType - upload: WAMediaUploadFunction - /** cache media so it does not have to be uploaded again */ - mediaCache?: CacheStore + mediaTypeOverride?: MediaType + upload: WAMediaUploadFunction + /** cache media so it does not have to be uploaded again */ + mediaCache?: CacheStore - mediaUploadTimeoutMs?: number + mediaUploadTimeoutMs?: number - options?: AxiosRequestConfig + options?: AxiosRequestConfig - backgroundColor?: string + backgroundColor?: string - font?: number + font?: number } export type MessageContentGenerationOptions = MediaGenerationOptions & { getUrlInfo?: (text: string) => Promise - getProfilePicUrl?: (jid: string, type: 'image' | 'preview') => Promise + getProfilePicUrl?: (jid: string, type: 'image' | 'preview') => Promise } export type MessageGenerationOptions = MessageContentGenerationOptions & MessageGenerationOptionsFromContent @@ -268,16 +299,16 @@ export type MessageUpsertType = 'append' | 'notify' export type MessageUserReceipt = proto.IUserReceipt -export type WAMessageUpdate = { update: Partial, key: proto.IMessageKey } +export type WAMessageUpdate = { update: Partial; key: proto.IMessageKey } export type WAMessageCursor = { before: WAMessageKey | undefined } | { after: WAMessageKey | undefined } -export type MessageUserReceiptUpdate = { key: proto.IMessageKey, receipt: MessageUserReceipt } +export type MessageUserReceiptUpdate = { key: proto.IMessageKey; receipt: MessageUserReceipt } export type MediaDecryptionKeyInfo = { - iv: Buffer - cipherKey: Buffer - macKey?: Buffer + iv: Buffer + cipherKey: Buffer + macKey?: Buffer } export type MinimalMessage = Pick diff --git a/src/Types/Product.ts b/src/Types/Product.ts index 2f51b1c..468c810 100644 --- a/src/Types/Product.ts +++ b/src/Types/Product.ts @@ -2,7 +2,7 @@ import { WAMediaUpload } from './Message' export type CatalogResult = { data: { - paging: { cursors: { before: string, after: string } } + paging: { cursors: { before: string; after: string } } // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any[] } @@ -82,4 +82,4 @@ export type GetCatalogOptions = { limit?: number jid?: string -} \ No newline at end of file +} diff --git a/src/Types/Signal.ts b/src/Types/Signal.ts index 12b8e5c..8942839 100644 --- a/src/Types/Signal.ts +++ b/src/Types/Signal.ts @@ -51,9 +51,7 @@ type E2ESessionOpts = { export type SignalRepository = { decryptGroupMessage(opts: DecryptGroupSignalOpts): Promise - processSenderKeyDistributionMessage( - opts: ProcessSenderKeyDistributionMessageOpts - ): Promise + processSenderKeyDistributionMessage(opts: ProcessSenderKeyDistributionMessageOpts): Promise decryptMessage(opts: DecryptSignalProtoOpts): Promise encryptMessage(opts: EncryptMessageOpts): Promise<{ type: 'pkmsg' | 'msg' @@ -65,4 +63,4 @@ export type SignalRepository = { }> injectE2ESession(opts: E2ESessionOpts): Promise jidToSignalProtocolAddress(jid: string): string -} \ No newline at end of file +} diff --git a/src/Types/Socket.ts b/src/Types/Socket.ts index 911dfee..22a18ff 100644 --- a/src/Types/Socket.ts +++ b/src/Types/Socket.ts @@ -1,4 +1,3 @@ - import { AxiosRequestConfig } from 'axios' import type { Agent } from 'https' import type { URL } from 'url' @@ -13,121 +12,124 @@ export type WAVersion = [number, number, number] export type WABrowserDescription = [string, string, string] export type CacheStore = { - /** get a cached key and change the stats */ - get(key: string): T | undefined - /** set a key in the cache */ - set(key: string, value: T): void - /** delete a key from the cache */ - del(key: string): void - /** flush all data */ - flushAll(): void + /** get a cached key and change the stats */ + get(key: string): T | undefined + /** set a key in the cache */ + set(key: string, value: T): void + /** delete a key from the cache */ + del(key: string): void + /** flush all data */ + flushAll(): void } -export type PatchedMessageWithRecipientJID = proto.IMessage & {recipientJid?: string} +export type PatchedMessageWithRecipientJID = proto.IMessage & { recipientJid?: string } export type SocketConfig = { - /** the WS url to connect to WA */ - waWebSocketUrl: string | URL - /** Fails the connection if the socket times out in this interval */ - connectTimeoutMs: number - /** Default timeout for queries, undefined for no timeout */ - defaultQueryTimeoutMs: number | undefined - /** ping-pong interval for WS connection */ - keepAliveIntervalMs: number + /** the WS url to connect to WA */ + waWebSocketUrl: string | URL + /** Fails the connection if the socket times out in this interval */ + connectTimeoutMs: number + /** Default timeout for queries, undefined for no timeout */ + defaultQueryTimeoutMs: number | undefined + /** ping-pong interval for WS connection */ + keepAliveIntervalMs: number /** should baileys use the mobile api instead of the multi device api - * @deprecated This feature has been removed - */ + * @deprecated This feature has been removed + */ mobile?: boolean - /** proxy agent */ - agent?: Agent - /** logger */ - logger: ILogger - /** version to connect with */ - version: WAVersion - /** override browser config */ - browser: WABrowserDescription - /** agent used for fetch requests -- uploading/downloading media */ - fetchAgent?: Agent - /** should the QR be printed in the terminal - * @deprecated This feature has been removed - */ - printQRInTerminal?: boolean - /** should events be emitted for actions done by this socket connection */ - emitOwnEvents: boolean - /** custom upload hosts to upload media to */ - customUploadHosts: MediaConnInfo['hosts'] - /** time to wait between sending new retry requests */ - retryRequestDelayMs: number - /** max retry count */ - maxMsgRetryCount: number - /** time to wait for the generation of the next QR in ms */ - qrTimeout?: number - /** provide an auth state object to maintain the auth state */ - auth: AuthenticationState - /** manage history processing with this control; by default will sync up everything */ - shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => boolean - /** transaction capability options for SignalKeyStore */ - transactionOpts: TransactionCapabilityOptions - /** marks the client as online whenever the socket successfully connects */ - markOnlineOnConnect: boolean - /** alphanumeric country code (USA -> US) for the number used */ - countryCode: string - /** provide a cache to store media, so does not have to be re-uploaded */ - mediaCache?: CacheStore - /** - * map to store the retry counts for failed messages; - * used to determine whether to retry a message or not */ - msgRetryCounterCache?: CacheStore - /** provide a cache to store a user's device list */ - userDevicesCache?: CacheStore - /** cache to store call offers */ - callOfferCache?: CacheStore - /** cache to track placeholder resends */ - placeholderResendCache?: CacheStore - /** width for link preview images */ - linkPreviewImageThumbnailWidth: number - /** Should Baileys ask the phone for full history, will be received async */ - syncFullHistory: boolean - /** Should baileys fire init queries automatically, default true */ - fireInitQueries: boolean - /** - * generate a high quality link preview, - * entails uploading the jpegThumbnail to WA - * */ - generateHighQualityLinkPreview: boolean + /** proxy agent */ + agent?: Agent + /** logger */ + logger: ILogger + /** version to connect with */ + version: WAVersion + /** override browser config */ + browser: WABrowserDescription + /** agent used for fetch requests -- uploading/downloading media */ + fetchAgent?: Agent + /** should the QR be printed in the terminal + * @deprecated This feature has been removed + */ + printQRInTerminal?: boolean + /** should events be emitted for actions done by this socket connection */ + emitOwnEvents: boolean + /** custom upload hosts to upload media to */ + customUploadHosts: MediaConnInfo['hosts'] + /** time to wait between sending new retry requests */ + retryRequestDelayMs: number + /** max retry count */ + maxMsgRetryCount: number + /** time to wait for the generation of the next QR in ms */ + qrTimeout?: number + /** provide an auth state object to maintain the auth state */ + auth: AuthenticationState + /** manage history processing with this control; by default will sync up everything */ + shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => boolean + /** transaction capability options for SignalKeyStore */ + transactionOpts: TransactionCapabilityOptions + /** marks the client as online whenever the socket successfully connects */ + markOnlineOnConnect: boolean + /** alphanumeric country code (USA -> US) for the number used */ + countryCode: string + /** provide a cache to store media, so does not have to be re-uploaded */ + mediaCache?: CacheStore + /** + * map to store the retry counts for failed messages; + * used to determine whether to retry a message or not */ + msgRetryCounterCache?: CacheStore + /** provide a cache to store a user's device list */ + userDevicesCache?: CacheStore + /** cache to store call offers */ + callOfferCache?: CacheStore + /** cache to track placeholder resends */ + placeholderResendCache?: CacheStore + /** width for link preview images */ + linkPreviewImageThumbnailWidth: number + /** Should Baileys ask the phone for full history, will be received async */ + syncFullHistory: boolean + /** Should baileys fire init queries automatically, default true */ + fireInitQueries: boolean + /** + * generate a high quality link preview, + * entails uploading the jpegThumbnail to WA + * */ + generateHighQualityLinkPreview: boolean - /** - * Returns if a jid should be ignored, - * no event for that jid will be triggered. - * Messages from that jid will also not be decrypted - * */ - shouldIgnoreJid: (jid: string) => boolean | undefined + /** + * Returns if a jid should be ignored, + * no event for that jid will be triggered. + * Messages from that jid will also not be decrypted + * */ + shouldIgnoreJid: (jid: string) => boolean | undefined - /** - * Optionally patch the message before sending out - * */ - patchMessageBeforeSending: ( - msg: proto.IMessage, - recipientJids?: string[], - ) => Promise | PatchedMessageWithRecipientJID[] | PatchedMessageWithRecipientJID + /** + * Optionally patch the message before sending out + * */ + patchMessageBeforeSending: ( + msg: proto.IMessage, + recipientJids?: string[] + ) => + | Promise + | PatchedMessageWithRecipientJID[] + | PatchedMessageWithRecipientJID - /** verify app state MACs */ - appStateMacVerification: { - patch: boolean - snapshot: boolean - } + /** verify app state MACs */ + appStateMacVerification: { + patch: boolean + snapshot: boolean + } - /** options for axios */ - options: AxiosRequestConfig<{}> - /** - * fetch a message from your store - * implement this so that messages failed to send - * (solves the "this message can take a while" issue) can be retried - * */ - getMessage: (key: proto.IMessageKey) => Promise + /** options for axios */ + options: AxiosRequestConfig<{}> + /** + * fetch a message from your store + * implement this so that messages failed to send + * (solves the "this message can take a while" issue) can be retried + * */ + getMessage: (key: proto.IMessageKey) => Promise - /** cached group metadata, use to prevent redundant requests to WA & speed up msg sending */ - cachedGroupMetadata: (jid: string) => Promise + /** cached group metadata, use to prevent redundant requests to WA & speed up msg sending */ + cachedGroupMetadata: (jid: string) => Promise - makeSignalRepository: (auth: SignalAuthState) => SignalRepository + makeSignalRepository: (auth: SignalAuthState) => SignalRepository } diff --git a/src/Types/State.ts b/src/Types/State.ts index 53c39b6..4e68574 100644 --- a/src/Types/State.ts +++ b/src/Types/State.ts @@ -26,4 +26,4 @@ export type ConnectionState = { * If this is false, the primary phone and other devices will receive notifs * */ isOnline?: boolean -} \ No newline at end of file +} diff --git a/src/Types/USync.ts b/src/Types/USync.ts index 2e61df1..849c317 100644 --- a/src/Types/USync.ts +++ b/src/Types/USync.ts @@ -5,23 +5,23 @@ import { USyncUser } from '../WAUSync' * Defines the interface for a USyncQuery protocol */ export interface USyncQueryProtocol { - /** - * The name of the protocol - */ - name: string - /** - * Defines what goes inside the query part of a USyncQuery - */ - getQueryElement: () => BinaryNode - /** - * Defines what goes inside the user part of a USyncQuery - */ - getUserElement: (user: USyncUser) => BinaryNode | null + /** + * The name of the protocol + */ + name: string + /** + * Defines what goes inside the query part of a USyncQuery + */ + getQueryElement: () => BinaryNode + /** + * Defines what goes inside the user part of a USyncQuery + */ + getUserElement: (user: USyncUser) => BinaryNode | null - /** - * Parse the result of the query - * @param data Data from the result - * @returns Whatever the protocol is supposed to return - */ - parser: (data: BinaryNode) => unknown -} \ No newline at end of file + /** + * Parse the result of the query + * @param data Data from the result + * @returns Whatever the protocol is supposed to return + */ + parser: (data: BinaryNode) => unknown +} diff --git a/src/Types/index.ts b/src/Types/index.ts index fe50887..1a22e82 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -16,51 +16,51 @@ import { SocketConfig } from './Socket' export type UserFacingSocketConfig = Partial & { auth: AuthenticationState } export type BrowsersMap = { - ubuntu(browser: string): [string, string, string] - macOS(browser: string): [string, string, string] - baileys(browser: string): [string, string, string] - windows(browser: string): [string, string, string] - appropriate(browser: string): [string, string, string] + ubuntu(browser: string): [string, string, string] + macOS(browser: string): [string, string, string] + baileys(browser: string): [string, string, string] + windows(browser: string): [string, string, string] + appropriate(browser: string): [string, string, string] } export enum DisconnectReason { - connectionClosed = 428, - connectionLost = 408, - connectionReplaced = 440, - timedOut = 408, - loggedOut = 401, - badSession = 500, - restartRequired = 515, - multideviceMismatch = 411, - forbidden = 403, - unavailableService = 503 + connectionClosed = 428, + connectionLost = 408, + connectionReplaced = 440, + timedOut = 408, + loggedOut = 401, + badSession = 500, + restartRequired = 515, + multideviceMismatch = 411, + forbidden = 403, + unavailableService = 503 } export type WAInitResponse = { - ref: string - ttl: number - status: 200 + ref: string + ttl: number + status: 200 } export type WABusinessHoursConfig = { - day_of_week: string - mode: string - open_time?: number - close_time?: number + day_of_week: string + mode: string + open_time?: number + close_time?: number } export type WABusinessProfile = { - description: string - email: string | undefined - business_hours: { - timezone?: string - config?: WABusinessHoursConfig[] - business_config?: WABusinessHoursConfig[] - } - website: string[] - category?: string - wid?: string - address?: string + description: string + email: string | undefined + business_hours: { + timezone?: string + config?: WABusinessHoursConfig[] + business_config?: WABusinessHoursConfig[] + } + website: string[] + category?: string + wid?: string + address?: string } -export type CurveKeyPair = { private: Uint8Array, public: Uint8Array } +export type CurveKeyPair = { private: Uint8Array; public: Uint8Array } diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index a64f2a2..90b31f9 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -1,7 +1,15 @@ import NodeCache from '@cacheable/node-cache' import { randomBytes } from 'crypto' import { DEFAULT_CACHE_TTLS } from '../Defaults' -import type { AuthenticationCreds, CacheStore, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types' +import type { + AuthenticationCreds, + CacheStore, + SignalDataSet, + SignalDataTypeMap, + SignalKeyStore, + SignalKeyStoreWithTransaction, + TransactionCapabilityOptions +} from '../Types' import { Curve, signedKeyPair } from './crypto' import { delay, generateRegistrationId } from './generics' import { ILogger } from './logger' @@ -17,11 +25,13 @@ export function makeCacheableSignalKeyStore( logger?: ILogger, _cache?: CacheStore ): SignalKeyStore { - const cache = _cache || new NodeCache({ - stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE, // 5 minutes - useClones: false, - deleteOnExpire: true, - }) + const cache = + _cache || + new NodeCache({ + stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE, // 5 minutes + useClones: false, + deleteOnExpire: true + }) function getUniqueId(type: string, id: string) { return `${type}.${id}` @@ -29,23 +39,23 @@ export function makeCacheableSignalKeyStore( return { async get(type, ids) { - const data: { [_: string]: SignalDataTypeMap[typeof type] } = { } + const data: { [_: string]: SignalDataTypeMap[typeof type] } = {} const idsToFetch: string[] = [] - for(const id of ids) { + for (const id of ids) { const item = cache.get(getUniqueId(type, id)) - if(typeof item !== 'undefined') { + if (typeof item !== 'undefined') { data[id] = item } else { idsToFetch.push(id) } } - if(idsToFetch.length) { + if (idsToFetch.length) { logger?.trace({ items: idsToFetch.length }, 'loading from store') const fetched = await store.get(type, idsToFetch) - for(const id of idsToFetch) { + for (const id of idsToFetch) { const item = fetched[id] - if(item) { + if (item) { data[id] = item cache.set(getUniqueId(type, id), item) } @@ -56,8 +66,8 @@ export function makeCacheableSignalKeyStore( }, async set(data) { let keys = 0 - for(const type in data) { - for(const id in data[type]) { + for (const type in data) { + for (const id in data[type]) { cache.set(getUniqueId(type, id), data[type][id]) keys += 1 } @@ -89,52 +99,45 @@ export const addTransactionCapability = ( // number of queries made to the DB during the transaction // only there for logging purposes let dbQueriesInTransaction = 0 - let transactionCache: SignalDataSet = { } - let mutations: SignalDataSet = { } + let transactionCache: SignalDataSet = {} + let mutations: SignalDataSet = {} let transactionsInProgress = 0 return { - get: async(type, ids) => { - if(isInTransaction()) { + get: async (type, ids) => { + if (isInTransaction()) { const dict = transactionCache[type] - const idsRequiringFetch = dict - ? ids.filter(item => typeof dict[item] === 'undefined') - : ids + const idsRequiringFetch = dict ? ids.filter(item => typeof dict[item] === 'undefined') : ids // only fetch if there are any items to fetch - if(idsRequiringFetch.length) { + if (idsRequiringFetch.length) { dbQueriesInTransaction += 1 const result = await state.get(type, idsRequiringFetch) transactionCache[type] ||= {} - Object.assign( - transactionCache[type]!, - result - ) + Object.assign(transactionCache[type]!, result) } - return ids.reduce( - (dict, id) => { - const value = transactionCache[type]?.[id] - if(value) { - dict[id] = value - } + return ids.reduce((dict, id) => { + const value = transactionCache[type]?.[id] + if (value) { + dict[id] = value + } - return dict - }, { } - ) + return dict + }, {}) } else { return state.get(type, ids) } }, set: data => { - if(isInTransaction()) { + if (isInTransaction()) { logger.trace({ types: Object.keys(data) }, 'caching in transaction') - for(const key in data) { - transactionCache[key] = transactionCache[key] || { } + for (const key in data) { + transactionCache[key] = transactionCache[key] || {} Object.assign(transactionCache[key], data[key]) - mutations[key] = mutations[key] || { } + mutations[key] = mutations[key] || {} Object.assign(mutations[key], data[key]) } } else { @@ -145,27 +148,27 @@ export const addTransactionCapability = ( async transaction(work) { let result: Awaited> transactionsInProgress += 1 - if(transactionsInProgress === 1) { + if (transactionsInProgress === 1) { logger.trace('entering transaction') } try { result = await work() // commit if this is the outermost transaction - if(transactionsInProgress === 1) { - if(Object.keys(mutations).length) { + if (transactionsInProgress === 1) { + if (Object.keys(mutations).length) { logger.trace('committing transaction') // retry mechanism to ensure we've some recovery // in case a transaction fails in the first attempt let tries = maxCommitRetries - while(tries) { + while (tries) { tries -= 1 //eslint-disable-next-line max-depth try { await state.set(mutations) logger.trace({ dbQueriesInTransaction }, 'committed transaction') break - } catch(error) { + } catch (error) { logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`) await delay(delayBetweenTriesMs) } @@ -176,9 +179,9 @@ export const addTransactionCapability = ( } } finally { transactionsInProgress -= 1 - if(transactionsInProgress === 0) { - transactionCache = { } - mutations = { } + if (transactionsInProgress === 0) { + transactionCache = {} + mutations = {} dbQueriesInTransaction = 0 } } @@ -211,6 +214,6 @@ export const initAuthCreds = (): AuthenticationCreds => { registered: false, pairingCode: undefined, lastPropHash: undefined, - routingInfo: undefined, + routingInfo: undefined } } diff --git a/src/Utils/baileys-event-stream.ts b/src/Utils/baileys-event-stream.ts index 00e4121..eb0cc02 100644 --- a/src/Utils/baileys-event-stream.ts +++ b/src/Utils/baileys-event-stream.ts @@ -16,15 +16,13 @@ export const captureEventStream = (ev: BaileysEventEmitter, filename: string) => // write mutex so data is appended in order const writeMutex = makeMutex() // monkey patch eventemitter to capture all events - ev.emit = function(...args: any[]) { + ev.emit = function (...args: any[]) { const content = JSON.stringify({ timestamp: Date.now(), event: args[0], data: args[1] }) + '\n' const result = oldEmit.apply(ev, args) - writeMutex.mutex( - async() => { - await writeFile(filename, content, { flag: 'a' }) - } - ) + writeMutex.mutex(async () => { + await writeFile(filename, content, { flag: 'a' }) + }) return result } @@ -38,7 +36,7 @@ export const captureEventStream = (ev: BaileysEventEmitter, filename: string) => export const readAndEmitEventStream = (filename: string, delayIntervalMs = 0) => { const ev = new EventEmitter() as BaileysEventEmitter - const fireEvents = async() => { + const fireEvents = async () => { // from: https://stackoverflow.com/questions/6156501/read-a-file-one-line-at-a-time-in-node-js const fileStream = createReadStream(filename) @@ -49,10 +47,10 @@ export const readAndEmitEventStream = (filename: string, delayIntervalMs = 0) => // Note: we use the crlfDelay option to recognize all instances of CR LF // ('\r\n') in input.txt as a single line break. for await (const line of rl) { - if(line) { + if (line) { const { event, data } = JSON.parse(line) ev.emit(event, data) - delayIntervalMs && await delay(delayIntervalMs) + delayIntervalMs && (await delay(delayIntervalMs)) } } @@ -63,4 +61,4 @@ export const readAndEmitEventStream = (filename: string, delayIntervalMs = 0) => ev, task: fireEvents() } -} \ No newline at end of file +} diff --git a/src/Utils/business.ts b/src/Utils/business.ts index 57459a5..41f9ad6 100644 --- a/src/Utils/business.ts +++ b/src/Utils/business.ts @@ -1,6 +1,16 @@ import { Boom } from '@hapi/boom' import { createHash } from 'crypto' -import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product, ProductCreate, ProductUpdate, WAMediaUpload, WAMediaUploadFunction } from '../Types' +import { + CatalogCollection, + CatalogStatus, + OrderDetails, + OrderProduct, + Product, + ProductCreate, + ProductUpdate, + WAMediaUpload, + WAMediaUploadFunction +} from '../Types' import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary' import { getStream, getUrlFromDirectPath, toReadable } from './messages-media' @@ -11,28 +21,24 @@ export const parseCatalogNode = (node: BinaryNode) => { return { products, - nextPageCursor: paging - ? getBinaryNodeChildString(paging, 'after') - : undefined + nextPageCursor: paging ? getBinaryNodeChildString(paging, 'after') : undefined } } export const parseCollectionsNode = (node: BinaryNode) => { const collectionsNode = getBinaryNodeChild(node, 'collections') - const collections = getBinaryNodeChildren(collectionsNode, 'collection').map( - collectionNode => { - const id = getBinaryNodeChildString(collectionNode, 'id')! - const name = getBinaryNodeChildString(collectionNode, 'name')! + const collections = getBinaryNodeChildren(collectionsNode, 'collection').map(collectionNode => { + const id = getBinaryNodeChildString(collectionNode, 'id')! + const name = getBinaryNodeChildString(collectionNode, 'name')! - const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode) - return { - id, - name, - products, - status: parseStatusInfo(collectionNode) - } + const products = getBinaryNodeChildren(collectionNode, 'product').map(parseProductNode) + return { + id, + name, + products, + status: parseStatusInfo(collectionNode) } - ) + }) return { collections @@ -41,26 +47,24 @@ export const parseCollectionsNode = (node: BinaryNode) => { export const parseOrderDetailsNode = (node: BinaryNode) => { const orderNode = getBinaryNodeChild(node, 'order') - const products = getBinaryNodeChildren(orderNode, 'product').map( - productNode => { - const imageNode = getBinaryNodeChild(productNode, 'image')! - return { - id: getBinaryNodeChildString(productNode, 'id')!, - name: getBinaryNodeChildString(productNode, 'name')!, - imageUrl: getBinaryNodeChildString(imageNode, 'url')!, - price: +getBinaryNodeChildString(productNode, 'price')!, - currency: getBinaryNodeChildString(productNode, 'currency')!, - quantity: +getBinaryNodeChildString(productNode, 'quantity')! - } + const products = getBinaryNodeChildren(orderNode, 'product').map(productNode => { + const imageNode = getBinaryNodeChild(productNode, 'image')! + return { + id: getBinaryNodeChildString(productNode, 'id')!, + name: getBinaryNodeChildString(productNode, 'name')!, + imageUrl: getBinaryNodeChildString(imageNode, 'url')!, + price: +getBinaryNodeChildString(productNode, 'price')!, + currency: getBinaryNodeChildString(productNode, 'currency')!, + quantity: +getBinaryNodeChildString(productNode, 'quantity')! } - ) + }) const priceNode = getBinaryNodeChild(orderNode, 'price') const orderDetails: OrderDetails = { price: { total: +getBinaryNodeChildString(priceNode, 'total')!, - currency: getBinaryNodeChildString(priceNode, 'currency')!, + currency: getBinaryNodeChildString(priceNode, 'currency')! }, products } @@ -69,94 +73,92 @@ export const parseOrderDetailsNode = (node: BinaryNode) => { } export const toProductNode = (productId: string | undefined, product: ProductCreate | ProductUpdate) => { - const attrs: BinaryNode['attrs'] = { } - const content: BinaryNode[] = [ ] + const attrs: BinaryNode['attrs'] = {} + const content: BinaryNode[] = [] - if(typeof productId !== 'undefined') { + if (typeof productId !== 'undefined') { content.push({ tag: 'id', - attrs: { }, + attrs: {}, content: Buffer.from(productId) }) } - if(typeof product.name !== 'undefined') { + if (typeof product.name !== 'undefined') { content.push({ tag: 'name', - attrs: { }, + attrs: {}, content: Buffer.from(product.name) }) } - if(typeof product.description !== 'undefined') { + if (typeof product.description !== 'undefined') { content.push({ tag: 'description', - attrs: { }, + attrs: {}, content: Buffer.from(product.description) }) } - if(typeof product.retailerId !== 'undefined') { + if (typeof product.retailerId !== 'undefined') { content.push({ tag: 'retailer_id', - attrs: { }, + attrs: {}, content: Buffer.from(product.retailerId) }) } - if(product.images.length) { + if (product.images.length) { content.push({ tag: 'media', - attrs: { }, - content: product.images.map( - img => { - if(!('url' in img)) { - throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 }) - } - - return { - tag: 'image', - attrs: { }, - content: [ - { - tag: 'url', - attrs: { }, - content: Buffer.from(img.url.toString()) - } - ] - } + attrs: {}, + content: product.images.map(img => { + if (!('url' in img)) { + throw new Boom('Expected img for product to already be uploaded', { statusCode: 400 }) } - ) + + return { + tag: 'image', + attrs: {}, + content: [ + { + tag: 'url', + attrs: {}, + content: Buffer.from(img.url.toString()) + } + ] + } + }) }) } - if(typeof product.price !== 'undefined') { + if (typeof product.price !== 'undefined') { content.push({ tag: 'price', - attrs: { }, + attrs: {}, content: Buffer.from(product.price.toString()) }) } - if(typeof product.currency !== 'undefined') { + if (typeof product.currency !== 'undefined') { content.push({ tag: 'currency', - attrs: { }, + attrs: {}, content: Buffer.from(product.currency) }) } - if('originCountryCode' in product) { - if(typeof product.originCountryCode === 'undefined') { + if ('originCountryCode' in product) { + if (typeof product.originCountryCode === 'undefined') { attrs['compliance_category'] = 'COUNTRY_ORIGIN_EXEMPT' } else { content.push({ tag: 'compliance_info', - attrs: { }, + attrs: {}, content: [ { tag: 'country_code_origin', - attrs: { }, + attrs: {}, content: Buffer.from(product.originCountryCode) } ] @@ -164,8 +166,7 @@ export const toProductNode = (productId: string | undefined, product: ProductCre } } - - if(typeof product.isHidden !== 'undefined') { + if (typeof product.isHidden !== 'undefined') { attrs['is_hidden'] = product.isHidden.toString() } @@ -188,16 +189,16 @@ export const parseProductNode = (productNode: BinaryNode) => { id, imageUrls: parseImageUrls(mediaNode), reviewStatus: { - whatsapp: getBinaryNodeChildString(statusInfoNode, 'status')!, + whatsapp: getBinaryNodeChildString(statusInfoNode, 'status')! }, availability: 'in stock', name: getBinaryNodeChildString(productNode, 'name')!, retailerId: getBinaryNodeChildString(productNode, 'retailer_id'), url: getBinaryNodeChildString(productNode, 'url'), description: getBinaryNodeChildString(productNode, 'description')!, - price: +getBinaryNodeChildString(productNode, 'price')!, + price: +getBinaryNodeChildString(productNode, 'price')!, currency: getBinaryNodeChildString(productNode, 'currency')!, - isHidden, + isHidden } return product @@ -206,10 +207,16 @@ export const parseProductNode = (productNode: BinaryNode) => { /** * Uploads images not already uploaded to WA's servers */ -export async function uploadingNecessaryImagesOfProduct(product: T, waUploadToServer: WAMediaUploadFunction, timeoutMs = 30_000) { +export async function uploadingNecessaryImagesOfProduct( + product: T, + waUploadToServer: WAMediaUploadFunction, + timeoutMs = 30_000 +) { product = { ...product, - images: product.images ? await uploadingNecessaryImages(product.images, waUploadToServer, timeoutMs) : product.images + images: product.images + ? await uploadingNecessaryImages(product.images, waUploadToServer, timeoutMs) + : product.images } return product } @@ -217,43 +224,37 @@ export async function uploadingNecessaryImagesOfProduct { const results = await Promise.all( - images.map>( - async img => { - - if('url' in img) { - const url = img.url.toString() - if(url.includes('.whatsapp.net')) { - return { url } - } + images.map>(async img => { + if ('url' in img) { + const url = img.url.toString() + if (url.includes('.whatsapp.net')) { + return { url } } - - const { stream } = await getStream(img) - const hasher = createHash('sha256') - const contentBlocks: Buffer[] = [] - for await (const block of stream) { - hasher.update(block) - contentBlocks.push(block) - } - - const sha = hasher.digest('base64') - - const { directPath } = await waUploadToServer( - toReadable(Buffer.concat(contentBlocks)), - { - mediaType: 'product-catalog-image', - fileEncSha256B64: sha, - timeoutMs - } - ) - return { url: getUrlFromDirectPath(directPath) } } - ) + + const { stream } = await getStream(img) + const hasher = createHash('sha256') + const contentBlocks: Buffer[] = [] + for await (const block of stream) { + hasher.update(block) + contentBlocks.push(block) + } + + const sha = hasher.digest('base64') + + const { directPath } = await waUploadToServer(toReadable(Buffer.concat(contentBlocks)), { + mediaType: 'product-catalog-image', + fileEncSha256B64: sha, + timeoutMs + }) + return { url: getUrlFromDirectPath(directPath) } + }) ) return results } @@ -270,6 +271,6 @@ const parseStatusInfo = (mediaNode: BinaryNode): CatalogStatus => { const node = getBinaryNodeChild(mediaNode, 'status_info') return { status: getBinaryNodeChildString(node, 'status')!, - canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true', + canAppeal: getBinaryNodeChildString(node, 'can_appeal') === 'true' } -} \ No newline at end of file +} diff --git a/src/Utils/chat-utils.ts b/src/Utils/chat-utils.ts index 945439e..aa72468 100644 --- a/src/Utils/chat-utils.ts +++ b/src/Utils/chat-utils.ts @@ -1,20 +1,32 @@ import { Boom } from '@hapi/boom' import { AxiosRequestConfig } from 'axios' import { proto } from '../../WAProto' -import { BaileysEventEmitter, Chat, ChatModification, ChatMutation, ChatUpdate, Contact, InitialAppStateSyncOptions, LastMessageList, LTHashState, WAPatchCreate, WAPatchName } from '../Types' +import { + BaileysEventEmitter, + Chat, + ChatModification, + ChatMutation, + ChatUpdate, + Contact, + InitialAppStateSyncOptions, + LastMessageList, + LTHashState, + WAPatchCreate, + WAPatchName +} from '../Types' import { ChatLabelAssociation, LabelAssociationType, MessageLabelAssociation } from '../Types/LabelAssociation' import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary' import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto' import { toNumber } from './generics' import { ILogger } from './logger' import { LT_HASH_ANTI_TAMPERING } from './lt-hash' -import { downloadContentFromMessage, } from './messages-media' +import { downloadContentFromMessage } from './messages-media' type FetchAppStateSyncKey = (keyId: string) => Promise export type ChatMutationMap = { [index: string]: ChatMutation } -const mutationKeys = async(keydata: Uint8Array) => { +const mutationKeys = async (keydata: Uint8Array) => { const expanded = await hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' }) return { indexKey: expanded.slice(0, 32), @@ -25,16 +37,21 @@ const mutationKeys = async(keydata: Uint8Array) => { } } -const generateMac = (operation: proto.SyncdMutation.SyncdOperation, data: Buffer, keyId: Uint8Array | string, key: Buffer) => { +const generateMac = ( + operation: proto.SyncdMutation.SyncdOperation, + data: Buffer, + keyId: Uint8Array | string, + key: Buffer +) => { const getKeyData = () => { let r: number switch (operation) { - case proto.SyncdMutation.SyncdOperation.SET: - r = 0x01 - break - case proto.SyncdMutation.SyncdOperation.REMOVE: - r = 0x02 - break + case proto.SyncdMutation.SyncdOperation.SET: + r = 0x01 + break + case proto.SyncdMutation.SyncdOperation.REMOVE: + r = 0x02 + break } const buff = Buffer.from([r]) @@ -58,7 +75,7 @@ const to64BitNetworkOrder = (e: number) => { return buff } -type Mac = { indexMac: Uint8Array, valueMac: Uint8Array, operation: proto.SyncdMutation.SyncdOperation } +type Mac = { indexMac: Uint8Array; valueMac: Uint8Array; operation: proto.SyncdMutation.SyncdOperation } const makeLtHashGenerator = ({ indexValueMap, hash }: Pick) => { indexValueMap = { ...indexValueMap } @@ -69,8 +86,8 @@ const makeLtHashGenerator = ({ indexValueMap, hash }: Pick { const indexMacBase64 = Buffer.from(indexMac).toString('base64') const prevOp = indexValueMap[indexMacBase64] - if(operation === proto.SyncdMutation.SyncdOperation.REMOVE) { - if(!prevOp) { + if (operation === proto.SyncdMutation.SyncdOperation.REMOVE) { + if (!prevOp) { throw new Boom('tried remove, but no previous op', { data: { indexMac, valueMac } }) } @@ -82,11 +99,11 @@ const makeLtHashGenerator = ({ indexValueMap, hash }: Pick { + finish: async () => { const hashArrayBuffer = new Uint8Array(hash).buffer const result = await LT_HASH_ANTI_TAMPERING.subtractThenAdd(hashArrayBuffer, addBuffs, subBuffs) const buffer = Buffer.from(result) @@ -100,34 +117,31 @@ const makeLtHashGenerator = ({ indexValueMap, hash }: Pick { - const total = Buffer.concat([ - lthash, - to64BitNetworkOrder(version), - Buffer.from(name, 'utf-8') - ]) + const total = Buffer.concat([lthash, to64BitNetworkOrder(version), Buffer.from(name, 'utf-8')]) return hmacSign(total, key, 'sha256') } -const generatePatchMac = (snapshotMac: Uint8Array, valueMacs: Uint8Array[], version: number, type: WAPatchName, key: Buffer) => { - const total = Buffer.concat([ - snapshotMac, - ...valueMacs, - to64BitNetworkOrder(version), - Buffer.from(type, 'utf-8') - ]) +const generatePatchMac = ( + snapshotMac: Uint8Array, + valueMacs: Uint8Array[], + version: number, + type: WAPatchName, + key: Buffer +) => { + const total = Buffer.concat([snapshotMac, ...valueMacs, to64BitNetworkOrder(version), Buffer.from(type, 'utf-8')]) return hmacSign(total, key) } export const newLTHashState = (): LTHashState => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} }) -export const encodeSyncdPatch = async( +export const encodeSyncdPatch = async ( { type, index, syncAction, apiVersion, operation }: WAPatchCreate, myAppStateKeyId: string, state: LTHashState, getAppStateSyncKey: FetchAppStateSyncKey ) => { const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined - if(!key) { + if (!key) { throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404 }) } @@ -185,7 +199,7 @@ export const encodeSyncdPatch = async( return { patch, state } } -export const decodeSyncdMutations = async( +export const decodeSyncdMutations = async ( msgMutations: (proto.ISyncdMutation | proto.ISyncdRecord)[], initialState: LTHashState, getAppStateSyncKey: FetchAppStateSyncKey, @@ -196,19 +210,20 @@ export const decodeSyncdMutations = async( // indexKey used to HMAC sign record.index.blob // valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32] // the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId - for(const msgMutation of msgMutations) { + for (const msgMutation of msgMutations) { // if it's a syncdmutation, get the operation property // otherwise, if it's only a record -- it'll be a SET mutation const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET - const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation as proto.ISyncdRecord + const record = + 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : (msgMutation as proto.ISyncdRecord) const key = await getKey(record.keyId!.id!) const content = Buffer.from(record.value!.blob!) const encContent = content.slice(0, -32) const ogValueMac = content.slice(-32) - if(validateMacs) { + if (validateMacs) { const contentHmac = generateMac(operation!, encContent, record.keyId!.id!, key.valueMacKey) - if(Buffer.compare(contentHmac, ogValueMac) !== 0) { + if (Buffer.compare(contentHmac, ogValueMac) !== 0) { throw new Boom('HMAC content verification failed') } } @@ -216,9 +231,9 @@ export const decodeSyncdMutations = async( const result = aesDecrypt(encContent, key.valueEncryptionKey) const syncAction = proto.SyncActionData.decode(result) - if(validateMacs) { + if (validateMacs) { const hmac = hmacSign(syncAction.index!, key.indexKey) - if(Buffer.compare(hmac, record.index!.blob!) !== 0) { + if (Buffer.compare(hmac, record.index!.blob!) !== 0) { throw new Boom('HMAC index verification failed') } } @@ -238,15 +253,18 @@ export const decodeSyncdMutations = async( async function getKey(keyId: Uint8Array) { const base64Key = Buffer.from(keyId).toString('base64') const keyEnc = await getAppStateSyncKey(base64Key) - if(!keyEnc) { - throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } }) + if (!keyEnc) { + throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { + statusCode: 404, + data: { msgMutations } + }) } return mutationKeys(keyEnc.keyData!) } } -export const decodeSyncdPatch = async( +export const decodeSyncdPatch = async ( msg: proto.ISyncdPatch, name: WAPatchName, initialState: LTHashState, @@ -254,18 +272,24 @@ export const decodeSyncdPatch = async( onMutation: (mutation: ChatMutation) => void, validateMacs: boolean ) => { - if(validateMacs) { + if (validateMacs) { const base64Key = Buffer.from(msg.keyId!.id!).toString('base64') const mainKeyObj = await getAppStateSyncKey(base64Key) - if(!mainKeyObj) { + if (!mainKeyObj) { throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } }) } const mainKey = await mutationKeys(mainKeyObj.keyData!) const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32)) - const patchMac = generatePatchMac(msg.snapshotMac!, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey) - if(Buffer.compare(patchMac, msg.patchMac!) !== 0) { + const patchMac = generatePatchMac( + msg.snapshotMac!, + mutationmacs, + toNumber(msg.version!.version), + name, + mainKey.patchMacKey + ) + if (Buffer.compare(patchMac, msg.patchMac!) !== 0) { throw new Boom('Invalid patch mac') } } @@ -274,68 +298,59 @@ export const decodeSyncdPatch = async( return result } -export const extractSyncdPatches = async( - result: BinaryNode, - options: AxiosRequestConfig<{}> -) => { +export const extractSyncdPatches = async (result: BinaryNode, options: AxiosRequestConfig<{}>) => { const syncNode = getBinaryNodeChild(result, 'sync') const collectionNodes = getBinaryNodeChildren(syncNode, 'collection') - const final = {} as { [T in WAPatchName]: { patches: proto.ISyncdPatch[], hasMorePatches: boolean, snapshot?: proto.ISyncdSnapshot } } + const final = {} as { + [T in WAPatchName]: { patches: proto.ISyncdPatch[]; hasMorePatches: boolean; snapshot?: proto.ISyncdSnapshot } + } await Promise.all( - collectionNodes.map( - async collectionNode => { - const patchesNode = getBinaryNodeChild(collectionNode, 'patches') + collectionNodes.map(async collectionNode => { + const patchesNode = getBinaryNodeChild(collectionNode, 'patches') - const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch') - const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot') + const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch') + const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot') - const syncds: proto.ISyncdPatch[] = [] - const name = collectionNode.attrs.name as WAPatchName + const syncds: proto.ISyncdPatch[] = [] + const name = collectionNode.attrs.name as WAPatchName - const hasMorePatches = collectionNode.attrs.has_more_patches === 'true' + const hasMorePatches = collectionNode.attrs.has_more_patches === 'true' - let snapshot: proto.ISyncdSnapshot | undefined = undefined - if(snapshotNode && !!snapshotNode.content) { - if(!Buffer.isBuffer(snapshotNode)) { - snapshotNode.content = Buffer.from(Object.values(snapshotNode.content)) - } - - const blobRef = proto.ExternalBlobReference.decode( - snapshotNode.content as Buffer - ) - const data = await downloadExternalBlob(blobRef, options) - snapshot = proto.SyncdSnapshot.decode(data) + let snapshot: proto.ISyncdSnapshot | undefined = undefined + if (snapshotNode && !!snapshotNode.content) { + if (!Buffer.isBuffer(snapshotNode)) { + snapshotNode.content = Buffer.from(Object.values(snapshotNode.content)) } - for(let { content } of patches) { - if(content) { - if(!Buffer.isBuffer(content)) { - content = Buffer.from(Object.values(content)) - } - - const syncd = proto.SyncdPatch.decode(content as Uint8Array) - if(!syncd.version) { - syncd.version = { version: +collectionNode.attrs.version + 1 } - } - - syncds.push(syncd) - } - } - - final[name] = { patches: syncds, hasMorePatches, snapshot } + const blobRef = proto.ExternalBlobReference.decode(snapshotNode.content as Buffer) + const data = await downloadExternalBlob(blobRef, options) + snapshot = proto.SyncdSnapshot.decode(data) } - ) + + for (let { content } of patches) { + if (content) { + if (!Buffer.isBuffer(content)) { + content = Buffer.from(Object.values(content)) + } + + const syncd = proto.SyncdPatch.decode(content as Uint8Array) + if (!syncd.version) { + syncd.version = { version: +collectionNode.attrs.version + 1 } + } + + syncds.push(syncd) + } + } + + final[name] = { patches: syncds, hasMorePatches, snapshot } + }) ) return final } - -export const downloadExternalBlob = async( - blob: proto.IExternalBlobReference, - options: AxiosRequestConfig<{}> -) => { +export const downloadExternalBlob = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => { const stream = await downloadContentFromMessage(blob, 'md-app-state', { options }) const bufferArray: Buffer[] = [] for await (const chunk of stream) { @@ -345,16 +360,13 @@ export const downloadExternalBlob = async( return Buffer.concat(bufferArray) } -export const downloadExternalPatch = async( - blob: proto.IExternalBlobReference, - options: AxiosRequestConfig<{}> -) => { +export const downloadExternalPatch = async (blob: proto.IExternalBlobReference, options: AxiosRequestConfig<{}>) => { const buffer = await downloadExternalBlob(blob, options) const syncData = proto.SyncdMutations.decode(buffer) return syncData } -export const decodeSyncdSnapshot = async( +export const decodeSyncdSnapshot = async ( name: WAPatchName, snapshot: proto.ISyncdSnapshot, getAppStateSyncKey: FetchAppStateSyncKey, @@ -365,34 +377,33 @@ export const decodeSyncdSnapshot = async( newState.version = toNumber(snapshot.version!.version) const mutationMap: ChatMutationMap = {} - const areMutationsRequired = typeof minimumVersionNumber === 'undefined' - || newState.version > minimumVersionNumber + const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber const { hash, indexValueMap } = await decodeSyncdMutations( snapshot.records!, newState, getAppStateSyncKey, areMutationsRequired - ? (mutation) => { - const index = mutation.syncAction.index?.toString() - mutationMap[index!] = mutation - } - : () => { }, + ? mutation => { + const index = mutation.syncAction.index?.toString() + mutationMap[index!] = mutation + } + : () => {}, validateMacs ) newState.hash = hash newState.indexValueMap = indexValueMap - if(validateMacs) { + if (validateMacs) { const base64Key = Buffer.from(snapshot.keyId!.id!).toString('base64') const keyEnc = await getAppStateSyncKey(base64Key) - if(!keyEnc) { + if (!keyEnc) { throw new Boom(`failed to find key "${base64Key}" to decode mutation`) } const result = await mutationKeys(keyEnc.keyData!) const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey) - if(Buffer.compare(snapshot.mac!, computedSnapshotMac) !== 0) { + if (Buffer.compare(snapshot.mac!, computedSnapshotMac) !== 0) { throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`) } } @@ -403,7 +414,7 @@ export const decodeSyncdSnapshot = async( } } -export const decodePatches = async( +export const decodePatches = async ( name: WAPatchName, syncds: proto.ISyncdPatch[], initial: LTHashState, @@ -420,9 +431,9 @@ export const decodePatches = async( const mutationMap: ChatMutationMap = {} - for(const syncd of syncds) { + for (const syncd of syncds) { const { version, keyId, snapshotMac } = syncd - if(syncd.externalMutations) { + if (syncd.externalMutations) { logger?.trace({ name, version }, 'downloading external patch') const ref = await downloadExternalPatch(syncd.externalMutations, options) logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch') @@ -441,26 +452,26 @@ export const decodePatches = async( getAppStateSyncKey, shouldMutate ? mutation => { - const index = mutation.syncAction.index?.toString() - mutationMap[index!] = mutation - } - : (() => { }), + const index = mutation.syncAction.index?.toString() + mutationMap[index!] = mutation + } + : () => {}, true ) newState.hash = decodeResult.hash newState.indexValueMap = decodeResult.indexValueMap - if(validateMacs) { + if (validateMacs) { const base64Key = Buffer.from(keyId!.id!).toString('base64') const keyEnc = await getAppStateSyncKey(base64Key) - if(!keyEnc) { + if (!keyEnc) { throw new Boom(`failed to find key "${base64Key}" to decode mutation`) } const result = await mutationKeys(keyEnc.keyData!) const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey) - if(Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) { + if (Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) { throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`) } } @@ -472,38 +483,35 @@ export const decodePatches = async( return { state: newState, mutationMap } } -export const chatModificationToAppPatch = ( - mod: ChatModification, - jid: string -) => { +export const chatModificationToAppPatch = (mod: ChatModification, jid: string) => { const OP = proto.SyncdMutation.SyncdOperation const getMessageRange = (lastMessages: LastMessageList) => { let messageRange: proto.SyncActionValue.ISyncActionMessageRange - if(Array.isArray(lastMessages)) { + if (Array.isArray(lastMessages)) { const lastMsg = lastMessages[lastMessages.length - 1] messageRange = { lastMessageTimestamp: lastMsg?.messageTimestamp, - messages: lastMessages?.length ? lastMessages.map( - m => { - if(!m.key?.id || !m.key?.remoteJid) { - throw new Boom('Incomplete key', { statusCode: 400, data: m }) - } + messages: lastMessages?.length + ? lastMessages.map(m => { + if (!m.key?.id || !m.key?.remoteJid) { + throw new Boom('Incomplete key', { statusCode: 400, data: m }) + } - if(isJidGroup(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) { - throw new Boom('Expected not from me message to have participant', { statusCode: 400, data: m }) - } + if (isJidGroup(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) { + throw new Boom('Expected not from me message to have participant', { statusCode: 400, data: m }) + } - if(!m.messageTimestamp || !toNumber(m.messageTimestamp)) { - throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m }) - } + if (!m.messageTimestamp || !toNumber(m.messageTimestamp)) { + throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m }) + } - if(m.key.participant) { - m.key.participant = jidNormalizedUser(m.key.participant) - } + if (m.key.participant) { + m.key.participant = jidNormalizedUser(m.key.participant) + } - return m - } - ) : undefined + return m + }) + : undefined } } else { messageRange = lastMessages @@ -513,7 +521,7 @@ export const chatModificationToAppPatch = ( } let patch: WAPatchCreate - if('mute' in mod) { + if ('mute' in mod) { patch = { syncAction: { muteAction: { @@ -526,7 +534,7 @@ export const chatModificationToAppPatch = ( apiVersion: 2, operation: OP.SET } - } else if('archive' in mod) { + } else if ('archive' in mod) { patch = { syncAction: { archiveChatAction: { @@ -539,7 +547,7 @@ export const chatModificationToAppPatch = ( apiVersion: 3, operation: OP.SET } - } else if('markRead' in mod) { + } else if ('markRead' in mod) { patch = { syncAction: { markChatAsReadAction: { @@ -552,7 +560,7 @@ export const chatModificationToAppPatch = ( apiVersion: 3, operation: OP.SET } - } else if('deleteForMe' in mod) { + } else if ('deleteForMe' in mod) { const { timestamp, key, deleteMedia } = mod.deleteForMe patch = { syncAction: { @@ -566,7 +574,7 @@ export const chatModificationToAppPatch = ( apiVersion: 3, operation: OP.SET } - } else if('clear' in mod) { + } else if ('clear' in mod) { patch = { syncAction: { clearChatAction: {} // add message range later @@ -576,7 +584,7 @@ export const chatModificationToAppPatch = ( apiVersion: 6, operation: OP.SET } - } else if('pin' in mod) { + } else if ('pin' in mod) { patch = { syncAction: { pinAction: { @@ -588,7 +596,7 @@ export const chatModificationToAppPatch = ( apiVersion: 5, operation: OP.SET } - } else if('star' in mod) { + } else if ('star' in mod) { const key = mod.star.messages[0] patch = { syncAction: { @@ -601,11 +609,11 @@ export const chatModificationToAppPatch = ( apiVersion: 2, operation: OP.SET } - } else if('delete' in mod) { + } else if ('delete' in mod) { patch = { syncAction: { deleteChatAction: { - messageRange: getMessageRange(mod.lastMessages), + messageRange: getMessageRange(mod.lastMessages) } }, index: ['deleteChat', jid, '1'], @@ -613,7 +621,7 @@ export const chatModificationToAppPatch = ( apiVersion: 6, operation: OP.SET } - } else if('pushNameSetting' in mod) { + } else if ('pushNameSetting' in mod) { patch = { syncAction: { pushNameSetting: { @@ -623,71 +631,64 @@ export const chatModificationToAppPatch = ( index: ['setting_pushName'], type: 'critical_block', apiVersion: 1, - operation: OP.SET, + operation: OP.SET } - } else if('addLabel' in mod) { + } else if ('addLabel' in mod) { patch = { syncAction: { labelEditAction: { name: mod.addLabel.name, color: mod.addLabel.color, - predefinedId : mod.addLabel.predefinedId, + predefinedId: mod.addLabel.predefinedId, deleted: mod.addLabel.deleted } }, index: ['label_edit', mod.addLabel.id], type: 'regular', apiVersion: 3, - operation: OP.SET, + operation: OP.SET } - } else if('addChatLabel' in mod) { + } else if ('addChatLabel' in mod) { patch = { syncAction: { labelAssociationAction: { - labeled: true, + labeled: true } }, index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid], type: 'regular', apiVersion: 3, - operation: OP.SET, + operation: OP.SET } - } else if('removeChatLabel' in mod) { + } else if ('removeChatLabel' in mod) { patch = { syncAction: { labelAssociationAction: { - labeled: false, + labeled: false } }, index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid], type: 'regular', apiVersion: 3, - operation: OP.SET, + operation: OP.SET } - } else if('addMessageLabel' in mod) { + } else if ('addMessageLabel' in mod) { patch = { syncAction: { labelAssociationAction: { - labeled: true, + labeled: true } }, - index: [ - LabelAssociationType.Message, - mod.addMessageLabel.labelId, - jid, - mod.addMessageLabel.messageId, - '0', - '0' - ], + index: [LabelAssociationType.Message, mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId, '0', '0'], type: 'regular', apiVersion: 3, - operation: OP.SET, + operation: OP.SET } - } else if('removeMessageLabel' in mod) { + } else if ('removeMessageLabel' in mod) { patch = { syncAction: { labelAssociationAction: { - labeled: false, + labeled: false } }, index: [ @@ -700,7 +701,7 @@ export const chatModificationToAppPatch = ( ], type: 'regular', apiVersion: 3, - operation: OP.SET, + operation: OP.SET } } else { throw new Boom('not supported') @@ -716,7 +717,7 @@ export const processSyncAction = ( ev: BaileysEventEmitter, me: Contact, initialSyncOpts?: InitialAppStateSyncOptions, - logger?: ILogger, + logger?: ILogger ) => { const isInitialSync = !!initialSyncOpts const accountSettings = initialSyncOpts?.accountSettings @@ -728,20 +729,15 @@ export const processSyncAction = ( index: [type, id, msgId, fromMe] } = syncAction - if(action?.muteAction) { - ev.emit( - 'chats.update', - [ - { - id, - muteEndTime: action.muteAction?.muted - ? toNumber(action.muteAction.muteEndTimestamp) - : null, - conditional: getChatUpdateConditional(id, undefined) - } - ] - ) - } else if(action?.archiveChatAction || type === 'archive' || type === 'unarchive') { + if (action?.muteAction) { + ev.emit('chats.update', [ + { + id, + muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null, + conditional: getChatUpdateConditional(id, undefined) + } + ]) + } else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') { // okay so we've to do some annoying computation here // when we're initially syncing the app state // there are a few cases we need to handle @@ -753,9 +749,7 @@ export const processSyncAction = ( // 2. if the account unarchiveChats setting is false -- then it doesn't matter, // it'll always take an app state action to mark in unarchived -- which we'll get anyway const archiveAction = action?.archiveChatAction - const isArchived = archiveAction - ? archiveAction.archived - : type === 'archive' + const isArchived = archiveAction ? archiveAction.archived : type === 'archive' // // basically we don't need to fire an "archive" update if the chat is being marked unarchvied // // this only applies for the initial sync // if(isInitialSync && !isArchived) { @@ -765,24 +759,28 @@ export const processSyncAction = ( const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange // logger?.debug({ chat: id, syncAction }, 'message range archive') - ev.emit('chats.update', [{ - id, - archived: isArchived, - conditional: getChatUpdateConditional(id, msgRange) - }]) - } else if(action?.markChatAsReadAction) { + ev.emit('chats.update', [ + { + id, + archived: isArchived, + conditional: getChatUpdateConditional(id, msgRange) + } + ]) + } else if (action?.markChatAsReadAction) { const markReadAction = action.markChatAsReadAction // basically we don't need to fire an "read" update if the chat is being marked as read // because the chat is read by default // this only applies for the initial sync const isNullUpdate = isInitialSync && markReadAction.read - ev.emit('chats.update', [{ - id, - unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1, - conditional: getChatUpdateConditional(id, markReadAction?.messageRange) - }]) - } else if(action?.deleteMessageForMeAction || type === 'deleteMessageForMe') { + ev.emit('chats.update', [ + { + id, + unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1, + conditional: getChatUpdateConditional(id, markReadAction?.messageRange) + } + ]) + } else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') { ev.emit('messages.delete', { keys: [ { @@ -792,30 +790,32 @@ export const processSyncAction = ( } ] }) - } else if(action?.contactAction) { + } else if (action?.contactAction) { ev.emit('contacts.upsert', [{ id, name: action.contactAction.fullName! }]) - } else if(action?.pushNameSetting) { + } else if (action?.pushNameSetting) { const name = action?.pushNameSetting?.name - if(name && me?.name !== name) { + if (name && me?.name !== name) { ev.emit('creds.update', { me: { ...me, name } }) } - } else if(action?.pinAction) { - ev.emit('chats.update', [{ - id, - pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null, - conditional: getChatUpdateConditional(id, undefined) - }]) - } else if(action?.unarchiveChatsSetting) { + } else if (action?.pinAction) { + ev.emit('chats.update', [ + { + id, + pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null, + conditional: getChatUpdateConditional(id, undefined) + } + ]) + } else if (action?.unarchiveChatsSetting) { const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats ev.emit('creds.update', { accountSettings: { unarchiveChats } }) logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`) - if(accountSettings) { + if (accountSettings) { accountSettings.unarchiveChats = unarchiveChats } - } else if(action?.starAction || type === 'star') { + } else if (action?.starAction || type === 'star') { let starred = action?.starAction?.starred - if(typeof starred !== 'boolean') { + if (typeof starred !== 'boolean') { starred = syncAction.index[syncAction.index.length - 1] === '1' } @@ -825,11 +825,11 @@ export const processSyncAction = ( update: { starred } } ]) - } else if(action?.deleteChatAction || type === 'deleteChat') { - if(!isInitialSync) { + } else if (action?.deleteChatAction || type === 'deleteChat') { + if (!isInitialSync) { ev.emit('chats.delete', [id]) } - } else if(action?.labelEditAction) { + } else if (action?.labelEditAction) { const { name, color, deleted, predefinedId } = action.labelEditAction ev.emit('labels.edit', { @@ -839,42 +839,47 @@ export const processSyncAction = ( deleted: deleted!, predefinedId: predefinedId ? String(predefinedId) : undefined }) - } else if(action?.labelAssociationAction) { + } else if (action?.labelAssociationAction) { ev.emit('labels.association', { - type: action.labelAssociationAction.labeled - ? 'add' - : 'remove', - association: type === LabelAssociationType.Chat - ? { - type: LabelAssociationType.Chat, - chatId: syncAction.index[2], - labelId: syncAction.index[1] - } as ChatLabelAssociation - : { - type: LabelAssociationType.Message, - chatId: syncAction.index[2], - messageId: syncAction.index[3], - labelId: syncAction.index[1] - } as MessageLabelAssociation + type: action.labelAssociationAction.labeled ? 'add' : 'remove', + association: + type === LabelAssociationType.Chat + ? ({ + type: LabelAssociationType.Chat, + chatId: syncAction.index[2], + labelId: syncAction.index[1] + } as ChatLabelAssociation) + : ({ + type: LabelAssociationType.Message, + chatId: syncAction.index[2], + messageId: syncAction.index[3], + labelId: syncAction.index[1] + } as MessageLabelAssociation) }) } else { logger?.debug({ syncAction, id }, 'unprocessable update') } - function getChatUpdateConditional(id: string, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined): ChatUpdate['conditional'] { + function getChatUpdateConditional( + id: string, + msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined + ): ChatUpdate['conditional'] { return isInitialSync - ? (data) => { - const chat = data.historySets.chats[id] || data.chatUpserts[id] - if(chat) { - return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true + ? data => { + const chat = data.historySets.chats[id] || data.chatUpserts[id] + if (chat) { + return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true + } } - } : undefined } - function isValidPatchBasedOnMessageRange(chat: Chat, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined) { - const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0) - const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0) - return lastMsgTimestamp >= chatLastMsgTimestamp + function isValidPatchBasedOnMessageRange( + chat: Chat, + msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined + ) { + const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0) + const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0) + return lastMsgTimestamp >= chatLastMsgTimestamp } } diff --git a/src/Utils/crypto.ts b/src/Utils/crypto.ts index 3cf027c..4ae3dc9 100644 --- a/src/Utils/crypto.ts +++ b/src/Utils/crypto.ts @@ -7,11 +7,8 @@ import { KeyPair } from '../Types' const { subtle } = globalThis.crypto /** prefix version byte to the pub keys, required for some curve crypto functions */ -export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) => ( - pubKey.length === 33 - ? pubKey - : Buffer.concat([ KEY_BUNDLE_TYPE, pubKey ]) -) +export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) => + pubKey.length === 33 ? pubKey : Buffer.concat([KEY_BUNDLE_TYPE, pubKey]) export const Curve = { generateKeyPair: (): KeyPair => { @@ -26,14 +23,12 @@ export const Curve = { const shared = libsignal.curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey) return Buffer.from(shared) }, - sign: (privateKey: Uint8Array, buf: Uint8Array) => ( - libsignal.curve.calculateSignature(privateKey, buf) - ), + sign: (privateKey: Uint8Array, buf: Uint8Array) => libsignal.curve.calculateSignature(privateKey, buf), verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => { try { libsignal.curve.verifySignature(generateSignalPubKey(pubKey), message, signature) return true - } catch(error) { + } catch (error) { return false } } @@ -73,7 +68,7 @@ export function aesDecryptGCM(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8 decipher.setAAD(additionalData) decipher.setAuthTag(tag) - return Buffer.concat([ decipher.update(enc), decipher.final() ]) + return Buffer.concat([decipher.update(enc), decipher.final()]) } export function aesEncryptCTR(plaintext: Uint8Array, key: Uint8Array, iv: Uint8Array) { @@ -111,7 +106,11 @@ export function aesEncrypWithIV(buffer: Buffer, key: Buffer, IV: Buffer) { } // sign HMAC using SHA 256 -export function hmacSign(buffer: Buffer | Uint8Array, key: Buffer | Uint8Array, variant: 'sha256' | 'sha512' = 'sha256') { +export function hmacSign( + buffer: Buffer | Uint8Array, + key: Buffer | Uint8Array, + variant: 'sha256' | 'sha512' = 'sha256' +) { return createHmac(variant, key).update(buffer).digest() } @@ -127,27 +126,17 @@ export function md5(buffer: Buffer) { export async function hkdf( buffer: Uint8Array | Buffer, expandedLength: number, - info: { salt?: Buffer, info?: string } + info: { salt?: Buffer; info?: string } ): Promise { // Ensure we have a Uint8Array for the key material - const inputKeyMaterial = buffer instanceof Uint8Array - ? buffer - : new Uint8Array(buffer) + const inputKeyMaterial = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer) // Set default values if not provided const salt = info.salt ? new Uint8Array(info.salt) : new Uint8Array(0) - const infoBytes = info.info - ? new TextEncoder().encode(info.info) - : new Uint8Array(0) + const infoBytes = info.info ? new TextEncoder().encode(info.info) : new Uint8Array(0) // Import the input key material - const importedKey = await subtle.importKey( - 'raw', - inputKeyMaterial, - { name: 'HKDF' }, - false, - ['deriveBits'] - ) + const importedKey = await subtle.importKey('raw', inputKeyMaterial, { name: 'HKDF' }, false, ['deriveBits']) // Derive bits using HKDF const derivedBits = await subtle.deriveBits( @@ -164,7 +153,6 @@ export async function hkdf( return Buffer.from(derivedBits) } - export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): Promise { // Convert inputs to formats Web Crypto API can work with const encoder = new TextEncoder() @@ -172,13 +160,7 @@ export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): P const saltBuffer = salt instanceof Uint8Array ? salt : new Uint8Array(salt) // Import the pairing code as key material - const keyMaterial = await subtle.importKey( - 'raw', - pairingCodeBuffer, - { name: 'PBKDF2' }, - false, - ['deriveBits'] - ) + const keyMaterial = await subtle.importKey('raw', pairingCodeBuffer, { name: 'PBKDF2' }, false, ['deriveBits']) // Derive bits using PBKDF2 with the same parameters // 2 << 16 = 131,072 iterations diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index 789af5d..0f9a7b5 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -1,7 +1,17 @@ import { Boom } from '@hapi/boom' import { proto } from '../../WAProto' import { SignalRepository, WAMessageKey } from '../Types' -import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidMetaIa, isJidNewsletter, isJidStatusBroadcast, isJidUser, isLidUser } from '../WABinary' +import { + areJidsSameUser, + BinaryNode, + isJidBroadcast, + isJidGroup, + isJidMetaIa, + isJidNewsletter, + isJidStatusBroadcast, + isJidUser, + isLidUser +} from '../WABinary' import { unpadRandomMax16 } from './generics' import { ILogger } from './logger' @@ -24,17 +34,20 @@ export const NACK_REASONS = { DBOperationFailed: 552 } -type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'direct_peer_status' | 'other_status' | 'newsletter' +type MessageType = + | 'chat' + | 'peer_broadcast' + | 'other_broadcast' + | 'group' + | 'direct_peer_status' + | 'other_status' + | 'newsletter' /** * Decode the received node as a message. * @note this will only parse the message, not decrypt it */ -export function decodeMessageNode( - stanza: BinaryNode, - meId: string, - meLid: string -) { +export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: string) { let msgType: MessageType let chatId: string let author: string @@ -47,9 +60,9 @@ export function decodeMessageNode( const isMe = (jid: string) => areJidsSameUser(jid, meId) const isMeLid = (jid: string) => areJidsSameUser(jid, meLid) - if(isJidUser(from) || isLidUser(from)) { - if(recipient && !isJidMetaIa(recipient)) { - if(!isMe(from) && !isMeLid(from)) { + if (isJidUser(from) || isLidUser(from)) { + if (recipient && !isJidMetaIa(recipient)) { + if (!isMe(from) && !isMeLid(from)) { throw new Boom('receipient present, but msg not from me', { data: stanza }) } @@ -60,21 +73,21 @@ export function decodeMessageNode( msgType = 'chat' author = from - } else if(isJidGroup(from)) { - if(!participant) { + } else if (isJidGroup(from)) { + if (!participant) { throw new Boom('No participant in group message') } msgType = 'group' author = participant chatId = from - } else if(isJidBroadcast(from)) { - if(!participant) { + } else if (isJidBroadcast(from)) { + if (!participant) { throw new Boom('No participant in group message') } const isParticipantMe = isMe(participant) - if(isJidStatusBroadcast(from)) { + if (isJidStatusBroadcast(from)) { msgType = isParticipantMe ? 'direct_peer_status' : 'other_status' } else { msgType = isParticipantMe ? 'peer_broadcast' : 'other_broadcast' @@ -82,7 +95,7 @@ export function decodeMessageNode( chatId = from author = participant - } else if(isJidNewsletter(from)) { + } else if (isJidNewsletter(from)) { msgType = 'newsletter' chatId = from author = from @@ -107,7 +120,7 @@ export function decodeMessageNode( broadcast: isJidBroadcast(from) } - if(key.fromMe) { + if (key.fromMe) { fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK } @@ -132,19 +145,19 @@ export const decryptMessageNode = ( author, async decrypt() { let decryptables = 0 - if(Array.isArray(stanza.content)) { - for(const { tag, attrs, content } of stanza.content) { - if(tag === 'verified_name' && content instanceof Uint8Array) { + if (Array.isArray(stanza.content)) { + for (const { tag, attrs, content } of stanza.content) { + if (tag === 'verified_name' && content instanceof Uint8Array) { const cert = proto.VerifiedNameCertificate.decode(content) const details = proto.VerifiedNameCertificate.Details.decode(cert.details!) fullMessage.verifiedBizName = details.verifiedName } - if(tag !== 'enc' && tag !== 'plaintext') { + if (tag !== 'enc' && tag !== 'plaintext') { continue } - if(!(content instanceof Uint8Array)) { + if (!(content instanceof Uint8Array)) { continue } @@ -155,53 +168,52 @@ export const decryptMessageNode = ( try { const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type switch (e2eType) { - case 'skmsg': - msgBuffer = await repository.decryptGroupMessage({ - group: sender, - authorJid: author, - msg: content - }) - break - case 'pkmsg': - case 'msg': - const user = isJidUser(sender) ? sender : author - msgBuffer = await repository.decryptMessage({ - jid: user, - type: e2eType, - ciphertext: content - }) - break - case 'plaintext': - msgBuffer = content - break - default: - throw new Error(`Unknown e2e type: ${e2eType}`) + case 'skmsg': + msgBuffer = await repository.decryptGroupMessage({ + group: sender, + authorJid: author, + msg: content + }) + break + case 'pkmsg': + case 'msg': + const user = isJidUser(sender) ? sender : author + msgBuffer = await repository.decryptMessage({ + jid: user, + type: e2eType, + ciphertext: content + }) + break + case 'plaintext': + msgBuffer = content + break + default: + throw new Error(`Unknown e2e type: ${e2eType}`) } - let msg: proto.IMessage = proto.Message.decode(e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer) + let msg: proto.IMessage = proto.Message.decode( + e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer + ) msg = msg.deviceSentMessage?.message || msg - if(msg.senderKeyDistributionMessage) { + if (msg.senderKeyDistributionMessage) { //eslint-disable-next-line max-depth - try { + try { await repository.processSenderKeyDistributionMessage({ authorJid: author, item: msg.senderKeyDistributionMessage }) - } catch(err) { + } catch (err) { logger.error({ key: fullMessage.key, err }, 'failed to decrypt message') - } + } } - if(fullMessage.message) { + if (fullMessage.message) { Object.assign(fullMessage.message, msg) } else { fullMessage.message = msg } - } catch(err) { - logger.error( - { key: fullMessage.key, err }, - 'failed to decrypt message' - ) + } catch (err) { + logger.error({ key: fullMessage.key, err }, 'failed to decrypt message') fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT fullMessage.messageStubParameters = [err.message] } @@ -209,7 +221,7 @@ export const decryptMessageNode = ( } // if nothing was found to decrypt - if(!decryptables) { + if (!decryptables) { fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT] } diff --git a/src/Utils/event-buffer.ts b/src/Utils/event-buffer.ts index bddad0a..0aa6383 100644 --- a/src/Utils/event-buffer.ts +++ b/src/Utils/event-buffer.ts @@ -1,6 +1,16 @@ import EventEmitter from 'events' import { proto } from '../../WAProto' -import { BaileysEvent, BaileysEventEmitter, BaileysEventMap, BufferedEventData, Chat, ChatUpdate, Contact, WAMessage, WAMessageStatus } from '../Types' +import { + BaileysEvent, + BaileysEventEmitter, + BaileysEventMap, + BufferedEventData, + Chat, + ChatUpdate, + Contact, + WAMessage, + WAMessageStatus +} from '../Types' import { trimUndefined } from './generics' import { ILogger } from './logger' import { updateMessageWithReaction, updateMessageWithReceipt } from './messages' @@ -18,10 +28,10 @@ const BUFFERABLE_EVENT = [ 'messages.delete', 'messages.reaction', 'message-receipt.update', - 'groups.update', + 'groups.update' ] as const -type BufferableEvent = typeof BUFFERABLE_EVENT[number] +type BufferableEvent = (typeof BUFFERABLE_EVENT)[number] /** * A map that contains a list of all events that have been triggered @@ -36,14 +46,14 @@ const BUFFERABLE_EVENT_SET = new Set(BUFFERABLE_EVENT) type BaileysBufferableEventEmitter = BaileysEventEmitter & { /** Use to process events in a batch */ - process(handler: (events: BaileysEventData) => void | Promise): (() => void) + process(handler: (events: BaileysEventData) => void | Promise): () => void /** * starts buffering events, call flush() to release them * */ buffer(): void /** buffers all events till the promise completes */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - createBufferedFunction(work: (...args: A) => Promise): ((...args: A) => Promise) + createBufferedFunction(work: (...args: A) => Promise): (...args: A) => Promise /** * flushes all buffered events * @param force if true, will flush all data regardless of any pending buffers @@ -68,7 +78,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter // take the generic event and fire it as a baileys event ev.on('event', (map: BaileysEventData) => { - for(const event in map) { + for (const event in map) { ev.emit(event, map[event]) } }) @@ -79,16 +89,16 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter function flush(force = false) { // no buffer going on - if(!buffersInProgress) { + if (!buffersInProgress) { return false } - if(!force) { + if (!force) { // reduce the number of buffers in progress buffersInProgress -= 1 // if there are still some buffers going on // then we don't flush now - if(buffersInProgress) { + if (buffersInProgress) { return false } } @@ -97,8 +107,8 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter const chatUpdates = Object.values(data.chatUpdates) // gather the remaining conditional events so we re-queue them let conditionalChatUpdatesLeft = 0 - for(const update of chatUpdates) { - if(update.conditional) { + for (const update of chatUpdates) { + if (update.conditional) { conditionalChatUpdatesLeft += 1 newData.chatUpdates[update.id!] = update delete data.chatUpdates[update.id!] @@ -106,16 +116,13 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter } const consolidatedData = consolidateEvents(data) - if(Object.keys(consolidatedData).length) { + if (Object.keys(consolidatedData).length) { ev.emit('event', consolidatedData) } data = newData - logger.trace( - { conditionalChatUpdatesLeft }, - 'released buffered events' - ) + logger.trace({ conditionalChatUpdatesLeft }, 'released buffered events') return true } @@ -132,7 +139,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter } }, emit(event: BaileysEvent, evData: BaileysEventMap[T]) { - if(buffersInProgress && BUFFERABLE_EVENT_SET.has(event)) { + if (buffersInProgress && BUFFERABLE_EVENT_SET.has(event)) { append(data, historyCache, event as BufferableEvent, evData, logger) return true } @@ -145,7 +152,7 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter buffer, flush, createBufferedFunction(work) { - return async(...args) => { + return async (...args) => { buffer() try { const result = await work(...args) @@ -157,30 +164,30 @@ export const makeEventBuffer = (logger: ILogger): BaileysBufferableEventEmitter }, on: (...args) => ev.on(...args), off: (...args) => ev.off(...args), - removeAllListeners: (...args) => ev.removeAllListeners(...args), + removeAllListeners: (...args) => ev.removeAllListeners(...args) } } const makeBufferData = (): BufferedEventData => { return { historySets: { - chats: { }, - messages: { }, - contacts: { }, + chats: {}, + messages: {}, + contacts: {}, isLatest: false, empty: true }, - chatUpserts: { }, - chatUpdates: { }, + chatUpserts: {}, + chatUpdates: {}, chatDeletes: new Set(), - contactUpserts: { }, - contactUpdates: { }, - messageUpserts: { }, - messageUpdates: { }, - messageReactions: { }, - messageDeletes: { }, - messageReceipts: { }, - groupUpdates: { } + contactUpserts: {}, + contactUpdates: {}, + messageUpserts: {}, + messageUpdates: {}, + messageReactions: {}, + messageDeletes: {}, + messageReceipts: {}, + groupUpdates: {} } } @@ -193,305 +200,298 @@ function append( logger: ILogger ) { switch (event) { - case 'messaging-history.set': - for(const chat of eventData.chats as Chat[]) { - const existingChat = data.historySets.chats[chat.id] - if(existingChat) { - existingChat.endOfHistoryTransferType = chat.endOfHistoryTransferType - } - - if(!existingChat && !historyCache.has(chat.id)) { - data.historySets.chats[chat.id] = chat - historyCache.add(chat.id) - - absorbingChatUpdate(chat) - } - } - - for(const contact of eventData.contacts as Contact[]) { - const existingContact = data.historySets.contacts[contact.id] - if(existingContact) { - Object.assign(existingContact, trimUndefined(contact)) - } else { - const historyContactId = `c:${contact.id}` - const hasAnyName = contact.notify || contact.name || contact.verifiedName - if(!historyCache.has(historyContactId) || hasAnyName) { - data.historySets.contacts[contact.id] = contact - historyCache.add(historyContactId) + case 'messaging-history.set': + for (const chat of eventData.chats as Chat[]) { + const existingChat = data.historySets.chats[chat.id] + if (existingChat) { + existingChat.endOfHistoryTransferType = chat.endOfHistoryTransferType } - } - } - for(const message of eventData.messages as WAMessage[]) { - const key = stringifyMessageKey(message.key) - const existingMsg = data.historySets.messages[key] - if(!existingMsg && !historyCache.has(key)) { - data.historySets.messages[key] = message - historyCache.add(key) - } - } + if (!existingChat && !historyCache.has(chat.id)) { + data.historySets.chats[chat.id] = chat + historyCache.add(chat.id) - data.historySets.empty = false - data.historySets.syncType = eventData.syncType - data.historySets.progress = eventData.progress - data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId - data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest - - break - case 'chats.upsert': - for(const chat of eventData as Chat[]) { - let upsert = data.chatUpserts[chat.id] - if(!upsert) { - upsert = data.historySets[chat.id] - if(upsert) { - logger.debug({ chatId: chat.id }, 'absorbed chat upsert in chat set') + absorbingChatUpdate(chat) } } - if(upsert) { - upsert = concatChats(upsert, chat) - } else { - upsert = chat - data.chatUpserts[chat.id] = upsert - } - - absorbingChatUpdate(upsert) - - if(data.chatDeletes.has(chat.id)) { - data.chatDeletes.delete(chat.id) - } - } - - break - case 'chats.update': - for(const update of eventData as ChatUpdate[]) { - const chatId = update.id! - const conditionMatches = update.conditional ? update.conditional(data) : true - if(conditionMatches) { - delete update.conditional - - // if there is an existing upsert, merge the update into it - const upsert = data.historySets.chats[chatId] || data.chatUpserts[chatId] - if(upsert) { - concatChats(upsert, update) + for (const contact of eventData.contacts as Contact[]) { + const existingContact = data.historySets.contacts[contact.id] + if (existingContact) { + Object.assign(existingContact, trimUndefined(contact)) } else { - // merge the update into the existing update - const chatUpdate = data.chatUpdates[chatId] || { } - data.chatUpdates[chatId] = concatChats(chatUpdate, update) - } - } else if(conditionMatches === undefined) { - // condition yet to be fulfilled - data.chatUpdates[chatId] = update - } - // otherwise -- condition not met, update is invalid - - // if the chat has been updated - // ignore any existing chat delete - if(data.chatDeletes.has(chatId)) { - data.chatDeletes.delete(chatId) - } - } - - break - case 'chats.delete': - for(const chatId of eventData as string[]) { - if(!data.chatDeletes.has(chatId)) { - data.chatDeletes.add(chatId) - } - - // remove any prior updates & upserts - if(data.chatUpdates[chatId]) { - delete data.chatUpdates[chatId] - } - - if(data.chatUpserts[chatId]) { - delete data.chatUpserts[chatId] - - } - - if(data.historySets.chats[chatId]) { - delete data.historySets.chats[chatId] - } - } - - break - case 'contacts.upsert': - for(const contact of eventData as Contact[]) { - let upsert = data.contactUpserts[contact.id] - if(!upsert) { - upsert = data.historySets.contacts[contact.id] - if(upsert) { - logger.debug({ contactId: contact.id }, 'absorbed contact upsert in contact set') + const historyContactId = `c:${contact.id}` + const hasAnyName = contact.notify || contact.name || contact.verifiedName + if (!historyCache.has(historyContactId) || hasAnyName) { + data.historySets.contacts[contact.id] = contact + historyCache.add(historyContactId) + } } } - if(upsert) { - upsert = Object.assign(upsert, trimUndefined(contact)) - } else { - upsert = contact - data.contactUpserts[contact.id] = upsert - } - - if(data.contactUpdates[contact.id]) { - upsert = Object.assign(data.contactUpdates[contact.id], trimUndefined(contact)) as Contact - delete data.contactUpdates[contact.id] - } - } - - break - case 'contacts.update': - const contactUpdates = eventData as BaileysEventMap['contacts.update'] - for(const update of contactUpdates) { - const id = update.id! - // merge into prior upsert - const upsert = data.historySets.contacts[id] || data.contactUpserts[id] - if(upsert) { - Object.assign(upsert, update) - } else { - // merge into prior update - const contactUpdate = data.contactUpdates[id] || { } - data.contactUpdates[id] = Object.assign(contactUpdate, update) - } - } - - break - case 'messages.upsert': - const { messages, type } = eventData as BaileysEventMap['messages.upsert'] - for(const message of messages) { - const key = stringifyMessageKey(message.key) - let existing = data.messageUpserts[key]?.message - if(!existing) { - existing = data.historySets.messages[key] - if(existing) { - logger.debug({ messageId: key }, 'absorbed message upsert in message set') + for (const message of eventData.messages as WAMessage[]) { + const key = stringifyMessageKey(message.key) + const existingMsg = data.historySets.messages[key] + if (!existingMsg && !historyCache.has(key)) { + data.historySets.messages[key] = message + historyCache.add(key) } } - if(existing) { - message.messageTimestamp = existing.messageTimestamp - } + data.historySets.empty = false + data.historySets.syncType = eventData.syncType + data.historySets.progress = eventData.progress + data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId + data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest - if(data.messageUpdates[key]) { - logger.debug('absorbed prior message update in message upsert') - Object.assign(message, data.messageUpdates[key].update) - delete data.messageUpdates[key] - } + break + case 'chats.upsert': + for (const chat of eventData as Chat[]) { + let upsert = data.chatUpserts[chat.id] + if (!upsert) { + upsert = data.historySets[chat.id] + if (upsert) { + logger.debug({ chatId: chat.id }, 'absorbed chat upsert in chat set') + } + } - if(data.historySets.messages[key]) { - data.historySets.messages[key] = message - } else { - data.messageUpserts[key] = { - message, - type: type === 'notify' || data.messageUpserts[key]?.type === 'notify' - ? 'notify' - : type + if (upsert) { + upsert = concatChats(upsert, chat) + } else { + upsert = chat + data.chatUpserts[chat.id] = upsert + } + + absorbingChatUpdate(upsert) + + if (data.chatDeletes.has(chat.id)) { + data.chatDeletes.delete(chat.id) } } - } - break - case 'messages.update': - const msgUpdates = eventData as BaileysEventMap['messages.update'] - for(const { key, update } of msgUpdates) { - const keyStr = stringifyMessageKey(key) - const existing = data.historySets.messages[keyStr] || data.messageUpserts[keyStr]?.message - if(existing) { - Object.assign(existing, update) - // if the message was received & read by us - // the chat counter must have been incremented - // so we need to decrement it - if(update.status === WAMessageStatus.READ && !key.fromMe) { - decrementChatReadCounterIfMsgDidUnread(existing) + break + case 'chats.update': + for (const update of eventData as ChatUpdate[]) { + const chatId = update.id! + const conditionMatches = update.conditional ? update.conditional(data) : true + if (conditionMatches) { + delete update.conditional + + // if there is an existing upsert, merge the update into it + const upsert = data.historySets.chats[chatId] || data.chatUpserts[chatId] + if (upsert) { + concatChats(upsert, update) + } else { + // merge the update into the existing update + const chatUpdate = data.chatUpdates[chatId] || {} + data.chatUpdates[chatId] = concatChats(chatUpdate, update) + } + } else if (conditionMatches === undefined) { + // condition yet to be fulfilled + data.chatUpdates[chatId] = update } - } else { - const msgUpdate = data.messageUpdates[keyStr] || { key, update: { } } - Object.assign(msgUpdate.update, update) - data.messageUpdates[keyStr] = msgUpdate - } - } + // otherwise -- condition not met, update is invalid - break - case 'messages.delete': - const deleteData = eventData as BaileysEventMap['messages.delete'] - if('keys' in deleteData) { - const { keys } = deleteData - for(const key of keys) { + // if the chat has been updated + // ignore any existing chat delete + if (data.chatDeletes.has(chatId)) { + data.chatDeletes.delete(chatId) + } + } + + break + case 'chats.delete': + for (const chatId of eventData as string[]) { + if (!data.chatDeletes.has(chatId)) { + data.chatDeletes.add(chatId) + } + + // remove any prior updates & upserts + if (data.chatUpdates[chatId]) { + delete data.chatUpdates[chatId] + } + + if (data.chatUpserts[chatId]) { + delete data.chatUpserts[chatId] + } + + if (data.historySets.chats[chatId]) { + delete data.historySets.chats[chatId] + } + } + + break + case 'contacts.upsert': + for (const contact of eventData as Contact[]) { + let upsert = data.contactUpserts[contact.id] + if (!upsert) { + upsert = data.historySets.contacts[contact.id] + if (upsert) { + logger.debug({ contactId: contact.id }, 'absorbed contact upsert in contact set') + } + } + + if (upsert) { + upsert = Object.assign(upsert, trimUndefined(contact)) + } else { + upsert = contact + data.contactUpserts[contact.id] = upsert + } + + if (data.contactUpdates[contact.id]) { + upsert = Object.assign(data.contactUpdates[contact.id], trimUndefined(contact)) as Contact + delete data.contactUpdates[contact.id] + } + } + + break + case 'contacts.update': + const contactUpdates = eventData as BaileysEventMap['contacts.update'] + for (const update of contactUpdates) { + const id = update.id! + // merge into prior upsert + const upsert = data.historySets.contacts[id] || data.contactUpserts[id] + if (upsert) { + Object.assign(upsert, update) + } else { + // merge into prior update + const contactUpdate = data.contactUpdates[id] || {} + data.contactUpdates[id] = Object.assign(contactUpdate, update) + } + } + + break + case 'messages.upsert': + const { messages, type } = eventData as BaileysEventMap['messages.upsert'] + for (const message of messages) { + const key = stringifyMessageKey(message.key) + let existing = data.messageUpserts[key]?.message + if (!existing) { + existing = data.historySets.messages[key] + if (existing) { + logger.debug({ messageId: key }, 'absorbed message upsert in message set') + } + } + + if (existing) { + message.messageTimestamp = existing.messageTimestamp + } + + if (data.messageUpdates[key]) { + logger.debug('absorbed prior message update in message upsert') + Object.assign(message, data.messageUpdates[key].update) + delete data.messageUpdates[key] + } + + if (data.historySets.messages[key]) { + data.historySets.messages[key] = message + } else { + data.messageUpserts[key] = { + message, + type: type === 'notify' || data.messageUpserts[key]?.type === 'notify' ? 'notify' : type + } + } + } + + break + case 'messages.update': + const msgUpdates = eventData as BaileysEventMap['messages.update'] + for (const { key, update } of msgUpdates) { const keyStr = stringifyMessageKey(key) - if(!data.messageDeletes[keyStr]) { - data.messageDeletes[keyStr] = key - - } - - if(data.messageUpserts[keyStr]) { - delete data.messageUpserts[keyStr] - } - - if(data.messageUpdates[keyStr]) { - delete data.messageUpdates[keyStr] + const existing = data.historySets.messages[keyStr] || data.messageUpserts[keyStr]?.message + if (existing) { + Object.assign(existing, update) + // if the message was received & read by us + // the chat counter must have been incremented + // so we need to decrement it + if (update.status === WAMessageStatus.READ && !key.fromMe) { + decrementChatReadCounterIfMsgDidUnread(existing) + } + } else { + const msgUpdate = data.messageUpdates[keyStr] || { key, update: {} } + Object.assign(msgUpdate.update, update) + data.messageUpdates[keyStr] = msgUpdate } } - } else { - // TODO: add support - } - break - case 'messages.reaction': - const reactions = eventData as BaileysEventMap['messages.reaction'] - for(const { key, reaction } of reactions) { - const keyStr = stringifyMessageKey(key) - const existing = data.messageUpserts[keyStr] - if(existing) { - updateMessageWithReaction(existing.message, reaction) + break + case 'messages.delete': + const deleteData = eventData as BaileysEventMap['messages.delete'] + if ('keys' in deleteData) { + const { keys } = deleteData + for (const key of keys) { + const keyStr = stringifyMessageKey(key) + if (!data.messageDeletes[keyStr]) { + data.messageDeletes[keyStr] = key + } + + if (data.messageUpserts[keyStr]) { + delete data.messageUpserts[keyStr] + } + + if (data.messageUpdates[keyStr]) { + delete data.messageUpdates[keyStr] + } + } } else { - data.messageReactions[keyStr] = data.messageReactions[keyStr] - || { key, reactions: [] } - updateMessageWithReaction(data.messageReactions[keyStr], reaction) + // TODO: add support } - } - break - case 'message-receipt.update': - const receipts = eventData as BaileysEventMap['message-receipt.update'] - for(const { key, receipt } of receipts) { - const keyStr = stringifyMessageKey(key) - const existing = data.messageUpserts[keyStr] - if(existing) { - updateMessageWithReceipt(existing.message, receipt) - } else { - data.messageReceipts[keyStr] = data.messageReceipts[keyStr] - || { key, userReceipt: [] } - updateMessageWithReceipt(data.messageReceipts[keyStr], receipt) + break + case 'messages.reaction': + const reactions = eventData as BaileysEventMap['messages.reaction'] + for (const { key, reaction } of reactions) { + const keyStr = stringifyMessageKey(key) + const existing = data.messageUpserts[keyStr] + if (existing) { + updateMessageWithReaction(existing.message, reaction) + } else { + data.messageReactions[keyStr] = data.messageReactions[keyStr] || { key, reactions: [] } + updateMessageWithReaction(data.messageReactions[keyStr], reaction) + } } - } - - break - case 'groups.update': - const groupUpdates = eventData as BaileysEventMap['groups.update'] - for(const update of groupUpdates) { - const id = update.id! - const groupUpdate = data.groupUpdates[id] || { } - if(!data.groupUpdates[id]) { - data.groupUpdates[id] = Object.assign(groupUpdate, update) + break + case 'message-receipt.update': + const receipts = eventData as BaileysEventMap['message-receipt.update'] + for (const { key, receipt } of receipts) { + const keyStr = stringifyMessageKey(key) + const existing = data.messageUpserts[keyStr] + if (existing) { + updateMessageWithReceipt(existing.message, receipt) + } else { + data.messageReceipts[keyStr] = data.messageReceipts[keyStr] || { key, userReceipt: [] } + updateMessageWithReceipt(data.messageReceipts[keyStr], receipt) + } } - } - break - default: - throw new Error(`"${event}" cannot be buffered`) + break + case 'groups.update': + const groupUpdates = eventData as BaileysEventMap['groups.update'] + for (const update of groupUpdates) { + const id = update.id! + const groupUpdate = data.groupUpdates[id] || {} + if (!data.groupUpdates[id]) { + data.groupUpdates[id] = Object.assign(groupUpdate, update) + } + } + + break + default: + throw new Error(`"${event}" cannot be buffered`) } function absorbingChatUpdate(existing: Chat) { const chatId = existing.id const update = data.chatUpdates[chatId] - if(update) { + if (update) { const conditionMatches = update.conditional ? update.conditional(data) : true - if(conditionMatches) { + if (conditionMatches) { delete update.conditional logger.debug({ chatId }, 'absorbed chat update in existing chat') Object.assign(existing, concatChats(update as Chat, existing)) delete data.chatUpdates[chatId] - } else if(conditionMatches === false) { + } else if (conditionMatches === false) { logger.debug({ chatId }, 'chat update condition fail, removing') delete data.chatUpdates[chatId] } @@ -503,15 +503,15 @@ function append( // if the message has already been marked read by us const chatId = message.key.remoteJid! const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId] - if( - isRealMessage(message, '') - && shouldIncrementChatUnread(message) - && typeof chat?.unreadCount === 'number' - && chat.unreadCount > 0 + if ( + isRealMessage(message, '') && + shouldIncrementChatUnread(message) && + typeof chat?.unreadCount === 'number' && + chat.unreadCount > 0 ) { logger.debug({ chatId: chat.id }, 'decrementing chat counter') chat.unreadCount -= 1 - if(chat.unreadCount === 0) { + if (chat.unreadCount === 0) { delete chat.unreadCount } } @@ -519,9 +519,9 @@ function append( } function consolidateEvents(data: BufferedEventData) { - const map: BaileysEventData = { } + const map: BaileysEventData = {} - if(!data.historySets.empty) { + if (!data.historySets.empty) { map['messaging-history.set'] = { chats: Object.values(data.historySets.chats), messages: Object.values(data.historySets.messages), @@ -534,22 +534,22 @@ function consolidateEvents(data: BufferedEventData) { } const chatUpsertList = Object.values(data.chatUpserts) - if(chatUpsertList.length) { + if (chatUpsertList.length) { map['chats.upsert'] = chatUpsertList } const chatUpdateList = Object.values(data.chatUpdates) - if(chatUpdateList.length) { + if (chatUpdateList.length) { map['chats.update'] = chatUpdateList } const chatDeleteList = Array.from(data.chatDeletes) - if(chatDeleteList.length) { + if (chatDeleteList.length) { map['chats.delete'] = chatDeleteList } const messageUpsertList = Object.values(data.messageUpserts) - if(messageUpsertList.length) { + if (messageUpsertList.length) { const type = messageUpsertList[0].type map['messages.upsert'] = { messages: messageUpsertList.map(m => m.message), @@ -558,41 +558,41 @@ function consolidateEvents(data: BufferedEventData) { } const messageUpdateList = Object.values(data.messageUpdates) - if(messageUpdateList.length) { + if (messageUpdateList.length) { map['messages.update'] = messageUpdateList } const messageDeleteList = Object.values(data.messageDeletes) - if(messageDeleteList.length) { + if (messageDeleteList.length) { map['messages.delete'] = { keys: messageDeleteList } } - const messageReactionList = Object.values(data.messageReactions).flatMap( - ({ key, reactions }) => reactions.flatMap(reaction => ({ key, reaction })) + const messageReactionList = Object.values(data.messageReactions).flatMap(({ key, reactions }) => + reactions.flatMap(reaction => ({ key, reaction })) ) - if(messageReactionList.length) { + if (messageReactionList.length) { map['messages.reaction'] = messageReactionList } - const messageReceiptList = Object.values(data.messageReceipts).flatMap( - ({ key, userReceipt }) => userReceipt.flatMap(receipt => ({ key, receipt })) + const messageReceiptList = Object.values(data.messageReceipts).flatMap(({ key, userReceipt }) => + userReceipt.flatMap(receipt => ({ key, receipt })) ) - if(messageReceiptList.length) { + if (messageReceiptList.length) { map['message-receipt.update'] = messageReceiptList } const contactUpsertList = Object.values(data.contactUpserts) - if(contactUpsertList.length) { + if (contactUpsertList.length) { map['contacts.upsert'] = contactUpsertList } const contactUpdateList = Object.values(data.contactUpdates) - if(contactUpdateList.length) { + if (contactUpdateList.length) { map['contacts.update'] = contactUpdateList } const groupUpdateList = Object.values(data.groupUpdates) - if(groupUpdateList.length) { + if (groupUpdateList.length) { map['groups.update'] = groupUpdateList } @@ -600,15 +600,17 @@ function consolidateEvents(data: BufferedEventData) { } function concatChats>(a: C, b: Partial) { - if(b.unreadCount === null && // neutralize unread counter - a.unreadCount! < 0) { + if ( + b.unreadCount === null && // neutralize unread counter + a.unreadCount! < 0 + ) { a.unreadCount = undefined b.unreadCount = undefined } - if(typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number') { + if (typeof a.unreadCount === 'number' && typeof b.unreadCount === 'number') { b = { ...b } - if(b.unreadCount! >= 0) { + if (b.unreadCount! >= 0) { b.unreadCount = Math.max(b.unreadCount!, 0) + Math.max(a.unreadCount, 0) } } @@ -616,4 +618,4 @@ function concatChats>(a: C, b: Partial) { return Object.assign(a, b) } -const stringifyMessageKey = (key: proto.IMessageKey) => `${key.remoteJid},${key.id},${key.fromMe ? '1' : '0'}` \ No newline at end of file +const stringifyMessageKey = (key: proto.IMessageKey) => `${key.remoteJid},${key.id},${key.fromMe ? '1' : '0'}` diff --git a/src/Utils/generics.ts b/src/Utils/generics.ts index cd00973..b57cc39 100644 --- a/src/Utils/generics.ts +++ b/src/Utils/generics.ts @@ -4,26 +4,34 @@ import { createHash, randomBytes } from 'crypto' import { platform, release } from 'os' import { proto } from '../../WAProto' import { version as baileysVersion } from '../Defaults/baileys-version.json' -import { BaileysEventEmitter, BaileysEventMap, BrowsersMap, ConnectionState, DisconnectReason, WACallUpdateType, WAVersion } from '../Types' +import { + BaileysEventEmitter, + BaileysEventMap, + BrowsersMap, + ConnectionState, + DisconnectReason, + WACallUpdateType, + WAVersion +} from '../Types' import { BinaryNode, getAllBinaryNodeChildren, jidDecode } from '../WABinary' const PLATFORM_MAP = { - 'aix': 'AIX', - 'darwin': 'Mac OS', - 'win32': 'Windows', - 'android': 'Android', - 'freebsd': 'FreeBSD', - 'openbsd': 'OpenBSD', - 'sunos': 'Solaris' + aix: 'AIX', + darwin: 'Mac OS', + win32: 'Windows', + android: 'Android', + freebsd: 'FreeBSD', + openbsd: 'OpenBSD', + sunos: 'Solaris' } export const Browsers: BrowsersMap = { - ubuntu: (browser) => ['Ubuntu', browser, '22.04.4'], - macOS: (browser) => ['Mac OS', browser, '14.4.1'], - baileys: (browser) => ['Baileys', browser, '6.5.0'], - windows: (browser) => ['Windows', browser, '10.0.22631'], + ubuntu: browser => ['Ubuntu', browser, '22.04.4'], + macOS: browser => ['Mac OS', browser, '14.4.1'], + baileys: browser => ['Baileys', browser, '6.5.0'], + windows: browser => ['Windows', browser, '10.0.22631'], /** The appropriate browser based on your OS & release */ - appropriate: (browser) => [ PLATFORM_MAP[platform()] || 'Ubuntu', browser, release() ] + appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()] } export const getPlatformId = (browser: string) => { @@ -34,7 +42,7 @@ export const getPlatformId = (browser: string) => { export const BufferJSON = { // eslint-disable-next-line @typescript-eslint/no-explicit-any replacer: (k, value: any) => { - if(Buffer.isBuffer(value) || value instanceof Uint8Array || value?.type === 'Buffer') { + if (Buffer.isBuffer(value) || value instanceof Uint8Array || value?.type === 'Buffer') { return { type: 'Buffer', data: Buffer.from(value?.data || value).toString('base64') } } @@ -43,7 +51,7 @@ export const BufferJSON = { // eslint-disable-next-line @typescript-eslint/no-explicit-any reviver: (_, value: any) => { - if(typeof value === 'object' && !!value && (value.buffer === true || value.type === 'Buffer')) { + if (typeof value === 'object' && !!value && (value.buffer === true || value.type === 'Buffer')) { const val = value.data || value.value return typeof val === 'string' ? Buffer.from(val, 'base64') : Buffer.from(val || []) } @@ -52,17 +60,13 @@ export const BufferJSON = { } } -export const getKeyAuthor = ( - key: proto.IMessageKey | undefined | null, - meId = 'me' -) => ( +export const getKeyAuthor = (key: proto.IMessageKey | undefined | null, meId = 'me') => (key?.fromMe ? meId : key?.participant || key?.remoteJid) || '' -) export const writeRandomPadMax16 = (msg: Uint8Array) => { const pad = randomBytes(1) pad[0] &= 0xf - if(!pad[0]) { + if (!pad[0]) { pad[0] = 0xf } @@ -71,23 +75,19 @@ export const writeRandomPadMax16 = (msg: Uint8Array) => { export const unpadRandomMax16 = (e: Uint8Array | Buffer) => { const t = new Uint8Array(e) - if(0 === t.length) { + if (0 === t.length) { throw new Error('unpadPkcs7 given empty bytes') } var r = t[t.length - 1] - if(r > t.length) { + if (r > t.length) { throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`) } return new Uint8Array(t.buffer, t.byteOffset, t.length - r) } -export const encodeWAMessage = (message: proto.IMessage) => ( - writeRandomPadMax16( - proto.Message.encode(message).finish() - ) -) +export const encodeWAMessage = (message: proto.IMessage) => writeRandomPadMax16(proto.Message.encode(message).finish()) export const generateRegistrationId = (): number => { return Uint16Array.from(randomBytes(2))[0] & 16383 @@ -96,7 +96,7 @@ export const generateRegistrationId = (): number => { export const encodeBigEndian = (e: number, t = 4) => { let r = e const a = new Uint8Array(t) - for(let i = t - 1; i >= 0; i--) { + for (let i = t - 1; i >= 0; i--) { a[i] = 255 & r r >>>= 8 } @@ -104,7 +104,8 @@ export const encodeBigEndian = (e: number, t = 4) => { return a } -export const toNumber = (t: Long | number | null | undefined): number => ((typeof t === 'object' && t) ? ('toNumber' in t ? t.toNumber() : (t as Long).low) : t || 0) +export const toNumber = (t: Long | number | null | undefined): number => + typeof t === 'object' && t ? ('toNumber' in t ? t.toNumber() : (t as Long).low) : t || 0 /** unix timestamp of a date in seconds */ export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime() / 1000) @@ -124,12 +125,12 @@ export const debouncedTimeout = (intervalMs = 1000, task?: () => void) => { timeout && clearTimeout(timeout) timeout = undefined }, - setTask: (newTask: () => void) => task = newTask, - setInterval: (newInterval: number) => intervalMs = newInterval + setTask: (newTask: () => void) => (task = newTask), + setInterval: (newInterval: number) => (intervalMs = newInterval) } } -export const delay = (ms: number) => delayCancellable (ms).delay +export const delay = (ms: number) => delayCancellable(ms).delay export const delayCancellable = (ms: number) => { const stack = new Error().stack @@ -140,7 +141,7 @@ export const delayCancellable = (ms: number) => { reject = _reject }) const cancel = () => { - clearTimeout (timeout) + clearTimeout(timeout) reject( new Boom('Cancelled', { statusCode: 500, @@ -154,29 +155,33 @@ export const delayCancellable = (ms: number) => { return { delay, cancel } } -export async function promiseTimeout(ms: number | undefined, promise: (resolve: (v: T) => void, reject: (error) => void) => void) { - if(!ms) { +export async function promiseTimeout( + ms: number | undefined, + promise: (resolve: (v: T) => void, reject: (error) => void) => void +) { + if (!ms) { return new Promise(promise) } const stack = new Error().stack // Create a promise that rejects in milliseconds - const { delay, cancel } = delayCancellable (ms) + const { delay, cancel } = delayCancellable(ms) const p = new Promise((resolve, reject) => { delay - .then(() => reject( - new Boom('Timed Out', { - statusCode: DisconnectReason.timedOut, - data: { - stack - } - }) - )) - .catch (err => reject(err)) + .then(() => + reject( + new Boom('Timed Out', { + statusCode: DisconnectReason.timedOut, + data: { + stack + } + }) + ) + ) + .catch(err => reject(err)) - promise (resolve, reject) - }) - .finally (cancel) + promise(resolve, reject) + }).finally(cancel) return p as Promise } @@ -186,9 +191,9 @@ export const generateMessageIDV2 = (userId?: string): string => { const data = Buffer.alloc(8 + 20 + 16) data.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000))) - if(userId) { + if (userId) { const id = jidDecode(userId) - if(id?.user) { + if (id?.user) { data.write(id.user, 8) data.write('@c.us', 8 + id.user.length) } @@ -205,37 +210,30 @@ export const generateMessageIDV2 = (userId?: string): string => { export const generateMessageID = () => '3EB0' + randomBytes(18).toString('hex').toUpperCase() export function bindWaitForEvent(ev: BaileysEventEmitter, event: T) { - return async(check: (u: BaileysEventMap[T]) => Promise, timeoutMs?: number) => { + return async (check: (u: BaileysEventMap[T]) => Promise, timeoutMs?: number) => { let listener: (item: BaileysEventMap[T]) => void let closeListener: (state: Partial) => void - await ( - promiseTimeout( - timeoutMs, - (resolve, reject) => { - closeListener = ({ connection, lastDisconnect }) => { - if(connection === 'close') { - reject( - lastDisconnect?.error - || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }) - ) - } - } - - ev.on('connection.update', closeListener) - listener = async(update) => { - if(await check(update)) { - resolve() - } - } - - ev.on(event, listener) + await promiseTimeout(timeoutMs, (resolve, reject) => { + closeListener = ({ connection, lastDisconnect }) => { + if (connection === 'close') { + reject( + lastDisconnect?.error || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }) + ) } - ) - .finally(() => { - ev.off(event, listener) - ev.off('connection.update', closeListener) - }) - ) + } + + ev.on('connection.update', closeListener) + listener = async update => { + if (await check(update)) { + resolve() + } + } + + ev.on(event, listener) + }).finally(() => { + ev.off(event, listener) + ev.off('connection.update', closeListener) + }) } } @@ -245,21 +243,18 @@ export const bindWaitForConnectionUpdate = (ev: BaileysEventEmitter) => bindWait * utility that fetches latest baileys version from the master branch. * Use to ensure your WA connection is always on the latest version */ -export const fetchLatestBaileysVersion = async(options: AxiosRequestConfig<{}> = { }) => { +export const fetchLatestBaileysVersion = async (options: AxiosRequestConfig<{}> = {}) => { const URL = 'https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/baileys-version.json' try { - const result = await axios.get<{ version: WAVersion }>( - URL, - { - ...options, - responseType: 'json' - } - ) + const result = await axios.get<{ version: WAVersion }>(URL, { + ...options, + responseType: 'json' + }) return { version: result.data.version, isLatest: true } - } catch(error) { + } catch (error) { return { version: baileysVersion as WAVersion, isLatest: false, @@ -272,20 +267,17 @@ export const fetchLatestBaileysVersion = async(options: AxiosRequestConfig<{}> = * A utility that fetches the latest web version of whatsapp. * Use to ensure your WA connection is always on the latest version */ -export const fetchLatestWaWebVersion = async(options: AxiosRequestConfig<{}>) => { +export const fetchLatestWaWebVersion = async (options: AxiosRequestConfig<{}>) => { try { - const { data } = await axios.get( - 'https://web.whatsapp.com/sw.js', - { - ...options, - responseType: 'json' - } - ) + const { data } = await axios.get('https://web.whatsapp.com/sw.js', { + ...options, + responseType: 'json' + }) const regex = /\\?"client_revision\\?":\s*(\d+)/ const match = data.match(regex) - if(!match?.[1]) { + if (!match?.[1]) { return { version: baileysVersion as WAVersion, isLatest: false, @@ -301,7 +293,7 @@ export const fetchLatestWaWebVersion = async(options: AxiosRequestConfig<{}>) => version: [2, 3000, +clientRevision] as WAVersion, isLatest: true } - } catch(error) { + } catch (error) { return { version: baileysVersion as WAVersion, isLatest: false, @@ -317,9 +309,9 @@ export const generateMdTagPrefix = () => { } const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = { - 'sender': proto.WebMessageInfo.Status.SERVER_ACK, - 'played': proto.WebMessageInfo.Status.PLAYED, - 'read': proto.WebMessageInfo.Status.READ, + sender: proto.WebMessageInfo.Status.SERVER_ACK, + played: proto.WebMessageInfo.Status.PLAYED, + read: proto.WebMessageInfo.Status.READ, 'read-self': proto.WebMessageInfo.Status.READ } /** @@ -328,7 +320,7 @@ const STATUS_MAP: { [_: string]: proto.WebMessageInfo.Status } = { */ export const getStatusFromReceiptType = (type: string | undefined) => { const status = STATUS_MAP[type!] - if(typeof type === 'undefined') { + if (typeof type === 'undefined') { return proto.WebMessageInfo.Status.DELIVERY_ACK } @@ -348,7 +340,7 @@ export const getErrorCodeFromStreamError = (node: BinaryNode) => { let reason = reasonNode?.tag || 'unknown' const statusCode = +(node.attrs.code || CODE_MAP[reason] || DisconnectReason.badSession) - if(statusCode === DisconnectReason.restartRequired) { + if (statusCode === DisconnectReason.restartRequired) { reason = 'restart required' } @@ -361,28 +353,28 @@ export const getErrorCodeFromStreamError = (node: BinaryNode) => { export const getCallStatusFromNode = ({ tag, attrs }: BinaryNode) => { let status: WACallUpdateType switch (tag) { - case 'offer': - case 'offer_notice': - status = 'offer' - break - case 'terminate': - if(attrs.reason === 'timeout') { - status = 'timeout' - } else { - //fired when accepted/rejected/timeout/caller hangs up - status = 'terminate' - } + case 'offer': + case 'offer_notice': + status = 'offer' + break + case 'terminate': + if (attrs.reason === 'timeout') { + status = 'timeout' + } else { + //fired when accepted/rejected/timeout/caller hangs up + status = 'terminate' + } - break - case 'reject': - status = 'reject' - break - case 'accept': - status = 'accept' - break - default: - status = 'ringing' - break + break + case 'reject': + status = 'reject' + break + case 'accept': + status = 'accept' + break + default: + status = 'ringing' + break } return status @@ -392,16 +384,17 @@ const UNEXPECTED_SERVER_CODE_TEXT = 'Unexpected server response: ' export const getCodeFromWSError = (error: Error) => { let statusCode = 500 - if(error?.message?.includes(UNEXPECTED_SERVER_CODE_TEXT)) { + if (error?.message?.includes(UNEXPECTED_SERVER_CODE_TEXT)) { const code = +error?.message.slice(UNEXPECTED_SERVER_CODE_TEXT.length) - if(!Number.isNaN(code) && code >= 400) { + if (!Number.isNaN(code) && code >= 400) { statusCode = code } - } else if( + } else if ( // eslint-disable-next-line @typescript-eslint/no-explicit-any - (error as any)?.code?.startsWith('E') - || error?.message?.includes('timed out') - ) { // handle ETIMEOUT, ENOTFOUND etc + (error as any)?.code?.startsWith('E') || + error?.message?.includes('timed out') + ) { + // handle ETIMEOUT, ENOTFOUND etc statusCode = 408 } @@ -417,9 +410,9 @@ export const isWABusinessPlatform = (platform: string) => { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function trimUndefined(obj: {[_: string]: any}) { - for(const key in obj) { - if(typeof obj[key] === 'undefined') { +export function trimUndefined(obj: { [_: string]: any }) { + for (const key in obj) { + if (typeof obj[key] === 'undefined') { delete obj[key] } } @@ -434,17 +427,17 @@ export function bytesToCrockford(buffer: Buffer): string { let bitCount = 0 const crockford: string[] = [] - for(const element of buffer) { + for (const element of buffer) { value = (value << 8) | (element & 0xff) bitCount += 8 - while(bitCount >= 5) { + while (bitCount >= 5) { crockford.push(CROCKFORD_CHARACTERS.charAt((value >>> (bitCount - 5)) & 31)) bitCount -= 5 } } - if(bitCount > 0) { + if (bitCount > 0) { crockford.push(CROCKFORD_CHARACTERS.charAt((value << (5 - bitCount)) & 31)) } diff --git a/src/Utils/history.ts b/src/Utils/history.ts index 49a014b..c42cf6b 100644 --- a/src/Utils/history.ts +++ b/src/Utils/history.ts @@ -10,10 +10,7 @@ import { downloadContentFromMessage } from './messages-media' const inflatePromise = promisify(inflate) -export const downloadHistory = async( - msg: proto.Message.IHistorySyncNotification, - options: AxiosRequestConfig<{}> -) => { +export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: AxiosRequestConfig<{}>) => { const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options }) const bufferArray: Buffer[] = [] for await (const chunk of stream) { @@ -35,59 +32,58 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { const chats: Chat[] = [] switch (item.syncType) { - case proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP: - case proto.HistorySync.HistorySyncType.RECENT: - case proto.HistorySync.HistorySyncType.FULL: - case proto.HistorySync.HistorySyncType.ON_DEMAND: - for(const chat of item.conversations! as Chat[]) { - contacts.push({ id: chat.id, name: chat.name || undefined }) + case proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP: + case proto.HistorySync.HistorySyncType.RECENT: + case proto.HistorySync.HistorySyncType.FULL: + case proto.HistorySync.HistorySyncType.ON_DEMAND: + for (const chat of item.conversations! as Chat[]) { + contacts.push({ id: chat.id, name: chat.name || undefined }) - const msgs = chat.messages || [] - delete chat.messages - delete chat.archived - delete chat.muteEndTime - delete chat.pinned + const msgs = chat.messages || [] + delete chat.messages + delete chat.archived + delete chat.muteEndTime + delete chat.pinned - for(const item of msgs) { - const message = item.message! - messages.push(message) + for (const item of msgs) { + const message = item.message! + messages.push(message) - if(!chat.messages?.length) { - // keep only the most recent message in the chat array - chat.messages = [{ message }] + if (!chat.messages?.length) { + // keep only the most recent message in the chat array + chat.messages = [{ message }] + } + + if (!message.key.fromMe && !chat.lastMessageRecvTimestamp) { + chat.lastMessageRecvTimestamp = toNumber(message.messageTimestamp) + } + + if ( + (message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_BSP || + message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_FB) && + message.messageStubParameters?.[0] + ) { + contacts.push({ + id: message.key.participant || message.key.remoteJid!, + verifiedName: message.messageStubParameters?.[0] + }) + } } - if(!message.key.fromMe && !chat.lastMessageRecvTimestamp) { - chat.lastMessageRecvTimestamp = toNumber(message.messageTimestamp) + if (isJidUser(chat.id) && chat.readOnly && chat.archived) { + delete chat.readOnly } - if( - (message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_BSP - || message.messageStubType === WAMessageStubType.BIZ_PRIVACY_MODE_TO_FB - ) - && message.messageStubParameters?.[0] - ) { - contacts.push({ - id: message.key.participant || message.key.remoteJid!, - verifiedName: message.messageStubParameters?.[0], - }) - } + chats.push({ ...chat }) } - if(isJidUser(chat.id) && chat.readOnly && chat.archived) { - delete chat.readOnly + break + case proto.HistorySync.HistorySyncType.PUSH_NAME: + for (const c of item.pushnames!) { + contacts.push({ id: c.id!, notify: c.pushname! }) } - chats.push({ ...chat }) - } - - break - case proto.HistorySync.HistorySyncType.PUSH_NAME: - for(const c of item.pushnames!) { - contacts.push({ id: c.id!, notify: c.pushname! }) - } - - break + break } return { @@ -99,7 +95,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => { } } -export const downloadAndProcessHistorySyncNotification = async( +export const downloadAndProcessHistorySyncNotification = async ( msg: proto.Message.IHistorySyncNotification, options: AxiosRequestConfig<{}> ) => { @@ -112,4 +108,4 @@ export const getHistoryMsg = (message: proto.IMessage) => { const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification return anyHistoryMsg -} \ No newline at end of file +} diff --git a/src/Utils/link-preview.ts b/src/Utils/link-preview.ts index 2605130..78454c4 100644 --- a/src/Utils/link-preview.ts +++ b/src/Utils/link-preview.ts @@ -7,10 +7,7 @@ import { extractImageThumb, getHttpStream } from './messages-media' const THUMBNAIL_WIDTH_PX = 192 /** Fetches an image and generates a thumbnail for it */ -const getCompressedJpegThumbnail = async( - url: string, - { thumbnailWidth, fetchOpts }: URLGenerationOptions -) => { +const getCompressedJpegThumbnail = async (url: string, { thumbnailWidth, fetchOpts }: URLGenerationOptions) => { const stream = await getHttpStream(url, fetchOpts) const result = await extractImageThumb(stream, thumbnailWidth) return result @@ -34,12 +31,12 @@ export type URLGenerationOptions = { * @param text first matched URL in text * @returns the URL info required to generate link preview */ -export const getUrlInfo = async( +export const getUrlInfo = async ( text: string, opts: URLGenerationOptions = { thumbnailWidth: THUMBNAIL_WIDTH_PX, fetchOpts: { timeout: 3000 } - }, + } ): Promise => { try { // retries @@ -48,7 +45,7 @@ export const getUrlInfo = async( const { getLinkPreview } = await import('link-preview-js') let previewLink = text - if(!text.startsWith('https://') && !text.startsWith('http://')) { + if (!text.startsWith('https://') && !text.startsWith('http://')) { previewLink = 'https://' + previewLink } @@ -58,14 +55,14 @@ export const getUrlInfo = async( handleRedirects: (baseURL: string, forwardedURL: string) => { const urlObj = new URL(baseURL) const forwardedURLObj = new URL(forwardedURL) - if(retries >= maxRetry) { + if (retries >= maxRetry) { return false } - if( - forwardedURLObj.hostname === urlObj.hostname - || forwardedURLObj.hostname === 'www.' + urlObj.hostname - || 'www.' + forwardedURLObj.hostname === urlObj.hostname + if ( + forwardedURLObj.hostname === urlObj.hostname || + forwardedURLObj.hostname === 'www.' + urlObj.hostname || + 'www.' + forwardedURLObj.hostname === urlObj.hostname ) { retries + 1 return true @@ -75,7 +72,7 @@ export const getUrlInfo = async( }, headers: opts.fetchOpts as {} }) - if(info && 'title' in info && info.title) { + if (info && 'title' in info && info.title) { const [image] = info.images const urlInfo: WAUrlInfo = { @@ -86,7 +83,7 @@ export const getUrlInfo = async( originalThumbnailUrl: image } - if(opts.uploadImage) { + if (opts.uploadImage) { const { imageMessage } = await prepareWAMessageMedia( { image: { url: image } }, { @@ -95,28 +92,21 @@ export const getUrlInfo = async( options: opts.fetchOpts } ) - urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail - ? Buffer.from(imageMessage.jpegThumbnail) - : undefined + urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined urlInfo.highQualityThumbnail = imageMessage || undefined } else { try { - urlInfo.jpegThumbnail = image - ? (await getCompressedJpegThumbnail(image, opts)).buffer - : undefined - } catch(error) { - opts.logger?.debug( - { err: error.stack, url: previewLink }, - 'error in generating thumbnail' - ) + urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined + } catch (error) { + opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail') } } return urlInfo } - } catch(error) { - if(!error.message.includes('receive a valid')) { + } catch (error) { + if (!error.message.includes('receive a valid')) { throw error } } -} \ No newline at end of file +} diff --git a/src/Utils/logger.ts b/src/Utils/logger.ts index a527422..147c94b 100644 --- a/src/Utils/logger.ts +++ b/src/Utils/logger.ts @@ -1,13 +1,13 @@ import P from 'pino' export interface ILogger { - level: string - child(obj: Record): ILogger - trace(obj: unknown, msg?: string) - debug(obj: unknown, msg?: string) - info(obj: unknown, msg?: string) - warn(obj: unknown, msg?: string) - error(obj: unknown, msg?: string) + level: string + child(obj: Record): ILogger + trace(obj: unknown, msg?: string) + debug(obj: unknown, msg?: string) + info(obj: unknown, msg?: string) + warn(obj: unknown, msg?: string) + error(obj: unknown, msg?: string) } export default P({ timestamp: () => `,"time":"${new Date().toJSON()}"` }) diff --git a/src/Utils/lt-hash.ts b/src/Utils/lt-hash.ts index e3cc666..249de4d 100644 --- a/src/Utils/lt-hash.ts +++ b/src/Utils/lt-hash.ts @@ -9,7 +9,6 @@ import { hkdf } from './crypto' const o = 128 class d { - salt: string constructor(e: string) { @@ -17,7 +16,7 @@ class d { } add(e, t) { var r = this - for(const item of t) { + for (const item of t) { e = r._addSingle(e, item) } @@ -25,7 +24,7 @@ class d { } subtract(e, t) { var r = this - for(const item of t) { + for (const item of t) { e = r._subtractSingle(e, item) } @@ -38,20 +37,20 @@ class d { async _addSingle(e, t) { var r = this const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer - return r.performPointwiseWithOverflow(await e, n, ((e, t) => e + t)) + return r.performPointwiseWithOverflow(await e, n, (e, t) => e + t) } async _subtractSingle(e, t) { var r = this const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer - return r.performPointwiseWithOverflow(await e, n, ((e, t) => e - t)) + return r.performPointwiseWithOverflow(await e, n, (e, t) => e - t) } performPointwiseWithOverflow(e, t, r) { - const n = new DataView(e) - , i = new DataView(t) - , a = new ArrayBuffer(n.byteLength) - , s = new DataView(a) - for(let e = 0; e < n.byteLength; e += 2) { + const n = new DataView(e), + i = new DataView(t), + a = new ArrayBuffer(n.byteLength), + s = new DataView(a) + for (let e = 0; e < n.byteLength; e += 2) { s.setUint16(e, r(n.getUint16(e, !0), i.getUint16(e, !0)), !0) } diff --git a/src/Utils/make-mutex.ts b/src/Utils/make-mutex.ts index 1d6a617..c760156 100644 --- a/src/Utils/make-mutex.ts +++ b/src/Utils/make-mutex.ts @@ -6,12 +6,12 @@ export const makeMutex = () => { return { mutex(code: () => Promise | T): Promise { - task = (async() => { + task = (async () => { // wait for the previous task to complete // if there is an error, we swallow so as to not block the queue try { await task - } catch{ } + } catch {} try { // execute the current task @@ -24,7 +24,7 @@ export const makeMutex = () => { // we replace the existing task, appending the new piece of execution to it // so the next task will have to wait for this one to finish return task - }, + } } } @@ -35,11 +35,11 @@ export const makeKeyedMutex = () => { return { mutex(key: string, task: () => Promise | T): Promise { - if(!map[key]) { + if (!map[key]) { map[key] = makeMutex() } return map[key].mutex(task) } } -} \ No newline at end of file +} diff --git a/src/Utils/messages-media.ts b/src/Utils/messages-media.ts index 0816131..ddd5d3a 100644 --- a/src/Utils/messages-media.ts +++ b/src/Utils/messages-media.ts @@ -11,7 +11,20 @@ import { Readable, Transform } from 'stream' import { URL } from 'url' import { proto } from '../../WAProto' import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults' -import { BaileysEventMap, DownloadableMessage, MediaConnInfo, MediaDecryptionKeyInfo, MediaType, MessageType, SocketConfig, WAGenericMediaMessage, WAMediaPayloadURL, WAMediaUpload, WAMediaUploadFunction, WAMessageContent } from '../Types' +import { + BaileysEventMap, + DownloadableMessage, + MediaConnInfo, + MediaDecryptionKeyInfo, + MediaType, + MessageType, + SocketConfig, + WAGenericMediaMessage, + WAMediaPayloadURL, + WAMediaUpload, + WAMediaUploadFunction, + WAMessageContent +} from '../Types' import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary' import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto' import { generateMessageIDV2 } from './generics' @@ -19,30 +32,24 @@ import { ILogger } from './logger' const getTmpFilesDirectory = () => tmpdir() -const getImageProcessingLibrary = async() => { +const getImageProcessingLibrary = async () => { const [_jimp, sharp] = await Promise.all([ - (async() => { - const jimp = await ( - import('jimp') - .catch(() => { }) - ) + (async () => { + const jimp = await import('jimp').catch(() => {}) return jimp })(), - (async() => { - const sharp = await ( - import('sharp') - .catch(() => { }) - ) + (async () => { + const sharp = await import('sharp').catch(() => {}) return sharp })() ]) - if(sharp) { + if (sharp) { return { sharp } } const jimp = _jimp?.default || _jimp - if(jimp) { + if (jimp) { return { jimp } } @@ -55,12 +62,15 @@ export const hkdfInfoKey = (type: MediaType) => { } /** generates all the keys required to encrypt/decrypt & sign a media message */ -export async function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): Promise { - if(!buffer) { +export async function getMediaKeys( + buffer: Uint8Array | string | null | undefined, + mediaType: MediaType +): Promise { + if (!buffer) { throw new Boom('Cannot derive from empty media key') } - if(typeof buffer === 'string') { + if (typeof buffer === 'string') { buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64') } @@ -69,49 +79,47 @@ export async function getMediaKeys(buffer: Uint8Array | string | null | undefine return { iv: expandedMediaKey.slice(0, 16), cipherKey: expandedMediaKey.slice(16, 48), - macKey: expandedMediaKey.slice(48, 80), + macKey: expandedMediaKey.slice(48, 80) } } /** Extracts video thumb using FFMPEG */ -const extractVideoThumb = async( +const extractVideoThumb = async ( path: string, destPath: string, time: string, - size: { width: number, height: number }, -) => new Promise((resolve, reject) => { - const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}` - exec(cmd, (err) => { - if(err) { - reject(err) - } else { - resolve() - } - }) -}) + size: { width: number; height: number } +) => + new Promise((resolve, reject) => { + const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}` + exec(cmd, err => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) -export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | string, width = 32) => { - if(bufferOrFilePath instanceof Readable) { +export const extractImageThumb = async (bufferOrFilePath: Readable | Buffer | string, width = 32) => { + if (bufferOrFilePath instanceof Readable) { bufferOrFilePath = await toBuffer(bufferOrFilePath) } const lib = await getImageProcessingLibrary() - if('sharp' in lib && typeof lib.sharp?.default === 'function') { + if ('sharp' in lib && typeof lib.sharp?.default === 'function') { const img = lib.sharp.default(bufferOrFilePath) const dimensions = await img.metadata() - const buffer = await img - .resize(width) - .jpeg({ quality: 50 }) - .toBuffer() + const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer() return { buffer, original: { width: dimensions.width, - height: dimensions.height, - }, + height: dimensions.height + } } - } else if('jimp' in lib && typeof lib.jimp?.read === 'function') { + } else if ('jimp' in lib && typeof lib.jimp?.read === 'function') { const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp const jimp = await read(bufferOrFilePath as string) @@ -119,10 +127,7 @@ export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | str width: jimp.getWidth(), height: jimp.getHeight() } - const buffer = await jimp - .quality(50) - .resize(width, AUTO, RESIZE_BILINEAR) - .getBufferAsync(MIME_JPEG) + const buffer = await jimp.quality(50).resize(width, AUTO, RESIZE_BILINEAR).getBufferAsync(MIME_JPEG) return { buffer, original: dimensions @@ -132,20 +137,14 @@ export const extractImageThumb = async(bufferOrFilePath: Readable | Buffer | str } } -export const encodeBase64EncodedStringForUpload = (b64: string) => ( - encodeURIComponent( - b64 - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/\=+$/, '') - ) -) +export const encodeBase64EncodedStringForUpload = (b64: string) => + encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '')) -export const generateProfilePicture = async(mediaUpload: WAMediaUpload) => { +export const generateProfilePicture = async (mediaUpload: WAMediaUpload) => { let bufferOrFilePath: Buffer | string - if(Buffer.isBuffer(mediaUpload)) { + if (Buffer.isBuffer(mediaUpload)) { bufferOrFilePath = mediaUpload - } else if('url' in mediaUpload) { + } else if ('url' in mediaUpload) { bufferOrFilePath = mediaUpload.url.toString() } else { bufferOrFilePath = await toBuffer(mediaUpload.stream) @@ -153,44 +152,42 @@ export const generateProfilePicture = async(mediaUpload: WAMediaUpload) => { const lib = await getImageProcessingLibrary() let img: Promise - if('sharp' in lib && typeof lib.sharp?.default === 'function') { - img = lib.sharp.default(bufferOrFilePath) + if ('sharp' in lib && typeof lib.sharp?.default === 'function') { + img = lib.sharp + .default(bufferOrFilePath) .resize(640, 640) .jpeg({ - quality: 50, + quality: 50 }) .toBuffer() - } else if('jimp' in lib && typeof lib.jimp?.read === 'function') { + } else if ('jimp' in lib && typeof lib.jimp?.read === 'function') { const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp const jimp = await read(bufferOrFilePath as string) const min = Math.min(jimp.getWidth(), jimp.getHeight()) const cropped = jimp.crop(0, 0, min, min) - img = cropped - .quality(50) - .resize(640, 640, RESIZE_BILINEAR) - .getBufferAsync(MIME_JPEG) + img = cropped.quality(50).resize(640, 640, RESIZE_BILINEAR).getBufferAsync(MIME_JPEG) } else { throw new Boom('No image processing library available') } return { - img: await img, + img: await img } } /** gets the SHA256 of the given media message */ export const mediaMessageSHA256B64 = (message: WAMessageContent) => { const media = Object.values(message)[0] as WAGenericMediaMessage - return media?.fileSha256 && Buffer.from(media.fileSha256).toString ('base64') + return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64') } export async function getAudioDuration(buffer: Buffer | string | Readable) { const musicMetadata = await import('music-metadata') let metadata: IAudioMetadata - if(Buffer.isBuffer(buffer)) { + if (Buffer.isBuffer(buffer)) { metadata = await musicMetadata.parseBuffer(buffer, undefined, { duration: true }) - } else if(typeof buffer === 'string') { + } else if (typeof buffer === 'string') { const rStream = createReadStream(buffer) try { metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true }) @@ -209,11 +206,11 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) { */ export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: ILogger) { try { - const { default: decoder } = await eval('import(\'audio-decode\')') + const { default: decoder } = await eval("import('audio-decode')") let audioData: Buffer - if(Buffer.isBuffer(buffer)) { + if (Buffer.isBuffer(buffer)) { audioData = buffer - } else if(typeof buffer === 'string') { + } else if (typeof buffer === 'string') { const rStream = createReadStream(buffer) audioData = await toBuffer(rStream) } else { @@ -226,10 +223,10 @@ export async function getAudioWaveform(buffer: Buffer | string | Readable, logge const samples = 64 // Number of samples we want to have in our final data set const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision const filteredData: number[] = [] - for(let i = 0; i < samples; i++) { - const blockStart = blockSize * i // the location of the first sample in the block - let sum = 0 - for(let j = 0; j < blockSize; j++) { + for (let i = 0; i < samples; i++) { + const blockStart = blockSize * i // the location of the first sample in the block + let sum = 0 + for (let j = 0; j < blockSize; j++) { sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block } @@ -238,20 +235,17 @@ export async function getAudioWaveform(buffer: Buffer | string | Readable, logge // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally. const multiplier = Math.pow(Math.max(...filteredData), -1) - const normalizedData = filteredData.map((n) => n * multiplier) + const normalizedData = filteredData.map(n => n * multiplier) // Generate waveform like WhatsApp - const waveform = new Uint8Array( - normalizedData.map((n) => Math.floor(100 * n)) - ) + const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n))) return waveform - } catch(e) { + } catch (e) { logger?.debug('Failed to generate waveform: ' + e) } } - export const toReadable = (buffer: Buffer) => { const readable = new Readable({ read: () => {} }) readable.push(buffer) @@ -259,7 +253,7 @@ export const toReadable = (buffer: Buffer) => { return readable } -export const toBuffer = async(stream: Readable) => { +export const toBuffer = async (stream: Readable) => { const chunks: Buffer[] = [] for await (const chunk of stream) { chunks.push(chunk) @@ -269,16 +263,16 @@ export const toBuffer = async(stream: Readable) => { return Buffer.concat(chunks) } -export const getStream = async(item: WAMediaUpload, opts?: AxiosRequestConfig) => { - if(Buffer.isBuffer(item)) { +export const getStream = async (item: WAMediaUpload, opts?: AxiosRequestConfig) => { + if (Buffer.isBuffer(item)) { return { stream: toReadable(item), type: 'buffer' } as const } - if('stream' in item) { + if ('stream' in item) { return { stream: item.stream, type: 'readable' } as const } - if(item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) { + if (item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) { return { stream: await getHttpStream(item.url, opts), type: 'remote' } as const } @@ -290,21 +284,21 @@ export async function generateThumbnail( file: string, mediaType: 'video' | 'image', options: { - logger?: ILogger - } + logger?: ILogger + } ) { let thumbnail: string | undefined - let originalImageDimensions: { width: number, height: number } | undefined - if(mediaType === 'image') { + let originalImageDimensions: { width: number; height: number } | undefined + if (mediaType === 'image') { const { buffer, original } = await extractImageThumb(file) thumbnail = buffer.toString('base64') - if(original.width && original.height) { + if (original.width && original.height) { originalImageDimensions = { width: original.width, - height: original.height, + height: original.height } } - } else if(mediaType === 'video') { + } else if (mediaType === 'video') { const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + '.jpg') try { await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 }) @@ -312,7 +306,7 @@ export async function generateThumbnail( thumbnail = buff.toString('base64') await fs.unlink(imgFilename) - } catch(err) { + } catch (err) { options.logger?.debug('could not generate video thumb: ' + err) } } @@ -323,7 +317,7 @@ export async function generateThumbnail( } } -export const getHttpStream = async(url: string | URL, options: AxiosRequestConfig & { isStream?: true } = {}) => { +export const getHttpStream = async (url: string | URL, options: AxiosRequestConfig & { isStream?: true } = {}) => { const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' }) return fetched.data as Readable } @@ -334,7 +328,7 @@ type EncryptedStreamOptions = { opts?: AxiosRequestConfig } -export const encryptedStream = async( +export const encryptedStream = async ( media: WAMediaUpload, mediaType: MediaType, { logger, saveOriginalFileIfRequired, opts }: EncryptedStreamOptions = {} @@ -350,9 +344,9 @@ export const encryptedStream = async( let bodyPath: string | undefined let writeStream: WriteStream | undefined let didSaveToTmpPath = false - if(type === 'file') { + if (type === 'file') { bodyPath = (media as WAMediaPayloadURL).url.toString() - } else if(saveOriginalFileIfRequired) { + } else if (saveOriginalFileIfRequired) { bodyPath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2()) writeStream = createWriteStream(bodyPath) didSaveToTmpPath = true @@ -368,21 +362,14 @@ export const encryptedStream = async( for await (const data of stream) { fileLength += data.length - if( - type === 'remote' - && opts?.maxContentLength - && fileLength + data.length > opts.maxContentLength - ) { - throw new Boom( - `content length exceeded when encrypting "${type}"`, - { - data: { media, type } - } - ) + if (type === 'remote' && opts?.maxContentLength && fileLength + data.length > opts.maxContentLength) { + throw new Boom(`content length exceeded when encrypting "${type}"`, { + data: { media, type } + }) } sha256Plain = sha256Plain.update(data) - if(writeStream && !writeStream.write(data)) { + if (writeStream && !writeStream.write(data)) { await once(writeStream, 'drain') } @@ -415,7 +402,7 @@ export const encryptedStream = async( fileLength, didSaveToTmpPath } - } catch(error) { + } catch (error) { // destroy all streams with error encWriteStream.destroy() writeStream?.destroy() @@ -425,10 +412,10 @@ export const encryptedStream = async( sha256Enc.destroy() stream.destroy() - if(didSaveToTmpPath) { + if (didSaveToTmpPath) { try { await fs.unlink(bodyPath!) - } catch(err) { + } catch (err) { logger?.error({ err }, 'failed to save to tmp path') } } @@ -451,17 +438,17 @@ const toSmallestChunkSize = (num: number) => { } export type MediaDownloadOptions = { - startByte?: number - endByte?: number + startByte?: number + endByte?: number options?: AxiosRequestConfig<{}> } export const getUrlFromDirectPath = (directPath: string) => `https://${DEF_HOST}${directPath}` -export const downloadContentFromMessage = async( +export const downloadContentFromMessage = async ( { mediaKey, directPath, url }: DownloadableMessage, type: MediaType, - opts: MediaDownloadOptions = { } + opts: MediaDownloadOptions = {} ) => { const downloadUrl = url || getUrlFromDirectPath(directPath!) const keys = await getMediaKeys(mediaKey, type) @@ -473,18 +460,18 @@ export const downloadContentFromMessage = async( * Decrypts and downloads an AES256-CBC encrypted file given the keys. * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext * */ -export const downloadEncryptedContent = async( +export const downloadEncryptedContent = async ( downloadUrl: string, { cipherKey, iv }: MediaDecryptionKeyInfo, - { startByte, endByte, options }: MediaDownloadOptions = { } + { startByte, endByte, options }: MediaDownloadOptions = {} ) => { let bytesFetched = 0 let startChunk = 0 let firstBlockIsIV = false // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV - if(startByte) { + if (startByte) { const chunk = toSmallestChunkSize(startByte || 0) - if(chunk) { + if (chunk) { startChunk = chunk - AES_CHUNK_SIZE bytesFetched = chunk @@ -495,33 +482,30 @@ export const downloadEncryptedContent = async( const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined const headers: AxiosRequestConfig['headers'] = { - ...options?.headers || { }, - Origin: DEFAULT_ORIGIN, + ...(options?.headers || {}), + Origin: DEFAULT_ORIGIN } - if(startChunk || endChunk) { + if (startChunk || endChunk) { headers.Range = `bytes=${startChunk}-` - if(endChunk) { + if (endChunk) { headers.Range += endChunk } } // download the message - const fetched = await getHttpStream( - downloadUrl, - { - ...options || { }, - headers, - maxBodyLength: Infinity, - maxContentLength: Infinity, - } - ) + const fetched = await getHttpStream(downloadUrl, { + ...(options || {}), + headers, + maxBodyLength: Infinity, + maxContentLength: Infinity + }) let remainingBytes = Buffer.from([]) let aes: Crypto.Decipher const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => { - if(startByte || endByte) { + if (startByte || endByte) { const start = bytesFetched >= startByte! ? undefined : Math.max(startByte! - bytesFetched, 0) const end = bytesFetched + bytes.length < endByte! ? undefined : Math.max(endByte! - bytesFetched, 0) @@ -541,9 +525,9 @@ export const downloadEncryptedContent = async( remainingBytes = data.slice(decryptLength) data = data.slice(0, decryptLength) - if(!aes) { + if (!aes) { let ivValue = iv - if(firstBlockIsIV) { + if (firstBlockIsIV) { ivValue = data.slice(0, AES_CHUNK_SIZE) data = data.slice(AES_CHUNK_SIZE) } @@ -551,16 +535,15 @@ export const downloadEncryptedContent = async( aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue) // if an end byte that is not EOF is specified // stop auto padding (PKCS7) -- otherwise throws an error for decryption - if(endByte) { + if (endByte) { aes.setAutoPadding(false) } - } try { pushBytes(aes.update(data), b => this.push(b)) callback() - } catch(error) { + } catch (error) { callback(error) } }, @@ -568,10 +551,10 @@ export const downloadEncryptedContent = async( try { pushBytes(aes.final(), b => this.push(b)) callback() - } catch(error) { + } catch (error) { callback(error) } - }, + } }) return fetched.pipe(output, { end: true }) } @@ -580,11 +563,7 @@ export function extensionForMediaMessage(message: WAMessageContent) { const getExtension = (mimetype: string) => mimetype.split(';')[0].split('/')[1] const type = Object.keys(message)[0] as MessageType let extension: string - if( - type === 'locationMessage' || - type === 'liveLocationMessage' || - type === 'productMessage' - ) { + if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') { extension = '.jpeg' } else { const messageContent = message[type] as WAGenericMediaMessage @@ -596,18 +575,18 @@ export function extensionForMediaMessage(message: WAMessageContent) { export const getWAUploadToServer = ( { customUploadHosts, fetchAgent, logger, options }: SocketConfig, - refreshMediaConn: (force: boolean) => Promise, + refreshMediaConn: (force: boolean) => Promise ): WAMediaUploadFunction => { - return async(stream, { mediaType, fileEncSha256B64, timeoutMs }) => { + return async (stream, { mediaType, fileEncSha256B64, timeoutMs }) => { // send a query JSON to obtain the url & auth token to upload our media let uploadInfo = await refreshMediaConn(false) - let urls: { mediaUrl: string, directPath: string } | undefined - const hosts = [ ...customUploadHosts, ...uploadInfo.hosts ] + let urls: { mediaUrl: string; directPath: string } | undefined + const hosts = [...customUploadHosts, ...uploadInfo.hosts] fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64) - for(const { hostname } of hosts) { + for (const { hostname } of hosts) { logger.debug(`uploading to "${hostname}"`) const auth = encodeURIComponent(uploadInfo.auth) // the auth token @@ -615,27 +594,22 @@ export const getWAUploadToServer = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any let result: any try { - - const body = await axios.post( - url, - stream, - { - ...options, - headers: { - ...options.headers || { }, - 'Content-Type': 'application/octet-stream', - 'Origin': DEFAULT_ORIGIN - }, - httpsAgent: fetchAgent, - timeout: timeoutMs, - responseType: 'json', - maxBodyLength: Infinity, - maxContentLength: Infinity, - } - ) + const body = await axios.post(url, stream, { + ...options, + headers: { + ...(options.headers || {}), + 'Content-Type': 'application/octet-stream', + Origin: DEFAULT_ORIGIN + }, + httpsAgent: fetchAgent, + timeout: timeoutMs, + responseType: 'json', + maxBodyLength: Infinity, + maxContentLength: Infinity + }) result = body.data - if(result?.url || result?.directPath) { + if (result?.url || result?.directPath) { urls = { mediaUrl: result.url, directPath: result.direct_path @@ -645,21 +619,21 @@ export const getWAUploadToServer = ( uploadInfo = await refreshMediaConn(true) throw new Error(`upload failed, reason: ${JSON.stringify(result)}`) } - } catch(error) { - if(axios.isAxiosError(error)) { + } catch (error) { + if (axios.isAxiosError(error)) { result = error.response?.data } const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname - logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`) + logger.warn( + { trace: error.stack, uploadResult: result }, + `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}` + ) } } - if(!urls) { - throw new Boom( - 'Media upload failed on all hosts', - { statusCode: 500 } - ) + if (!urls) { + throw new Boom('Media upload failed on all hosts', { statusCode: 500 }) } return urls @@ -673,11 +647,7 @@ const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => { /** * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL */ -export const encryptMediaRetryRequest = async( - key: proto.IMessageKey, - mediaKey: Buffer | Uint8Array, - meId: string -) => { +export const encryptMediaRetryRequest = async (key: proto.IMessageKey, mediaKey: Buffer | Uint8Array, meId: string) => { const recp: proto.IServerErrorReceipt = { stanzaId: key.id } const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish() @@ -698,17 +668,17 @@ export const encryptMediaRetryRequest = async( // keeping it here to maintain parity with WA Web { tag: 'encrypt', - attrs: { }, + attrs: {}, content: [ - { tag: 'enc_p', attrs: { }, content: ciphertext }, - { tag: 'enc_iv', attrs: { }, content: iv } + { tag: 'enc_p', attrs: {}, content: ciphertext }, + { tag: 'enc_iv', attrs: {}, content: iv } ] }, { tag: 'rmr', attrs: { jid: key.remoteJid!, - 'from_me': (!!key.fromMe).toString(), + from_me: (!!key.fromMe).toString(), // @ts-ignore participant: key.participant || undefined } @@ -732,17 +702,17 @@ export const decodeMediaRetryNode = (node: BinaryNode) => { } const errorNode = getBinaryNodeChild(node, 'error') - if(errorNode) { + if (errorNode) { const errorCode = +errorNode.attrs.code - event.error = new Boom( - `Failed to re-upload media (${errorCode})`, - { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) } - ) + event.error = new Boom(`Failed to re-upload media (${errorCode})`, { + data: errorNode.attrs, + statusCode: getStatusCodeForMediaRetry(errorCode) + }) } else { const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt') const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p') const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv') - if(ciphertext && iv) { + if (ciphertext && iv) { event.media = { ciphertext, iv } } else { event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 }) @@ -752,8 +722,8 @@ export const decodeMediaRetryNode = (node: BinaryNode) => { return event } -export const decryptMediaRetryData = async( - { ciphertext, iv }: { ciphertext: Uint8Array, iv: Uint8Array }, +export const decryptMediaRetryData = async ( + { ciphertext, iv }: { ciphertext: Uint8Array; iv: Uint8Array }, mediaKey: Uint8Array, msgId: string ) => { @@ -768,5 +738,5 @@ const MEDIA_RETRY_STATUS_MAP = { [proto.MediaRetryNotification.ResultType.SUCCESS]: 200, [proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412, [proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404, - [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418, -} as const \ No newline at end of file + [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418 +} as const diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index b164fc8..a775530 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -21,13 +21,20 @@ import { WAMessageContent, WAMessageStatus, WAProto, - WATextMessage, + WATextMessage } from '../Types' import { isJidGroup, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary' import { sha256 } from './crypto' import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics' import { ILogger } from './logger' -import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, MediaDownloadOptions } from './messages-media' +import { + downloadContentFromMessage, + encryptedStream, + generateThumbnail, + getAudioDuration, + getAudioWaveform, + MediaDownloadOptions +} from './messages-media' type MediaUploadData = { media: WAMediaUpload @@ -51,15 +58,15 @@ const MIMETYPE_MAP: { [T in MediaType]?: string } = { document: 'application/pdf', audio: 'audio/ogg; codecs=opus', sticker: 'image/webp', - 'product-catalog-image': 'image/jpeg', + 'product-catalog-image': 'image/jpeg' } const MessageTypeProto = { - 'image': WAProto.Message.ImageMessage, - 'video': WAProto.Message.VideoMessage, - 'audio': WAProto.Message.AudioMessage, - 'sticker': WAProto.Message.StickerMessage, - 'document': WAProto.Message.DocumentMessage, + image: WAProto.Message.ImageMessage, + video: WAProto.Message.VideoMessage, + audio: WAProto.Message.AudioMessage, + sticker: WAProto.Message.StickerMessage, + document: WAProto.Message.DocumentMessage } as const /** @@ -69,25 +76,30 @@ const MessageTypeProto = { */ export const extractUrlFromText = (text: string) => text.match(URL_REGEX)?.[0] -export const generateLinkPreviewIfRequired = async(text: string, getUrlInfo: MessageGenerationOptions['getUrlInfo'], logger: MessageGenerationOptions['logger']) => { +export const generateLinkPreviewIfRequired = async ( + text: string, + getUrlInfo: MessageGenerationOptions['getUrlInfo'], + logger: MessageGenerationOptions['logger'] +) => { const url = extractUrlFromText(text) - if(!!getUrlInfo && url) { + if (!!getUrlInfo && url) { try { const urlInfo = await getUrlInfo(url) return urlInfo - } catch(error) { // ignore if fails + } catch (error) { + // ignore if fails logger?.warn({ trace: error.stack }, 'url generation failed') } } } -const assertColor = async(color) => { +const assertColor = async color => { let assertedColor - if(typeof color === 'number') { + if (typeof color === 'number') { assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1 } else { let hex = color.trim().replace('#', '') - if(hex.length <= 6) { + if (hex.length <= 6) { hex = 'FF' + hex.padStart(6, '0') } @@ -96,20 +108,17 @@ const assertColor = async(color) => { } } -export const prepareWAMessageMedia = async( - message: AnyMediaMessageContent, - options: MediaGenerationOptions -) => { +export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, options: MediaGenerationOptions) => { const logger = options.logger - let mediaType: typeof MEDIA_KEYS[number] | undefined - for(const key of MEDIA_KEYS) { - if(key in message) { + let mediaType: (typeof MEDIA_KEYS)[number] | undefined + for (const key of MEDIA_KEYS) { + if (key in message) { mediaType = key } } - if(!mediaType) { + if (!mediaType) { throw new Boom('Invalid media type', { statusCode: 400 }) } @@ -119,26 +128,26 @@ export const prepareWAMessageMedia = async( } delete uploadData[mediaType] // check if cacheable + generate cache key - const cacheableKey = typeof uploadData.media === 'object' && - ('url' in uploadData.media) && - !!uploadData.media.url && - !!options.mediaCache && ( - // generate the key + const cacheableKey = + typeof uploadData.media === 'object' && + 'url' in uploadData.media && + !!uploadData.media.url && + !!options.mediaCache && + // generate the key mediaType + ':' + uploadData.media.url.toString() - ) - if(mediaType === 'document' && !uploadData.fileName) { + if (mediaType === 'document' && !uploadData.fileName) { uploadData.fileName = 'file' } - if(!uploadData.mimetype) { + if (!uploadData.mimetype) { uploadData.mimetype = MIMETYPE_MAP[mediaType] } // check for cache hit - if(cacheableKey) { + if (cacheableKey) { const mediaBuff = options.mediaCache!.get(cacheableKey) - if(mediaBuff) { + if (mediaBuff) { logger?.debug({ cacheableKey }, 'got media cache hit') const obj = WAProto.Message.decode(mediaBuff) @@ -151,48 +160,39 @@ export const prepareWAMessageMedia = async( } const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined' - const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && - (typeof uploadData['jpegThumbnail'] === 'undefined') + const requiresThumbnailComputation = + (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined' const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation - const { - mediaKey, - encWriteStream, - bodyPath, - fileEncSha256, - fileSha256, - fileLength, - didSaveToTmpPath - } = await encryptedStream( - uploadData.media, - options.mediaTypeOverride || mediaType, - { + const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } = + await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, { logger, saveOriginalFileIfRequired: requiresOriginalForSomeProcessing, opts: options.options - } - ) - // url safe Base64 encode the SHA256 hash of the body + }) + // url safe Base64 encode the SHA256 hash of the body const fileEncSha256B64 = fileEncSha256.toString('base64') const [{ mediaUrl, directPath }] = await Promise.all([ - (async() => { - const result = await options.upload( - encWriteStream, - { fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs } - ) + (async () => { + const result = await options.upload(encWriteStream, { + fileEncSha256B64, + mediaType, + timeoutMs: options.mediaUploadTimeoutMs + }) logger?.debug({ mediaType, cacheableKey }, 'uploaded media') return result })(), - (async() => { + (async () => { try { - if(requiresThumbnailComputation) { - const { - thumbnail, - originalImageDimensions - } = await generateThumbnail(bodyPath!, mediaType as 'image' | 'video', options) + if (requiresThumbnailComputation) { + const { thumbnail, originalImageDimensions } = await generateThumbnail( + bodyPath!, + mediaType as 'image' | 'video', + options + ) uploadData.jpegThumbnail = thumbnail - if(!uploadData.width && originalImageDimensions) { + if (!uploadData.width && originalImageDimensions) { uploadData.width = originalImageDimensions.width uploadData.height = originalImageDimensions.height logger?.debug('set dimensions') @@ -201,63 +201,58 @@ export const prepareWAMessageMedia = async( logger?.debug('generated thumbnail') } - if(requiresDurationComputation) { + if (requiresDurationComputation) { uploadData.seconds = await getAudioDuration(bodyPath!) logger?.debug('computed audio duration') } - if(requiresWaveformProcessing) { + if (requiresWaveformProcessing) { uploadData.waveform = await getAudioWaveform(bodyPath!, logger) logger?.debug('processed waveform') } - if(requiresAudioBackground) { + if (requiresAudioBackground) { uploadData.backgroundArgb = await assertColor(options.backgroundColor) logger?.debug('computed backgroundColor audio status') } - } catch(error) { + } catch (error) { logger?.warn({ trace: error.stack }, 'failed to obtain extra info') } - })(), - ]) - .finally( - async() => { - encWriteStream.destroy() - // remove tmp files - if(didSaveToTmpPath && bodyPath) { - try { - await fs.access(bodyPath) - await fs.unlink(bodyPath) - logger?.debug('removed tmp file') - } catch(error) { - logger?.warn('failed to remove tmp file') - } - } + })() + ]).finally(async () => { + encWriteStream.destroy() + // remove tmp files + if (didSaveToTmpPath && bodyPath) { + try { + await fs.access(bodyPath) + await fs.unlink(bodyPath) + logger?.debug('removed tmp file') + } catch (error) { + logger?.warn('failed to remove tmp file') } - ) - - const obj = WAProto.Message.fromObject({ - [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject( - { - url: mediaUrl, - directPath, - mediaKey, - fileEncSha256, - fileSha256, - fileLength, - mediaKeyTimestamp: unixTimestampSeconds(), - ...uploadData, - media: undefined - } - ) + } }) - if(uploadData.ptv) { + const obj = WAProto.Message.fromObject({ + [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({ + url: mediaUrl, + directPath, + mediaKey, + fileEncSha256, + fileSha256, + fileLength, + mediaKeyTimestamp: unixTimestampSeconds(), + ...uploadData, + media: undefined + }) + }) + + if (uploadData.ptv) { obj.ptvMessage = obj.videoMessage delete obj.videoMessage } - if(cacheableKey) { + if (cacheableKey) { logger?.debug({ cacheableKey }, 'set cache') options.mediaCache!.set(cacheableKey, WAProto.Message.encode(obj).finish()) } @@ -285,12 +280,9 @@ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration?: n * @param message the message to forward * @param options.forceForward will show the message as forwarded even if it is from you */ -export const generateForwardMessageContent = ( - message: WAMessage, - forceForward?: boolean -) => { +export const generateForwardMessageContent = (message: WAMessage, forceForward?: boolean) => { let content = message.message - if(!content) { + if (!content) { throw new Boom('no content in message', { statusCode: 400 }) } @@ -302,14 +294,14 @@ export const generateForwardMessageContent = ( let score = content[key].contextInfo?.forwardingScore || 0 score += message.key.fromMe && !forceForward ? 0 : 1 - if(key === 'conversation') { + if (key === 'conversation') { content.extendedTextMessage = { text: content[key] } delete content.conversation key = 'extendedTextMessage' } - if(score > 0) { + if (score > 0) { content[key].contextInfo = { forwardingScore: score, isForwarded: true } } else { content[key].contextInfo = {} @@ -318,20 +310,20 @@ export const generateForwardMessageContent = ( return content } -export const generateWAMessageContent = async( +export const generateWAMessageContent = async ( message: AnyMessageContent, options: MessageContentGenerationOptions ) => { let m: WAMessageContent = {} - if('text' in message) { + if ('text' in message) { const extContent = { text: message.text } as WATextMessage let urlInfo = message.linkPreview - if(typeof urlInfo === 'undefined') { + if (typeof urlInfo === 'undefined') { urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger) } - if(urlInfo) { + if (urlInfo) { extContent.matchedText = urlInfo['matched-text'] extContent.jpegThumbnail = urlInfo.jpegThumbnail extContent.description = urlInfo.description @@ -339,7 +331,7 @@ export const generateWAMessageContent = async( extContent.previewType = 0 const img = urlInfo.highQualityThumbnail - if(img) { + if (img) { extContent.thumbnailDirectPath = img.directPath extContent.mediaKey = img.mediaKey extContent.mediaKeyTimestamp = img.mediaKeyTimestamp @@ -350,50 +342,50 @@ export const generateWAMessageContent = async( } } - if(options.backgroundColor) { + if (options.backgroundColor) { extContent.backgroundArgb = await assertColor(options.backgroundColor) } - if(options.font) { + if (options.font) { extContent.font = options.font } m.extendedTextMessage = extContent - } else if('contacts' in message) { + } else if ('contacts' in message) { const contactLen = message.contacts.contacts.length - if(!contactLen) { + if (!contactLen) { throw new Boom('require atleast 1 contact', { statusCode: 400 }) } - if(contactLen === 1) { + if (contactLen === 1) { m.contactMessage = WAProto.Message.ContactMessage.fromObject(message.contacts.contacts[0]) } else { m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.fromObject(message.contacts) } - } else if('location' in message) { + } else if ('location' in message) { m.locationMessage = WAProto.Message.LocationMessage.fromObject(message.location) - } else if('react' in message) { - if(!message.react.senderTimestampMs) { + } else if ('react' in message) { + if (!message.react.senderTimestampMs) { message.react.senderTimestampMs = Date.now() } m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(message.react) - } else if('delete' in message) { + } else if ('delete' in message) { m.protocolMessage = { key: message.delete, type: WAProto.Message.ProtocolMessage.Type.REVOKE } - } else if('forward' in message) { - m = generateForwardMessageContent( - message.forward, - message.force - ) - } else if('disappearingMessagesInChat' in message) { - const exp = typeof message.disappearingMessagesInChat === 'boolean' ? - (message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) : - message.disappearingMessagesInChat + } else if ('forward' in message) { + m = generateForwardMessageContent(message.forward, message.force) + } else if ('disappearingMessagesInChat' in message) { + const exp = + typeof message.disappearingMessagesInChat === 'boolean' + ? message.disappearingMessagesInChat + ? WA_DEFAULT_EPHEMERAL + : 0 + : message.disappearingMessagesInChat m = prepareDisappearingMessageSettingContent(exp) - } else if('groupInvite' in message) { + } else if ('groupInvite' in message) { m.groupInviteMessage = {} m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration @@ -403,16 +395,16 @@ export const generateWAMessageContent = async( m.groupInviteMessage.groupName = message.groupInvite.subject //TODO: use built-in interface and get disappearing mode info etc. //TODO: cache / use store!? - if(options.getProfilePicUrl) { + if (options.getProfilePicUrl) { const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview') - if(pfpUrl) { + if (pfpUrl) { const resp = await axios.get(pfpUrl, { responseType: 'arraybuffer' }) - if(resp.status === 200) { + if (resp.status === 200) { m.groupInviteMessage.jpegThumbnail = resp.data } } } - } else if('pin' in message) { + } else if ('pin' in message) { m.pinInChatMessage = {} m.messageContextInfo = {} @@ -421,77 +413,67 @@ export const generateWAMessageContent = async( m.pinInChatMessage.senderTimestampMs = Date.now() m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0 - } else if('buttonReply' in message) { + } else if ('buttonReply' in message) { switch (message.type) { - case 'template': - m.templateButtonReplyMessage = { - selectedDisplayText: message.buttonReply.displayText, - selectedId: message.buttonReply.id, - selectedIndex: message.buttonReply.index, - } - break - case 'plain': - m.buttonsResponseMessage = { - selectedButtonId: message.buttonReply.id, - selectedDisplayText: message.buttonReply.displayText, - type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT, - } - break + case 'template': + m.templateButtonReplyMessage = { + selectedDisplayText: message.buttonReply.displayText, + selectedId: message.buttonReply.id, + selectedIndex: message.buttonReply.index + } + break + case 'plain': + m.buttonsResponseMessage = { + selectedButtonId: message.buttonReply.id, + selectedDisplayText: message.buttonReply.displayText, + type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT + } + break } - } else if('ptv' in message && message.ptv) { - const { videoMessage } = await prepareWAMessageMedia( - { video: message.video }, - options - ) + } else if ('ptv' in message && message.ptv) { + const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options) m.ptvMessage = videoMessage - } else if('product' in message) { - const { imageMessage } = await prepareWAMessageMedia( - { image: message.product.productImage }, - options - ) + } else if ('product' in message) { + const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options) m.productMessage = WAProto.Message.ProductMessage.fromObject({ ...message, product: { ...message.product, - productImage: imageMessage, + productImage: imageMessage } }) - } else if('listReply' in message) { + } else if ('listReply' in message) { m.listResponseMessage = { ...message.listReply } - } else if('poll' in message) { + } else if ('poll' in message) { message.poll.selectableCount ||= 0 message.poll.toAnnouncementGroup ||= false - if(!Array.isArray(message.poll.values)) { + if (!Array.isArray(message.poll.values)) { throw new Boom('Invalid poll values', { statusCode: 400 }) } - if( - message.poll.selectableCount < 0 - || message.poll.selectableCount > message.poll.values.length - ) { - throw new Boom( - `poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, - { statusCode: 400 } - ) + if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) { + throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, { + statusCode: 400 + }) } m.messageContextInfo = { // encKey - messageSecret: message.poll.messageSecret || randomBytes(32), + messageSecret: message.poll.messageSecret || randomBytes(32) } const pollCreationMessage = { name: message.poll.name, selectableOptionsCount: message.poll.selectableCount, - options: message.poll.values.map(optionName => ({ optionName })), + options: message.poll.values.map(optionName => ({ optionName })) } - if(message.poll.toAnnouncementGroup) { + if (message.poll.toAnnouncementGroup) { // poll v2 is for community announcement groups (single select and multiple) m.pollCreationMessageV2 = pollCreationMessage } else { - if(message.poll.selectableCount > 0) { + if (message.poll.selectableCount > 0) { //poll v3 is for single select polls m.pollCreationMessageV3 = pollCreationMessage } else { @@ -499,30 +481,27 @@ export const generateWAMessageContent = async( m.pollCreationMessage = pollCreationMessage } } - } else if('sharePhoneNumber' in message) { + } else if ('sharePhoneNumber' in message) { m.protocolMessage = { type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER } - } else if('requestPhoneNumber' in message) { + } else if ('requestPhoneNumber' in message) { m.requestPhoneNumberMessage = {} } else { - m = await prepareWAMessageMedia( - message, - options - ) + m = await prepareWAMessageMedia(message, options) } - if('viewOnce' in message && !!message.viewOnce) { + if ('viewOnce' in message && !!message.viewOnce) { m = { viewOnceMessage: { message: m } } } - if('mentions' in message && message.mentions?.length) { + if ('mentions' in message && message.mentions?.length) { const [messageType] = Object.keys(m) - m[messageType].contextInfo = m[messageType] || { } + m[messageType].contextInfo = m[messageType] || {} m[messageType].contextInfo.mentionedJid = message.mentions } - if('edit' in message) { + if ('edit' in message) { m = { protocolMessage: { key: message.edit, @@ -533,7 +512,7 @@ export const generateWAMessageContent = async( } } - if('contextInfo' in message && !!message.contextInfo) { + if ('contextInfo' in message && !!message.contextInfo) { const [messageType] = Object.keys(m) m[messageType] = m[messageType] || {} m[messageType].contextInfo = message.contextInfo @@ -549,7 +528,7 @@ export const generateWAMessageFromContent = ( ) => { // set timestamp to now // if not specified - if(!options.timestamp) { + if (!options.timestamp) { options.timestamp = new Date() } @@ -558,8 +537,10 @@ export const generateWAMessageFromContent = ( const timestamp = unixTimestampSeconds(options.timestamp) const { quoted, userJid } = options - if(quoted) { - const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid) + if (quoted) { + const participant = quoted.key.fromMe + ? userJid + : quoted.participant || quoted.key.participant || quoted.key.remoteJid let quotedMsg = normalizeMessageContent(quoted.message)! const msgType = getContentType(quotedMsg)! @@ -567,25 +548,25 @@ export const generateWAMessageFromContent = ( quotedMsg = proto.Message.fromObject({ [msgType]: quotedMsg[msgType] }) const quotedContent = quotedMsg[msgType] - if(typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) { + if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) { delete quotedContent.contextInfo } - const contextInfo: proto.IContextInfo = innerMessage[key].contextInfo || { } + const contextInfo: proto.IContextInfo = innerMessage[key].contextInfo || {} contextInfo.participant = jidNormalizedUser(participant!) contextInfo.stanzaId = quoted.key.id contextInfo.quotedMessage = quotedMsg // if a participant is quoted, then it must be a group // hence, remoteJid of group must also be entered - if(jid !== quoted.key.remoteJid) { + if (jid !== quoted.key.remoteJid) { contextInfo.remoteJid = quoted.key.remoteJid } innerMessage[key].contextInfo = contextInfo } - if( + if ( // if we want to send a disappearing message !!options?.ephemeralExpiration && // and it's not a protocol message -- delete, toggle disappear message @@ -595,7 +576,7 @@ export const generateWAMessageFromContent = ( ) { innerMessage[key].contextInfo = { ...(innerMessage[key].contextInfo || {}), - expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL, + expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString() } } @@ -606,7 +587,7 @@ export const generateWAMessageFromContent = ( key: { remoteJid: jid, fromMe: true, - id: options?.messageId || generateMessageIDV2(), + id: options?.messageId || generateMessageIDV2() }, message: message, messageTimestamp: timestamp, @@ -617,26 +598,15 @@ export const generateWAMessageFromContent = ( return WAProto.WebMessageInfo.fromObject(messageJSON) } -export const generateWAMessage = async( - jid: string, - content: AnyMessageContent, - options: MessageGenerationOptions, -) => { +export const generateWAMessage = async (jid: string, content: AnyMessageContent, options: MessageGenerationOptions) => { // ensure msg ID is with every log options.logger = options?.logger?.child({ msgId: options.messageId }) - return generateWAMessageFromContent( - jid, - await generateWAMessageContent( - content, - options - ), - options - ) + return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options) } /** Get the key to access the true type of content */ export const getContentType = (content: WAProto.IMessage | undefined) => { - if(content) { + if (content) { const keys = Object.keys(content) const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage') return key as keyof typeof content @@ -650,32 +620,32 @@ export const getContentType = (content: WAProto.IMessage | undefined) => { * @returns */ export const normalizeMessageContent = (content: WAMessageContent | null | undefined): WAMessageContent | undefined => { - if(!content) { - return undefined - } + if (!content) { + return undefined + } - // set max iterations to prevent an infinite loop - for(let i = 0;i < 5;i++) { - const inner = getFutureProofMessage(content) - if(!inner) { - break - } + // set max iterations to prevent an infinite loop + for (let i = 0; i < 5; i++) { + const inner = getFutureProofMessage(content) + if (!inner) { + break + } - content = inner.message - } + content = inner.message + } - return content! + return content! - function getFutureProofMessage(message: typeof content) { - return ( - message?.ephemeralMessage - || message?.viewOnceMessage - || message?.documentWithCaptionMessage - || message?.viewOnceMessageV2 - || message?.viewOnceMessageV2Extension - || message?.editedMessage - ) - } + function getFutureProofMessage(message: typeof content) { + return ( + message?.ephemeralMessage || + message?.viewOnceMessage || + message?.documentWithCaptionMessage || + message?.viewOnceMessageV2 || + message?.viewOnceMessageV2Extension || + message?.editedMessage + ) + } } /** @@ -683,40 +653,40 @@ export const normalizeMessageContent = (content: WAMessageContent | null | undef * Eg. extracts the inner message from a disappearing message/view once message */ export const extractMessageContent = (content: WAMessageContent | undefined | null): WAMessageContent | undefined => { - const extractFromTemplateMessage = (msg: proto.Message.TemplateMessage.IHydratedFourRowTemplate | proto.Message.IButtonsMessage) => { - if(msg.imageMessage) { + const extractFromTemplateMessage = ( + msg: proto.Message.TemplateMessage.IHydratedFourRowTemplate | proto.Message.IButtonsMessage + ) => { + if (msg.imageMessage) { return { imageMessage: msg.imageMessage } - } else if(msg.documentMessage) { + } else if (msg.documentMessage) { return { documentMessage: msg.documentMessage } - } else if(msg.videoMessage) { + } else if (msg.videoMessage) { return { videoMessage: msg.videoMessage } - } else if(msg.locationMessage) { + } else if (msg.locationMessage) { return { locationMessage: msg.locationMessage } } else { return { conversation: - 'contentText' in msg - ? msg.contentText - : ('hydratedContentText' in msg ? msg.hydratedContentText : '') + 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : '' } } } content = normalizeMessageContent(content) - if(content?.buttonsMessage) { - return extractFromTemplateMessage(content.buttonsMessage) + if (content?.buttonsMessage) { + return extractFromTemplateMessage(content.buttonsMessage) } - if(content?.templateMessage?.hydratedFourRowTemplate) { + if (content?.templateMessage?.hydratedFourRowTemplate) { return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate) } - if(content?.templateMessage?.hydratedTemplate) { + if (content?.templateMessage?.hydratedTemplate) { return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate) } - if(content?.templateMessage?.fourRowTemplate) { + if (content?.templateMessage?.fourRowTemplate) { return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate) } @@ -726,17 +696,22 @@ export const extractMessageContent = (content: WAMessageContent | undefined | nu /** * Returns the device predicted by message ID */ -export const getDevice = (id: string) => /^3A.{18}$/.test(id) ? 'ios' : - /^3E.{20}$/.test(id) ? 'web' : - /^(.{21}|.{32})$/.test(id) ? 'android' : - /^(3F|.{18}$)/.test(id) ? 'desktop' : - 'unknown' +export const getDevice = (id: string) => + /^3A.{18}$/.test(id) + ? 'ios' + : /^3E.{20}$/.test(id) + ? 'web' + : /^(.{21}|.{32})$/.test(id) + ? 'android' + : /^(3F|.{18}$)/.test(id) + ? 'desktop' + : 'unknown' /** Upserts a receipt in the message */ export const updateMessageWithReceipt = (msg: Pick, receipt: MessageUserReceipt) => { msg.userReceipt = msg.userReceipt || [] const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid) - if(recp) { + if (recp) { Object.assign(recp, receipt) } else { msg.userReceipt.push(receipt) @@ -747,9 +722,8 @@ export const updateMessageWithReceipt = (msg: Pick, re export const updateMessageWithReaction = (msg: Pick, reaction: proto.IReaction) => { const authorID = getKeyAuthor(reaction.key) - const reactions = (msg.reactions || []) - .filter(r => getKeyAuthor(r.key) !== authorID) - if(reaction.text) { + const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID) + if (reaction.text) { reactions.push(reaction) } @@ -757,15 +731,11 @@ export const updateMessageWithReaction = (msg: Pick, rea } /** Update the message with a new poll update */ -export const updateMessageWithPollUpdate = ( - msg: Pick, - update: proto.IPollUpdate -) => { +export const updateMessageWithPollUpdate = (msg: Pick, update: proto.IPollUpdate) => { const authorID = getKeyAuthor(update.pollUpdateMessageKey) - const reactions = (msg.pollUpdates || []) - .filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID) - if(update.vote?.selectedOptions?.length) { + const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID) + if (update.vote?.selectedOptions?.length) { reactions.push(update) } @@ -787,26 +757,33 @@ export function getAggregateVotesInPollMessage( { message, pollUpdates }: Pick, meId?: string ) { - const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || [] - const voteHashMap = opts.reduce((acc, opt) => { - const hash = sha256(Buffer.from(opt.optionName || '')).toString() - acc[hash] = { - name: opt.optionName || '', - voters: [] - } - return acc - }, {} as { [_: string]: VoteAggregation }) + const opts = + message?.pollCreationMessage?.options || + message?.pollCreationMessageV2?.options || + message?.pollCreationMessageV3?.options || + [] + const voteHashMap = opts.reduce( + (acc, opt) => { + const hash = sha256(Buffer.from(opt.optionName || '')).toString() + acc[hash] = { + name: opt.optionName || '', + voters: [] + } + return acc + }, + {} as { [_: string]: VoteAggregation } + ) - for(const update of pollUpdates || []) { + for (const update of pollUpdates || []) { const { vote } = update - if(!vote) { + if (!vote) { continue } - for(const option of vote.selectedOptions || []) { + for (const option of vote.selectedOptions || []) { const hash = option.toString() let data = voteHashMap[hash] - if(!data) { + if (!data) { voteHashMap[hash] = { name: 'Unknown', voters: [] @@ -814,9 +791,7 @@ export function getAggregateVotesInPollMessage( data = voteHashMap[hash] } - voteHashMap[hash].voters.push( - getKeyAuthor(update.pollUpdateMessageKey, meId) - ) + voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId)) } } @@ -825,11 +800,11 @@ export function getAggregateVotesInPollMessage( /** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */ export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => { - const keyMap: { [id: string]: { jid: string, participant: string | undefined, messageIds: string[] } } = { } - for(const { remoteJid, id, participant, fromMe } of keys) { - if(!fromMe) { + const keyMap: { [id: string]: { jid: string; participant: string | undefined; messageIds: string[] } } = {} + for (const { remoteJid, id, participant, fromMe } of keys) { + if (!fromMe) { const uqKey = `${remoteJid}:${participant || ''}` - if(!keyMap[uqKey]) { + if (!keyMap[uqKey]) { keyMap[uqKey] = { jid: remoteJid!, participant: participant!, @@ -854,31 +829,33 @@ const REUPLOAD_REQUIRED_STATUS = [410, 404] /** * Downloads the given message. Throws an error if it's not a media message */ -export const downloadMediaMessage = async( +export const downloadMediaMessage = async ( message: WAMessage, type: Type, options: MediaDownloadOptions, ctx?: DownloadMediaMessageContext ) => { - const result = await downloadMsg() - .catch(async(error) => { - if(ctx && axios.isAxiosError(error) && // check if the message requires a reupload - REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!)) { - ctx.logger.info({ key: message.key }, 'sending reupload media request...') - // request reupload - message = await ctx.reuploadRequest(message) - const result = await downloadMsg() - return result - } + const result = await downloadMsg().catch(async error => { + if ( + ctx && + axios.isAxiosError(error) && // check if the message requires a reupload + REUPLOAD_REQUIRED_STATUS.includes(error.response?.status!) + ) { + ctx.logger.info({ key: message.key }, 'sending reupload media request...') + // request reupload + message = await ctx.reuploadRequest(message) + const result = await downloadMsg() + return result + } - throw error - }) + throw error + }) return result as Type extends 'buffer' ? Buffer : Transform async function downloadMsg() { const mContent = extractMessageContent(message.message) - if(!mContent) { + if (!mContent) { throw new Boom('No message present', { statusCode: 400, data: message }) } @@ -886,12 +863,12 @@ export const downloadMediaMessage = async( let mediaType = contentType?.replace('Message', '') as MediaType const media = mContent[contentType!] - if(!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) { + if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) { throw new Boom(`"${contentType}" message is not a media message`) } let download: DownloadableMessage - if('thumbnailDirectPath' in media && !('url' in media)) { + if ('thumbnailDirectPath' in media && !('url' in media)) { download = { directPath: media.thumbnailDirectPath, mediaKey: media.mediaKey @@ -902,7 +879,7 @@ export const downloadMediaMessage = async( } const stream = await downloadContentFromMessage(download, mediaType, options) - if(type === 'buffer') { + if (type === 'buffer') { const bufferArray: Buffer[] = [] for await (const chunk of stream) { bufferArray.push(chunk) @@ -918,16 +895,14 @@ export const downloadMediaMessage = async( /** Checks whether the given message is a media message; if it is returns the inner content */ export const assertMediaContent = (content: proto.IMessage | null | undefined) => { content = extractMessageContent(content) - const mediaContent = content?.documentMessage - || content?.imageMessage - || content?.videoMessage - || content?.audioMessage - || content?.stickerMessage - if(!mediaContent) { - throw new Boom( - 'given message is not a media message', - { statusCode: 400, data: content } - ) + const mediaContent = + content?.documentMessage || + content?.imageMessage || + content?.videoMessage || + content?.audioMessage || + content?.stickerMessage + if (!mediaContent) { + throw new Boom('given message is not a media message', { statusCode: 400, data: content }) } return mediaContent diff --git a/src/Utils/noise-handler.ts b/src/Utils/noise-handler.ts index 7d4d873..c303b2e 100644 --- a/src/Utils/noise-handler.ts +++ b/src/Utils/noise-handler.ts @@ -27,7 +27,7 @@ export const makeNoiseHandler = ({ logger = logger.child({ class: 'ns' }) const authenticate = (data: Uint8Array) => { - if(!isFinished) { + if (!isFinished) { hash = sha256(Buffer.concat([hash, data])) } } @@ -47,7 +47,7 @@ export const makeNoiseHandler = ({ const iv = generateIV(isFinished ? readCounter : writeCounter) const result = aesDecryptGCM(ciphertext, decKey, iv, hash) - if(isFinished) { + if (isFinished) { readCounter += 1 } else { writeCounter += 1 @@ -57,12 +57,12 @@ export const makeNoiseHandler = ({ return result } - const localHKDF = async(data: Uint8Array) => { + const localHKDF = async (data: Uint8Array) => { const key = await hkdf(Buffer.from(data), 64, { salt, info: '' }) return [key.slice(0, 32), key.slice(32)] } - const mixIntoKey = async(data: Uint8Array) => { + const mixIntoKey = async (data: Uint8Array) => { const [write, read] = await localHKDF(data) salt = write encKey = read @@ -71,7 +71,7 @@ export const makeNoiseHandler = ({ writeCounter = 0 } - const finishInit = async() => { + const finishInit = async () => { const [write, read] = await localHKDF(new Uint8Array(0)) encKey = write decKey = read @@ -102,7 +102,7 @@ export const makeNoiseHandler = ({ authenticate, mixIntoKey, finishInit, - processHandshake: async({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => { + processHandshake: async ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => { authenticate(serverHello!.ephemeral!) await mixIntoKey(Curve.sharedKey(privateKey, serverHello!.ephemeral!)) @@ -115,7 +115,7 @@ export const makeNoiseHandler = ({ const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!) - if(issuerSerial !== WA_CERT_DETAILS.SERIAL) { + if (issuerSerial !== WA_CERT_DETAILS.SERIAL) { throw new Boom('certification match failed', { statusCode: 400 }) } @@ -125,13 +125,13 @@ export const makeNoiseHandler = ({ return keyEnc }, encodeFrame: (data: Buffer | Uint8Array) => { - if(isFinished) { + if (isFinished) { data = encrypt(data) } let header: Buffer - if(routingInfo) { + if (routingInfo) { header = Buffer.alloc(7) header.write('ED', 0, 'utf8') header.writeUint8(0, 2) @@ -146,7 +146,7 @@ export const makeNoiseHandler = ({ const introSize = sentIntro ? 0 : header.length const frame = Buffer.alloc(introSize + 3 + data.byteLength) - if(!sentIntro) { + if (!sentIntro) { frame.set(header) sentIntro = true } @@ -157,26 +157,26 @@ export const makeNoiseHandler = ({ return frame }, - decodeFrame: async(newData: Buffer | Uint8Array, onFrame: (buff: Uint8Array | BinaryNode) => void) => { + decodeFrame: async (newData: Buffer | Uint8Array, onFrame: (buff: Uint8Array | BinaryNode) => void) => { // the binary protocol uses its own framing mechanism // on top of the WS frames // so we get this data and separate out the frames const getBytesSize = () => { - if(inBytes.length >= 3) { + if (inBytes.length >= 3) { return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1) } } - inBytes = Buffer.concat([ inBytes, newData ]) + inBytes = Buffer.concat([inBytes, newData]) logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`) let size = getBytesSize() - while(size && inBytes.length >= size + 3) { + while (size && inBytes.length >= size + 3) { let frame: Uint8Array | BinaryNode = inBytes.slice(3, size + 3) inBytes = inBytes.slice(size + 3) - if(isFinished) { + if (isFinished) { const result = decrypt(frame) frame = await decodeBinaryNode(result) } @@ -188,4 +188,4 @@ export const makeNoiseHandler = ({ } } } -} \ No newline at end of file +} diff --git a/src/Utils/process-message.ts b/src/Utils/process-message.ts index c1267d1..35b4445 100644 --- a/src/Utils/process-message.ts +++ b/src/Utils/process-message.ts @@ -1,6 +1,17 @@ import { AxiosRequestConfig } from 'axios' import { proto } from '../../WAProto' -import { AuthenticationCreds, BaileysEventEmitter, CacheStore, Chat, GroupMetadata, ParticipantAction, RequestJoinAction, RequestJoinMethod, SignalKeyStoreWithTransaction, WAMessageStubType } from '../Types' +import { + AuthenticationCreds, + BaileysEventEmitter, + CacheStore, + Chat, + GroupMetadata, + ParticipantAction, + RequestJoinAction, + RequestJoinMethod, + SignalKeyStoreWithTransaction, + WAMessageStubType +} from '../Types' import { getContentType, normalizeMessageContent } from '../Utils/messages' import { areJidsSameUser, isJidBroadcast, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary' import { aesDecryptGCM, hmacSign } from './crypto' @@ -25,9 +36,7 @@ const REAL_MSG_STUB_TYPES = new Set([ WAMessageStubType.CALL_MISSED_VOICE ]) -const REAL_MSG_REQ_ME_STUB_TYPES = new Set([ - WAMessageStubType.GROUP_PARTICIPANT_ADD -]) +const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD]) /** Cleans a received message to further processing */ export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => { @@ -36,25 +45,25 @@ export const cleanMessage = (message: proto.IWebMessageInfo, meId: string) => { message.key.participant = message.key.participant ? jidNormalizedUser(message.key.participant) : undefined const content = normalizeMessageContent(message.message) // if the message has a reaction, ensure fromMe & remoteJid are from our perspective - if(content?.reactionMessage) { + if (content?.reactionMessage) { normaliseKey(content.reactionMessage.key!) } - if(content?.pollUpdateMessage) { + if (content?.pollUpdateMessage) { normaliseKey(content.pollUpdateMessage.pollCreationMessageKey!) } function normaliseKey(msgKey: proto.IMessageKey) { // if the reaction is from another user // we've to correctly map the key to this user's perspective - if(!message.key.fromMe) { + if (!message.key.fromMe) { // if the sender believed the message being reacted to is not from them // we've to correct the key to be from them, or some other participant msgKey.fromMe = !msgKey.fromMe ? areJidsSameUser(msgKey.participant || msgKey.remoteJid!, meId) - // if the message being reacted to, was from them - // fromMe automatically becomes false - : false + : // if the message being reacted to, was from them + // fromMe automatically becomes false + false // set the remoteJid to being the same as the chat the message came from msgKey.remoteJid = message.key.remoteJid // set participant of the message @@ -67,33 +76,26 @@ export const isRealMessage = (message: proto.IWebMessageInfo, meId: string) => { const normalizedContent = normalizeMessageContent(message.message) const hasSomeContent = !!getContentType(normalizedContent) return ( - !!normalizedContent - || REAL_MSG_STUB_TYPES.has(message.messageStubType!) - || ( - REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!) - && message.messageStubParameters?.some(p => areJidsSameUser(meId, p)) - ) + (!!normalizedContent || + REAL_MSG_STUB_TYPES.has(message.messageStubType!) || + (REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!) && + message.messageStubParameters?.some(p => areJidsSameUser(meId, p)))) && + hasSomeContent && + !normalizedContent?.protocolMessage && + !normalizedContent?.reactionMessage && + !normalizedContent?.pollUpdateMessage ) - && hasSomeContent - && !normalizedContent?.protocolMessage - && !normalizedContent?.reactionMessage - && !normalizedContent?.pollUpdateMessage } -export const shouldIncrementChatUnread = (message: proto.IWebMessageInfo) => ( +export const shouldIncrementChatUnread = (message: proto.IWebMessageInfo) => !message.key.fromMe && !message.messageStubType -) /** * Get the ID of the chat from the given key. * Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant */ export const getChatId = ({ remoteJid, participant, fromMe }: proto.IMessageKey) => { - if( - isJidBroadcast(remoteJid!) - && !isJidStatusBroadcast(remoteJid!) - && !fromMe - ) { + if (isJidBroadcast(remoteJid!) && !isJidStatusBroadcast(remoteJid!) && !fromMe) { return participant! } @@ -119,22 +121,15 @@ type PollContext = { */ export function decryptPollVote( { encPayload, encIv }: proto.Message.IPollEncValue, - { - pollCreatorJid, - pollMsgId, - pollEncKey, - voterJid, - }: PollContext + { pollCreatorJid, pollMsgId, pollEncKey, voterJid }: PollContext ) { - const sign = Buffer.concat( - [ - toBinary(pollMsgId), - toBinary(pollCreatorJid), - toBinary(voterJid), - toBinary('Poll Vote'), - new Uint8Array([1]) - ] - ) + const sign = Buffer.concat([ + toBinary(pollMsgId), + toBinary(pollCreatorJid), + toBinary(voterJid), + toBinary('Poll Vote'), + new Uint8Array([1]) + ]) const key0 = hmacSign(pollEncKey, new Uint8Array(32), 'sha256') const decKey = hmacSign(sign, key0, 'sha256') @@ -148,17 +143,9 @@ export function decryptPollVote( } } -const processMessage = async( +const processMessage = async ( message: proto.IWebMessageInfo, - { - shouldProcessHistoryMsg, - placeholderResendCache, - ev, - creds, - keyStore, - logger, - options - }: ProcessMessageContext + { shouldProcessHistoryMsg, placeholderResendCache, ev, creds, keyStore, logger, options }: ProcessMessageContext ) => { const meId = creds.me!.id const { accountSettings } = creds @@ -166,11 +153,11 @@ const processMessage = async( const chat: Partial = { id: jidNormalizedUser(getChatId(message.key)) } const isRealMsg = isRealMessage(message, meId) - if(isRealMsg) { + if (isRealMsg) { chat.messages = [{ message }] chat.conversationTimestamp = toNumber(message.messageTimestamp) // only increment unread count if not CIPHERTEXT and from another person - if(shouldIncrementChatUnread(message)) { + if (shouldIncrementChatUnread(message)) { chat.unreadCount = (chat.unreadCount || 0) + 1 } } @@ -179,63 +166,56 @@ const processMessage = async( // unarchive chat if it's a real message, or someone reacted to our message // and we've the unarchive chats setting on - if( - (isRealMsg || content?.reactionMessage?.key?.fromMe) - && accountSettings?.unarchiveChats - ) { + if ((isRealMsg || content?.reactionMessage?.key?.fromMe) && accountSettings?.unarchiveChats) { chat.archived = false chat.readOnly = false } const protocolMsg = content?.protocolMessage - if(protocolMsg) { + if (protocolMsg) { switch (protocolMsg.type) { - case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION: - const histNotification = protocolMsg.historySyncNotification! - const process = shouldProcessHistoryMsg - const isLatest = !creds.processedHistoryMessages?.length + case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION: + const histNotification = protocolMsg.historySyncNotification! + const process = shouldProcessHistoryMsg + const isLatest = !creds.processedHistoryMessages?.length - logger?.info({ - histNotification, - process, - id: message.key.id, - isLatest, - }, 'got history notification') + logger?.info( + { + histNotification, + process, + id: message.key.id, + isLatest + }, + 'got history notification' + ) - if(process) { - if(histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND) { - ev.emit('creds.update', { - processedHistoryMessages: [ - ...(creds.processedHistoryMessages || []), - { key: message.key, messageTimestamp: message.messageTimestamp } - ] + if (process) { + if (histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND) { + ev.emit('creds.update', { + processedHistoryMessages: [ + ...(creds.processedHistoryMessages || []), + { key: message.key, messageTimestamp: message.messageTimestamp } + ] + }) + } + + const data = await downloadAndProcessHistorySyncNotification(histNotification, options) + + ev.emit('messaging-history.set', { + ...data, + isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined, + peerDataRequestSessionId: histNotification.peerDataRequestSessionId }) } - const data = await downloadAndProcessHistorySyncNotification( - histNotification, - options - ) - - ev.emit('messaging-history.set', { - ...data, - isLatest: - histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND - ? isLatest - : undefined, - peerDataRequestSessionId: histNotification.peerDataRequestSessionId - }) - } - - break - case proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE: - const keys = protocolMsg.appStateSyncKeyShare!.keys - if(keys?.length) { - let newAppStateSyncKeyId = '' - await keyStore.transaction( - async() => { + break + case proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE: + const keys = protocolMsg.appStateSyncKeyShare!.keys + if (keys?.length) { + let newAppStateSyncKeyId = '' + await keyStore.transaction(async () => { const newKeys: string[] = [] - for(const { keyData, keyId } of keys) { + for (const { keyData, keyId } of keys) { const strKeyId = Buffer.from(keyId!.keyId!).toString('base64') newKeys.push(strKeyId) @@ -244,65 +224,59 @@ const processMessage = async( newAppStateSyncKeyId = strKeyId } - logger?.info( - { newAppStateSyncKeyId, newKeys }, - 'injecting new app state sync keys' - ) - } - ) + logger?.info({ newAppStateSyncKeyId, newKeys }, 'injecting new app state sync keys') + }) - ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId }) - } else { - logger?.info({ protocolMsg }, 'recv app state sync with 0 keys') - } - - break - case proto.Message.ProtocolMessage.Type.REVOKE: - ev.emit('messages.update', [ - { - key: { - ...message.key, - id: protocolMsg.key!.id - }, - update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key } + ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId }) + } else { + logger?.info({ protocolMsg }, 'recv app state sync with 0 keys') } - ]) - break - case proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING: - Object.assign(chat, { - ephemeralSettingTimestamp: toNumber(message.messageTimestamp), - ephemeralExpiration: protocolMsg.ephemeralExpiration || null - }) - break - case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE: - const response = protocolMsg.peerDataOperationRequestResponseMessage! - if(response) { - placeholderResendCache?.del(response.stanzaId!) - // TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.). - const { peerDataOperationResult } = response - for(const result of peerDataOperationResult!) { - const { placeholderMessageResendResponse: retryResponse } = result - //eslint-disable-next-line max-depth - if(retryResponse) { - const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes!) - // wait till another upsert event is available, don't want it to be part of the PDO response message - setTimeout(() => { - ev.emit('messages.upsert', { - messages: [webMessageInfo], - type: 'notify', - requestId: response.stanzaId! - }) - }, 500) - } - } - } - case proto.Message.ProtocolMessage.Type.MESSAGE_EDIT: - ev.emit( - 'messages.update', - [ + break + case proto.Message.ProtocolMessage.Type.REVOKE: + ev.emit('messages.update', [ { - // flip the sender / fromMe properties because they're in the perspective of the sender + key: { + ...message.key, + id: protocolMsg.key!.id + }, + update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key } + } + ]) + break + case proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING: + Object.assign(chat, { + ephemeralSettingTimestamp: toNumber(message.messageTimestamp), + ephemeralExpiration: protocolMsg.ephemeralExpiration || null + }) + break + case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE: + const response = protocolMsg.peerDataOperationRequestResponseMessage! + if (response) { + placeholderResendCache?.del(response.stanzaId!) + // TODO: IMPLEMENT HISTORY SYNC ETC (sticker uploads etc.). + const { peerDataOperationResult } = response + for (const result of peerDataOperationResult!) { + const { placeholderMessageResendResponse: retryResponse } = result + //eslint-disable-next-line max-depth + if (retryResponse) { + const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes!) + // wait till another upsert event is available, don't want it to be part of the PDO response message + setTimeout(() => { + ev.emit('messages.upsert', { + messages: [webMessageInfo], + type: 'notify', + requestId: response.stanzaId! + }) + }, 500) + } + } + } + + case proto.Message.ProtocolMessage.Type.MESSAGE_EDIT: + ev.emit('messages.update', [ + { + // flip the sender / fromMe properties because they're in the perspective of the sender key: { ...message.key, id: protocolMsg.key?.id }, update: { message: { @@ -315,26 +289,26 @@ const processMessage = async( : message.messageTimestamp } } - ] - ) - break + ]) + break } - } else if(content?.reactionMessage) { + } else if (content?.reactionMessage) { const reaction: proto.IReaction = { ...content.reactionMessage, - key: message.key, + key: message.key } - ev.emit('messages.reaction', [{ - reaction, - key: content.reactionMessage?.key!, - }]) - } else if(message.messageStubType) { + ev.emit('messages.reaction', [ + { + reaction, + key: content.reactionMessage?.key! + } + ]) + } else if (message.messageStubType) { const jid = message.key?.remoteJid! //let actor = whatsappID (message.participant) let participants: string[] - const emitParticipantsUpdate = (action: ParticipantAction) => ( + const emitParticipantsUpdate = (action: ParticipantAction) => ev.emit('group-participants.update', { id: jid, author: message.participant!, participants, action }) - ) const emitGroupUpdate = (update: Partial) => { ev.emit('groups.update', [{ id: jid, ...update, author: message.participant ?? undefined }]) } @@ -346,76 +320,75 @@ const processMessage = async( const participantsIncludesMe = () => participants.find(jid => areJidsSameUser(meId, jid)) switch (message.messageStubType) { - case WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER: - participants = message.messageStubParameters || [] - emitParticipantsUpdate('modify') - break - case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: - case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: - participants = message.messageStubParameters || [] - emitParticipantsUpdate('remove') - // mark the chat read only if you left the group - if(participantsIncludesMe()) { - chat.readOnly = true - } + case WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER: + participants = message.messageStubParameters || [] + emitParticipantsUpdate('modify') + break + case WAMessageStubType.GROUP_PARTICIPANT_LEAVE: + case WAMessageStubType.GROUP_PARTICIPANT_REMOVE: + participants = message.messageStubParameters || [] + emitParticipantsUpdate('remove') + // mark the chat read only if you left the group + if (participantsIncludesMe()) { + chat.readOnly = true + } - break - case WAMessageStubType.GROUP_PARTICIPANT_ADD: - case WAMessageStubType.GROUP_PARTICIPANT_INVITE: - case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: - participants = message.messageStubParameters || [] - if(participantsIncludesMe()) { - chat.readOnly = false - } + break + case WAMessageStubType.GROUP_PARTICIPANT_ADD: + case WAMessageStubType.GROUP_PARTICIPANT_INVITE: + case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN: + participants = message.messageStubParameters || [] + if (participantsIncludesMe()) { + chat.readOnly = false + } - emitParticipantsUpdate('add') - break - case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE: - participants = message.messageStubParameters || [] - emitParticipantsUpdate('demote') - break - case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE: - participants = message.messageStubParameters || [] - emitParticipantsUpdate('promote') - break - case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: - const announceValue = message.messageStubParameters?.[0] - emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' }) - break - case WAMessageStubType.GROUP_CHANGE_RESTRICT: - const restrictValue = message.messageStubParameters?.[0] - emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' }) - break - case WAMessageStubType.GROUP_CHANGE_SUBJECT: - const name = message.messageStubParameters?.[0] - chat.name = name - emitGroupUpdate({ subject: name }) - break - case WAMessageStubType.GROUP_CHANGE_DESCRIPTION: - const description = message.messageStubParameters?.[0] - chat.description = description - emitGroupUpdate({ desc: description }) - break - case WAMessageStubType.GROUP_CHANGE_INVITE_LINK: - const code = message.messageStubParameters?.[0] - emitGroupUpdate({ inviteCode: code }) - break - case WAMessageStubType.GROUP_MEMBER_ADD_MODE: - const memberAddValue = message.messageStubParameters?.[0] - emitGroupUpdate({ memberAddMode: memberAddValue === 'all_member_add' }) - break - case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE: - const approvalMode = message.messageStubParameters?.[0] - emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' }) - break - case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: - const participant = message.messageStubParameters?.[0] as string - const action = message.messageStubParameters?.[1] as RequestJoinAction - const method = message.messageStubParameters?.[2] as RequestJoinMethod - emitGroupRequestJoin(participant, action, method) - break + emitParticipantsUpdate('add') + break + case WAMessageStubType.GROUP_PARTICIPANT_DEMOTE: + participants = message.messageStubParameters || [] + emitParticipantsUpdate('demote') + break + case WAMessageStubType.GROUP_PARTICIPANT_PROMOTE: + participants = message.messageStubParameters || [] + emitParticipantsUpdate('promote') + break + case WAMessageStubType.GROUP_CHANGE_ANNOUNCE: + const announceValue = message.messageStubParameters?.[0] + emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' }) + break + case WAMessageStubType.GROUP_CHANGE_RESTRICT: + const restrictValue = message.messageStubParameters?.[0] + emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' }) + break + case WAMessageStubType.GROUP_CHANGE_SUBJECT: + const name = message.messageStubParameters?.[0] + chat.name = name + emitGroupUpdate({ subject: name }) + break + case WAMessageStubType.GROUP_CHANGE_DESCRIPTION: + const description = message.messageStubParameters?.[0] + chat.description = description + emitGroupUpdate({ desc: description }) + break + case WAMessageStubType.GROUP_CHANGE_INVITE_LINK: + const code = message.messageStubParameters?.[0] + emitGroupUpdate({ inviteCode: code }) + break + case WAMessageStubType.GROUP_MEMBER_ADD_MODE: + const memberAddValue = message.messageStubParameters?.[0] + emitGroupUpdate({ memberAddMode: memberAddValue === 'all_member_add' }) + break + case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE: + const approvalMode = message.messageStubParameters?.[0] + emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' }) + break + case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: + const participant = message.messageStubParameters?.[0] as string + const action = message.messageStubParameters?.[1] as RequestJoinAction + const method = message.messageStubParameters?.[2] as RequestJoinMethod + emitGroupRequestJoin(participant, action, method) + break } - } /* else if(content?.pollUpdateMessage) { const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey! // we need to fetch the poll creation message to get the poll enc key @@ -466,7 +439,7 @@ const processMessage = async( } } */ - if(Object.keys(chat).length > 1) { + if (Object.keys(chat).length > 1) { ev.emit('chats.update', [chat]) } } diff --git a/src/Utils/signal.ts b/src/Utils/signal.ts index 8725399..582d00e 100644 --- a/src/Utils/signal.ts +++ b/src/Utils/signal.ts @@ -1,25 +1,39 @@ import { chunk } from 'lodash' import { KEY_BUNDLE_TYPE } from '../Defaults' import { SignalRepository } from '../Types' -import { AuthenticationCreds, AuthenticationState, KeyPair, SignalIdentity, SignalKeyStore, SignedKeyPair } from '../Types/Auth' -import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, jidDecode, JidWithDevice, S_WHATSAPP_NET } from '../WABinary' +import { + AuthenticationCreds, + AuthenticationState, + KeyPair, + SignalIdentity, + SignalKeyStore, + SignedKeyPair +} from '../Types/Auth' +import { + assertNodeErrorFree, + BinaryNode, + getBinaryNodeChild, + getBinaryNodeChildBuffer, + getBinaryNodeChildren, + getBinaryNodeChildUInt, + jidDecode, + JidWithDevice, + S_WHATSAPP_NET +} from '../WABinary' import { DeviceListData, ParsedDeviceInfo, USyncQueryResultList } from '../WAUSync' import { Curve, generateSignalPubKey } from './crypto' import { encodeBigEndian } from './generics' -export const createSignalIdentity = ( - wid: string, - accountSignatureKey: Uint8Array -): SignalIdentity => { +export const createSignalIdentity = (wid: string, accountSignatureKey: Uint8Array): SignalIdentity => { return { identifier: { name: wid, deviceId: 0 }, identifierKey: generateSignalPubKey(accountSignatureKey) } } -export const getPreKeys = async({ get }: SignalKeyStore, min: number, limit: number) => { +export const getPreKeys = async ({ get }: SignalKeyStore, min: number, limit: number) => { const idList: string[] = [] - for(let id = min; id < limit;id++) { + for (let id = min; id < limit; id++) { idList.push(id.toString()) } @@ -30,9 +44,9 @@ export const generateOrGetPreKeys = (creds: AuthenticationCreds, range: number) const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId const remaining = range - avaliable const lastPreKeyId = creds.nextPreKeyId + remaining - 1 - const newPreKeys: { [id: number]: KeyPair } = { } - if(remaining > 0) { - for(let i = creds.nextPreKeyId;i <= lastPreKeyId;i++) { + const newPreKeys: { [id: number]: KeyPair } = {} + if (remaining > 0) { + for (let i = creds.nextPreKeyId; i <= lastPreKeyId; i++) { newPreKeys[i] = Curve.generateKeyPair() } } @@ -40,46 +54,40 @@ export const generateOrGetPreKeys = (creds: AuthenticationCreds, range: number) return { newPreKeys, lastPreKeyId, - preKeysRange: [creds.firstUnuploadedPreKeyId, range] as const, + preKeysRange: [creds.firstUnuploadedPreKeyId, range] as const } } -export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => ( - { - tag: 'skey', - attrs: { }, - content: [ - { tag: 'id', attrs: { }, content: encodeBigEndian(key.keyId, 3) }, - { tag: 'value', attrs: { }, content: key.keyPair.public }, - { tag: 'signature', attrs: { }, content: key.signature } - ] - } -) +export const xmppSignedPreKey = (key: SignedKeyPair): BinaryNode => ({ + tag: 'skey', + attrs: {}, + content: [ + { tag: 'id', attrs: {}, content: encodeBigEndian(key.keyId, 3) }, + { tag: 'value', attrs: {}, content: key.keyPair.public }, + { tag: 'signature', attrs: {}, content: key.signature } + ] +}) -export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => ( - { - tag: 'key', - attrs: { }, - content: [ - { tag: 'id', attrs: { }, content: encodeBigEndian(id, 3) }, - { tag: 'value', attrs: { }, content: pair.public } - ] - } -) +export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => ({ + tag: 'key', + attrs: {}, + content: [ + { tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) }, + { tag: 'value', attrs: {}, content: pair.public } + ] +}) -export const parseAndInjectE2ESessions = async( - node: BinaryNode, - repository: SignalRepository -) => { - const extractKey = (key: BinaryNode) => ( - key ? ({ - keyId: getBinaryNodeChildUInt(key, 'id', 3)!, - publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!), - signature: getBinaryNodeChildBuffer(key, 'signature')!, - }) : undefined - ) +export const parseAndInjectE2ESessions = async (node: BinaryNode, repository: SignalRepository) => { + const extractKey = (key: BinaryNode) => + key + ? { + keyId: getBinaryNodeChildUInt(key, 'id', 3)!, + publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')!), + signature: getBinaryNodeChildBuffer(key, 'signature')! + } + : undefined const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user') - for(const node of nodes) { + for (const node of nodes) { assertNodeErrorFree(node) } @@ -90,27 +98,25 @@ export const parseAndInjectE2ESessions = async( // It's rare case when you need to E2E sessions for so many users, but it's possible const chunkSize = 100 const chunks = chunk(nodes, chunkSize) - for(const nodesChunk of chunks) { + for (const nodesChunk of chunks) { await Promise.all( - nodesChunk.map( - async node => { - const signedKey = getBinaryNodeChild(node, 'skey')! - const key = getBinaryNodeChild(node, 'key')! - const identity = getBinaryNodeChildBuffer(node, 'identity')! - const jid = node.attrs.jid - const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) + nodesChunk.map(async node => { + const signedKey = getBinaryNodeChild(node, 'skey')! + const key = getBinaryNodeChild(node, 'key')! + const identity = getBinaryNodeChildBuffer(node, 'identity')! + const jid = node.attrs.jid + const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) - await repository.injectE2ESession({ - jid, - session: { - registrationId: registrationId!, - identityKey: generateSignalPubKey(identity), - signedPreKey: extractKey(signedKey)!, - preKey: extractKey(key)! - } - }) - } - ) + await repository.injectE2ESession({ + jid, + session: { + registrationId: registrationId!, + identityKey: generateSignalPubKey(identity), + signedPreKey: extractKey(signedKey)!, + preKey: extractKey(key)! + } + }) + }) ) } } @@ -120,14 +126,13 @@ export const extractDeviceJids = (result: USyncQueryResultList[], myJid: string, const extracted: JidWithDevice[] = [] - - for(const userResult of result) { - const { devices, id } = userResult as { devices: ParsedDeviceInfo, id: string } + for (const userResult of result) { + const { devices, id } = userResult as { devices: ParsedDeviceInfo; id: string } const { user } = jidDecode(id)! const deviceList = devices?.deviceList as DeviceListData[] - if(Array.isArray(deviceList)) { - for(const { id: device, keyIndex } of deviceList) { - if( + if (Array.isArray(deviceList)) { + for (const { id: device, keyIndex } of deviceList) { + if ( (!excludeZeroDevices || device !== 0) && // if zero devices are not-excluded, or device is non zero (myUser !== user || myDevice !== device) && // either different user or if me user, not this device (device === 0 || !!keyIndex) // ensure that "key-index" is specified for "non-zero" devices, produces a bad req otherwise @@ -145,7 +150,7 @@ export const extractDeviceJids = (result: USyncQueryResultList[], myJid: string, * get the next N keys for upload or processing * @param count number of pre-keys to get or generate */ -export const getNextPreKeys = async({ creds, keys }: AuthenticationState, count: number) => { +export const getNextPreKeys = async ({ creds, keys }: AuthenticationState, count: number) => { const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(creds, count) const update: Partial = { @@ -160,7 +165,7 @@ export const getNextPreKeys = async({ creds, keys }: AuthenticationState, count: return { update, preKeys } } -export const getNextPreKeysNode = async(state: AuthenticationState, count: number) => { +export const getNextPreKeysNode = async (state: AuthenticationState, count: number) => { const { creds } = state const { update, preKeys } = await getNextPreKeys(state, count) @@ -169,13 +174,13 @@ export const getNextPreKeysNode = async(state: AuthenticationState, count: numbe attrs: { xmlns: 'encrypt', type: 'set', - to: S_WHATSAPP_NET, + to: S_WHATSAPP_NET }, content: [ - { tag: 'registration', attrs: { }, content: encodeBigEndian(creds.registrationId) }, - { tag: 'type', attrs: { }, content: KEY_BUNDLE_TYPE }, - { tag: 'identity', attrs: { }, content: creds.signedIdentityKey.public }, - { tag: 'list', attrs: { }, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) }, + { tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) }, + { tag: 'type', attrs: {}, content: KEY_BUNDLE_TYPE }, + { tag: 'identity', attrs: {}, content: creds.signedIdentityKey.public }, + { tag: 'list', attrs: {}, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) }, xmppSignedPreKey(creds.signedPreKey) ] } diff --git a/src/Utils/use-multi-file-auth-state.ts b/src/Utils/use-multi-file-auth-state.ts index 94d1712..9aefd18 100644 --- a/src/Utils/use-multi-file-auth-state.ts +++ b/src/Utils/use-multi-file-auth-state.ts @@ -15,7 +15,7 @@ const fileLocks = new Map() // Get or create a mutex for a specific file path const getFileLock = (path: string): Mutex => { let mutex = fileLocks.get(path) - if(!mutex) { + if (!mutex) { mutex = new Mutex() fileLocks.set(path, mutex) } @@ -30,13 +30,15 @@ const getFileLock = (path: string): Mutex => { * Again, I wouldn't endorse this for any production level use other than perhaps a bot. * Would recommend writing an auth state for use with a proper SQL or No-SQL DB * */ -export const useMultiFileAuthState = async(folder: string): Promise<{ state: AuthenticationState, saveCreds: () => Promise }> => { +export const useMultiFileAuthState = async ( + folder: string +): Promise<{ state: AuthenticationState; saveCreds: () => Promise }> => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const writeData = async(data: any, file: string) => { + const writeData = async (data: any, file: string) => { const filePath = join(folder, fixFileName(file)!) const mutex = getFileLock(filePath) - return mutex.acquire().then(async(release) => { + return mutex.acquire().then(async release => { try { await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer)) } finally { @@ -45,12 +47,12 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut }) } - const readData = async(file: string) => { + const readData = async (file: string) => { try { const filePath = join(folder, fixFileName(file)!) const mutex = getFileLock(filePath) - return await mutex.acquire().then(async(release) => { + return await mutex.acquire().then(async release => { try { const data = await readFile(filePath, { encoding: 'utf-8' }) return JSON.parse(data, BufferJSON.reviver) @@ -58,32 +60,33 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut release() } }) - } catch(error) { + } catch (error) { return null } } - const removeData = async(file: string) => { + const removeData = async (file: string) => { try { const filePath = join(folder, fixFileName(file)!) const mutex = getFileLock(filePath) - return mutex.acquire().then(async(release) => { + return mutex.acquire().then(async release => { try { await unlink(filePath) - } catch{ + } catch { } finally { release() } }) - } catch{ - } + } catch {} } - const folderInfo = await stat(folder).catch(() => { }) - if(folderInfo) { - if(!folderInfo.isDirectory()) { - throw new Error(`found something that is not a directory at ${folder}, either delete it or specify a different location`) + const folderInfo = await stat(folder).catch(() => {}) + if (folderInfo) { + if (!folderInfo.isDirectory()) { + throw new Error( + `found something that is not a directory at ${folder}, either delete it or specify a different location` + ) } } else { await mkdir(folder, { recursive: true }) @@ -91,33 +94,31 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut const fixFileName = (file?: string) => file?.replace(/\//g, '__')?.replace(/:/g, '-') - const creds: AuthenticationCreds = await readData('creds.json') || initAuthCreds() + const creds: AuthenticationCreds = (await readData('creds.json')) || initAuthCreds() return { state: { creds, keys: { - get: async(type, ids) => { - const data: { [_: string]: SignalDataTypeMap[typeof type] } = { } + get: async (type, ids) => { + const data: { [_: string]: SignalDataTypeMap[typeof type] } = {} await Promise.all( - ids.map( - async id => { - let value = await readData(`${type}-${id}.json`) - if(type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.fromObject(value) - } - - data[id] = value + ids.map(async id => { + let value = await readData(`${type}-${id}.json`) + if (type === 'app-state-sync-key' && value) { + value = proto.Message.AppStateSyncKeyData.fromObject(value) } - ) + + data[id] = value + }) ) return data }, - set: async(data) => { + set: async data => { const tasks: Promise[] = [] - for(const category in data) { - for(const id in data[category]) { + for (const category in data) { + for (const id in data[category]) { const value = data[category][id] const file = `${category}-${id}.json` tasks.push(value ? writeData(value, file) : removeData(file)) @@ -128,8 +129,8 @@ export const useMultiFileAuthState = async(folder: string): Promise<{ state: Aut } } }, - saveCreds: async() => { + saveCreds: async () => { return writeData(creds, 'creds.json') } } -} \ No newline at end of file +} diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index 9dd25aa..9ae2075 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -13,7 +13,7 @@ const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => { appVersion: { primary: config.version[0], secondary: config.version[1], - tertiary: config.version[2], + tertiary: config.version[2] }, platform: proto.ClientPayload.UserAgent.Platform.WEB, releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE, @@ -23,30 +23,29 @@ const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => { localeLanguageIso6391: 'en', mnc: '000', mcc: '000', - localeCountryIso31661Alpha2: config.countryCode, + localeCountryIso31661Alpha2: config.countryCode } } const PLATFORM_MAP = { 'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN, - 'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN32 + Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32 } const getWebInfo = (config: SocketConfig): proto.ClientPayload.IWebInfo => { let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER - if(config.syncFullHistory && PLATFORM_MAP[config.browser[0]]) { + if (config.syncFullHistory && PLATFORM_MAP[config.browser[0]]) { webSubPlatform = PLATFORM_MAP[config.browser[0]] } return { webSubPlatform } } - const getClientPayload = (config: SocketConfig) => { const payload: proto.IClientPayload = { connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN, connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED, - userAgent: getUserAgent(config), + userAgent: getUserAgent(config) } payload.webInfo = getWebInfo(config) @@ -54,7 +53,6 @@ const getClientPayload = (config: SocketConfig) => { return payload } - export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => { const { user, device } = jidDecode(userJid)! const payload: proto.IClientPayload = { @@ -62,7 +60,7 @@ export const generateLoginNode = (userJid: string, config: SocketConfig): proto. passive: false, pull: true, username: +user, - device: device, + device: device } return proto.ClientPayload.fromObject(payload) } @@ -85,7 +83,7 @@ export const generateRegistrationNode = ( const companion: proto.IDeviceProps = { os: config.browser[0], platformType: getPlatformType(config.browser[1]), - requireFullSync: config.syncFullHistory, + requireFullSync: config.syncFullHistory } const companionProto = proto.DeviceProps.encode(companion).finish() @@ -102,8 +100,8 @@ export const generateRegistrationNode = ( eIdent: signedIdentityKey.public, eSkeyId: encodeBigEndian(signedPreKey.keyId, 3), eSkeyVal: signedPreKey.keyPair.public, - eSkeySig: signedPreKey.signature, - }, + eSkeySig: signedPreKey.signature + } } return proto.ClientPayload.fromObject(registerPayload) @@ -111,7 +109,11 @@ export const generateRegistrationNode = ( export const configureSuccessfulPairing = ( stanza: BinaryNode, - { advSecretKey, signedIdentityKey, signalIdentities }: Pick + { + advSecretKey, + signedIdentityKey, + signalIdentities + }: Pick ) => { const msgId = stanza.attrs.id @@ -122,7 +124,7 @@ export const configureSuccessfulPairing = ( const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device') const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz') - if(!deviceIdentityNode || !deviceNode) { + if (!deviceIdentityNode || !deviceNode) { throw new Boom('Missing device-identity or device in pair success node', { data: stanza }) } @@ -132,20 +134,20 @@ export const configureSuccessfulPairing = ( const { details, hmac } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer) // check HMAC matches const advSign = hmacSign(details!, Buffer.from(advSecretKey, 'base64')) - if(Buffer.compare(hmac!, advSign) !== 0) { + if (Buffer.compare(hmac!, advSign) !== 0) { throw new Boom('Invalid account signature') } const account = proto.ADVSignedDeviceIdentity.decode(details!) const { accountSignatureKey, accountSignature, details: deviceDetails } = account // verify the device signature matches - const accountMsg = Buffer.concat([ Buffer.from([6, 0]), deviceDetails!, signedIdentityKey.public ]) - if(!Curve.verify(accountSignatureKey!, accountMsg, accountSignature!)) { + const accountMsg = Buffer.concat([Buffer.from([6, 0]), deviceDetails!, signedIdentityKey.public]) + if (!Curve.verify(accountSignatureKey!, accountMsg, accountSignature!)) { throw new Boom('Failed to verify account signature') } // sign the details with our identity key - const deviceMsg = Buffer.concat([ Buffer.from([6, 1]), deviceDetails!, signedIdentityKey.public, accountSignatureKey! ]) + const deviceMsg = Buffer.concat([Buffer.from([6, 1]), deviceDetails!, signedIdentityKey.public, accountSignatureKey!]) account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg) const identity = createSignalIdentity(jid, accountSignatureKey!) @@ -158,12 +160,12 @@ export const configureSuccessfulPairing = ( attrs: { to: S_WHATSAPP_NET, type: 'result', - id: msgId, + id: msgId }, content: [ { tag: 'pair-device-sign', - attrs: { }, + attrs: {}, content: [ { tag: 'device-identity', @@ -178,10 +180,7 @@ export const configureSuccessfulPairing = ( const authUpdate: Partial = { account, me: { id: jid, name: bizName }, - signalIdentities: [ - ...(signalIdentities || []), - identity - ], + signalIdentities: [...(signalIdentities || []), identity], platform: platformNode?.attrs.name } @@ -191,18 +190,13 @@ export const configureSuccessfulPairing = ( } } -export const encodeSignedDeviceIdentity = ( - account: proto.IADVSignedDeviceIdentity, - includeSignatureKey: boolean -) => { +export const encodeSignedDeviceIdentity = (account: proto.IADVSignedDeviceIdentity, includeSignatureKey: boolean) => { account = { ...account } // set to null if we are not to include the signature key // or if we are including the signature key but it is empty - if(!includeSignatureKey || !account.accountSignatureKey?.length) { + if (!includeSignatureKey || !account.accountSignatureKey?.length) { account.accountSignatureKey = null } - return proto.ADVSignedDeviceIdentity - .encode(account) - .finish() + return proto.ADVSignedDeviceIdentity.encode(account).finish() } diff --git a/src/WABinary/constants.ts b/src/WABinary/constants.ts index e403b26..7c4d83c 100644 --- a/src/WABinary/constants.ts +++ b/src/WABinary/constants.ts @@ -1,4 +1,3 @@ - export const TAGS = { LIST_EMPTY: 0, DICTIONARY_0: 236, @@ -19,24 +18,1286 @@ export const TAGS = { STREAM_END: 2 } export const DOUBLE_BYTE_TOKENS = [ - ['media-for1-1.cdn.whatsapp.net', 'relay', 'media-gru2-2.cdn.whatsapp.net', 'uncompressed', 'medium', 'voip_settings', 'device', 'reason', 'media-lim1-1.cdn.whatsapp.net', 'media-qro1-2.cdn.whatsapp.net', 'media-gru1-2.cdn.whatsapp.net', 'action', 'features', 'media-gru2-1.cdn.whatsapp.net', 'media-gru1-1.cdn.whatsapp.net', 'media-otp1-1.cdn.whatsapp.net', 'kyc-id', 'priority', 'phash', 'mute', 'token', '100', 'media-qro1-1.cdn.whatsapp.net', 'none', 'media-mrs2-2.cdn.whatsapp.net', 'sign_credential', '03', 'media-mrs2-1.cdn.whatsapp.net', 'protocol', 'timezone', 'transport', 'eph_setting', '1080', 'original_dimensions', 'media-frx5-1.cdn.whatsapp.net', 'background', 'disable', 'original_image_url', '5', 'transaction-id', 'direct_path', '103', 'appointment_only', 'request_image_url', 'peer_pid', 'address', '105', '104', '102', 'media-cdt1-1.cdn.whatsapp.net', '101', '109', '110', '106', 'background_location', 'v_id', 'sync', 'status-old', '111', '107', 'ppic', 'media-scl2-1.cdn.whatsapp.net', 'business_profile', '108', 'invite', '04', 'audio_duration', 'media-mct1-1.cdn.whatsapp.net', 'media-cdg2-1.cdn.whatsapp.net', 'media-los2-1.cdn.whatsapp.net', 'invis', 'net', 'voip_payload_type', 'status-revoke-delay', '404', 'state', 'use_correct_order_for_hmac_sha1', 'ver', 'media-mad1-1.cdn.whatsapp.net', 'order', '540', 'skey', 'blinded_credential', 'android', 'contact_remove', 'enable_downlink_relay_latency_only', 'duration', 'enable_vid_one_way_codec_nego', '6', 'media-sof1-1.cdn.whatsapp.net', 'accept', 'all', 'signed_credential', 'media-atl3-1.cdn.whatsapp.net', 'media-lhr8-1.cdn.whatsapp.net', 'website', '05', 'latitude', 'media-dfw5-1.cdn.whatsapp.net', 'forbidden', 'enable_audio_piggyback_network_mtu_fix', 'media-dfw5-2.cdn.whatsapp.net', 'note.m4r', 'media-atl3-2.cdn.whatsapp.net', 'jb_nack_discard_count_fix', 'longitude', 'Opening.m4r', 'media-arn2-1.cdn.whatsapp.net', 'email', 'timestamp', 'admin', 'media-pmo1-1.cdn.whatsapp.net', 'America/Sao_Paulo', 'contact_add', 'media-sin6-1.cdn.whatsapp.net', 'interactive', '8000', 'acs_public_key', 'sigquit_anr_detector_release_rollover_percent', 'media.fmed1-2.fna.whatsapp.net', 'groupadd', 'enabled_for_video_upgrade', 'latency_update_threshold', 'media-frt3-2.cdn.whatsapp.net', 'calls_row_constraint_layout', 'media.fgbb2-1.fna.whatsapp.net', 'mms4_media_retry_notification_encryption_enabled', 'timeout', 'media-sin6-3.cdn.whatsapp.net', 'audio_nack_jitter_multiplier', 'jb_discard_count_adjust_pct_rc', 'audio_reserve_bps', 'delta', 'account_sync', 'default', 'media.fjed4-6.fna.whatsapp.net', '06', 'lock_video_orientation', 'media-frt3-1.cdn.whatsapp.net', 'w:g2', 'media-sin6-2.cdn.whatsapp.net', 'audio_nack_algo_mask', 'media.fgbb2-2.fna.whatsapp.net', 'media.fmed1-1.fna.whatsapp.net', 'cond_range_target_bitrate', 'mms4_server_error_receipt_encryption_enabled', 'vid_rc_dyn', 'fri', 'cart_v1_1_order_message_changes_enabled', 'reg_push', 'jb_hist_deposit_value', 'privatestats', 'media.fist7-2.fna.whatsapp.net', 'thu', 'jb_discard_count_adjust_pct', 'mon', 'group_call_video_maximization_enabled', 'mms_cat_v1_forward_hot_override_enabled', 'audio_nack_new_rtt', 'media.fsub2-3.fna.whatsapp.net', 'media_upload_aggressive_retry_exponential_backoff_enabled', 'tue', 'wed', 'media.fruh4-2.fna.whatsapp.net', 'audio_nack_max_seq_req', 'max_rtp_audio_packet_resends', 'jb_hist_max_cdf_value', '07', 'audio_nack_max_jb_delay', 'mms_forward_partially_downloaded_video', 'media-lcy1-1.cdn.whatsapp.net', 'resume', 'jb_inband_fec_aware', 'new_commerce_entry_point_enabled', '480', 'payments_upi_generate_qr_amount_limit', 'sigquit_anr_detector_rollover_percent', 'media.fsdu2-1.fna.whatsapp.net', 'fbns', 'aud_pkt_reorder_pct', 'dec', 'stop_probing_before_accept_send', 'media_upload_max_aggressive_retries', 'edit_business_profile_new_mode_enabled', 'media.fhex4-1.fna.whatsapp.net', 'media.fjed4-3.fna.whatsapp.net', 'sigquit_anr_detector_64bit_rollover_percent', 'cond_range_ema_jb_last_delay', 'watls_enable_early_data_http_get', 'media.fsdu2-2.fna.whatsapp.net', 'message_qr_disambiguation_enabled', 'media-mxp1-1.cdn.whatsapp.net', 'sat', 'vertical', 'media.fruh4-5.fna.whatsapp.net', '200', 'media-sof1-2.cdn.whatsapp.net', '-1', 'height', 'product_catalog_hide_show_items_enabled', 'deep_copy_frm_last', 'tsoffline', 'vp8/h.264', 'media.fgye5-3.fna.whatsapp.net', 'media.ftuc1-2.fna.whatsapp.net', 'smb_upsell_chat_banner_enabled', 'canonical', '08', '9', '.', 'media.fgyd4-4.fna.whatsapp.net', 'media.fsti4-1.fna.whatsapp.net', 'mms_vcache_aggregation_enabled', 'mms_hot_content_timespan_in_seconds', 'nse_ver', 'rte', 'third_party_sticker_web_sync', 'cond_range_target_total_bitrate', 'media_upload_aggressive_retry_enabled', 'instrument_spam_report_enabled', 'disable_reconnect_tone', 'move_media_folder_from_sister_app', 'one_tap_calling_in_group_chat_size', '10', 'storage_mgmt_banner_threshold_mb', 'enable_backup_passive_mode', 'sharechat_inline_player_enabled', 'media.fcnq2-1.fna.whatsapp.net', 'media.fhex4-2.fna.whatsapp.net', 'media.fist6-3.fna.whatsapp.net', 'ephemeral_drop_column_stage', 'reconnecting_after_network_change_threshold_ms', 'media-lhr8-2.cdn.whatsapp.net', 'cond_jb_last_delay_ema_alpha', 'entry_point_block_logging_enabled', 'critical_event_upload_log_config', 'respect_initial_bitrate_estimate', 'smaller_image_thumbs_status_enabled', 'media.fbtz1-4.fna.whatsapp.net', 'media.fjed4-1.fna.whatsapp.net', 'width', '720', 'enable_frame_dropper', 'enable_one_side_mode', 'urn:xmpp:whatsapp:dirty', 'new_sticker_animation_behavior_v2', 'media.flim3-2.fna.whatsapp.net', 'media.fuio6-2.fna.whatsapp.net', 'skip_forced_signaling', 'dleq_proof', 'status_video_max_bitrate', 'lazy_send_probing_req', 'enhanced_storage_management', 'android_privatestats_endpoint_dit_enabled', 'media.fscl13-2.fna.whatsapp.net', 'video_duration'], - ['group_call_discoverability_enabled', 'media.faep9-2.fna.whatsapp.net', 'msgr', 'bloks_loggedin_access_app_id', 'db_status_migration_step', 'watls_prefer_ip6', 'jabber:iq:privacy', '68', 'media.fsaw1-11.fna.whatsapp.net', 'mms4_media_conn_persist_enabled', 'animated_stickers_thread_clean_up', 'media.fcgk3-2.fna.whatsapp.net', 'media.fcgk4-6.fna.whatsapp.net', 'media.fgye5-2.fna.whatsapp.net', 'media.flpb1-1.fna.whatsapp.net', 'media.fsub2-1.fna.whatsapp.net', 'media.fuio6-3.fna.whatsapp.net', 'not-allowed', 'partial_pjpeg_bw_threshold', 'cap_estimated_bitrate', 'mms_chatd_resume_check_over_thrift', 'smb_upsell_business_profile_enabled', 'product_catalog_webclient', 'groups', 'sigquit_anr_detector_release_updated_rollout', 'syncd_key_rotation_enabled', 'media.fdmm2-1.fna.whatsapp.net', 'media-hou1-1.cdn.whatsapp.net', 'remove_old_chat_notifications', 'smb_biztools_deeplink_enabled', 'use_downloadable_filters_int', 'group_qr_codes_enabled', 'max_receipt_processing_time', 'optimistic_image_processing_enabled', 'smaller_video_thumbs_status_enabled', 'watls_early_data', 'reconnecting_before_relay_failover_threshold_ms', 'cond_range_packet_loss_pct', 'groups_privacy_blacklist', 'status-revoke-drop', 'stickers_animated_thumbnail_download', 'dedupe_transcode_shared_images', 'dedupe_transcode_shared_videos', 'media.fcnq2-2.fna.whatsapp.net', 'media.fgyd4-1.fna.whatsapp.net', 'media.fist7-1.fna.whatsapp.net', 'media.flim3-3.fna.whatsapp.net', 'add_contact_by_qr_enabled', 'https://faq.whatsapp.com/payments', 'multicast_limit_global', 'sticker_notification_preview', 'smb_better_catalog_list_adapters_enabled', 'bloks_use_minscript_android', 'pen_smoothing_enabled', 'media.fcgk4-5.fna.whatsapp.net', 'media.fevn1-3.fna.whatsapp.net', 'media.fpoj7-1.fna.whatsapp.net', 'media-arn2-2.cdn.whatsapp.net', 'reconnecting_before_network_change_threshold_ms', 'android_media_use_fresco_for_gifs', 'cond_in_congestion', 'status_image_max_edge', 'sticker_search_enabled', 'starred_stickers_web_sync', 'db_blank_me_jid_migration_step', 'media.fist6-2.fna.whatsapp.net', 'media.ftuc1-1.fna.whatsapp.net', '09', 'anr_fast_logs_upload_rollout', 'camera_core_integration_enabled', '11', 'third_party_sticker_caching', 'thread_dump_contact_support', 'wam_privatestats_enabled', 'vcard_as_document_size_kb', 'maxfpp', 'fbip', 'ephemeral_allow_group_members', 'media-bom1-2.cdn.whatsapp.net', 'media-xsp1-1.cdn.whatsapp.net', 'disable_prewarm', 'frequently_forwarded_max', 'media.fbtz1-5.fna.whatsapp.net', 'media.fevn7-1.fna.whatsapp.net', 'media.fgyd4-2.fna.whatsapp.net', 'sticker_tray_animation_fully_visible_items', 'green_alert_banner_duration', 'reconnecting_after_p2p_failover_threshold_ms', 'connected', 'share_biz_vcard_enabled', 'stickers_animation', '0a', '1200', 'WhatsApp', 'group_description_length', 'p_v_id', 'payments_upi_intent_transaction_limit', 'frequently_forwarded_messages', 'media-xsp1-2.cdn.whatsapp.net', 'media.faep8-1.fna.whatsapp.net', 'media.faep8-2.fna.whatsapp.net', 'media.faep9-1.fna.whatsapp.net', 'media.fdmm2-2.fna.whatsapp.net', 'media.fgzt3-1.fna.whatsapp.net', 'media.flim4-2.fna.whatsapp.net', 'media.frao1-1.fna.whatsapp.net', 'media.fscl9-2.fna.whatsapp.net', 'media.fsub2-2.fna.whatsapp.net', 'superadmin', 'media.fbog10-1.fna.whatsapp.net', 'media.fcgh28-1.fna.whatsapp.net', 'media.fjdo10-1.fna.whatsapp.net', 'third_party_animated_sticker_import', 'delay_fec', 'attachment_picker_refresh', 'android_linked_devices_re_auth_enabled', 'rc_dyn', 'green_alert_block_jitter', 'add_contact_logging_enabled', 'biz_message_logging_enabled', 'conversation_media_preview_v2', 'media-jnb1-1.cdn.whatsapp.net', 'ab_key', 'media.fcgk4-2.fna.whatsapp.net', 'media.fevn1-1.fna.whatsapp.net', 'media.fist6-1.fna.whatsapp.net', 'media.fruh4-4.fna.whatsapp.net', 'media.fsti4-2.fna.whatsapp.net', 'mms_vcard_autodownload_size_kb', 'watls_enabled', 'notif_ch_override_off', 'media.fsaw1-14.fna.whatsapp.net', 'media.fscl13-1.fna.whatsapp.net', 'db_group_participant_migration_step', '1020', 'cond_range_sterm_rtt', 'invites_logging_enabled', 'triggered_block_enabled', 'group_call_max_participants', 'media-iad3-1.cdn.whatsapp.net', 'product_catalog_open_deeplink', 'shops_required_tos_version', 'image_max_kbytes', 'cond_low_quality_vid_mode', 'db_receipt_migration_step', 'jb_early_prob_hist_shrink', 'media.fdmm2-3.fna.whatsapp.net', 'media.fdmm2-4.fna.whatsapp.net', 'media.fruh4-1.fna.whatsapp.net', 'media.fsaw2-2.fna.whatsapp.net', 'remove_geolocation_videos', 'new_animation_behavior', 'fieldstats_beacon_chance', '403', 'authkey_reset_on_ban', 'continuous_ptt_playback', 'reconnecting_after_relay_failover_threshold_ms', 'false', 'group', 'sun', 'conversation_swipe_to_reply', 'ephemeral_messages_setting', 'smaller_video_thumbs_enabled', 'md_device_sync_enabled', 'bloks_shops_pdp_url_regex', 'lasso_integration_enabled', 'media-bom1-1.cdn.whatsapp.net', 'new_backup_format_enabled', '256', 'media.faep6-1.fna.whatsapp.net', 'media.fasr1-1.fna.whatsapp.net', 'media.fbtz1-7.fna.whatsapp.net', 'media.fesb4-1.fna.whatsapp.net', 'media.fjdo1-2.fna.whatsapp.net', 'media.frba2-1.fna.whatsapp.net', 'watls_no_dns', '600', 'db_broadcast_me_jid_migration_step', 'new_wam_runtime_enabled', 'group_update', 'enhanced_block_enabled', 'sync_wifi_threshold_kb', 'mms_download_nc_cat', 'bloks_minification_enabled', 'ephemeral_messages_enabled', 'reject', 'voip_outgoing_xml_signaling', 'creator', 'dl_bw', 'payments_request_messages', 'target_bitrate', 'bloks_rendercore_enabled', 'media-hbe1-1.cdn.whatsapp.net', 'media-hel3-1.cdn.whatsapp.net', 'media-kut2-2.cdn.whatsapp.net', 'media-lax3-1.cdn.whatsapp.net', 'media-lax3-2.cdn.whatsapp.net', 'sticker_pack_deeplink_enabled', 'hq_image_bw_threshold', 'status_info', 'voip', 'dedupe_transcode_videos', 'grp_uii_cleanup', 'linked_device_max_count', 'media.flim1-1.fna.whatsapp.net', 'media.fsaw2-1.fna.whatsapp.net', 'reconnecting_after_call_active_threshold_ms', '1140', 'catalog_pdp_new_design', 'media.fbtz1-10.fna.whatsapp.net', 'media.fsaw1-15.fna.whatsapp.net', '0b', 'consumer_rc_provider', 'mms_async_fast_forward_ttl', 'jb_eff_size_fix', 'voip_incoming_xml_signaling', 'media_provider_share_by_uuid', 'suspicious_links', 'dedupe_transcode_images', 'green_alert_modal_start', 'media-cgk1-1.cdn.whatsapp.net', 'media-lga3-1.cdn.whatsapp.net', 'template_doc_mime_types', 'important_messages', 'user_add', 'vcard_max_size_kb', 'media.fada2-1.fna.whatsapp.net', 'media.fbog2-5.fna.whatsapp.net', 'media.fbtz1-3.fna.whatsapp.net', 'media.fcgk3-1.fna.whatsapp.net', 'media.fcgk7-1.fna.whatsapp.net', 'media.flim1-3.fna.whatsapp.net', 'media.fscl9-1.fna.whatsapp.net', 'ctwa_context_enterprise_enabled', 'media.fsaw1-13.fna.whatsapp.net', 'media.fuio11-2.fna.whatsapp.net', 'status_collapse_muted', 'db_migration_level_force', 'recent_stickers_web_sync', 'bloks_session_state', 'bloks_shops_enabled', 'green_alert_setting_deep_links_enabled', 'restrict_groups', 'battery', 'green_alert_block_start', 'refresh', 'ctwa_context_enabled', 'md_messaging_enabled', 'status_image_quality', 'md_blocklist_v2_server', 'media-del1-1.cdn.whatsapp.net', '13', 'userrate', 'a_v_id', 'cond_rtt_ema_alpha', 'invalid'], - ['media.fada1-1.fna.whatsapp.net', 'media.fadb3-2.fna.whatsapp.net', 'media.fbhz2-1.fna.whatsapp.net', 'media.fcor2-1.fna.whatsapp.net', 'media.fjed4-2.fna.whatsapp.net', 'media.flhe4-1.fna.whatsapp.net', 'media.frak1-2.fna.whatsapp.net', 'media.fsub6-3.fna.whatsapp.net', 'media.fsub6-7.fna.whatsapp.net', 'media.fvvi1-1.fna.whatsapp.net', 'search_v5_eligible', 'wam_real_time_enabled', 'report_disk_event', 'max_tx_rott_based_bitrate', 'product', 'media.fjdo10-2.fna.whatsapp.net', 'video_frame_crc_sample_interval', 'media_max_autodownload', '15', 'h.264', 'wam_privatestats_buffer_count', 'md_phash_v2_enabled', 'account_transfer_enabled', 'business_product_catalog', 'enable_non_dyn_codec_param_fix', 'is_user_under_epd_jurisdiction', 'media.fbog2-4.fna.whatsapp.net', 'media.fbtz1-2.fna.whatsapp.net', 'media.fcfc1-1.fna.whatsapp.net', 'media.fjed4-5.fna.whatsapp.net', 'media.flhe4-2.fna.whatsapp.net', 'media.flim1-2.fna.whatsapp.net', 'media.flos5-1.fna.whatsapp.net', 'android_key_store_auth_ver', '010', 'anr_process_monitor', 'delete_old_auth_key', 'media.fcor10-3.fna.whatsapp.net', 'storage_usage_enabled', 'android_camera2_support_level', 'dirty', 'consumer_content_provider', 'status_video_max_duration', '0c', 'bloks_cache_enabled', 'media.fadb2-2.fna.whatsapp.net', 'media.fbko1-1.fna.whatsapp.net', 'media.fbtz1-9.fna.whatsapp.net', 'media.fcgk4-4.fna.whatsapp.net', 'media.fesb4-2.fna.whatsapp.net', 'media.fevn1-2.fna.whatsapp.net', 'media.fist2-4.fna.whatsapp.net', 'media.fjdo1-1.fna.whatsapp.net', 'media.fruh4-6.fna.whatsapp.net', 'media.fsrg5-1.fna.whatsapp.net', 'media.fsub6-6.fna.whatsapp.net', 'minfpp', '5000', 'locales', 'video_max_bitrate', 'use_new_auth_key', 'bloks_http_enabled', 'heartbeat_interval', 'media.fbog11-1.fna.whatsapp.net', 'ephemeral_group_query_ts', 'fec_nack', 'search_in_storage_usage', 'c', 'media-amt2-1.cdn.whatsapp.net', 'linked_devices_ui_enabled', '14', 'async_data_load_on_startup', 'voip_incoming_xml_ack', '16', 'db_migration_step', 'init_bwe', 'max_participants', 'wam_buffer_count', 'media.fada2-2.fna.whatsapp.net', 'media.fadb3-1.fna.whatsapp.net', 'media.fcor2-2.fna.whatsapp.net', 'media.fdiy1-2.fna.whatsapp.net', 'media.frba3-2.fna.whatsapp.net', 'media.fsaw2-3.fna.whatsapp.net', '1280', 'status_grid_enabled', 'w:biz', 'product_catalog_deeplink', 'media.fgye10-2.fna.whatsapp.net', 'media.fuio11-1.fna.whatsapp.net', 'optimistic_upload', 'work_manager_init', 'lc', 'catalog_message', 'cond_net_medium', 'enable_periodical_aud_rr_processing', 'cond_range_ema_rtt', 'media-tir2-1.cdn.whatsapp.net', 'frame_ms', 'group_invite_sending', 'payments_web_enabled', 'wallpapers_v2', '0d', 'browser', 'hq_image_max_edge', 'image_edit_zoom', 'linked_devices_re_auth_enabled', 'media.faly3-2.fna.whatsapp.net', 'media.fdoh5-3.fna.whatsapp.net', 'media.fesb3-1.fna.whatsapp.net', 'media.fknu1-1.fna.whatsapp.net', 'media.fmex3-1.fna.whatsapp.net', 'media.fruh4-3.fna.whatsapp.net', '255', 'web_upgrade_to_md_modal', 'audio_piggyback_timeout_msec', 'enable_audio_oob_fec_feature', 'from_ip', 'image_max_edge', 'message_qr_enabled', 'powersave', 'receipt_pre_acking', 'video_max_edge', 'full', '011', '012', 'enable_audio_oob_fec_for_sender', 'md_voip_enabled', 'enable_privatestats', 'max_fec_ratio', 'payments_cs_faq_url', 'media-xsp1-3.cdn.whatsapp.net', 'hq_image_quality', 'media.fasr1-2.fna.whatsapp.net', 'media.fbog3-1.fna.whatsapp.net', 'media.ffjr1-6.fna.whatsapp.net', 'media.fist2-3.fna.whatsapp.net', 'media.flim4-3.fna.whatsapp.net', 'media.fpbc2-4.fna.whatsapp.net', 'media.fpku1-1.fna.whatsapp.net', 'media.frba1-1.fna.whatsapp.net', 'media.fudi1-1.fna.whatsapp.net', 'media.fvvi1-2.fna.whatsapp.net', 'gcm_fg_service', 'enable_dec_ltr_size_check', 'clear', 'lg', 'media.fgru11-1.fna.whatsapp.net', '18', 'media-lga3-2.cdn.whatsapp.net', 'pkey', '0e', 'max_subject', 'cond_range_lterm_rtt', 'announcement_groups', 'biz_profile_options', 's_t', 'media.fabv2-1.fna.whatsapp.net', 'media.fcai3-1.fna.whatsapp.net', 'media.fcgh1-1.fna.whatsapp.net', 'media.fctg1-4.fna.whatsapp.net', 'media.fdiy1-1.fna.whatsapp.net', 'media.fisb4-1.fna.whatsapp.net', 'media.fpku1-2.fna.whatsapp.net', 'media.fros9-1.fna.whatsapp.net', 'status_v3_text', 'usync_sidelist', '17', 'announcement', '...', 'md_group_notification', '0f', 'animated_pack_in_store', '013', 'America/Mexico_City', '1260', 'media-ams4-1.cdn.whatsapp.net', 'media-cgk1-2.cdn.whatsapp.net', 'media-cpt1-1.cdn.whatsapp.net', 'media-maa2-1.cdn.whatsapp.net', 'media.fgye10-1.fna.whatsapp.net', 'e', 'catalog_cart', 'hfm_string_changes', 'init_bitrate', 'packless_hsm', 'group_info', 'America/Belem', '50', '960', 'cond_range_bwe', 'decode', 'encode', 'media.fada1-8.fna.whatsapp.net', 'media.fadb1-2.fna.whatsapp.net', 'media.fasu6-1.fna.whatsapp.net', 'media.fbog4-1.fna.whatsapp.net', 'media.fcgk9-2.fna.whatsapp.net', 'media.fdoh5-2.fna.whatsapp.net', 'media.ffjr1-2.fna.whatsapp.net', 'media.fgua1-1.fna.whatsapp.net', 'media.fgye1-1.fna.whatsapp.net', 'media.fist1-4.fna.whatsapp.net', 'media.fpbc2-2.fna.whatsapp.net', 'media.fres2-1.fna.whatsapp.net', 'media.fsdq1-2.fna.whatsapp.net', 'media.fsub6-5.fna.whatsapp.net', 'profilo_enabled', 'template_hsm', 'use_disorder_prefetching_timer', 'video_codec_priority', 'vpx_max_qp', 'ptt_reduce_recording_delay', '25', 'iphone', 'Windows', 's_o', 'Africa/Lagos', 'abt', 'media-kut2-1.cdn.whatsapp.net', 'media-mba1-1.cdn.whatsapp.net', 'media-mxp1-2.cdn.whatsapp.net', 'md_blocklist_v2', 'url_text', 'enable_short_offset', 'group_join_permissions', 'enable_audio_piggyback_feature', 'image_quality', 'media.fcgk7-2.fna.whatsapp.net', 'media.fcgk8-2.fna.whatsapp.net', 'media.fclo7-1.fna.whatsapp.net', 'media.fcmn1-1.fna.whatsapp.net', 'media.feoh1-1.fna.whatsapp.net', 'media.fgyd4-3.fna.whatsapp.net', 'media.fjed4-4.fna.whatsapp.net', 'media.flim1-4.fna.whatsapp.net', 'media.flim2-4.fna.whatsapp.net', 'media.fplu6-1.fna.whatsapp.net', 'media.frak1-1.fna.whatsapp.net', 'media.fsdq1-1.fna.whatsapp.net', 'to_ip', '015', 'vp8', '19', '21', '1320', 'auth_key_ver', 'message_processing_dedup', 'server-error', 'wap4_enabled', '420', '014', 'cond_range_rtt', 'ptt_fast_lock_enabled', 'media-ort2-1.cdn.whatsapp.net', 'fwd_ui_start_ts'], - ['contact_blacklist', 'Asia/Jakarta', 'media.fepa10-1.fna.whatsapp.net', 'media.fmex10-3.fna.whatsapp.net', 'disorder_prefetching_start_when_empty', 'America/Bogota', 'use_local_probing_rx_bitrate', 'America/Argentina/Buenos_Aires', 'cross_post', 'media.fabb1-1.fna.whatsapp.net', 'media.fbog4-2.fna.whatsapp.net', 'media.fcgk9-1.fna.whatsapp.net', 'media.fcmn2-1.fna.whatsapp.net', 'media.fdel3-1.fna.whatsapp.net', 'media.ffjr1-1.fna.whatsapp.net', 'media.fgdl5-1.fna.whatsapp.net', 'media.flpb1-2.fna.whatsapp.net', 'media.fmex2-1.fna.whatsapp.net', 'media.frba2-2.fna.whatsapp.net', 'media.fros2-2.fna.whatsapp.net', 'media.fruh2-1.fna.whatsapp.net', 'media.fybz2-2.fna.whatsapp.net', 'options', '20', 'a', '017', '018', 'mute_always', 'user_notice', 'Asia/Kolkata', 'gif_provider', 'locked', 'media-gua1-1.cdn.whatsapp.net', 'piggyback_exclude_force_flush', '24', 'media.frec39-1.fna.whatsapp.net', 'user_remove', 'file_max_size', 'cond_packet_loss_pct_ema_alpha', 'media.facc1-1.fna.whatsapp.net', 'media.fadb2-1.fna.whatsapp.net', 'media.faly3-1.fna.whatsapp.net', 'media.fbdo6-2.fna.whatsapp.net', 'media.fcmn2-2.fna.whatsapp.net', 'media.fctg1-3.fna.whatsapp.net', 'media.ffez1-2.fna.whatsapp.net', 'media.fist1-3.fna.whatsapp.net', 'media.fist2-2.fna.whatsapp.net', 'media.flim2-2.fna.whatsapp.net', 'media.fmct2-3.fna.whatsapp.net', 'media.fpei3-1.fna.whatsapp.net', 'media.frba3-1.fna.whatsapp.net', 'media.fsdu8-2.fna.whatsapp.net', 'media.fstu2-1.fna.whatsapp.net', 'media_type', 'receipt_agg', '016', 'enable_pli_for_crc_mismatch', 'live', 'enc_rekey', 'frskmsg', 'd', 'media.fdel11-2.fna.whatsapp.net', 'proto', '2250', 'audio_piggyback_enable_cache', 'skip_nack_if_ltrp_sent', 'mark_dtx_jb_frames', 'web_service_delay', '7282', 'catalog_send_all', 'outgoing', '360', '30', 'LIMITED', '019', 'audio_picker', 'bpv2_phase', 'media.fada1-7.fna.whatsapp.net', 'media.faep7-1.fna.whatsapp.net', 'media.fbko1-2.fna.whatsapp.net', 'media.fbni1-2.fna.whatsapp.net', 'media.fbtz1-1.fna.whatsapp.net', 'media.fbtz1-8.fna.whatsapp.net', 'media.fcjs3-1.fna.whatsapp.net', 'media.fesb3-2.fna.whatsapp.net', 'media.fgdl5-4.fna.whatsapp.net', 'media.fist2-1.fna.whatsapp.net', 'media.flhe2-2.fna.whatsapp.net', 'media.flim2-1.fna.whatsapp.net', 'media.fmex1-1.fna.whatsapp.net', 'media.fpat3-2.fna.whatsapp.net', 'media.fpat3-3.fna.whatsapp.net', 'media.fros2-1.fna.whatsapp.net', 'media.fsdu8-1.fna.whatsapp.net', 'media.fsub3-2.fna.whatsapp.net', 'payments_chat_plugin', 'cond_congestion_no_rtcp_thr', 'green_alert', 'not-a-biz', '..', 'shops_pdp_urls_config', 'source', 'media-dus1-1.cdn.whatsapp.net', 'mute_video', '01b', 'currency', 'max_keys', 'resume_check', 'contact_array', 'qr_scanning', '23', 'b', 'media.fbfh15-1.fna.whatsapp.net', 'media.flim22-1.fna.whatsapp.net', 'media.fsdu11-1.fna.whatsapp.net', 'media.fsdu15-1.fna.whatsapp.net', 'Chrome', 'fts_version', '60', 'media.fada1-6.fna.whatsapp.net', 'media.faep4-2.fna.whatsapp.net', 'media.fbaq5-1.fna.whatsapp.net', 'media.fbni1-1.fna.whatsapp.net', 'media.fcai3-2.fna.whatsapp.net', 'media.fdel3-2.fna.whatsapp.net', 'media.fdmm3-2.fna.whatsapp.net', 'media.fhex3-1.fna.whatsapp.net', 'media.fisb4-2.fna.whatsapp.net', 'media.fkhi5-2.fna.whatsapp.net', 'media.flos2-1.fna.whatsapp.net', 'media.fmct2-1.fna.whatsapp.net', 'media.fntr7-1.fna.whatsapp.net', 'media.frak3-1.fna.whatsapp.net', 'media.fruh5-2.fna.whatsapp.net', 'media.fsub6-1.fna.whatsapp.net', 'media.fuab1-2.fna.whatsapp.net', 'media.fuio1-1.fna.whatsapp.net', 'media.fver1-1.fna.whatsapp.net', 'media.fymy1-1.fna.whatsapp.net', 'product_catalog', '1380', 'audio_oob_fec_max_pkts', '22', '254', 'media-ort2-2.cdn.whatsapp.net', 'media-sjc3-1.cdn.whatsapp.net', '1600', '01a', '01c', '405', 'key_frame_interval', 'body', 'media.fcgh20-1.fna.whatsapp.net', 'media.fesb10-2.fna.whatsapp.net', '125', '2000', 'media.fbsb1-1.fna.whatsapp.net', 'media.fcmn3-2.fna.whatsapp.net', 'media.fcpq1-1.fna.whatsapp.net', 'media.fdel1-2.fna.whatsapp.net', 'media.ffor2-1.fna.whatsapp.net', 'media.fgdl1-4.fna.whatsapp.net', 'media.fhex2-1.fna.whatsapp.net', 'media.fist1-2.fna.whatsapp.net', 'media.fjed5-2.fna.whatsapp.net', 'media.flim6-4.fna.whatsapp.net', 'media.flos2-2.fna.whatsapp.net', 'media.fntr6-2.fna.whatsapp.net', 'media.fpku3-2.fna.whatsapp.net', 'media.fros8-1.fna.whatsapp.net', 'media.fymy1-2.fna.whatsapp.net', 'ul_bw', 'ltrp_qp_offset', 'request', 'nack', 'dtx_delay_state_reset', 'timeoffline', '28', '01f', '32', 'enable_ltr_pool', 'wa_msys_crypto', '01d', '58', 'dtx_freeze_hg_update', 'nack_if_rpsi_throttled', '253', '840', 'media.famd15-1.fna.whatsapp.net', 'media.fbog17-2.fna.whatsapp.net', 'media.fcai19-2.fna.whatsapp.net', 'media.fcai21-4.fna.whatsapp.net', 'media.fesb10-4.fna.whatsapp.net', 'media.fesb10-5.fna.whatsapp.net', 'media.fmaa12-1.fna.whatsapp.net', 'media.fmex11-3.fna.whatsapp.net', 'media.fpoa33-1.fna.whatsapp.net', '1050', '021', 'clean', 'cond_range_ema_packet_loss_pct', 'media.fadb6-5.fna.whatsapp.net', 'media.faqp4-1.fna.whatsapp.net', 'media.fbaq3-1.fna.whatsapp.net', 'media.fbel2-1.fna.whatsapp.net', 'media.fblr4-2.fna.whatsapp.net', 'media.fclo8-1.fna.whatsapp.net', 'media.fcoo1-2.fna.whatsapp.net', 'media.ffjr1-4.fna.whatsapp.net', 'media.ffor9-1.fna.whatsapp.net', 'media.fisb3-1.fna.whatsapp.net', 'media.fkhi2-2.fna.whatsapp.net', 'media.fkhi4-1.fna.whatsapp.net', 'media.fpbc1-2.fna.whatsapp.net', 'media.fruh2-2.fna.whatsapp.net', 'media.fruh5-1.fna.whatsapp.net', 'media.fsub3-1.fna.whatsapp.net', 'payments_transaction_limit', '252', '27', '29', 'tintagel', '01e', '237', '780', 'callee_updated_payload', '020', '257', 'price', '025', '239', 'payments_cs_phone_number', 'mediaretry', 'w:auth:backup:token', 'Glass.caf', 'max_bitrate', '240', '251', '660', 'media.fbog16-1.fna.whatsapp.net', 'media.fcgh21-1.fna.whatsapp.net', 'media.fkul19-2.fna.whatsapp.net', 'media.flim21-2.fna.whatsapp.net', 'media.fmex10-4.fna.whatsapp.net', '64', '33', '34', '35', 'interruption', 'media.fabv3-1.fna.whatsapp.net', 'media.fadb6-1.fna.whatsapp.net', 'media.fagr1-1.fna.whatsapp.net', 'media.famd1-1.fna.whatsapp.net', 'media.famm6-1.fna.whatsapp.net', 'media.faqp2-3.fna.whatsapp.net'], - + [ + 'media-for1-1.cdn.whatsapp.net', + 'relay', + 'media-gru2-2.cdn.whatsapp.net', + 'uncompressed', + 'medium', + 'voip_settings', + 'device', + 'reason', + 'media-lim1-1.cdn.whatsapp.net', + 'media-qro1-2.cdn.whatsapp.net', + 'media-gru1-2.cdn.whatsapp.net', + 'action', + 'features', + 'media-gru2-1.cdn.whatsapp.net', + 'media-gru1-1.cdn.whatsapp.net', + 'media-otp1-1.cdn.whatsapp.net', + 'kyc-id', + 'priority', + 'phash', + 'mute', + 'token', + '100', + 'media-qro1-1.cdn.whatsapp.net', + 'none', + 'media-mrs2-2.cdn.whatsapp.net', + 'sign_credential', + '03', + 'media-mrs2-1.cdn.whatsapp.net', + 'protocol', + 'timezone', + 'transport', + 'eph_setting', + '1080', + 'original_dimensions', + 'media-frx5-1.cdn.whatsapp.net', + 'background', + 'disable', + 'original_image_url', + '5', + 'transaction-id', + 'direct_path', + '103', + 'appointment_only', + 'request_image_url', + 'peer_pid', + 'address', + '105', + '104', + '102', + 'media-cdt1-1.cdn.whatsapp.net', + '101', + '109', + '110', + '106', + 'background_location', + 'v_id', + 'sync', + 'status-old', + '111', + '107', + 'ppic', + 'media-scl2-1.cdn.whatsapp.net', + 'business_profile', + '108', + 'invite', + '04', + 'audio_duration', + 'media-mct1-1.cdn.whatsapp.net', + 'media-cdg2-1.cdn.whatsapp.net', + 'media-los2-1.cdn.whatsapp.net', + 'invis', + 'net', + 'voip_payload_type', + 'status-revoke-delay', + '404', + 'state', + 'use_correct_order_for_hmac_sha1', + 'ver', + 'media-mad1-1.cdn.whatsapp.net', + 'order', + '540', + 'skey', + 'blinded_credential', + 'android', + 'contact_remove', + 'enable_downlink_relay_latency_only', + 'duration', + 'enable_vid_one_way_codec_nego', + '6', + 'media-sof1-1.cdn.whatsapp.net', + 'accept', + 'all', + 'signed_credential', + 'media-atl3-1.cdn.whatsapp.net', + 'media-lhr8-1.cdn.whatsapp.net', + 'website', + '05', + 'latitude', + 'media-dfw5-1.cdn.whatsapp.net', + 'forbidden', + 'enable_audio_piggyback_network_mtu_fix', + 'media-dfw5-2.cdn.whatsapp.net', + 'note.m4r', + 'media-atl3-2.cdn.whatsapp.net', + 'jb_nack_discard_count_fix', + 'longitude', + 'Opening.m4r', + 'media-arn2-1.cdn.whatsapp.net', + 'email', + 'timestamp', + 'admin', + 'media-pmo1-1.cdn.whatsapp.net', + 'America/Sao_Paulo', + 'contact_add', + 'media-sin6-1.cdn.whatsapp.net', + 'interactive', + '8000', + 'acs_public_key', + 'sigquit_anr_detector_release_rollover_percent', + 'media.fmed1-2.fna.whatsapp.net', + 'groupadd', + 'enabled_for_video_upgrade', + 'latency_update_threshold', + 'media-frt3-2.cdn.whatsapp.net', + 'calls_row_constraint_layout', + 'media.fgbb2-1.fna.whatsapp.net', + 'mms4_media_retry_notification_encryption_enabled', + 'timeout', + 'media-sin6-3.cdn.whatsapp.net', + 'audio_nack_jitter_multiplier', + 'jb_discard_count_adjust_pct_rc', + 'audio_reserve_bps', + 'delta', + 'account_sync', + 'default', + 'media.fjed4-6.fna.whatsapp.net', + '06', + 'lock_video_orientation', + 'media-frt3-1.cdn.whatsapp.net', + 'w:g2', + 'media-sin6-2.cdn.whatsapp.net', + 'audio_nack_algo_mask', + 'media.fgbb2-2.fna.whatsapp.net', + 'media.fmed1-1.fna.whatsapp.net', + 'cond_range_target_bitrate', + 'mms4_server_error_receipt_encryption_enabled', + 'vid_rc_dyn', + 'fri', + 'cart_v1_1_order_message_changes_enabled', + 'reg_push', + 'jb_hist_deposit_value', + 'privatestats', + 'media.fist7-2.fna.whatsapp.net', + 'thu', + 'jb_discard_count_adjust_pct', + 'mon', + 'group_call_video_maximization_enabled', + 'mms_cat_v1_forward_hot_override_enabled', + 'audio_nack_new_rtt', + 'media.fsub2-3.fna.whatsapp.net', + 'media_upload_aggressive_retry_exponential_backoff_enabled', + 'tue', + 'wed', + 'media.fruh4-2.fna.whatsapp.net', + 'audio_nack_max_seq_req', + 'max_rtp_audio_packet_resends', + 'jb_hist_max_cdf_value', + '07', + 'audio_nack_max_jb_delay', + 'mms_forward_partially_downloaded_video', + 'media-lcy1-1.cdn.whatsapp.net', + 'resume', + 'jb_inband_fec_aware', + 'new_commerce_entry_point_enabled', + '480', + 'payments_upi_generate_qr_amount_limit', + 'sigquit_anr_detector_rollover_percent', + 'media.fsdu2-1.fna.whatsapp.net', + 'fbns', + 'aud_pkt_reorder_pct', + 'dec', + 'stop_probing_before_accept_send', + 'media_upload_max_aggressive_retries', + 'edit_business_profile_new_mode_enabled', + 'media.fhex4-1.fna.whatsapp.net', + 'media.fjed4-3.fna.whatsapp.net', + 'sigquit_anr_detector_64bit_rollover_percent', + 'cond_range_ema_jb_last_delay', + 'watls_enable_early_data_http_get', + 'media.fsdu2-2.fna.whatsapp.net', + 'message_qr_disambiguation_enabled', + 'media-mxp1-1.cdn.whatsapp.net', + 'sat', + 'vertical', + 'media.fruh4-5.fna.whatsapp.net', + '200', + 'media-sof1-2.cdn.whatsapp.net', + '-1', + 'height', + 'product_catalog_hide_show_items_enabled', + 'deep_copy_frm_last', + 'tsoffline', + 'vp8/h.264', + 'media.fgye5-3.fna.whatsapp.net', + 'media.ftuc1-2.fna.whatsapp.net', + 'smb_upsell_chat_banner_enabled', + 'canonical', + '08', + '9', + '.', + 'media.fgyd4-4.fna.whatsapp.net', + 'media.fsti4-1.fna.whatsapp.net', + 'mms_vcache_aggregation_enabled', + 'mms_hot_content_timespan_in_seconds', + 'nse_ver', + 'rte', + 'third_party_sticker_web_sync', + 'cond_range_target_total_bitrate', + 'media_upload_aggressive_retry_enabled', + 'instrument_spam_report_enabled', + 'disable_reconnect_tone', + 'move_media_folder_from_sister_app', + 'one_tap_calling_in_group_chat_size', + '10', + 'storage_mgmt_banner_threshold_mb', + 'enable_backup_passive_mode', + 'sharechat_inline_player_enabled', + 'media.fcnq2-1.fna.whatsapp.net', + 'media.fhex4-2.fna.whatsapp.net', + 'media.fist6-3.fna.whatsapp.net', + 'ephemeral_drop_column_stage', + 'reconnecting_after_network_change_threshold_ms', + 'media-lhr8-2.cdn.whatsapp.net', + 'cond_jb_last_delay_ema_alpha', + 'entry_point_block_logging_enabled', + 'critical_event_upload_log_config', + 'respect_initial_bitrate_estimate', + 'smaller_image_thumbs_status_enabled', + 'media.fbtz1-4.fna.whatsapp.net', + 'media.fjed4-1.fna.whatsapp.net', + 'width', + '720', + 'enable_frame_dropper', + 'enable_one_side_mode', + 'urn:xmpp:whatsapp:dirty', + 'new_sticker_animation_behavior_v2', + 'media.flim3-2.fna.whatsapp.net', + 'media.fuio6-2.fna.whatsapp.net', + 'skip_forced_signaling', + 'dleq_proof', + 'status_video_max_bitrate', + 'lazy_send_probing_req', + 'enhanced_storage_management', + 'android_privatestats_endpoint_dit_enabled', + 'media.fscl13-2.fna.whatsapp.net', + 'video_duration' + ], + [ + 'group_call_discoverability_enabled', + 'media.faep9-2.fna.whatsapp.net', + 'msgr', + 'bloks_loggedin_access_app_id', + 'db_status_migration_step', + 'watls_prefer_ip6', + 'jabber:iq:privacy', + '68', + 'media.fsaw1-11.fna.whatsapp.net', + 'mms4_media_conn_persist_enabled', + 'animated_stickers_thread_clean_up', + 'media.fcgk3-2.fna.whatsapp.net', + 'media.fcgk4-6.fna.whatsapp.net', + 'media.fgye5-2.fna.whatsapp.net', + 'media.flpb1-1.fna.whatsapp.net', + 'media.fsub2-1.fna.whatsapp.net', + 'media.fuio6-3.fna.whatsapp.net', + 'not-allowed', + 'partial_pjpeg_bw_threshold', + 'cap_estimated_bitrate', + 'mms_chatd_resume_check_over_thrift', + 'smb_upsell_business_profile_enabled', + 'product_catalog_webclient', + 'groups', + 'sigquit_anr_detector_release_updated_rollout', + 'syncd_key_rotation_enabled', + 'media.fdmm2-1.fna.whatsapp.net', + 'media-hou1-1.cdn.whatsapp.net', + 'remove_old_chat_notifications', + 'smb_biztools_deeplink_enabled', + 'use_downloadable_filters_int', + 'group_qr_codes_enabled', + 'max_receipt_processing_time', + 'optimistic_image_processing_enabled', + 'smaller_video_thumbs_status_enabled', + 'watls_early_data', + 'reconnecting_before_relay_failover_threshold_ms', + 'cond_range_packet_loss_pct', + 'groups_privacy_blacklist', + 'status-revoke-drop', + 'stickers_animated_thumbnail_download', + 'dedupe_transcode_shared_images', + 'dedupe_transcode_shared_videos', + 'media.fcnq2-2.fna.whatsapp.net', + 'media.fgyd4-1.fna.whatsapp.net', + 'media.fist7-1.fna.whatsapp.net', + 'media.flim3-3.fna.whatsapp.net', + 'add_contact_by_qr_enabled', + 'https://faq.whatsapp.com/payments', + 'multicast_limit_global', + 'sticker_notification_preview', + 'smb_better_catalog_list_adapters_enabled', + 'bloks_use_minscript_android', + 'pen_smoothing_enabled', + 'media.fcgk4-5.fna.whatsapp.net', + 'media.fevn1-3.fna.whatsapp.net', + 'media.fpoj7-1.fna.whatsapp.net', + 'media-arn2-2.cdn.whatsapp.net', + 'reconnecting_before_network_change_threshold_ms', + 'android_media_use_fresco_for_gifs', + 'cond_in_congestion', + 'status_image_max_edge', + 'sticker_search_enabled', + 'starred_stickers_web_sync', + 'db_blank_me_jid_migration_step', + 'media.fist6-2.fna.whatsapp.net', + 'media.ftuc1-1.fna.whatsapp.net', + '09', + 'anr_fast_logs_upload_rollout', + 'camera_core_integration_enabled', + '11', + 'third_party_sticker_caching', + 'thread_dump_contact_support', + 'wam_privatestats_enabled', + 'vcard_as_document_size_kb', + 'maxfpp', + 'fbip', + 'ephemeral_allow_group_members', + 'media-bom1-2.cdn.whatsapp.net', + 'media-xsp1-1.cdn.whatsapp.net', + 'disable_prewarm', + 'frequently_forwarded_max', + 'media.fbtz1-5.fna.whatsapp.net', + 'media.fevn7-1.fna.whatsapp.net', + 'media.fgyd4-2.fna.whatsapp.net', + 'sticker_tray_animation_fully_visible_items', + 'green_alert_banner_duration', + 'reconnecting_after_p2p_failover_threshold_ms', + 'connected', + 'share_biz_vcard_enabled', + 'stickers_animation', + '0a', + '1200', + 'WhatsApp', + 'group_description_length', + 'p_v_id', + 'payments_upi_intent_transaction_limit', + 'frequently_forwarded_messages', + 'media-xsp1-2.cdn.whatsapp.net', + 'media.faep8-1.fna.whatsapp.net', + 'media.faep8-2.fna.whatsapp.net', + 'media.faep9-1.fna.whatsapp.net', + 'media.fdmm2-2.fna.whatsapp.net', + 'media.fgzt3-1.fna.whatsapp.net', + 'media.flim4-2.fna.whatsapp.net', + 'media.frao1-1.fna.whatsapp.net', + 'media.fscl9-2.fna.whatsapp.net', + 'media.fsub2-2.fna.whatsapp.net', + 'superadmin', + 'media.fbog10-1.fna.whatsapp.net', + 'media.fcgh28-1.fna.whatsapp.net', + 'media.fjdo10-1.fna.whatsapp.net', + 'third_party_animated_sticker_import', + 'delay_fec', + 'attachment_picker_refresh', + 'android_linked_devices_re_auth_enabled', + 'rc_dyn', + 'green_alert_block_jitter', + 'add_contact_logging_enabled', + 'biz_message_logging_enabled', + 'conversation_media_preview_v2', + 'media-jnb1-1.cdn.whatsapp.net', + 'ab_key', + 'media.fcgk4-2.fna.whatsapp.net', + 'media.fevn1-1.fna.whatsapp.net', + 'media.fist6-1.fna.whatsapp.net', + 'media.fruh4-4.fna.whatsapp.net', + 'media.fsti4-2.fna.whatsapp.net', + 'mms_vcard_autodownload_size_kb', + 'watls_enabled', + 'notif_ch_override_off', + 'media.fsaw1-14.fna.whatsapp.net', + 'media.fscl13-1.fna.whatsapp.net', + 'db_group_participant_migration_step', + '1020', + 'cond_range_sterm_rtt', + 'invites_logging_enabled', + 'triggered_block_enabled', + 'group_call_max_participants', + 'media-iad3-1.cdn.whatsapp.net', + 'product_catalog_open_deeplink', + 'shops_required_tos_version', + 'image_max_kbytes', + 'cond_low_quality_vid_mode', + 'db_receipt_migration_step', + 'jb_early_prob_hist_shrink', + 'media.fdmm2-3.fna.whatsapp.net', + 'media.fdmm2-4.fna.whatsapp.net', + 'media.fruh4-1.fna.whatsapp.net', + 'media.fsaw2-2.fna.whatsapp.net', + 'remove_geolocation_videos', + 'new_animation_behavior', + 'fieldstats_beacon_chance', + '403', + 'authkey_reset_on_ban', + 'continuous_ptt_playback', + 'reconnecting_after_relay_failover_threshold_ms', + 'false', + 'group', + 'sun', + 'conversation_swipe_to_reply', + 'ephemeral_messages_setting', + 'smaller_video_thumbs_enabled', + 'md_device_sync_enabled', + 'bloks_shops_pdp_url_regex', + 'lasso_integration_enabled', + 'media-bom1-1.cdn.whatsapp.net', + 'new_backup_format_enabled', + '256', + 'media.faep6-1.fna.whatsapp.net', + 'media.fasr1-1.fna.whatsapp.net', + 'media.fbtz1-7.fna.whatsapp.net', + 'media.fesb4-1.fna.whatsapp.net', + 'media.fjdo1-2.fna.whatsapp.net', + 'media.frba2-1.fna.whatsapp.net', + 'watls_no_dns', + '600', + 'db_broadcast_me_jid_migration_step', + 'new_wam_runtime_enabled', + 'group_update', + 'enhanced_block_enabled', + 'sync_wifi_threshold_kb', + 'mms_download_nc_cat', + 'bloks_minification_enabled', + 'ephemeral_messages_enabled', + 'reject', + 'voip_outgoing_xml_signaling', + 'creator', + 'dl_bw', + 'payments_request_messages', + 'target_bitrate', + 'bloks_rendercore_enabled', + 'media-hbe1-1.cdn.whatsapp.net', + 'media-hel3-1.cdn.whatsapp.net', + 'media-kut2-2.cdn.whatsapp.net', + 'media-lax3-1.cdn.whatsapp.net', + 'media-lax3-2.cdn.whatsapp.net', + 'sticker_pack_deeplink_enabled', + 'hq_image_bw_threshold', + 'status_info', + 'voip', + 'dedupe_transcode_videos', + 'grp_uii_cleanup', + 'linked_device_max_count', + 'media.flim1-1.fna.whatsapp.net', + 'media.fsaw2-1.fna.whatsapp.net', + 'reconnecting_after_call_active_threshold_ms', + '1140', + 'catalog_pdp_new_design', + 'media.fbtz1-10.fna.whatsapp.net', + 'media.fsaw1-15.fna.whatsapp.net', + '0b', + 'consumer_rc_provider', + 'mms_async_fast_forward_ttl', + 'jb_eff_size_fix', + 'voip_incoming_xml_signaling', + 'media_provider_share_by_uuid', + 'suspicious_links', + 'dedupe_transcode_images', + 'green_alert_modal_start', + 'media-cgk1-1.cdn.whatsapp.net', + 'media-lga3-1.cdn.whatsapp.net', + 'template_doc_mime_types', + 'important_messages', + 'user_add', + 'vcard_max_size_kb', + 'media.fada2-1.fna.whatsapp.net', + 'media.fbog2-5.fna.whatsapp.net', + 'media.fbtz1-3.fna.whatsapp.net', + 'media.fcgk3-1.fna.whatsapp.net', + 'media.fcgk7-1.fna.whatsapp.net', + 'media.flim1-3.fna.whatsapp.net', + 'media.fscl9-1.fna.whatsapp.net', + 'ctwa_context_enterprise_enabled', + 'media.fsaw1-13.fna.whatsapp.net', + 'media.fuio11-2.fna.whatsapp.net', + 'status_collapse_muted', + 'db_migration_level_force', + 'recent_stickers_web_sync', + 'bloks_session_state', + 'bloks_shops_enabled', + 'green_alert_setting_deep_links_enabled', + 'restrict_groups', + 'battery', + 'green_alert_block_start', + 'refresh', + 'ctwa_context_enabled', + 'md_messaging_enabled', + 'status_image_quality', + 'md_blocklist_v2_server', + 'media-del1-1.cdn.whatsapp.net', + '13', + 'userrate', + 'a_v_id', + 'cond_rtt_ema_alpha', + 'invalid' + ], + [ + 'media.fada1-1.fna.whatsapp.net', + 'media.fadb3-2.fna.whatsapp.net', + 'media.fbhz2-1.fna.whatsapp.net', + 'media.fcor2-1.fna.whatsapp.net', + 'media.fjed4-2.fna.whatsapp.net', + 'media.flhe4-1.fna.whatsapp.net', + 'media.frak1-2.fna.whatsapp.net', + 'media.fsub6-3.fna.whatsapp.net', + 'media.fsub6-7.fna.whatsapp.net', + 'media.fvvi1-1.fna.whatsapp.net', + 'search_v5_eligible', + 'wam_real_time_enabled', + 'report_disk_event', + 'max_tx_rott_based_bitrate', + 'product', + 'media.fjdo10-2.fna.whatsapp.net', + 'video_frame_crc_sample_interval', + 'media_max_autodownload', + '15', + 'h.264', + 'wam_privatestats_buffer_count', + 'md_phash_v2_enabled', + 'account_transfer_enabled', + 'business_product_catalog', + 'enable_non_dyn_codec_param_fix', + 'is_user_under_epd_jurisdiction', + 'media.fbog2-4.fna.whatsapp.net', + 'media.fbtz1-2.fna.whatsapp.net', + 'media.fcfc1-1.fna.whatsapp.net', + 'media.fjed4-5.fna.whatsapp.net', + 'media.flhe4-2.fna.whatsapp.net', + 'media.flim1-2.fna.whatsapp.net', + 'media.flos5-1.fna.whatsapp.net', + 'android_key_store_auth_ver', + '010', + 'anr_process_monitor', + 'delete_old_auth_key', + 'media.fcor10-3.fna.whatsapp.net', + 'storage_usage_enabled', + 'android_camera2_support_level', + 'dirty', + 'consumer_content_provider', + 'status_video_max_duration', + '0c', + 'bloks_cache_enabled', + 'media.fadb2-2.fna.whatsapp.net', + 'media.fbko1-1.fna.whatsapp.net', + 'media.fbtz1-9.fna.whatsapp.net', + 'media.fcgk4-4.fna.whatsapp.net', + 'media.fesb4-2.fna.whatsapp.net', + 'media.fevn1-2.fna.whatsapp.net', + 'media.fist2-4.fna.whatsapp.net', + 'media.fjdo1-1.fna.whatsapp.net', + 'media.fruh4-6.fna.whatsapp.net', + 'media.fsrg5-1.fna.whatsapp.net', + 'media.fsub6-6.fna.whatsapp.net', + 'minfpp', + '5000', + 'locales', + 'video_max_bitrate', + 'use_new_auth_key', + 'bloks_http_enabled', + 'heartbeat_interval', + 'media.fbog11-1.fna.whatsapp.net', + 'ephemeral_group_query_ts', + 'fec_nack', + 'search_in_storage_usage', + 'c', + 'media-amt2-1.cdn.whatsapp.net', + 'linked_devices_ui_enabled', + '14', + 'async_data_load_on_startup', + 'voip_incoming_xml_ack', + '16', + 'db_migration_step', + 'init_bwe', + 'max_participants', + 'wam_buffer_count', + 'media.fada2-2.fna.whatsapp.net', + 'media.fadb3-1.fna.whatsapp.net', + 'media.fcor2-2.fna.whatsapp.net', + 'media.fdiy1-2.fna.whatsapp.net', + 'media.frba3-2.fna.whatsapp.net', + 'media.fsaw2-3.fna.whatsapp.net', + '1280', + 'status_grid_enabled', + 'w:biz', + 'product_catalog_deeplink', + 'media.fgye10-2.fna.whatsapp.net', + 'media.fuio11-1.fna.whatsapp.net', + 'optimistic_upload', + 'work_manager_init', + 'lc', + 'catalog_message', + 'cond_net_medium', + 'enable_periodical_aud_rr_processing', + 'cond_range_ema_rtt', + 'media-tir2-1.cdn.whatsapp.net', + 'frame_ms', + 'group_invite_sending', + 'payments_web_enabled', + 'wallpapers_v2', + '0d', + 'browser', + 'hq_image_max_edge', + 'image_edit_zoom', + 'linked_devices_re_auth_enabled', + 'media.faly3-2.fna.whatsapp.net', + 'media.fdoh5-3.fna.whatsapp.net', + 'media.fesb3-1.fna.whatsapp.net', + 'media.fknu1-1.fna.whatsapp.net', + 'media.fmex3-1.fna.whatsapp.net', + 'media.fruh4-3.fna.whatsapp.net', + '255', + 'web_upgrade_to_md_modal', + 'audio_piggyback_timeout_msec', + 'enable_audio_oob_fec_feature', + 'from_ip', + 'image_max_edge', + 'message_qr_enabled', + 'powersave', + 'receipt_pre_acking', + 'video_max_edge', + 'full', + '011', + '012', + 'enable_audio_oob_fec_for_sender', + 'md_voip_enabled', + 'enable_privatestats', + 'max_fec_ratio', + 'payments_cs_faq_url', + 'media-xsp1-3.cdn.whatsapp.net', + 'hq_image_quality', + 'media.fasr1-2.fna.whatsapp.net', + 'media.fbog3-1.fna.whatsapp.net', + 'media.ffjr1-6.fna.whatsapp.net', + 'media.fist2-3.fna.whatsapp.net', + 'media.flim4-3.fna.whatsapp.net', + 'media.fpbc2-4.fna.whatsapp.net', + 'media.fpku1-1.fna.whatsapp.net', + 'media.frba1-1.fna.whatsapp.net', + 'media.fudi1-1.fna.whatsapp.net', + 'media.fvvi1-2.fna.whatsapp.net', + 'gcm_fg_service', + 'enable_dec_ltr_size_check', + 'clear', + 'lg', + 'media.fgru11-1.fna.whatsapp.net', + '18', + 'media-lga3-2.cdn.whatsapp.net', + 'pkey', + '0e', + 'max_subject', + 'cond_range_lterm_rtt', + 'announcement_groups', + 'biz_profile_options', + 's_t', + 'media.fabv2-1.fna.whatsapp.net', + 'media.fcai3-1.fna.whatsapp.net', + 'media.fcgh1-1.fna.whatsapp.net', + 'media.fctg1-4.fna.whatsapp.net', + 'media.fdiy1-1.fna.whatsapp.net', + 'media.fisb4-1.fna.whatsapp.net', + 'media.fpku1-2.fna.whatsapp.net', + 'media.fros9-1.fna.whatsapp.net', + 'status_v3_text', + 'usync_sidelist', + '17', + 'announcement', + '...', + 'md_group_notification', + '0f', + 'animated_pack_in_store', + '013', + 'America/Mexico_City', + '1260', + 'media-ams4-1.cdn.whatsapp.net', + 'media-cgk1-2.cdn.whatsapp.net', + 'media-cpt1-1.cdn.whatsapp.net', + 'media-maa2-1.cdn.whatsapp.net', + 'media.fgye10-1.fna.whatsapp.net', + 'e', + 'catalog_cart', + 'hfm_string_changes', + 'init_bitrate', + 'packless_hsm', + 'group_info', + 'America/Belem', + '50', + '960', + 'cond_range_bwe', + 'decode', + 'encode', + 'media.fada1-8.fna.whatsapp.net', + 'media.fadb1-2.fna.whatsapp.net', + 'media.fasu6-1.fna.whatsapp.net', + 'media.fbog4-1.fna.whatsapp.net', + 'media.fcgk9-2.fna.whatsapp.net', + 'media.fdoh5-2.fna.whatsapp.net', + 'media.ffjr1-2.fna.whatsapp.net', + 'media.fgua1-1.fna.whatsapp.net', + 'media.fgye1-1.fna.whatsapp.net', + 'media.fist1-4.fna.whatsapp.net', + 'media.fpbc2-2.fna.whatsapp.net', + 'media.fres2-1.fna.whatsapp.net', + 'media.fsdq1-2.fna.whatsapp.net', + 'media.fsub6-5.fna.whatsapp.net', + 'profilo_enabled', + 'template_hsm', + 'use_disorder_prefetching_timer', + 'video_codec_priority', + 'vpx_max_qp', + 'ptt_reduce_recording_delay', + '25', + 'iphone', + 'Windows', + 's_o', + 'Africa/Lagos', + 'abt', + 'media-kut2-1.cdn.whatsapp.net', + 'media-mba1-1.cdn.whatsapp.net', + 'media-mxp1-2.cdn.whatsapp.net', + 'md_blocklist_v2', + 'url_text', + 'enable_short_offset', + 'group_join_permissions', + 'enable_audio_piggyback_feature', + 'image_quality', + 'media.fcgk7-2.fna.whatsapp.net', + 'media.fcgk8-2.fna.whatsapp.net', + 'media.fclo7-1.fna.whatsapp.net', + 'media.fcmn1-1.fna.whatsapp.net', + 'media.feoh1-1.fna.whatsapp.net', + 'media.fgyd4-3.fna.whatsapp.net', + 'media.fjed4-4.fna.whatsapp.net', + 'media.flim1-4.fna.whatsapp.net', + 'media.flim2-4.fna.whatsapp.net', + 'media.fplu6-1.fna.whatsapp.net', + 'media.frak1-1.fna.whatsapp.net', + 'media.fsdq1-1.fna.whatsapp.net', + 'to_ip', + '015', + 'vp8', + '19', + '21', + '1320', + 'auth_key_ver', + 'message_processing_dedup', + 'server-error', + 'wap4_enabled', + '420', + '014', + 'cond_range_rtt', + 'ptt_fast_lock_enabled', + 'media-ort2-1.cdn.whatsapp.net', + 'fwd_ui_start_ts' + ], + [ + 'contact_blacklist', + 'Asia/Jakarta', + 'media.fepa10-1.fna.whatsapp.net', + 'media.fmex10-3.fna.whatsapp.net', + 'disorder_prefetching_start_when_empty', + 'America/Bogota', + 'use_local_probing_rx_bitrate', + 'America/Argentina/Buenos_Aires', + 'cross_post', + 'media.fabb1-1.fna.whatsapp.net', + 'media.fbog4-2.fna.whatsapp.net', + 'media.fcgk9-1.fna.whatsapp.net', + 'media.fcmn2-1.fna.whatsapp.net', + 'media.fdel3-1.fna.whatsapp.net', + 'media.ffjr1-1.fna.whatsapp.net', + 'media.fgdl5-1.fna.whatsapp.net', + 'media.flpb1-2.fna.whatsapp.net', + 'media.fmex2-1.fna.whatsapp.net', + 'media.frba2-2.fna.whatsapp.net', + 'media.fros2-2.fna.whatsapp.net', + 'media.fruh2-1.fna.whatsapp.net', + 'media.fybz2-2.fna.whatsapp.net', + 'options', + '20', + 'a', + '017', + '018', + 'mute_always', + 'user_notice', + 'Asia/Kolkata', + 'gif_provider', + 'locked', + 'media-gua1-1.cdn.whatsapp.net', + 'piggyback_exclude_force_flush', + '24', + 'media.frec39-1.fna.whatsapp.net', + 'user_remove', + 'file_max_size', + 'cond_packet_loss_pct_ema_alpha', + 'media.facc1-1.fna.whatsapp.net', + 'media.fadb2-1.fna.whatsapp.net', + 'media.faly3-1.fna.whatsapp.net', + 'media.fbdo6-2.fna.whatsapp.net', + 'media.fcmn2-2.fna.whatsapp.net', + 'media.fctg1-3.fna.whatsapp.net', + 'media.ffez1-2.fna.whatsapp.net', + 'media.fist1-3.fna.whatsapp.net', + 'media.fist2-2.fna.whatsapp.net', + 'media.flim2-2.fna.whatsapp.net', + 'media.fmct2-3.fna.whatsapp.net', + 'media.fpei3-1.fna.whatsapp.net', + 'media.frba3-1.fna.whatsapp.net', + 'media.fsdu8-2.fna.whatsapp.net', + 'media.fstu2-1.fna.whatsapp.net', + 'media_type', + 'receipt_agg', + '016', + 'enable_pli_for_crc_mismatch', + 'live', + 'enc_rekey', + 'frskmsg', + 'd', + 'media.fdel11-2.fna.whatsapp.net', + 'proto', + '2250', + 'audio_piggyback_enable_cache', + 'skip_nack_if_ltrp_sent', + 'mark_dtx_jb_frames', + 'web_service_delay', + '7282', + 'catalog_send_all', + 'outgoing', + '360', + '30', + 'LIMITED', + '019', + 'audio_picker', + 'bpv2_phase', + 'media.fada1-7.fna.whatsapp.net', + 'media.faep7-1.fna.whatsapp.net', + 'media.fbko1-2.fna.whatsapp.net', + 'media.fbni1-2.fna.whatsapp.net', + 'media.fbtz1-1.fna.whatsapp.net', + 'media.fbtz1-8.fna.whatsapp.net', + 'media.fcjs3-1.fna.whatsapp.net', + 'media.fesb3-2.fna.whatsapp.net', + 'media.fgdl5-4.fna.whatsapp.net', + 'media.fist2-1.fna.whatsapp.net', + 'media.flhe2-2.fna.whatsapp.net', + 'media.flim2-1.fna.whatsapp.net', + 'media.fmex1-1.fna.whatsapp.net', + 'media.fpat3-2.fna.whatsapp.net', + 'media.fpat3-3.fna.whatsapp.net', + 'media.fros2-1.fna.whatsapp.net', + 'media.fsdu8-1.fna.whatsapp.net', + 'media.fsub3-2.fna.whatsapp.net', + 'payments_chat_plugin', + 'cond_congestion_no_rtcp_thr', + 'green_alert', + 'not-a-biz', + '..', + 'shops_pdp_urls_config', + 'source', + 'media-dus1-1.cdn.whatsapp.net', + 'mute_video', + '01b', + 'currency', + 'max_keys', + 'resume_check', + 'contact_array', + 'qr_scanning', + '23', + 'b', + 'media.fbfh15-1.fna.whatsapp.net', + 'media.flim22-1.fna.whatsapp.net', + 'media.fsdu11-1.fna.whatsapp.net', + 'media.fsdu15-1.fna.whatsapp.net', + 'Chrome', + 'fts_version', + '60', + 'media.fada1-6.fna.whatsapp.net', + 'media.faep4-2.fna.whatsapp.net', + 'media.fbaq5-1.fna.whatsapp.net', + 'media.fbni1-1.fna.whatsapp.net', + 'media.fcai3-2.fna.whatsapp.net', + 'media.fdel3-2.fna.whatsapp.net', + 'media.fdmm3-2.fna.whatsapp.net', + 'media.fhex3-1.fna.whatsapp.net', + 'media.fisb4-2.fna.whatsapp.net', + 'media.fkhi5-2.fna.whatsapp.net', + 'media.flos2-1.fna.whatsapp.net', + 'media.fmct2-1.fna.whatsapp.net', + 'media.fntr7-1.fna.whatsapp.net', + 'media.frak3-1.fna.whatsapp.net', + 'media.fruh5-2.fna.whatsapp.net', + 'media.fsub6-1.fna.whatsapp.net', + 'media.fuab1-2.fna.whatsapp.net', + 'media.fuio1-1.fna.whatsapp.net', + 'media.fver1-1.fna.whatsapp.net', + 'media.fymy1-1.fna.whatsapp.net', + 'product_catalog', + '1380', + 'audio_oob_fec_max_pkts', + '22', + '254', + 'media-ort2-2.cdn.whatsapp.net', + 'media-sjc3-1.cdn.whatsapp.net', + '1600', + '01a', + '01c', + '405', + 'key_frame_interval', + 'body', + 'media.fcgh20-1.fna.whatsapp.net', + 'media.fesb10-2.fna.whatsapp.net', + '125', + '2000', + 'media.fbsb1-1.fna.whatsapp.net', + 'media.fcmn3-2.fna.whatsapp.net', + 'media.fcpq1-1.fna.whatsapp.net', + 'media.fdel1-2.fna.whatsapp.net', + 'media.ffor2-1.fna.whatsapp.net', + 'media.fgdl1-4.fna.whatsapp.net', + 'media.fhex2-1.fna.whatsapp.net', + 'media.fist1-2.fna.whatsapp.net', + 'media.fjed5-2.fna.whatsapp.net', + 'media.flim6-4.fna.whatsapp.net', + 'media.flos2-2.fna.whatsapp.net', + 'media.fntr6-2.fna.whatsapp.net', + 'media.fpku3-2.fna.whatsapp.net', + 'media.fros8-1.fna.whatsapp.net', + 'media.fymy1-2.fna.whatsapp.net', + 'ul_bw', + 'ltrp_qp_offset', + 'request', + 'nack', + 'dtx_delay_state_reset', + 'timeoffline', + '28', + '01f', + '32', + 'enable_ltr_pool', + 'wa_msys_crypto', + '01d', + '58', + 'dtx_freeze_hg_update', + 'nack_if_rpsi_throttled', + '253', + '840', + 'media.famd15-1.fna.whatsapp.net', + 'media.fbog17-2.fna.whatsapp.net', + 'media.fcai19-2.fna.whatsapp.net', + 'media.fcai21-4.fna.whatsapp.net', + 'media.fesb10-4.fna.whatsapp.net', + 'media.fesb10-5.fna.whatsapp.net', + 'media.fmaa12-1.fna.whatsapp.net', + 'media.fmex11-3.fna.whatsapp.net', + 'media.fpoa33-1.fna.whatsapp.net', + '1050', + '021', + 'clean', + 'cond_range_ema_packet_loss_pct', + 'media.fadb6-5.fna.whatsapp.net', + 'media.faqp4-1.fna.whatsapp.net', + 'media.fbaq3-1.fna.whatsapp.net', + 'media.fbel2-1.fna.whatsapp.net', + 'media.fblr4-2.fna.whatsapp.net', + 'media.fclo8-1.fna.whatsapp.net', + 'media.fcoo1-2.fna.whatsapp.net', + 'media.ffjr1-4.fna.whatsapp.net', + 'media.ffor9-1.fna.whatsapp.net', + 'media.fisb3-1.fna.whatsapp.net', + 'media.fkhi2-2.fna.whatsapp.net', + 'media.fkhi4-1.fna.whatsapp.net', + 'media.fpbc1-2.fna.whatsapp.net', + 'media.fruh2-2.fna.whatsapp.net', + 'media.fruh5-1.fna.whatsapp.net', + 'media.fsub3-1.fna.whatsapp.net', + 'payments_transaction_limit', + '252', + '27', + '29', + 'tintagel', + '01e', + '237', + '780', + 'callee_updated_payload', + '020', + '257', + 'price', + '025', + '239', + 'payments_cs_phone_number', + 'mediaretry', + 'w:auth:backup:token', + 'Glass.caf', + 'max_bitrate', + '240', + '251', + '660', + 'media.fbog16-1.fna.whatsapp.net', + 'media.fcgh21-1.fna.whatsapp.net', + 'media.fkul19-2.fna.whatsapp.net', + 'media.flim21-2.fna.whatsapp.net', + 'media.fmex10-4.fna.whatsapp.net', + '64', + '33', + '34', + '35', + 'interruption', + 'media.fabv3-1.fna.whatsapp.net', + 'media.fadb6-1.fna.whatsapp.net', + 'media.fagr1-1.fna.whatsapp.net', + 'media.famd1-1.fna.whatsapp.net', + 'media.famm6-1.fna.whatsapp.net', + 'media.faqp2-3.fna.whatsapp.net' + ] ] export const SINGLE_BYTE_TOKENS: (string | null)[] = [ - '', 'xmlstreamstart', 'xmlstreamend', 's.whatsapp.net', 'type', 'participant', 'from', 'receipt', 'id', 'broadcast', 'status', 'message', 'notification', 'notify', 'to', 'jid', 'user', 'class', 'offline', 'g.us', 'result', 'mediatype', 'enc', 'skmsg', 'off_cnt', 'xmlns', 'presence', 'participants', 'ack', 't', 'iq', 'device_hash', 'read', 'value', 'media', 'picture', 'chatstate', 'unavailable', 'text', 'urn:xmpp:whatsapp:push', 'devices', 'verified_name', 'contact', 'composing', 'edge_routing', 'routing_info', 'item', 'image', 'verified_level', 'get', 'fallback_hostname', '2', 'media_conn', '1', 'v', 'handshake', 'fallback_class', 'count', 'config', 'offline_preview', 'download_buckets', 'w:profile:picture', 'set', 'creation', 'location', 'fallback_ip4', 'msg', 'urn:xmpp:ping', 'fallback_ip6', 'call-creator', 'relaylatency', 'success', 'subscribe', 'video', 'business_hours_config', 'platform', 'hostname', 'version', 'unknown', '0', 'ping', 'hash', 'edit', 'subject', 'max_buckets', 'download', 'delivery', 'props', 'sticker', 'name', 'last', 'contacts', 'business', 'primary', 'preview', 'w:p', 'pkmsg', 'call-id', 'retry', 'prop', 'call', 'auth_ttl', 'available', 'relay_id', 'last_id', 'day_of_week', 'w', 'host', 'seen', 'bits', 'list', 'atn', 'upload', 'is_new', 'w:stats', 'key', 'paused', 'specific_hours', 'multicast', 'stream:error', 'mmg.whatsapp.net', 'code', 'deny', 'played', 'profile', 'fna', 'device-list', 'close_time', 'latency', 'gcm', 'pop', 'audio', '26', 'w:web', 'open_time', 'error', 'auth', 'ip4', 'update', 'profile_options', 'config_value', 'category', 'catalog_not_created', '00', 'config_code', 'mode', 'catalog_status', 'ip6', 'blocklist', 'registration', '7', 'web', 'fail', 'w:m', 'cart_enabled', 'ttl', 'gif', '300', 'device_orientation', 'identity', 'query', '401', 'media-gig2-1.cdn.whatsapp.net', 'in', '3', 'te2', 'add', 'fallback', 'categories', 'ptt', 'encrypt', 'notice', 'thumbnail-document', 'item-not-found', '12', 'thumbnail-image', 'stage', 'thumbnail-link', 'usync', 'out', 'thumbnail-video', '8', '01', 'context', 'sidelist', 'thumbnail-gif', 'terminate', 'not-authorized', 'orientation', 'dhash', 'capability', 'side_list', 'md-app-state', 'description', 'serial', 'readreceipts', 'te', 'business_hours', 'md-msg-hist', 'tag', 'attribute_padding', 'document', 'open_24h', 'delete', 'expiration', 'active', 'prev_v_id', 'true', 'passive', 'index', '4', 'conflict', 'remove', 'w:gp2', 'config_expo_key', 'screen_height', 'replaced', '02', 'screen_width', 'uploadfieldstat', '2:47DEQpj8', 'media-bog1-1.cdn.whatsapp.net', 'encopt', 'url', 'catalog_exists', 'keygen', 'rate', 'offer', 'opus', 'media-mia3-1.cdn.whatsapp.net', 'privacy', 'media-mia3-2.cdn.whatsapp.net', 'signature', 'preaccept', 'token_id', 'media-eze1-1.cdn.whatsapp.net' + '', + 'xmlstreamstart', + 'xmlstreamend', + 's.whatsapp.net', + 'type', + 'participant', + 'from', + 'receipt', + 'id', + 'broadcast', + 'status', + 'message', + 'notification', + 'notify', + 'to', + 'jid', + 'user', + 'class', + 'offline', + 'g.us', + 'result', + 'mediatype', + 'enc', + 'skmsg', + 'off_cnt', + 'xmlns', + 'presence', + 'participants', + 'ack', + 't', + 'iq', + 'device_hash', + 'read', + 'value', + 'media', + 'picture', + 'chatstate', + 'unavailable', + 'text', + 'urn:xmpp:whatsapp:push', + 'devices', + 'verified_name', + 'contact', + 'composing', + 'edge_routing', + 'routing_info', + 'item', + 'image', + 'verified_level', + 'get', + 'fallback_hostname', + '2', + 'media_conn', + '1', + 'v', + 'handshake', + 'fallback_class', + 'count', + 'config', + 'offline_preview', + 'download_buckets', + 'w:profile:picture', + 'set', + 'creation', + 'location', + 'fallback_ip4', + 'msg', + 'urn:xmpp:ping', + 'fallback_ip6', + 'call-creator', + 'relaylatency', + 'success', + 'subscribe', + 'video', + 'business_hours_config', + 'platform', + 'hostname', + 'version', + 'unknown', + '0', + 'ping', + 'hash', + 'edit', + 'subject', + 'max_buckets', + 'download', + 'delivery', + 'props', + 'sticker', + 'name', + 'last', + 'contacts', + 'business', + 'primary', + 'preview', + 'w:p', + 'pkmsg', + 'call-id', + 'retry', + 'prop', + 'call', + 'auth_ttl', + 'available', + 'relay_id', + 'last_id', + 'day_of_week', + 'w', + 'host', + 'seen', + 'bits', + 'list', + 'atn', + 'upload', + 'is_new', + 'w:stats', + 'key', + 'paused', + 'specific_hours', + 'multicast', + 'stream:error', + 'mmg.whatsapp.net', + 'code', + 'deny', + 'played', + 'profile', + 'fna', + 'device-list', + 'close_time', + 'latency', + 'gcm', + 'pop', + 'audio', + '26', + 'w:web', + 'open_time', + 'error', + 'auth', + 'ip4', + 'update', + 'profile_options', + 'config_value', + 'category', + 'catalog_not_created', + '00', + 'config_code', + 'mode', + 'catalog_status', + 'ip6', + 'blocklist', + 'registration', + '7', + 'web', + 'fail', + 'w:m', + 'cart_enabled', + 'ttl', + 'gif', + '300', + 'device_orientation', + 'identity', + 'query', + '401', + 'media-gig2-1.cdn.whatsapp.net', + 'in', + '3', + 'te2', + 'add', + 'fallback', + 'categories', + 'ptt', + 'encrypt', + 'notice', + 'thumbnail-document', + 'item-not-found', + '12', + 'thumbnail-image', + 'stage', + 'thumbnail-link', + 'usync', + 'out', + 'thumbnail-video', + '8', + '01', + 'context', + 'sidelist', + 'thumbnail-gif', + 'terminate', + 'not-authorized', + 'orientation', + 'dhash', + 'capability', + 'side_list', + 'md-app-state', + 'description', + 'serial', + 'readreceipts', + 'te', + 'business_hours', + 'md-msg-hist', + 'tag', + 'attribute_padding', + 'document', + 'open_24h', + 'delete', + 'expiration', + 'active', + 'prev_v_id', + 'true', + 'passive', + 'index', + '4', + 'conflict', + 'remove', + 'w:gp2', + 'config_expo_key', + 'screen_height', + 'replaced', + '02', + 'screen_width', + 'uploadfieldstat', + '2:47DEQpj8', + 'media-bog1-1.cdn.whatsapp.net', + 'encopt', + 'url', + 'catalog_exists', + 'keygen', + 'rate', + 'offer', + 'opus', + 'media-mia3-1.cdn.whatsapp.net', + 'privacy', + 'media-mia3-2.cdn.whatsapp.net', + 'signature', + 'preaccept', + 'token_id', + 'media-eze1-1.cdn.whatsapp.net' ] -export const TOKEN_MAP: { [token: string]: { dict?: number, index: number } } = { } +export const TOKEN_MAP: { [token: string]: { dict?: number; index: number } } = {} -for(const [i, SINGLE_BYTE_TOKEN] of SINGLE_BYTE_TOKENS.entries()) { +for (const [i, SINGLE_BYTE_TOKEN] of SINGLE_BYTE_TOKENS.entries()) { TOKEN_MAP[SINGLE_BYTE_TOKEN!] = { index: i } } -for(const [i, DOUBLE_BYTE_TOKEN] of DOUBLE_BYTE_TOKENS.entries()) { - for(const [j, element] of DOUBLE_BYTE_TOKEN.entries()) { +for (const [i, DOUBLE_BYTE_TOKEN] of DOUBLE_BYTE_TOKENS.entries()) { + for (const [j, element] of DOUBLE_BYTE_TOKEN.entries()) { TOKEN_MAP[element] = { dict: i, index: j } } -} \ No newline at end of file +} diff --git a/src/WABinary/decode.ts b/src/WABinary/decode.ts index 706a135..d97554b 100644 --- a/src/WABinary/decode.ts +++ b/src/WABinary/decode.ts @@ -6,10 +6,11 @@ import type { BinaryNode, BinaryNodeCodingOptions } from './types' const inflatePromise = promisify(inflate) -export const decompressingIfRequired = async(buffer: Buffer) => { - if(2 & buffer.readUInt8()) { +export const decompressingIfRequired = async (buffer: Buffer) => { + if (2 & buffer.readUInt8()) { buffer = await inflatePromise(buffer.slice(1)) - } else { // nodes with no compression have a 0x00 prefix, we remove that + } else { + // nodes with no compression have a 0x00 prefix, we remove that buffer = buffer.slice(1) } @@ -24,7 +25,7 @@ export const decodeDecompressedBinaryNode = ( const { DOUBLE_BYTE_TOKENS, SINGLE_BYTE_TOKENS, TAGS } = opts const checkEOS = (length: number) => { - if(indexRef.index + length > buffer.length) { + if (indexRef.index + length > buffer.length) { throw new Error('end of stream') } } @@ -54,7 +55,7 @@ export const decodeDecompressedBinaryNode = ( const readInt = (n: number, littleEndian = false) => { checkEOS(n) let val = 0 - for(let i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { const shift = littleEndian ? i : n - 1 - i val |= next() << (shift * 8) } @@ -68,7 +69,7 @@ export const decodeDecompressedBinaryNode = ( } const unpackHex = (value: number) => { - if(value >= 0 && value < 16) { + if (value >= 0 && value < 16) { return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10 } @@ -76,26 +77,26 @@ export const decodeDecompressedBinaryNode = ( } const unpackNibble = (value: number) => { - if(value >= 0 && value <= 9) { + if (value >= 0 && value <= 9) { return '0'.charCodeAt(0) + value } switch (value) { - case 10: - return '-'.charCodeAt(0) - case 11: - return '.'.charCodeAt(0) - case 15: - return '\0'.charCodeAt(0) - default: - throw new Error('invalid nibble: ' + value) + case 10: + return '-'.charCodeAt(0) + case 11: + return '.'.charCodeAt(0) + case 15: + return '\0'.charCodeAt(0) + default: + throw new Error('invalid nibble: ' + value) } } const unpackByte = (tag: number, value: number) => { - if(tag === TAGS.NIBBLE_8) { + if (tag === TAGS.NIBBLE_8) { return unpackNibble(value) - } else if(tag === TAGS.HEX_8) { + } else if (tag === TAGS.HEX_8) { return unpackHex(value) } else { throw new Error('unknown tag: ' + tag) @@ -106,13 +107,13 @@ export const decodeDecompressedBinaryNode = ( const startByte = readByte() let value = '' - for(let i = 0; i < (startByte & 127); i++) { + for (let i = 0; i < (startByte & 127); i++) { const curByte = readByte() value += String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4)) value += String.fromCharCode(unpackByte(tag, curByte & 0x0f)) } - if(startByte >> 7 !== 0) { + if (startByte >> 7 !== 0) { value = value.slice(0, -1) } @@ -125,21 +126,21 @@ export const decodeDecompressedBinaryNode = ( const readListSize = (tag: number) => { switch (tag) { - case TAGS.LIST_EMPTY: - return 0 - case TAGS.LIST_8: - return readByte() - case TAGS.LIST_16: - return readInt(2) - default: - throw new Error('invalid tag for list size: ' + tag) + case TAGS.LIST_EMPTY: + return 0 + case TAGS.LIST_8: + return readByte() + case TAGS.LIST_16: + return readInt(2) + default: + throw new Error('invalid tag for list size: ' + tag) } } const readJidPair = () => { const i = readString(readByte()) const j = readString(readByte()) - if(j) { + if (j) { return (i || '') + '@' + j } @@ -153,48 +154,44 @@ export const decodeDecompressedBinaryNode = ( const device = readByte() const user = readString(readByte()) - return jidEncode( - user, - domainType === 0 || domainType === 128 ? 's.whatsapp.net' : 'lid', - device - ) + return jidEncode(user, domainType === 0 || domainType === 128 ? 's.whatsapp.net' : 'lid', device) } const readString = (tag: number): string => { - if(tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) { + if (tag >= 1 && tag < SINGLE_BYTE_TOKENS.length) { return SINGLE_BYTE_TOKENS[tag] || '' } switch (tag) { - case TAGS.DICTIONARY_0: - case TAGS.DICTIONARY_1: - case TAGS.DICTIONARY_2: - case TAGS.DICTIONARY_3: - return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte()) - case TAGS.LIST_EMPTY: - return '' - case TAGS.BINARY_8: - return readStringFromChars(readByte()) - case TAGS.BINARY_20: - return readStringFromChars(readInt20()) - case TAGS.BINARY_32: - return readStringFromChars(readInt(4)) - case TAGS.JID_PAIR: - return readJidPair() - case TAGS.AD_JID: - return readAdJid() - case TAGS.HEX_8: - case TAGS.NIBBLE_8: - return readPacked8(tag) - default: - throw new Error('invalid string with tag: ' + tag) + case TAGS.DICTIONARY_0: + case TAGS.DICTIONARY_1: + case TAGS.DICTIONARY_2: + case TAGS.DICTIONARY_3: + return getTokenDouble(tag - TAGS.DICTIONARY_0, readByte()) + case TAGS.LIST_EMPTY: + return '' + case TAGS.BINARY_8: + return readStringFromChars(readByte()) + case TAGS.BINARY_20: + return readStringFromChars(readInt20()) + case TAGS.BINARY_32: + return readStringFromChars(readInt(4)) + case TAGS.JID_PAIR: + return readJidPair() + case TAGS.AD_JID: + return readAdJid() + case TAGS.HEX_8: + case TAGS.NIBBLE_8: + return readPacked8(tag) + default: + throw new Error('invalid string with tag: ' + tag) } } const readList = (tag: number) => { const items: BinaryNode[] = [] const size = readListSize(tag) - for(let i = 0;i < size;i++) { + for (let i = 0; i < size; i++) { items.push(decodeDecompressedBinaryNode(buffer, opts, indexRef)) } @@ -203,12 +200,12 @@ export const decodeDecompressedBinaryNode = ( const getTokenDouble = (index1: number, index2: number) => { const dict = DOUBLE_BYTE_TOKENS[index1] - if(!dict) { + if (!dict) { throw new Error(`Invalid double token dict (${index1})`) } const value = dict[index2] - if(typeof value === 'undefined') { + if (typeof value === 'undefined') { throw new Error(`Invalid double token (${index2})`) } @@ -217,44 +214,44 @@ export const decodeDecompressedBinaryNode = ( const listSize = readListSize(readByte()) const header = readString(readByte()) - if(!listSize || !header.length) { + if (!listSize || !header.length) { throw new Error('invalid node') } - const attrs: BinaryNode['attrs'] = { } + const attrs: BinaryNode['attrs'] = {} let data: BinaryNode['content'] - if(listSize === 0 || !header) { + if (listSize === 0 || !header) { throw new Error('invalid node') } // read the attributes in const attributesLength = (listSize - 1) >> 1 - for(let i = 0; i < attributesLength; i++) { + for (let i = 0; i < attributesLength; i++) { const key = readString(readByte()) const value = readString(readByte()) attrs[key] = value } - if(listSize % 2 === 0) { + if (listSize % 2 === 0) { const tag = readByte() - if(isListTag(tag)) { + if (isListTag(tag)) { data = readList(tag) } else { let decoded: Buffer | string switch (tag) { - case TAGS.BINARY_8: - decoded = readBytes(readByte()) - break - case TAGS.BINARY_20: - decoded = readBytes(readInt20()) - break - case TAGS.BINARY_32: - decoded = readBytes(readInt(4)) - break - default: - decoded = readString(tag) - break + case TAGS.BINARY_8: + decoded = readBytes(readByte()) + break + case TAGS.BINARY_20: + decoded = readBytes(readInt20()) + break + case TAGS.BINARY_32: + decoded = readBytes(readInt(4)) + break + default: + decoded = readString(tag) + break } data = decoded @@ -268,7 +265,7 @@ export const decodeDecompressedBinaryNode = ( } } -export const decodeBinaryNode = async(buff: Buffer): Promise => { +export const decodeBinaryNode = async (buff: Buffer): Promise => { const decompBuff = await decompressingIfRequired(buff) return decodeDecompressedBinaryNode(decompBuff, constants) } diff --git a/src/WABinary/encode.ts b/src/WABinary/encode.ts index 39f7881..43ae977 100644 --- a/src/WABinary/encode.ts +++ b/src/WABinary/encode.ts @@ -1,4 +1,3 @@ - import * as constants from './constants' import { FullJid, jidDecode } from './jid-utils' import type { BinaryNode, BinaryNodeCodingOptions } from './types' @@ -22,14 +21,14 @@ const encodeBinaryNodeInner = ( const pushByte = (value: number) => buffer.push(value & 0xff) const pushInt = (value: number, n: number, littleEndian = false) => { - for(let i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { const curShift = littleEndian ? i : n - 1 - i buffer.push((value >> (curShift * 8)) & 0xff) } } const pushBytes = (bytes: Uint8Array | Buffer | number[]) => { - for(const b of bytes) { + for (const b of bytes) { buffer.push(b) } } @@ -38,18 +37,16 @@ const encodeBinaryNodeInner = ( pushBytes([(value >> 8) & 0xff, value & 0xff]) } - const pushInt20 = (value: number) => ( - pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff]) - ) + const pushInt20 = (value: number) => pushBytes([(value >> 16) & 0x0f, (value >> 8) & 0xff, value & 0xff]) const writeByteLength = (length: number) => { - if(length >= 4294967296) { + if (length >= 4294967296) { throw new Error('string too large to encode: ' + length) } - if(length >= 1 << 20) { + if (length >= 1 << 20) { pushByte(TAGS.BINARY_32) pushInt(length, 4) // 32 bit integer - } else if(length >= 256) { + } else if (length >= 256) { pushByte(TAGS.BINARY_20) pushInt20(length) } else { @@ -59,20 +56,20 @@ const encodeBinaryNodeInner = ( } const writeStringRaw = (str: string) => { - const bytes = Buffer.from (str, 'utf-8') + const bytes = Buffer.from(str, 'utf-8') writeByteLength(bytes.length) pushBytes(bytes) } const writeJid = ({ domainType, device, user, server }: FullJid) => { - if(typeof device !== 'undefined') { + if (typeof device !== 'undefined') { pushByte(TAGS.AD_JID) pushByte(domainType || 0) pushByte(device || 0) writeString(user) } else { pushByte(TAGS.JID_PAIR) - if(user.length) { + if (user.length) { writeString(user) } else { pushByte(TAGS.LIST_EMPTY) @@ -84,35 +81,35 @@ const encodeBinaryNodeInner = ( const packNibble = (char: string) => { switch (char) { - case '-': - return 10 - case '.': - return 11 - case '\0': - return 15 - default: - if(char >= '0' && char <= '9') { - return char.charCodeAt(0) - '0'.charCodeAt(0) - } + case '-': + return 10 + case '.': + return 11 + case '\0': + return 15 + default: + if (char >= '0' && char <= '9') { + return char.charCodeAt(0) - '0'.charCodeAt(0) + } - throw new Error(`invalid byte for nibble "${char}"`) + throw new Error(`invalid byte for nibble "${char}"`) } } const packHex = (char: string) => { - if(char >= '0' && char <= '9') { + if (char >= '0' && char <= '9') { return char.charCodeAt(0) - '0'.charCodeAt(0) } - if(char >= 'A' && char <= 'F') { + if (char >= 'A' && char <= 'F') { return 10 + char.charCodeAt(0) - 'A'.charCodeAt(0) } - if(char >= 'a' && char <= 'f') { + if (char >= 'a' && char <= 'f') { return 10 + char.charCodeAt(0) - 'a'.charCodeAt(0) } - if(char === '\0') { + if (char === '\0') { return 15 } @@ -120,14 +117,14 @@ const encodeBinaryNodeInner = ( } const writePackedBytes = (str: string, type: 'nibble' | 'hex') => { - if(str.length > TAGS.PACKED_MAX) { + if (str.length > TAGS.PACKED_MAX) { throw new Error('Too many bytes to pack') } pushByte(type === 'nibble' ? TAGS.NIBBLE_8 : TAGS.HEX_8) let roundedLength = Math.ceil(str.length / 2.0) - if(str.length % 2 !== 0) { + if (str.length % 2 !== 0) { roundedLength |= 128 } @@ -140,23 +137,23 @@ const encodeBinaryNodeInner = ( } const strLengthHalf = Math.floor(str.length / 2) - for(let i = 0; i < strLengthHalf;i++) { + for (let i = 0; i < strLengthHalf; i++) { pushByte(packBytePair(str[2 * i], str[2 * i + 1])) } - if(str.length % 2 !== 0) { + if (str.length % 2 !== 0) { pushByte(packBytePair(str[str.length - 1], '\x00')) } } const isNibble = (str?: string) => { - if(!str || str.length > TAGS.PACKED_MAX) { + if (!str || str.length > TAGS.PACKED_MAX) { return false } - for(const char of str) { + for (const char of str) { const isInNibbleRange = char >= '0' && char <= '9' - if(!isInNibbleRange && char !== '-' && char !== '.') { + if (!isInNibbleRange && char !== '-' && char !== '.') { return false } } @@ -165,13 +162,13 @@ const encodeBinaryNodeInner = ( } const isHex = (str?: string) => { - if(!str || str.length > TAGS.PACKED_MAX) { + if (!str || str.length > TAGS.PACKED_MAX) { return false } - for(const char of str) { + for (const char of str) { const isInNibbleRange = char >= '0' && char <= '9' - if(!isInNibbleRange && !(char >= 'A' && char <= 'F')) { + if (!isInNibbleRange && !(char >= 'A' && char <= 'F')) { return false } } @@ -180,25 +177,25 @@ const encodeBinaryNodeInner = ( } const writeString = (str?: string) => { - if(str === undefined || str === null) { + if (str === undefined || str === null) { pushByte(TAGS.LIST_EMPTY) return } const tokenIndex = TOKEN_MAP[str] - if(tokenIndex) { - if(typeof tokenIndex.dict === 'number') { + if (tokenIndex) { + if (typeof tokenIndex.dict === 'number') { pushByte(TAGS.DICTIONARY_0 + tokenIndex.dict) } pushByte(tokenIndex.index) - } else if(isNibble(str)) { + } else if (isNibble(str)) { writePackedBytes(str, 'nibble') - } else if(isHex(str)) { + } else if (isHex(str)) { writePackedBytes(str, 'hex') - } else if(str) { + } else if (str) { const decodedJid = jidDecode(str) - if(decodedJid) { + if (decodedJid) { writeJid(decodedJid) } else { writeStringRaw(str) @@ -207,9 +204,9 @@ const encodeBinaryNodeInner = ( } const writeListStart = (listSize: number) => { - if(listSize === 0) { + if (listSize === 0) { pushByte(TAGS.LIST_EMPTY) - } else if(listSize < 256) { + } else if (listSize < 256) { pushBytes([TAGS.LIST_8, listSize]) } else { pushByte(TAGS.LIST_16) @@ -217,37 +214,36 @@ const encodeBinaryNodeInner = ( } } - if(!tag) { + if (!tag) { throw new Error('Invalid node: tag cannot be undefined') } - const validAttributes = Object.keys(attrs || {}).filter(k => ( - typeof attrs[k] !== 'undefined' && attrs[k] !== null - )) + const validAttributes = Object.keys(attrs || {}).filter(k => typeof attrs[k] !== 'undefined' && attrs[k] !== null) writeListStart(2 * validAttributes.length + 1 + (typeof content !== 'undefined' ? 1 : 0)) writeString(tag) - for(const key of validAttributes) { - if(typeof attrs[key] === 'string') { + for (const key of validAttributes) { + if (typeof attrs[key] === 'string') { writeString(key) writeString(attrs[key]) } } - if(typeof content === 'string') { + if (typeof content === 'string') { writeString(content) - } else if(Buffer.isBuffer(content) || content instanceof Uint8Array) { + } else if (Buffer.isBuffer(content) || content instanceof Uint8Array) { writeByteLength(content.length) pushBytes(content) - } else if(Array.isArray(content)) { - const validContent = content.filter(item => item && (item.tag || Buffer.isBuffer(item) || item instanceof Uint8Array || typeof item === 'string') + } else if (Array.isArray(content)) { + const validContent = content.filter( + item => item && (item.tag || Buffer.isBuffer(item) || item instanceof Uint8Array || typeof item === 'string') ) writeListStart(validContent.length) - for(const item of validContent) { + for (const item of validContent) { encodeBinaryNodeInner(item, opts, buffer) } - } else if(typeof content === 'undefined') { + } else if (typeof content === 'undefined') { // do nothing } else { throw new Error(`invalid children for header "${tag}": ${content} (${typeof content})`) diff --git a/src/WABinary/generic-utils.ts b/src/WABinary/generic-utils.ts index f519e68..96ab7d2 100644 --- a/src/WABinary/generic-utils.ts +++ b/src/WABinary/generic-utils.ts @@ -5,7 +5,7 @@ import { BinaryNode } from './types' // some extra useful utilities export const getBinaryNodeChildren = (node: BinaryNode | undefined, childTag: string) => { - if(Array.isArray(node?.content)) { + if (Array.isArray(node?.content)) { return node.content.filter(item => item.tag === childTag) } @@ -13,7 +13,7 @@ export const getBinaryNodeChildren = (node: BinaryNode | undefined, childTag: st } export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => { - if(Array.isArray(content)) { + if (Array.isArray(content)) { return content } @@ -21,37 +21,37 @@ export const getAllBinaryNodeChildren = ({ content }: BinaryNode) => { } export const getBinaryNodeChild = (node: BinaryNode | undefined, childTag: string) => { - if(Array.isArray(node?.content)) { + if (Array.isArray(node?.content)) { return node?.content.find(item => item.tag === childTag) } } export const getBinaryNodeChildBuffer = (node: BinaryNode | undefined, childTag: string) => { const child = getBinaryNodeChild(node, childTag)?.content - if(Buffer.isBuffer(child) || child instanceof Uint8Array) { + if (Buffer.isBuffer(child) || child instanceof Uint8Array) { return child } } export const getBinaryNodeChildString = (node: BinaryNode | undefined, childTag: string) => { const child = getBinaryNodeChild(node, childTag)?.content - if(Buffer.isBuffer(child) || child instanceof Uint8Array) { + if (Buffer.isBuffer(child) || child instanceof Uint8Array) { return Buffer.from(child).toString('utf-8') - } else if(typeof child === 'string') { + } else if (typeof child === 'string') { return child } } export const getBinaryNodeChildUInt = (node: BinaryNode, childTag: string, length: number) => { const buff = getBinaryNodeChildBuffer(node, childTag) - if(buff) { + if (buff) { return bufferToUInt(buff, length) } } export const assertNodeErrorFree = (node: BinaryNode) => { const errNode = getBinaryNodeChild(node, 'error') - if(errNode) { + if (errNode) { throw new Boom(errNode.attrs.text || 'Unknown error', { data: +errNode.attrs.code }) } } @@ -62,16 +62,17 @@ export const reduceBinaryNodeToDictionary = (node: BinaryNode, tag: string) => { (dict, { attrs }) => { dict[attrs.name || attrs.config_code] = attrs.value || attrs.config_value return dict - }, { } as { [_: string]: string } + }, + {} as { [_: string]: string } ) return dict } export const getBinaryNodeMessages = ({ content }: BinaryNode) => { const msgs: proto.WebMessageInfo[] = [] - if(Array.isArray(content)) { - for(const item of content) { - if(item.tag === 'message') { + if (Array.isArray(content)) { + for (const item of content) { + if (item.tag === 'message') { msgs.push(proto.WebMessageInfo.decode(item.content as Buffer)) } } @@ -82,7 +83,7 @@ export const getBinaryNodeMessages = ({ content }: BinaryNode) => { function bufferToUInt(e: Uint8Array | Buffer, t: number) { let a = 0 - for(let i = 0; i < t; i++) { + for (let i = 0; i < t; i++) { a = 256 * a + e[i] } @@ -92,20 +93,20 @@ function bufferToUInt(e: Uint8Array | Buffer, t: number) { const tabs = (n: number) => '\t'.repeat(n) export function binaryNodeToString(node: BinaryNode | BinaryNode['content'], i = 0) { - if(!node) { + if (!node) { return node } - if(typeof node === 'string') { + if (typeof node === 'string') { return tabs(i) + node } - if(node instanceof Uint8Array) { + if (node instanceof Uint8Array) { return tabs(i) + Buffer.from(node).toString('hex') } - if(Array.isArray(node)) { - return node.map((x) => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n') + if (Array.isArray(node)) { + return node.map(x => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n') } const children = binaryNodeToString(node.content, i + 1) @@ -118,4 +119,4 @@ export function binaryNodeToString(node: BinaryNode | BinaryNode['content'], i = const content: string = children ? `>\n${children}\n${tabs(i)}` : '/>' return tag + content -} \ No newline at end of file +} diff --git a/src/WABinary/jid-utils.ts b/src/WABinary/jid-utils.ts index 202211e..075437f 100644 --- a/src/WABinary/jid-utils.ts +++ b/src/WABinary/jid-utils.ts @@ -8,8 +8,8 @@ export const META_AI_JID = '13135550002@c.us' export type JidServer = 'c.us' | 'g.us' | 'broadcast' | 's.whatsapp.net' | 'call' | 'lid' | 'newsletter' | 'bot' export type JidWithDevice = { - user: string - device?: number + user: string + device?: number } export type FullJid = JidWithDevice & { @@ -17,14 +17,13 @@ export type FullJid = JidWithDevice & { domainType?: number } - export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => { return `${user || ''}${!!agent ? `_${agent}` : ''}${!!device ? `:${device}` : ''}@${server}` } export const jidDecode = (jid: string | undefined): FullJid | undefined => { const sepIdx = typeof jid === 'string' ? jid.indexOf('@') : -1 - if(sepIdx < 0) { + if (sepIdx < 0) { return undefined } @@ -43,34 +42,33 @@ export const jidDecode = (jid: string | undefined): FullJid | undefined => { } /** is the jid a user */ -export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) => ( +export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) => jidDecode(jid1)?.user === jidDecode(jid2)?.user -) /** is the jid Meta IA */ -export const isJidMetaIa = (jid: string | undefined) => (jid?.endsWith('@bot')) +export const isJidMetaIa = (jid: string | undefined) => jid?.endsWith('@bot') /** is the jid a user */ -export const isJidUser = (jid: string | undefined) => (jid?.endsWith('@s.whatsapp.net')) +export const isJidUser = (jid: string | undefined) => jid?.endsWith('@s.whatsapp.net') /** is the jid a group */ -export const isLidUser = (jid: string | undefined) => (jid?.endsWith('@lid')) +export const isLidUser = (jid: string | undefined) => jid?.endsWith('@lid') /** is the jid a broadcast */ -export const isJidBroadcast = (jid: string | undefined) => (jid?.endsWith('@broadcast')) +export const isJidBroadcast = (jid: string | undefined) => jid?.endsWith('@broadcast') /** is the jid a group */ -export const isJidGroup = (jid: string | undefined) => (jid?.endsWith('@g.us')) +export const isJidGroup = (jid: string | undefined) => jid?.endsWith('@g.us') /** is the jid the status broadcast */ export const isJidStatusBroadcast = (jid: string) => jid === 'status@broadcast' /** is the jid a newsletter */ -export const isJidNewsletter = (jid: string | undefined) => (jid?.endsWith('@newsletter')) +export const isJidNewsletter = (jid: string | undefined) => jid?.endsWith('@newsletter') const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/ -export const isJidBot = (jid: string | undefined) => (jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us')) +export const isJidBot = (jid: string | undefined) => jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us') export const jidNormalizedUser = (jid: string | undefined) => { const result = jidDecode(jid) - if(!result) { + if (!result) { return '' } const { user, server } = result - return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer) + return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : (server as JidServer)) } diff --git a/src/WABinary/types.ts b/src/WABinary/types.ts index dcc0ea2..5125a58 100644 --- a/src/WABinary/types.ts +++ b/src/WABinary/types.ts @@ -7,11 +7,11 @@ import * as constants from './constants' * to maintain functional code structure * */ export type BinaryNode = { - tag: string - attrs: { [key: string]: string } + tag: string + attrs: { [key: string]: string } content?: BinaryNode[] | string | Uint8Array } export type BinaryNodeAttributes = BinaryNode['attrs'] export type BinaryNodeData = BinaryNode['content'] -export type BinaryNodeCodingOptions = typeof constants \ No newline at end of file +export type BinaryNodeCodingOptions = typeof constants diff --git a/src/WAM/constants.ts b/src/WAM/constants.ts index c2a93e2..08fd34e 100644 --- a/src/WAM/constants.ts +++ b/src/WAM/constants.ts @@ -29,16 +29,16 @@ export const WEB_EVENTS: Event[] = [ { FALSE: 0, TRUE: 1, - UNDEFINED: 2, - }, + UNDEFINED: 2 + } ], webcWindowNightmare: [16, 'boolean'], webcWindowPhantom: [15, 'boolean'], - webcWindowSelenium: [17, 'boolean'], + webcWindowSelenium: [17, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'Login', @@ -52,8 +52,8 @@ export const WEB_EVENTS: Event[] = [ ENC_KEY_READ_FAILURE: 3, ENC_KEY_STORED_USED: 4, ENC_KEY_PLAIN_DELETED: 5, - ENC_KEY_PLAIN_RECOVERED: 6, - }, + ENC_KEY_PLAIN_RECOVERED: 6 + } ], connectionOrigin: [ 6, @@ -61,8 +61,8 @@ export const WEB_EVENTS: Event[] = [ PERSON: 1, PUSH: 2, OTHER: 3, - BACKOFF: 4, - }, + BACKOFF: 4 + } ], connectionSequenceStep: [ 11, @@ -76,8 +76,8 @@ export const WEB_EVENTS: Event[] = [ PRIMARY_HTTP: 8, SOFTLAYER_HTTP: 9, HOST_FALLBACK_HTTP: 10, - NO_DNS_HTTP: 11, - }, + NO_DNS_HTTP: 11 + } ], connectionT: [5, 'timer'], dnsResolutionMethod: [ @@ -86,8 +86,8 @@ export const WEB_EVENTS: Event[] = [ SYSTEM: 1, GOOGLE: 2, HARDCODED: 3, - NO_DNS: 4, - }, + NO_DNS: 4 + } ], loginDnsResolver: [ 13, @@ -95,8 +95,8 @@ export const WEB_EVENTS: Event[] = [ SYSTEM: 1, GOOGLE: 2, HARDCODED: 3, - NO_DNS: 4, - }, + NO_DNS: 4 + } ], loginIpSource: [ 14, @@ -106,8 +106,8 @@ export const WEB_EVENTS: Event[] = [ PUSH_FALLBACKS: 3, G_FALLBACK_WHATSAPP_NET: 4, HARDCODED_LIST: 5, - EX_WHATSAPP_NET: 6, - }, + EX_WHATSAPP_NET: 6 + } ], loginPort: [ 15, @@ -115,8 +115,8 @@ export const WEB_EVENTS: Event[] = [ P5222: 1, P443: 2, P80: 3, - UNKNOWN: 4, - }, + UNKNOWN: 4 + } ], loginResult: [ 1, @@ -127,8 +127,8 @@ export const WEB_EVENTS: Event[] = [ SERVER_GOAWAY: 4, NETWORK_ERROR: 5, ANDROID_KEYSTORE_ERROR: 6, - CERTIFICATE_ERROR: 7, - }, + CERTIFICATE_ERROR: 7 + } ], loginT: [3, 'timer'], longConnect: [4, 'boolean'], @@ -137,11 +137,11 @@ export const WEB_EVENTS: Event[] = [ pendingAcksCount: [17, 'integer'], retryCount: [2, 'integer'], sequenceStep: [7, 'integer'], - serverErrorCode: [9, 'integer'], + serverErrorCode: [9, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcPageLoad', @@ -155,8 +155,8 @@ export const WEB_EVENTS: Event[] = [ CHECKING: 2, DOWNLOADING: 3, UPDATEREADY: 4, - OBSOLETE: 5, - }, + OBSOLETE: 5 + } ], webcCached: [30, 'boolean'], webcConnectEnd: [10, 'timer'], @@ -190,8 +190,8 @@ export const WEB_EVENTS: Event[] = [ NAVIGATE_NEXT: 0, RELOAD: 1, BACK_FORWARD: 2, - UNDEFINED: 255, - }, + UNDEFINED: 255 + } ], webcPageLoadT: [34, 'timer'], webcParallellyFetched: [41, 'boolean'], @@ -209,11 +209,11 @@ export const WEB_EVENTS: Event[] = [ webcWsNormal: [27, 'timer'], webcWsOpening: [24, 'timer'], webcWsPairing: [25, 'timer'], - webcWsSyncing: [26, 'timer'], + webcWsSyncing: [26, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'OfflineResume', @@ -244,8 +244,8 @@ export const WEB_EVENTS: Event[] = [ COMPLETE: 1, INCOMPLETE_UNKNOWN_ERROR: 2, INCOMPLETE_DISCONNECT: 3, - INCOMPLETE_APP_RESTART: 4, - }, + INCOMPLETE_APP_RESTART: 4 + } ], offlineSizeBytes: [10, 'integer'], onTrickleMode: [15, 'boolean'], @@ -260,11 +260,11 @@ export const WEB_EVENTS: Event[] = [ processedNotificationCount: [32, 'integer'], processedReceiptCount: [33, 'integer'], socketConnectT: [12, 'timer'], - transientOfflineSessionId: [34, 'string'], + transientOfflineSessionId: [34, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcDbOpen', @@ -272,11 +272,11 @@ export const WEB_EVENTS: Event[] = [ props: { webcDbName: [1, 'string'], webcDbOpenNumAttempts: [3, 'integer'], - webcDbOpenWasSuccess: [2, 'boolean'], + webcDbOpenWasSuccess: [2, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PsIdUpdate', @@ -288,15 +288,15 @@ export const WEB_EVENTS: Event[] = [ { CREATED: 1, ROTATED: 2, - DELETED: 3, - }, + DELETED: 3 + } ], psIdKey: [1, 'integer'], - psIdRotationFrequence: [3, 'integer'], + psIdRotationFrequence: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebDbVersionsSource', @@ -308,21 +308,21 @@ export const WEB_EVENTS: Event[] = [ KNOB: 1, LOCAL: 2, STATIC: 3, - KNOB_WITH_LOCAL_OVERRIDE: 4, - }, + KNOB_WITH_LOCAL_OVERRIDE: 4 + } ], webSchemaInitiator: [ 2, { MAIN: 1, WEB_WORKER: 2, - SERVICE_WORKER: 3, - }, - ], + SERVICE_WORKER: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcAssetLoad', @@ -333,17 +333,17 @@ export const WEB_EVENTS: Event[] = [ { UNCACHED: 0, IDB: 1, - SW: 2, - }, + SW: 2 + } ], webcAssetFromCache: [2, 'boolean'], webcAssetLoadT: [3, 'timer'], webcAssetName: [1, 'string'], - webcAssetSize: [5, 'number'], + webcAssetSize: [5, 'number'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcStorageStat', @@ -352,11 +352,11 @@ export const WEB_EVENTS: Event[] = [ webcAgeOfStorage: [3, 'integer'], webcPackingEnabled: [4, 'boolean'], webcStorageQuota: [2, 'integer'], - webcStorageUsage: [1, 'integer'], + webcStorageUsage: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'OfflineResumeStage', @@ -373,8 +373,8 @@ export const WEB_EVENTS: Event[] = [ SCREEN_LOAD: 4, OFFLINE_PREVIEW: 5, OFFLINE_COMPLETE_RECEIVED: 6, - PREACKS_SENT: 7, - }, + PREACKS_SENT: 7 + } ], isResumeInForeground: [5, 'boolean'], isResumeStartedInForeground: [14, 'boolean'], @@ -393,17 +393,17 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 3, CONNECT_REASON_USER: 4, CONNECT_REASON_PUSH: 5, - CONNECT_REASON_BACKOFF: 6, - }, + CONNECT_REASON_BACKOFF: 6 + } ], offlineSessionId: [2, 'string'], offlineSizeBytes: [16, 'integer'], offlineStageTimestampMs: [3, 'integer'], - passiveModeT: [17, 'timer'], + passiveModeT: [17, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcStreamModeChange', @@ -421,13 +421,13 @@ export const WEB_EVENTS: Event[] = [ TOS_BLOCK: 6, SMB_TOS_BLOCK: 7, DEPRECATED_VERSION: 8, - LOCK: 9, - }, - ], + LOCK: 9 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcStatusSync', @@ -439,11 +439,11 @@ export const WEB_EVENTS: Event[] = [ webcStatusRecentRowCount: [5, 'integer'], webcStatusSyncT: [1, 'timer'], webcStatusViewedItemCount: [3, 'integer'], - webcStatusViewedRowCount: [6, 'integer'], + webcStatusViewedRowCount: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'TsNavigation', @@ -455,8 +455,8 @@ export const WEB_EVENTS: Event[] = [ { BUSINESS_INITIATED: 0, CONSUMER_INITIATED: 1, - NO_MESSAGES_LAST_24H: 2, - }, + NO_MESSAGES_LAST_24H: 2 + } ], entryPointConversionApp: [24, 'string'], entryPointConversionSource: [25, 'string'], @@ -558,8 +558,8 @@ export const WEB_EVENTS: Event[] = [ GROUP_MEMBER_ADD_GROUP_CREATION: 89, GROUP_MEMBER_ADD_EXISTING_GROUP: 90, GROUP_CHAT: 91, - GROUP_CREATION: 92, - }, + GROUP_CREATION: 92 + } ], navigationSource: [ 3, @@ -654,8 +654,8 @@ export const WEB_EVENTS: Event[] = [ GROUP_MEMBER_ADD_GROUP_CREATION: 89, GROUP_MEMBER_ADD_EXISTING_GROUP: 90, GROUP_CHAT: 91, - GROUP_CREATION: 92, - }, + GROUP_CREATION: 92 + } ], relativeTimestampMs: [4, 'integer'], smbCatalogBusinessVertical: [20, 'string'], @@ -671,8 +671,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL: 5, SUB_GROUP: 6, DEFAULT_SUB_GROUP: 7, - PARENT_GROUP: 8, - }, + PARENT_GROUP: 8 + } ], tsSessionId: [5, 'integer'], typeOfGroup: [ @@ -680,13 +680,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UserActivity', @@ -698,11 +698,11 @@ export const WEB_EVENTS: Event[] = [ userActivitySessionCum: [7, 'integer'], userActivitySessionId: [1, 'string'], userActivitySessionSeq: [6, 'integer'], - userActivityStartTime: [2, 'integer'], + userActivityStartTime: [2, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'TsBitArray', @@ -714,11 +714,11 @@ export const WEB_EVENTS: Event[] = [ cumulativeBits: [4, 'integer'], relativeTimestampMs: [5, 'integer'], sessionSeq: [6, 'integer'], - tsSessionId: [7, 'integer'], + tsSessionId: [7, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcResourceLoad', @@ -726,21 +726,21 @@ export const WEB_EVENTS: Event[] = [ props: { webcResourceCached: [3, 'boolean'], webcResourceDuration: [2, 'timer'], - webcResourceName: [1, 'string'], + webcResourceName: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateMessageRange', id: 2522, props: { - additionalMessagesCount: [1, 'integer'], + additionalMessagesCount: [1, 'integer'] }, weight: 1000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapDataApplied', @@ -753,8 +753,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_LOW: 2, REGULAR_HIGH: 3, CRITICAL_BLOCK: 4, - CRITICAL_UNBLOCK_LOW: 5, - }, + CRITICAL_UNBLOCK_LOW: 5 + } ], historySyncChunkOrder: [14, 'integer'], historySyncStageProgress: [11, 'integer'], @@ -768,40 +768,40 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdBootstrapPayloadType: [ 3, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdBootstrapSource: [ 2, { APP_STATE: 1, - HISTORY: 2, - }, + HISTORY: 2 + } ], mdBootstrapStepDuration: [6, 'integer'], mdBootstrapStepResult: [ 12, { SUCCESS: 1, - FAILURE: 2, - }, + FAILURE: 2 + } ], mdRegAttemptId: [9, 'string'], mdSessionId: [1, 'string'], mdTimestamp: [4, 'integer'], sentViaMms: [13, 'boolean'], - usedSnapshot: [7, 'boolean'], + usedSnapshot: [7, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapAppStateDataDownloaded', @@ -817,34 +817,34 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdBootstrapPayloadSize: [4, 'integer'], mdBootstrapPayloadType: [ 2, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdBootstrapStepDuration: [6, 'integer'], mdBootstrapStepResult: [ 7, { SUCCESS: 1, - FAILURE: 2, - }, + FAILURE: 2 + } ], mdRegAttemptId: [10, 'string'], mdSessionId: [1, 'string'], mdStorageQuotaBytes: [8, 'integer'], mdStorageQuotaUsedBytes: [9, 'integer'], - mdTimestamp: [3, 'integer'], + mdTimestamp: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateKeyRotation', @@ -855,13 +855,13 @@ export const WEB_EVENTS: Event[] = [ { APP_STATE_SYNC_KEY_EXPIRY: 1, DEVICE_DEREGISTERATION: 2, - NO_KEYS: 3, - }, - ], + NO_KEYS: 3 + } + ] }, weight: 1000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateSyncDaily', @@ -875,11 +875,11 @@ export const WEB_EVENTS: Event[] = [ storedMutationCount: [7, 'integer'], unsetActionCount: [8, 'integer'], unsupportedActionCount: [5, 'integer'], - uploadConflictCount: [10, 'integer'], + uploadConflictCount: [10, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcPwaEvent', @@ -888,13 +888,13 @@ export const WEB_EVENTS: Event[] = [ webcPwaAction: [ 2, { - INSTALL: 1, - }, - ], + INSTALL: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DeepLinkClick', @@ -902,11 +902,11 @@ export const WEB_EVENTS: Event[] = [ props: { deepLinkHasPhoneNumber: [2, 'boolean'], deepLinkHasText: [1, 'boolean'], - deepLinkSessionId: [3, 'string'], + deepLinkSessionId: [3, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageSecretErrors', @@ -976,14 +976,14 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageSecretAllowedList: [ 1, { - MESSAGE_POLL: 0, - }, + MESSAGE_POLL: 0 + } ], messageSecretError: [ 2, @@ -991,13 +991,13 @@ export const WEB_EVENTS: Event[] = [ MISSING_MESSAGE_SECRET: 0, WRONG_LENGTH: 1, ENCRYPTION_ERROR: 2, - DECRYPTION_ERROR: 3, - }, - ], + DECRYPTION_ERROR: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatMessageCounts', @@ -1010,8 +1010,8 @@ export const WEB_EVENTS: Event[] = [ 60, { SHOPS: 0, - NATIVE: 1, - }, + NATIVE: 1 + } ], bizConversationDepth: [65, 'integer'], blockReason: [ @@ -1022,8 +1022,8 @@ export const WEB_EVENTS: Event[] = [ NO_SIGN_UP: 2, SPAM: 3, OFFENSIVE_MESSAGES: 4, - OTP_DID_NOT_REQUEST: 5, - }, + OTP_DID_NOT_REQUEST: 5 + } ], broadcastMsgsReceived: [30, 'integer'], broadcastMsgsSent: [29, 'integer'], @@ -1043,16 +1043,16 @@ export const WEB_EVENTS: Event[] = [ { NOT_MUTED: 1, MUTED_NO_NOTIFICATIONS: 2, - MUTED_SILENT_NOTIFICATIONS: 3, - }, + MUTED_SILENT_NOTIFICATIONS: 3 + } ], chatOrigins: [ 179, { LID_USERNAME: 1, LID_CTWA: 2, - OTHERS: 3, - }, + OTHERS: 3 + } ], chatOverflowClicks: [79, 'integer'], chatTypeInd: [ @@ -1061,8 +1061,8 @@ export const WEB_EVENTS: Event[] = [ INDIVIDUAL: 1, SMB: 2, ENT: 3, - INTEROP: 4, - }, + INTEROP: 4 + } ], collectionInquiriesSent: [44, 'integer'], commandSheetShow: [174, 'integer'], @@ -1076,8 +1076,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], documentMessagesReceived: [151, 'integer'], documentMessagesSent: [152, 'integer'], @@ -1092,8 +1092,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], ephemeralityTriggerAction: [ 110, @@ -1102,8 +1102,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], eventCreationMessagesReceived: [142, 'integer'], eventCreationMessagesSent: [143, 'integer'], @@ -1138,8 +1138,8 @@ export const WEB_EVENTS: Event[] = [ { NEW: 0, RETAINED: 1, - RESURRECTED: 2, - }, + RESURRECTED: 2 + } ], gifMessagesReceived: [167, 'integer'], gifMessagesSent: [168, 'integer'], @@ -1149,8 +1149,8 @@ export const WEB_EVENTS: Event[] = [ 87, { ADMINS_ONLY: 1, - ALL_PARTICIPANTS: 2, - }, + ALL_PARTICIPANTS: 2 + } ], groupMembershipReplies: [51, 'integer'], groupPrivateReplies: [52, 'integer'], @@ -1198,8 +1198,8 @@ export const WEB_EVENTS: Event[] = [ MASKED_PHONE_NUMBER: 4, VERIFIED_BUSINESS_NAME: 5, PLACEHOLDER: 6, - PUSHNAME: 7, - }, + PUSHNAME: 7 + } ], ordersSent: [38, 'integer'], p2mOdNnpTransactionsSent: [83, 'integer'], @@ -1258,8 +1258,8 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, + DEFAULT_SUBGROUP: 3 + } ], urlMessagesReceived: [138, 'integer'], urlMessagesSent: [139, 'integer'], @@ -1270,11 +1270,11 @@ export const WEB_EVENTS: Event[] = [ viewOnceMessagesOpened: [18, 'integer'], viewOnceMessagesReceived: [17, 'integer'], viewOnceMessagesSent: [16, 'integer'], - voiceCallsOffered: [77, 'integer'], + voiceCallsOffered: [77, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AddressingModeMismatch', @@ -1286,15 +1286,15 @@ export const WEB_EVENTS: Event[] = [ ADD_PARTICIPANT: 1, REMOVE_PARTICIPANT: 2, PROMOTE_PARTICIPANT: 3, - DEMOTE_PARTICIPANT: 4, - }, + DEMOTE_PARTICIPANT: 4 + } ], localAddressingMode: [ 2, { PN: 1, - LID: 2, - }, + LID: 2 + } ], mismatchOrigin: [ 6, @@ -1303,21 +1303,21 @@ export const WEB_EVENTS: Event[] = [ ACK_OUTGOING_MESSAGE: 2, GROUP_NOTIFICATION: 3, GROUP_PROFILE_PICTURE_NOTIFICATION: 4, - IQ_RESPONSES: 5, - }, + IQ_RESPONSES: 5 + } ], notificationTag: [3, 'string'], serverAddressingMode: [ 5, { PN: 1, - LID: 2, - }, - ], + LID: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBadDeviceSentMessage', @@ -1328,20 +1328,20 @@ export const WEB_EVENTS: Event[] = [ { INVALID_SENDER: 1, MISSING_DSM: 2, - INVALID_DSM: 3, - }, + INVALID_DSM: 3 + } ], peerType: [ 1, { PRIMARY: 1, - COMPANION: 2, - }, - ], + COMPANION: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GatedMessageReceived', @@ -1351,13 +1351,13 @@ export const WEB_EVENTS: Event[] = [ 1, { TOS3: 1, - COUNTRY: 2, - }, - ], + COUNTRY: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageReceive', @@ -1368,8 +1368,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 37, @@ -1377,16 +1377,16 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], chatOrigins: [ 38, { LID_USERNAME: 1, LID_CTWA: 2, - OTHERS: 3, - }, + OTHERS: 3 + } ], deviceCount: [16, 'integer'], deviceSizeBucket: [ @@ -1407,8 +1407,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], disappearingChatInitiator: [ 14, @@ -1417,8 +1417,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], editType: [ 25, @@ -1426,8 +1426,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], ephemeralityDuration: [13, 'integer'], ephemeralityInitiator: [ @@ -1435,8 +1435,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], ephemeralityTriggerAction: [ 27, @@ -1445,8 +1445,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], hasUsername: [39, 'boolean'], isAComment: [36, 'boolean'], @@ -1458,15 +1458,15 @@ export const WEB_EVENTS: Event[] = [ 33, { PN: 1, - LID: 2, - }, + LID: 2 + } ], messageAddressingMode: [ 34, { PN: 1, - LID: 2, - }, + LID: 2 + } ], messageIsInternational: [4, 'boolean'], messageIsInvisible: [23, 'boolean'], @@ -1535,8 +1535,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageQueueTime: [15, 'timer'], messageReceiveT0: [6, 'timer'], @@ -1549,8 +1549,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], mutedGroupMessage: [8, 'boolean'], numOfWebUrlsInTextMessage: [3, 'integer'], @@ -1564,8 +1564,8 @@ export const WEB_EVENTS: Event[] = [ MASKED_PHONE_NUMBER: 4, VERIFIED_BUSINESS_NAME: 5, PLACEHOLDER: 6, - PUSHNAME: 7, - }, + PUSHNAME: 7 + } ], paddingBytesSize: [22, 'integer'], participantCount: [17, 'integer'], @@ -1574,16 +1574,16 @@ export const WEB_EVENTS: Event[] = [ 20, { SENDER: 0, - ADMIN: 1, - }, + ADMIN: 1 + } ], senderDefaultDisappearingDuration: [11, 'integer'], serverAddressingMode: [ 35, { PN: 1, - LID: 2, - }, + LID: 2 + } ], stickerIsAi: [29, 'boolean'], stickerIsFromStickerMaker: [31, 'boolean'], @@ -1594,21 +1594,21 @@ export const WEB_EVENTS: Event[] = [ WEB_STICKER_MAKER: 2, IOS_STICKER_MAKER: 3, ANDROID_STICKER_MAKER: 4, - TRANSPARENT_IMAGE: 5, - }, + TRANSPARENT_IMAGE: 5 + } ], typeOfGroup: [ 21, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PlaceholderActivity', @@ -1633,8 +1633,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], e2eSenderType: [ 16, @@ -1644,8 +1644,8 @@ export const WEB_EVENTS: Event[] = [ MY_COMPANION: 3, OTHER_COMPANION: 4, MY_HOSTED_COMPANION: 5, - OTHER_HOSTED_COMPANION: 6, - }, + OTHER_HOSTED_COMPANION: 6 + } ], isHostedChat: [19, 'boolean'], isLid: [12, 'boolean'], @@ -1716,8 +1716,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 5, @@ -1727,8 +1727,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], participantCount: [10, 'integer'], placeholderActionInd: [ @@ -1737,8 +1737,8 @@ export const WEB_EVENTS: Event[] = [ OTHER: 0, ADD: 1, VIEW: 2, - POPULATE: 3, - }, + POPULATE: 3 + } ], placeholderAddReason: [ 11, @@ -1756,8 +1756,8 @@ export const WEB_EVENTS: Event[] = [ SIGNAL_FUTURE_MESSAGE: 10, SIGNAL_INVALID_SIGNATURE: 11, SIGNAL_BAD_MAC: 12, - SIGNAL_INVALID_SESSION: 13, - }, + SIGNAL_INVALID_SESSION: 13 + } ], placeholderChatTypeInd: [ 3, @@ -1768,8 +1768,8 @@ export const WEB_EVENTS: Event[] = [ STATUS: 3, BROADCAST: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], placeholderPopulationType: [ 17, @@ -1777,8 +1777,8 @@ export const WEB_EVENTS: Event[] = [ OTHER: 0, RETRY: 1, PEER_MESSAGE: 2, - RESEND: 3, - }, + RESEND: 3 + } ], placeholderTimePeriod: [4, 'integer'], placeholderTypeInd: [ @@ -1787,21 +1787,21 @@ export const WEB_EVENTS: Event[] = [ OTHER: 0, CIPHERTEXT: 1, FANOUT: 2, - DOWNGRADE: 3, - }, + DOWNGRADE: 3 + } ], typeOfGroup: [ 13, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SenderKeyExpired', @@ -1815,8 +1815,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, STATUS: 3, BROADCAST: 4, - CHANNEL: 5, - }, + CHANNEL: 5 + } ], deviceSizeBucket: [ 2, @@ -1836,8 +1836,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], expiryReason: [ 3, @@ -1849,13 +1849,13 @@ export const WEB_EVENTS: Event[] = [ PERIODIC_ROTATION: 5, KEY_CORRUPTION: 6, PEER_COMPANION_UNPAIR: 7, - OTHER_DEVICE_UNPAIR: 8, - }, - ], + OTHER_DEVICE_UNPAIR: 8 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MexEventV2', @@ -1869,11 +1869,11 @@ export const WEB_EVENTS: Event[] = [ mexEventV2IsMex: [6, 'boolean'], mexEventV2OperationName: [7, 'string'], mexEventV2QueryId: [8, 'string'], - mexEventV2StartTime: [9, 'integer'], + mexEventV2StartTime: [9, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GraphqlCatalogRequest', @@ -1885,8 +1885,8 @@ export const WEB_EVENTS: Event[] = [ { SMB: 1, API_DC: 2, - API: 3, - }, + API: 3 + } ], graphqlCatalogEndpoint: [ 1, @@ -1898,21 +1898,21 @@ export const WEB_EVENTS: Event[] = [ GET_SINGLE_COLLECTION: 5, GET_CATEGORIES: 6, GET_VARIANTS: 7, - GET_PROMOTIONS: 8, - }, + GET_PROMOTIONS: 8 + } ], graphqlErrorCode: [3, 'integer'], graphqlRequestResult: [ 2, { SUCCESS: 1, - FAILURE: 2, - }, - ], + FAILURE: 2 + } + ] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'MexEvent', @@ -1928,11 +1928,11 @@ export const WEB_EVENTS: Event[] = [ mexEventRequestSize: [8, 'integer'], mexEventResponseSize: [9, 'integer'], mexEventRetries: [10, 'integer'], - mexEventStartTime: [11, 'integer'], + mexEventStartTime: [11, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'FmxAction', @@ -1944,8 +1944,8 @@ export const WEB_EVENTS: Event[] = [ 3, { FMX_CARD: 0, - SAFETY_TOOLS: 1, - }, + SAFETY_TOOLS: 1 + } ], fmxEvent: [ 4, @@ -1959,8 +1959,8 @@ export const WEB_EVENTS: Event[] = [ FMX_CARD_INSERTED: 6, FMX_CARD_VIEWED: 7, LEARN_MORE: 8, - HIGHLIGHT_GROUP_NAME: 9, - }, + HIGHLIGHT_GROUP_NAME: 9 + } ], highlightGroupType: [ 6, @@ -1971,26 +1971,26 @@ export const WEB_EVENTS: Event[] = [ ADMIN: 3, SAVED_CONTACTS: 4, PARTICIPANTS: 5, - MORE: 6, - }, + MORE: 6 + } ], isSenderSmb: [7, 'boolean'], - notAContactShown: [5, 'boolean'], + notAContactShown: [5, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UnknownStanza', id: 3448, props: { unknownStanzaTag: [1, 'string'], - unknownStanzaType: [2, 'string'], + unknownStanzaType: [2, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'IncomingMessageDrop', @@ -2001,8 +2001,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 14, @@ -2010,8 +2010,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], e2eCiphertextType: [ 1, @@ -2019,8 +2019,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE: 0, PREKEY_MESSAGE: 1, SENDER_KEY_MESSAGE: 2, - MESSAGE_SECRET_MESSAGE: 3, - }, + MESSAGE_SECRET_MESSAGE: 3 + } ], e2eDestination: [ 2, @@ -2030,8 +2030,8 @@ export const WEB_EVENTS: Event[] = [ LIST: 2, STATUS: 3, CHANNEL: 4, - INTEROP: 5, - }, + INTEROP: 5 + } ], e2eFailureReason: [ 9, @@ -2134,8 +2134,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_INVALID_KEY_MATEIRAL_DATA_LEN: 95, ERROR_SESSION_STATE_GET_SENDER_RATCHET_KEY: 96, ERROR_SESSION_STATE_GET_LOCAL_IDENTITY_KEY: 97, - ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98, - }, + ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98 + } ], e2eSenderType: [ 3, @@ -2145,16 +2145,16 @@ export const WEB_EVENTS: Event[] = [ MY_COMPANION: 3, OTHER_COMPANION: 4, MY_HOSTED_COMPANION: 5, - OTHER_HOSTED_COMPANION: 6, - }, + OTHER_HOSTED_COMPANION: 6 + } ], invisibleMessageCategory: [ 13, { PEER: 1, INVISIBLE_KEY_DISTRIBUTION: 2, - OTHER: 3, - }, + OTHER: 3 + } ], messageDropReason: [ 4, @@ -2174,8 +2174,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE_REVOKED: 13, PAYMENT_MESSAGE_REVOKED: 14, DUPLICATE_MESSAGE: 15, - DUPLICATE_DELIVERY: 16, - }, + DUPLICATE_DELIVERY: 16 + } ], messageMediaType: [ 5, @@ -2241,8 +2241,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], offline: [6, 'boolean'], offlineCount: [11, 'integer'], @@ -2251,21 +2251,21 @@ export const WEB_EVENTS: Event[] = [ 8, { SENDER: 0, - ADMIN: 1, - }, + ADMIN: 1 + } ], typeOfGroup: [ 10, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcOfflineNotificationProcess', @@ -2279,8 +2279,8 @@ export const WEB_EVENTS: Event[] = [ OFFLINE_PREVIEW: 3, PROCESSING: 4, PROCESS_COMPLETE: 5, - PROCESS_INTERRUPTED: 6, - }, + PROCESS_INTERRUPTED: 6 + } ], offlineProcessDecryptErrorCount: [5, 'integer'], offlineProcessMailboxAge: [6, 'integer'], @@ -2292,14 +2292,14 @@ export const WEB_EVENTS: Event[] = [ 12, { PUSH_NOTIFICATION: 1, - PERIODIC_BACKGROUND_SYNC: 2, - }, + PERIODIC_BACKGROUND_SYNC: 2 + } ], - swVersion: [11, 'string'], + swVersion: [11, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdCriticalEvent', @@ -2312,8 +2312,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_LOW: 2, REGULAR_HIGH: 3, CRITICAL_BLOCK: 4, - CRITICAL_UNBLOCK_LOW: 5, - }, + CRITICAL_UNBLOCK_LOW: 5 + } ], mdCriticalEventCode: [ 1, @@ -2333,13 +2333,13 @@ export const WEB_EVENTS: Event[] = [ ACTION_INVALID_INDEX_DATA: 13, MISSING_MUTATION_TO_REMOVE: 14, LTHASH_INCONSISTENCY_ON_DAILY_CHECK: 15, - LTHASH_INCONSISTENCY_ON_SNAPSHOT_MAC_MISMATCH: 16, - }, - ], + LTHASH_INCONSISTENCY_ON_SNAPSHOT_MAC_MISMATCH: 16 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdLinkDeviceCompanion', @@ -2356,27 +2356,27 @@ export const WEB_EVENTS: Event[] = [ UPLOAD_PREKEYS: 4, COMPLETE: 5, GENERATE_PREKEYS: 6, - SENT_PREKEYS: 7, - }, + SENT_PREKEYS: 7 + } ], mdRegAttemptId: [9, 'string'], mdSessionId: [1, 'string'], mdTimestampS: [7, 'integer'], - mdWasUpgraded: [5, 'boolean'], + mdWasUpgraded: [5, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AdvStoredTimestampExpired', id: 3036, props: { - advExpireTimeInHours: [1, 'integer'], + advExpireTimeInHours: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcQplHealth', @@ -2397,13 +2397,13 @@ export const WEB_EVENTS: Event[] = [ POINT_NAME_TOO_LONG: 9, ANNOTATION_KEY_TOO_LONG: 10, POINT_DATA_TOO_LONG: 11, - ERROR_PARSING_CONFIG: 12, - }, - ], + ERROR_PARSING_CONFIG: 12 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CommunityHomeAction', @@ -2413,11 +2413,11 @@ export const WEB_EVENTS: Event[] = [ communityHomeGroupJoins: [2, 'integer'], communityHomeGroupNavigations: [3, 'integer'], communityHomeId: [4, 'string'], - communityHomeViews: [5, 'integer'], + communityHomeViews: [5, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CommunityTabAction', @@ -2427,11 +2427,11 @@ export const WEB_EVENTS: Event[] = [ communityTabGroupNavigations: [1, 'integer'], communityTabToHomeViews: [2, 'integer'], communityTabViews: [3, 'integer'], - communityTabViewsViaContextMenu: [5, 'integer'], + communityTabViewsViaContextMenu: [5, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'Daily', @@ -2449,8 +2449,8 @@ export const WEB_EVENTS: Event[] = [ ENC_KEY_READ_FAILURE: 3, ENC_KEY_STORED_USED: 4, ENC_KEY_PLAIN_DELETED: 5, - ENC_KEY_PLAIN_RECOVERED: 6, - }, + ENC_KEY_PLAIN_RECOVERED: 6 + } ], androidXmppWorkersRuntime: [167, 'integer'], appCodeHash: [103, 'string'], @@ -2471,8 +2471,8 @@ export const WEB_EVENTS: Event[] = [ 10, { WIFI_ONLY: 0, - WIFI_OR_CELLULAR: 1, - }, + WIFI_OR_CELLULAR: 1 + } ], backupRestoreEncryptionVersion: [138, 'integer'], backupSchedule: [ @@ -2482,8 +2482,8 @@ export const WEB_EVENTS: Event[] = [ DAILY: 1, WEEKLY: 2, MONTHLY: 3, - MANUAL: 4, - }, + MANUAL: 4 + } ], channelsMediaFolderSize: [186, 'integer'], chatDatabaseSize: [19, 'integer'], @@ -2497,8 +2497,8 @@ export const WEB_EVENTS: Event[] = [ { NOT_ADDED: 1, UNVERIFIED: 2, - VERIFIED: 3, - }, + VERIFIED: 3 + } ], entSecurityNotificationsEnabled: [134, 'boolean'], experimentTmoPreloadGroupDaily: [166, 'integer'], @@ -2538,8 +2538,8 @@ export const WEB_EVENTS: Event[] = [ { ALLOWED: 1, BLOCKED: 2, - UNKNOWN: 3, - }, + UNKNOWN: 3 + } ], packageName: [102, 'string'], passkeyExists: [165, 'boolean'], @@ -2559,8 +2559,8 @@ export const WEB_EVENTS: Event[] = [ ONLY_SHARE_WITH: 2, MY_CONTACTS: 3, MY_CONTACTS_EXCEPT: 4, - EVERYONE: 5, - }, + EVERYONE: 5 + } ], privacySettingsAboutExceptNum: [ 142, @@ -2578,8 +2578,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], privacySettingsGroups: [ 143, @@ -2588,8 +2588,8 @@ export const WEB_EVENTS: Event[] = [ ONLY_SHARE_WITH: 2, MY_CONTACTS: 3, MY_CONTACTS_EXCEPT: 4, - EVERYONE: 5, - }, + EVERYONE: 5 + } ], privacySettingsGroupsExceptNum: [ 144, @@ -2607,8 +2607,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], privacySettingsLastSeen: [ 145, @@ -2617,8 +2617,8 @@ export const WEB_EVENTS: Event[] = [ ONLY_SHARE_WITH: 2, MY_CONTACTS: 3, MY_CONTACTS_EXCEPT: 4, - EVERYONE: 5, - }, + EVERYONE: 5 + } ], privacySettingsLastSeenExceptNum: [ 146, @@ -2636,8 +2636,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], privacySettingsProfilePhoto: [ 147, @@ -2646,8 +2646,8 @@ export const WEB_EVENTS: Event[] = [ ONLY_SHARE_WITH: 2, MY_CONTACTS: 3, MY_CONTACTS_EXCEPT: 4, - EVERYONE: 5, - }, + EVERYONE: 5 + } ], privacySettingsProfilePhotoExceptNum: [ 148, @@ -2665,8 +2665,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], privacySettingsStatus: [ 150, @@ -2675,8 +2675,8 @@ export const WEB_EVENTS: Event[] = [ ONLY_SHARE_WITH: 2, MY_CONTACTS: 3, MY_CONTACTS_EXCEPT: 4, - EVERYONE: 5, - }, + EVERYONE: 5 + } ], privacySettingsStatusExceptNum: [ 151, @@ -2694,8 +2694,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], privacySettingsStatusShareNum: [ 152, @@ -2713,8 +2713,8 @@ export const WEB_EVENTS: Event[] = [ B70: 11, B80: 12, B90: 13, - B100: 14, - }, + B100: 14 + } ], receiptsEnabled: [8, 'boolean'], secretCodeActive: [172, 'boolean'], @@ -2726,11 +2726,11 @@ export const WEB_EVENTS: Event[] = [ supportedDecoders: [169, 'string'], supportedEncoders: [170, 'string'], videoFolderFileCount: [23, 'integer'], - videoFolderSize: [22, 'integer'], + videoFolderSize: [22, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'KeepInChatNotif', @@ -2739,11 +2739,11 @@ export const WEB_EVENTS: Event[] = [ kicGroupNotificationTaps: [3, 'integer'], kicGroupNotifications: [4, 'integer'], kicNotificationTaps: [5, 'integer'], - kicNotifications: [6, 'integer'], + kicNotifications: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NotificationSetting', @@ -2755,16 +2755,16 @@ export const WEB_EVENTS: Event[] = [ 3, { DEFAULT: 1, - CUSTOM: 2, - }, + CUSTOM: 2 + } ], inAppNotificationAlertStyle: [ 4, { NONE: 1, BANNERS: 2, - ALERTS: 3, - }, + ALERTS: 3 + } ], inAppNotificationSound: [5, 'boolean'], inAppNotificationVibrate: [6, 'boolean'], @@ -2774,15 +2774,15 @@ export const WEB_EVENTS: Event[] = [ 9, { DEFAULT: 1, - CUSTOM: 2, - }, + CUSTOM: 2 + } ], offlineNotification: [11, 'boolean'], - showPreview: [10, 'boolean'], + showPreview: [10, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PnhDailyCtwa', @@ -2790,11 +2790,11 @@ export const WEB_EVENTS: Event[] = [ props: { matMessagesReceived: [1, 'integer'], threadDs: [3, 'string'], - threadId: [2, 'string'], + threadId: [2, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PnhDaily', @@ -2812,13 +2812,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PrivacyHighlightDaily', @@ -2830,8 +2830,8 @@ export const WEB_EVENTS: Event[] = [ privacyHighlightCategory: [ 4, { - E2EE: 0, - }, + E2EE: 0 + } ], privacyHighlightSurface: [ 5, @@ -2848,13 +2848,13 @@ export const WEB_EVENTS: Event[] = [ LINKED_DEVICES_SCREEN: 9, CALLING_SCREEN_AUDIO: 10, CALLING_SCREEN_VIDEO: 11, - SPLIT_VIEW_HOME_PLACEHOLDER: 12, - }, - ], + SPLIT_VIEW_HOME_PLACEHOLDER: 12 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PttDaily', @@ -2909,41 +2909,41 @@ export const WEB_EVENTS: Event[] = [ pttStopTapGroup: [26, 'integer'], pttStopTapIndividual: [27, 'integer'], pttStopTapInterop: [51, 'integer'], - pttStopTapNewsletter: [41, 'integer'], + pttStopTapNewsletter: [41, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ScreenLockSettingsData', id: 4802, props: { - screenAutoLockDuration: [1, 'integer'], + screenAutoLockDuration: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ScreenLockSettings', id: 3872, props: { - screenLockDuration: [1, 'integer'], + screenLockDuration: [1, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'WebcFtsStorage', id: 3642, props: { - ftsTotalSize: [1, 'integer'], + ftsTotalSize: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NotificationEngagement', @@ -2968,8 +2968,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], groupTypeClient: [ 3, @@ -2977,8 +2977,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_GROUP: 1, SUB_GROUP: 2, DEFAULT_SUB_GROUP: 3, - PARENT_GROUP: 4, - }, + PARENT_GROUP: 4 + } ], isAGroup: [4, 'boolean'], isWebBackgroundSyncNotif: [18, 'boolean'], @@ -2994,11 +2994,11 @@ export const WEB_EVENTS: Event[] = [ totalNotifRtcVoipDecline: [12, 'integer'], totalNotifShowPreview: [13, 'integer'], totalNotifShown: [14, 'integer'], - totalNotifTapToOpen: [15, 'integer'], + totalNotifTapToOpen: [15, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapHistorySyncStatusAfterPairing', @@ -3011,8 +3011,8 @@ export const WEB_EVENTS: Event[] = [ MINS_20: 2, MINS_40: 3, MINS_60: 4, - MINS_5: 5, - }, + MINS_5: 5 + } ], isLoopRunning: [12, 'boolean'], lastProcessedNotificationChunkOrder: [2, 'integer'], @@ -3026,8 +3026,8 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdHistorySyncStatusResult: [ 5, @@ -3044,19 +3044,19 @@ export const WEB_EVENTS: Event[] = [ FAIL_TO_STORE_CHUNK: 10, FAIL_TO_FETCH: 11, FAIL_TO_PREPROCESS: 12, - FAIL_TO_ENCRYPT: 13, - }, + FAIL_TO_ENCRYPT: 13 + } ], mdSessionId: [6, 'string'], mdTimestamp: [7, 'integer'], missingNotificationCount: [8, 'integer'], nextNotificationChunkOrder: [9, 'integer'], totalProcessedMessageCount: [10, 'integer'], - unprocessedNotificationCount: [11, 'integer'], + unprocessedNotificationCount: [11, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebDbVersionNonAnonymous', @@ -3076,14 +3076,14 @@ export const WEB_EVENTS: Event[] = [ WORKER_STORAGE: 9, SW: 10, WAWC: 11, - WAWC_DB_ENC: 12, - }, + WAWC_DB_ENC: 12 + } ], - webDbVersionNumber: [2, 'integer'], + webDbVersionNumber: [2, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SyncdKeyCount', @@ -3093,11 +3093,11 @@ export const WEB_EVENTS: Event[] = [ p80MuationsPerKey: [2, 'integer'], p95MuationsPerKey: [3, 'integer'], syncdSessionLengthDays: [4, 'integer'], - totalKeyCount: [5, 'integer'], + totalKeyCount: [5, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateSyncMutationStats', @@ -3113,8 +3113,8 @@ export const WEB_EVENTS: Event[] = [ LT500: 5, LT1K: 6, LT5K: 7, - GTE5K: 8, - }, + GTE5K: 8 + } ], failed: [ 2, @@ -3126,8 +3126,8 @@ export const WEB_EVENTS: Event[] = [ LT500: 5, LT1K: 6, LT5K: 7, - GTE5K: 8, - }, + GTE5K: 8 + } ], invalid: [ 3, @@ -3139,8 +3139,8 @@ export const WEB_EVENTS: Event[] = [ LT500: 5, LT1K: 6, LT5K: 7, - GTE5K: 8, - }, + GTE5K: 8 + } ], orphan: [ 4, @@ -3152,8 +3152,8 @@ export const WEB_EVENTS: Event[] = [ LT500: 5, LT1K: 6, LT5K: 7, - GTE5K: 8, - }, + GTE5K: 8 + } ], syncdAction: [5, 'string'], unsupported: [ @@ -3166,13 +3166,13 @@ export const WEB_EVENTS: Event[] = [ LT500: 5, LT1K: 6, LT5K: 7, - GTE5K: 8, - }, - ], + GTE5K: 8 + } + ] }, weight: 20, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'GroupJoinC', @@ -3180,7 +3180,7 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageHighRetryCount', @@ -3204,8 +3204,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], e2eSenderType: [ 3, @@ -3215,8 +3215,8 @@ export const WEB_EVENTS: Event[] = [ MY_COMPANION: 3, OTHER_COMPANION: 4, MY_HOSTED_COMPANION: 5, - OTHER_HOSTED_COMPANION: 6, - }, + OTHER_HOSTED_COMPANION: 6 + } ], mediaType: [ 1, @@ -3282,8 +3282,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 4, @@ -3293,14 +3293,14 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], - retryCount: [2, 'integer'], + retryCount: [2, 'integer'] }, weight: 20, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'OfflineCountTooHigh', @@ -3327,8 +3327,8 @@ export const WEB_EVENTS: Event[] = [ CALL_RELAY: 15, MUTE: 16, SCREEN_SHARE: 17, - UNKNOWN: 18, - }, + UNKNOWN: 18 + } ], mediaType: [ 3, @@ -3394,8 +3394,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 4, @@ -3405,8 +3405,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], notificationStanzaType: [6, 'string'], offlineCount: [2, 'integer'], @@ -3418,13 +3418,13 @@ export const WEB_EVENTS: Event[] = [ RECEIPT: 2, CALL: 3, NOTIFICATION: 4, - APPDATA: 5, - }, - ], + APPDATA: 5 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'E2eMessageRecv', @@ -3435,8 +3435,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 19, @@ -3444,8 +3444,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], e2eCiphertextType: [ 5, @@ -3453,8 +3453,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE: 0, PREKEY_MESSAGE: 1, SENDER_KEY_MESSAGE: 2, - MESSAGE_SECRET_MESSAGE: 3, - }, + MESSAGE_SECRET_MESSAGE: 3 + } ], e2eCiphertextVersion: [6, 'integer'], e2eDestination: [ @@ -3465,8 +3465,8 @@ export const WEB_EVENTS: Event[] = [ LIST: 2, STATUS: 3, CHANNEL: 4, - INTEROP: 5, - }, + INTEROP: 5 + } ], e2eFailureReason: [ 2, @@ -3569,8 +3569,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_INVALID_KEY_MATEIRAL_DATA_LEN: 95, ERROR_SESSION_STATE_GET_SENDER_RATCHET_KEY: 96, ERROR_SESSION_STATE_GET_LOCAL_IDENTITY_KEY: 97, - ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98, - }, + ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98 + } ], e2eSenderType: [ 8, @@ -3580,8 +3580,8 @@ export const WEB_EVENTS: Event[] = [ MY_COMPANION: 3, OTHER_COMPANION: 4, MY_HOSTED_COMPANION: 5, - OTHER_HOSTED_COMPANION: 6, - }, + OTHER_HOSTED_COMPANION: 6 + } ], e2eSuccessful: [1, 'boolean'], editType: [ @@ -3590,8 +3590,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], isHostedChat: [20, 'boolean'], isLid: [11, 'boolean'], @@ -3599,15 +3599,15 @@ export const WEB_EVENTS: Event[] = [ 16, { PN: 1, - LID: 2, - }, + LID: 2 + } ], messageAddressingMode: [ 17, { PN: 1, - LID: 2, - }, + LID: 2 + } ], messageMediaType: [ 7, @@ -3673,8 +3673,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], offline: [9, 'boolean'], retryCount: [3, 'integer'], @@ -3682,15 +3682,15 @@ export const WEB_EVENTS: Event[] = [ 10, { SENDER: 0, - ADMIN: 1, - }, + ADMIN: 1 + } ], serverAddressingMode: [ 18, { PN: 1, - LID: 2, - }, + LID: 2 + } ], stanzaType: [ 14, @@ -3699,21 +3699,21 @@ export const WEB_EVENTS: Event[] = [ RECEIPT: 2, CALL: 3, NOTIFICATION: 4, - APPDATA: 5, - }, + APPDATA: 5 + } ], typeOfGroup: [ 12, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 20, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ReceiptStanzaReceive', @@ -3785,8 +3785,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 10, @@ -3796,16 +3796,16 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], receiptAggregation: [ 11, { NONE: 0, MULTI_MESSAGES: 1, - MULTI_PARTICIPANTS: 2, - }, + MULTI_PARTICIPANTS: 2 + } ], receiptStanzaDuration: [1, 'timer'], receiptStanzaHasOrphaned: [6, 'boolean'], @@ -3820,15 +3820,15 @@ export const WEB_EVENTS: Event[] = [ WAITING_TO_PROCESS: 2, PROCESS: 3, WAITING_TO_ACK: 5, - ACK: 4, - }, + ACK: 4 + } ], receiptStanzaTotalCount: [7, 'integer'], - receiptStanzaType: [4, 'string'], + receiptStanzaType: [4, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CtwaActionBannerUnderstand', @@ -3845,25 +3845,25 @@ export const WEB_EVENTS: Event[] = [ 8, { LOCAL: 0, - UNIVERSAL: 1, - }, + UNIVERSAL: 1 + } ], validLocale: [9, 'boolean'], - validNotification: [10, 'boolean'], + validNotification: [10, 'boolean'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'WaOldCode', id: 3940, props: { - deviceId: [1, 'string'], + deviceId: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NotificationDelivery', @@ -3874,8 +3874,8 @@ export const WEB_EVENTS: Event[] = [ 2, { SHOW: 1, - REMOVE: 2, - }, + REMOVE: 2 + } ], notificationDeliveryT: [3, 'integer'], notificationDestination: [ @@ -3885,8 +3885,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, OTHER: 3, CHANNEL: 4, - INTEROP: 5, - }, + INTEROP: 5 + } ], notificationId: [5, 'string'], notificationSource: [ @@ -3894,8 +3894,8 @@ export const WEB_EVENTS: Event[] = [ { PUSH_TRIGGERED: 1, MAIN_APP: 2, - IN_APP: 3, - }, + IN_APP: 3 + } ], threadId: [7, 'string'], uiNotificationType: [ @@ -3929,33 +3929,33 @@ export const WEB_EVENTS: Event[] = [ OTHER: 26, INVITE_JOINED: 27, SCHEDULED_CALL_LOCAL_REMINDER: 28, - PTV_MESSAGE: 29, - }, - ], + PTV_MESSAGE: 29 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateDirtyBits', id: 2520, props: { - dirtyBitsFalsePositive: [2, 'boolean'], + dirtyBitsFalsePositive: [2, 'boolean'] }, weight: 1000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdAppStateOfflineNotifications', id: 2602, props: { - redundantCount: [1, 'integer'], + redundantCount: [1, 'integer'] }, weight: 1000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdRetryFromUnknownDevice', @@ -3966,13 +3966,13 @@ export const WEB_EVENTS: Event[] = [ 1, { PRIMARY: 1, - COMPANION: 2, - }, - ], + COMPANION: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'E2eRetryReject', @@ -3986,8 +3986,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], msgRetryCount: [2, 'integer'], retryRejectReason: [ @@ -3996,31 +3996,31 @@ export const WEB_EVENTS: Event[] = [ OTHER: 0, DOUBLE_CHECKMARK: 1, IDENTITY_CHANGE: 2, - MESSAGE_NOT_EXIST: 3, - }, + MESSAGE_NOT_EXIST: 3 + } ], retryRevoke: [4, 'boolean'], senderDeviceType: [ 5, { PRIMARY: 1, - COMPANION: 2, - }, - ], + COMPANION: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ClockSkewDifferenceT', id: 3178, props: { - clockSkewHourly: [1, 'integer'], + clockSkewHourly: [1, 'integer'] }, weight: 10000, wamChannel: 'private', - privateStatsIdInt: 37887164, + privateStatsIdInt: 37887164 }, { name: 'MdBootstrapAppStateCriticalDataProcessing', @@ -4039,23 +4039,23 @@ export const WEB_EVENTS: Event[] = [ APPLIED_MUTATIONS: 8, PUSHNAME_APPLIED: 9, PUSHNAME_INVALID: 10, - ENTERED_RETRY_MODE: 11, - }, + ENTERED_RETRY_MODE: 11 + } ], mdBootstrapPayloadType: [ 2, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdRegAttemptId: [3, 'string'], mdSessionId: [4, 'string'], - mdTimestamp: [5, 'integer'], + mdTimestamp: [5, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcNativeUpsellCta', @@ -4066,8 +4066,8 @@ export const WEB_EVENTS: Event[] = [ { IMPRESSION: 1, CTA_BTN_CLICK: 2, - CTA_DISMISS: 3, - }, + CTA_DISMISS: 3 + } ], webcNativeUpsellCtaSource: [ 1, @@ -4081,13 +4081,13 @@ export const WEB_EVENTS: Event[] = [ CALL_BTN_MODAL_2: 7, MISSED_CALL_MODAL: 8, MISSED_CALL_MODAL_2: 9, - QR_BANNER_2: 10, - }, - ], + QR_BANNER_2: 10 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebDbTableUsage', @@ -4097,23 +4097,23 @@ export const WEB_EVENTS: Event[] = [ webScenario: [ 2, { - OFFLINE_RESUME: 0, - }, + OFFLINE_RESUME: 0 + } ], webTable: [3, 'string'], webTableLogReason: [ 4, { BASE: 0, - EXCEEDED_THRESHOLD: 1, - }, + EXCEEDED_THRESHOLD: 1 + } ], webTableReadCount: [5, 'integer'], - webTableWriteCount: [6, 'integer'], + webTableWriteCount: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebDbLoadFromVersionFailureNonAnonymous', @@ -4124,8 +4124,8 @@ export const WEB_EVENTS: Event[] = [ { MAIN: 1, WEB_WORKER: 2, - SERVICE_WORKER: 3, - }, + SERVICE_WORKER: 3 + } ], webDbName: [ 2, @@ -4141,13 +4141,13 @@ export const WEB_EVENTS: Event[] = [ WORKER_STORAGE: 9, SW: 10, WAWC: 11, - WAWC_DB_ENC: 12, - }, - ], + WAWC_DB_ENC: 12 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcJobInfo', @@ -4161,8 +4161,8 @@ export const WEB_EVENTS: Event[] = [ COMPLETED: 0, ERROR: 1, TIMEOUT: 2, - ABORTED: 3, - }, + ABORTED: 3 + } ], pendingJobsCount: [4, 'integer'], scenario: [ @@ -4170,26 +4170,26 @@ export const WEB_EVENTS: Event[] = [ { INITIAL_PAIRING: 0, OFFLINE_RESUME: 1, - IDLE: 2, - }, + IDLE: 2 + } ], webcJobAddedT: [6, 'integer'], webcJobCompletedT: [8, 'integer'], - webcJobStartedT: [7, 'integer'], + webcJobStartedT: [7, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcRawPlatforms', id: 2416, props: { - webcRawPlatform: [1, 'string'], + webcRawPlatform: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebWamForceFlush', @@ -4197,7 +4197,7 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatMute', @@ -4208,15 +4208,15 @@ export const WEB_EVENTS: Event[] = [ { MUTE: 1, UNMUTE: 2, - EXPIRE: 3, - }, + EXPIRE: 3 + } ], chatMuteNotificationChoice: [ 5, { NO_NOTIFICATIONS_WHEN_MUTED: 1, - YES_NOTIFICATIONS_WHEN_MUTED: 2, - }, + YES_NOTIFICATIONS_WHEN_MUTED: 2 + } ], muteChatType: [ 4, @@ -4224,8 +4224,8 @@ export const WEB_EVENTS: Event[] = [ ONE_ON_ONE: 1, GROUP: 2, CHANNEL: 3, - INTEROP: 4, - }, + INTEROP: 4 + } ], muteDuration: [1, 'timer'], muteEntryPoint: [ @@ -4233,20 +4233,20 @@ export const WEB_EVENTS: Event[] = [ { CHAT_LIST_SCREEN: 1, CONTACT_INFO: 2, - CONVERSATION_SCREEN: 3, - }, + CONVERSATION_SCREEN: 3 + } ], muteGroupSize: [2, 'integer'], waOfficialAccountName: [ 7, { - WHATSAPP_CHATPSA: 1, - }, - ], + WHATSAPP_CHATPSA: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatPsaAction', @@ -4316,8 +4316,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], psaCampaignId: [4, 'string'], psaMessageActionType: [ @@ -4328,14 +4328,14 @@ export const WEB_EVENTS: Event[] = [ REACT: 3, LINK_CLICK: 4, MEDIA_PLAY: 5, - DELETE: 6, - }, + DELETE: 6 + } ], - psaMsgId: [5, 'string'], + psaMsgId: [5, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatPsaRead', @@ -4405,8 +4405,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], psaCampaignId: [4, 'string'], psaMsgId: [5, 'string'], @@ -4414,13 +4414,13 @@ export const WEB_EVENTS: Event[] = [ 3, { CHAT_LIST: 1, - CHAT: 2, - }, - ], + CHAT: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatPsaRemove', @@ -4490,8 +4490,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], lastReceivedMessageTs: [4, 'timer'], lastReceivedMsgId: [8, 'string'], @@ -4501,8 +4501,8 @@ export const WEB_EVENTS: Event[] = [ OTHER: 0, MESSAGES_ARENT_HELPFUL: 1, TOO_MANY_MESSAGES: 2, - IT_LOOKS_SUSPICIOUS: 3, - }, + IT_LOOKS_SUSPICIOUS: 3 + } ], psaCampaignId: [9, 'string'], psaMessageRemoveAction: [ @@ -4513,8 +4513,8 @@ export const WEB_EVENTS: Event[] = [ ARCHIVE: 3, UNARCHIVE: 4, CLEAR: 5, - DELETE_ALL: 6, - }, + DELETE_ALL: 6 + } ], psaMessageRemoveEntryPoint: [ 6, @@ -4533,19 +4533,19 @@ export const WEB_EVENTS: Event[] = [ DELETE_ALL_FROM_CONTACT_INFO: 12, DELETE_ALL_FROM_CONVERSATION: 13, CLEAR_FROM_CONVERSATION: 14, - BLOCK_FROM_CONSENT_MODAL: 15, - }, + BLOCK_FROM_CONSENT_MODAL: 15 + } ], waOfficialAccountName: [ 7, { - WHATSAPP_CHATPSA: 1, - }, - ], + WHATSAPP_CHATPSA: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ForwardSend', @@ -4558,8 +4558,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], e2eCiphertextType: [ 12, @@ -4567,8 +4567,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE: 0, PREKEY_MESSAGE: 1, SENDER_KEY_MESSAGE: 2, - MESSAGE_SECRET_MESSAGE: 3, - }, + MESSAGE_SECRET_MESSAGE: 3 + } ], e2eCiphertextVersion: [11, 'integer'], ephemeralityDuration: [18, 'integer'], @@ -4577,8 +4577,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], ephemeralityTriggerAction: [ 25, @@ -4587,8 +4587,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], fastForwardEnabled: [5, 'boolean'], isForwardedForward: [22, 'boolean'], @@ -4601,8 +4601,8 @@ export const WEB_EVENTS: Event[] = [ API_MARKETING: 1, API_UTILITY: 2, OTHER_API_BIZ_MSG: 3, - SMB_BIZ_MSG: 4, - }, + SMB_BIZ_MSG: 4 + } ], messageForwardAgeT: [4, 'timer'], messageIsFanout: [6, 'boolean'], @@ -4672,8 +4672,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageSendT: [13, 'timer'], messageType: [ @@ -4684,8 +4684,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], receiverDefaultDisappearingDuration: [20, 'integer'], resendCount: [8, 'integer'], @@ -4696,15 +4696,15 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, + DEFAULT_SUBGROUP: 3 + } ], wouldBeFrequentlyForwardedAt3: [16, 'boolean'], - wouldBeFrequentlyForwardedAt4: [17, 'boolean'], + wouldBeFrequentlyForwardedAt4: [17, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NonMessagePeerDataMediaUpload', @@ -4722,8 +4722,8 @@ export const WEB_EVENTS: Event[] = [ SEND_RECENT_STICKER_BOOTSTRAP: 1, GENERAL_LINK_PREVIEW: 2, HISTORY_SYNC_ON_DEMAND: 3, - PLACEHOLDER_MESSAGE_RESEND: 4, - }, + PLACEHOLDER_MESSAGE_RESEND: 4 + } ], peerDataResponseResult: [ 8, @@ -4734,15 +4734,15 @@ export const WEB_EVENTS: Event[] = [ REQUEST_INVALID: 4, FAIL_TO_UPLOAD: 5, FAIL_TO_SEND_RESPONSE: 6, - REQUEST_TOO_OLD: 7, - }, + REQUEST_TOO_OLD: 7 + } ], peerDataSuccessInlineNoUploadCount: [9, 'integer'], - peerDataSuccessUploadCount: [7, 'integer'], + peerDataSuccessUploadCount: [7, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NonMessagePeerDataOperationResponse', @@ -4758,8 +4758,8 @@ export const WEB_EVENTS: Event[] = [ SEND_RECENT_STICKER_BOOTSTRAP: 1, GENERAL_LINK_PREVIEW: 2, HISTORY_SYNC_ON_DEMAND: 3, - PLACEHOLDER_MESSAGE_RESEND: 4, - }, + PLACEHOLDER_MESSAGE_RESEND: 4 + } ], peerDataResponseApplyResult: [ 8, @@ -4768,16 +4768,16 @@ export const WEB_EVENTS: Event[] = [ OTHER_ERROR: 2, INVALID_RESPONSE: 3, FAIL_TO_DOWNLOAD: 4, - REQUEST_TIMEOUT: 5, - }, + REQUEST_TIMEOUT: 5 + } ], peerDataResponseCount: [5, 'integer'], peerDataSuccessProcessCount: [6, 'integer'], - peerDataSuccessResponseCount: [7, 'integer'], + peerDataSuccessResponseCount: [7, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'NonMessagePeerDataRequest', @@ -4792,13 +4792,13 @@ export const WEB_EVENTS: Event[] = [ SEND_RECENT_STICKER_BOOTSTRAP: 1, GENERAL_LINK_PREVIEW: 2, HISTORY_SYNC_ON_DEMAND: 3, - PLACEHOLDER_MESSAGE_RESEND: 4, - }, - ], + PLACEHOLDER_MESSAGE_RESEND: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcLinkPreviewResponseHandle', @@ -4807,11 +4807,11 @@ export const WEB_EVENTS: Event[] = [ didRespondHqPreview: [5, 'boolean'], isPreviewSuccess: [2, 'boolean'], previewDurationMs: [4, 'integer'], - previewSessionId: [3, 'string'], + previewSessionId: [3, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapHistoryDataDownloaded', @@ -4829,8 +4829,8 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdBootstrapMessagesCount: [5, 'integer'], mdBootstrapPayloadSize: [4, 'integer'], @@ -4838,27 +4838,27 @@ export const WEB_EVENTS: Event[] = [ 2, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdBootstrapStepDuration: [7, 'integer'], mdBootstrapStepResult: [ 8, { SUCCESS: 1, - FAILURE: 2, - }, + FAILURE: 2 + } ], mdHsOldestMessageTimestamp: [11, 'integer'], mdRegAttemptId: [12, 'string'], mdSessionId: [1, 'string'], mdStorageQuotaBytes: [9, 'integer'], mdStorageQuotaUsedBytes: [10, 'integer'], - mdTimestamp: [3, 'integer'], + mdTimestamp: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapHistoryDataStartDownloading', @@ -4875,24 +4875,24 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdBootstrapPayloadSize: [4, 'integer'], mdBootstrapPayloadType: [ 5, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdBootstrapStepDuration: [6, 'integer'], mdSessionId: [7, 'string'], - mdTimestamp: [8, 'integer'], + mdTimestamp: [8, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'E2eMessageSend', @@ -4903,8 +4903,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 17, @@ -4912,8 +4912,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], e2eCiphertextType: [ 5, @@ -4921,8 +4921,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE: 0, PREKEY_MESSAGE: 1, SENDER_KEY_MESSAGE: 2, - MESSAGE_SECRET_MESSAGE: 3, - }, + MESSAGE_SECRET_MESSAGE: 3 + } ], e2eCiphertextVersion: [6, 'integer'], e2eDestination: [ @@ -4933,8 +4933,8 @@ export const WEB_EVENTS: Event[] = [ LIST: 2, STATUS: 3, CHANNEL: 4, - INTEROP: 5, - }, + INTEROP: 5 + } ], e2eFailureReason: [ 2, @@ -5037,15 +5037,15 @@ export const WEB_EVENTS: Event[] = [ ERROR_INVALID_KEY_MATEIRAL_DATA_LEN: 95, ERROR_SESSION_STATE_GET_SENDER_RATCHET_KEY: 96, ERROR_SESSION_STATE_GET_LOCAL_IDENTITY_KEY: 97, - ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98, - }, + ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98 + } ], e2eReceiverType: [ 8, { PRIMARY: 1, - COMPANION: 2, - }, + COMPANION: 2 + } ], e2eSuccessful: [1, 'boolean'], editType: [ @@ -5054,8 +5054,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], encRetryCount: [9, 'integer'], isLid: [12, 'boolean'], @@ -5063,8 +5063,8 @@ export const WEB_EVENTS: Event[] = [ 16, { PN: 1, - LID: 2, - }, + LID: 2 + } ], messageIsInvisible: [10, 'boolean'], messageMediaType: [ @@ -5131,29 +5131,29 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], retryCount: [3, 'integer'], revokeType: [ 11, { SENDER: 0, - ADMIN: 1, - }, + ADMIN: 1 + } ], typeOfGroup: [ 13, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PrekeysDepletion', @@ -5177,8 +5177,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], messageType: [ 2, @@ -5188,8 +5188,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], prekeysFetchReason: [ 1, @@ -5206,13 +5206,13 @@ export const WEB_EVENTS: Event[] = [ USER_INTENT_PREFETCH: 10, RESEND_MESSAGE: 11, RETRY_MESSAGE: 12, - USER_INTENT_STATUS_PREFETCH: 13, - }, - ], + USER_INTENT_STATUS_PREFETCH: 13 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AndroidMessageSendPerf', @@ -5240,8 +5240,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], durationAbs: [11, 'timer'], durationRelative: [12, 'timer'], @@ -5252,8 +5252,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], fetchPrekeys: [15, 'boolean'], fetchPrekeysPercentage: [21, 'integer'], @@ -5275,8 +5275,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], isDirectedMessage: [33, 'boolean'], isE2eBackfill: [27, 'boolean'], @@ -5350,8 +5350,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageIsFirstUserMessage: [30, 'boolean'], messageIsInvisible: [31, 'boolean'], @@ -5363,8 +5363,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], networkWasDisconnected: [14, 'boolean'], participantCount: [37, 'integer'], @@ -5383,8 +5383,8 @@ export const WEB_EVENTS: Event[] = [ CLIENT_WAITING_TO_ENCRYPT: 5, CLIENT_READY_TO_SEND: 6, CLIENT_ENCRYPT: 7, - CLIENT_PREKEYS_FETCH: 8, - }, + CLIENT_PREKEYS_FETCH: 8 + } ], senderDeviceCount: [40, 'integer'], senderKeyDistributionCountPercentage: [23, 'integer'], @@ -5407,8 +5407,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], threadsInExecution: [19, 'integer'], typeOfGroup: [ @@ -5416,13 +5416,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 2000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdDeviceSyncAck', @@ -5436,47 +5436,47 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, STATUS: 3, BROADCAST: 4, - CHANNEL: 5, - }, + CHANNEL: 5 + } ], isLid: [3, 'boolean'], localAddressingMode: [ 5, { PN: 1, - LID: 2, - }, + LID: 2 + } ], revoke: [2, 'boolean'], serverAddressingMode: [ 6, { PN: 1, - LID: 2, - }, + LID: 2 + } ], typeOfGroup: [ 4, { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AdvMetadataCreationFailure', id: 3048, props: { - advMetadataIsMe: [1, 'boolean'], + advMetadataIsMe: [1, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdGroupParticipantMissAck', @@ -5500,8 +5500,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], isLid: [2, 'boolean'], messageIsRevoke: [3, 'boolean'], @@ -5512,13 +5512,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CrashLog', @@ -5547,34 +5547,34 @@ export const WEB_EVENTS: Event[] = [ UFAD: 11, EXPERIMENTAL_UFAD_DETECTION: 12, UX_BREAKING_EXCEPTION: 13, - UX_GRACEFUL_RECOVERY_EXCEPTION: 14, - }, - ], + UX_GRACEFUL_RECOVERY_EXCEPTION: 14 + } + ] }, weight: 100, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcPageResume', id: 884, props: { - webcResumeCount: [1, 'integer'], + webcResumeCount: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcPhoneDisconnected', id: 878, props: { webcPhoneBbarShownT: [2, 'timer'], - webcPhoneDisconnectedT: [1, 'timer'], + webcPhoneDisconnectedT: [1, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdSyncdDogfoodingFeatureUsage', @@ -5587,13 +5587,13 @@ export const WEB_EVENTS: Event[] = [ UNPIN_4TH_CHAT_MUTATION: 2, DELETE_MUTATION: 3, CLEAR_CHAT_REMOVE_STARRED_MUTATION: 4, - CLEAR_CHAT_KEEP_STARRED_MUTATION: 5, - }, - ], + CLEAR_CHAT_KEEP_STARRED_MUTATION: 5 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdFatalError', @@ -5606,8 +5606,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_LOW: 2, REGULAR_HIGH: 3, CRITICAL_BLOCK: 4, - CRITICAL_UNBLOCK_LOW: 5, - }, + CRITICAL_UNBLOCK_LOW: 5 + } ], currentPrimaryAppVersion: [14, 'string'], daysSinceLastPeriodicSync: [11, 'integer'], @@ -5735,8 +5735,8 @@ export const WEB_EVENTS: Event[] = [ MALFORMED_MUTATION_DELETE_CHAT: 99, FAILED_MUTATION_CLEAR_CHAT: 100, FAILED_MUTATION_DELETE_CHAT: 101, - CHAT_DB_CORRUPTION: 102, - }, + CHAT_DB_CORRUPTION: 102 + } ], patchSnapshotMutationCount: [9, 'integer'], patchVersion: [5, 'integer'], @@ -5746,16 +5746,16 @@ export const WEB_EVENTS: Event[] = [ { SNAPSHOT: 1, EXTERNAL_PATCH: 2, - INLINE_PATCH: 3, - }, + INLINE_PATCH: 3 + } ], timeSincePairingMs: [6, 'integer'], timeSinceRefreshMs: [7, 'integer'], - timeSinceTabTakeoverMs: [8, 'integer'], + timeSinceTabTakeoverMs: [8, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MediaUpload2', @@ -5767,8 +5767,8 @@ export const WEB_EVENTS: Event[] = [ { HOSTNAME: 0, IP4: 1, - IP6: 2, - }, + IP6: 2 + } ], debugMediaException: [34, 'string'], debugMediaIp: [32, 'string'], @@ -5785,8 +5785,8 @@ export const WEB_EVENTS: Event[] = [ { HTTP1: 0, HTTP2: 1, - HTTP3: 2, - }, + HTTP3: 2 + } ], isViewOnce: [49, 'boolean'], mediaId: [46, 'integer'], @@ -5799,8 +5799,8 @@ export const WEB_EVENTS: Event[] = [ LIGER: 2, APACHE: 3, WATLS: 4, - CRONET: 5, - }, + CRONET: 5 + } ], originalSize: [53, 'integer'], overallAttemptCount: [4, 'integer'], @@ -5818,8 +5818,8 @@ export const WEB_EVENTS: Event[] = [ { RESUME_CHECK: 1, UPLOAD: 2, - FINALIZE: 3, - }, + FINALIZE: 3 + } ], overallMediaKeyReuse: [ 40, @@ -5827,8 +5827,8 @@ export const WEB_EVENTS: Event[] = [ NONE_NEW_CONTENT: 1, NONE_EXPIRED: 2, REUSED: 3, - NONE_WAS_STATUS: 4, - }, + NONE_WAS_STATUS: 4 + } ], overallMediaSize: [7, 'number'], overallMediaType: [ @@ -5895,8 +5895,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], overallMmsVersion: [6, 'integer'], overallOptimisticFlag: [ @@ -5906,8 +5906,8 @@ export const WEB_EVENTS: Event[] = [ OPTIMISTIC: 1, OPT_USED: 2, OPT_TAKEOVER: 3, - OPT_DISABLED: 4, - }, + OPT_DISABLED: 4 + } ], overallQueueT: [9, 'timer'], overallRetryCount: [3, 'integer'], @@ -5923,8 +5923,8 @@ export const WEB_EVENTS: Event[] = [ MEDIA_RETRY: 5, WEB_REUPLOAD: 6, THUMBNAIL: 7, - EXPRESS_PATH_UPLOAD: 8, - }, + EXPRESS_PATH_UPLOAD: 8 + } ], overallUploadOrigin: [ 44, @@ -5941,8 +5941,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL: 10, BROADCAST: 11, MULTI_CHAT: 12, - INTEROP: 13, - }, + INTEROP: 13 + } ], overallUploadResult: [ 35, @@ -5987,8 +5987,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_NO_ENCRYPTION_ALGORITHM: 38, ERROR_HOST_SWITCH_REQUIRED: 39, ERROR_WAMSYS: 40, - ERROR_INVALID_URL: 41, - }, + ERROR_INVALID_URL: 41 + } ], overallUserVisibleT: [14, 'timer'], photoQualitySetting: [ @@ -5997,8 +5997,8 @@ export const WEB_EVENTS: Event[] = [ AUTO: 0, DATA_SAVER: 1, HIGH_QUALITY: 2, - HIGHEST_QUALITY: 3, - }, + HIGHEST_QUALITY: 3 + } ], resumeConnectT: [17, 'timer'], resumeHttpCode: [20, 'integer'], @@ -6017,8 +6017,8 @@ export const WEB_EVENTS: Event[] = [ OTHER: 1, CAMERA: 2, GALLERY: 3, - SHARE: 4, - }, + SHARE: 4 + } ], usedFallbackHint: [47, 'string'], videoQualitySetting: [ @@ -6027,13 +6027,13 @@ export const WEB_EVENTS: Event[] = [ AUTO: 0, DATA_SAVER: 1, HIGH_QUALITY: 2, - HIGHEST_QUALITY: 3, - }, - ], + HIGHEST_QUALITY: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMediaErrorUnknownDetails', @@ -6046,13 +6046,13 @@ export const WEB_EVENTS: Event[] = [ 2, { DOWNLOAD: 1, - UPLOAD: 2, - }, - ], + UPLOAD: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMediaLoad', @@ -6063,14 +6063,14 @@ export const WEB_EVENTS: Event[] = [ { SUCCESS: 0, SILENCE: 1, - ZEROWIDTH: 2, - }, + ZEROWIDTH: 2 + } ], - webcMediaLoadT: [1, 'timer'], + webcMediaLoadT: [1, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StickerError', @@ -6081,13 +6081,13 @@ export const WEB_EVENTS: Event[] = [ { DECOMPRESSION: 2, SENDER_VALIDATION: 3, - RECEIVER_VALIDATION: 4, - }, - ], + RECEIVER_VALIDATION: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StickerLatency', @@ -6097,14 +6097,14 @@ export const WEB_EVENTS: Event[] = [ stickerLatencyAction: [ 2, { - DECOMPRESSION: 2, - }, + DECOMPRESSION: 2 + } ], - stickerLatencyTtAction: [3, 'integer'], + stickerLatencyTtAction: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcProgressiveImage', @@ -6113,11 +6113,11 @@ export const WEB_EVENTS: Event[] = [ webcFirstRenderScans: [1, 'integer'], webcFirstRenderT: [2, 'timer'], webcFullQualityT: [4, 'timer'], - webcMidQualityT: [3, 'timer'], + webcMidQualityT: [3, 'timer'] }, weight: 10, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SendDocument', @@ -6134,13 +6134,13 @@ export const WEB_EVENTS: Event[] = [ DOCUMENT: 5, COMPRESSED_FILE: 6, EXECUTABLE: 7, - VCARD: 8, - }, - ], + VCARD: 8 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMediaAnalyzed', @@ -6148,11 +6148,11 @@ export const WEB_EVENTS: Event[] = [ props: { webcMediaAnalyzeT: [3, 'timer'], webcMediaExtensions: [2, 'string'], - webcMediaSupported: [1, 'boolean'], + webcMediaSupported: [1, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'VideoTranscoder', @@ -6166,8 +6166,8 @@ export const WEB_EVENTS: Event[] = [ { SLOMO: 0, VIDEO: 1, - GIF: 2, - }, + GIF: 2 + } ], sourceFrameRate: [13, 'number'], sourceHeight: [10, 'number'], @@ -6181,8 +6181,8 @@ export const WEB_EVENTS: Event[] = [ { IMAGE: 0, VIDEO: 1, - GIF: 2, - }, + GIF: 2 + } ], targetFrameRate: [21, 'number'], targetHeight: [18, 'number'], @@ -6192,8 +6192,8 @@ export const WEB_EVENTS: Event[] = [ 1, { WA_IPHONE: 0, - FB_IPHONE: 1, - }, + FB_IPHONE: 1 + } ], transcoderContainsVideocomposition: [5, 'boolean'], transcoderHasEdits: [6, 'boolean'], @@ -6204,14 +6204,14 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, SUCCEEDED: 1, FAILED: 2, - CANCELLED: 3, - }, + CANCELLED: 3 + } ], - transcoderT: [3, 'timer'], + transcoderT: [3, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StickerSend', @@ -6232,15 +6232,15 @@ export const WEB_EVENTS: Event[] = [ WEB_STICKER_MAKER: 2, IOS_STICKER_MAKER: 3, ANDROID_STICKER_MAKER: 4, - TRANSPARENT_IMAGE: 5, - }, + TRANSPARENT_IMAGE: 5 + } ], stickerSendMessageType: [ 4, { REGULAR: 1, - PAYMENTS: 2, - }, + PAYMENTS: 2 + } ], stickerSendOrigin: [ 1, @@ -6257,13 +6257,13 @@ export const WEB_EVENTS: Event[] = [ AI_STICKER_CREATE: 10, AI_STICKER_CREATE_TRAY: 11, AI_STICKER_CREATE_CHAT: 12, - STATUS_QUICK_REPLY: 13, - }, - ], + STATUS_QUICK_REPLY: 13 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MediaDownload2', @@ -6275,8 +6275,8 @@ export const WEB_EVENTS: Event[] = [ { HOSTNAME: 0, IP4: 1, - IP6: 2, - }, + IP6: 2 + } ], daysSinceReceive: [46, 'integer'], debugMediaException: [24, 'string'], @@ -6297,8 +6297,8 @@ export const WEB_EVENTS: Event[] = [ INELIGIBLE_IMAGE_TOO_SMALL: 3, INELIGIBLE_PARTIAL_HASHES_NOT_FOUND: 4, ERROR_DETERMINING_ELIGIBILITY: 5, - NOT_DOWNLOADED_ENOUGH_BYTES_TO_DETERMINE_ELIGIBILITY: 6, - }, + NOT_DOWNLOADED_ENOUGH_BYTES_TO_DETERMINE_ELIGIBILITY: 6 + } ], downloadResumePoint: [14, 'integer'], downloadTimeToFirstByteT: [21, 'timer'], @@ -6313,8 +6313,8 @@ export const WEB_EVENTS: Event[] = [ { HTTP1: 0, HTTP2: 1, - HTTP3: 2, - }, + HTTP3: 2 + } ], isSenderPlatformCapi: [52, 'boolean'], isViewOnce: [41, 'boolean'], @@ -6327,8 +6327,8 @@ export const WEB_EVENTS: Event[] = [ LIGER: 2, APACHE: 3, WATLS: 4, - CRONET: 5, - }, + CRONET: 5 + } ], overallAttemptCount: [4, 'integer'], overallBackendStore: [ @@ -6340,8 +6340,8 @@ export const WEB_EVENTS: Event[] = [ OIL: 3, EXPRESS_PATH: 4, STATIC: 5, - MANIFOLD: 6, - }, + MANIFOLD: 6 + } ], overallConnBlockFetchT: [10, 'timer'], overallConnectionClass: [29, 'string'], @@ -6357,8 +6357,8 @@ export const WEB_EVENTS: Event[] = [ PREFETCH: 3, HEADER: 4, THUMBNAIL: 5, - EXPRESS_PATH_DOWNLOAD: 6, - }, + EXPRESS_PATH_DOWNLOAD: 6 + } ], overallDownloadOrigin: [ 35, @@ -6377,8 +6377,8 @@ export const WEB_EVENTS: Event[] = [ COMMUNITY: 12, CHANNEL: 13, BROADCAST: 14, - INTEROP: 15, - }, + INTEROP: 15 + } ], overallDownloadResult: [ 25, @@ -6420,8 +6420,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_NO_MEDIA_HASH: 36, ERROR_NO_MEDIA_KEY: 37, ERROR_NO_SIDECAR: 38, - ERROR_HASH_VERIFICATION_FAILURE: 39, - }, + ERROR_HASH_VERIFICATION_FAILURE: 39 + } ], overallFileValidationT: [13, 'timer'], overallIsEncrypted: [28, 'boolean'], @@ -6491,19 +6491,19 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], overallMmsVersion: [6, 'integer'], overallQueueT: [9, 'timer'], overallRetryCount: [3, 'integer'], overallT: [8, 'timer'], sleepModeAffected: [51, 'boolean'], - usedFallbackHint: [40, 'string'], + usedFallbackHint: [40, 'string'] }, weight: 50, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMediaRmr', @@ -6573,8 +6573,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], webcBrowserNetworkType: [2, 'string'], webcBrowserStorageQuotaBytes: [11, 'integer'], @@ -6587,8 +6587,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 1, BROADCAST_LIST: 2, COMMUNITY: 3, - NEWSLETTER: 4, - }, + NEWSLETTER: 4 + } ], webcMediaRmrError: [8, 'boolean'], webcMediaRmrT: [6, 'timer'], @@ -6613,14 +6613,14 @@ export const WEB_EVENTS: Event[] = [ MSG_INIT: 12, MSG_UPDATE: 13, MSG_DELETE: 14, - MSG_RENDER: 15, - }, + MSG_RENDER: 15 + } ], - webcRmrStatusCode: [13, 'integer'], + webcRmrStatusCode: [13, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BlockEventsFs', @@ -6678,15 +6678,15 @@ export const WEB_EVENTS: Event[] = [ BIZ_BLOCK_LIST: 46, BIZ_CALL_LOG_BLOCK: 47, ONGOING_CALL_LINK_BLOCK: 48, - ONE_TO_ONE_BLOCKED_CHAT_COMPOSER: 49, - }, + ONE_TO_ONE_BLOCKED_CHAT_COMPOSER: 49 + } ], blockEventActionType: [ 2, { BLOCK: 0, - UNBLOCK: 1, - }, + UNBLOCK: 1 + } ], blockEventIsSuspicious: [3, 'boolean'], blockEventIsUnsub: [4, 'boolean'], @@ -6722,13 +6722,13 @@ export const WEB_EVENTS: Event[] = [ CALL_IS_FULL: 25, SILENCED: 26, CALL_MISSED_SILENCED: 27, - CALL_DOES_NOT_EXIST_FOR_REJOIN: 28, - }, - ], + CALL_DOES_NOT_EXIST_FOR_REJOIN: 28 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SignCredential', @@ -6738,8 +6738,8 @@ export const WEB_EVENTS: Event[] = [ 6, { FOREGROUND: 1, - BACKGROUND: 2, - }, + BACKGROUND: 2 + } ], isFromWameta: [8, 'boolean'], overallT: [4, 'timer'], @@ -6747,8 +6747,8 @@ export const WEB_EVENTS: Event[] = [ 7, { DIT: 1, - DIRECTORY_SEARCH: 2, - }, + DIRECTORY_SEARCH: 2 + } ], retryCount: [2, 'integer'], signCredentialResult: [ @@ -6758,15 +6758,15 @@ export const WEB_EVENTS: Event[] = [ ERROR_BAD_REQUEST: 2, ERROR_SERVER: 3, ERROR_OTHER: 4, - ERROR_CLIENT_NETWORK: 5, - }, + ERROR_CLIENT_NETWORK: 5 + } ], signCredentialT: [3, 'timer'], - waConnectedToChatd: [5, 'boolean'], + waConnectedToChatd: [5, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PsBufferUpload', @@ -6776,8 +6776,8 @@ export const WEB_EVENTS: Event[] = [ 6, { FOREGROUND: 1, - BACKGROUND: 2, - }, + BACKGROUND: 2 + } ], isFromWamsys: [12, 'boolean'], isUserSampled: [14, 'boolean'], @@ -6796,8 +6796,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_SERVER_OTHER: 8, SKIPPED_NO_NETWORK: 9, SKIPPED_NO_DATA: 10, - ERROR_ACCESS_TOKEN: 11, - }, + ERROR_ACCESS_TOKEN: 11 + } ], psBufferUploadT: [2, 'timer'], psDitheredT: [11, 'integer'], @@ -6820,21 +6820,21 @@ export const WEB_EVENTS: Event[] = [ REASON_LAST_SIGNREQ_OTHER_ERROR: 12, REASON_WAIT_FOR_GEN_TOKEN: 13, REASON_GEN_SHAREDKEY_FAILURE: 14, - REASON_WAIT_FOR_GEN_FIRST_TOKEN: 15, - }, + REASON_WAIT_FOR_GEN_FIRST_TOKEN: 15 + } ], psUploadReason: [ 9, { REASON_PS_PINGER: 0, - REASON_PS_OFFCYCLE: 1, - }, + REASON_PS_OFFCYCLE: 1 + } ], - waConnectedToChatd: [5, 'boolean'], + waConnectedToChatd: [5, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EditMessageSend', @@ -6847,8 +6847,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], mediaType: [ 8, @@ -6914,8 +6914,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageSendResultIsTerminal: [3, 'boolean'], messageType: [ @@ -6926,8 +6926,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], resendCount: [5, 'integer'], retryCount: [6, 'integer'], @@ -6936,13 +6936,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageSend', @@ -6953,8 +6953,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 55, @@ -6962,16 +6962,16 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], chatOrigins: [ 58, { LID_USERNAME: 1, LID_CTWA: 2, - OTHERS: 3, - }, + OTHERS: 3 + } ], deviceCount: [31, 'integer'], deviceSizeBucket: [ @@ -6992,8 +6992,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], disappearingChatInitiator: [ 30, @@ -7002,8 +7002,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], e2eBackfill: [23, 'boolean'], e2eCiphertextType: [ @@ -7012,8 +7012,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE: 0, PREKEY_MESSAGE: 1, SENDER_KEY_MESSAGE: 2, - MESSAGE_SECRET_MESSAGE: 3, - }, + MESSAGE_SECRET_MESSAGE: 3 + } ], e2eCiphertextVersion: [9, 'integer'], e2eFailureReason: [ @@ -7117,8 +7117,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_INVALID_KEY_MATEIRAL_DATA_LEN: 95, ERROR_SESSION_STATE_GET_SENDER_RATCHET_KEY: 96, ERROR_SESSION_STATE_GET_LOCAL_IDENTITY_KEY: 97, - ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98, - }, + ERROR_SESSION_STATE_GET_REMOTE_IDENTITY_KEY: 98 + } ], editDuration: [43, 'integer'], editType: [ @@ -7127,8 +7127,8 @@ export const WEB_EVENTS: Event[] = [ NOT_EDITED: 0, EDITED: 1, SENDER_REVOKE: 2, - ADMIN_REVOKE: 3, - }, + ADMIN_REVOKE: 3 + } ], ephemeralityDuration: [21, 'integer'], ephemeralityInitiator: [ @@ -7136,8 +7136,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], ephemeralityTriggerAction: [ 48, @@ -7146,8 +7146,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], excessPayloadKbSize: [40, 'integer'], fastForwardEnabled: [15, 'boolean'], @@ -7161,8 +7161,8 @@ export const WEB_EVENTS: Event[] = [ 53, { PN: 1, - LID: 2, - }, + LID: 2 + } ], mediaCaptionPresent: [8, 'boolean'], messageDistributionType: [ @@ -7170,8 +7170,8 @@ export const WEB_EVENTS: Event[] = [ { REGULAR_MESSAGE: 0, DIRECT_MESSAGE: 1, - SENDER_KEY_DISTRIBUTION_MESSAGE: 2, - }, + SENDER_KEY_DISTRIBUTION_MESSAGE: 2 + } ], messageForwardAgeT: [14, 'timer'], messageIsFanout: [5, 'boolean'], @@ -7246,8 +7246,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageSendOptUploadEnabled: [12, 'boolean'], messageSendResult: [ @@ -7266,8 +7266,8 @@ export const WEB_EVENTS: Event[] = [ ERROR_E2EE: 12, ERROR_INVALID_PROTOBUF: 13, SERVER_ERROR: 14, - EPHEMERALLY_EXPIRED: 15, - }, + EPHEMERALLY_EXPIRED: 15 + } ], messageSendResultIsTerminal: [17, 'boolean'], messageSendT: [11, 'timer'], @@ -7279,8 +7279,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], networkWasDisconnected: [37, 'boolean'], oppositeVisibleIdentification: [ @@ -7292,8 +7292,8 @@ export const WEB_EVENTS: Event[] = [ MASKED_PHONE_NUMBER: 4, VERIFIED_BUSINESS_NAME: 5, PLACEHOLDER: 6, - PUSHNAME: 7, - }, + PUSHNAME: 7 + } ], overallMediaSize: [42, 'number'], participantCount: [32, 'integer'], @@ -7305,8 +7305,8 @@ export const WEB_EVENTS: Event[] = [ 34, { SENDER: 0, - ADMIN: 1, - }, + ADMIN: 1 + } ], sendButtonPressT: [45, 'integer'], senderDefaultDisappearingDuration: [27, 'integer'], @@ -7322,8 +7322,8 @@ export const WEB_EVENTS: Event[] = [ WEB_STICKER_MAKER: 2, IOS_STICKER_MAKER: 3, ANDROID_STICKER_MAKER: 4, - TRANSPARENT_IMAGE: 5, - }, + TRANSPARENT_IMAGE: 5 + } ], thumbSize: [20, 'number'], typeOfGroup: [ @@ -7331,13 +7331,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'RevokeMessageSend', @@ -7352,8 +7352,8 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], resendCount: [3, 'integer'], retryCount: [4, 'integer'], @@ -7362,13 +7362,13 @@ export const WEB_EVENTS: Event[] = [ 6, { SENDER: 0, - ADMIN: 1, - }, - ], + ADMIN: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BotBizJourney', @@ -7385,23 +7385,23 @@ export const WEB_EVENTS: Event[] = [ BOT_BIZ_NUX_APPEAR: 5, BOT_BIZ_NUX_DISMISS: 6, BOT_BIZ_NUX_SELECT: 7, - BOT_BIZ_INFO_CHAT_CLICK: 8, - }, + BOT_BIZ_INFO_CHAT_CLICK: 8 + } ], botBizEntryPoint: [ 6, { SHARED_BOT_BIZ_CARD: 1, SHARED_BOT_BIZ_DEEPLINK: 2, - BOT_BIZ_CHAT: 3, - }, + BOT_BIZ_CHAT: 3 + } ], botBizType: [ 7, { BOT_BIZ_3P: 1, - BOT_BIZ_1P: 2, - }, + BOT_BIZ_1P: 2 + } ], botType: [ 4, @@ -7409,13 +7409,13 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, - ], + BOT_3P_BIZ: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'TsExternal', @@ -7429,14 +7429,14 @@ export const WEB_EVENTS: Event[] = [ CALL: 1, VIDEO: 2, PTT_RECORD: 3, - PTT_PLAY: 4, - }, + PTT_PLAY: 4 + } ], - tsSessionId: [4, 'integer'], + tsSessionId: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DeepLinkConversion', @@ -7446,15 +7446,15 @@ export const WEB_EVENTS: Event[] = [ 8, { BUSINESS: 0, - CONSUMER: 1, - }, + CONSUMER: 1 + } ], ctwaChatCreationMode: [ 4, { JID: 0, - LID: 1, - }, + LID: 1 + } ], ctwaConversionType: [ 3, @@ -7473,8 +7473,8 @@ export const WEB_EVENTS: Event[] = [ FIRST_BIZ_REPLY_CONTINUATION: 11, SECOND_MESSAGE_CONTINUATION: 12, SECOND_BIZ_REPLY_CONTINUATION: 13, - THIRD_MESSAGE_CONTINUATION: 14, - }, + THIRD_MESSAGE_CONTINUATION: 14 + } ], deepLinkConversionData: [2, 'string'], deepLinkConversionSource: [1, 'string'], @@ -7483,14 +7483,14 @@ export const WEB_EVENTS: Event[] = [ 5, { VIEWED: 0, - DISMISSED: 1, - }, + DISMISSED: 1 + } ], - trustBannerType: [6, 'string'], + trustBannerType: [6, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EphemeralOutOfSyncInfo', @@ -7514,8 +7514,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], incomingMessageEphemeralityDuration: [2, 'integer'], isAGroup: [3, 'boolean'], @@ -7523,11 +7523,11 @@ export const WEB_EVENTS: Event[] = [ otherDefaultModeDuration: [6, 'integer'], threadEphemeralityDuration: [7, 'integer'], threadId: [8, 'string'], - userDefaultModeDuration: [9, 'integer'], + userDefaultModeDuration: [9, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EphemeralSyncResponseReceive', @@ -7540,8 +7540,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], clientEphemeralityDuration: [2, 'integer'], clientEphemeralityInitiator: [ @@ -7549,8 +7549,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], clientEphemeralitySettingTimestamp: [4, 'integer'], clientEphemeralityTriggerAction: [ @@ -7560,8 +7560,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], esrDisappearingModeInitiator: [ 6, @@ -7570,8 +7570,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], esrEphemeralityDuration: [7, 'integer'], esrEphemeralityInitiator: [ @@ -7579,8 +7579,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], esrEphemeralitySettingTimestamp: [9, 'integer'], esrEphemeralityTriggerAction: [ @@ -7590,8 +7590,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], esrFailureReason: [ 11, @@ -7604,22 +7604,22 @@ export const WEB_EVENTS: Event[] = [ ATTEMPTS_EXHAUSTED: 6, NO_USER_INFO: 7, NO_CHAT_SESSION: 8, - INVALID_EPHEMERAL_DURATION: 9, - }, + INVALID_EPHEMERAL_DURATION: 9 + } ], esrResolveResult: [ 12, { SUCCESS: 1, - ERROR: 2, - }, + ERROR: 2 + } ], isAGroup: [13, 'boolean'], - threadId: [14, 'string'], + threadId: [14, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EphemeralSyncResponseSend', @@ -7632,8 +7632,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], clientEphemeralityDuration: [2, 'integer'], clientEphemeralityInitiator: [ @@ -7641,8 +7641,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], clientEphemeralitySettingTimestamp: [4, 'integer'], clientEphemeralityTriggerAction: [ @@ -7652,8 +7652,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], esrDisappearingModeInitiator: [ 6, @@ -7662,8 +7662,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], esrEphemeralityDuration: [7, 'integer'], esrEphemeralityInitiator: [ @@ -7671,8 +7671,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], esrEphemeralitySettingTimestamp: [9, 'integer'], esrEphemeralityTriggerAction: [ @@ -7682,8 +7682,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], esrFailureReason: [ 11, @@ -7696,16 +7696,16 @@ export const WEB_EVENTS: Event[] = [ ATTEMPTS_EXHAUSTED: 6, NO_USER_INFO: 7, NO_CHAT_SESSION: 8, - INVALID_EPHEMERAL_DURATION: 9, - }, + INVALID_EPHEMERAL_DURATION: 9 + } ], esrSendAttempt: [12, 'integer'], esrSendResult: [ 13, { SUCCESS: 1, - ERROR: 2, - }, + ERROR: 2 + } ], isAGroup: [14, 'boolean'], messageDisappearingModeInitiator: [ @@ -7715,8 +7715,8 @@ export const WEB_EVENTS: Event[] = [ INITIATED_BY_ME: 2, INITIATED_BY_OTHER: 3, CHAT_PICKER: 4, - BIZ_UPGRADE_FB_HOSTING: 5, - }, + BIZ_UPGRADE_FB_HOSTING: 5 + } ], messageEphemeralityDuration: [16, 'integer'], messageEphemeralityInitiator: [ @@ -7724,8 +7724,8 @@ export const WEB_EVENTS: Event[] = [ { INITIATED_BY_ME: 1, INITIATED_BY_OTHER: 2, - BIZ_UPGRADE_FB_HOSTING: 3, - }, + BIZ_UPGRADE_FB_HOSTING: 3 + } ], messageEphemeralitySettingTimestamp: [18, 'integer'], messageEphemeralityTriggerAction: [ @@ -7735,14 +7735,14 @@ export const WEB_EVENTS: Event[] = [ CHAT_SETTINGS: 1, ACCOUNT_SETTINGS: 2, BULK_CHANGE: 3, - BIZ_SUPPORTS_FB_HOSTING: 4, - }, + BIZ_SUPPORTS_FB_HOSTING: 4 + } ], - threadId: [20, 'string'], + threadId: [20, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelGapDetected', @@ -7755,15 +7755,15 @@ export const WEB_EVENTS: Event[] = [ OWNER: 1, ADMIN: 2, FOLLOWER: 3, - GUEST: 4, - }, + GUEST: 4 + } ], cid: [3, 'string'], - gapSize: [4, 'integer'], + gapSize: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelMessageHistoryRequest', @@ -7778,8 +7778,8 @@ export const WEB_EVENTS: Event[] = [ NEWER: 2, LATEST: 3, EXACT: 4, - GAP: 5, - }, + GAP: 5 + } ], channelUserType: [ 3, @@ -7787,17 +7787,17 @@ export const WEB_EVENTS: Event[] = [ OWNER: 1, ADMIN: 2, FOLLOWER: 3, - GUEST: 4, - }, + GUEST: 4 + } ], cid: [4, 'string'], processingDurationT: [5, 'timer'], requestDurationT: [6, 'timer'], - requestSuccessful: [7, 'boolean'], + requestSuccessful: [7, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelCoreEvent', @@ -7810,8 +7810,8 @@ export const WEB_EVENTS: Event[] = [ FOLLOW: 1, UNFOLLOW: 2, MUTE: 3, - UNMUTE: 4, - }, + UNMUTE: 4 + } ], channelDirectorySessionId: [7, 'integer'], channelEntryPoint: [ @@ -7830,15 +7830,15 @@ export const WEB_EVENTS: Event[] = [ STATUS: 10, ADMIN_INVITE_MESSAGE: 11, MEDIA_BROWSER: 12, - SIMILAR_CHANNEL: 13, - }, + SIMILAR_CHANNEL: 13 + } ], channelEntryPointApp: [ 3, { EXTERNAL_UNKNOWN: 1, - WHATSAPP: 2, - }, + WHATSAPP: 2 + } ], channelEntryPointMetadata: [ 10, @@ -7846,15 +7846,15 @@ export const WEB_EVENTS: Event[] = [ STATUS_HEADER: 1, LINK_TOOLTIP: 2, LINK_BUTTON: 3, - POST_TOOLTIP: 4, - }, + POST_TOOLTIP: 4 + } ], channelEventUnit: [ 12, { RECOMMENDED_CHANNELS: 1, - SIMILAR_CHANNELS: 2, - }, + SIMILAR_CHANNELS: 2 + } ], cid: [4, 'string'], directoryChannelIndex: [9, 'integer'], @@ -7867,14 +7867,14 @@ export const WEB_EVENTS: Event[] = [ CHANNEL_DIRECTORY: 3, CHANNEL_DIRECTORY_SEARCH: 4, CHANNEL_PROFILE: 5, - CHANNEL_UPDATES_HOME_SEARCH: 6, - }, + CHANNEL_UPDATES_HOME_SEARCH: 6 + } ], - similarChannelsSessionId: [13, 'integer'], + similarChannelsSessionId: [13, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UserNoticeError', @@ -7885,8 +7885,8 @@ export const WEB_EVENTS: Event[] = [ { LEGACY_USER_NOTICE: 0, BADGED_USER_NOTICE: 1, - PDFN_DISCLOSURE: 2, - }, + PDFN_DISCLOSURE: 2 + } ], userNoticeContentVersion: [2, 'integer'], userNoticeErrorEvent: [ @@ -7906,14 +7906,14 @@ export const WEB_EVENTS: Event[] = [ JSON_FETCH_REDIRECT: 12, IMAGE_FETCH_REDIRECT: 13, IMAGE_FETCH_FORBIDDEN: 14, - JSON_FETCH_FORBIDDEN: 15, - }, + JSON_FETCH_FORBIDDEN: 15 + } ], - userNoticeId: [1, 'integer'], + userNoticeId: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StickerCommonQueryToStaticServer', @@ -7928,13 +7928,13 @@ export const WEB_EVENTS: Event[] = [ STICKER_STORE_DATA: 0, PREVIEW_IMAGE_DOWNLOAD: 1, STICKER_PACK_DATA: 2, - STICKER_SEARCH: 3, - }, - ], + STICKER_SEARCH: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdExpansionAgentBrowserMdId', @@ -7956,13 +7956,13 @@ export const WEB_EVENTS: Event[] = [ CUSTOM_AGENT_NAME: 2, MESSAGE_INFO: 3, ACTIVE: 4, - DELETE: 5, - }, - ], + DELETE: 5 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PsPhoneNumberHyperlink', @@ -7981,15 +7981,15 @@ export const WEB_EVENTS: Event[] = [ CLICK_COPY_PHONE_NUMBER: 7, CLOSE_DIALOG_BOX: 8, MESSAGE_SENT: 9, - CLICK_CALL_ON_WHATSAPP: 10, - }, + CLICK_CALL_ON_WHATSAPP: 10 + } ], phoneNumberStatusOnWa: [3, 'boolean'], - sequenceNumber: [4, 'integer'], + sequenceNumber: [4, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'GroupCreateC', @@ -7997,7 +7997,7 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SendRevokeMessage', @@ -8067,8 +8067,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 1, @@ -8078,24 +8078,24 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], - revokeSendDelay: [3, 'integer'], + revokeSendDelay: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UtmMessageSend', id: 4018, props: { - businessPhoneNumber: [1, 'integer'], + businessPhoneNumber: [1, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'WebcMessageSend', @@ -8166,8 +8166,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageSendT: [4, 'timer'], messageType: [ @@ -8178,13 +8178,13 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, - ], + INTEROP: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdChatAssignmentSecondaryAction', @@ -8195,8 +8195,8 @@ export const WEB_EVENTS: Event[] = [ 8, { ASSIGNED: 0, - UNASSIGNED: 1, - }, + UNASSIGNED: 1 + } ], mdChatAssignmentSecondaryActionBrowserId: [2, 'string'], mdChatAssignmentSecondaryActionChatType: [ @@ -8207,24 +8207,24 @@ export const WEB_EVENTS: Event[] = [ INDIVIDUAL: 2, COMMUNITY: 3, CHANNEL: 4, - INTEROP: 5, - }, + INTEROP: 5 + } ], mdChatAssignmentSecondaryActionError: [ 4, { ERROR_FETCHING_AGENT_NAME: 0, ERROR_FETCHING_CHAT: 1, - ERROR_OTHER: 2, - }, + ERROR_OTHER: 2 + } ], mdChatAssignmentSecondaryActionMdId: [5, 'integer'], mdChatAssignmentSecondaryActionSource: [ 6, { NONE: 0, - BOOTSTRAP: 1, - }, + BOOTSTRAP: 1 + } ], mdChatAssignmentSecondaryActionType: [ 7, @@ -8233,13 +8233,13 @@ export const WEB_EVENTS: Event[] = [ ACTION_SYSTEM_MESSAGE_CREATION_ERROR: 1, ACTION_SYSTEM_MESSAGE_RENDERED: 2, ACTION_CHAT_STATUS_TICKER_SHOWN: 3, - ACTION_TOOLTIP_SHOWN: 4, - }, - ], + ACTION_TOOLTIP_SHOWN: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdChatAssignment', @@ -8253,8 +8253,8 @@ export const WEB_EVENTS: Event[] = [ { ACTION_ASSIGNED: 0, ACTION_UNASSIGNED: 1, - ACTION_REASSIGNED: 2, - }, + ACTION_REASSIGNED: 2 + } ], chatAssignmentAgentId: [5, 'string'], chatAssignmentBrowserId: [6, 'string'], @@ -8264,8 +8264,8 @@ export const WEB_EVENTS: Event[] = [ INDIVIDUAL: 0, GROUP: 1, COMMUNITY: 2, - CHANNEL: 3, - }, + CHANNEL: 3 + } ], chatAssignmentEntryPoint: [ 8, @@ -8274,15 +8274,15 @@ export const WEB_EVENTS: Event[] = [ CONTACT_INFO_SCREEN: 1, MULTI_SELECT: 2, SYSTEM_MESSAGE: 3, - CHAT_LIST_SWIPE: 4, - }, + CHAT_LIST_SWIPE: 4 + } ], chatAssignmentMdId: [9, 'integer'], - chatsCnt: [10, 'integer'], + chatsCnt: [10, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'OtpRetriever', @@ -8294,8 +8294,8 @@ export const WEB_EVENTS: Event[] = [ 12, { INBOX: 1, - ARCHIVED: 2, - }, + ARCHIVED: 2 + } ], ctaFallbackReason: [ 2, @@ -8310,15 +8310,15 @@ export const WEB_EVENTS: Event[] = [ INCOMPATIBLE_OS_VERSION: 7, NO_RETRIEVER_BUTTON: 8, FEATURE_DISABLED: 9, - AMBIGUOUS_DELIVERY_DESTINATION: 10, - }, + AMBIGUOUS_DELIVERY_DESTINATION: 10 + } ], ctaType: [ 3, { COPY_CODE: 0, - AUTOFILL: 1, - }, + AUTOFILL: 1 + } ], isKeepChatsArchivedEnabled: [13, 'boolean'], isMessageNotificationEnabled: [14, 'boolean'], @@ -8337,8 +8337,8 @@ export const WEB_EVENTS: Event[] = [ OTP_CONFIGURATION: 6, OTP_REQUEST_SENDER: 7, OTP_ZERO_TAP_SENDER: 8, - OTP_CONF_OPTION: 9, - }, + OTP_CONF_OPTION: 9 + } ], otpEventType: [ 6, @@ -8359,8 +8359,8 @@ export const WEB_EVENTS: Event[] = [ ZERO_TAP_SEND_CODE_FAILED: 13, OTP_CONF_OPT_ZERO_TAP_FLAG_ENABLED: 14, OTP_CONF_OPT_ZERO_TAP_FLAG_DISABLED: 15, - HANDSHAKE_CONFIRMATION_SENT: 16, - }, + HANDSHAKE_CONFIRMATION_SENT: 16 + } ], otpFailureReason: [16, 'string'], otpHandshakeElapsedTimeMs: [21, 'integer'], @@ -8371,8 +8371,8 @@ export const WEB_EVENTS: Event[] = [ { ONE_TAP: 0, ZERO_TAP: 1, - COPY_CODE: 2, - }, + COPY_CODE: 2 + } ], otpSdkVersion: [22, 'string'], otpSessionId: [8, 'string'], @@ -8380,11 +8380,11 @@ export const WEB_EVENTS: Event[] = [ templateId: [17, 'string'], thirdPartyPackageNameFromIntent: [9, 'string'], thirdPartyPackageSignatureHash: [10, 'string'], - waDeviceId: [25, 'integer'], + waDeviceId: [25, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'BizCatalogView', @@ -8396,8 +8396,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 1, SMB: 2, ENT: 3, - CLOUDAPI: 4, - }, + CLOUDAPI: 4 + } ], cartToggle: [13, 'boolean'], catalogCategoryId: [19, 'string'], @@ -8426,8 +8426,8 @@ export const WEB_EVENTS: Event[] = [ CATALOG_ENTRY_POINT_BIZ_ACTION_BAR: 20, CATALOG_ENTRY_POINT_BIZ_ONBOARDING: 21, CATALOG_ENTRY_POINT_TRUST_CARD: 22, - CATALOG_ENTRY_POINT_FLOWS: 23, - }, + CATALOG_ENTRY_POINT_FLOWS: 23 + } ], catalogEventSampled: [11, 'boolean'], catalogOwnerJid: [10, 'string'], @@ -8476,8 +8476,8 @@ export const WEB_EVENTS: Event[] = [ ACTION_PLP_PRODUCT_VARIANT_BOTTOM_SHEET_OPEN: 44, ACTION_PLP_PRODUCT_VARIANT_CHANGE: 45, ACTION_PDP_PRODUCT_VARIANT_CHANGE: 46, - ACTION_PLP_BOTTOM_SHEET_SEE_MORE_DETAILS: 47, - }, + ACTION_PLP_BOTTOM_SHEET_SEE_MORE_DETAILS: 47 + } ], collectionId: [15, 'string'], collectionIndex: [16, 'string'], @@ -8489,16 +8489,16 @@ export const WEB_EVENTS: Event[] = [ QR_CODE_SHEET: 3, DEEP_LINK_BANNER: 4, DEEP_LINK_SMB_NOTIFICATION: 5, - DEEP_LINK_MESSENGER_APP: 6, - }, + DEEP_LINK_MESSENGER_APP: 6 + } ], entryPointConversationInitiated: [ 22, { BUSINESS_INITIATED: 0, CONSUMER_INITIATED: 1, - NO_MESSAGES_LAST_24H: 2, - }, + NO_MESSAGES_LAST_24H: 2 + } ], entryPointConversionApp: [20, 'string'], entryPointConversionSource: [21, 'string'], @@ -8509,11 +8509,11 @@ export const WEB_EVENTS: Event[] = [ productId: [9, 'string'], productIndex: [17, 'string'], quantity: [6, 'integer'], - sequenceNumber: [18, 'integer'], + sequenceNumber: [18, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'CatalogBiz', @@ -8598,8 +8598,8 @@ export const WEB_EVENTS: Event[] = [ ACTION_COLLECTION_ASSIGN_ITEMS: 77, ACTION_COLLECTION_UNASSIGN_ITEMS: 78, ACTION_COLLECTION_CHANGE_ORDER: 80, - ACTION_COLLECTION_ITEM_CHANGE_ORDER: 81, - }, + ACTION_COLLECTION_ITEM_CHANGE_ORDER: 81 + } ], catalogEntryPoint: [ 7, @@ -8626,8 +8626,8 @@ export const WEB_EVENTS: Event[] = [ CATALOG_ENTRY_POINT_BIZ_ACTION_BAR: 20, CATALOG_ENTRY_POINT_BIZ_ONBOARDING: 21, CATALOG_ENTRY_POINT_TRUST_CARD: 22, - CATALOG_ENTRY_POINT_FLOWS: 23, - }, + CATALOG_ENTRY_POINT_FLOWS: 23 + } ], catalogSessionId: [3, 'string'], collectionCount: [18, 'integer'], @@ -8641,8 +8641,8 @@ export const WEB_EVENTS: Event[] = [ QR_CODE_SHEET: 3, DEEP_LINK_BANNER: 4, DEEP_LINK_SMB_NOTIFICATION: 5, - DEEP_LINK_MESSENGER_APP: 6, - }, + DEEP_LINK_MESSENGER_APP: 6 + } ], errorCode: [5, 'integer'], isOrderMsgAttached: [10, 'boolean'], @@ -8651,21 +8651,21 @@ export const WEB_EVENTS: Event[] = [ productId: [2, 'string'], productIds: [12, 'string'], productIndex: [16, 'string'], - quantity: [11, 'integer'], + quantity: [11, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcImgError', id: 1700, props: { - webcImgErrorCode: [1, 'number'], + webcImgErrorCode: [1, 'number'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GroupJourney', @@ -8727,8 +8727,8 @@ export const WEB_EVENTS: Event[] = [ CLOSE_BTN_CLICKED: 51, AI_CHAT_CLICK: 52, NEW_CHAT_CLICK: 53, - SERP_LOADED: 54, - }, + SERP_LOADED: 54 + } ], appSessionId: [2, 'string'], groupSize: [3, 'integer'], @@ -8746,8 +8746,8 @@ export const WEB_EVENTS: Event[] = [ GROUP_INFO: 9, NOTIFICATION: 10, EXPRESSIONS_EMOJI: 11, - INVITE_NON_WA_CONTACT: 12, - }, + INVITE_NON_WA_CONTACT: 12 + } ], threadType: [ 5, @@ -8759,8 +8759,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL: 5, SUB_GROUP: 6, DEFAULT_SUB_GROUP: 7, - PARENT_GROUP: 8, - }, + PARENT_GROUP: 8 + } ], uiSurface: [ 7, @@ -8855,21 +8855,21 @@ export const WEB_EVENTS: Event[] = [ GROUP_MEMBER_ADD_GROUP_CREATION: 89, GROUP_MEMBER_ADD_EXISTING_GROUP: 90, GROUP_CHAT: 91, - GROUP_CREATION: 92, - }, + GROUP_CREATION: 92 + } ], userRole: [ 6, { MEMBER: 0, ADMIN: 1, - CADMIN: 2, - }, - ], + CADMIN: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMessageQuery', @@ -8887,8 +8887,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 1, BROADCAST_LIST: 2, COMMUNITY: 3, - NEWSLETTER: 4, - }, + NEWSLETTER: 4 + } ], webcDocumentMessageCount: [16, 'integer'], webcEarliestMessageIndex: [11, 'integer'], @@ -8900,16 +8900,16 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, USER_SCROLL: 1, NEW_MESSAGE_PREFETCH: 2, - SEARCH_RESULT_CLICK: 3, - }, + SEARCH_RESULT_CLICK: 3 + } ], webcMessageQueryType: [ 3, { LOAD_PREV: 0, LOAD_NEXT: 1, - LOAD_AROUND: 2, - }, + LOAD_AROUND: 2 + } ], webcOtherMessageCount: [18, 'integer'], webcPhotoMessageCount: [7, 'integer'], @@ -8918,11 +8918,11 @@ export const WEB_EVENTS: Event[] = [ webcResponseBytes: [10, 'integer'], webcStickerMessageCount: [17, 'integer'], webcTextMessageCount: [5, 'integer'], - webcVideoMessageCount: [6, 'integer'], + webcVideoMessageCount: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MediaPicker', @@ -8995,8 +8995,8 @@ export const WEB_EVENTS: Event[] = [ THUNDERSTORM_IN_APP_PHOTO_LIBRARY: 49, TRANSPARENT_IMAGE_EDIT_STICKER: 50, BUSINESS_FLOWS: 51, - SYSTEM_INTENT: 52, - }, + SYSTEM_INTENT: 52 + } ], mediaPickerOriginThirdParty: [21, 'boolean'], mediaPickerSent: [2, 'integer'], @@ -9069,8 +9069,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], motionPhotoImpressionCount: [31, 'integer'], motionPhotoSentCount: [32, 'integer'], @@ -9081,8 +9081,8 @@ export const WEB_EVENTS: Event[] = [ AUTO: 0, DATA_SAVER: 1, HIGH_QUALITY: 2, - HIGHEST_QUALITY: 3, - }, + HIGHEST_QUALITY: 3 + } ], pickerSessionId: [30, 'integer'], statusRecipients: [17, 'integer'], @@ -9092,13 +9092,13 @@ export const WEB_EVENTS: Event[] = [ AUTO: 0, DATA_SAVER: 1, HIGH_QUALITY: 2, - HIGHEST_QUALITY: 3, - }, - ], + HIGHEST_QUALITY: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcStatusSession', @@ -9110,11 +9110,11 @@ export const WEB_EVENTS: Event[] = [ webcStatusRecentRowCount: [5, 'integer'], webcStatusSessionId: [1, 'integer'], webcStatusViewedItemCount: [3, 'integer'], - webcStatusViewedRowCount: [6, 'integer'], + webcStatusViewedRowCount: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'TestAnonymousDailyId', @@ -9124,14 +9124,14 @@ export const WEB_EVENTS: Event[] = [ 1, { TEST_VALUE1: 1, - TEST_VALUE2: 2, - }, + TEST_VALUE2: 2 + } ], - psTestFloatField: [2, 'number'], + psTestFloatField: [2, 'number'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 248614979, + privateStatsIdInt: 248614979 }, { name: 'TestAnonymousDaily', @@ -9139,17 +9139,17 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'TestAnonymousIdLess', id: 3004, props: { - psTimeSinceLastEventInMin: [1, 'integer'], + psTimeSinceLastEventInMin: [1, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'TestAnonymousMonthlyId', @@ -9157,7 +9157,7 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'private', - privateStatsIdInt: 191000728, + privateStatsIdInt: 191000728 }, { name: 'TestAnonymousWeeklyId', @@ -9165,11 +9165,11 @@ export const WEB_EVENTS: Event[] = [ props: { psTestBooleanField: [2, 'boolean'], psTestStringField: [3, 'string'], - psTimeSinceLastEventInMin: [1, 'integer'], + psTimeSinceLastEventInMin: [1, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 42196056, + privateStatsIdInt: 42196056 }, { name: 'ChannelSimilarChannels', @@ -9180,8 +9180,8 @@ export const WEB_EVENTS: Event[] = [ { DISPLAYED: 0, HIDDEN: 1, - CLOSED: 2, - }, + CLOSED: 2 + } ], bannerStatusReason: [ 2, @@ -9190,8 +9190,8 @@ export const WEB_EVENTS: Event[] = [ NOT_ENOUGH_SIMILAR_CHANNELS: 1, SIMILAR_CHANNELS_FOUND: 2, CLOSE_TAP: 3, - UNFOLLOW_TAP: 4, - }, + UNFOLLOW_TAP: 4 + } ], cid: [3, 'string'], similarChannelDisplayRank: [4, 'integer'], @@ -9203,8 +9203,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL_DIRECTORY: 3, CHANNEL_DIRECTORY_SEARCH: 4, CHANNEL_PROFILE: 5, - CHANNEL_UPDATES_HOME_SEARCH: 6, - }, + CHANNEL_UPDATES_HOME_SEARCH: 6 + } ], similarChannelId: [6, 'string'], similarChannelRank: [7, 'integer'], @@ -9214,14 +9214,14 @@ export const WEB_EVENTS: Event[] = [ OWNER: 1, ADMIN: 2, FOLLOWER: 3, - GUEST: 4, - }, + GUEST: 4 + } ], - similarChannelsSessionId: [10, 'integer'], + similarChannelsSessionId: [10, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UiMessageYourselfAction', @@ -9236,20 +9236,20 @@ export const WEB_EVENTS: Event[] = [ SEARCH_BAR_PRESSED: 3, SEARCH_FULL_NAME_YOU_SELECTED: 4, NEW_NTS_CREATED: 5, - EXISTING_NTS_OPENED: 6, - }, + EXISTING_NTS_OPENED: 6 + } ], uiMessageYourselfFunnelName: [ 3, { NEW_CHAT: 1, - CONTACT_AND_GLOBAL_SEARCH: 2, - }, - ], + CONTACT_AND_GLOBAL_SEARCH: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MediaStreamPlayback', @@ -9325,8 +9325,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], overallPlayT: [10, 'timer'], overallT: [1, 'timer'], @@ -9337,8 +9337,8 @@ export const WEB_EVENTS: Event[] = [ { CONVERSATION: 1, STATUS: 2, - CHANNELS: 3, - }, + CHANNELS: 3 + } ], playbackState: [ 11, @@ -9349,17 +9349,17 @@ export const WEB_EVENTS: Event[] = [ BUFFERING: 4, OUTSIDE: 5, ENDED: 6, - ERROR: 7, - }, + ERROR: 7 + } ], seekCount: [13, 'integer'], totalRebufferingCount: [9, 'integer'], totalRebufferingT: [8, 'timer'], - videoDuration: [6, 'integer'], + videoDuration: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PrivacyTipAction', @@ -9371,13 +9371,13 @@ export const WEB_EVENTS: Event[] = [ VIEW: 1, CLICK_PRIVACY_TIP: 2, CLICK_OK: 3, - CLICK_OUTSIDE: 4, - }, - ], + CLICK_OUTSIDE: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UiAction', @@ -9388,8 +9388,8 @@ export const WEB_EVENTS: Event[] = [ { DIRECT_CHAT: 0, INVOKED: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], botType: [ 11, @@ -9397,8 +9397,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, METABOT: 1, BOT_1P_BIZ: 2, - BOT_3P_BIZ: 3, - }, + BOT_3P_BIZ: 3 + } ], dbMainThreadCount: [13, 'integer'], dbReadsCount: [14, 'integer'], @@ -9409,8 +9409,8 @@ export const WEB_EVENTS: Event[] = [ 10, { PN: 1, - LID: 2, - }, + LID: 2 + } ], participantCount: [6, 'integer'], sizeBucket: [ @@ -9431,8 +9431,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], uiActionChatType: [ 7, @@ -9441,8 +9441,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, SUBGROUP: 3, DEFAULT_SUBGROUP: 4, - CHANNEL: 5, - }, + CHANNEL: 5 + } ], uiActionPreloaded: [2, 'boolean'], uiActionT: [3, 'timer'], @@ -9467,13 +9467,13 @@ export const WEB_EVENTS: Event[] = [ CHAT_LIST_OPEN: 15, CALL_LIST_OPEN: 16, CHANNEL_INFO_OPEN: 17, - TTRC: 18, - }, - ], + TTRC: 18 + } + ] }, weight: 5000, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AdvertiseTooltipImpression', @@ -9538,8 +9538,8 @@ export const WEB_EVENTS: Event[] = [ SMB_HOME_SCREEN_OVERFLOW_MANAGE_ITEM: 54, SMB_BUSINESS_HOME_QP_CARD: 55, SMB_CREATED_AD: 56, - SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57, - }, + SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57 + } ], tooltipAction: [ 2, @@ -9547,13 +9547,13 @@ export const WEB_EVENTS: Event[] = [ EMPTY: 1, ENTER_AD_CREATION_FLOW: 2, DISMISS: 3, - IGNORE: 4, - }, - ], + IGNORE: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelLinkShare', @@ -9564,16 +9564,16 @@ export const WEB_EVENTS: Event[] = [ { WHATSAPP: 1, STATUS: 2, - EXTERNAL: 3, - }, + EXTERNAL: 3 + } ], channelLinkShareEntryPoint: [ 2, { CHANNEL_INFO_PAGE: 1, CHANNEL_THREAD: 2, - PRODUCER_CONTEXT_CARD: 3, - }, + PRODUCER_CONTEXT_CARD: 3 + } ], channelLinkShareScreen: [ 4, @@ -9581,14 +9581,14 @@ export const WEB_EVENTS: Event[] = [ CONTEXT_CARD: 1, CHANNEL_INFO: 2, CHANNEL_THREAD: 3, - SHARE_LINK_SCREEN: 4, - }, + SHARE_LINK_SCREEN: 4 + } ], - cid: [3, 'string'], + cid: [3, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CommunityFeatureUsage', @@ -9601,19 +9601,19 @@ export const WEB_EVENTS: Event[] = [ ENTRY: 1, GROUP_NAV: 2, GROUP_ADD: 3, - COMMUNITY_NAV: 4, - }, + COMMUNITY_NAV: 4 + } ], communityUiFeature: [ 3, { - SUBGROUP_SWITCH: 1, - }, - ], + SUBGROUP_SWITCH: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GroupProfilePicture', @@ -9632,8 +9632,8 @@ export const WEB_EVENTS: Event[] = [ TAP_ACTION_ITEM_WEB_SEARCH: 7, EMOJI_PANEL_OPEN: 8, STICKER_PANEL_OPEN: 9, - PROFILE_PIC_UPDATED: 10, - }, + PROFILE_PIC_UPDATED: 10 + } ], hasProfilePicture: [3, 'boolean'], isAdmin: [4, 'boolean'], @@ -9657,8 +9657,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 15, LT4500: 16, LT5000: 17, - LARGEST_BUCKET: 18, - }, + LARGEST_BUCKET: 18 + } ], profilePictureType: [ 6, @@ -9668,13 +9668,13 @@ export const WEB_EVENTS: Event[] = [ WEB_SEARCH: 3, EMOJI: 4, STICKER: 5, - REMOVE_PHOTO: 6, - }, - ], + REMOVE_PHOTO: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'KeepInChatNux', @@ -9687,8 +9687,8 @@ export const WEB_EVENTS: Event[] = [ FIRST_DM_NUX_IMPRESSION: 1, KIC_NUX_IMPRESSION: 2, KIC_NUX_LEARN_MORE_TAP: 3, - KIC_SYSTEM_MESSAGE_GENERATE: 4, - }, + KIC_SYSTEM_MESSAGE_GENERATE: 4 + } ], threadId: [3, 'string'], trigger: [ @@ -9700,13 +9700,13 @@ export const WEB_EVENTS: Event[] = [ USER_MESSAGE_KEPT: 4, KEPT_FOLDER_TAP_FIRST_TIME: 5, UNKEEP_MESSAGE_FIRST_TIME: 6, - EPHEMERAL_SETTINGS: 7, - }, - ], + EPHEMERAL_SETTINGS: 7 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'LwiEntryPointImpression', @@ -9774,8 +9774,8 @@ export const WEB_EVENTS: Event[] = [ SMB_HOME_SCREEN_OVERFLOW_MANAGE_ITEM: 54, SMB_BUSINESS_HOME_QP_CARD: 55, SMB_CREATED_AD: 56, - SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57, - }, + SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57 + } ], lwiSubEntryPoint: [ 6, @@ -9784,15 +9784,15 @@ export const WEB_EVENTS: Event[] = [ SMB_HOME_SCREEN_STATUS_TAB: 2, SMB_HOME_SCREEN_CALL_HISTORY_TAB: 3, SMB_HOME_SCREEN_COMMUNITIES_TAB: 4, - SMB_HOME_SCREEN_BIZ_HOME_TAB: 5, - }, + SMB_HOME_SCREEN_BIZ_HOME_TAB: 5 + } ], statusSessionId: [5, 'integer'], - userHasLinkedFbPage: [4, 'boolean'], + userHasLinkedFbPage: [4, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'LwiEntryTap', @@ -9860,8 +9860,8 @@ export const WEB_EVENTS: Event[] = [ SMB_HOME_SCREEN_OVERFLOW_MANAGE_ITEM: 54, SMB_BUSINESS_HOME_QP_CARD: 55, SMB_CREATED_AD: 56, - SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57, - }, + SMB_BUSINESS_HOME_CARD_COUPON_PROMOTION: 57 + } ], lwiFlowId: [1, 'string'], lwiSubEntryPoint: [ @@ -9871,23 +9871,23 @@ export const WEB_EVENTS: Event[] = [ SMB_HOME_SCREEN_STATUS_TAB: 2, SMB_HOME_SCREEN_CALL_HISTORY_TAB: 3, SMB_HOME_SCREEN_COMMUNITIES_TAB: 4, - SMB_HOME_SCREEN_BIZ_HOME_TAB: 5, - }, + SMB_HOME_SCREEN_BIZ_HOME_TAB: 5 + } ], statusSessionId: [6, 'integer'], statusTypeMedia: [ 12, { IMAGE: 1, - VIDEO: 2, - }, + VIDEO: 2 + } ], userHasLinkedFbPage: [5, 'boolean'], - waCampaignId: [10, 'string'], + waCampaignId: [10, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'LwiScreen', @@ -9897,8 +9897,8 @@ export const WEB_EVENTS: Event[] = [ 41, { REGULAR: 1, - RECOMMENDED: 2, - }, + RECOMMENDED: 2 + } ], adMediaOriginalAspectRatio: [47, 'string'], adMediaPreviewAspectRatio: [48, 'string'], @@ -9906,8 +9906,8 @@ export const WEB_EVENTS: Event[] = [ 40, { IMAGE: 1, - VIDEO: 2, - }, + VIDEO: 2 + } ], adsContentSelected: [ 42, @@ -9919,24 +9919,24 @@ export const WEB_EVENTS: Event[] = [ LWI_ADS_CONTENT_TYPE_CAMERA: 9, LWI_ADS_CONTENT_TYPE_RECENTLY_USED_MEDIA: 10, LWI_ADS_CONTENT_TYPE_CATALOGS_ALL: 11, - LWI_ADS_CONTENT_TYPE_STATUSES_ALL: 12, - }, + LWI_ADS_CONTENT_TYPE_STATUSES_ALL: 12 + } ], alertCount: [33, 'integer'], audienceType: [ 45, { REGION: 1, - MAP: 2, - }, + MAP: 2 + } ], billingStatus: [ 36, { UNKNOWN: 1, NO_ACTION_REQUIRED: 2, - HAS_PENDING_ACTIONS: 3, - }, + HAS_PENDING_ACTIONS: 3 + } ], createAdEnabled: [12, 'boolean'], ctwaAdAccountType: [ @@ -9944,16 +9944,16 @@ export const WEB_EVENTS: Event[] = [ { CTWA_FB_PAGE_LINKED_ACCOUNT: 0, CTWA_FB_PAGELESS_ACCOUNT: 1, - CTWA_WA_AD_ACCOUNT: 2, - }, + CTWA_WA_AD_ACCOUNT: 2 + } ], ctwaLoginType: [ 59, { CTWA_LOGIN_TYPE_FB_NATIVE: 0, CTWA_LOGIN_TYPE_FB_WEB: 1, - CTWA_LOGIN_TYPE_WA_AD_ACCOUNT: 2, - }, + CTWA_LOGIN_TYPE_WA_AD_ACCOUNT: 2 + } ], defaultAdsContentSelected: [ 9, @@ -9965,15 +9965,15 @@ export const WEB_EVENTS: Event[] = [ LWI_ADS_CONTENT_TYPE_CAMERA: 9, LWI_ADS_CONTENT_TYPE_RECENTLY_USED_MEDIA: 10, LWI_ADS_CONTENT_TYPE_CATALOGS_ALL: 11, - LWI_ADS_CONTENT_TYPE_STATUSES_ALL: 12, - }, + LWI_ADS_CONTENT_TYPE_STATUSES_ALL: 12 + } ], defaultAudienceLocationType: [ 57, { CITY_LEVEL: 1, - COUNTRY_LEVEL: 2, - }, + COUNTRY_LEVEL: 2 + } ], itemCount: [39, 'integer'], lwiAdCampaignId: [46, 'string'], @@ -9981,8 +9981,8 @@ export const WEB_EVENTS: Event[] = [ 22, { PAGE: 1, - WHATSAPP: 2, - }, + WHATSAPP: 2 + } ], lwiAlertReason: [ 6, @@ -10023,8 +10023,8 @@ export const WEB_EVENTS: Event[] = [ LWI_INVALID_STATE: 34, LWI_FAILED_TO_ENROLL_COUPON: 35, LWI_CHANGES_NOT_SAVED: 36, - LWI_LOCATION_SYSTEM_SETTING_RESOLUTION_REQUIRED: 37, - }, + LWI_LOCATION_SYSTEM_SETTING_RESOLUTION_REQUIRED: 37 + } ], lwiBudgetInLocal: [15, 'integer'], lwiBudgetOptionsInLocal: [54, 'string'], @@ -10038,8 +10038,8 @@ export const WEB_EVENTS: Event[] = [ VIEW_AD: 5, COMPLETE_PAYMENT: 6, RECREATE_AD_WITH_RECOMMENDATION: 7, - EDIT_AD_WITH_RECOMMENDATION: 8, - }, + EDIT_AD_WITH_RECOMMENDATION: 8 + } ], lwiCtwaAdStatusType: [ 25, @@ -10054,8 +10054,8 @@ export const WEB_EVENTS: Event[] = [ COMPLETED: 8, EXTENDABLE: 9, UNABLE_TO_CREATE: 10, - LIMITED_DELIVERY: 11, - }, + LIMITED_DELIVERY: 11 + } ], lwiCurrency: [16, 'string'], lwiDefaultBudgetInLocal: [17, 'integer'], @@ -10065,8 +10065,8 @@ export const WEB_EVENTS: Event[] = [ { UNKOWN: 1, MATCHES_TARGETING_SPEC: 2, - DIFFERS_FROM_TARGETING_SPEC: 3, - }, + DIFFERS_FROM_TARGETING_SPEC: 3 + } ], lwiDurationInDays: [20, 'integer'], lwiEventSequenceNumber: [2, 'integer'], @@ -10316,8 +10316,8 @@ export const WEB_EVENTS: Event[] = [ LWI_ACTION_SAVE_CHANGES_DIALOG_OPTION_TAPPED: 240, LWI_ACTION_GO_BACK_DIALOG_OPTION_TAPPED: 241, LWI_ACTION_LOCATION_SYSTEM_SETTING_TURN_ON_OK_TAPPED: 242, - LWI_ACTION_LOCATION_SYSTEM_SETTING_TURN_ON_NO_THANKS_TAPPED: 243, - }, + LWI_ACTION_LOCATION_SYSTEM_SETTING_TURN_ON_NO_THANKS_TAPPED: 243 + } ], lwiScreenReference: [ 4, @@ -10395,8 +10395,8 @@ export const WEB_EVENTS: Event[] = [ LWI_SCREEN_CONSENT_HOST: 71, LWI_SCREEN_COUPON_PROMOTION_NUX: 72, LWI_DIALOG_LOCATION_PERMISSION: 73, - LWI_DIALOG_SYSTEM_LOCATION_SETTINGS_RESOLUTION: 74, - }, + LWI_DIALOG_SYSTEM_LOCATION_SETTINGS_RESOLUTION: 74 + } ], lwiTargetingSpec: [21, 'string'], lwiTotalCtwaAds: [26, 'integer'], @@ -10408,8 +10408,8 @@ export const WEB_EVENTS: Event[] = [ { ONBOARDING_ENTRY_POINT_FAST_TRACK: 1, ONBOARDING_ENTRY_POINT_AD_REVIEW_SCREEN: 2, - ONBOARDING_ENTRY_POINT_CONSENT_HOST: 3, - }, + ONBOARDING_ENTRY_POINT_CONSENT_HOST: 3 + } ], paymentMethodSet: [13, 'boolean'], productId: [3, 'string'], @@ -10431,14 +10431,14 @@ export const WEB_EVENTS: Event[] = [ { UNKNOWN: 1, NO_ACTION_REQUIRED: 2, - HAS_ERRORS: 3, - }, + HAS_ERRORS: 3 + } ], - waAdAccountId: [44, 'string'], + waAdAccountId: [44, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ManageAdsEntryPointImpression', @@ -10450,13 +10450,13 @@ export const WEB_EVENTS: Event[] = [ WEB_OVERFLOW_MENU: 1, SMB_CHAT_LIST_CTWA_BANNER: 2, SMB_NATIVE_ADS_MANAGEMENT: 3, - SMB_BUSINESS_TOOLS_MANAGE_ADS_LIST_ITEM: 4, - }, - ], + SMB_BUSINESS_TOOLS_MANAGE_ADS_LIST_ITEM: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ManageAdsEntryPointTap', @@ -10468,13 +10468,13 @@ export const WEB_EVENTS: Event[] = [ WEB_OVERFLOW_MENU: 1, SMB_CHAT_LIST_CTWA_BANNER: 2, SMB_NATIVE_ADS_MANAGEMENT: 3, - SMB_BUSINESS_TOOLS_MANAGE_ADS_LIST_ITEM: 4, - }, - ], + SMB_BUSINESS_TOOLS_MANAGE_ADS_LIST_ITEM: 4 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SmbDataSharingConsentScreen', @@ -10486,8 +10486,8 @@ export const WEB_EVENTS: Event[] = [ NEW_ORDER: 0, CART: 1, LABEL_CHAT: 2, - LABEL_MESSAGE: 3, - }, + LABEL_MESSAGE: 3 + } ], smbDataSharingConsentScreenType: [ 1, @@ -10495,14 +10495,14 @@ export const WEB_EVENTS: Event[] = [ SMB_DATA_SHARING_CONSENT_SCREEN_VIEW: 0, SMB_DATA_SHARING_CONSENT_SCREEN_AGREE: 1, SMB_DATA_SHARING_CONSENT_SCREEN_DISAGREE: 2, - SMB_DATA_SHARING_CONSENT_SCREEN_CANCEL: 3, - }, + SMB_DATA_SHARING_CONSENT_SCREEN_CANCEL: 3 + } ], - smbDataSharingConsentScreenVersion: [2, 'integer'], + smbDataSharingConsentScreenVersion: [2, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SuspendedGroupDelete', @@ -10512,13 +10512,13 @@ export const WEB_EVENTS: Event[] = [ 1, { BOTTOM_SHEET_BTN: 1, - BLOCKED_COMPOSER_BTN: 2, - }, - ], + BLOCKED_COMPOSER_BTN: 2 + } + ] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'SupportAiSession', @@ -10543,13 +10543,13 @@ export const WEB_EVENTS: Event[] = [ SUBMIT_MESSAGE_FEEDBACK_FAILED: 13, SUBMIT_MESSAGE_FEEDBACK_SUCCEEDED: 14, NEGATIVE_FEEDBACK_OPTIONS_SCREEN_CANCELLED: 15, - NEGATIVE_FEEDBACK_OPTIONS_SCREEN_SHOWN: 16, - }, - ], + NEGATIVE_FEEDBACK_OPTIONS_SCREEN_SHOWN: 16 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BannerEvent', @@ -10564,8 +10564,8 @@ export const WEB_EVENTS: Event[] = [ DISMISS: 3, ELIGIBLE: 4, REVOKED: 5, - RENDERED: 6, - }, + RENDERED: 6 + } ], bannerType: [ 1, @@ -10636,13 +10636,13 @@ export const WEB_EVENTS: Event[] = [ GOOGLE_STORAGE_90_PERCENT_FULL: 64, GOOGLE_BACKUP_GB_THRESHOLD: 65, PAYMENTS_PIX_ONBOARDING_BANNER: 66, - P2M_PIX_ORDER_HOME_BANNER: 67, - }, - ], + P2M_PIX_ORDER_HOME_BANNER: 67 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ViewBusinessProfile', @@ -10661,8 +10661,8 @@ export const WEB_EVENTS: Event[] = [ B1K: 8, B10K: 9, B100K: 10, - B1M: 11, - }, + B1M: 11 + } ], bizIgSize: [ 10, @@ -10677,8 +10677,8 @@ export const WEB_EVENTS: Event[] = [ B1K: 8, B10K: 9, B100K: 10, - B1M: 11, - }, + B1M: 11 + } ], businessProfileJid: [3, 'string'], catalogSessionId: [6, 'string'], @@ -10689,8 +10689,8 @@ export const WEB_EVENTS: Event[] = [ 5, { FACEBOOK: 0, - INSTAGRAM: 1, - }, + INSTAGRAM: 1 + } ], profileEntryPoint: [ 8, @@ -10720,8 +10720,8 @@ export const WEB_EVENTS: Event[] = [ MISSED_CALL_NOTIFICATION_BLOCK_ACTION: 23, INTEROP: 24, FORWARDED_BIZ_MSG_DIRECT_TAP: 25, - FORWARDED_BIZ_MSG_CHAT_HEADER: 26, - }, + FORWARDED_BIZ_MSG_CHAT_HEADER: 26 + } ], scrollDepth: [4, 'integer'], viewBusinessProfileAction: [ @@ -10745,20 +10745,20 @@ export const WEB_EVENTS: Event[] = [ ACTION_APP_IMPRESSION: 16, ACTION_CLICK_STATUS: 17, ACTION_EXIT: 18, - ACTION_COVER_PHOTO_IMPRESSION: 19, - }, + ACTION_COVER_PHOTO_IMPRESSION: 19 + } ], websiteSource: [ 2, { SOURCE_OTHER: 1, - SOURCE_INSTAGRAM: 2, - }, - ], + SOURCE_INSTAGRAM: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DisappearingMessageKeepInChat', @@ -10777,15 +10777,15 @@ export const WEB_EVENTS: Event[] = [ UNKEEP_MESSAGE: 2, VIEW_KEPT_MESSAGES: 3, SEARCH_RESULTS_DISPLAY: 4, - SEARCH_RESULTS_TAP: 5, - }, + SEARCH_RESULTS_TAP: 5 + } ], kicActor: [ 6, { SENDER: 1, - RECIPIENT: 2, - }, + RECIPIENT: 2 + } ], kicEntryPoint: [ 7, @@ -10795,8 +10795,8 @@ export const WEB_EVENTS: Event[] = [ CHAT: 3, MEDIA: 4, DOCS: 5, - LINKS: 6, - }, + LINKS: 6 + } ], mediaType: [ 8, @@ -10862,18 +10862,18 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageExpiredOnUnkeep: [9, 'boolean'], messageExpiryTimer: [10, 'integer'], messagesInFolder: [11, 'integer'], messagesSelected: [12, 'integer'], - threadId: [13, 'string'], + threadId: [13, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MdBootstrapHistoryDataReceived', @@ -10890,22 +10890,22 @@ export const WEB_EVENTS: Event[] = [ PUSHNAME: 4, STATUS_V3: 5, NON_BLOCKING_DATA: 6, - ON_DEMAND: 7, - }, + ON_DEMAND: 7 + } ], mdBootstrapPayloadType: [ 2, { CRITICAL: 1, - NON_CRITICAL: 2, - }, + NON_CRITICAL: 2 + } ], mdSessionId: [1, 'string'], - mdTimestamp: [4, 'integer'], + mdTimestamp: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AttachmentTrayActions', @@ -10917,8 +10917,8 @@ export const WEB_EVENTS: Event[] = [ { CLICK: 1, SEND: 2, - CANCEL: 3, - }, + CANCEL: 3 + } ], attachmentTrayActionTarget: [ 3, @@ -10938,8 +10938,8 @@ export const WEB_EVENTS: Event[] = [ ORDER: 13, CATALOG: 14, QUICK_REPLY: 15, - STICKER_MAKER: 16, - }, + STICKER_MAKER: 16 + } ], groupSizeBucket: [ 4, @@ -10959,8 +10959,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], isAGroup: [5, 'boolean'], isSuccessful: [6, 'boolean'], @@ -10969,14 +10969,14 @@ export const WEB_EVENTS: Event[] = [ { PHOTO: 1, VIDEO: 2, - MIXED: 3, - }, + MIXED: 3 + } ], - sendTime: [8, 'integer'], + sendTime: [8, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'AutoMuteLargeGroupActions', @@ -10991,36 +10991,36 @@ export const WEB_EVENTS: Event[] = [ USER_DIALOG_VIEW: 4, USER_ADMIT_BY_OK: 5, USER_ADMIT_BY_MESSAGE_SEND: 6, - USER_DISMISS_BY_UNMUTE: 7, - }, + USER_DISMISS_BY_UNMUTE: 7 + } ], autoMuteGroupId: [2, 'string'], - autoMuteGroupSize: [3, 'integer'], + autoMuteGroupSize: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BusinessMute', id: 1376, props: { muteT: [2, 'timer'], - muteeId: [1, 'string'], + muteeId: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BusinessUnmute', id: 1378, props: { - muteeId: [1, 'string'], + muteeId: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelOpen', @@ -11043,8 +11043,8 @@ export const WEB_EVENTS: Event[] = [ STATUS: 10, ADMIN_INVITE_MESSAGE: 11, MEDIA_BROWSER: 12, - SIMILAR_CHANNEL: 13, - }, + SIMILAR_CHANNEL: 13 + } ], channelEntryPointMetadata: [ 9, @@ -11052,8 +11052,8 @@ export const WEB_EVENTS: Event[] = [ STATUS_HEADER: 1, LINK_TOOLTIP: 2, LINK_BUTTON: 3, - POST_TOOLTIP: 4, - }, + POST_TOOLTIP: 4 + } ], channelSessionId: [3, 'integer'], channelUserType: [ @@ -11062,27 +11062,27 @@ export const WEB_EVENTS: Event[] = [ OWNER: 1, ADMIN: 2, FOLLOWER: 3, - GUEST: 4, - }, + GUEST: 4 + } ], cid: [6, 'string'], hasNetworkConnection: [7, 'boolean'], similarChannelsSessionId: [11, 'integer'], - unreadMessages: [5, 'integer'], + unreadMessages: [5, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GroupCatchUp', id: 3058, props: { - mentionsCountPendingPercentage: [4, 'integer'], + mentionsCountPendingPercentage: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'InlineVideoPlaybackClosed', @@ -11097,8 +11097,8 @@ export const WEB_EVENTS: Event[] = [ LOGO: 1, MUSIC: 2, AUTHOR: 3, - WATCH_MORE_END: 4, - }, + WATCH_MORE_END: 4 + } ], inlineVideoDurationT: [2, 'timer'], inlineVideoError: [11, 'string'], @@ -11115,8 +11115,8 @@ export const WEB_EVENTS: Event[] = [ STREAMABLE: 5, NETFLIX: 6, LASSO: 7, - SHARECHAT: 8, - }, + SHARECHAT: 8 + } ], inlineVideoWatchT: [5, 'timer'], messageType: [ @@ -11127,13 +11127,13 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, - ], + INTEROP: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageDeleteActions', @@ -11143,8 +11143,8 @@ export const WEB_EVENTS: Event[] = [ 1, { DELETE_FOR_ME: 0, - DELETE_FOR_EVERYONE: 1, - }, + DELETE_FOR_EVERYONE: 1 + } ], isAGroup: [2, 'boolean'], mediaType: [ @@ -11211,15 +11211,15 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messagesDeleted: [3, 'integer'], - threadId: [4, 'string'], + threadId: [4, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'Ptt', @@ -11235,23 +11235,23 @@ export const WEB_EVENTS: Event[] = [ { SENT: 1, CANCELLED: 2, - TOO_SHORT: 3, - }, + TOO_SHORT: 3 + } ], pttSize: [3, 'number'], pttSource: [ 2, { FROM_CONVERSATION: 0, - FROM_VOICEMAIL: 1, - }, + FROM_VOICEMAIL: 1 + } ], pttStop: [6, 'boolean'], - pttStopTapCnt: [10, 'integer'], + pttStopTapCnt: [10, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SnackbarDeleteUndo', @@ -11322,22 +11322,22 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messagesUndeleted: [2, 'integer'], snackbarActionType: [ 3, { SNACKBAR_SHOWN: 0, - MESSAGE_UNDELETE: 1, - }, + MESSAGE_UNDELETE: 1 + } ], - threadId: [4, 'string'], + threadId: [4, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebInternDogfoodingUpsell', @@ -11348,13 +11348,13 @@ export const WEB_EVENTS: Event[] = [ { SHOWN: 0, ACCEPT: 1, - DISMISS: 2, - }, - ], + DISMISS: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcChatOpen', @@ -11366,11 +11366,11 @@ export const WEB_EVENTS: Event[] = [ webcFinalRenderedMessageCount: [5, 'integer'], webcRenderedMessageCount: [4, 'integer'], webcUnreadCount: [1, 'number'], - webcWindowHeightFloat: [8, 'number'], + webcWindowHeightFloat: [8, 'number'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcElectronDeprecationCta', @@ -11381,28 +11381,28 @@ export const WEB_EVENTS: Event[] = [ { IMPRESSION: 1, CTA_BTN_CLICK: 2, - CTA_DISMISS: 3, - }, + CTA_DISMISS: 3 + } ], webcElectronDeprecationCtaSource: [ 2, { INTRO_PANEL: 1, BUTTERBAR: 2, - LINK_DEVICE_BANNER: 3, - }, + LINK_DEVICE_BANNER: 3 + } ], webcElectronDeprecationCtaType: [ 3, { SOFT_MIGRATION: 1, - APP_EXPIRY_NOTICE: 2, - }, - ], + APP_EXPIRY_NOTICE: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcLogin', @@ -11424,11 +11424,11 @@ export const WEB_EVENTS: Event[] = [ webcSyncMessageCount: [5, 'integer'], webcSyncMessageSize: [7, 'integer'], webcSyncMessageT: [6, 'timer'], - webcSyncT: [4, 'timer'], + webcSyncT: [4, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMemoryStat', @@ -11439,11 +11439,11 @@ export const WEB_EVENTS: Event[] = [ numMessages: [8, 'number'], totalJsHeapSize: [10, 'integer'], uptime: [6, 'number'], - usedJsHeapSize: [11, 'integer'], + usedJsHeapSize: [11, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMenu', @@ -11454,8 +11454,8 @@ export const WEB_EVENTS: Event[] = [ { THREADS_SCREEN_CLICK: 1, CHAT_SCREEN_CLICK: 2, - SETTINGS_SCREEN_CLICK: 3, - }, + SETTINGS_SCREEN_CLICK: 3 + } ], webcMenuItemLabel: [ 3, @@ -11484,13 +11484,13 @@ export const WEB_EVENTS: Event[] = [ SETTINGS_HELP: 22, OPEN: 23, CLOSE: 24, - BUSINESS_TOOLS: 25, - }, - ], + BUSINESS_TOOLS: 25 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatFolderOpen', @@ -11498,11 +11498,11 @@ export const WEB_EVENTS: Event[] = [ props: { activityIndicatorCount: [2, 'integer'], folderType: [1, 'string'], - hasImportantMessages: [3, 'boolean'], + hasImportantMessages: [3, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'OrderDetailsActionsSmb', @@ -11519,8 +11519,8 @@ export const WEB_EVENTS: Event[] = [ 14, { OPPOSITE_PARTY_INITIATED: 0, - SELF_INITIATED: 1, - }, + SELF_INITIATED: 1 + } ], messageDepth: [15, 'integer'], orderDetailEntryPoint: [7, 'string'], @@ -11585,18 +11585,18 @@ export const WEB_EVENTS: Event[] = [ CONTINUE_ORDER_REQUEST: 57, VIEW_PROMPT: 58, CLICK_PROCEED_WITHOUT_CATALOG: 59, - CLICK_CREATE_ORDER_DETAILS_FROM_PAYMENT_METHOD_ADDED_PROMPT: 60, - }, + CLICK_CREATE_ORDER_DETAILS_FROM_PAYMENT_METHOD_ADDED_PROMPT: 60 + } ], orderEligibleToSend: [11, 'boolean'], paymentStatus: [9, 'boolean'], paymentType: [10, 'string'], sharingOrderStatusEvents: [12, 'boolean'], - threadIdHmac: [16, 'string'], + threadIdHmac: [16, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcStickerMakerEvents', @@ -11612,13 +11612,13 @@ export const WEB_EVENTS: Event[] = [ STICKER_ADDED: 4, TEXT_ADDED: 5, IMAGE_OUTLINED: 6, - SEND_STICKER: 7, - }, - ], + SEND_STICKER: 7 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'QuickReply', @@ -11633,8 +11633,8 @@ export const WEB_EVENTS: Event[] = [ 16, { OPPOSITE_PARTY_INITIATED: 0, - SELF_INITIATED: 1, - }, + SELF_INITIATED: 1 + } ], messageDepth: [17, 'integer'], quickReplyAction: [ @@ -11653,8 +11653,8 @@ export const WEB_EVENTS: Event[] = [ ACTION_SETTINGS_MEDIA_TRANSCODE: 11, ACTION_CHAT_CLICK_CANCEL: 12, ACTION_SMART_DEFAULT_CLICK: 13, - QUICK_REPLY_MESSAGE_SENT: 14, - }, + QUICK_REPLY_MESSAGE_SENT: 14 + } ], quickReplyCount: [2, 'integer'], quickReplyEntryPoint: [ @@ -11665,8 +11665,8 @@ export const WEB_EVENTS: Event[] = [ QUICK_REPLY_ENTRY_POINT_SETTINGS_MENU: 3, QUICK_REPLY_ENTRY_POINT_BANNERS: 4, QUICK_REPLY_ENTRY_POINT_NUX: 5, - QUICK_REPLY_ENTRY_POINT_ACTION_BAR: 6, - }, + QUICK_REPLY_ENTRY_POINT_ACTION_BAR: 6 + } ], quickReplyKeywordCount: [3, 'integer'], quickReplyKeywordMatched: [4, 'boolean'], @@ -11676,8 +11676,8 @@ export const WEB_EVENTS: Event[] = [ UNKNOWN: 0, CONVERSATIONS: 1, BUTTON: 2, - KEYBOARD: 3, - }, + KEYBOARD: 3 + } ], quickReplyTranscodeResult: [ 8, @@ -11687,16 +11687,16 @@ export const WEB_EVENTS: Event[] = [ QUICK_REPLY_TRANSCODE_RESULT_FAIL_IMAGE_UNKNOWN: 3, QUICK_REPLY_TRANSCODE_RESULT_FAIL_IMAGE_ENCODING: 4, QUICK_REPLY_TRANSCODE_RESULT_FAIL_IMAGE_FILE_COPY: 5, - QUICK_REPLY_TRANSCODE_RESULT_FAIL_VIDEO_UNKNOWN: 6, - }, + QUICK_REPLY_TRANSCODE_RESULT_FAIL_VIDEO_UNKNOWN: 6 + } ], threadCreationDate: [14, 'string'], threadEntryPoint: [13, 'string'], - threadIdHmac: [15, 'string'], + threadIdHmac: [15, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UserNotice', @@ -11708,16 +11708,16 @@ export const WEB_EVENTS: Event[] = [ AUTO_START: 0, BANNER: 1, DEEP_LINK: 2, - JUST_IN_TIME: 3, - }, + JUST_IN_TIME: 3 + } ], noticeType: [ 4, { LEGACY_USER_NOTICE: 0, BADGED_USER_NOTICE: 1, - PDFN_DISCLOSURE: 2, - }, + PDFN_DISCLOSURE: 2 + } ], userNoticeContentVersion: [2, 'integer'], userNoticeEvent: [ @@ -11775,14 +11775,14 @@ export const WEB_EVENTS: Event[] = [ PDFN_6_SECONDARY_BTN_CLICKED: 1020, PDFN_7_SECONDARY_BTN_CLICKED: 1021, PDFN_8_SECONDARY_BTN_CLICKED: 1022, - PDFN_9_SECONDARY_BTN_CLICKED: 1023, - }, + PDFN_9_SECONDARY_BTN_CLICKED: 1023 + } ], - userNoticeId: [1, 'integer'], + userNoticeId: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessageContextMenuActions', @@ -11797,8 +11797,8 @@ export const WEB_EVENTS: Event[] = [ OPEN: 1, CLICK: 2, COMPLETE: 3, - CANCEL: 4, - }, + CANCEL: 4 + } ], messageContextMenuOption: [ 5, @@ -11814,13 +11814,13 @@ export const WEB_EVENTS: Event[] = [ REPORT: 9, MESSAGE_CONTACT: 10, MESSAGE_INFO: 11, - EDIT: 12, - }, - ], + EDIT: 12 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WaFsGroupJoinRequestAction', @@ -11836,16 +11836,16 @@ export const WEB_EVENTS: Event[] = [ MEMBERSHIP_REQUEST_APPROVAL_MODE_OFF: 4, MEMBERSHIP_REQUEST_APPROVE: 5, MEMBERSHIP_REQUEST_REJECT: 6, - MEMBERSHIP_REQUEST_CANCEL: 7, - }, + MEMBERSHIP_REQUEST_CANCEL: 7 + } ], groupJoinRequestGroupsInCommon: [5, 'integer'], isSuccessful: [3, 'boolean'], - serverResponseTime: [4, 'timer'], + serverResponseTime: [4, 'timer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ReportToAdminEvents', @@ -11857,14 +11857,14 @@ export const WEB_EVENTS: Event[] = [ CLICK_OPEN_ADMIN_DASHBOARD: 0, CLICK_SEND_FOR_ADMIN_REVIEW: 1, CLICK_CONFIRM_SEND_FOR_ADMIN_REVIEW: 2, - CLICK_CANCEL_SEND_FOR_ADMIN_REVIEW: 3, - }, + CLICK_CANCEL_SEND_FOR_ADMIN_REVIEW: 3 + } ], - rtaGroupId: [2, 'string'], + rtaGroupId: [2, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatAction', @@ -11876,8 +11876,8 @@ export const WEB_EVENTS: Event[] = [ INDIVIDUAL: 1, GROUP: 2, BUSINESS: 3, - BROADCAST_LIST: 4, - }, + BROADCAST_LIST: 4 + } ], chatActionEntryPoint: [ 2, @@ -11889,8 +11889,8 @@ export const WEB_EVENTS: Event[] = [ CONVERSATION_LIST_BULK_EDIT: 5, CONVERSATION_MENU: 6, WEB_ACTION: 7, - SYSTEM_NOTIFICATIONS: 8, - }, + SYSTEM_NOTIFICATIONS: 8 + } ], chatActionMuteDuration: [4, 'timer'], chatActionType: [ @@ -11904,13 +11904,13 @@ export const WEB_EVENTS: Event[] = [ DELETE: 6, PIN: 7, UNREAD: 8, - READ: 9, - }, - ], + READ: 9 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'LabelEvent', @@ -11930,8 +11930,8 @@ export const WEB_EVENTS: Event[] = [ CLICK_POSITIVE: 5, CLICK_NEGATIVE: 6, UPDATE_LABEL_COUNT: 7, - AUTO_ADDED: 8, - }, + AUTO_ADDED: 8 + } ], labelOperationEntryPoint: [6, 'string'], labelTarget: [ @@ -11950,25 +11950,25 @@ export const WEB_EVENTS: Event[] = [ BULK_UNLABEL_DIALOG: 11, LABEL_COMBINED_DIALOG: 12, GROUP: 13, - BROADCAST: 14, - }, + BROADCAST: 14 + } ], lastMessageDirection: [ 14, { OPPOSITE_PARTY_INITIATED: 0, - SELF_INITIATED: 1, - }, + SELF_INITIATED: 1 + } ], messageDepth: [15, 'integer'], predefinedLabelNumber: [3, 'integer'], threadCreationDate: [11, 'string'], threadId: [12, 'string'], - threadIdHmac: [13, 'string'], + threadIdHmac: [13, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StatusReportingEvents', @@ -11980,13 +11980,13 @@ export const WEB_EVENTS: Event[] = [ CLICK_REPORT: 0, CLICK_SUBMIT_REPORT: 1, CLICK_CANCEL_REPORT: 2, - CLICK_SUBMIT_REPORT_BLOCK: 3, - }, - ], + CLICK_SUBMIT_REPORT_BLOCK: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BusinessInteraction', @@ -11996,16 +11996,16 @@ export const WEB_EVENTS: Event[] = [ 1, { ACTION_CLICK: 1, - ACTION_MSG_SENT: 2, - }, + ACTION_MSG_SENT: 2 + } ], businessInteractionTargetScreen: [ 2, { INDIVIDUAL_CHAT: 1, LANDING_PAGE: 2, - OTHER: 3, - }, + OTHER: 3 + } ], businessJid: [3, 'string'], entryPointApp: [ @@ -12014,8 +12014,8 @@ export const WEB_EVENTS: Event[] = [ FACEBOOK: 1, INSTAGRAM: 2, WHATSAPP: 3, - EXTERNAL: 4, - }, + EXTERNAL: 4 + } ], entryPointSource: [ 5, @@ -12024,8 +12024,8 @@ export const WEB_EVENTS: Event[] = [ MESSAGE_SHORT_LINK: 2, QR_CODE: 3, CUSTOM_LINK: 4, - CUSTOM_QR_CODE_LINK: 5, - }, + CUSTOM_QR_CODE_LINK: 5 + } ], internalEntryPoint: [ 6, @@ -12035,14 +12035,14 @@ export const WEB_EVENTS: Event[] = [ GROUP: 3, OTHER: 4, OUTSIDE_OF_WA: 5, - CHANNEL: 6, - }, + CHANNEL: 6 + } ], - sequenceNumber: [7, 'integer'], + sequenceNumber: [7, 'integer'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'BusinessToolsClick', @@ -12058,8 +12058,8 @@ export const WEB_EVENTS: Event[] = [ ENTRY_DEEPLINK: 4, ENTRY_STATUS_TAB_MENU: 5, ENTRY_CALLS_TAB_MENU: 6, - ENTRY_BUSINESS_TOOLS_TAB: 7, - }, + ENTRY_BUSINESS_TOOLS_TAB: 7 + } ], businessToolsItem: [ 3, @@ -12085,8 +12085,8 @@ export const WEB_EVENTS: Event[] = [ PREMIUM_TOOLS: 18, BUSINESS_DIRECTORY: 19, MANAGE_ADS: 20, - META_VERIFIED: 21, - }, + META_VERIFIED: 21 + } ], businessToolsSequenceNumber: [2, 'integer'], businessToolsSessionId: [1, 'string'], @@ -12094,13 +12094,13 @@ export const WEB_EVENTS: Event[] = [ 4, { FACEBOOK: 0, - INSTAGRAM: 1, - }, - ], + INSTAGRAM: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BusinessToolsEntry', @@ -12116,15 +12116,15 @@ export const WEB_EVENTS: Event[] = [ ENTRY_DEEPLINK: 4, ENTRY_STATUS_TAB_MENU: 5, ENTRY_CALLS_TAB_MENU: 6, - ENTRY_BUSINESS_TOOLS_TAB: 7, - }, + ENTRY_BUSINESS_TOOLS_TAB: 7 + } ], businessToolsSequenceNumber: [2, 'integer'], - businessToolsSessionId: [1, 'string'], + businessToolsSessionId: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'BusinessToolsImpression', @@ -12140,15 +12140,15 @@ export const WEB_EVENTS: Event[] = [ ENTRY_DEEPLINK: 4, ENTRY_STATUS_TAB_MENU: 5, ENTRY_CALLS_TAB_MENU: 6, - ENTRY_BUSINESS_TOOLS_TAB: 7, - }, + ENTRY_BUSINESS_TOOLS_TAB: 7 + } ], businessToolsSequenceNumber: [2, 'integer'], - businessToolsSessionId: [1, 'string'], + businessToolsSessionId: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CadminDemote', @@ -12158,8 +12158,8 @@ export const WEB_EVENTS: Event[] = [ 1, { PROMOTION_NOTIFICATION: 1, - MEMBER_LIST: 2, - }, + MEMBER_LIST: 2 + } ], cadminDemoteResult: [ 2, @@ -12169,14 +12169,14 @@ export const WEB_EVENTS: Event[] = [ CANCEL: 3, RETRY_SUCCESS: 4, RETRY_FAILURE: 5, - RETRY_CANCEL: 6, - }, + RETRY_CANCEL: 6 + } ], - isLastCadminOrCreator: [4, 'boolean'], + isLastCadminOrCreator: [4, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelAdmin', @@ -12187,8 +12187,8 @@ export const WEB_EVENTS: Event[] = [ 2, { CREATION: 1, - EDIT: 2, - }, + EDIT: 2 + } ], channelAdminAction: [ 3, @@ -12210,14 +12210,14 @@ export const WEB_EVENTS: Event[] = [ SEARCH_FOLLOWER: 15, REACTIONS_SET_TO_ANY_EMOJI: 16, REACTIONS_SET_TO_DEFAULT_EMOJI: 17, - REACTIONS_SET_TO_NONE_EMOJI: 18, - }, + REACTIONS_SET_TO_NONE_EMOJI: 18 + } ], - channelAdminSessionId: [4, 'integer'], + channelAdminSessionId: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChannelDyi', @@ -12228,13 +12228,13 @@ export const WEB_EVENTS: Event[] = [ { CHANNEL_REPORT_REQUEST: 1, CHANNEL_REPORT_DOWNLOAD: 2, - CHANNEL_REPORT_EXPORT: 3, - }, - ], + CHANNEL_REPORT_EXPORT: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ChatFilterEvent', @@ -12296,8 +12296,8 @@ export const WEB_EVENTS: Event[] = [ CLOSE_BTN_CLICKED: 51, AI_CHAT_CLICK: 52, NEW_CHAT_CLICK: 53, - SERP_LOADED: 54, - }, + SERP_LOADED: 54 + } ], activitySessionId: [6, 'string'], filterType: [ @@ -12325,8 +12325,8 @@ export const WEB_EVENTS: Event[] = [ PERSONAL: 19, BUSINESS: 20, LABEL: 21, - FAVORITES: 22, - }, + FAVORITES: 22 + } ], labelName: [11, 'string'], metadata: [7, 'string'], @@ -12341,20 +12341,20 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, BROADCAST_LIST: 3, MESSAGE: 4, - BUSINESS: 5, - }, + BUSINESS: 5 + } ], sessionId: [3, 'integer'], targetScreen: [ 5, { - CHAT_LIST: 0, - }, - ], + CHAT_LIST: 0 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CommunityCreation', @@ -12377,8 +12377,8 @@ export const WEB_EVENTS: Event[] = [ CREATE_COMMUNITY_FAIL: 11, HELP_ICON_CLICK: 12, LINK_GROUP_CONFIRMATION_OK: 13, - LINK_GROUP_CONFIRMATION_CANCEL: 14, - }, + LINK_GROUP_CONFIRMATION_CANCEL: 14 + } ], communityCreationCurrentScreen: [ 3, @@ -12391,8 +12391,8 @@ export const WEB_EVENTS: Event[] = [ DEEP_LINK: 6, BANNER: 7, GROUP_INFO: 8, - LINK_GROUP_CONFIRMATION: 9, - }, + LINK_GROUP_CONFIRMATION: 9 + } ], communityCreationEntrypoint: [ 5, @@ -12404,15 +12404,15 @@ export const WEB_EVENTS: Event[] = [ DEEP_LINK_BANNER: 5, DEEP_LINK_PSA: 6, DEEP_LINK_CHAT: 7, - DEEP_LINK_CHANNEL: 8, - }, + DEEP_LINK_CHANNEL: 8 + } ], communityCreationSessionId: [4, 'string'], - communityId: [6, 'string'], + communityId: [6, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ContactUsSession', @@ -12427,8 +12427,8 @@ export const WEB_EVENTS: Event[] = [ EMAIL_SEND: 3, IN_APP_FAQ: 4, CANCELLED: 5, - FAQ: 6, - }, + FAQ: 6 + } ], contactUsFaq: [2, 'boolean'], contactUsLogs: [4, 'boolean'], @@ -12437,11 +12437,11 @@ export const WEB_EVENTS: Event[] = [ contactUsOutageEmail: [6, 'boolean'], contactUsScreenshotC: [19, 'number'], contactUsT: [11, 'timer'], - languageCode: [21, 'string'], + languageCode: [21, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DeepLinkMsgSent', @@ -12450,14 +12450,14 @@ export const WEB_EVENTS: Event[] = [ deepLinkAction: [ 1, { - MSG_SENT: 1, - }, + MSG_SENT: 1 + } ], - deepLinkSessionId: [2, 'string'], + deepLinkSessionId: [2, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DeepLinkOpen', @@ -12471,8 +12471,8 @@ export const WEB_EVENTS: Event[] = [ QR_CODE_SHEET: 3, DEEP_LINK_BANNER: 4, DEEP_LINK_SMB_NOTIFICATION: 5, - DEEP_LINK_MESSENGER_APP: 6, - }, + DEEP_LINK_MESSENGER_APP: 6 + } ], deepLinkSessionId: [6, 'string'], deepLinkType: [ @@ -12594,8 +12594,8 @@ export const WEB_EVENTS: Event[] = [ DEEP_LINK_CONTACTS_PERMISSION: 114, DEEP_LINK_NOTIFICATIONS_PERMISSION: 115, DEEP_LINK_WABAI_ONBOARDING: 116, - DEEP_LINK_CHAT_LIST: 117, - }, + DEEP_LINK_CHAT_LIST: 117 + } ], isContact: [4, 'boolean'], linkOwnerType: [ @@ -12603,14 +12603,14 @@ export const WEB_EVENTS: Event[] = [ { CONSUMER: 1, SMB: 2, - ENT: 3, - }, + ENT: 3 + } ], - sourceSurface: [7, 'integer'], + sourceSurface: [7, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DisappearingMessageChatPicker', @@ -12622,8 +12622,8 @@ export const WEB_EVENTS: Event[] = [ { DEFAULT_MODE_SETTING: 0, STORAGE_SETTING: 1, - PRIVACY_SETTING: 2, - }, + PRIVACY_SETTING: 2 + } ], dmChatPickerEventName: [ 3, @@ -12631,18 +12631,18 @@ export const WEB_EVENTS: Event[] = [ CHAT_PICKER_LINK_IMPRESSION: 0, CHAT_PICKER_TRAY_OPEN: 1, CHAT_PICKER_TRAY_EXIT: 2, - CHAT_PICKER_CHATS_SELECTED: 3, - }, + CHAT_PICKER_CHATS_SELECTED: 3 + } ], ephemeralityDuration: [4, 'integer'], groupChatsSelected: [5, 'integer'], groupSizeDistributionJson: [9, 'string'], newlyEphemeralChats: [7, 'integer'], - totalChatsInChatPicker: [8, 'integer'], + totalChatsInChatPicker: [8, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DisappearingModeSettingChange', @@ -12657,16 +12657,16 @@ export const WEB_EVENTS: Event[] = [ GROUP_CHAT_DISAPPEARING_MESSAGES_SETTING: 4, DEEP_LINK: 5, STORAGE_SETTINGS: 6, - PRIVACY_SETTINGS: 7, - }, + PRIVACY_SETTINGS: 7 + } ], lastToggleTimestamp: [3, 'integer'], newEphemeralityDuration: [2, 'integer'], - previousEphemeralityDuration: [1, 'integer'], + previousEphemeralityDuration: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'DisappearingModeSettingEvents', @@ -12681,8 +12681,8 @@ export const WEB_EVENTS: Event[] = [ GROUP_CHAT_DISAPPEARING_MESSAGES_SETTING: 4, DEEP_LINK: 5, STORAGE_SETTINGS: 6, - PRIVACY_SETTINGS: 7, - }, + PRIVACY_SETTINGS: 7 + } ], disappearingModeSettingEventName: [ 1, @@ -12690,16 +12690,16 @@ export const WEB_EVENTS: Event[] = [ DEFAULT_MESSAGE_TIMER_OPEN: 1, DEFAULT_MESSAGE_TIMER_SET: 2, DEFAULT_MESSAGE_TIMER_EXIT: 3, - LEARN_MORE_CLICK: 4, - }, + LEARN_MORE_CLICK: 4 + } ], lastToggleTimestamp: [2, 'integer'], newEphemeralityDuration: [3, 'integer'], - previousEphemeralityDuration: [4, 'integer'], + previousEphemeralityDuration: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EditBusinessProfile', @@ -12714,8 +12714,8 @@ export const WEB_EVENTS: Event[] = [ QUICK_REPLY_SMART_DEFAULT: 4, WA_PAGES: 5, PROFILE_COMPLETENESS: 6, - DIRECTORY_ONBOARDING: 7, - }, + DIRECTORY_ONBOARDING: 7 + } ], editBusinessProfileSessionId: [2, 'string'], editProfileAction: [ @@ -12737,8 +12737,8 @@ export const WEB_EVENTS: Event[] = [ UPGRADE_TO_CUSTOM_LINK_CLICK: 14, DIALOG_BOX_GEOCODE_IMPRESSION: 15, DIALOG_BOX_GEOCODE_ACCEPT: 16, - DIALOG_BOX_GEOCODE_REVOKE: 17, - }, + DIALOG_BOX_GEOCODE_REVOKE: 17 + } ], editProfileActionField: [ 9, @@ -12749,19 +12749,19 @@ export const WEB_EVENTS: Event[] = [ EMAIL: 4, WEBSITE: 5, CATEGORY: 6, - PROFILE: 7, - }, + PROFILE: 7 + } ], hasAddress: [5, 'boolean'], hasCategory: [4, 'boolean'], hasDescription: [3, 'boolean'], hasEmail: [7, 'boolean'], hasHours: [6, 'boolean'], - hasWebsite: [8, 'boolean'], + hasWebsite: [8, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'EphemeralSettingChange', @@ -12777,8 +12777,8 @@ export const WEB_EVENTS: Event[] = [ CHAT_PICKER: 4, EPHEMERAL_NUX: 5, CHAT_PICKER_DISAPPEARING_MODE_TIMER: 6, - CHAT_PICKER_STORAGE_SETTING: 7, - }, + CHAT_PICKER_STORAGE_SETTING: 7 + } ], ephemeralSettingGroupSize: [ 5, @@ -12800,15 +12800,15 @@ export const WEB_EVENTS: Event[] = [ LT4000: 15, LT4500: 16, LT5000: 17, - LARGEST_BUCKET: 18, - }, + LARGEST_BUCKET: 18 + } ], previousEphemeralityDuration: [2, 'integer'], - threadId: [6, 'string'], + threadId: [6, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GifFromProviderSent', @@ -12818,13 +12818,13 @@ export const WEB_EVENTS: Event[] = [ 1, { GIPHY: 0, - TENOR: 1, - }, - ], + TENOR: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GifSearchCancelled', @@ -12834,13 +12834,13 @@ export const WEB_EVENTS: Event[] = [ 1, { GIPHY: 0, - TENOR: 1, - }, - ], + TENOR: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GifSearchNoResults', @@ -12850,15 +12850,15 @@ export const WEB_EVENTS: Event[] = [ 1, { GIPHY: 0, - TENOR: 1, - }, + TENOR: 1 + } ], inputLanguageCode: [3, 'string'], - languageCode: [2, 'string'], + languageCode: [2, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GifSearchResultTapped', @@ -12868,14 +12868,14 @@ export const WEB_EVENTS: Event[] = [ 1, { GIPHY: 0, - TENOR: 1, - }, + TENOR: 1 + } ], - rank: [2, 'integer'], + rank: [2, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GifSearchSessionStarted', @@ -12885,13 +12885,13 @@ export const WEB_EVENTS: Event[] = [ 1, { GIPHY: 0, - TENOR: 1, - }, - ], + TENOR: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'KeepInChatErrors', @@ -12904,8 +12904,8 @@ export const WEB_EVENTS: Event[] = [ 4, { KEEP_MESSAGE: 1, - UNKEEP_MESSAGE: 2, - }, + UNKEEP_MESSAGE: 2 + } ], kicErrorCode: [ 5, @@ -12925,14 +12925,14 @@ export const WEB_EVENTS: Event[] = [ KEPT_BEYOND_EXPIRY: 13, NOT_PART_OF_THE_GROUP: 14, CONTACT_BLOCKED: 15, - UNKNOWN: 999, - }, + UNKNOWN: 999 + } ], - kicMessageEphemeralityDuration: [6, 'integer'], + kicMessageEphemeralityDuration: [6, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'KeepInChatPerf', @@ -12957,30 +12957,30 @@ export const WEB_EVENTS: Event[] = [ KEPT_BEYOND_EXPIRY: 13, NOT_PART_OF_THE_GROUP: 14, CONTACT_BLOCKED: 15, - UNKNOWN: 999, - }, + UNKNOWN: 999 + } ], kicMessageEphemeralityDuration: [3, 'integer'], kicRequestType: [ 4, { KEEP: 1, - UNKEEP: 2, - }, + UNKEEP: 2 + } ], requestSendTime: [5, 'integer'], response: [ 6, { SUCCESS: 1, - ERROR: 2, - }, + ERROR: 2 + } ], - threadId: [7, 'string'], + threadId: [7, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MessagingUserJourney', @@ -13052,8 +13052,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messagingActionType: [ 2, @@ -13098,8 +13098,8 @@ export const WEB_EVENTS: Event[] = [ CLICK_PIN: 37, DISPLAY: 38, SELECT_OPTION: 39, - CLICK_UNPIN: 40, - }, + CLICK_UNPIN: 40 + } ], pinInChatExpirySecs: [3, 'integer'], threadType: [ @@ -13112,8 +13112,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL: 5, SUB_GROUP: 6, DEFAULT_SUB_GROUP: 7, - PARENT_GROUP: 8, - }, + PARENT_GROUP: 8 + } ], uiSurface: [ 5, @@ -13208,8 +13208,8 @@ export const WEB_EVENTS: Event[] = [ GROUP_MEMBER_ADD_GROUP_CREATION: 89, GROUP_MEMBER_ADD_EXISTING_GROUP: 90, GROUP_CHAT: 91, - GROUP_CREATION: 92, - }, + GROUP_CREATION: 92 + } ], userJourneyFunnelId: [6, 'string'], userRole: [ @@ -13217,13 +13217,13 @@ export const WEB_EVENTS: Event[] = [ { MEMBER: 0, ADMIN: 1, - CADMIN: 2, - }, - ], + CADMIN: 2 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'MetaVerifiedInteraction', @@ -13235,8 +13235,8 @@ export const WEB_EVENTS: Event[] = [ { SMBA: 1, SMBI: 2, - ENT: 3, - }, + ENT: 3 + } ], metaVerifiedInteractionAction: [ 3, @@ -13251,8 +13251,8 @@ export const WEB_EVENTS: Event[] = [ MV_INTERACTION_ACTION_CLICK_CUSTOM_WEBPAGE_AND_LINK: 8, MV_INTERACTION_ACTION_CLICK_MULTI_DEVICE: 9, MV_INTERACTION_ACTION_CLICK_MV_LEARN_MORE: 10, - MV_INTERACTION_ACTION_CLICK_MV_HOME: 11, - }, + MV_INTERACTION_ACTION_CLICK_MV_HOME: 11 + } ], metaVerifiedInteractionAssetType: [ 4, @@ -13260,8 +13260,8 @@ export const WEB_EVENTS: Event[] = [ CHANNEL: 1, SMB: 2, ENT: 3, - PERSONAL: 4, - }, + PERSONAL: 4 + } ], metaVerifiedInteractionReferral: [ 5, @@ -13270,8 +13270,8 @@ export const WEB_EVENTS: Event[] = [ CONTACT_CARD: 2, SETTINGS: 3, BUSINESS_TOOLS: 4, - NOTIFICATION: 5, - }, + NOTIFICATION: 5 + } ], metaVerifiedInteractionSurface: [ 6, @@ -13281,13 +13281,13 @@ export const WEB_EVENTS: Event[] = [ CROSS_SELL_PROFILE_INTERSTITIAL: 3, META_VERIFIED_HOME: 4, SETTINGS: 5, - BUSINESS_TOOLS: 6, - }, - ], + BUSINESS_TOOLS: 6 + } + ] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'PaidMessagingUserInteractionsLogger', @@ -13304,24 +13304,24 @@ export const WEB_EVENTS: Event[] = [ CTA_CALL: 5, CTA_REMINDER: 6, SEE_ALL: 7, - THUMBNAIL: 8, - }, + THUMBNAIL: 8 + } ], pmxActionType: [ 2, { VIEW: 0, CLICK: 1, - READ: 2, - }, + READ: 2 + } ], pmxComponentType: [ 3, { NONE: 0, HEADER: 1, - BUTTON: 2, - }, + BUTTON: 2 + } ], pmxHashedMessageKey: [11, 'integer'], pmxHeaderMediaType: [ @@ -13332,33 +13332,33 @@ export const WEB_EVENTS: Event[] = [ VIDEO: 2, LOCATION: 3, DOCUMENT: 4, - GIF: 5, - }, + GIF: 5 + } ], pmxHostStorage: [ 8, { NONE: 0, ON_PREMISE: 1, - FACEBOOK: 2, - }, + FACEBOOK: 2 + } ], pmxMarketingFormat: [ 5, { CAROUSEL: 0, MPM: 1, - CUSTOM: 2, - }, + CUSTOM: 2 + } ], pmxMessageDeliveredTs: [10, 'integer'], pmxQueryParams: [6, 'string'], pmxSenderCountryCode: [9, 'string'], - templateId: [7, 'string'], + templateId: [7, 'string'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'PinInChatInteraction', @@ -13368,8 +13368,8 @@ export const WEB_EVENTS: Event[] = [ 1, { ADMIN: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], groupSize: [2, 'integer'], groupTypeClient: [ @@ -13378,8 +13378,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_GROUP: 1, SUB_GROUP: 2, DEFAULT_SUB_GROUP: 3, - PARENT_GROUP: 4, - }, + PARENT_GROUP: 4 + } ], isAGroup: [4, 'boolean'], isSelfPin: [8, 'boolean'], @@ -13447,21 +13447,21 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], pinCount: [6, 'integer'], pinInChatInteractionType: [ 7, { - TAP_ON_BANNER: 1, - }, + TAP_ON_BANNER: 1 + } ], - pinIndex: [9, 'integer'], + pinIndex: [9, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PinInChatMessageSend', @@ -13471,8 +13471,8 @@ export const WEB_EVENTS: Event[] = [ 1, { ADMIN: 1, - MEMBER: 2, - }, + MEMBER: 2 + } ], groupTypeClient: [ 2, @@ -13480,8 +13480,8 @@ export const WEB_EVENTS: Event[] = [ REGULAR_GROUP: 1, SUB_GROUP: 2, DEFAULT_SUB_GROUP: 3, - PARENT_GROUP: 4, - }, + PARENT_GROUP: 4 + } ], isAGroup: [3, 'boolean'], isSelfParentMessage: [7, 'boolean'], @@ -13550,22 +13550,22 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], pinInChatExpirySecs: [5, 'integer'], pinInChatType: [ 6, { PIN_FOR_ALL: 1, - UNPIN_FOR_ALL: 2, - }, + UNPIN_FOR_ALL: 2 + } ], - timeRemainingToExpirySecs: [9, 'integer'], + timeRemainingToExpirySecs: [9, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PnhRequestRevealAction', @@ -13579,21 +13579,21 @@ export const WEB_EVENTS: Event[] = [ SEND_REQUEST: 3, SHARE_PN_SHEET_APPEAR: 4, DISMISS: 5, - SHARE_NUMBER: 6, - }, + SHARE_NUMBER: 6 + } ], pnhChatParty: [ 2, { BIZ: 1, - CONSUMER: 2, - }, + CONSUMER: 2 + } ], pnhChatType: [ 3, { - CTWA: 1, - }, + CTWA: 1 + } ], pnhEntryPoint: [ 4, @@ -13604,14 +13604,14 @@ export const WEB_EVENTS: Event[] = [ VIDEO: 4, PN_REQUEST: 5, SYSTEM_MESSAGE: 6, - CHAT_INFO_PN_VISIBILITY: 7, - }, + CHAT_INFO_PN_VISIBILITY: 7 + } ], - threadId: [5, 'string'], + threadId: [5, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PollsActions', @@ -13625,8 +13625,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, STATUS: 3, BROADCAST: 4, - CHANNEL: 5, - }, + CHANNEL: 5 + } ], groupSizeBucket: [ 1, @@ -13646,8 +13646,8 @@ export const WEB_EVENTS: Event[] = [ LT4000: 12, LT4500: 13, LT5000: 14, - LARGEST_BUCKET: 15, - }, + LARGEST_BUCKET: 15 + } ], isAGroup: [6, 'boolean'], isAdmin: [2, 'boolean'], @@ -13659,8 +13659,8 @@ export const WEB_EVENTS: Event[] = [ VIEW_RESULTS_MODAL: 4, REMOVE_VOTE: 5, VOTE: 6, - CHANGE_VOTE: 7, - }, + CHANGE_VOTE: 7 + } ], pollCreationDs: [4, 'integer'], pollOptionsCount: [5, 'integer'], @@ -13669,13 +13669,13 @@ export const WEB_EVENTS: Event[] = [ { GROUP: 1, SUBGROUP: 2, - DEFAULT_SUBGROUP: 3, - }, - ], + DEFAULT_SUBGROUP: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PrivacySettingsClick', @@ -13691,8 +13691,8 @@ export const WEB_EVENTS: Event[] = [ PRIVACY_CHECKUP_BANNER: 4, PRIVACY_CHECKUP_DEEP_LINK: 5, PRIVACY_CHECKUP_WA_CHAT: 6, - PRIVACY_CHECKUP_SETTINGS_SEARCH: 7, - }, + PRIVACY_CHECKUP_SETTINGS_SEARCH: 7 + } ], privacyControlItem: [ 2, @@ -13718,13 +13718,13 @@ export const WEB_EVENTS: Event[] = [ FACE_AND_HAND_EFFECTS: 18, ADVANCED: 19, CHAT_LOCK: 20, - AVATAR: 21, - }, - ], + AVATAR: 21 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PttPlayback', @@ -13743,8 +13743,8 @@ export const WEB_EVENTS: Event[] = [ { SPEED_1: 0, SPEED_1_5: 1, - SPEED_2: 2, - }, + SPEED_2: 2 + } ], pttPlaybackSpeedCnt: [11, 'integer'], pttPlayedOutOfChat: [18, 'boolean'], @@ -13759,8 +13759,8 @@ export const WEB_EVENTS: Event[] = [ ANDROIDPLAYER: 4, EXOPLAYER: 5, UWPPLAYER: 6, - VOIPPLAYER: 7, - }, + VOIPPLAYER: 7 + } ], pttPlayerInitT: [2, 'timer'], pttPlayerPlayT: [3, 'timer'], @@ -13769,8 +13769,8 @@ export const WEB_EVENTS: Event[] = [ 5, { MANUAL: 0, - SEQUENTIAL: 1, - }, + SEQUENTIAL: 1 + } ], pttType: [ 6, @@ -13781,13 +13781,13 @@ export const WEB_EVENTS: Event[] = [ AMR_NB: 3, AMR_WB: 4, OPUS: 5, - MULTIPLE_TRACKS: 6, - }, - ], + MULTIPLE_TRACKS: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ReactionActions', @@ -13857,8 +13857,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], messageType: [ 1, @@ -13868,21 +13868,21 @@ export const WEB_EVENTS: Event[] = [ BROADCAST: 3, STATUS: 4, CHANNEL: 5, - INTEROP: 6, - }, + INTEROP: 6 + } ], reactionAction: [ 2, { OPEN_TRAY: 1, DELETE: 2, - UPDATE: 3, - }, - ], + UPDATE: 3 + } + ] }, weight: 20, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SettingsClick', @@ -13895,8 +13895,8 @@ export const WEB_EVENTS: Event[] = [ SETTINGS_SEARCH: 1, DEEP_LINK: 2, PRIVACY_CHECKUP: 3, - METAB_SCREEN: 4, - }, + METAB_SCREEN: 4 + } ], settingsItem: [ 1, @@ -13934,20 +13934,20 @@ export const WEB_EVENTS: Event[] = [ LOGOUT: 30, META_VERIFIED: 31, TEXT_STATUS: 32, - THIRD_PARTY: 33, - }, + THIRD_PARTY: 33 + } ], settingsPageType: [ 3, { SETTINGS: 0, - ME_TAB: 1, - }, - ], + ME_TAB: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SettingsSearchInitiate', @@ -13957,13 +13957,13 @@ export const WEB_EVENTS: Event[] = [ 1, { SETTINGS: 0, - ME_TAB: 1, - }, - ], + ME_TAB: 1 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SettingsSearchTap', @@ -14006,13 +14006,13 @@ export const WEB_EVENTS: Event[] = [ LOGOUT: 30, META_VERIFIED: 31, TEXT_STATUS: 32, - THIRD_PARTY: 33, - }, - ], + THIRD_PARTY: 33 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SmbDataSharingConsentSetting', @@ -14023,15 +14023,15 @@ export const WEB_EVENTS: Event[] = [ { ENTRY_POINT_ORDER_SCREEN: 0, ENTRY_POINT_SETTINGS_SCREEN: 1, - ENTRY_POINT_LABELS_SCREEN: 2, - }, + ENTRY_POINT_LABELS_SCREEN: 2 + } ], smbDataSharingConsentSettingType: [2, 'boolean'], - smbDataSharingConsentSettingVersion: [3, 'integer'], + smbDataSharingConsentSettingVersion: [3, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StatusItemView', @@ -14103,8 +14103,8 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], psaCampaignId: [17, 'string'], psaCampaignItemIndex: [18, 'integer'], @@ -14116,8 +14116,8 @@ export const WEB_EVENTS: Event[] = [ { SUCCESS: 1, CANCEL: 2, - ERROR: 3, - }, + ERROR: 3 + } ], statusItemImpressionCount: [14, 'integer'], statusItemIndex: [16, 'integer'], @@ -14154,8 +14154,8 @@ export const WEB_EVENTS: Event[] = [ MEDIA_INVALID_CODE: 23, MEDIA_SUSPICIOUS_CONTENT: 24, MEDIA_ERROR_CRONET: 25, - PARTIAL_IMAGE_DOWNLOAD: 26, - }, + PARTIAL_IMAGE_DOWNLOAD: 26 + } ], statusItemViewTime: [6, 'timer'], statusRowIndex: [2, 'integer'], @@ -14176,8 +14176,8 @@ export const WEB_EVENTS: Event[] = [ SEE_ALL_RECENT: 12, SEE_ALL_VIEWED: 13, SEE_ALL_MUTED: 14, - SEE_ALL_SEARCH: 15, - }, + SEE_ALL_SEARCH: 15 + } ], statusViewerSessionId: [1, 'integer'], urlStatusClicked: [ @@ -14185,21 +14185,21 @@ export const WEB_EVENTS: Event[] = [ { ONE_CLICK: 1, TWO_CLICKS: 2, - NO_CLICK: 3, - }, + NO_CLICK: 3 + } ], urlStatusType: [ 27, { NO_PREVIEW: 1, TRUNCATED: 2, - NON_TRUNCATED: 3, - }, - ], + NON_TRUNCATED: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StatusReply', @@ -14226,22 +14226,22 @@ export const WEB_EVENTS: Event[] = [ GIF_VIDEO: 14, QUICK_REPLY: 15, POLL: 16, - AVATAR_QUICK_REPLY: 17, - }, + AVATAR_QUICK_REPLY: 17 + } ], statusReplyResult: [ 2, { OK: 1, CANCELLED: 2, - ERROR_UNKNOWN: 3, - }, + ERROR_UNKNOWN: 3 + } ], - statusSessionId: [1, 'integer'], + statusSessionId: [1, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'StatusRowView', @@ -14256,8 +14256,8 @@ export const WEB_EVENTS: Event[] = [ FOWARDS_SWIPE: 3, BACKWARDS_TAP: 4, FOWARDS_TAP: 5, - PREVIOUS_ROW_TIMEOUT: 6, - }, + PREVIOUS_ROW_TIMEOUT: 6 + } ], statusRowIndex: [4, 'integer'], statusRowSection: [ @@ -14277,17 +14277,17 @@ export const WEB_EVENTS: Event[] = [ SEE_ALL_RECENT: 12, SEE_ALL_VIEWED: 13, SEE_ALL_MUTED: 14, - SEE_ALL_SEARCH: 15, - }, + SEE_ALL_SEARCH: 15 + } ], statusRowUnreadItemCount: [7, 'integer'], statusRowViewCount: [6, 'integer'], statusSessionId: [1, 'integer'], - statusViewerSessionId: [2, 'integer'], + statusViewerSessionId: [2, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'UiRevokeAction', @@ -14301,15 +14301,15 @@ export const WEB_EVENTS: Event[] = [ ADMIN_DELETE_FOR_EVERYONE: 2, SENDER_DELETE_FOR_EVERYONE: 3, ADMIN_AND_SENDER_DELETE_FOR_EVERYONE: 4, - DELETE_FOR_EVERYONE_SELECTED: 5, - }, + DELETE_FOR_EVERYONE_SELECTED: 5 + } ], uiRevokeActionDuration: [2, 'integer'], - uiRevokeActionSessionId: [3, 'string'], + uiRevokeActionSessionId: [3, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WaShopsManagement', @@ -14332,14 +14332,14 @@ export const WEB_EVENTS: Event[] = [ ACTION_SHARE_SHOPS: 11, ACTION_CLICK_VIEW_SHOPS_FROM_EDIT_BIZ_PROFILE: 12, ACTION_CLICK_COMMERCE_MANAGER_FROM_EDIT_BIZ_PROFILE: 13, - ACTION_CLICK_CANCEL_FROM_EDIT_BIZ_PROFILE: 14, - }, + ACTION_CLICK_CANCEL_FROM_EDIT_BIZ_PROFILE: 14 + } ], - shopsSellerJid: [3, 'string'], + shopsSellerJid: [3, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcButterbarEvent', @@ -14351,8 +14351,8 @@ export const WEB_EVENTS: Event[] = [ IMPRESSION: 1, CLICK_CTA: 2, CLICK_DISMISS: 3, - AUTO_DISMISS: 4, - }, + AUTO_DISMISS: 4 + } ], webcButterbarType: [ 2, @@ -14363,23 +14363,23 @@ export const WEB_EVENTS: Event[] = [ UPDATE_DUE_TO_SOFT_MIN: 4, UWP_UPSELL: 5, NOTIFICATION: 6, - OFFLINE_NOTIFICATION: 7, - }, - ], + OFFLINE_NOTIFICATION: 7 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcEmojiOpen', id: 1166, props: { - webcEmojiOpenTab: [1, 'string'], + webcEmojiOpenTab: [1, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcLinkPreviewDisplay', @@ -14396,13 +14396,13 @@ export const WEB_EVENTS: Event[] = [ PREVIEW_MALFORMED: 3, PREVIEW_NOT_FOUND: 4, PREVIEW_GENERAL_ERROR: 5, - PREVIEW_DECRYPTION_ERROR: 6, - }, - ], + PREVIEW_DECRYPTION_ERROR: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WhatsappQuickPromotionClientEligibilityWaterfall', @@ -14412,11 +14412,11 @@ export const WEB_EVENTS: Event[] = [ instanceLogData: [5, 'string'], promotionId: [2, 'string'], qpFailureReason: [3, 'string'], - step: [4, 'string'], + step: [4, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'GatedChatOpened', @@ -14426,14 +14426,14 @@ export const WEB_EVENTS: Event[] = [ 1, { TOS3: 1, - COUNTRY: 2, - }, + COUNTRY: 2 + } ], - selfInitiated: [2, 'boolean'], + selfInitiated: [2, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'HfmTextSearchComplete', @@ -14441,7 +14441,7 @@ export const WEB_EVENTS: Event[] = [ props: {}, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'SmbPaidMessagesButtonLogger', @@ -14452,8 +14452,8 @@ export const WEB_EVENTS: Event[] = [ pmButtonEventType: [ 3, { - CLICK: 0, - }, + CLICK: 0 + } ], pmButtonIndex: [4, 'integer'], pmButtonType: [ @@ -14463,15 +14463,15 @@ export const WEB_EVENTS: Event[] = [ CTA_URL: 1, CTA_CALL: 2, CTA_CATALOG: 3, - CTA_CATALOG_ITEM: 4, - }, + CTA_CATALOG_ITEM: 4 + } ], pmIsTrackableLink: [7, 'string'], - pmServerCampaignId: [6, 'string'], + pmServerCampaignId: [6, 'string'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 113760892, + privateStatsIdInt: 113760892 }, { name: 'SystemMessageClick', @@ -14483,8 +14483,8 @@ export const WEB_EVENTS: Event[] = [ 3, { PRIVACY: 1, - GROUPS: 2, - }, + GROUPS: 2 + } ], systemMessageType: [ 4, @@ -14503,13 +14503,13 @@ export const WEB_EVENTS: Event[] = [ GROUP_INVITE_LINK_UNAVAILABLE: 15, GROUP_INVITE_LINK_AVAILABLE: 16, GROUP_JOIN_REQUEST: 17, - GROUP_SUGGEST: 18, - }, - ], + GROUP_SUGGEST: 18 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'ViewOnceScreenshotActions', @@ -14522,8 +14522,8 @@ export const WEB_EVENTS: Event[] = [ { PHOTO: 1, VIDEO: 2, - PTT: 3, - }, + PTT: 3 + } ], voSsAction: [ 4, @@ -14542,13 +14542,13 @@ export const WEB_EVENTS: Event[] = [ SCREENSHOT_TAKEN: 12, SCREEN_RECORDING_BLOCKED: 13, SCREEN_RECORDING_STARTED: 14, - PLACEHOLDER_MESSAGE_LEARN_MORE_TAP: 15, - }, - ], + PLACEHOLDER_MESSAGE_LEARN_MORE_TAP: 15 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CtwaConsumerDisclosure', @@ -14564,21 +14564,21 @@ export const WEB_EVENTS: Event[] = [ BACK_BUTTON_TOOLBAR: 3, BACK_BUTTON_SYSTEM: 4, DISMISS: 5, - DISCLOSURE_INFO_VIEW: 6, - }, + DISCLOSURE_INFO_VIEW: 6 + } ], disclosureType: [ 2, { NON_BLOCKING: 0, BLOCKING: 1, - INFO: 2, - }, - ], + INFO: 2 + } + ] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'Ctwa3pdConversion', @@ -14589,11 +14589,11 @@ export const WEB_EVENTS: Event[] = [ ctwa3pdConversionType: [3, 'string'], ctwa3pdSchemaVersion: [4, 'integer'], ctwa3pdSurfaceType: [5, 'string'], - ctwaTrackingPayload: [6, 'string'], + ctwaTrackingPayload: [6, 'string'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CtwaLabelSignal', @@ -14604,8 +14604,8 @@ export const WEB_EVENTS: Event[] = [ 2, { CHAT: 0, - MESSAGE: 1, - }, + MESSAGE: 1 + } ], ctwaLabelType: [ 3, @@ -14613,17 +14613,17 @@ export const WEB_EVENTS: Event[] = [ NEW_ORDER: 0, PENDING_PAYMENT: 1, PAID: 2, - ORDER_COMPLETE: 3, - }, + ORDER_COMPLETE: 3 + } ], deepLinkConversionData: [4, 'string'], deepLinkConversionSource: [5, 'string'], eventSharingSettingEnabled: [6, 'boolean'], - globalSharingSettingEnabled: [7, 'boolean'], + globalSharingSettingEnabled: [7, 'boolean'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'CtwaOrderSignal', @@ -14639,8 +14639,8 @@ export const WEB_EVENTS: Event[] = [ 6, { CREATED: 0, - UPDATED: 1, - }, + UPDATED: 1 + } ], orderStatus: [ 7, @@ -14651,13 +14651,13 @@ export const WEB_EVENTS: Event[] = [ CANCELLED: 3, PENDING: 4, PARTIALLY_SHIPPED: 5, - PAID_CHANGE: 6, - }, - ], + PAID_CHANGE: 6 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'PsChannelPostForward', @@ -14667,8 +14667,8 @@ export const WEB_EVENTS: Event[] = [ 6, { UPDATE: 0, - UPDATE_CARD: 1, - }, + UPDATE_CARD: 1 + } ], channelForwardGroupType: [ 1, @@ -14678,8 +14678,8 @@ export const WEB_EVENTS: Event[] = [ GROUP: 2, STATUS: 3, BROADCAST: 4, - CHANNEL: 5, - }, + CHANNEL: 5 + } ], cid: [2, 'string'], mediaType: [ @@ -14746,14 +14746,14 @@ export const WEB_EVENTS: Event[] = [ EVENT_RESPOND: 59, LOTTIE_STICKER: 60, INTERACTIVE_PRODUCT_CAROUSEL: 61, - INTERACTIVE_PRODUCT: 62, - }, + INTERACTIVE_PRODUCT: 62 + } ], - postId: [4, 'string'], + postId: [4, 'string'] }, weight: 1, wamChannel: 'private', - privateStatsIdInt: 0, + privateStatsIdInt: 0 }, { name: 'WebContactListStartNewChat', @@ -14765,13 +14765,13 @@ export const WEB_EVENTS: Event[] = [ { CONTACT: 1, GROUP: 2, - CONTACTLESS: 3, - }, - ], + CONTACTLESS: 3 + } + ] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, + privateStatsIdInt: -1 }, { name: 'WebcMediaEditorSend', @@ -14783,566 +14783,459 @@ export const WEB_EVENTS: Event[] = [ imageCount: [1, 'integer'], paintedImageCount: [3, 'integer'], stickerLayerCount: [6, 'integer'], - textLayerCount: [4, 'integer'], + textLayerCount: [4, 'integer'] }, weight: 1, wamChannel: 'regular', - privateStatsIdInt: -1, - }, + privateStatsIdInt: -1 + } ] export const WEB_GLOBALS: Global[] = [ { - 'name': 'abKey2', - 'id': 4473, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'abKey2', + id: 4473, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'appBuild', - 'id': 1657, - 'type': { - 'DEBUG': 1, - 'ALPHA': 2, - 'BETA': 3, - 'RELEASE': 4 + name: 'appBuild', + id: 1657, + type: { + DEBUG: 1, + ALPHA: 2, + BETA: 3, + RELEASE: 4 }, - 'channels': [ - 'regular', - 'private' - ] + channels: ['regular', 'private'] }, { - 'name': 'appIsBetaRelease', - 'id': 21, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular', - 'private' - ] + name: 'appIsBetaRelease', + id: 21, + type: 'boolean', + validator: 'boolean', + channels: ['regular', 'private'] }, { - 'name': 'appVersion', - 'id': 17, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular', - 'private' - ] + name: 'appVersion', + id: 17, + type: 'string', + validator: 'string', + channels: ['regular', 'private'] }, { - 'name': 'browser', - 'id': 779, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'browser', + id: 779, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'browserVersion', - 'id': 295, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'browserVersion', + id: 295, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'datacenter', - 'id': 2795, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'datacenter', + id: 2795, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'deviceClassification', - 'id': 14507, - 'type': { - 'MOBILE': 0, - 'TABLET': 1, - 'WEARABLES': 2, - 'VR': 3, - 'DESKTOP': 4, - 'FOLDABLE': 5, - 'AR_GLASS': 6, - 'UNDEFINED': 100 + name: 'deviceClassification', + id: 14507, + type: { + MOBILE: 0, + TABLET: 1, + WEARABLES: 2, + VR: 3, + DESKTOP: 4, + FOLDABLE: 5, + AR_GLASS: 6, + UNDEFINED: 100 }, - 'channels': [ - 'regular' - ] + channels: ['regular'] }, { - 'name': 'deviceName', - 'id': 13, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular', - 'private' - ] + name: 'deviceName', + id: 13, + type: 'string', + validator: 'string', + channels: ['regular', 'private'] }, { - 'name': 'deviceVersion', - 'id': 4505, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'deviceVersion', + id: 4505, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'expoKey', - 'id': 5029, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular', - 'private' - ] + name: 'expoKey', + id: 5029, + type: 'string', + validator: 'string', + channels: ['regular', 'private'] }, { - 'name': 'mcc', - 'id': 5, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'mcc', + id: 5, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'memClass', - 'id': 655, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'memClass', + id: 655, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'mnc', - 'id': 3, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'mnc', + id: 3, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'networkIsWifi', - 'id': 23, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular' - ] + name: 'networkIsWifi', + id: 23, + type: 'boolean', + validator: 'boolean', + channels: ['regular'] }, { - 'name': 'ocVersion', - 'id': 6251, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'ocVersion', + id: 6251, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'osVersion', - 'id': 15, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular', - 'private' - ] + name: 'osVersion', + id: 15, + type: 'string', + validator: 'string', + channels: ['regular', 'private'] }, { - 'name': 'platform', - 'id': 11, - 'type': { - 'IPHONE': 1, - 'ANDROID': 2, - 'BB': 3, - 'BBX': 7, - 'S40': 4, - 'SYMBIAN': 5, - 'WP': 6, - 'WEBCLIENT': 8, - 'OSMETA': 11, - 'ENT': 12, - 'SMBA': 13, - 'KAIOS': 14, - 'SMBI': 15, - 'WINDOWS': 16, - 'WEB': 17, - 'PORTAL': 18, - 'BLOKS': 19, - 'BLUEA': 20, - 'BLUEI': 21, - 'FBLITEA': 22, - 'GREENA': 23, - 'GREENI': 24, - 'IGDA': 25, - 'IGDI': 26, - 'IGLITEA': 27, - 'MLITEA': 28, - 'MSGRA': 29, - 'MSGRI': 30, - 'MSGRP': 31, - 'MSGRW': 32, - 'IGDW': 33, - 'PAGE': 34, - 'MSGRDM': 35, - 'MSGRDW': 36, - 'MSGROM': 37, - 'MSGROC': 38, - 'MSGRM': 43, - 'IGDM': 44, - 'WEARM': 45, - 'CAPI': 46, - 'XR': 47, - 'MACOS': 48, - 'WAMETA_REPL': 49, - 'ARDEV': 50, - 'WEAROS': 51, - 'MSGRVR': 52, - 'BLUEW': 53, - 'IPHONEWAMETATEST': 54, - 'MSGRAR': 57, - 'IPAD': 58, - 'WAVOIP_CLI': 59, - 'MSGRT': 60, - 'IGDT': 61, - 'ANDROIDWAMETATEST': 62, - 'MSGRSG': 63, - 'IGDSG': 64, - 'INTEROP': 65, - 'INTEROP_MSGR': 66, - 'TEST': 9, - 'UNKNOWN': 10 + name: 'platform', + id: 11, + type: { + IPHONE: 1, + ANDROID: 2, + BB: 3, + BBX: 7, + S40: 4, + SYMBIAN: 5, + WP: 6, + WEBCLIENT: 8, + OSMETA: 11, + ENT: 12, + SMBA: 13, + KAIOS: 14, + SMBI: 15, + WINDOWS: 16, + WEB: 17, + PORTAL: 18, + BLOKS: 19, + BLUEA: 20, + BLUEI: 21, + FBLITEA: 22, + GREENA: 23, + GREENI: 24, + IGDA: 25, + IGDI: 26, + IGLITEA: 27, + MLITEA: 28, + MSGRA: 29, + MSGRI: 30, + MSGRP: 31, + MSGRW: 32, + IGDW: 33, + PAGE: 34, + MSGRDM: 35, + MSGRDW: 36, + MSGROM: 37, + MSGROC: 38, + MSGRM: 43, + IGDM: 44, + WEARM: 45, + CAPI: 46, + XR: 47, + MACOS: 48, + WAMETA_REPL: 49, + ARDEV: 50, + WEAROS: 51, + MSGRVR: 52, + BLUEW: 53, + IPHONEWAMETATEST: 54, + MSGRAR: 57, + IPAD: 58, + WAVOIP_CLI: 59, + MSGRT: 60, + IGDT: 61, + ANDROIDWAMETATEST: 62, + MSGRSG: 63, + IGDSG: 64, + INTEROP: 65, + INTEROP_MSGR: 66, + TEST: 9, + UNKNOWN: 10 }, - 'channels': [ - 'regular', - 'private' - ] + channels: ['regular', 'private'] }, { - 'name': 'psCountryCode', - 'id': 6833, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'private' - ] + name: 'psCountryCode', + id: 6833, + type: 'string', + validator: 'string', + channels: ['private'] }, { - 'name': 'psId', - 'id': 6005, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'private' - ] + name: 'psId', + id: 6005, + type: 'string', + validator: 'string', + channels: ['private'] }, { - 'name': 'serviceImprovementOptOut', - 'id': 13293, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular', - 'private' - ] + name: 'serviceImprovementOptOut', + id: 13293, + type: 'boolean', + validator: 'boolean', + channels: ['regular', 'private'] }, { - 'name': 'streamId', - 'id': 3543, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'streamId', + id: 3543, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'wametaLoggerTestFilter', - 'id': 15881, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular', - 'private' - ] + name: 'wametaLoggerTestFilter', + id: 15881, + type: 'string', + validator: 'string', + channels: ['regular', 'private'] }, { - 'name': 'webcBucket', - 'id': 875, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcBucket', + id: 875, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcEnv', - 'id': 633, - 'type': { - 'PROD': 0, - 'INTERN': 1, - 'DEV': 2, - 'E2E': 3 + name: 'webcEnv', + id: 633, + type: { + PROD: 0, + INTERN: 1, + DEV: 2, + E2E: 3 }, - 'channels': [ - 'regular' - ] + channels: ['regular'] }, { - 'name': 'webcNativeAutolaunch', - 'id': 1009, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular' - ] + name: 'webcNativeAutolaunch', + id: 1009, + type: 'boolean', + validator: 'boolean', + channels: ['regular'] }, { - 'name': 'webcNativeBetaUpdates', - 'id': 1007, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular' - ] + name: 'webcNativeBetaUpdates', + id: 1007, + type: 'boolean', + validator: 'boolean', + channels: ['regular'] }, { - 'name': 'webcPhoneAppVersion', - 'id': 1005, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcPhoneAppVersion', + id: 1005, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcPhoneCharging', - 'id': 783, - 'type': 'boolean', - 'validator': 'boolean', - 'channels': [ - 'regular' - ] + name: 'webcPhoneCharging', + id: 783, + type: 'boolean', + validator: 'boolean', + channels: ['regular'] }, { - 'name': 'webcPhoneDeviceManufacturer', - 'id': 829, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcPhoneDeviceManufacturer', + id: 829, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcPhoneDeviceModel', - 'id': 831, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcPhoneDeviceModel', + id: 831, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcPhoneOsBuildNumber', - 'id': 833, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcPhoneOsBuildNumber', + id: 833, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcPhoneOsVersion', - 'id': 835, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcPhoneOsVersion', + id: 835, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcPhonePlatform', - 'id': 707, - 'type': { - 'IPHONE': 1, - 'ANDROID': 2, - 'BB': 3, - 'BBX': 7, - 'S40': 4, - 'SYMBIAN': 5, - 'WP': 6, - 'WEBCLIENT': 8, - 'OSMETA': 11, - 'ENT': 12, - 'SMBA': 13, - 'KAIOS': 14, - 'SMBI': 15, - 'WINDOWS': 16, - 'WEB': 17, - 'PORTAL': 18, - 'BLOKS': 19, - 'BLUEA': 20, - 'BLUEI': 21, - 'FBLITEA': 22, - 'GREENA': 23, - 'GREENI': 24, - 'IGDA': 25, - 'IGDI': 26, - 'IGLITEA': 27, - 'MLITEA': 28, - 'MSGRA': 29, - 'MSGRI': 30, - 'MSGRP': 31, - 'MSGRW': 32, - 'IGDW': 33, - 'PAGE': 34, - 'MSGRDM': 35, - 'MSGRDW': 36, - 'MSGROM': 37, - 'MSGROC': 38, - 'MSGRM': 43, - 'IGDM': 44, - 'WEARM': 45, - 'CAPI': 46, - 'XR': 47, - 'MACOS': 48, - 'WAMETA_REPL': 49, - 'ARDEV': 50, - 'WEAROS': 51, - 'MSGRVR': 52, - 'BLUEW': 53, - 'IPHONEWAMETATEST': 54, - 'MSGRAR': 57, - 'IPAD': 58, - 'WAVOIP_CLI': 59, - 'MSGRT': 60, - 'IGDT': 61, - 'ANDROIDWAMETATEST': 62, - 'MSGRSG': 63, - 'IGDSG': 64, - 'INTEROP': 65, - 'INTEROP_MSGR': 66, - 'TEST': 9, - 'UNKNOWN': 10 + name: 'webcPhonePlatform', + id: 707, + type: { + IPHONE: 1, + ANDROID: 2, + BB: 3, + BBX: 7, + S40: 4, + SYMBIAN: 5, + WP: 6, + WEBCLIENT: 8, + OSMETA: 11, + ENT: 12, + SMBA: 13, + KAIOS: 14, + SMBI: 15, + WINDOWS: 16, + WEB: 17, + PORTAL: 18, + BLOKS: 19, + BLUEA: 20, + BLUEI: 21, + FBLITEA: 22, + GREENA: 23, + GREENI: 24, + IGDA: 25, + IGDI: 26, + IGLITEA: 27, + MLITEA: 28, + MSGRA: 29, + MSGRI: 30, + MSGRP: 31, + MSGRW: 32, + IGDW: 33, + PAGE: 34, + MSGRDM: 35, + MSGRDW: 36, + MSGROM: 37, + MSGROC: 38, + MSGRM: 43, + IGDM: 44, + WEARM: 45, + CAPI: 46, + XR: 47, + MACOS: 48, + WAMETA_REPL: 49, + ARDEV: 50, + WEAROS: 51, + MSGRVR: 52, + BLUEW: 53, + IPHONEWAMETATEST: 54, + MSGRAR: 57, + IPAD: 58, + WAVOIP_CLI: 59, + MSGRT: 60, + IGDT: 61, + ANDROIDWAMETATEST: 62, + MSGRSG: 63, + IGDSG: 64, + INTEROP: 65, + INTEROP_MSGR: 66, + TEST: 9, + UNKNOWN: 10 }, - 'channels': [ - 'regular' - ] + channels: ['regular'] }, { - 'name': 'webcTabId', - 'id': 3727, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcTabId', + id: 3727, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcWebArch', - 'id': 6605, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcWebArch', + id: 6605, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcWebDeviceManufacturer', - 'id': 6599, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcWebDeviceManufacturer', + id: 6599, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcWebDeviceModel', - 'id': 6601, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcWebDeviceModel', + id: 6601, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcWebOsReleaseNumber', - 'id': 6603, - 'type': 'string', - 'validator': 'string', - 'channels': [ - 'regular' - ] + name: 'webcWebOsReleaseNumber', + id: 6603, + type: 'string', + validator: 'string', + channels: ['regular'] }, { - 'name': 'webcWebPlatform', - 'id': 899, - 'type': { - 'WEB': 1, - 'WIN32': 2, - 'DARWIN': 3, - 'IOS_TABLET': 4, - 'ANDROID_TABLET': 5, - 'WINSTORE': 6, - 'MACSTORE': 7, - 'DARWIN_BETA': 8, - 'WIN32_BETA': 9, - 'PWA': 10 + name: 'webcWebPlatform', + id: 899, + type: { + WEB: 1, + WIN32: 2, + DARWIN: 3, + IOS_TABLET: 4, + ANDROID_TABLET: 5, + WINSTORE: 6, + MACSTORE: 7, + DARWIN_BETA: 8, + WIN32_BETA: 9, + PWA: 10 }, - 'channels': [ - 'regular', - 'private' - ] + channels: ['regular', 'private'] }, { - 'name': 'yearClass', - 'id': 689, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'yearClass', + id: 689, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'yearClass2016', - 'id': 2617, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'yearClass2016', + id: 2617, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'commitTime', - 'id': 47, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'commitTime', + id: 47, + type: 'integer', + channels: ['regular', 'private'] }, { - 'name': 'sequenceNumber', - 'id': 3433, - 'type': 'integer', - 'channels': [ - 'regular', - 'private' - ] + name: 'sequenceNumber', + id: 3433, + type: 'integer', + channels: ['regular', 'private'] } ] @@ -15353,18 +15246,18 @@ export const FLAG_BYTE = 8, FLAG_EXTENDED = 4 export type Event = { - name: string - id: number - props: {[key: string]: [number, string | {[key: string]: number}]} - weight: number - wamChannel: string - privateStatsIdInt: number + name: string + id: number + props: { [key: string]: [number, string | { [key: string]: number }] } + weight: number + wamChannel: string + privateStatsIdInt: number } export type Global = { name: string id: number - type: string | {[key: string]: number} + type: string | { [key: string]: number } validator?: string channels: string[] } diff --git a/src/WAM/encode.ts b/src/WAM/encode.ts index 53208b4..7babd7f 100644 --- a/src/WAM/encode.ts +++ b/src/WAM/encode.ts @@ -1,5 +1,14 @@ import { BinaryInfo } from './BinaryInfo' -import { FLAG_BYTE, FLAG_EVENT, FLAG_EXTENDED, FLAG_FIELD, FLAG_GLOBAL, Value, WEB_EVENTS, WEB_GLOBALS } from './constants' +import { + FLAG_BYTE, + FLAG_EVENT, + FLAG_EXTENDED, + FLAG_FIELD, + FLAG_GLOBAL, + Value, + WEB_EVENTS, + WEB_GLOBALS +} from './constants' const getHeaderBitLength = (key: number) => (key < 256 ? 2 : 3) @@ -10,12 +19,10 @@ export const encodeWAM = (binaryInfo: BinaryInfo) => { encodeEvents(binaryInfo) console.log(binaryInfo.buffer) - const totalSize = binaryInfo.buffer - .map((a) => a.length) - .reduce((a, b) => a + b) + const totalSize = binaryInfo.buffer.map(a => a.length).reduce((a, b) => a + b) const buffer = Buffer.alloc(totalSize) let offset = 0 - for(const buffer_ of binaryInfo.buffer) { + for (const buffer_ of binaryInfo.buffer) { buffer_.copy(buffer, offset) offset += buffer_.length } @@ -34,11 +41,11 @@ function encodeWAMHeader(binaryInfo: BinaryInfo) { binaryInfo.buffer.push(headerBuffer) } -function encodeGlobalAttributes(binaryInfo: BinaryInfo, globals: {[key: string]: Value}) { - for(const [key, _value] of Object.entries(globals)) { +function encodeGlobalAttributes(binaryInfo: BinaryInfo, globals: { [key: string]: Value }) { + for (const [key, _value] of Object.entries(globals)) { const id = WEB_GLOBALS.find(a => a?.name === key)!.id let value = _value - if(typeof value === 'boolean') { + if (typeof value === 'boolean') { value = value ? 1 : 0 } @@ -47,30 +54,27 @@ function encodeGlobalAttributes(binaryInfo: BinaryInfo, globals: {[key: string]: } function encodeEvents(binaryInfo: BinaryInfo) { - for(const [ - name, - { props, globals }, - ] of binaryInfo.events.map((a) => Object.entries(a)[0])) { + for (const [name, { props, globals }] of binaryInfo.events.map(a => Object.entries(a)[0])) { encodeGlobalAttributes(binaryInfo, globals) - const event = WEB_EVENTS.find((a) => a.name === name)! + const event = WEB_EVENTS.find(a => a.name === name)! const props_ = Object.entries(props) let extended = false - for(const [, value] of props_) { + for (const [, value] of props_) { extended ||= value !== null } const eventFlag = extended ? FLAG_EVENT : FLAG_EVENT | FLAG_EXTENDED binaryInfo.buffer.push(serializeData(event.id, -event.weight, eventFlag)) - for(let i = 0; i < props_.length; i++) { + for (let i = 0; i < props_.length; i++) { const [key, _value] = props_[i] - const id = (event.props)[key][0] - extended = i < (props_.length - 1) + const id = event.props[key][0] + extended = i < props_.length - 1 let value = _value - if(typeof value === 'boolean') { + if (typeof value === 'boolean') { value = value ? 1 : 0 } @@ -80,34 +84,33 @@ function encodeEvents(binaryInfo: BinaryInfo) { } } - function serializeData(key: number, value: Value, flag: number): Buffer { const bufferLength = getHeaderBitLength(key) let buffer: Buffer let offset = 0 - if(value === null) { - if(flag === FLAG_GLOBAL) { + if (value === null) { + if (flag === FLAG_GLOBAL) { buffer = Buffer.alloc(bufferLength) offset = serializeHeader(buffer, offset, key, flag) return buffer } - } else if(typeof value === 'number' && Number.isInteger(value)) { + } else if (typeof value === 'number' && Number.isInteger(value)) { // is number - if(value === 0 || value === 1) { + if (value === 0 || value === 1) { buffer = Buffer.alloc(bufferLength) offset = serializeHeader(buffer, offset, key, flag | ((value + 1) << 4)) return buffer - } else if(-128 <= value && value < 128) { + } else if (-128 <= value && value < 128) { buffer = Buffer.alloc(bufferLength + 1) offset = serializeHeader(buffer, offset, key, flag | (3 << 4)) buffer.writeInt8(value, offset) return buffer - } else if(-32768 <= value && value < 32768) { + } else if (-32768 <= value && value < 32768) { buffer = Buffer.alloc(bufferLength + 2) offset = serializeHeader(buffer, offset, key, flag | (4 << 4)) buffer.writeInt16LE(value, offset) return buffer - } else if(-2147483648 <= value && value < 2147483648) { + } else if (-2147483648 <= value && value < 2147483648) { buffer = Buffer.alloc(bufferLength + 4) offset = serializeHeader(buffer, offset, key, flag | (5 << 4)) buffer.writeInt32LE(value, offset) @@ -118,20 +121,20 @@ function serializeData(key: number, value: Value, flag: number): Buffer { buffer.writeDoubleLE(value, offset) return buffer } - } else if(typeof value === 'number') { + } else if (typeof value === 'number') { // is float buffer = Buffer.alloc(bufferLength + 8) offset = serializeHeader(buffer, offset, key, flag | (7 << 4)) buffer.writeDoubleLE(value, offset) return buffer - } else if(typeof value === 'string') { + } else if (typeof value === 'string') { // is string const utf8Bytes = Buffer.byteLength(value, 'utf8') - if(utf8Bytes < 256) { + if (utf8Bytes < 256) { buffer = Buffer.alloc(bufferLength + 1 + utf8Bytes) offset = serializeHeader(buffer, offset, key, flag | (8 << 4)) buffer.writeUint8(utf8Bytes, offset++) - } else if(utf8Bytes < 65536) { + } else if (utf8Bytes < 65536) { buffer = Buffer.alloc(bufferLength + 2 + utf8Bytes) offset = serializeHeader(buffer, offset, key, flag | (9 << 4)) buffer.writeUInt16LE(utf8Bytes, offset) @@ -150,13 +153,8 @@ function serializeData(key: number, value: Value, flag: number): Buffer { throw 'missing' } -function serializeHeader( - buffer: Buffer, - offset: number, - key: number, - flag: number -) { - if(key < 256) { +function serializeHeader(buffer: Buffer, offset: number, key: number, flag: number) { + if (key < 256) { buffer.writeUInt8(flag, offset) offset += 1 buffer.writeUInt8(key, offset) @@ -169,4 +167,4 @@ function serializeHeader( } return offset -} \ No newline at end of file +} diff --git a/src/WAM/index.ts b/src/WAM/index.ts index 5ad5c3a..ae8a4d4 100644 --- a/src/WAM/index.ts +++ b/src/WAM/index.ts @@ -1,3 +1,3 @@ export * from './constants' export * from './encode' -export * from './BinaryInfo' \ No newline at end of file +export * from './BinaryInfo' diff --git a/src/WAUSync/Protocols/USyncContactProtocol.ts b/src/WAUSync/Protocols/USyncContactProtocol.ts index f6448c3..70f59ba 100644 --- a/src/WAUSync/Protocols/USyncContactProtocol.ts +++ b/src/WAUSync/Protocols/USyncContactProtocol.ts @@ -8,7 +8,7 @@ export class USyncContactProtocol implements USyncQueryProtocol { getQueryElement(): BinaryNode { return { tag: 'contact', - attrs: {}, + attrs: {} } } @@ -17,16 +17,16 @@ export class USyncContactProtocol implements USyncQueryProtocol { return { tag: 'contact', attrs: {}, - content: user.phone, + content: user.phone } } parser(node: BinaryNode): boolean { - if(node.tag === 'contact') { + if (node.tag === 'contact') { assertNodeErrorFree(node) return node?.attrs?.type === 'in' } return false } -} \ No newline at end of file +} diff --git a/src/WAUSync/Protocols/USyncDeviceProtocol.ts b/src/WAUSync/Protocols/USyncDeviceProtocol.ts index d5bef2f..34b4a0d 100644 --- a/src/WAUSync/Protocols/USyncDeviceProtocol.ts +++ b/src/WAUSync/Protocols/USyncDeviceProtocol.ts @@ -15,7 +15,7 @@ export type DeviceListData = { } export type ParsedDeviceInfo = { - deviceList?: DeviceListData[] + deviceList?: DeviceListData[] keyIndex?: KeyIndexData } @@ -26,8 +26,8 @@ export class USyncDeviceProtocol implements USyncQueryProtocol { return { tag: 'devices', attrs: { - version: '2', - }, + version: '2' + } } } @@ -42,16 +42,16 @@ export class USyncDeviceProtocol implements USyncQueryProtocol { const deviceList: DeviceListData[] = [] let keyIndex: KeyIndexData | undefined = undefined - if(node.tag === 'devices') { + if (node.tag === 'devices') { assertNodeErrorFree(node) const deviceListNode = getBinaryNodeChild(node, 'device-list') const keyIndexNode = getBinaryNodeChild(node, 'key-index-list') - if(Array.isArray(deviceListNode?.content)) { - for(const { tag, attrs } of deviceListNode.content) { + if (Array.isArray(deviceListNode?.content)) { + for (const { tag, attrs } of deviceListNode.content) { const id = +attrs.id const keyIndex = +attrs['key-index'] - if(tag === 'device') { + if (tag === 'device') { deviceList.push({ id, keyIndex, @@ -61,7 +61,7 @@ export class USyncDeviceProtocol implements USyncQueryProtocol { } } - if(keyIndexNode?.tag === 'key-index-list') { + if (keyIndexNode?.tag === 'key-index-list') { keyIndex = { timestamp: +keyIndexNode.attrs['ts'], signedKeyIndex: keyIndexNode?.content as Uint8Array, @@ -75,4 +75,4 @@ export class USyncDeviceProtocol implements USyncQueryProtocol { keyIndex } } -} \ No newline at end of file +} diff --git a/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts b/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts index 512b3ba..6568f17 100644 --- a/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts +++ b/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts @@ -12,7 +12,7 @@ export class USyncDisappearingModeProtocol implements USyncQueryProtocol { getQueryElement(): BinaryNode { return { tag: 'disappearing_mode', - attrs: {}, + attrs: {} } } @@ -21,15 +21,15 @@ export class USyncDisappearingModeProtocol implements USyncQueryProtocol { } parser(node: BinaryNode): DisappearingModeData | undefined { - if(node.tag === 'status') { + if (node.tag === 'status') { assertNodeErrorFree(node) const duration: number = +node?.attrs.duration const setAt = new Date(+(node?.attrs.t || 0) * 1000) return { duration, - setAt, + setAt } } } -} \ No newline at end of file +} diff --git a/src/WAUSync/Protocols/USyncStatusProtocol.ts b/src/WAUSync/Protocols/USyncStatusProtocol.ts index 02cd2b1..83ff2c0 100644 --- a/src/WAUSync/Protocols/USyncStatusProtocol.ts +++ b/src/WAUSync/Protocols/USyncStatusProtocol.ts @@ -12,7 +12,7 @@ export class USyncStatusProtocol implements USyncQueryProtocol { getQueryElement(): BinaryNode { return { tag: 'status', - attrs: {}, + attrs: {} } } @@ -21,24 +21,24 @@ export class USyncStatusProtocol implements USyncQueryProtocol { } parser(node: BinaryNode): StatusData | undefined { - if(node.tag === 'status') { + if (node.tag === 'status') { assertNodeErrorFree(node) let status: string | null = node?.content!.toString() const setAt = new Date(+(node?.attrs.t || 0) * 1000) - if(!status) { - if(+node.attrs?.code === 401) { + if (!status) { + if (+node.attrs?.code === 401) { status = '' } else { status = null } - } else if(typeof status === 'string' && status.length === 0) { + } else if (typeof status === 'string' && status.length === 0) { status = null } return { status, - setAt, + setAt } } } -} \ No newline at end of file +} diff --git a/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts b/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts index cc8cd87..7e84436 100644 --- a/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts +++ b/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts @@ -3,21 +3,21 @@ import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChi import { USyncUser } from '../USyncUser' export type BotProfileCommand = { - name: string - description: string + name: string + description: string } export type BotProfileInfo = { - jid: string - name: string - attributes: string - description: string - category: string - isDefault: boolean - prompts: string[] - personaId: string - commands: BotProfileCommand[] - commandsDescription: string + jid: string + name: string + attributes: string + description: string + category: string + isDefault: boolean + prompts: string[] + personaId: string + commands: BotProfileCommand[] + commandsDescription: string } export class USyncBotProfileProtocol implements USyncQueryProtocol { @@ -26,7 +26,7 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol { getQueryElement(): BinaryNode { return { tag: 'bot', - attrs: { }, + attrs: {}, content: [{ tag: 'profile', attrs: { v: '1' } }] } } @@ -34,14 +34,14 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol { getUserElement(user: USyncUser): BinaryNode { return { tag: 'bot', - attrs: { }, - content: [{ tag: 'profile', attrs: { 'persona_id': user.personaId } }] + attrs: {}, + content: [{ tag: 'profile', attrs: { persona_id: user.personaId } }] } } parser(node: BinaryNode): BotProfileInfo { - const botNode = getBinaryNodeChild(node, 'bot') - const profile = getBinaryNodeChild(botNode, 'profile') + const botNode = getBinaryNodeChild(node, 'bot') + const profile = getBinaryNodeChild(botNode, 'profile') const commandsNode = getBinaryNodeChild(profile, 'commands') const promptsNode = getBinaryNodeChild(profile, 'prompts') @@ -49,21 +49,20 @@ export class USyncBotProfileProtocol implements USyncQueryProtocol { const commands: BotProfileCommand[] = [] const prompts: string[] = [] - for(const command of getBinaryNodeChildren(commandsNode, 'command')) { - commands.push({ + for (const command of getBinaryNodeChildren(commandsNode, 'command')) { + commands.push({ name: getBinaryNodeChildString(command, 'name')!, description: getBinaryNodeChildString(command, 'description')! }) } - for(const prompt of getBinaryNodeChildren(promptsNode, 'prompt')) { - prompts.push(`${getBinaryNodeChildString(prompt, 'emoji')!} ${getBinaryNodeChildString(prompt, 'text')!}`) + for (const prompt of getBinaryNodeChildren(promptsNode, 'prompt')) { + prompts.push(`${getBinaryNodeChildString(prompt, 'emoji')!} ${getBinaryNodeChildString(prompt, 'text')!}`) } - return { - isDefault: !!getBinaryNodeChild(profile, 'default'), - jid: node.attrs.jid, + isDefault: !!getBinaryNodeChild(profile, 'default'), + jid: node.attrs.jid, name: getBinaryNodeChildString(profile, 'name')!, attributes: getBinaryNodeChildString(profile, 'attributes')!, description: getBinaryNodeChildString(profile, 'description')!, diff --git a/src/WAUSync/Protocols/UsyncLIDProtocol.ts b/src/WAUSync/Protocols/UsyncLIDProtocol.ts index 54508ea..4d6a80b 100644 --- a/src/WAUSync/Protocols/UsyncLIDProtocol.ts +++ b/src/WAUSync/Protocols/UsyncLIDProtocol.ts @@ -7,7 +7,7 @@ export class USyncLIDProtocol implements USyncQueryProtocol { getQueryElement(): BinaryNode { return { tag: 'lid', - attrs: {}, + attrs: {} } } @@ -16,7 +16,7 @@ export class USyncLIDProtocol implements USyncQueryProtocol { } parser(node: BinaryNode): string | null { - if(node.tag === 'lid') { + if (node.tag === 'lid') { return node.attrs.val } diff --git a/src/WAUSync/Protocols/index.ts b/src/WAUSync/Protocols/index.ts index 56f1daa..c6e6442 100644 --- a/src/WAUSync/Protocols/index.ts +++ b/src/WAUSync/Protocols/index.ts @@ -1,4 +1,4 @@ export * from './USyncDeviceProtocol' export * from './USyncContactProtocol' export * from './USyncStatusProtocol' -export * from './USyncDisappearingModeProtocol' \ No newline at end of file +export * from './USyncDisappearingModeProtocol' diff --git a/src/WAUSync/USyncQuery.ts b/src/WAUSync/USyncQuery.ts index c52cbba..1f3a56f 100644 --- a/src/WAUSync/USyncQuery.ts +++ b/src/WAUSync/USyncQuery.ts @@ -2,14 +2,19 @@ import { USyncQueryProtocol } from '../Types/USync' import { BinaryNode, getBinaryNodeChild } from '../WABinary' import { USyncBotProfileProtocol } from './Protocols/UsyncBotProfileProtocol' import { USyncLIDProtocol } from './Protocols/UsyncLIDProtocol' -import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol } from './Protocols' +import { + USyncContactProtocol, + USyncDeviceProtocol, + USyncDisappearingModeProtocol, + USyncStatusProtocol +} from './Protocols' import { USyncUser } from './USyncUser' -export type USyncQueryResultList = { [protocol: string]: unknown, id: string } +export type USyncQueryResultList = { [protocol: string]: unknown; id: string } export type USyncQueryResult = { - list: USyncQueryResultList[] - sideList: USyncQueryResultList[] + list: USyncQueryResultList[] + sideList: USyncQueryResultList[] } export class USyncQuery { @@ -41,18 +46,20 @@ export class USyncQuery { } parseUSyncQueryResult(result: BinaryNode): USyncQueryResult | undefined { - if(result.attrs.type !== 'result') { + if (result.attrs.type !== 'result') { return } - const protocolMap = Object.fromEntries(this.protocols.map((protocol) => { - return [protocol.name, protocol.parser] - })) + const protocolMap = Object.fromEntries( + this.protocols.map(protocol => { + return [protocol.name, protocol.parser] + }) + ) const queryResult: USyncQueryResult = { // TODO: implement errors etc. list: [], - sideList: [], + sideList: [] } const usyncNode = getBinaryNodeChild(result, 'usync') @@ -62,18 +69,24 @@ export class USyncQuery { //const resultNode = getBinaryNodeChild(usyncNode, 'result') const listNode = getBinaryNodeChild(usyncNode, 'list') - if(Array.isArray(listNode?.content) && typeof listNode !== 'undefined') { - queryResult.list = listNode.content.map((node) => { + if (Array.isArray(listNode?.content) && typeof listNode !== 'undefined') { + queryResult.list = listNode.content.map(node => { const id = node?.attrs.jid - const data = Array.isArray(node?.content) ? Object.fromEntries(node.content.map((content) => { - const protocol = content.tag - const parser = protocolMap[protocol] - if(parser) { - return [protocol, parser(content)] - } else { - return [protocol, null] - } - }).filter(([, b]) => b !== null) as [string, unknown][]) : {} + const data = Array.isArray(node?.content) + ? Object.fromEntries( + node.content + .map(content => { + const protocol = content.tag + const parser = protocolMap[protocol] + if (parser) { + return [protocol, parser(content)] + } else { + return [protocol, null] + } + }) + .filter(([, b]) => b !== null) as [string, unknown][] + ) + : {} return { ...data, id } }) } diff --git a/src/WAUSync/USyncUser.ts b/src/WAUSync/USyncUser.ts index 169e10b..764fb75 100644 --- a/src/WAUSync/USyncUser.ts +++ b/src/WAUSync/USyncUser.ts @@ -26,7 +26,7 @@ export class USyncUser { } withPersonaId(personaId: string) { - this.personaId = personaId - return this + this.personaId = personaId + return this } } diff --git a/src/WAUSync/index.ts b/src/WAUSync/index.ts index e36c97e..5492886 100644 --- a/src/WAUSync/index.ts +++ b/src/WAUSync/index.ts @@ -1,3 +1,3 @@ export * from './Protocols' export * from './USyncQuery' -export * from './USyncUser' \ No newline at end of file +export * from './USyncUser'