@nexustechpro/baileys 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,727 +1,426 @@
1
- import { Boom } from '@hapi/boom';
2
- import { proto } from '../../WAProto/index.js';
3
- import { LabelAssociationType } from '../Types/LabelAssociation.js';
1
+ import { Boom } from '@hapi/boom'
2
+ import { expandAppStateKeys } from 'whatsapp-rust-bridge'
3
+ import { proto } from '../../WAProto/index.js'
4
+ import { LabelAssociationType } from '../Types/LabelAssociation.js'
4
5
  import { processContactAction, emitSyncActionResults } from './sync-action-utils.js'
5
- import { getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser, isJidUser } from '../WABinary/index.js';
6
- import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto.js';
7
- import { toNumber } from './generics.js';
8
- import { LT_HASH_ANTI_TAMPERING } from './lt-hash.js';
9
- import { downloadContentFromMessage } from './messages-media.js';
6
+ import { getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser, isJidUser } from '../WABinary/index.js'
7
+ import { aesDecrypt, aesEncrypt, hmacSign } from './crypto.js'
8
+ import { toNumber } from './generics.js'
9
+ import { LT_HASH_ANTI_TAMPERING } from './lt-hash.js'
10
+ import { downloadContentFromMessage } from './messages-media.js'
10
11
 
11
- const mutationKeys = async (keydata) => {
12
- const expanded = hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' });
13
- return {
14
- indexKey: expanded.slice(0, 32),
15
- valueEncryptionKey: expanded.slice(32, 64),
16
- valueMacKey: expanded.slice(64, 96),
17
- snapshotMacKey: expanded.slice(96, 128),
18
- patchMacKey: expanded.slice(128, 160)
19
- };
20
- };
12
+ const mutationKeys = (keydata) => {
13
+ const keys = expandAppStateKeys(keydata)
14
+ return { indexKey: keys.indexKey, valueEncryptionKey: keys.valueEncryptionKey, valueMacKey: keys.valueMacKey, snapshotMacKey: keys.snapshotMacKey, patchMacKey: keys.patchMacKey }
15
+ }
21
16
 
22
17
  const generateMac = (operation, data, keyId, key) => {
23
- const getKeyData = () => {
24
- let r;
25
- switch (operation) {
26
- case proto.SyncdMutation.SyncdOperation.SET:
27
- r = 0x01;
28
- break;
29
- case proto.SyncdMutation.SyncdOperation.REMOVE:
30
- r = 0x02;
31
- break;
32
- }
33
- const buff = Buffer.from([r]);
34
- return Buffer.concat([buff, Buffer.from(keyId, 'base64')]);
35
- };
36
- const keyData = getKeyData();
37
- const last = Buffer.alloc(8);
38
- last.set([keyData.length], last.length - 1);
39
- const total = Buffer.concat([keyData, data, last]);
40
- const hmac = hmacSign(total, key, 'sha512');
41
- return hmac.slice(0, 32);
42
- };
18
+ const opByte = operation === proto.SyncdMutation.SyncdOperation.SET ? 0x01 : 0x02
19
+ const keyIdBuffer = typeof keyId === 'string' ? Buffer.from(keyId, 'base64') : keyId
20
+ const keyData = new Uint8Array(1 + keyIdBuffer.length)
21
+ keyData[0] = opByte
22
+ keyData.set(keyIdBuffer, 1)
23
+ const last = new Uint8Array(8)
24
+ last[7] = keyData.length
25
+ const total = new Uint8Array(keyData.length + data.length + last.length)
26
+ total.set(keyData, 0)
27
+ total.set(data, keyData.length)
28
+ total.set(last, keyData.length + data.length)
29
+ const hmac = hmacSign(total, key, 'sha512')
30
+ return hmac.subarray(0, 32)
31
+ }
43
32
 
44
33
  const to64BitNetworkOrder = (e) => {
45
- const buff = Buffer.alloc(8);
46
- buff.writeUint32BE(e, 4);
47
- return buff;
48
- };
34
+ const buff = Buffer.alloc(8)
35
+ buff.writeUint32BE(e, 4)
36
+ return buff
37
+ }
49
38
 
50
- const makeLtHashGenerator = ({ indexValueMap, hash }) => {
51
- indexValueMap = { ...indexValueMap };
52
- const addBuffs = [];
53
- const subBuffs = [];
39
+ export const makeLtHashGenerator = ({ indexValueMap, hash }) => {
40
+ indexValueMap = { ...indexValueMap }
41
+ const addBuffs = []
42
+ const subBuffs = []
54
43
  return {
55
44
  mix: ({ indexMac, valueMac, operation }) => {
56
- const indexMacBase64 = Buffer.from(indexMac).toString('base64');
57
- const prevOp = indexValueMap[indexMacBase64];
45
+ const indexMacBase64 = Buffer.from(indexMac).toString('base64')
46
+ const prevOp = indexValueMap[indexMacBase64]
58
47
  if (operation === proto.SyncdMutation.SyncdOperation.REMOVE) {
59
- if (!prevOp) {
60
- // Skip remove if there's no previous operation - likely sync corruption
61
- // The state will be resync'd from scratch
62
- return;
63
- }
64
- delete indexValueMap[indexMacBase64];
65
- }
66
- else {
67
- addBuffs.push(new Uint8Array(valueMac).buffer);
68
- indexValueMap[indexMacBase64] = { valueMac };
69
- }
70
- if (prevOp) {
71
- subBuffs.push(new Uint8Array(prevOp.valueMac).buffer);
48
+ if (!prevOp) return
49
+ delete indexValueMap[indexMacBase64]
50
+ } else {
51
+ addBuffs.push(valueMac)
52
+ indexValueMap[indexMacBase64] = { valueMac }
72
53
  }
54
+ if (prevOp) subBuffs.push(prevOp.valueMac)
73
55
  },
74
- finish: async () => {
75
- const hashArrayBuffer = new Uint8Array(hash).buffer;
76
- const result = await LT_HASH_ANTI_TAMPERING.subtractThenAdd(hashArrayBuffer, addBuffs, subBuffs);
77
- const buffer = Buffer.from(result);
78
- return {
79
- hash: buffer,
80
- indexValueMap
81
- };
56
+ finish: () => {
57
+ const result = LT_HASH_ANTI_TAMPERING.subtractThenAdd(hash, subBuffs, addBuffs)
58
+ return { hash: Buffer.from(result), indexValueMap }
82
59
  }
83
- };
84
- };
60
+ }
61
+ }
85
62
 
86
63
  const generateSnapshotMac = (lthash, version, name, key) => {
87
- const total = Buffer.concat([lthash, to64BitNetworkOrder(version), Buffer.from(name, 'utf-8')]);
88
- return hmacSign(total, key, 'sha256');
89
- };
64
+ const total = Buffer.concat([lthash, to64BitNetworkOrder(version), Buffer.from(name, 'utf-8')])
65
+ return hmacSign(total, key, 'sha256')
66
+ }
90
67
 
91
68
  const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
92
- const total = Buffer.concat([snapshotMac, ...valueMacs, to64BitNetworkOrder(version), Buffer.from(type, 'utf-8')]);
93
- return hmacSign(total, key);
94
- };
69
+ const total = Buffer.concat([snapshotMac, ...valueMacs, to64BitNetworkOrder(version), Buffer.from(type, 'utf-8')])
70
+ return hmacSign(total, key)
71
+ }
95
72
 
96
- export const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });
73
+ export const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} })
97
74
 
98
- // Added: was missing from compiled output, required by chats.js
99
75
  export const ensureLTHashStateVersion = (state) => {
100
- if (typeof state.version !== 'number' || isNaN(state.version)) {
101
- state.version = 0;
102
- }
103
- return state;
104
- };
76
+ if (typeof state.version !== 'number' || isNaN(state.version)) state.version = 0
77
+ return state
78
+ }
79
+
80
+ export const MAX_SYNC_ATTEMPTS = 2
81
+
82
+ /** Check if an error is a missing app state sync key. WA Web treats these as "Blocked" (waits for key arrival), not fatal. */
83
+ export const isMissingKeyError = (error) => error?.data?.isMissingKey === true
105
84
 
106
- // Added: used by chats.js to detect missing key errors across all throw sites
107
- export const isMissingKeyError = (error) => {
108
- return error?.data?.isMissingKey === true
109
- || error?.output?.statusCode === 404
110
- || error?.statusCode === 404;
111
- };
85
+ /** Determines if an app state sync error is unrecoverable. TypeError indicates a WASM crash; otherwise we give up after MAX_SYNC_ATTEMPTS. Missing keys are NOT checked here — they are handled separately as "Blocked". */
86
+ export const isAppStateSyncIrrecoverable = (error, attempts) => attempts >= MAX_SYNC_ATTEMPTS || error?.name === 'TypeError'
112
87
 
113
88
  export const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
114
- const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;
115
- if (!key) {
116
- throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404, data: { isMissingKey: true } });
117
- }
118
- const encKeyId = Buffer.from(myAppStateKeyId, 'base64');
119
- state = { ...state, indexValueMap: { ...state.indexValueMap } };
120
- const indexBuffer = Buffer.from(JSON.stringify(index));
121
- const dataProto = proto.SyncActionData.fromObject({
122
- index: indexBuffer,
123
- value: syncAction,
124
- padding: new Uint8Array(0),
125
- version: apiVersion
126
- });
127
- const encoded = proto.SyncActionData.encode(dataProto).finish();
128
- const keyValue = await mutationKeys(key.keyData);
129
- const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey);
130
- const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey);
131
- const indexMac = hmacSign(indexBuffer, keyValue.indexKey);
132
- const generator = makeLtHashGenerator(state);
133
- generator.mix({ indexMac, valueMac, operation });
134
- Object.assign(state, await generator.finish());
135
- state.version += 1;
136
- const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey);
89
+ const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined
90
+ if (!key) throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { data: { isMissingKey: true } })
91
+ const encKeyId = Buffer.from(myAppStateKeyId, 'base64')
92
+ state = { ...state, indexValueMap: { ...state.indexValueMap } }
93
+ const indexBuffer = Buffer.from(JSON.stringify(index))
94
+ const dataProto = proto.SyncActionData.fromObject({ index: indexBuffer, value: syncAction, padding: new Uint8Array(0), version: apiVersion })
95
+ const encoded = proto.SyncActionData.encode(dataProto).finish()
96
+ const keyValue = mutationKeys(key.keyData)
97
+ const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
98
+ const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
99
+ const indexMac = hmacSign(indexBuffer, keyValue.indexKey)
100
+ const generator = makeLtHashGenerator(state)
101
+ generator.mix({ indexMac, valueMac, operation })
102
+ Object.assign(state, generator.finish())
103
+ state.version += 1
104
+ const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey)
137
105
  const patch = {
138
106
  patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),
139
107
  snapshotMac: snapshotMac,
140
108
  keyId: { id: encKeyId },
141
- mutations: [
142
- {
143
- operation: operation,
144
- record: {
145
- index: { blob: indexMac },
146
- value: { blob: Buffer.concat([encValue, valueMac]) },
147
- keyId: { id: encKeyId }
148
- }
149
- }
150
- ]
151
- };
152
- const base64Index = indexMac.toString('base64');
153
- state.indexValueMap[base64Index] = { valueMac };
154
- return { patch, state };
155
- };
109
+ mutations: [{ operation: operation, record: { index: { blob: indexMac }, value: { blob: Buffer.concat([encValue, valueMac]) }, keyId: { id: encKeyId } } }]
110
+ }
111
+ const base64Index = indexMac.toString('base64')
112
+ state.indexValueMap[base64Index] = { valueMac }
113
+ return { patch, state }
114
+ }
156
115
 
157
- export const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, onMutation, validateMacs, logger) => {
158
- const ltGenerator = makeLtHashGenerator(initialState);
116
+ export const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
117
+ const ltGenerator = makeLtHashGenerator(initialState)
159
118
  const derivedKeyCache = new Map()
160
- let skippedMutations = 0;
161
119
  for (const msgMutation of msgMutations) {
162
- const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET;
163
- const record = 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : msgMutation;
164
- let key;
120
+ const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
121
+ const record = 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : msgMutation
122
+ let key
165
123
  try {
166
- key = await getKey(record.keyId.id);
124
+ key = await getKey(record.keyId.id)
167
125
  } catch (err) {
168
- if (err?.data?.isMissingKey) {
169
- skippedMutations++;
170
- logger?.warn?.({ keyId: Buffer.from(record.keyId.id).toString('base64'), skippedMutations }, 'Skipping mutation with missing key — non-critical data may be stale');
171
- continue;
172
- }
173
- throw err; // re-throw non-missing-key errors (e.g. HMAC failures)
126
+ if (isMissingKeyError(err)) throw err
127
+ continue
174
128
  }
175
- const content = Buffer.from(record.value.blob);
176
- const encContent = content.slice(0, -32);
177
- const ogValueMac = content.slice(-32);
129
+ const content = record.value.blob
130
+ const encContent = content.subarray(0, -32)
131
+ const ogValueMac = content.subarray(-32)
178
132
  if (validateMacs) {
179
- const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey);
180
- if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
181
- throw new Boom('HMAC content verification failed');
182
- }
133
+ const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey)
134
+ if (Buffer.compare(contentHmac, ogValueMac) !== 0) continue
135
+ }
136
+ let result
137
+ try {
138
+ result = aesDecrypt(encContent, key.valueEncryptionKey)
139
+ } catch {
140
+ continue
183
141
  }
184
- const result = aesDecrypt(encContent, key.valueEncryptionKey);
185
- const syncAction = proto.SyncActionData.decode(result);
142
+ const syncAction = proto.SyncActionData.decode(result)
186
143
  if (validateMacs) {
187
- const hmac = hmacSign(syncAction.index, key.indexKey);
188
- if (Buffer.compare(hmac, record.index.blob) !== 0) {
189
- throw new Boom('HMAC index verification failed');
190
- }
144
+ const hmac = hmacSign(syncAction.index, key.indexKey)
145
+ if (Buffer.compare(hmac, record.index.blob) !== 0) throw new Boom('HMAC index verification failed')
191
146
  }
192
- const indexStr = Buffer.from(syncAction.index).toString();
193
- onMutation({ syncAction, index: JSON.parse(indexStr) });
194
- ltGenerator.mix({
195
- indexMac: record.index.blob,
196
- valueMac: ogValueMac,
197
- operation: operation
198
- });
199
- }
200
- if (skippedMutations > 0) {
201
- logger?.info?.({ skippedMutations }, 'App state sync completed with skipped mutations');
147
+ const indexStr = Buffer.from(syncAction.index).toString()
148
+ onMutation({ syncAction, index: JSON.parse(indexStr) })
149
+ ltGenerator.mix({ indexMac: record.index.blob, valueMac: ogValueMac, operation: operation })
202
150
  }
203
- return await ltGenerator.finish();
151
+ return ltGenerator.finish()
204
152
 
205
153
  async function getKey(keyId) {
206
154
  const base64Key = Buffer.from(keyId).toString('base64')
207
155
  const cached = derivedKeyCache.get(base64Key)
208
156
  if (cached) return cached
209
157
  const keyEnc = await getAppStateSyncKey(base64Key)
210
- if (!keyEnc) throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { isMissingKey: true, msgMutations } })
211
- const keys = await mutationKeys(keyEnc.keyData)
158
+ if (!keyEnc) throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true, msgMutations } })
159
+ const keys = mutationKeys(keyEnc.keyData)
212
160
  derivedKeyCache.set(base64Key, keys)
213
161
  return keys
214
162
  }
215
- };
163
+ }
216
164
 
217
165
  export const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
218
166
  if (validateMacs) {
219
- const base64Key = Buffer.from(msg.keyId.id).toString('base64');
220
- const mainKeyObj = await getAppStateSyncKey(base64Key);
221
- if (!mainKeyObj) {
222
- throw new Boom(`failed to find key "${base64Key}" to decode patch`, {
223
- statusCode: 404,
224
- data: { isMissingKey: true, msg }
225
- });
226
- }
227
- const mainKey = await mutationKeys(mainKeyObj.keyData);
228
- const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));
229
- const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version.version), name, mainKey.patchMacKey);
230
- if (Buffer.compare(patchMac, msg.patchMac) !== 0) {
231
- throw new Boom('Invalid patch mac');
232
- }
233
- }
234
- const result = await decodeSyncdMutations(msg.mutations, initialState, getAppStateSyncKey, onMutation, validateMacs);
235
- return result;
236
- };
167
+ const base64Key = Buffer.from(msg.keyId.id).toString('base64')
168
+ const mainKeyObj = await getAppStateSyncKey(base64Key)
169
+ if (!mainKeyObj) throw new Boom(`failed to find key "${base64Key}" to decode patch`, { data: { isMissingKey: true, msg } })
170
+ const mainKey = mutationKeys(mainKeyObj.keyData)
171
+ const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32))
172
+ const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version.version), name, mainKey.patchMacKey)
173
+ if (Buffer.compare(patchMac, msg.patchMac) !== 0) throw new Boom('Invalid patch mac')
174
+ }
175
+ const result = await decodeSyncdMutations(msg.mutations, initialState, getAppStateSyncKey, onMutation, validateMacs)
176
+ return result
177
+ }
237
178
 
238
179
  export const extractSyncdPatches = async (result, options) => {
239
- const syncNode = getBinaryNodeChild(result, 'sync');
240
- const collectionNodes = getBinaryNodeChildren(syncNode, 'collection');
241
- const final = {};
180
+ const syncNode = getBinaryNodeChild(result, 'sync')
181
+ const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
182
+ const final = {}
242
183
  await Promise.all(collectionNodes.map(async (collectionNode) => {
243
- const patchesNode = getBinaryNodeChild(collectionNode, 'patches');
244
- const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch');
245
- const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot');
246
- const syncds = [];
247
- const name = collectionNode.attrs.name;
248
- const hasMorePatches = collectionNode.attrs.has_more_patches === 'true';
249
- let snapshot = undefined;
184
+ const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
185
+ const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch')
186
+ const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot')
187
+ const syncds = []
188
+ const name = collectionNode.attrs.name
189
+ const hasMorePatches = collectionNode.attrs.has_more_patches === 'true'
190
+ let snapshot = undefined
250
191
  if (snapshotNode && !!snapshotNode.content) {
251
- if (!Buffer.isBuffer(snapshotNode)) {
252
- snapshotNode.content = Buffer.from(Object.values(snapshotNode.content));
253
- }
254
- const blobRef = proto.ExternalBlobReference.decode(snapshotNode.content);
255
- const data = await downloadExternalBlob(blobRef, options);
256
- snapshot = proto.SyncdSnapshot.decode(data);
192
+ if (!Buffer.isBuffer(snapshotNode)) snapshotNode.content = Buffer.from(Object.values(snapshotNode.content))
193
+ const blobRef = proto.ExternalBlobReference.decode(snapshotNode.content)
194
+ const data = await downloadExternalBlob(blobRef, options)
195
+ snapshot = proto.SyncdSnapshot.decode(data)
257
196
  }
258
197
  for (let { content } of patches) {
259
198
  if (content) {
260
- if (!Buffer.isBuffer(content)) {
261
- content = Buffer.from(Object.values(content));
262
- }
263
- const syncd = proto.SyncdPatch.decode(content);
264
- if (!syncd.version) {
265
- syncd.version = { version: +collectionNode.attrs.version + 1 };
266
- }
267
- syncds.push(syncd);
199
+ if (!Buffer.isBuffer(content)) content = Buffer.from(Object.values(content))
200
+ const syncd = proto.SyncdPatch.decode(content)
201
+ if (!syncd.version) syncd.version = { version: +collectionNode.attrs.version + 1 }
202
+ syncds.push(syncd)
268
203
  }
269
204
  }
270
- final[name] = { patches: syncds, hasMorePatches, snapshot };
271
- }));
272
- return final;
273
- };
205
+ final[name] = { patches: syncds, hasMorePatches, snapshot }
206
+ }))
207
+ return final
208
+ }
274
209
 
275
210
  export const downloadExternalBlob = async (blob, options) => {
276
- const stream = await downloadContentFromMessage(blob, 'md-app-state', { options });
277
- const bufferArray = [];
278
- for await (const chunk of stream) {
279
- bufferArray.push(chunk);
280
- }
281
- return Buffer.concat(bufferArray);
282
- };
211
+ const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
212
+ const bufferArray = []
213
+ for await (const chunk of stream) bufferArray.push(chunk)
214
+ return Buffer.concat(bufferArray)
215
+ }
283
216
 
284
217
  export const downloadExternalPatch = async (blob, options) => {
285
- const buffer = await downloadExternalBlob(blob, options);
286
- const syncData = proto.SyncdMutations.decode(buffer);
287
- return syncData;
288
- };
218
+ const buffer = await downloadExternalBlob(blob, options)
219
+ const syncData = proto.SyncdMutations.decode(buffer)
220
+ return syncData
221
+ }
289
222
 
290
223
  export const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true, logger) => {
291
- const newState = newLTHashState();
292
- newState.version = toNumber(snapshot.version.version);
293
- const mutationMap = {};
294
- const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber;
295
- const { hash, indexValueMap } = await decodeSyncdMutations(snapshot.records, newState, getAppStateSyncKey, areMutationsRequired
296
- ? mutation => {
297
- const index = mutation.syncAction.index?.toString();
298
- mutationMap[index] = mutation;
299
- }
300
- : () => { }, validateMacs, logger);
301
- newState.hash = hash;
302
- newState.indexValueMap = indexValueMap;
224
+ const newState = newLTHashState()
225
+ newState.version = toNumber(snapshot.version.version)
226
+ const mutationMap = {}
227
+ const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber
228
+ const { hash, indexValueMap } = await decodeSyncdMutations(snapshot.records, newState, getAppStateSyncKey, areMutationsRequired ? mutation => { const index = mutation.syncAction.index?.toString(); mutationMap[index] = mutation } : () => { }, validateMacs)
229
+ newState.hash = hash
230
+ newState.indexValueMap = indexValueMap
303
231
  if (validateMacs) {
304
- const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');
305
- const keyEnc = await getAppStateSyncKey(base64Key);
306
- if (!keyEnc) {
307
- throw new Boom(`failed to find key "${base64Key}" to decode mutation`, {
308
- statusCode: 404,
309
- data: { isMissingKey: true }
310
- });
311
- }
312
- const result = await mutationKeys(keyEnc.keyData);
313
- const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
232
+ const base64Key = Buffer.from(snapshot.keyId.id).toString('base64')
233
+ const keyEnc = await getAppStateSyncKey(base64Key)
234
+ if (!keyEnc) throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } })
235
+ const result = mutationKeys(keyEnc.keyData)
236
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
314
237
  if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {
315
- throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`);
238
+ // LTHash verification may fail when decodeSyncdMutations skipped undecryptable records (poisoned server-side snapshot)
239
+ // Fall through with a warning so the session stays alive with partial state
240
+ logger?.warn({ name, version: newState.version }, 'LTHash verification failed on snapshot, continuing with partial state')
316
241
  }
317
242
  }
318
- return { state: newState, mutationMap };
319
- };
243
+ return { state: newState, mutationMap }
244
+ }
320
245
 
321
246
  export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, options, minimumVersionNumber, logger, validateMacs = true) => {
322
- const newState = {
323
- ...initial,
324
- indexValueMap: { ...initial.indexValueMap }
325
- };
326
- const mutationMap = {};
247
+ const newState = { ...initial, indexValueMap: { ...initial.indexValueMap } }
248
+ const mutationMap = {}
327
249
  for (const syncd of syncds) {
328
- const { version, keyId, snapshotMac } = syncd;
250
+ const { version, keyId, snapshotMac } = syncd
329
251
  if (syncd.externalMutations) {
330
- logger?.trace({ name, version }, 'downloading external patch');
331
- const ref = await downloadExternalPatch(syncd.externalMutations, options);
332
- logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch');
333
- syncd.mutations?.push(...ref.mutations);
252
+ logger?.trace({ name, version }, 'downloading external patch')
253
+ const ref = await downloadExternalPatch(syncd.externalMutations, options)
254
+ logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch')
255
+ syncd.mutations?.push(...ref.mutations)
334
256
  }
335
- const patchVersion = toNumber(version.version);
336
- newState.version = patchVersion;
337
- const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber;
338
- const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate
339
- ? mutation => {
340
- const index = mutation.syncAction.index?.toString();
341
- mutationMap[index] = mutation;
342
- }
343
- : () => { }, true);
344
- newState.hash = decodeResult.hash;
345
- newState.indexValueMap = decodeResult.indexValueMap;
257
+ const patchVersion = toNumber(version.version)
258
+ newState.version = patchVersion
259
+ const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
260
+ let decodeResult
261
+ try {
262
+ decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate ? mutation => { const index = mutation.syncAction.index?.toString(); mutationMap[index] = mutation } : () => { }, validateMacs)
263
+ } catch (err) {
264
+ if (isMissingKeyError(err)) throw err
265
+ logger?.warn({ name, version: patchVersion, error: err.message }, 'failed to decode patch, skipping')
266
+ continue
267
+ }
268
+ newState.hash = decodeResult.hash
269
+ newState.indexValueMap = decodeResult.indexValueMap
346
270
  if (validateMacs) {
347
- const base64Key = Buffer.from(keyId.id).toString('base64');
348
- const keyEnc = await getAppStateSyncKey(base64Key);
349
- if (!keyEnc) {
350
- throw new Boom(`failed to find key "${base64Key}" to decode mutation`, {
351
- statusCode: 404,
352
- data: { isMissingKey: true }
353
- });
354
- }
355
- const result = await mutationKeys(keyEnc.keyData);
356
- const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
271
+ const base64Key = Buffer.from(keyId.id).toString('base64')
272
+ const keyEnc = await getAppStateSyncKey(base64Key)
273
+ if (!keyEnc) throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } })
274
+ const result = mutationKeys(keyEnc.keyData)
275
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
357
276
  if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
358
- throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`);
277
+ logger?.warn({ name, version: newState.version }, 'LTHash verification failed, skipping remaining patches')
278
+ break
359
279
  }
360
280
  }
361
- syncd.mutations = [];
281
+ syncd.mutations = []
362
282
  }
363
- return { state: newState, mutationMap };
364
- };
283
+ return { state: newState, mutationMap }
284
+ }
365
285
 
366
286
  export const chatModificationToAppPatch = (mod, jid) => {
367
- const OP = proto.SyncdMutation.SyncdOperation;
287
+ const OP = proto.SyncdMutation.SyncdOperation
368
288
  const getMessageRange = (lastMessages) => {
369
- let messageRange;
289
+ let messageRange
370
290
  if (Array.isArray(lastMessages)) {
371
- const lastMsg = lastMessages[lastMessages.length - 1];
291
+ const lastMsg = lastMessages[lastMessages.length - 1]
372
292
  messageRange = {
373
293
  lastMessageTimestamp: lastMsg?.messageTimestamp,
374
- messages: lastMessages?.length
375
- ? lastMessages.map(m => {
376
- if (!m.key?.id || !m.key?.remoteJid) {
377
- throw new Boom('Incomplete key', { statusCode: 400, data: m });
378
- }
379
- if (isJidGroup(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) {
380
- throw new Boom('Expected not from me message to have participant', { statusCode: 400, data: m });
381
- }
382
- if (!m.messageTimestamp || !toNumber(m.messageTimestamp)) {
383
- throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m });
384
- }
385
- if (m.key.participant) {
386
- m.key.participant = jidNormalizedUser(m.key.participant);
387
- }
388
- return m;
389
- })
390
- : undefined
391
- };
392
- }
393
- else {
394
- messageRange = lastMessages;
294
+ messages: lastMessages?.length ? lastMessages.map(m => {
295
+ if (!m.key?.id || !m.key?.remoteJid) throw new Boom('Incomplete key', { statusCode: 400, data: m })
296
+ 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 })
297
+ if (!m.messageTimestamp || !toNumber(m.messageTimestamp)) throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m })
298
+ if (m.key.participant) m.key.participant = jidNormalizedUser(m.key.participant)
299
+ return m
300
+ }) : undefined
301
+ }
302
+ } else {
303
+ messageRange = lastMessages
395
304
  }
396
- return messageRange;
397
- };
398
- let patch;
399
- if ('mute' in mod) {
400
- patch = {
401
- syncAction: { muteAction: { muted: !!mod.mute, muteEndTimestamp: mod.mute || undefined } },
402
- index: ['mute', jid],
403
- type: 'regular_high',
404
- apiVersion: 2,
405
- operation: OP.SET
406
- };
407
- }
408
- else if ('archive' in mod) {
409
- patch = {
410
- syncAction: { archiveChatAction: { archived: !!mod.archive, messageRange: getMessageRange(mod.lastMessages) } },
411
- index: ['archive', jid],
412
- type: 'regular_low',
413
- apiVersion: 3,
414
- operation: OP.SET
415
- };
416
- }
417
- else if ('markRead' in mod) {
418
- patch = {
419
- syncAction: { markChatAsReadAction: { read: mod.markRead, messageRange: getMessageRange(mod.lastMessages) } },
420
- index: ['markChatAsRead', jid],
421
- type: 'regular_low',
422
- apiVersion: 3,
423
- operation: OP.SET
424
- };
425
- }
426
- else if ('deleteForMe' in mod) {
427
- const { timestamp, key, deleteMedia } = mod.deleteForMe;
428
- patch = {
429
- syncAction: { deleteMessageForMeAction: { deleteMedia, messageTimestamp: timestamp } },
430
- index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],
431
- type: 'regular_high',
432
- apiVersion: 3,
433
- operation: OP.SET
434
- };
435
- }
436
- else if ('clear' in mod) {
437
- patch = {
438
- syncAction: { clearChatAction: { messageRange: getMessageRange(mod.lastMessages) } },
439
- index: ['clearChat', jid, '1', '0'],
440
- type: 'regular_high',
441
- apiVersion: 6,
442
- operation: OP.SET
443
- };
444
- }
445
- else if ('pin' in mod) {
446
- patch = {
447
- syncAction: { pinAction: { pinned: !!mod.pin } },
448
- index: ['pin_v1', jid],
449
- type: 'regular_low',
450
- apiVersion: 5,
451
- operation: OP.SET
452
- };
453
- }
454
- else if ('contact' in mod) {
455
- patch = {
456
- syncAction: { contactAction: mod.contact || {} },
457
- index: ['contact', jid],
458
- type: 'critical_unblock_low',
459
- apiVersion: 2,
460
- operation: mod.contact ? OP.SET : OP.REMOVE
461
- };
305
+ return messageRange
462
306
  }
463
- else if ('disableLinkPreviews' in mod) {
464
- patch = {
465
- syncAction: { privacySettingDisableLinkPreviewsAction: mod.disableLinkPreviews || {} },
466
- index: ['setting_disableLinkPreviews'],
467
- type: 'regular',
468
- apiVersion: 8,
469
- operation: OP.SET
470
- };
471
- }
472
- else if ('star' in mod) {
473
- const key = mod.star.messages[0];
474
- patch = {
475
- syncAction: { starAction: { starred: !!mod.star.star } },
476
- index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'],
477
- type: 'regular_low',
478
- apiVersion: 2,
479
- operation: OP.SET
480
- };
481
- }
482
- else if ('delete' in mod) {
483
- patch = {
484
- syncAction: { deleteChatAction: { messageRange: getMessageRange(mod.lastMessages) } },
485
- index: ['deleteChat', jid, '1'],
486
- type: 'regular_high',
487
- apiVersion: 6,
488
- operation: OP.SET
489
- };
490
- }
491
- else if ('pushNameSetting' in mod) {
492
- patch = {
493
- syncAction: { pushNameSetting: { name: mod.pushNameSetting } },
494
- index: ['setting_pushName'],
495
- type: 'critical_block',
496
- apiVersion: 1,
497
- operation: OP.SET
498
- };
499
- }
500
- else if ('quickReply' in mod) {
501
- patch = {
502
- syncAction: {
503
- quickReplyAction: {
504
- count: 0,
505
- deleted: mod.quickReply.deleted || false,
506
- keywords: [],
507
- message: mod.quickReply.message || '',
508
- shortcut: mod.quickReply.shortcut || ''
509
- }
510
- },
511
- index: ['quick_reply', mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000))],
512
- type: 'regular',
513
- apiVersion: 2,
514
- operation: OP.SET
515
- };
516
- }
517
- else if ('addLabel' in mod) {
518
- patch = {
519
- syncAction: {
520
- labelEditAction: {
521
- name: mod.addLabel.name,
522
- color: mod.addLabel.color,
523
- predefinedId: mod.addLabel.predefinedId,
524
- deleted: mod.addLabel.deleted
525
- }
526
- },
527
- index: ['label_edit', mod.addLabel.id],
528
- type: 'regular',
529
- apiVersion: 3,
530
- operation: OP.SET
531
- };
532
- }
533
- else if ('addChatLabel' in mod) {
534
- patch = {
535
- syncAction: { labelAssociationAction: { labeled: true } },
536
- index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
537
- type: 'regular',
538
- apiVersion: 3,
539
- operation: OP.SET
540
- };
541
- }
542
- else if ('removeChatLabel' in mod) {
543
- patch = {
544
- syncAction: { labelAssociationAction: { labeled: false } },
545
- index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
546
- type: 'regular',
547
- apiVersion: 3,
548
- operation: OP.SET
549
- };
550
- }
551
- else if ('addMessageLabel' in mod) {
552
- patch = {
553
- syncAction: { labelAssociationAction: { labeled: true } },
554
- index: [LabelAssociationType.Message, mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId, '0', '0'],
555
- type: 'regular',
556
- apiVersion: 3,
557
- operation: OP.SET
558
- };
559
- }
560
- else if ('removeMessageLabel' in mod) {
561
- patch = {
562
- syncAction: { labelAssociationAction: { labeled: false } },
563
- index: [LabelAssociationType.Message, mod.removeMessageLabel.labelId, jid, mod.removeMessageLabel.messageId, '0', '0'],
564
- type: 'regular',
565
- apiVersion: 3,
566
- operation: OP.SET
567
- };
568
- }
569
- else {
570
- throw new Boom('not supported');
571
- }
572
- patch.syncAction.timestamp = Date.now();
573
- return patch;
574
- };
307
+ let patch
308
+ if ('mute' in mod) {
309
+ patch = { syncAction: { muteAction: { muted: !!mod.mute, muteEndTimestamp: mod.mute || undefined } }, index: ['mute', jid], type: 'regular_high', apiVersion: 2, operation: OP.SET }
310
+ } else if ('archive' in mod) {
311
+ patch = { syncAction: { archiveChatAction: { archived: !!mod.archive, messageRange: getMessageRange(mod.lastMessages) } }, index: ['archive', jid], type: 'regular_low', apiVersion: 3, operation: OP.SET }
312
+ } else if ('markRead' in mod) {
313
+ patch = { syncAction: { markChatAsReadAction: { read: mod.markRead, messageRange: getMessageRange(mod.lastMessages) } }, index: ['markChatAsRead', jid], type: 'regular_low', apiVersion: 3, operation: OP.SET }
314
+ } else if ('deleteForMe' in mod) {
315
+ const { timestamp, key, deleteMedia } = mod.deleteForMe
316
+ patch = { syncAction: { deleteMessageForMeAction: { deleteMedia, messageTimestamp: timestamp } }, index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'], type: 'regular_high', apiVersion: 3, operation: OP.SET }
317
+ } else if ('clear' in mod) {
318
+ patch = { syncAction: { clearChatAction: { messageRange: getMessageRange(mod.lastMessages) } }, index: ['clearChat', jid, '1', '0'], type: 'regular_high', apiVersion: 6, operation: OP.SET }
319
+ } else if ('pin' in mod) {
320
+ patch = { syncAction: { pinAction: { pinned: !!mod.pin } }, index: ['pin_v1', jid], type: 'regular_low', apiVersion: 5, operation: OP.SET }
321
+ } else if ('contact' in mod) {
322
+ patch = { syncAction: { contactAction: mod.contact || {} }, index: ['contact', jid], type: 'critical_unblock_low', apiVersion: 2, operation: mod.contact ? OP.SET : OP.REMOVE }
323
+ } else if ('disableLinkPreviews' in mod) {
324
+ patch = { syncAction: { privacySettingDisableLinkPreviewsAction: mod.disableLinkPreviews || {} }, index: ['setting_disableLinkPreviews'], type: 'regular', apiVersion: 8, operation: OP.SET }
325
+ } else if ('star' in mod) {
326
+ const key = mod.star.messages[0]
327
+ patch = { syncAction: { starAction: { starred: !!mod.star.star } }, index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'], type: 'regular_low', apiVersion: 2, operation: OP.SET }
328
+ } else if ('delete' in mod) {
329
+ patch = { syncAction: { deleteChatAction: { messageRange: getMessageRange(mod.lastMessages) } }, index: ['deleteChat', jid, '1'], type: 'regular_high', apiVersion: 6, operation: OP.SET }
330
+ } else if ('pushNameSetting' in mod) {
331
+ patch = { syncAction: { pushNameSetting: { name: mod.pushNameSetting } }, index: ['setting_pushName'], type: 'critical_block', apiVersion: 1, operation: OP.SET }
332
+ } else if ('quickReply' in mod) {
333
+ patch = { syncAction: { quickReplyAction: { count: 0, deleted: mod.quickReply.deleted || false, keywords: [], message: mod.quickReply.message || '', shortcut: mod.quickReply.shortcut || '' } }, index: ['quick_reply', mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000))], type: 'regular', apiVersion: 2, operation: OP.SET }
334
+ } else if ('addLabel' in mod) {
335
+ patch = { syncAction: { labelEditAction: { name: mod.addLabel.name, color: mod.addLabel.color, predefinedId: mod.addLabel.predefinedId, deleted: mod.addLabel.deleted } }, index: ['label_edit', mod.addLabel.id], type: 'regular', apiVersion: 3, operation: OP.SET }
336
+ } else if ('addChatLabel' in mod) {
337
+ patch = { syncAction: { labelAssociationAction: { labeled: true } }, index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid], type: 'regular', apiVersion: 3, operation: OP.SET }
338
+ } else if ('removeChatLabel' in mod) {
339
+ patch = { syncAction: { labelAssociationAction: { labeled: false } }, index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid], type: 'regular', apiVersion: 3, operation: OP.SET }
340
+ } else if ('addMessageLabel' in mod) {
341
+ patch = { syncAction: { labelAssociationAction: { labeled: true } }, index: [LabelAssociationType.Message, mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId, '0', '0'], type: 'regular', apiVersion: 3, operation: OP.SET }
342
+ } else if ('removeMessageLabel' in mod) {
343
+ patch = { syncAction: { labelAssociationAction: { labeled: false } }, index: [LabelAssociationType.Message, mod.removeMessageLabel.labelId, jid, mod.removeMessageLabel.messageId, '0', '0'], type: 'regular', apiVersion: 3, operation: OP.SET }
344
+ } else {
345
+ throw new Boom('not supported')
346
+ }
347
+ patch.syncAction.timestamp = Date.now()
348
+ return patch
349
+ }
575
350
 
576
351
  export const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
577
- const isInitialSync = !!initialSyncOpts;
578
- const accountSettings = initialSyncOpts?.accountSettings;
579
- logger?.trace({ syncAction, initialSync: !!initialSyncOpts }, 'processing sync action');
580
- const { syncAction: { value: action }, index: [type, id, msgId, fromMe] } = syncAction;
352
+ const isInitialSync = !!initialSyncOpts
353
+ const accountSettings = initialSyncOpts?.accountSettings
354
+ logger?.trace({ syncAction, initialSync: !!initialSyncOpts }, 'processing sync action')
355
+ const { syncAction: { value: action }, index: [type, id, msgId, fromMe] } = syncAction
581
356
  if (action?.muteAction) {
582
- ev.emit('chats.update', [{
583
- id,
584
- muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null,
585
- conditional: getChatUpdateConditional(id, undefined)
586
- }]);
587
- }
588
- else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
589
- const archiveAction = action?.archiveChatAction;
590
- const isArchived = archiveAction ? archiveAction.archived : type === 'archive';
591
- const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange;
592
- ev.emit('chats.update', [{
593
- id,
594
- archived: isArchived,
595
- conditional: getChatUpdateConditional(id, msgRange)
596
- }]);
597
- }
598
- else if (action?.markChatAsReadAction) {
599
- const markReadAction = action.markChatAsReadAction;
600
- const isNullUpdate = isInitialSync && markReadAction.read;
601
- ev.emit('chats.update', [{
602
- id,
603
- unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
604
- conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
605
- }]);
606
- }
607
- else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
608
- ev.emit('messages.delete', {
609
- keys: [{ remoteJid: id, id: msgId, fromMe: fromMe === '1' }]
610
- });
611
- }
612
- else if (action?.contactAction) {
357
+ ev.emit('chats.update', [{ id, muteEndTime: action.muteAction?.muted ? toNumber(action.muteAction.muteEndTimestamp) : null, conditional: getChatUpdateConditional(id, undefined) }])
358
+ } else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
359
+ const archiveAction = action?.archiveChatAction
360
+ const isArchived = archiveAction ? archiveAction.archived : type === 'archive'
361
+ const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange
362
+ ev.emit('chats.update', [{ id, archived: isArchived, conditional: getChatUpdateConditional(id, msgRange) }])
363
+ } else if (action?.markChatAsReadAction) {
364
+ const markReadAction = action.markChatAsReadAction
365
+ const isNullUpdate = isInitialSync && markReadAction.read
366
+ ev.emit('chats.update', [{ id, unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1, conditional: getChatUpdateConditional(id, markReadAction?.messageRange) }])
367
+ } else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
368
+ ev.emit('messages.delete', { keys: [{ remoteJid: id, id: msgId, fromMe: fromMe === '1' }] })
369
+ } else if (action?.contactAction) {
613
370
  const results = processContactAction(action.contactAction, id, logger)
614
371
  emitSyncActionResults(ev, results)
615
- }
616
- else if (action?.pushNameSetting) {
617
- const name = action?.pushNameSetting?.name;
618
- if (name && me?.name !== name) {
619
- ev.emit('creds.update', { me: { ...me, name } });
620
- }
621
- }
622
- else if (action?.pinAction) {
623
- ev.emit('chats.update', [{
624
- id,
625
- pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
626
- conditional: getChatUpdateConditional(id, undefined)
627
- }]);
628
- }
629
- else if (action?.unarchiveChatsSetting) {
630
- const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats;
631
- ev.emit('creds.update', { accountSettings: { unarchiveChats } });
632
- logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`);
633
- if (accountSettings) {
634
- accountSettings.unarchiveChats = unarchiveChats;
635
- }
636
- }
637
- else if (action?.starAction || type === 'star') {
638
- let starred = action?.starAction?.starred;
639
- if (typeof starred !== 'boolean') {
640
- starred = syncAction.index[syncAction.index.length - 1] === '1';
641
- }
642
- ev.emit('messages.update', [{
643
- key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' },
644
- update: { starred }
645
- }]);
646
- }
647
- else if (action?.deleteChatAction || type === 'deleteChat') {
648
- if (!isInitialSync) {
649
- ev.emit('chats.delete', [id]);
650
- }
651
- }
652
- else if (action?.labelEditAction) {
653
- const { name, color, deleted, predefinedId } = action.labelEditAction;
654
- ev.emit('labels.edit', {
655
- id: id,
656
- name: name,
657
- color: color,
658
- deleted: deleted,
659
- predefinedId: predefinedId ? String(predefinedId) : undefined
660
- });
661
- }
662
- else if (action?.labelAssociationAction) {
663
- ev.emit('labels.association', {
664
- type: action.labelAssociationAction.labeled ? 'add' : 'remove',
665
- association: type === LabelAssociationType.Chat
666
- ? { type: LabelAssociationType.Chat, chatId: syncAction.index[2], labelId: syncAction.index[1] }
667
- : { type: LabelAssociationType.Message, chatId: syncAction.index[2], messageId: syncAction.index[3], labelId: syncAction.index[1] }
668
- });
669
- }
670
- else if (action?.localeSetting?.locale) {
372
+ } else if (action?.pushNameSetting) {
373
+ const name = action?.pushNameSetting?.name
374
+ if (name && me?.name !== name) ev.emit('creds.update', { me: { ...me, name } })
375
+ } else if (action?.pinAction) {
376
+ ev.emit('chats.update', [{ id, pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null, conditional: getChatUpdateConditional(id, undefined) }])
377
+ } else if (action?.unarchiveChatsSetting) {
378
+ const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats
379
+ ev.emit('creds.update', { accountSettings: { unarchiveChats } })
380
+ logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`)
381
+ if (accountSettings) accountSettings.unarchiveChats = unarchiveChats
382
+ } else if (action?.starAction || type === 'star') {
383
+ let starred = action?.starAction?.starred
384
+ if (typeof starred !== 'boolean') starred = syncAction.index[syncAction.index.length - 1] === '1'
385
+ ev.emit('messages.update', [{ key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' }, update: { starred } }])
386
+ } else if (action?.deleteChatAction || type === 'deleteChat') {
387
+ if (!isInitialSync) ev.emit('chats.delete', [id])
388
+ } else if (action?.labelEditAction) {
389
+ const { name, color, deleted, predefinedId } = action.labelEditAction
390
+ ev.emit('labels.edit', { id: id, name: name, color: color, deleted: deleted, predefinedId: predefinedId ? String(predefinedId) : undefined })
391
+ } else if (action?.labelAssociationAction) {
392
+ 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] } : { type: LabelAssociationType.Message, chatId: syncAction.index[2], messageId: syncAction.index[3], labelId: syncAction.index[1] } })
393
+ } else if (action?.localeSetting?.locale) {
671
394
  ev.emit('settings.update', { setting: 'locale', value: action.localeSetting.locale })
672
- }
673
- else if (action?.timeFormatAction) {
395
+ } else if (action?.timeFormatAction) {
674
396
  ev.emit('settings.update', { setting: 'timeFormat', value: action.timeFormatAction })
675
- }
676
- else if (action?.pnForLidChatAction) {
397
+ } else if (action?.pnForLidChatAction) {
677
398
  if (action.pnForLidChatAction.pnJid) ev.emit('lid-mapping.update', { lid: id, pn: action.pnForLidChatAction.pnJid })
678
- }
679
- else if (action?.privacySettingRelayAllCalls) {
399
+ } else if (action?.privacySettingRelayAllCalls) {
680
400
  ev.emit('settings.update', { setting: 'privacySettingRelayAllCalls', value: action.privacySettingRelayAllCalls })
681
- }
682
- else if (action?.statusPrivacy) {
401
+ } else if (action?.statusPrivacy) {
683
402
  ev.emit('settings.update', { setting: 'statusPrivacy', value: action.statusPrivacy })
684
- }
685
- else if (action?.lockChatAction) {
686
- ev.emit('chats.lock', { id, locked: !!action.lockChatAction.locked })
687
- }
688
- else if (action?.privacySettingDisableLinkPreviewsAction) {
403
+ } else if (action?.lockChatAction) {
404
+ ev.emit('chats.lock', { id: id, locked: !!action.lockChatAction.locked })
405
+ } else if (action?.privacySettingDisableLinkPreviewsAction) {
689
406
  ev.emit('settings.update', { setting: 'disableLinkPreviews', value: action.privacySettingDisableLinkPreviewsAction })
690
- }
691
- else if (action?.notificationActivitySettingAction?.notificationActivitySetting) {
407
+ } else if (action?.notificationActivitySettingAction?.notificationActivitySetting) {
692
408
  ev.emit('settings.update', { setting: 'notificationActivitySetting', value: action.notificationActivitySettingAction.notificationActivitySetting })
693
- }
694
- else if (action?.lidContactAction) {
695
- ev.emit('contacts.upsert', [{
696
- id,
697
- name: action.lidContactAction.fullName || action.lidContactAction.firstName || action.lidContactAction.username || undefined,
698
- username: action.lidContactAction.username || undefined,
699
- lid: id,
700
- phoneNumber: undefined
701
- }])
702
- }
703
- else if (action?.privacySettingChannelsPersonalisedRecommendationAction) {
409
+ } else if (action?.lidContactAction) {
410
+ ev.emit('contacts.upsert', [{ id, name: action.lidContactAction.fullName || action.lidContactAction.firstName || action.lidContactAction.username || undefined, username: action.lidContactAction.username || undefined, lid: id, phoneNumber: undefined }])
411
+ } else if (action?.privacySettingChannelsPersonalisedRecommendationAction) {
704
412
  ev.emit('settings.update', { setting: 'channelsPersonalisedRecommendation', value: action.privacySettingChannelsPersonalisedRecommendationAction })
705
- }
706
- else {
707
- logger?.debug({ syncAction, id }, 'unprocessable update');
413
+ } else {
414
+ logger?.debug({ syncAction, id }, 'unprocessable update')
708
415
  }
709
416
 
710
417
  function getChatUpdateConditional(id, msgRange) {
711
- return isInitialSync
712
- ? data => {
713
- const chat = data.historySets.chats[id] || data.chatUpserts[id];
714
- if (chat) {
715
- return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true;
716
- }
717
- }
718
- : undefined;
418
+ return isInitialSync ? data => { const chat = data.historySets.chats[id] || data.chatUpserts[id]; if (chat) return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true } : undefined
719
419
  }
720
420
 
721
421
  function isValidPatchBasedOnMessageRange(chat, msgRange) {
722
- const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0);
723
- const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0);
724
- return lastMsgTimestamp >= chatLastMsgTimestamp;
422
+ const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0)
423
+ const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0)
424
+ return lastMsgTimestamp >= chatLastMsgTimestamp
725
425
  }
726
- };
727
- //# sourceMappingURL=chat-utils.js.map
426
+ }