@pontalabs/baileys 1.0.11 → 1.1.0
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 +96 -85
- package/lib/Defaults/connection.js +39 -0
- package/lib/Defaults/constants.js +50 -0
- package/lib/Defaults/history.js +13 -0
- package/lib/Defaults/index.js +36 -151
- package/lib/Defaults/media.js +43 -0
- package/lib/Defaults/phonenumber-mcc.json +221 -221
- package/lib/Defaults/prefix.js +12 -0
- package/lib/Defaults/vialeys-version.json +7 -0
- package/lib/Signal/Group/ciphertext-message.js +10 -15
- package/lib/Signal/Group/group-session-builder.js +34 -60
- package/lib/Signal/Group/group_cipher.js +69 -76
- package/lib/Signal/Group/index.js +41 -60
- package/lib/Signal/Group/keyhelper.js +12 -57
- package/lib/Signal/Group/sender-chain-key.js +20 -33
- package/lib/Signal/Group/sender-key-distribution-message.js +43 -48
- package/lib/Signal/Group/sender-key-message.js +58 -53
- package/lib/Signal/Group/sender-key-name.js +21 -31
- package/lib/Signal/Group/sender-key-record.js +21 -34
- package/lib/Signal/Group/sender-key-state.js +59 -106
- package/lib/Signal/Group/sender-message-key.js +16 -21
- package/lib/Signal/libsignal.js +282 -203
- package/lib/Signal/lid-mapping.js +164 -184
- package/lib/Socket/Client/index.js +31 -22
- package/lib/Socket/Client/types.js +9 -15
- package/lib/Socket/Client/websocket.js +42 -37
- package/lib/Socket/business.js +212 -336
- package/lib/Socket/chats.js +696 -940
- package/lib/Socket/community.js +454 -369
- package/lib/Socket/groups.js +278 -280
- package/lib/Socket/index.js +9 -21
- package/lib/Socket/messages-recv.js +1027 -1570
- package/lib/Socket/messages-send.js +1180 -1136
- package/lib/Socket/mex.js +59 -0
- package/lib/Socket/newsletter.js +193 -251
- package/lib/Socket/socket.js +758 -898
- package/lib/Store/index.js +35 -24
- package/lib/Store/make-cache-manager-store.js +53 -66
- package/lib/Store/make-in-memory-store.js +319 -325
- package/lib/Store/make-ordered-dictionary.js +49 -57
- package/lib/Store/object-repository.js +10 -15
- package/lib/Types/Auth.js +2 -3
- package/lib/Types/Bussines.js +2 -3
- package/lib/Types/Call.js +2 -3
- package/lib/Types/Chat.js +10 -9
- package/lib/Types/Contact.js +2 -3
- package/lib/Types/Events.js +2 -3
- package/lib/Types/GroupMetadata.js +2 -3
- package/lib/Types/Label.js +6 -12
- package/lib/Types/LabelAssociation.js +4 -13
- package/lib/Types/Message.js +10 -18
- package/lib/Types/MexUpdates.js +15 -18
- package/lib/Types/Newsletter.js +26 -34
- package/lib/Types/Product.js +2 -3
- package/lib/Types/Signal.js +2 -3
- package/lib/Types/Socket.js +2 -3
- package/lib/Types/State.js +8 -13
- package/lib/Types/USync.js +2 -3
- package/lib/Types/index.js +49 -40
- package/lib/Utils/auth-utils.js +190 -495
- package/lib/Utils/browser-utils.js +29 -0
- package/lib/Utils/business.js +177 -199
- package/lib/Utils/chat-utils.js +711 -624
- package/lib/Utils/crypto.js +116 -161
- package/lib/Utils/decode-wa-message.js +225 -321
- package/lib/Utils/event-buffer.js +363 -405
- package/lib/Utils/generics.js +318 -472
- package/lib/Utils/history.js +72 -76
- package/lib/Utils/index.js +49 -41
- package/lib/Utils/link-preview.js +59 -103
- package/lib/Utils/logger.js +4 -7
- package/lib/Utils/lt-hash.js +29 -42
- package/lib/Utils/make-mutex.js +26 -42
- package/lib/Utils/message-retry-manager.js +134 -160
- package/lib/Utils/messages-media.js +665 -731
- package/lib/Utils/messages.js +518 -1
- package/lib/Utils/noise-handler.js +127 -125
- package/lib/Utils/pre-key-manager.js +86 -0
- package/lib/Utils/process-message.js +739 -351
- package/lib/Utils/signal.js +146 -129
- package/lib/Utils/use-multi-file-auth-state.js +68 -135
- package/lib/Utils/validate-connection.js +183 -142
- package/lib/Utils/vialeys-event-stream.js +41 -0
- package/lib/WABinary/constants.js +1281 -1289
- package/lib/WABinary/decode.js +145 -200
- package/lib/WABinary/encode.js +149 -202
- package/lib/WABinary/generic-utils.js +79 -97
- package/lib/WABinary/index.js +36 -25
- package/lib/WABinary/jid-utils.js +91 -123
- package/lib/WABinary/types.js +2 -3
- package/lib/WAM/BinaryInfo.js +8 -13
- package/lib/WAM/constants.js +16584 -11464
- package/lib/WAM/encode.js +117 -133
- package/lib/WAM/index.js +34 -23
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +62 -69
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +11 -25
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +32 -44
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +13 -23
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +24 -38
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +17 -31
- package/lib/WAUSync/Protocols/index.js +37 -26
- package/lib/WAUSync/USyncQuery.js +66 -66
- package/lib/WAUSync/USyncUser.js +13 -18
- package/lib/WAUSync/index.js +34 -23
- package/lib/index.js +95 -28
- package/package.json +2 -1
- package/lib/Defaults/baileys-version.json +0 -3
- package/lib/Signal/Group/queue-job.d.ts +0 -1
- package/lib/Signal/Group/queue-job.js +0 -57
- package/lib/Socket/usync.d.ts +0 -37
- package/lib/Socket/usync.js +0 -83
- package/lib/Utils/baileys-event-stream.d.ts +0 -18
- package/lib/Utils/baileys-event-stream.js +0 -70
- package/lib/Utils/signal-housekeeping.d.ts +0 -19
- package/lib/Utils/signal-housekeeping.js +0 -73
- package/lib/Utils/use-mongo-file-auth-state.d.ts +0 -6
- package/lib/Utils/use-mongo-file-auth-state.js +0 -84
- package/lib/Utils/use-single-file-auth-state.d.ts +0 -13
- package/lib/Utils/use-single-file-auth-state.js +0 -80
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -1,561 +1,256 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const crypto_2 = require("./crypto")
|
|
14
|
-
const generics_1 = require("./generics")
|
|
15
|
-
const mutex_1 = require("async-mutex")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// Cache bypass for signal-sensitive keys so runtime state doesn't keep stale
|
|
19
|
-
// session/pre-key material alive after decrypt failures or identity rotation.
|
|
20
|
-
const NO_CACHE_TYPES = new Set([
|
|
21
|
-
'pre-key',
|
|
22
|
-
'session',
|
|
23
|
-
'signed-pre-key',
|
|
24
|
-
'identity-key'
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Adds caching capability to a SignalKeyStore
|
|
29
|
-
* @param store the store to add caching to
|
|
30
|
-
* @param logger to log trace events
|
|
31
|
-
* @param _cache cache store to use
|
|
32
|
-
*/
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const { randomBytes: randomBytes } = require("crypto");
|
|
4
|
+
const { default: PQueue } = require("p-queue");
|
|
5
|
+
const { default: NodeCache } = require("@cacheable/node-cache");
|
|
6
|
+
const { DEFAULT_CACHE_TTLS: DEFAULT_CACHE_TTLS } = require("../Defaults/constants");
|
|
7
|
+
const { AsyncLocalStorage: AsyncLocalStorage } = require("async_hooks");
|
|
8
|
+
const { LRUCache: LRUCache } = require("lru-cache");
|
|
9
|
+
const { Mutex: Mutex } = require("async-mutex");
|
|
10
|
+
const { Curve: Curve, signedKeyPair: signedKeyPair } = require("./crypto");
|
|
11
|
+
const { delay: delay, generateRegistrationId: generateRegistrationId } = require("./generics");
|
|
12
|
+
const { PreKeyManager: PreKeyManager } = require("./pre-key-manager");
|
|
33
13
|
function makeCacheableSignalKeyStore(store, logger, _cache) {
|
|
34
|
-
const cache =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const cacheMutex = new
|
|
42
|
-
|
|
14
|
+
const cache =
|
|
15
|
+
_cache ||
|
|
16
|
+
new NodeCache({
|
|
17
|
+
stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE,
|
|
18
|
+
useClones: false,
|
|
19
|
+
deleteOnExpire: true,
|
|
20
|
+
});
|
|
21
|
+
const cacheMutex = new Mutex();
|
|
43
22
|
function getUniqueId(type, id) {
|
|
44
|
-
return `${type}.${id}
|
|
23
|
+
return `${type}.${id}`;
|
|
45
24
|
}
|
|
46
25
|
return {
|
|
47
26
|
async get(type, ids) {
|
|
48
|
-
if (NO_CACHE_TYPES.has(type)) {
|
|
49
|
-
logger?.trace({ type, items: ids.length }, 'bypassing cache for sensitive key type')
|
|
50
|
-
return await store.get(type, ids)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
27
|
return cacheMutex.runExclusive(async () => {
|
|
54
|
-
const data = {}
|
|
55
|
-
const idsToFetch = []
|
|
28
|
+
const data = {};
|
|
29
|
+
const idsToFetch = [];
|
|
56
30
|
for (const id of ids) {
|
|
57
|
-
const item = cache.get(getUniqueId(type, id))
|
|
58
|
-
if (typeof item !==
|
|
59
|
-
data[id] = item
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
idsToFetch.push(id)
|
|
31
|
+
const item = cache.get(getUniqueId(type, id));
|
|
32
|
+
if (typeof item !== "undefined") {
|
|
33
|
+
data[id] = item;
|
|
34
|
+
} else {
|
|
35
|
+
idsToFetch.push(id);
|
|
63
36
|
}
|
|
64
37
|
}
|
|
65
38
|
if (idsToFetch.length) {
|
|
66
|
-
logger?.trace({ items: idsToFetch.length },
|
|
67
|
-
const fetched = await store.get(type, idsToFetch)
|
|
39
|
+
logger?.trace({ items: idsToFetch.length }, "loading from store");
|
|
40
|
+
const fetched = await store.get(type, idsToFetch);
|
|
68
41
|
for (const id of idsToFetch) {
|
|
69
|
-
const item = fetched[id]
|
|
42
|
+
const item = fetched[id];
|
|
70
43
|
if (item) {
|
|
71
|
-
data[id] = item
|
|
72
|
-
cache.set(getUniqueId(type, id), item)
|
|
44
|
+
data[id] = item;
|
|
45
|
+
cache.set(getUniqueId(type, id), item);
|
|
73
46
|
}
|
|
74
47
|
}
|
|
75
48
|
}
|
|
76
|
-
return data
|
|
77
|
-
})
|
|
49
|
+
return data;
|
|
50
|
+
});
|
|
78
51
|
},
|
|
79
52
|
async set(data) {
|
|
80
53
|
return cacheMutex.runExclusive(async () => {
|
|
81
|
-
let keys = 0
|
|
54
|
+
let keys = 0;
|
|
82
55
|
for (const type in data) {
|
|
83
|
-
if (NO_CACHE_TYPES.has(type)) {
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
56
|
for (const id in data[type]) {
|
|
87
|
-
await cache.set(getUniqueId(type, id), data[type][id])
|
|
88
|
-
keys += 1
|
|
57
|
+
await cache.set(getUniqueId(type, id), data[type][id]);
|
|
58
|
+
keys += 1;
|
|
89
59
|
}
|
|
90
60
|
}
|
|
91
|
-
logger?.trace({ keys },
|
|
92
|
-
await store.set(data)
|
|
93
|
-
})
|
|
61
|
+
logger?.trace({ keys: keys }, "updated cache");
|
|
62
|
+
await store.set(data);
|
|
63
|
+
});
|
|
94
64
|
},
|
|
95
65
|
async clear() {
|
|
96
|
-
await cache.flushAll()
|
|
97
|
-
await store.clear?.call(store)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Handles pre-key operations.
|
|
104
|
-
*
|
|
105
|
-
* IMPORTANT: locking for this function is provided by the CALLER via the
|
|
106
|
-
* session-scoped `getKeyTypeMutex(keyType)` mutex (the same one used by
|
|
107
|
-
* get()/set() for that key type). We intentionally do NOT acquire our own
|
|
108
|
-
* mutex here anymore.
|
|
109
|
-
*
|
|
110
|
-
* Previously this used module-level singleton mutexes (`preKeyMutex`,
|
|
111
|
-
* `signedPreKeyMutex`) declared OUTSIDE `addTransactionCapability`. Because
|
|
112
|
-
* those were created once per process (not once per auth state / socket),
|
|
113
|
-
* every session running in the same Node process shared the exact same
|
|
114
|
-
* lock. That caused two problems:
|
|
115
|
-
* 1. Unrelated sessions serialized on each other's pre-key writes
|
|
116
|
-
* (needless contention/latency when running multiple accounts).
|
|
117
|
-
* 2. The lock domain didn't match the one used by get()/withMutexes()
|
|
118
|
-
* (`getKeyTypeMutex`), so reads/writes and deletion-validation could
|
|
119
|
-
* interleave without real mutual exclusion (TOCTOU race on the
|
|
120
|
-
* "does this key still exist" check), which is what let pre-key /
|
|
121
|
-
* signed-pre-key state get corrupted under concurrent access.
|
|
122
|
-
*/
|
|
123
|
-
async function handlePreKeyOperations(data, keyType, transactionCache, mutations, logger, isInTransaction, state) {
|
|
124
|
-
const keyData = data[keyType]
|
|
125
|
-
if (!keyData)
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
// Ensure structures exist
|
|
129
|
-
transactionCache[keyType] = transactionCache[keyType] || {}
|
|
130
|
-
mutations[keyType] = mutations[keyType] || {}
|
|
131
|
-
|
|
132
|
-
// Separate deletions from updates for batch processing
|
|
133
|
-
const deletionKeys = []
|
|
134
|
-
const updateKeys = []
|
|
135
|
-
|
|
136
|
-
for (const keyId in keyData) {
|
|
137
|
-
if (keyData[keyId] === null) {
|
|
138
|
-
deletionKeys.push(keyId)
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
updateKeys.push(keyId)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Process updates first (no validation needed)
|
|
146
|
-
for (const keyId of updateKeys) {
|
|
147
|
-
if (transactionCache[keyType]) {
|
|
148
|
-
transactionCache[keyType][keyId] = keyData[keyId]
|
|
149
|
-
}
|
|
150
|
-
if (mutations[keyType]) {
|
|
151
|
-
mutations[keyType][keyId] = keyData[keyId]
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Process deletions with validation
|
|
156
|
-
if (deletionKeys.length === 0)
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
if (isInTransaction) {
|
|
160
|
-
// In transaction, only allow deletion if key exists in cache
|
|
161
|
-
for (const keyId of deletionKeys) {
|
|
162
|
-
if (transactionCache[keyType]) {
|
|
163
|
-
transactionCache[keyType][keyId] = null
|
|
164
|
-
if (mutations[keyType]) {
|
|
165
|
-
// Mark for deletion in mutations
|
|
166
|
-
mutations[keyType][keyId] = null
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
logger.warn(`Skipping deletion of non-existent ${keyType} in transaction: ${keyId}`)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Outside transaction, batch validate all deletions
|
|
177
|
-
if (!state)
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
const existingKeys = await state.get(keyType, deletionKeys)
|
|
181
|
-
for (const keyId of deletionKeys) {
|
|
182
|
-
if (existingKeys[keyId]) {
|
|
183
|
-
if (transactionCache[keyType])
|
|
184
|
-
transactionCache[keyType][keyId] = null
|
|
185
|
-
if (mutations[keyType])
|
|
186
|
-
mutations[keyType][keyId] = null
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Handles normal key operations for transactions
|
|
196
|
-
*/
|
|
197
|
-
function handleNormalKeyOperations(data, key, transactionCache, mutations) {
|
|
198
|
-
Object.assign(transactionCache[key], data[key])
|
|
199
|
-
mutations[key] = mutations[key] || {}
|
|
200
|
-
Object.assign(mutations[key], data[key])
|
|
66
|
+
await cache.flushAll();
|
|
67
|
+
await store.clear?.call(store);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
201
70
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const keyData = data[keyType]
|
|
216
|
-
if (!keyData)
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
// Validate deletions
|
|
220
|
-
const deletionIds = Object.keys(keyData).filter(id => keyData[id] === null)
|
|
221
|
-
if (deletionIds.length === 0)
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
const existingKeys = await state.get(keyType, deletionIds)
|
|
225
|
-
for (const keyId of deletionIds) {
|
|
226
|
-
if (!existingKeys[keyId]) {
|
|
227
|
-
logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`)
|
|
228
|
-
if (data[keyType])
|
|
229
|
-
delete data[keyType][keyId]
|
|
71
|
+
const addTransactionCapability = (
|
|
72
|
+
state,
|
|
73
|
+
logger,
|
|
74
|
+
{ maxCommitRetries: maxCommitRetries, delayBetweenTriesMs: delayBetweenTriesMs }
|
|
75
|
+
) => {
|
|
76
|
+
const txStorage = new AsyncLocalStorage();
|
|
77
|
+
const keyQueues = new Map();
|
|
78
|
+
const txMutexes = new Map();
|
|
79
|
+
const txMutexRefCounts = new Map();
|
|
80
|
+
const preKeyManager = new PreKeyManager(state, logger);
|
|
81
|
+
function getQueue(key) {
|
|
82
|
+
if (!keyQueues.has(key)) {
|
|
83
|
+
keyQueues.set(key, new PQueue({ concurrency: 1 }));
|
|
230
84
|
}
|
|
85
|
+
return keyQueues.get(key);
|
|
231
86
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
* Uses async-mutex's runExclusive with efficient batching
|
|
237
|
-
*/
|
|
238
|
-
async function withMutexes(keyTypes, getKeyTypeMutex, fn) {
|
|
239
|
-
if (keyTypes.length === 0) {
|
|
240
|
-
return fn()
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (keyTypes.length === 1) {
|
|
244
|
-
return getKeyTypeMutex(keyTypes[0]).runExclusive(fn)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// For multiple mutexes, sort by key type to prevent deadlocks
|
|
248
|
-
// Then acquire all mutexes in order using Promise.all for better efficiency
|
|
249
|
-
const sortedKeyTypes = [...keyTypes].sort()
|
|
250
|
-
const mutexes = sortedKeyTypes.map(getKeyTypeMutex)
|
|
251
|
-
|
|
252
|
-
// Acquire all mutexes in order to prevent deadlocks
|
|
253
|
-
const releases = []
|
|
254
|
-
try {
|
|
255
|
-
for (const mutex of mutexes) {
|
|
256
|
-
releases.push(await mutex.acquire())
|
|
87
|
+
function getTxMutex(key) {
|
|
88
|
+
if (!txMutexes.has(key)) {
|
|
89
|
+
txMutexes.set(key, new Mutex());
|
|
90
|
+
txMutexRefCounts.set(key, 0);
|
|
257
91
|
}
|
|
258
|
-
return
|
|
92
|
+
return txMutexes.get(key);
|
|
259
93
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const release = releases.pop()
|
|
264
|
-
if (release)
|
|
265
|
-
release()
|
|
266
|
-
}
|
|
94
|
+
function acquireTxMutexRef(key) {
|
|
95
|
+
const count = txMutexRefCounts.get(key) ?? 0;
|
|
96
|
+
txMutexRefCounts.set(key, count + 1);
|
|
267
97
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs }) => {
|
|
278
|
-
// number of queries made to the DB during the transaction
|
|
279
|
-
// only there for logging purposes
|
|
280
|
-
let dbQueriesInTransaction = 0
|
|
281
|
-
let transactionCache = {}
|
|
282
|
-
let mutations = {}
|
|
283
|
-
|
|
284
|
-
// LRU Cache to hold mutexes for different key types
|
|
285
|
-
const mutexCache = new LRUCache_1.LRUCache({
|
|
286
|
-
ttl: 60 * 60 * 1000, // 1 hour
|
|
287
|
-
ttlAutopurge: true,
|
|
288
|
-
updateAgeOnGet: true
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
let transactionsInProgress = 0
|
|
292
|
-
|
|
293
|
-
function getKeyTypeMutex(type) {
|
|
294
|
-
return getMutex(`keytype:${type}`)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function getSenderKeyMutex(senderKeyName) {
|
|
298
|
-
return getMutex(`senderkey:${senderKeyName}`)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function getTransactionMutex(key) {
|
|
302
|
-
return getMutex(`transaction:${key}`)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Get or create a mutex for a specific key name
|
|
306
|
-
function getMutex(key) {
|
|
307
|
-
let mutex = mutexCache.get(key)
|
|
308
|
-
if (!mutex) {
|
|
309
|
-
mutex = new mutex_1.Mutex()
|
|
310
|
-
mutexCache.set(key, mutex)
|
|
311
|
-
logger.info({ key }, 'created new mutex')
|
|
98
|
+
function releaseTxMutexRef(key) {
|
|
99
|
+
const count = (txMutexRefCounts.get(key) ?? 1) - 1;
|
|
100
|
+
txMutexRefCounts.set(key, count);
|
|
101
|
+
if (count <= 0) {
|
|
102
|
+
const mutex = txMutexes.get(key);
|
|
103
|
+
if (mutex && !mutex.isLocked()) {
|
|
104
|
+
txMutexes.delete(key);
|
|
105
|
+
txMutexRefCounts.delete(key);
|
|
106
|
+
}
|
|
312
107
|
}
|
|
313
|
-
return mutex
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Sender key operations with proper mutex sequencing
|
|
317
|
-
function queueSenderKeyOperation(senderKeyName, operation) {
|
|
318
|
-
return getSenderKeyMutex(senderKeyName).runExclusive(operation)
|
|
319
108
|
}
|
|
320
|
-
|
|
321
|
-
// Check if we are currently in a transaction
|
|
322
109
|
function isInTransaction() {
|
|
323
|
-
return
|
|
110
|
+
return !!txStorage.getStore();
|
|
324
111
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
logger.trace('no mutations in transaction')
|
|
330
|
-
return
|
|
112
|
+
async function commitWithRetry(mutations) {
|
|
113
|
+
if (Object.keys(mutations).length === 0) {
|
|
114
|
+
logger.trace("no mutations in transaction");
|
|
115
|
+
return;
|
|
331
116
|
}
|
|
332
|
-
logger.trace(
|
|
333
|
-
let
|
|
334
|
-
while (tries > 0) {
|
|
335
|
-
tries -= 1;
|
|
117
|
+
logger.trace("committing transaction");
|
|
118
|
+
for (let attempt = 0; attempt < maxCommitRetries; attempt++) {
|
|
336
119
|
try {
|
|
337
|
-
await state.set(mutations)
|
|
338
|
-
logger.trace(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
120
|
+
await state.set(mutations);
|
|
121
|
+
logger.trace(
|
|
122
|
+
{ mutationCount: Object.keys(mutations).length },
|
|
123
|
+
"committed transaction"
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const retriesLeft = maxCommitRetries - attempt - 1;
|
|
128
|
+
logger.warn(`failed to commit mutations, retries left=${retriesLeft}`);
|
|
129
|
+
if (retriesLeft === 0) {
|
|
130
|
+
throw error;
|
|
345
131
|
}
|
|
132
|
+
await delay(delayBetweenTriesMs);
|
|
346
133
|
}
|
|
347
134
|
}
|
|
348
135
|
}
|
|
349
|
-
|
|
350
|
-
// Helper function to clean up transaction state
|
|
351
|
-
function cleanupTransactionState() {
|
|
352
|
-
transactionsInProgress -= 1
|
|
353
|
-
if (transactionsInProgress === 0) {
|
|
354
|
-
transactionCache = {}
|
|
355
|
-
mutations = {}
|
|
356
|
-
dbQueriesInTransaction = 0
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Helper function to execute work within transaction
|
|
361
|
-
async function executeTransactionWork(work) {
|
|
362
|
-
const result = await work()
|
|
363
|
-
// commit if this is the outermost transaction
|
|
364
|
-
if (transactionsInProgress === 1) {
|
|
365
|
-
await commitTransaction()
|
|
366
|
-
}
|
|
367
|
-
return result
|
|
368
|
-
}
|
|
369
|
-
|
|
370
136
|
return {
|
|
371
137
|
get: async (type, ids) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// only fetch if there are any items to fetch
|
|
377
|
-
if (idsRequiringFetch.length) {
|
|
378
|
-
dbQueriesInTransaction += 1
|
|
379
|
-
|
|
380
|
-
// Use per-sender-key queue for sender-key operations when possible
|
|
381
|
-
if (type === 'sender-key') {
|
|
382
|
-
logger.info({ idsRequiringFetch }, 'processing sender keys in transaction')
|
|
383
|
-
// For sender keys, process each one with queued operations to maintain serialization
|
|
384
|
-
for (const senderKeyName of idsRequiringFetch) {
|
|
385
|
-
await queueSenderKeyOperation(senderKeyName, async () => {
|
|
386
|
-
logger.info({ senderKeyName }, 'fetching sender key in transaction')
|
|
387
|
-
const result = await state.get(type, [senderKeyName])
|
|
388
|
-
// Update transaction cache
|
|
389
|
-
transactionCache[type] || (transactionCache[type] = {})
|
|
390
|
-
Object.assign(transactionCache[type], result)
|
|
391
|
-
logger.info({ senderKeyName, hasResult: !!result[senderKeyName] }, 'sender key fetch complete')
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
// Use runExclusive for cleaner mutex handling
|
|
397
|
-
await getKeyTypeMutex(type).runExclusive(async () => {
|
|
398
|
-
const result = await state.get(type, idsRequiringFetch)
|
|
399
|
-
// Update transaction cache
|
|
400
|
-
transactionCache[type] || (transactionCache[type] = {})
|
|
401
|
-
Object.assign(transactionCache[type], result)
|
|
402
|
-
})
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return ids.reduce((dict, id) => {
|
|
406
|
-
const value = transactionCache[type]?.[id]
|
|
407
|
-
if (value) {
|
|
408
|
-
dict[id] = value
|
|
409
|
-
}
|
|
410
|
-
return dict
|
|
411
|
-
}, {})
|
|
138
|
+
const ctx = txStorage.getStore();
|
|
139
|
+
if (!ctx) {
|
|
140
|
+
return state.get(type, ids);
|
|
412
141
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
142
|
+
const cached = ctx.cache[type] || {};
|
|
143
|
+
const missing = ids.filter((id) => !(id in cached));
|
|
144
|
+
if (missing.length > 0) {
|
|
145
|
+
ctx.dbQueries++;
|
|
146
|
+
logger.trace(
|
|
147
|
+
{ type: type, count: missing.length },
|
|
148
|
+
"fetching missing keys in transaction"
|
|
149
|
+
);
|
|
150
|
+
const fetched = await getTxMutex(type).runExclusive(() => state.get(type, missing));
|
|
151
|
+
ctx.cache[type] = ctx.cache[type] || {};
|
|
152
|
+
Object.assign(ctx.cache[type], fetched);
|
|
153
|
+
}
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const id of ids) {
|
|
156
|
+
const value = ctx.cache[type]?.[id];
|
|
157
|
+
if (value !== undefined && value !== null) {
|
|
158
|
+
result[id] = value;
|
|
426
159
|
}
|
|
427
160
|
}
|
|
161
|
+
return result;
|
|
428
162
|
},
|
|
429
163
|
set: async (data) => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// interleave with a concurrent non-transactional get()/set() for the
|
|
438
|
-
// same key type on this SAME auth state - no cross-session locking,
|
|
439
|
-
// no lock-domain mismatch.
|
|
440
|
-
if (key === 'pre-key' || key === 'signed-pre-key') {
|
|
441
|
-
await getKeyTypeMutex(key).runExclusive(() => handlePreKeyOperations(data, key, transactionCache, mutations, logger, true))
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
// Normal handling for other key types
|
|
445
|
-
handleNormalKeyOperations(data, key, transactionCache, mutations)
|
|
164
|
+
const ctx = txStorage.getStore();
|
|
165
|
+
if (!ctx) {
|
|
166
|
+
const types = Object.keys(data);
|
|
167
|
+
for (const type_ of types) {
|
|
168
|
+
const type = type_;
|
|
169
|
+
if (type === "pre-key") {
|
|
170
|
+
await preKeyManager.validateDeletions(data, type);
|
|
446
171
|
}
|
|
447
172
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (hasSenderKeys) {
|
|
454
|
-
logger.info({ senderKeyNames }, 'processing sender key set operations')
|
|
455
|
-
// Handle sender key operations with per-key queues
|
|
456
|
-
for (const senderKeyName of senderKeyNames) {
|
|
457
|
-
await queueSenderKeyOperation(senderKeyName, async () => {
|
|
458
|
-
// Create data subset for this specific sender key
|
|
459
|
-
const senderKeyData = {
|
|
460
|
-
'sender-key': {
|
|
461
|
-
[senderKeyName]: data['sender-key'][senderKeyName]
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
logger.trace({ senderKeyName }, 'storing sender key')
|
|
465
|
-
// Apply changes to the store
|
|
466
|
-
await state.set(senderKeyData)
|
|
467
|
-
logger.trace({ senderKeyName }, 'sender key stored')
|
|
173
|
+
await Promise.all(
|
|
174
|
+
types.map((type) =>
|
|
175
|
+
getQueue(type).add(async () => {
|
|
176
|
+
const typeData = { [type]: data[type] };
|
|
177
|
+
await state.set(typeData);
|
|
468
178
|
})
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
await withMutexes(Object.keys(data), getKeyTypeMutex, async () => {
|
|
490
|
-
// Process pre-keys and signed-pre-keys separately with specialized mutexes
|
|
491
|
-
for (const key_ in data) {
|
|
492
|
-
const keyType = key_
|
|
493
|
-
if (keyType === 'pre-key' || keyType === 'signed-pre-key') {
|
|
494
|
-
await processPreKeyDeletions(data, keyType, state, logger)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Apply changes to the store
|
|
498
|
-
await state.set(data)
|
|
499
|
-
})
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
logger.trace({ types: Object.keys(data) }, "caching in transaction");
|
|
184
|
+
for (const key_ in data) {
|
|
185
|
+
const key = key_;
|
|
186
|
+
ctx.cache[key] = ctx.cache[key] || {};
|
|
187
|
+
ctx.mutations[key] = ctx.mutations[key] || {};
|
|
188
|
+
if (key === "pre-key") {
|
|
189
|
+
await preKeyManager.processOperations(
|
|
190
|
+
data,
|
|
191
|
+
key,
|
|
192
|
+
ctx.cache,
|
|
193
|
+
ctx.mutations,
|
|
194
|
+
true
|
|
195
|
+
);
|
|
196
|
+
} else {
|
|
197
|
+
Object.assign(ctx.cache[key], data[key]);
|
|
198
|
+
Object.assign(ctx.mutations[key], data[key]);
|
|
500
199
|
}
|
|
501
200
|
}
|
|
502
201
|
},
|
|
503
|
-
isInTransaction,
|
|
504
|
-
async
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
logger.trace('entering transaction')
|
|
510
|
-
}
|
|
511
|
-
try {
|
|
512
|
-
return await executeTransactionWork(work)
|
|
513
|
-
}
|
|
514
|
-
finally {
|
|
515
|
-
cleanupTransactionState();
|
|
516
|
-
// Release AFTER commit & cleanup to prevent race conditions
|
|
517
|
-
// where another transaction starts before this one finishes writing
|
|
518
|
-
releaseTxMutex()
|
|
519
|
-
}
|
|
202
|
+
isInTransaction: isInTransaction,
|
|
203
|
+
transaction: async (work, key) => {
|
|
204
|
+
const existing = txStorage.getStore();
|
|
205
|
+
if (existing) {
|
|
206
|
+
logger.trace("reusing existing transaction context");
|
|
207
|
+
return work();
|
|
520
208
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
209
|
+
const mutex = getTxMutex(key);
|
|
210
|
+
acquireTxMutexRef(key);
|
|
211
|
+
try {
|
|
212
|
+
return await mutex.runExclusive(async () => {
|
|
213
|
+
const ctx = { cache: {}, mutations: {}, dbQueries: 0 };
|
|
214
|
+
logger.trace("entering transaction");
|
|
215
|
+
try {
|
|
216
|
+
const result = await txStorage.run(ctx, work);
|
|
217
|
+
await commitWithRetry(ctx.mutations);
|
|
218
|
+
logger.trace({ dbQueries: ctx.dbQueries }, "transaction completed");
|
|
219
|
+
return result;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error({ error: error }, "transaction failed, rolling back");
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
} finally {
|
|
226
|
+
releaseTxMutexRef(key);
|
|
529
227
|
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
};
|
|
534
231
|
const initAuthCreds = () => {
|
|
535
|
-
const identityKey =
|
|
232
|
+
const identityKey = Curve.generateKeyPair();
|
|
536
233
|
return {
|
|
537
|
-
noiseKey:
|
|
538
|
-
pairingEphemeralKeyPair:
|
|
234
|
+
noiseKey: Curve.generateKeyPair(),
|
|
235
|
+
pairingEphemeralKeyPair: Curve.generateKeyPair(),
|
|
539
236
|
signedIdentityKey: identityKey,
|
|
540
|
-
signedPreKey:
|
|
541
|
-
registrationId:
|
|
542
|
-
advSecretKey:
|
|
237
|
+
signedPreKey: signedKeyPair(identityKey, 1),
|
|
238
|
+
registrationId: generateRegistrationId(),
|
|
239
|
+
advSecretKey: randomBytes(32).toString("base64"),
|
|
543
240
|
processedHistoryMessages: [],
|
|
544
241
|
nextPreKeyId: 1,
|
|
545
242
|
firstUnuploadedPreKeyId: 1,
|
|
546
243
|
accountSyncCounter: 0,
|
|
547
|
-
accountSettings: {
|
|
548
|
-
unarchiveChats: false
|
|
549
|
-
},
|
|
244
|
+
accountSettings: { unarchiveChats: false },
|
|
550
245
|
registered: false,
|
|
551
246
|
pairingCode: undefined,
|
|
552
247
|
lastPropHash: undefined,
|
|
553
|
-
routingInfo: undefined
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
248
|
+
routingInfo: undefined,
|
|
249
|
+
additionalData: undefined,
|
|
250
|
+
};
|
|
251
|
+
};
|
|
557
252
|
module.exports = {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
253
|
+
makeCacheableSignalKeyStore: makeCacheableSignalKeyStore,
|
|
254
|
+
addTransactionCapability: addTransactionCapability,
|
|
255
|
+
initAuthCreds: initAuthCreds,
|
|
256
|
+
};
|