Path: blob/master/node_modules/@adiwajshing/baileys/lib/Utils/chat-utils.js
1129 views
"use strict";1Object.defineProperty(exports, "__esModule", { value: true });2exports.processSyncAction = exports.chatModificationToAppPatch = exports.decodePatches = exports.decodeSyncdSnapshot = exports.downloadExternalPatch = exports.downloadExternalBlob = exports.extractSyncdPatches = exports.decodeSyncdPatch = exports.decodeSyncdMutations = exports.encodeSyncdPatch = exports.newLTHashState = void 0;3const boom_1 = require("@hapi/boom");4const WAProto_1 = require("../../WAProto");5const WABinary_1 = require("../WABinary");6const crypto_1 = require("./crypto");7const generics_1 = require("./generics");8const lt_hash_1 = require("./lt-hash");9const messages_media_1 = require("./messages-media");10const mutationKeys = (keydata) => {11const expanded = (0, crypto_1.hkdf)(keydata, 160, { info: 'WhatsApp Mutation Keys' });12return {13indexKey: expanded.slice(0, 32),14valueEncryptionKey: expanded.slice(32, 64),15valueMacKey: expanded.slice(64, 96),16snapshotMacKey: expanded.slice(96, 128),17patchMacKey: expanded.slice(128, 160)18};19};20const generateMac = (operation, data, keyId, key) => {21const getKeyData = () => {22let r;23switch (operation) {24case WAProto_1.proto.SyncdMutation.SyncdOperation.SET:25r = 0x01;26break;27case WAProto_1.proto.SyncdMutation.SyncdOperation.REMOVE:28r = 0x02;29break;30}31const buff = Buffer.from([r]);32return Buffer.concat([buff, Buffer.from(keyId, 'base64')]);33};34const keyData = getKeyData();35const last = Buffer.alloc(8); // 8 bytes36last.set([keyData.length], last.length - 1);37const total = Buffer.concat([keyData, data, last]);38const hmac = (0, crypto_1.hmacSign)(total, key, 'sha512');39return hmac.slice(0, 32);40};41const to64BitNetworkOrder = (e) => {42const t = new ArrayBuffer(8);43new DataView(t).setUint32(4, e, !1);44return Buffer.from(t);45};46const makeLtHashGenerator = ({ indexValueMap, hash }) => {47indexValueMap = { ...indexValueMap };48const addBuffs = [];49const subBuffs = [];50return {51mix: ({ indexMac, valueMac, operation }) => {52const indexMacBase64 = Buffer.from(indexMac).toString('base64');53const prevOp = indexValueMap[indexMacBase64];54if (operation === WAProto_1.proto.SyncdMutation.SyncdOperation.REMOVE) {55if (!prevOp) {56throw new boom_1.Boom('tried remove, but no previous op', { data: { indexMac, valueMac } });57}58// remove from index value mac, since this mutation is erased59delete indexValueMap[indexMacBase64];60}61else {62addBuffs.push(new Uint8Array(valueMac).buffer);63// add this index into the history map64indexValueMap[indexMacBase64] = { valueMac };65}66if (prevOp) {67subBuffs.push(new Uint8Array(prevOp.valueMac).buffer);68}69},70finish: () => {71const result = lt_hash_1.LT_HASH_ANTI_TAMPERING.subtractThenAdd(new Uint8Array(hash).buffer, addBuffs, subBuffs);72const buffer = Buffer.from(result);73return {74hash: buffer,75indexValueMap76};77}78};79};80const generateSnapshotMac = (lthash, version, name, key) => {81const total = Buffer.concat([82lthash,83to64BitNetworkOrder(version),84Buffer.from(name, 'utf-8')85]);86return (0, crypto_1.hmacSign)(total, key, 'sha256');87};88const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {89const total = Buffer.concat([90snapshotMac,91...valueMacs,92to64BitNetworkOrder(version),93Buffer.from(type, 'utf-8')94]);95return (0, crypto_1.hmacSign)(total, key);96};97const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });98exports.newLTHashState = newLTHashState;99const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {100const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;101if (!key) {102throw new boom_1.Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404 });103}104const encKeyId = Buffer.from(myAppStateKeyId, 'base64');105state = { ...state, indexValueMap: { ...state.indexValueMap } };106const indexBuffer = Buffer.from(JSON.stringify(index));107const dataProto = WAProto_1.proto.SyncActionData.fromObject({108index: indexBuffer,109value: syncAction,110padding: new Uint8Array(0),111version: apiVersion112});113const encoded = WAProto_1.proto.SyncActionData.encode(dataProto).finish();114const keyValue = mutationKeys(key.keyData);115const encValue = (0, crypto_1.aesEncrypt)(encoded, keyValue.valueEncryptionKey);116const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey);117const indexMac = (0, crypto_1.hmacSign)(indexBuffer, keyValue.indexKey);118// update LT hash119const generator = makeLtHashGenerator(state);120generator.mix({ indexMac, valueMac, operation });121Object.assign(state, generator.finish());122state.version += 1;123const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey);124const patch = {125patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),126snapshotMac: snapshotMac,127keyId: { id: encKeyId },128mutations: [129{130operation: operation,131record: {132index: {133blob: indexMac134},135value: {136blob: Buffer.concat([encValue, valueMac])137},138keyId: { id: encKeyId }139}140}141]142};143const base64Index = indexMac.toString('base64');144state.indexValueMap[base64Index] = { valueMac };145return { patch, state };146};147exports.encodeSyncdPatch = encodeSyncdPatch;148const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, onMutation, validateMacs) => {149const keyCache = {};150const getKey = async (keyId) => {151const base64Key = Buffer.from(keyId).toString('base64');152let key = keyCache[base64Key];153if (!key) {154const keyEnc = await getAppStateSyncKey(base64Key);155if (!keyEnc) {156throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } });157}158const result = mutationKeys(keyEnc.keyData);159keyCache[base64Key] = result;160key = result;161}162return key;163};164const ltGenerator = makeLtHashGenerator(initialState);165// indexKey used to HMAC sign record.index.blob166// valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32]167// the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId168for (const msgMutation of msgMutations) {169// if it's a syncdmutation, get the operation property170// otherwise, if it's only a record -- it'll be a SET mutation171const operation = 'operation' in msgMutation ? msgMutation.operation : WAProto_1.proto.SyncdMutation.SyncdOperation.SET;172const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation;173const key = await getKey(record.keyId.id);174const content = Buffer.from(record.value.blob);175const encContent = content.slice(0, -32);176const ogValueMac = content.slice(-32);177if (validateMacs) {178const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey);179if (Buffer.compare(contentHmac, ogValueMac) !== 0) {180throw new boom_1.Boom('HMAC content verification failed');181}182}183const result = (0, crypto_1.aesDecrypt)(encContent, key.valueEncryptionKey);184const syncAction = WAProto_1.proto.SyncActionData.decode(result);185if (validateMacs) {186const hmac = (0, crypto_1.hmacSign)(syncAction.index, key.indexKey);187if (Buffer.compare(hmac, record.index.blob) !== 0) {188throw new boom_1.Boom('HMAC index verification failed');189}190}191const indexStr = Buffer.from(syncAction.index).toString();192onMutation({ syncAction, index: JSON.parse(indexStr) });193ltGenerator.mix({194indexMac: record.index.blob,195valueMac: ogValueMac,196operation: operation197});198}199return ltGenerator.finish();200};201exports.decodeSyncdMutations = decodeSyncdMutations;202const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, onMutation, validateMacs) => {203if (validateMacs) {204const base64Key = Buffer.from(msg.keyId.id).toString('base64');205const mainKeyObj = await getAppStateSyncKey(base64Key);206const mainKey = mutationKeys(mainKeyObj.keyData);207const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));208const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, (0, generics_1.toNumber)(msg.version.version), name, mainKey.patchMacKey);209if (Buffer.compare(patchMac, msg.patchMac) !== 0) {210throw new boom_1.Boom('Invalid patch mac');211}212}213const result = await (0, exports.decodeSyncdMutations)(msg.mutations, initialState, getAppStateSyncKey, onMutation, validateMacs);214return result;215};216exports.decodeSyncdPatch = decodeSyncdPatch;217const extractSyncdPatches = async (result) => {218const syncNode = (0, WABinary_1.getBinaryNodeChild)(result, 'sync');219const collectionNodes = (0, WABinary_1.getBinaryNodeChildren)(syncNode, 'collection');220const final = {};221await Promise.all(collectionNodes.map(async (collectionNode) => {222const patchesNode = (0, WABinary_1.getBinaryNodeChild)(collectionNode, 'patches');223const patches = (0, WABinary_1.getBinaryNodeChildren)(patchesNode || collectionNode, 'patch');224const snapshotNode = (0, WABinary_1.getBinaryNodeChild)(collectionNode, 'snapshot');225const syncds = [];226const name = collectionNode.attrs.name;227const hasMorePatches = collectionNode.attrs.has_more_patches === 'true';228let snapshot = undefined;229if (snapshotNode && !!snapshotNode.content) {230if (!Buffer.isBuffer(snapshotNode)) {231snapshotNode.content = Buffer.from(Object.values(snapshotNode.content));232}233const blobRef = WAProto_1.proto.ExternalBlobReference.decode(snapshotNode.content);234const data = await (0, exports.downloadExternalBlob)(blobRef);235snapshot = WAProto_1.proto.SyncdSnapshot.decode(data);236}237for (let { content } of patches) {238if (content) {239if (!Buffer.isBuffer(content)) {240content = Buffer.from(Object.values(content));241}242const syncd = WAProto_1.proto.SyncdPatch.decode(content);243if (!syncd.version) {244syncd.version = { version: +collectionNode.attrs.version + 1 };245}246syncds.push(syncd);247}248}249final[name] = { patches: syncds, hasMorePatches, snapshot };250}));251return final;252};253exports.extractSyncdPatches = extractSyncdPatches;254const downloadExternalBlob = async (blob) => {255const stream = await (0, messages_media_1.downloadContentFromMessage)(blob, 'md-app-state');256let buffer = Buffer.from([]);257for await (const chunk of stream) {258buffer = Buffer.concat([buffer, chunk]);259}260return buffer;261};262exports.downloadExternalBlob = downloadExternalBlob;263const downloadExternalPatch = async (blob) => {264const buffer = await (0, exports.downloadExternalBlob)(blob);265const syncData = WAProto_1.proto.SyncdMutations.decode(buffer);266return syncData;267};268exports.downloadExternalPatch = downloadExternalPatch;269const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, onMutation, validateMacs = true) => {270const newState = (0, exports.newLTHashState)();271newState.version = (0, generics_1.toNumber)(snapshot.version.version);272onMutation = onMutation || (() => { });273const { hash, indexValueMap } = await (0, exports.decodeSyncdMutations)(snapshot.records, newState, getAppStateSyncKey, mutation => {274if (onMutation) {275const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber;276if (areMutationsRequired) {277onMutation(mutation);278}279}280}, validateMacs);281newState.hash = hash;282newState.indexValueMap = indexValueMap;283if (validateMacs) {284const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');285const keyEnc = await getAppStateSyncKey(base64Key);286if (!keyEnc) {287throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500 });288}289const result = mutationKeys(keyEnc.keyData);290const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);291if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {292throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`, { statusCode: 500 });293}294}295return {296state: newState,297};298};299exports.decodeSyncdSnapshot = decodeSyncdSnapshot;300const decodePatches = async (name, syncds, initial, getAppStateSyncKey, onMutation, minimumVersionNumber, logger, validateMacs = true) => {301var _a;302syncds = [...syncds];303const successfulMutations = [];304const newState = {305...initial,306indexValueMap: { ...initial.indexValueMap }307};308while (syncds.length) {309const syncd = syncds[0];310const { version, keyId, snapshotMac } = syncd;311if (syncd.externalMutations) {312logger === null || logger === void 0 ? void 0 : logger.trace({ name, version }, 'downloading external patch');313const ref = await (0, exports.downloadExternalPatch)(syncd.externalMutations);314logger === null || logger === void 0 ? void 0 : logger.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch');315(_a = syncd.mutations) === null || _a === void 0 ? void 0 : _a.push(...ref.mutations);316}317const patchVersion = (0, generics_1.toNumber)(version.version);318newState.version = patchVersion;319const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber;320const decodeResult = await (0, exports.decodeSyncdPatch)(syncd, name, newState, getAppStateSyncKey, shouldMutate ? onMutation : (() => { }), validateMacs);321newState.hash = decodeResult.hash;322newState.indexValueMap = decodeResult.indexValueMap;323if (validateMacs) {324const base64Key = Buffer.from(keyId.id).toString('base64');325const keyEnc = await getAppStateSyncKey(base64Key);326if (!keyEnc) {327throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`);328}329const result = mutationKeys(keyEnc.keyData);330const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);331if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {332throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name}`);333}334}335// clear memory used up by the mutations336syncd.mutations = [];337// pop first element338syncds.splice(0, 1);339}340return {341newMutations: successfulMutations,342state: newState343};344};345exports.decodePatches = decodePatches;346const chatModificationToAppPatch = (mod, jid) => {347const OP = WAProto_1.proto.SyncdMutation.SyncdOperation;348const getMessageRange = (lastMessages) => {349let messageRange;350if (Array.isArray(lastMessages)) {351const lastMsg = lastMessages[lastMessages.length - 1];352messageRange = {353lastMessageTimestamp: lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.messageTimestamp,354messages: (lastMessages === null || lastMessages === void 0 ? void 0 : lastMessages.length) ? lastMessages.map(m => {355var _a, _b;356if (!((_a = m.key) === null || _a === void 0 ? void 0 : _a.id) || !((_b = m.key) === null || _b === void 0 ? void 0 : _b.remoteJid)) {357throw new boom_1.Boom('Incomplete key', { statusCode: 400, data: m });358}359if ((0, WABinary_1.isJidGroup)(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) {360throw new boom_1.Boom('Expected not from me message to have participant', { statusCode: 400, data: m });361}362if (!m.messageTimestamp || !(0, generics_1.toNumber)(m.messageTimestamp)) {363throw new boom_1.Boom('Missing timestamp in last message list', { statusCode: 400, data: m });364}365if (m.key.participant) {366m.key.participant = (0, WABinary_1.jidNormalizedUser)(m.key.participant);367}368return m;369}) : undefined370};371}372else {373messageRange = lastMessages;374}375return messageRange;376};377let patch;378if ('mute' in mod) {379patch = {380syncAction: {381muteAction: {382muted: !!mod.mute,383muteEndTimestamp: mod.mute || undefined384}385},386index: ['mute', jid],387type: 'regular_high',388apiVersion: 2,389operation: OP.SET390};391}392else if ('archive' in mod) {393patch = {394syncAction: {395archiveChatAction: {396archived: !!mod.archive,397messageRange: getMessageRange(mod.lastMessages)398}399},400index: ['archive', jid],401type: 'regular_low',402apiVersion: 3,403operation: OP.SET404};405}406else if ('markRead' in mod) {407patch = {408syncAction: {409markChatAsReadAction: {410read: mod.markRead,411messageRange: getMessageRange(mod.lastMessages)412}413},414index: ['markChatAsRead', jid],415type: 'regular_low',416apiVersion: 3,417operation: OP.SET418};419}420else if ('clear' in mod) {421if (mod.clear === 'all') {422throw new boom_1.Boom('not supported');423}424else {425const key = mod.clear.messages[0];426patch = {427syncAction: {428deleteMessageForMeAction: {429deleteMedia: false,430messageTimestamp: key.timestamp431}432},433index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],434type: 'regular_high',435apiVersion: 3,436operation: OP.SET437};438}439}440else if ('pin' in mod) {441patch = {442syncAction: {443pinAction: {444pinned: !!mod.pin445}446},447index: ['pin_v1', jid],448type: 'regular_low',449apiVersion: 5,450operation: OP.SET451};452}453else if ('delete' in mod) {454patch = {455syncAction: {456deleteChatAction: {457messageRange: getMessageRange(mod.lastMessages),458}459},460index: ['deleteChat', jid, '1'],461type: 'regular_high',462apiVersion: 6,463operation: OP.SET464};465}466else if ('pushNameSetting' in mod) {467patch = {468syncAction: {469pushNameSetting: {470name: mod.pushNameSetting471}472},473index: ['setting_pushName'],474type: 'critical_block',475apiVersion: 1,476operation: OP.SET,477};478}479else {480throw new boom_1.Boom('not supported');481}482patch.syncAction.timestamp = Date.now();483return patch;484};485exports.chatModificationToAppPatch = chatModificationToAppPatch;486const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {487var _a, _b, _c, _d, _e, _f;488const isInitialSync = !!initialSyncOpts;489const recvChats = initialSyncOpts === null || initialSyncOpts === void 0 ? void 0 : initialSyncOpts.recvChats;490const accountSettings = initialSyncOpts === null || initialSyncOpts === void 0 ? void 0 : initialSyncOpts.accountSettings;491const { syncAction: { value: action }, index: [type, id, msgId, fromMe] } = syncAction;492if (action === null || action === void 0 ? void 0 : action.muteAction) {493ev.emit('chats.update', [494{495id,496mute: ((_a = action.muteAction) === null || _a === void 0 ? void 0 : _a.muted) ?497(0, generics_1.toNumber)(action.muteAction.muteEndTimestamp) :498null499}500]);501}502else if (action === null || action === void 0 ? void 0 : action.archiveChatAction) {503// okay so we've to do some annoying computation here504// when we're initially syncing the app state505// there are a few cases we need to handle506// 1. if the account unarchiveChats setting is true507// a. if the chat is archived, and no further messages have been received -- simple, keep archived508// b. if the chat was archived, and the user received messages from the other person afterwards509// then the chat should be marked unarchved --510// we compare the timestamp of latest message from the other person to determine this511// 2. if the account unarchiveChats setting is false -- then it doesn't matter,512// it'll always take an app state action to mark in unarchived -- which we'll get anyway513const archiveAction = action.archiveChatAction;514if (isValidPatchBasedOnMessageRange(id, archiveAction.messageRange)515|| !isInitialSync516|| !(accountSettings === null || accountSettings === void 0 ? void 0 : accountSettings.unarchiveChats)) {517// basically we don't need to fire an "archive" update if the chat is being marked unarchvied518// this only applies for the initial sync519if (isInitialSync && !archiveAction.archived) {520ev.emit('chats.update', [{ id, archive: false }]);521}522else {523ev.emit('chats.update', [{ id, archive: !!(archiveAction === null || archiveAction === void 0 ? void 0 : archiveAction.archived) }]);524}525}526}527else if (action === null || action === void 0 ? void 0 : action.markChatAsReadAction) {528const markReadAction = action.markChatAsReadAction;529if (isValidPatchBasedOnMessageRange(id, markReadAction.messageRange)530|| !isInitialSync) {531// basically we don't need to fire an "read" update if the chat is being marked as read532// because the chat is read by default533// this only applies for the initial sync534if (isInitialSync && markReadAction.read) {535ev.emit('chats.update', [{ id, unreadCount: null }]);536}537else {538ev.emit('chats.update', [{ id, unreadCount: !!(markReadAction === null || markReadAction === void 0 ? void 0 : markReadAction.read) ? 0 : -1 }]);539}540}541}542else if ((action === null || action === void 0 ? void 0 : action.deleteMessageForMeAction) || type === 'deleteMessageForMe') {543ev.emit('messages.delete', { keys: [544{545remoteJid: id,546id: msgId,547fromMe: fromMe === '1'548}549] });550}551else if (action === null || action === void 0 ? void 0 : action.contactAction) {552ev.emit('contacts.upsert', [{ id, name: action.contactAction.fullName }]);553}554else if (action === null || action === void 0 ? void 0 : action.pushNameSetting) {555if ((me === null || me === void 0 ? void 0 : me.name) !== (action === null || action === void 0 ? void 0 : action.pushNameSetting)) {556ev.emit('creds.update', { me: { ...me, name: (_b = action === null || action === void 0 ? void 0 : action.pushNameSetting) === null || _b === void 0 ? void 0 : _b.name } });557}558}559else if (action === null || action === void 0 ? void 0 : action.pinAction) {560ev.emit('chats.update', [{ id, pin: ((_c = action.pinAction) === null || _c === void 0 ? void 0 : _c.pinned) ? (0, generics_1.toNumber)(action.timestamp) : null }]);561}562else if (action === null || action === void 0 ? void 0 : action.unarchiveChatsSetting) {563const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats;564ev.emit('creds.update', { accountSettings: { unarchiveChats } });565logger === null || logger === void 0 ? void 0 : logger.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`);566if (accountSettings) {567accountSettings.unarchiveChats = unarchiveChats;568}569}570else if ((action === null || action === void 0 ? void 0 : action.starAction) || type === 'star') {571let starred = (_d = action === null || action === void 0 ? void 0 : action.starAction) === null || _d === void 0 ? void 0 : _d.starred;572if (typeof starred !== 'boolean') {573starred = syncAction.index[syncAction.index.length - 1] === '1';574}575ev.emit('messages.update', [576{577key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' },578update: { starred }579}580]);581}582else if ((action === null || action === void 0 ? void 0 : action.deleteChatAction) || type === 'deleteChat') {583if ((((_e = action === null || action === void 0 ? void 0 : action.deleteChatAction) === null || _e === void 0 ? void 0 : _e.messageRange)584&& isValidPatchBasedOnMessageRange(id, (_f = action === null || action === void 0 ? void 0 : action.deleteChatAction) === null || _f === void 0 ? void 0 : _f.messageRange))585|| !isInitialSync) {586ev.emit('chats.delete', [id]);587}588}589else {590logger === null || logger === void 0 ? void 0 : logger.warn({ syncAction, id }, 'unprocessable update');591}592function isValidPatchBasedOnMessageRange(id, msgRange) {593const chat = recvChats === null || recvChats === void 0 ? void 0 : recvChats[id];594const lastMsgTimestamp = (msgRange === null || msgRange === void 0 ? void 0 : msgRange.lastMessageTimestamp) || (msgRange === null || msgRange === void 0 ? void 0 : msgRange.lastSystemMessageTimestamp) || 0;595const chatLastMsgTimestamp = (chat === null || chat === void 0 ? void 0 : chat.lastMsgRecvTimestamp) || 0;596return lastMsgTimestamp >= chatLastMsgTimestamp;597}598};599exports.processSyncAction = processSyncAction;600601602