@pontalabs/baileys 1.0.6 → 1.0.7
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/LICENSE +1 -1
- package/README.md +839 -267
- package/WAProto/GenerateStatics.sh +4 -3
- package/WAProto/WAProto.proto +511 -1215
- package/WAProto/index.d.ts +55 -14017
- package/WAProto/index.js +29 -97689
- package/lib/Defaults/index.d.ts +74 -72
- package/lib/Defaults/index.js +108 -94
- package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
- package/lib/Signal/Group/ciphertext-message.js +16 -9
- package/lib/Signal/Group/group-session-builder.d.ts +13 -11
- package/lib/Signal/Group/group-session-builder.js +61 -19
- package/lib/Signal/Group/group_cipher.d.ts +16 -14
- package/lib/Signal/Group/group_cipher.js +70 -41
- package/lib/Signal/Group/index.d.ts +11 -12
- package/lib/Signal/Group/index.js +61 -12
- package/lib/Signal/Group/keyhelper.d.ts +14 -9
- package/lib/Signal/Group/keyhelper.js +58 -10
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
- package/lib/Signal/Group/sender-chain-key.js +34 -13
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
- package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
- package/lib/Signal/Group/sender-key-message.d.ts +18 -18
- package/lib/Signal/Group/sender-key-message.js +45 -38
- package/lib/Signal/Group/sender-key-name.d.ts +15 -14
- package/lib/Signal/Group/sender-key-name.js +31 -20
- package/lib/Signal/Group/sender-key-record.d.ts +25 -24
- package/lib/Signal/Group/sender-key-record.js +35 -18
- package/lib/Signal/Group/sender-key-state.d.ts +34 -29
- package/lib/Signal/Group/sender-key-state.js +100 -37
- package/lib/Signal/Group/sender-message-key.d.ts +10 -11
- package/lib/Signal/Group/sender-message-key.js +22 -15
- package/lib/Signal/libsignal.d.ts +8 -5
- package/lib/Signal/libsignal.js +142 -288
- package/lib/Signal/lid-mapping.d.ts +28 -23
- package/lib/Signal/lid-mapping.js +184 -277
- package/lib/Socket/Client/index.d.ts +2 -3
- package/lib/Socket/Client/index.js +22 -3
- package/lib/Socket/Client/types.d.ts +15 -15
- package/lib/Socket/Client/types.js +15 -8
- package/lib/Socket/Client/websocket.d.ts +12 -12
- package/lib/Socket/Client/websocket.js +37 -29
- package/lib/Socket/business.d.ts +178 -208
- package/lib/Socket/business.js +127 -91
- package/lib/Socket/chats.d.ts +93 -120
- package/lib/Socket/chats.js +652 -727
- package/lib/Socket/community.js +10 -2
- package/lib/Socket/groups.d.ts +111 -143
- package/lib/Socket/groups.js +186 -165
- package/lib/Socket/index.d.ts +183 -252
- package/lib/Socket/index.js +18 -38
- package/lib/Socket/messages-recv.d.ts +167 -196
- package/lib/Socket/messages-recv.js +1567 -1443
- package/lib/Socket/messages-send.d.ts +158 -192
- package/lib/Socket/messages-send.js +1208 -1102
- package/lib/Socket/newsletter.d.ts +138 -163
- package/lib/Socket/newsletter.js +249 -153
- package/lib/Socket/socket.d.ts +36 -50
- package/lib/Socket/socket.js +820 -749
- package/lib/Store/index.d.ts +4 -4
- package/lib/Store/index.js +24 -4
- package/lib/Store/make-in-memory-store.d.ts +104 -44
- package/lib/Store/make-in-memory-store.js +213 -199
- package/lib/Store/make-ordered-dictionary.d.ts +12 -13
- package/lib/Store/make-ordered-dictionary.js +45 -38
- package/lib/Store/object-repository.d.ts +10 -11
- package/lib/Store/object-repository.js +16 -9
- package/lib/Types/Auth.d.ts +97 -93
- package/lib/Types/Auth.js +3 -2
- package/lib/Types/Bussines.js +3 -2
- package/lib/Types/Call.d.ts +13 -14
- package/lib/Types/Call.js +3 -2
- package/lib/Types/Chat.d.ts +97 -78
- package/lib/Types/Chat.js +9 -8
- package/lib/Types/Contact.d.ts +9 -12
- package/lib/Types/Contact.js +3 -2
- package/lib/Types/Events.d.ts +176 -206
- package/lib/Types/Events.js +3 -2
- package/lib/Types/GroupMetadata.d.ts +48 -53
- package/lib/Types/GroupMetadata.js +3 -2
- package/lib/Types/Label.d.ts +14 -13
- package/lib/Types/Label.js +30 -24
- package/lib/Types/LabelAssociation.d.ts +20 -15
- package/lib/Types/LabelAssociation.js +12 -6
- package/lib/Types/Message.d.ts +412 -248
- package/lib/Types/Message.js +19 -17
- package/lib/Types/Product.d.ts +71 -58
- package/lib/Types/Product.js +3 -2
- package/lib/Types/Signal.d.ts +82 -71
- package/lib/Types/Signal.js +3 -2
- package/lib/Types/Socket.d.ts +81 -76
- package/lib/Types/Socket.js +3 -3
- package/lib/Types/State.d.ts +19 -75
- package/lib/Types/State.js +14 -56
- package/lib/Types/USync.d.ts +8 -8
- package/lib/Types/USync.js +3 -2
- package/lib/Types/index.d.ts +62 -47
- package/lib/Types/index.js +50 -26
- package/lib/Utils/auth-utils.d.ts +10 -13
- package/lib/Utils/auth-utils.js +472 -213
- package/lib/Utils/business.d.ts +20 -14
- package/lib/Utils/business.js +134 -110
- package/lib/Utils/chat-utils.d.ts +70 -88
- package/lib/Utils/chat-utils.js +403 -466
- package/lib/Utils/crypto.d.ts +46 -27
- package/lib/Utils/crypto.js +188 -117
- package/lib/Utils/decode-wa-message.d.ts +45 -58
- package/lib/Utils/decode-wa-message.js +311 -212
- package/lib/Utils/event-buffer.d.ts +17 -14
- package/lib/Utils/event-buffer.js +296 -321
- package/lib/Utils/generics.d.ts +92 -66
- package/lib/Utils/generics.js +436 -270
- package/lib/Utils/history.d.ts +23 -24
- package/lib/Utils/history.js +76 -106
- package/lib/Utils/index.d.ts +21 -24
- package/lib/Utils/index.js +41 -24
- package/lib/Utils/link-preview.d.ts +14 -12
- package/lib/Utils/link-preview.js +75 -40
- package/lib/Utils/logger.d.ts +11 -10
- package/lib/Utils/logger.js +7 -3
- package/lib/Utils/lt-hash.d.ts +14 -8
- package/lib/Utils/lt-hash.js +53 -3
- package/lib/Utils/make-mutex.d.ts +7 -7
- package/lib/Utils/make-mutex.js +44 -28
- package/lib/Utils/message-retry-manager.d.ts +88 -115
- package/lib/Utils/message-retry-manager.js +160 -265
- package/lib/Utils/messages-media.d.ts +107 -105
- package/lib/Utils/messages-media.js +619 -548
- package/lib/Utils/messages.d.ts +66 -52
- package/lib/Utils/messages.js +1558 -1730
- package/lib/Utils/noise-handler.d.ts +18 -18
- package/lib/Utils/noise-handler.js +130 -176
- package/lib/Utils/process-message.d.ts +32 -43
- package/lib/Utils/process-message.js +282 -484
- package/lib/Utils/rich-message-utils.js +724 -1220
- package/lib/Utils/signal.d.ts +32 -37
- package/lib/Utils/signal.js +105 -140
- package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
- package/lib/Utils/use-multi-file-auth-state.js +120 -70
- package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
- package/lib/Utils/use-single-file-auth-state.js +66 -92
- package/lib/Utils/validate-connection.d.ts +13 -11
- package/lib/Utils/validate-connection.js +113 -128
- package/lib/WABinary/constants.d.ts +27 -25
- package/lib/WABinary/constants.js +1292 -1451
- package/lib/WABinary/decode.d.ts +9 -7
- package/lib/WABinary/decode.js +189 -163
- package/lib/WABinary/encode.d.ts +3 -3
- package/lib/WABinary/encode.js +155 -110
- package/lib/WABinary/generic-utils.d.ts +27 -14
- package/lib/WABinary/generic-utils.js +105 -177
- package/lib/WABinary/index.d.ts +5 -6
- package/lib/WABinary/index.js +25 -6
- package/lib/WABinary/jid-utils.d.ts +54 -41
- package/lib/WABinary/jid-utils.js +152 -83
- package/lib/WABinary/types.d.ts +13 -10
- package/lib/WABinary/types.js +3 -2
- package/lib/WAM/BinaryInfo.d.ts +15 -8
- package/lib/WAM/BinaryInfo.js +14 -7
- package/lib/WAM/constants.d.ts +37 -30
- package/lib/WAM/constants.js +11983 -19465
- package/lib/WAM/encode.d.ts +3 -3
- package/lib/WAM/encode.js +109 -95
- package/lib/WAM/index.d.ts +3 -4
- package/lib/WAM/index.js +23 -4
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
- package/lib/WAUSync/Protocols/index.d.ts +6 -6
- package/lib/WAUSync/Protocols/index.js +26 -6
- package/lib/WAUSync/USyncQuery.d.ts +28 -27
- package/lib/WAUSync/USyncQuery.js +62 -68
- package/lib/WAUSync/USyncUser.d.ts +10 -15
- package/lib/WAUSync/USyncUser.js +19 -20
- package/lib/WAUSync/index.d.ts +3 -4
- package/lib/WAUSync/index.js +23 -4
- package/lib/index.d.ts +12 -11
- package/lib/index.js +39 -12
- package/package.json +42 -86
- package/engine-requirements.js +0 -10
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -1,290 +1,545 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true })
|
|
8
|
+
|
|
9
|
+
const crypto_1 = require("crypto")
|
|
10
|
+
const node_cache_1 = __importDefault(require("@cacheable/node-cache"))
|
|
11
|
+
const Defaults_1 = require("../Defaults")
|
|
12
|
+
const LRUCache_1 = require("lru-cache")
|
|
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
|
+
|
|
11
27
|
/**
|
|
12
28
|
* Adds caching capability to a SignalKeyStore
|
|
13
29
|
* @param store the store to add caching to
|
|
14
30
|
* @param logger to log trace events
|
|
15
31
|
* @param _cache cache store to use
|
|
16
32
|
*/
|
|
17
|
-
|
|
18
|
-
const cache = _cache ||
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
function makeCacheableSignalKeyStore(store, logger, _cache) {
|
|
34
|
+
const cache = _cache || new node_cache_1.default({
|
|
35
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.SIGNAL_STORE,
|
|
36
|
+
useClones: false,
|
|
37
|
+
deleteOnExpire: true,
|
|
38
|
+
})
|
|
39
|
+
|
|
24
40
|
// Mutex for protecting cache operations
|
|
25
|
-
const cacheMutex = new Mutex()
|
|
41
|
+
const cacheMutex = new mutex_1.Mutex()
|
|
42
|
+
|
|
26
43
|
function getUniqueId(type, id) {
|
|
27
|
-
return `${type}.${id}
|
|
44
|
+
return `${type}.${id}`
|
|
28
45
|
}
|
|
29
46
|
return {
|
|
30
47
|
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
|
+
|
|
31
53
|
return cacheMutex.runExclusive(async () => {
|
|
32
|
-
const data = {}
|
|
33
|
-
const idsToFetch = []
|
|
54
|
+
const data = {}
|
|
55
|
+
const idsToFetch = []
|
|
34
56
|
for (const id of ids) {
|
|
35
|
-
const item =
|
|
57
|
+
const item = cache.get(getUniqueId(type, id))
|
|
36
58
|
if (typeof item !== 'undefined') {
|
|
37
|
-
data[id] = item
|
|
59
|
+
data[id] = item
|
|
38
60
|
}
|
|
39
61
|
else {
|
|
40
|
-
idsToFetch.push(id)
|
|
62
|
+
idsToFetch.push(id)
|
|
41
63
|
}
|
|
42
64
|
}
|
|
43
65
|
if (idsToFetch.length) {
|
|
44
|
-
logger?.trace({ items: idsToFetch.length }, 'loading from store')
|
|
45
|
-
const fetched = await store.get(type, idsToFetch)
|
|
66
|
+
logger?.trace({ items: idsToFetch.length }, 'loading from store')
|
|
67
|
+
const fetched = await store.get(type, idsToFetch)
|
|
46
68
|
for (const id of idsToFetch) {
|
|
47
|
-
const item = fetched[id]
|
|
69
|
+
const item = fetched[id]
|
|
48
70
|
if (item) {
|
|
49
|
-
data[id] = item
|
|
50
|
-
|
|
51
|
-
await cache.set(getUniqueId(type, id), item);
|
|
71
|
+
data[id] = item
|
|
72
|
+
cache.set(getUniqueId(type, id), item)
|
|
52
73
|
}
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
|
-
return data
|
|
56
|
-
})
|
|
76
|
+
return data
|
|
77
|
+
})
|
|
57
78
|
},
|
|
58
79
|
async set(data) {
|
|
59
80
|
return cacheMutex.runExclusive(async () => {
|
|
60
|
-
let keys = 0
|
|
81
|
+
let keys = 0
|
|
61
82
|
for (const type in data) {
|
|
83
|
+
if (NO_CACHE_TYPES.has(type)) {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
62
86
|
for (const id in data[type]) {
|
|
63
|
-
await cache.set(getUniqueId(type, id), data[type][id])
|
|
64
|
-
keys += 1
|
|
87
|
+
await cache.set(getUniqueId(type, id), data[type][id])
|
|
88
|
+
keys += 1
|
|
65
89
|
}
|
|
66
90
|
}
|
|
67
|
-
logger?.trace({ keys }, 'updated cache')
|
|
68
|
-
await store.set(data)
|
|
69
|
-
})
|
|
91
|
+
logger?.trace({ keys }, 'updated cache')
|
|
92
|
+
await store.set(data)
|
|
93
|
+
})
|
|
70
94
|
},
|
|
71
95
|
async clear() {
|
|
72
|
-
await cache.flushAll()
|
|
73
|
-
await store.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
|
|
74
187
|
}
|
|
75
|
-
|
|
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])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Process pre-key / signed-pre-key deletions with validation.
|
|
205
|
+
*
|
|
206
|
+
* NOTE: this is only ever called from inside a `withMutexes([..., keyType, ...])`
|
|
207
|
+
* block in `set()` below, which already holds the session-scoped
|
|
208
|
+
* `getKeyTypeMutex(keyType)` lock for the duration of the call. It must NOT
|
|
209
|
+
* acquire its own mutex here - `async-mutex`'s Mutex is not reentrant, so
|
|
210
|
+
* doing so would either deadlock (if it tried to re-acquire the same lock)
|
|
211
|
+
* or - as in the previous version - silently acquire a *different*, global
|
|
212
|
+
* lock that gave no real protection at all.
|
|
213
|
+
*/
|
|
214
|
+
async function processPreKeyDeletions(data, keyType, state, logger) {
|
|
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]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Executes a function with mutexes acquired for given key types
|
|
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())
|
|
257
|
+
}
|
|
258
|
+
return await fn()
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
// Release in reverse order
|
|
262
|
+
while (releases.length > 0) {
|
|
263
|
+
const release = releases.pop()
|
|
264
|
+
if (release)
|
|
265
|
+
release()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
76
268
|
}
|
|
269
|
+
|
|
77
270
|
/**
|
|
78
|
-
* Adds DB
|
|
79
|
-
*
|
|
271
|
+
* Adds DB like transaction capability (https://en.wikipedia.org/wiki/Database_transaction) to the SignalKeyStore,
|
|
272
|
+
* this allows batch read & write operations & improves the performance of the lib
|
|
80
273
|
* @param state the key store to apply this capability to
|
|
81
274
|
* @param logger logger to log events
|
|
82
275
|
* @returns SignalKeyStore with transaction capability
|
|
83
276
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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}`)
|
|
101
295
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function getTxMutex(key) {
|
|
106
|
-
if (!txMutexes.has(key)) {
|
|
107
|
-
txMutexes.set(key, new Mutex());
|
|
108
|
-
txMutexRefCounts.set(key, 0);
|
|
109
|
-
}
|
|
110
|
-
return txMutexes.get(key);
|
|
296
|
+
|
|
297
|
+
function getSenderKeyMutex(senderKeyName) {
|
|
298
|
+
return getMutex(`senderkey:${senderKeyName}`)
|
|
111
299
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
function acquireTxMutexRef(key) {
|
|
116
|
-
const count = txMutexRefCounts.get(key) ?? 0;
|
|
117
|
-
txMutexRefCounts.set(key, count + 1);
|
|
300
|
+
|
|
301
|
+
function getTransactionMutex(key) {
|
|
302
|
+
return getMutex(`transaction:${key}`)
|
|
118
303
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const mutex = txMutexes.get(key);
|
|
128
|
-
if (mutex && !mutex.isLocked()) {
|
|
129
|
-
txMutexes.delete(key);
|
|
130
|
-
txMutexRefCounts.delete(key);
|
|
131
|
-
}
|
|
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')
|
|
132
312
|
}
|
|
313
|
+
return mutex
|
|
133
314
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
315
|
+
|
|
316
|
+
// Sender key operations with proper mutex sequencing
|
|
317
|
+
function queueSenderKeyOperation(senderKeyName, operation) {
|
|
318
|
+
return getSenderKeyMutex(senderKeyName).runExclusive(operation)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check if we are currently in a transaction
|
|
137
322
|
function isInTransaction() {
|
|
138
|
-
return
|
|
323
|
+
return transactionsInProgress > 0
|
|
139
324
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return;
|
|
325
|
+
|
|
326
|
+
// Helper function to handle transaction commit with retries
|
|
327
|
+
async function commitTransaction() {
|
|
328
|
+
if (!Object.keys(mutations).length) {
|
|
329
|
+
logger.trace('no mutations in transaction')
|
|
330
|
+
return
|
|
147
331
|
}
|
|
148
332
|
logger.trace('committing transaction');
|
|
149
|
-
|
|
333
|
+
let tries = maxCommitRetries
|
|
334
|
+
while (tries > 0) {
|
|
335
|
+
tries -= 1;
|
|
150
336
|
try {
|
|
151
|
-
await state.set(mutations)
|
|
152
|
-
logger.trace({
|
|
153
|
-
return
|
|
337
|
+
await state.set(mutations)
|
|
338
|
+
logger.trace({ dbQueriesInTransaction }, 'committed transaction')
|
|
339
|
+
return
|
|
154
340
|
}
|
|
155
341
|
catch (error) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
throw error;
|
|
342
|
+
logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`)
|
|
343
|
+
if (tries > 0) {
|
|
344
|
+
await generics_1.delay(delayBetweenTriesMs)
|
|
160
345
|
}
|
|
161
|
-
await delay(delayBetweenTriesMs);
|
|
162
346
|
}
|
|
163
347
|
}
|
|
164
348
|
}
|
|
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
|
+
|
|
165
370
|
return {
|
|
166
371
|
get: async (type, ids) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
372
|
+
if (isInTransaction()) {
|
|
373
|
+
const dict = transactionCache[type]
|
|
374
|
+
const idsRequiringFetch = dict ? ids.filter(item => typeof dict[item] === 'undefined') : ids
|
|
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
|
+
}, {})
|
|
182
412
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
413
|
+
else {
|
|
414
|
+
// Not in transaction, fetch directly with queue protection
|
|
415
|
+
if (type === 'sender-key') {
|
|
416
|
+
// For sender keys, use individual queues to maintain per-key serialization
|
|
417
|
+
const results = {}
|
|
418
|
+
for (const senderKeyName of ids) {
|
|
419
|
+
const result = await queueSenderKeyOperation(senderKeyName, async () => await state.get(type, [senderKeyName]))
|
|
420
|
+
Object.assign(results, result)
|
|
421
|
+
}
|
|
422
|
+
return results
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
return await getKeyTypeMutex(type).runExclusive(() => state.get(type, ids))
|
|
189
426
|
}
|
|
190
427
|
}
|
|
191
|
-
return result;
|
|
192
428
|
},
|
|
193
429
|
set: async (data) => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
430
|
+
if (isInTransaction()) {
|
|
431
|
+
logger.trace({ types: Object.keys(data) }, 'caching in transaction')
|
|
432
|
+
for (const key_ in data) {
|
|
433
|
+
const key = key_
|
|
434
|
+
transactionCache[key] = transactionCache[key] || {}
|
|
435
|
+
// Special handling for pre-keys and signed-pre-keys.
|
|
436
|
+
// Locked via the session-scoped getKeyTypeMutex(key) so this can't
|
|
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)
|
|
203
446
|
}
|
|
204
447
|
}
|
|
205
|
-
// Write all data in parallel
|
|
206
|
-
await Promise.all(types.map(type => getQueue(type).add(async () => {
|
|
207
|
-
const typeData = { [type]: data[type] };
|
|
208
|
-
await state.set(typeData);
|
|
209
|
-
})));
|
|
210
|
-
return;
|
|
211
448
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
449
|
+
else {
|
|
450
|
+
// Not in transaction, apply directly with mutex protection
|
|
451
|
+
const hasSenderKeys = 'sender-key' in data
|
|
452
|
+
const senderKeyNames = hasSenderKeys ? Object.keys(data['sender-key'] || {}) : []
|
|
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')
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
// Handle any non-sender-key data with regular mutexes
|
|
471
|
+
const nonSenderKeyData = { ...data }
|
|
472
|
+
delete nonSenderKeyData['sender-key']
|
|
473
|
+
if (Object.keys(nonSenderKeyData).length > 0) {
|
|
474
|
+
await withMutexes(Object.keys(nonSenderKeyData), getKeyTypeMutex, async () => {
|
|
475
|
+
// Process pre-keys and signed-pre-keys separately with specialized mutexes
|
|
476
|
+
for (const key_ in nonSenderKeyData) {
|
|
477
|
+
const keyType = key_
|
|
478
|
+
if (keyType === 'pre-key' || keyType === 'signed-pre-key') {
|
|
479
|
+
await processPreKeyDeletions(nonSenderKeyData, keyType, state, logger)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Apply changes to the store
|
|
483
|
+
await state.set(nonSenderKeyData)
|
|
484
|
+
})
|
|
485
|
+
}
|
|
222
486
|
}
|
|
223
487
|
else {
|
|
224
|
-
//
|
|
225
|
-
Object.
|
|
226
|
-
|
|
488
|
+
// No sender keys - use original logic
|
|
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
|
+
})
|
|
227
500
|
}
|
|
228
501
|
}
|
|
229
502
|
},
|
|
230
503
|
isInTransaction,
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
// Nested transaction - reuse existing context
|
|
234
|
-
if (existing) {
|
|
235
|
-
logger.trace('reusing existing transaction context');
|
|
236
|
-
return work();
|
|
237
|
-
}
|
|
238
|
-
// New transaction - acquire mutex and create context
|
|
239
|
-
const mutex = getTxMutex(key);
|
|
240
|
-
acquireTxMutexRef(key);
|
|
504
|
+
async transaction(work, key) {
|
|
505
|
+
const releaseTxMutex = await getTransactionMutex(key).acquire()
|
|
241
506
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
logger.error({ error }, 'transaction failed, rolling back');
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
});
|
|
507
|
+
transactionsInProgress += 1;
|
|
508
|
+
if (transactionsInProgress === 1) {
|
|
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
|
+
}
|
|
261
520
|
}
|
|
262
|
-
|
|
263
|
-
|
|
521
|
+
catch (error) {
|
|
522
|
+
// Only release here if we haven't already released in the finally block
|
|
523
|
+
// (i.e., error thrown before transactionsInProgress was incremented)
|
|
524
|
+
if (transactionsInProgress > 0) {
|
|
525
|
+
cleanupTransactionState()
|
|
526
|
+
}
|
|
527
|
+
releaseTxMutex()
|
|
528
|
+
throw error
|
|
264
529
|
}
|
|
265
530
|
}
|
|
266
|
-
};
|
|
267
|
-
};
|
|
268
|
-
/**
|
|
269
|
-
* Returns the authenticated user's JID, or throws a Boom-401 if creds are not yet authenticated.
|
|
270
|
-
* Use this anywhere we'd otherwise reach for `creds.me!.id` to fail fast with a descriptive error.
|
|
271
|
-
*/
|
|
272
|
-
export const assertMeId = (creds) => {
|
|
273
|
-
const id = creds.me?.id;
|
|
274
|
-
if (!id) {
|
|
275
|
-
throw new Boom('Cannot proceed: socket is not authenticated yet (creds.me.id is missing)', { statusCode: 401 });
|
|
276
531
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const identityKey = Curve.generateKeyPair()
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const initAuthCreds = () => {
|
|
535
|
+
const identityKey = crypto_2.Curve.generateKeyPair()
|
|
281
536
|
return {
|
|
282
|
-
noiseKey: Curve.generateKeyPair(),
|
|
283
|
-
pairingEphemeralKeyPair: Curve.generateKeyPair(),
|
|
537
|
+
noiseKey: crypto_2.Curve.generateKeyPair(),
|
|
538
|
+
pairingEphemeralKeyPair: crypto_2.Curve.generateKeyPair(),
|
|
284
539
|
signedIdentityKey: identityKey,
|
|
285
|
-
signedPreKey: signedKeyPair(identityKey, 1),
|
|
286
|
-
registrationId: generateRegistrationId(),
|
|
287
|
-
advSecretKey: randomBytes(32).toString('base64'),
|
|
540
|
+
signedPreKey: crypto_2.signedKeyPair(identityKey, 1),
|
|
541
|
+
registrationId: generics_1.generateRegistrationId(),
|
|
542
|
+
advSecretKey: crypto_1.randomBytes(32).toString('base64'),
|
|
288
543
|
processedHistoryMessages: [],
|
|
289
544
|
nextPreKeyId: 1,
|
|
290
545
|
firstUnuploadedPreKeyId: 1,
|
|
@@ -295,8 +550,12 @@ export const initAuthCreds = () => {
|
|
|
295
550
|
registered: false,
|
|
296
551
|
pairingCode: undefined,
|
|
297
552
|
lastPropHash: undefined,
|
|
298
|
-
routingInfo: undefined
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
553
|
+
routingInfo: undefined
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
module.exports = {
|
|
558
|
+
makeCacheableSignalKeyStore,
|
|
559
|
+
addTransactionCapability,
|
|
560
|
+
initAuthCreds
|
|
561
|
+
}
|