@nexustechpro/baileys 2.0.6 → 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.
- package/README.md +72 -1
- package/lib/Defaults/index.js +3 -2
- package/lib/Signal/libsignal.js +552 -358
- package/lib/Socket/chats.js +192 -273
- package/lib/Socket/messages-recv.js +4 -2
- package/lib/Socket/messages-send.js +42 -18
- package/lib/Socket/newsletter.js +87 -36
- package/lib/Socket/nexus-handler.js +44 -42
- package/lib/Socket/socket.js +23 -10
- package/lib/Store/make-in-memory-store.js +29 -20
- package/lib/Utils/auth-utils.js +2 -2
- package/lib/Utils/chat-utils.js +319 -620
- package/lib/Utils/decode-wa-message.js +11 -26
- package/lib/Utils/key-store.js +1 -1
- package/lib/Utils/link-preview.js +134 -71
- package/lib/Utils/messages-media.js +97 -26
- package/lib/Utils/messages.js +723 -820
- package/lib/Utils/use-multi-file-auth-state.js +183 -90
- package/lib/index.js +1 -1
- package/package.json +10 -8
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -1,727 +1,426 @@
|
|
|
1
|
-
import { Boom } from '@hapi/boom'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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,
|
|
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 =
|
|
12
|
-
const
|
|
13
|
-
return {
|
|
14
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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:
|
|
75
|
-
const
|
|
76
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
export const
|
|
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
|
-
|
|
117
|
-
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
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
|
|
169
|
-
|
|
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 =
|
|
176
|
-
const encContent = content.
|
|
177
|
-
const ogValueMac = content.
|
|
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
|
-
|
|
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
|
|
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
|
|
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`, {
|
|
211
|
-
const keys =
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
:
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
patch = {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
patch = {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
else
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
else if (action?.
|
|
589
|
-
const
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
else if (action?.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
686
|
-
|
|
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
|
-
|
|
695
|
-
|
|
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
|
-
|
|
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
|
+
}
|