@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.
Files changed (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +839 -267
  3. package/WAProto/GenerateStatics.sh +4 -3
  4. package/WAProto/WAProto.proto +511 -1215
  5. package/WAProto/index.d.ts +55 -14017
  6. package/WAProto/index.js +29 -97689
  7. package/lib/Defaults/index.d.ts +74 -72
  8. package/lib/Defaults/index.js +108 -94
  9. package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
  10. package/lib/Signal/Group/ciphertext-message.js +16 -9
  11. package/lib/Signal/Group/group-session-builder.d.ts +13 -11
  12. package/lib/Signal/Group/group-session-builder.js +61 -19
  13. package/lib/Signal/Group/group_cipher.d.ts +16 -14
  14. package/lib/Signal/Group/group_cipher.js +70 -41
  15. package/lib/Signal/Group/index.d.ts +11 -12
  16. package/lib/Signal/Group/index.js +61 -12
  17. package/lib/Signal/Group/keyhelper.d.ts +14 -9
  18. package/lib/Signal/Group/keyhelper.js +58 -10
  19. package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
  20. package/lib/Signal/Group/sender-chain-key.js +34 -13
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
  22. package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
  23. package/lib/Signal/Group/sender-key-message.d.ts +18 -18
  24. package/lib/Signal/Group/sender-key-message.js +45 -38
  25. package/lib/Signal/Group/sender-key-name.d.ts +15 -14
  26. package/lib/Signal/Group/sender-key-name.js +31 -20
  27. package/lib/Signal/Group/sender-key-record.d.ts +25 -24
  28. package/lib/Signal/Group/sender-key-record.js +35 -18
  29. package/lib/Signal/Group/sender-key-state.d.ts +34 -29
  30. package/lib/Signal/Group/sender-key-state.js +100 -37
  31. package/lib/Signal/Group/sender-message-key.d.ts +10 -11
  32. package/lib/Signal/Group/sender-message-key.js +22 -15
  33. package/lib/Signal/libsignal.d.ts +8 -5
  34. package/lib/Signal/libsignal.js +142 -288
  35. package/lib/Signal/lid-mapping.d.ts +28 -23
  36. package/lib/Signal/lid-mapping.js +184 -277
  37. package/lib/Socket/Client/index.d.ts +2 -3
  38. package/lib/Socket/Client/index.js +22 -3
  39. package/lib/Socket/Client/types.d.ts +15 -15
  40. package/lib/Socket/Client/types.js +15 -8
  41. package/lib/Socket/Client/websocket.d.ts +12 -12
  42. package/lib/Socket/Client/websocket.js +37 -29
  43. package/lib/Socket/business.d.ts +178 -208
  44. package/lib/Socket/business.js +127 -91
  45. package/lib/Socket/chats.d.ts +93 -120
  46. package/lib/Socket/chats.js +652 -727
  47. package/lib/Socket/community.js +10 -2
  48. package/lib/Socket/groups.d.ts +111 -143
  49. package/lib/Socket/groups.js +186 -165
  50. package/lib/Socket/index.d.ts +183 -252
  51. package/lib/Socket/index.js +18 -38
  52. package/lib/Socket/messages-recv.d.ts +167 -196
  53. package/lib/Socket/messages-recv.js +1567 -1443
  54. package/lib/Socket/messages-send.d.ts +158 -192
  55. package/lib/Socket/messages-send.js +1208 -1102
  56. package/lib/Socket/newsletter.d.ts +138 -163
  57. package/lib/Socket/newsletter.js +249 -153
  58. package/lib/Socket/socket.d.ts +36 -50
  59. package/lib/Socket/socket.js +820 -749
  60. package/lib/Store/index.d.ts +4 -4
  61. package/lib/Store/index.js +24 -4
  62. package/lib/Store/make-in-memory-store.d.ts +104 -44
  63. package/lib/Store/make-in-memory-store.js +213 -199
  64. package/lib/Store/make-ordered-dictionary.d.ts +12 -13
  65. package/lib/Store/make-ordered-dictionary.js +45 -38
  66. package/lib/Store/object-repository.d.ts +10 -11
  67. package/lib/Store/object-repository.js +16 -9
  68. package/lib/Types/Auth.d.ts +97 -93
  69. package/lib/Types/Auth.js +3 -2
  70. package/lib/Types/Bussines.js +3 -2
  71. package/lib/Types/Call.d.ts +13 -14
  72. package/lib/Types/Call.js +3 -2
  73. package/lib/Types/Chat.d.ts +97 -78
  74. package/lib/Types/Chat.js +9 -8
  75. package/lib/Types/Contact.d.ts +9 -12
  76. package/lib/Types/Contact.js +3 -2
  77. package/lib/Types/Events.d.ts +176 -206
  78. package/lib/Types/Events.js +3 -2
  79. package/lib/Types/GroupMetadata.d.ts +48 -53
  80. package/lib/Types/GroupMetadata.js +3 -2
  81. package/lib/Types/Label.d.ts +14 -13
  82. package/lib/Types/Label.js +30 -24
  83. package/lib/Types/LabelAssociation.d.ts +20 -15
  84. package/lib/Types/LabelAssociation.js +12 -6
  85. package/lib/Types/Message.d.ts +412 -248
  86. package/lib/Types/Message.js +19 -17
  87. package/lib/Types/Product.d.ts +71 -58
  88. package/lib/Types/Product.js +3 -2
  89. package/lib/Types/Signal.d.ts +82 -71
  90. package/lib/Types/Signal.js +3 -2
  91. package/lib/Types/Socket.d.ts +81 -76
  92. package/lib/Types/Socket.js +3 -3
  93. package/lib/Types/State.d.ts +19 -75
  94. package/lib/Types/State.js +14 -56
  95. package/lib/Types/USync.d.ts +8 -8
  96. package/lib/Types/USync.js +3 -2
  97. package/lib/Types/index.d.ts +62 -47
  98. package/lib/Types/index.js +50 -26
  99. package/lib/Utils/auth-utils.d.ts +10 -13
  100. package/lib/Utils/auth-utils.js +472 -213
  101. package/lib/Utils/business.d.ts +20 -14
  102. package/lib/Utils/business.js +134 -110
  103. package/lib/Utils/chat-utils.d.ts +70 -88
  104. package/lib/Utils/chat-utils.js +403 -466
  105. package/lib/Utils/crypto.d.ts +46 -27
  106. package/lib/Utils/crypto.js +188 -117
  107. package/lib/Utils/decode-wa-message.d.ts +45 -58
  108. package/lib/Utils/decode-wa-message.js +311 -212
  109. package/lib/Utils/event-buffer.d.ts +17 -14
  110. package/lib/Utils/event-buffer.js +296 -321
  111. package/lib/Utils/generics.d.ts +92 -66
  112. package/lib/Utils/generics.js +436 -270
  113. package/lib/Utils/history.d.ts +23 -24
  114. package/lib/Utils/history.js +76 -106
  115. package/lib/Utils/index.d.ts +21 -24
  116. package/lib/Utils/index.js +41 -24
  117. package/lib/Utils/link-preview.d.ts +14 -12
  118. package/lib/Utils/link-preview.js +75 -40
  119. package/lib/Utils/logger.d.ts +11 -10
  120. package/lib/Utils/logger.js +7 -3
  121. package/lib/Utils/lt-hash.d.ts +14 -8
  122. package/lib/Utils/lt-hash.js +53 -3
  123. package/lib/Utils/make-mutex.d.ts +7 -7
  124. package/lib/Utils/make-mutex.js +44 -28
  125. package/lib/Utils/message-retry-manager.d.ts +88 -115
  126. package/lib/Utils/message-retry-manager.js +160 -265
  127. package/lib/Utils/messages-media.d.ts +107 -105
  128. package/lib/Utils/messages-media.js +619 -548
  129. package/lib/Utils/messages.d.ts +66 -52
  130. package/lib/Utils/messages.js +1558 -1730
  131. package/lib/Utils/noise-handler.d.ts +18 -18
  132. package/lib/Utils/noise-handler.js +130 -176
  133. package/lib/Utils/process-message.d.ts +32 -43
  134. package/lib/Utils/process-message.js +282 -484
  135. package/lib/Utils/rich-message-utils.js +724 -1220
  136. package/lib/Utils/signal.d.ts +32 -37
  137. package/lib/Utils/signal.js +105 -140
  138. package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
  139. package/lib/Utils/use-multi-file-auth-state.js +120 -70
  140. package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
  141. package/lib/Utils/use-single-file-auth-state.js +66 -92
  142. package/lib/Utils/validate-connection.d.ts +13 -11
  143. package/lib/Utils/validate-connection.js +113 -128
  144. package/lib/WABinary/constants.d.ts +27 -25
  145. package/lib/WABinary/constants.js +1292 -1451
  146. package/lib/WABinary/decode.d.ts +9 -7
  147. package/lib/WABinary/decode.js +189 -163
  148. package/lib/WABinary/encode.d.ts +3 -3
  149. package/lib/WABinary/encode.js +155 -110
  150. package/lib/WABinary/generic-utils.d.ts +27 -14
  151. package/lib/WABinary/generic-utils.js +105 -177
  152. package/lib/WABinary/index.d.ts +5 -6
  153. package/lib/WABinary/index.js +25 -6
  154. package/lib/WABinary/jid-utils.d.ts +54 -41
  155. package/lib/WABinary/jid-utils.js +152 -83
  156. package/lib/WABinary/types.d.ts +13 -10
  157. package/lib/WABinary/types.js +3 -2
  158. package/lib/WAM/BinaryInfo.d.ts +15 -8
  159. package/lib/WAM/BinaryInfo.js +14 -7
  160. package/lib/WAM/constants.d.ts +37 -30
  161. package/lib/WAM/constants.js +11983 -19465
  162. package/lib/WAM/encode.d.ts +3 -3
  163. package/lib/WAM/encode.js +109 -95
  164. package/lib/WAM/index.d.ts +3 -4
  165. package/lib/WAM/index.js +23 -4
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
  167. package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
  168. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
  169. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
  170. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
  171. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
  172. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
  173. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
  174. package/lib/WAUSync/Protocols/index.d.ts +6 -6
  175. package/lib/WAUSync/Protocols/index.js +26 -6
  176. package/lib/WAUSync/USyncQuery.d.ts +28 -27
  177. package/lib/WAUSync/USyncQuery.js +62 -68
  178. package/lib/WAUSync/USyncUser.d.ts +10 -15
  179. package/lib/WAUSync/USyncUser.js +19 -20
  180. package/lib/WAUSync/index.d.ts +3 -4
  181. package/lib/WAUSync/index.js +23 -4
  182. package/lib/index.d.ts +12 -11
  183. package/lib/index.js +39 -12
  184. package/package.json +42 -86
  185. package/engine-requirements.js +0 -10
@@ -1,290 +1,545 @@
1
- import NodeCache from '@cacheable/node-cache';
2
- import { Boom } from '@hapi/boom';
3
- import { AsyncLocalStorage } from 'async_hooks';
4
- import { Mutex } from 'async-mutex';
5
- import { randomBytes } from 'crypto';
6
- import PQueue from 'p-queue';
7
- import { DEFAULT_CACHE_TTLS } from '../Defaults/index.js';
8
- import { Curve, signedKeyPair } from './crypto.js';
9
- import { delay, generateRegistrationId } from './generics.js';
10
- import { PreKeyManager } from './pre-key-manager.js';
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
- export function makeCacheableSignalKeyStore(store, logger, _cache) {
18
- const cache = _cache ||
19
- new NodeCache({
20
- stdTTL: DEFAULT_CACHE_TTLS.SIGNAL_STORE, // 5 minutes
21
- useClones: false,
22
- deleteOnExpire: true
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 = (await cache.get(getUniqueId(type, id)));
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
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
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-like transaction capability to the SignalKeyStore
79
- * Uses AsyncLocalStorage for automatic context management
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
- export const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs }) => {
85
- const txStorage = new AsyncLocalStorage();
86
- // Queues for concurrency control (keyed by signal data type - bounded set)
87
- const keyQueues = new Map();
88
- // Transaction mutexes with reference counting for cleanup
89
- const txMutexes = new Map();
90
- const txMutexRefCounts = new Map();
91
- // Pre-key manager for specialized operations
92
- const preKeyManager = new PreKeyManager(state, logger);
93
- /**
94
- * Get or create a queue for a specific key type
95
- */
96
- function getQueue(key) {
97
- if (!keyQueues.has(key)) {
98
- keyQueues.set(key, new PQueue({ concurrency: 1 }));
99
- }
100
- return keyQueues.get(key);
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
- * Get or create a transaction mutex
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
- * Acquire a reference to a transaction mutex
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
- * Release a reference to a transaction mutex and cleanup if no longer needed
121
- */
122
- function releaseTxMutexRef(key) {
123
- const count = (txMutexRefCounts.get(key) ?? 1) - 1;
124
- txMutexRefCounts.set(key, count);
125
- // Cleanup if no more references and mutex is not locked
126
- if (count <= 0) {
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
- * Check if currently in a transaction
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 !!txStorage.getStore();
323
+ return transactionsInProgress > 0
139
324
  }
140
- /**
141
- * Commit transaction with retries
142
- */
143
- async function commitWithRetry(mutations) {
144
- if (Object.keys(mutations).length === 0) {
145
- logger.trace('no mutations in transaction');
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
- for (let attempt = 0; attempt < maxCommitRetries; attempt++) {
333
+ let tries = maxCommitRetries
334
+ while (tries > 0) {
335
+ tries -= 1;
150
336
  try {
151
- await state.set(mutations);
152
- logger.trace({ mutationCount: Object.keys(mutations).length }, 'committed transaction');
153
- return;
337
+ await state.set(mutations)
338
+ logger.trace({ dbQueriesInTransaction }, 'committed transaction')
339
+ return
154
340
  }
155
341
  catch (error) {
156
- const retriesLeft = maxCommitRetries - attempt - 1;
157
- logger.warn(`failed to commit mutations, retries left=${retriesLeft}`);
158
- if (retriesLeft === 0) {
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
- const ctx = txStorage.getStore();
168
- if (!ctx) {
169
- // No transaction - direct read without exclusive lock for concurrency
170
- return state.get(type, ids);
171
- }
172
- // In transaction - check cache first
173
- const cached = ctx.cache[type] || {};
174
- const missing = ids.filter(id => !(id in cached));
175
- if (missing.length > 0) {
176
- ctx.dbQueries++;
177
- logger.trace({ type, count: missing.length }, 'fetching missing keys in transaction');
178
- const fetched = await getTxMutex(type).runExclusive(() => state.get(type, missing));
179
- // Update cache
180
- ctx.cache[type] = ctx.cache[type] || {};
181
- Object.assign(ctx.cache[type], fetched);
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
- // Return requested ids from cache
184
- const result = {};
185
- for (const id of ids) {
186
- const value = ctx.cache[type]?.[id];
187
- if (value !== undefined && value !== null) {
188
- result[id] = value;
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
- const ctx = txStorage.getStore();
195
- if (!ctx) {
196
- // No transaction - direct write with queue protection
197
- const types = Object.keys(data);
198
- // Process pre-keys with validation
199
- for (const type_ of types) {
200
- const type = type_;
201
- if (type === 'pre-key') {
202
- await preKeyManager.validateDeletions(data, type);
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
- // In transaction - update cache and mutations
213
- logger.trace({ types: Object.keys(data) }, 'caching in transaction');
214
- for (const key_ in data) {
215
- const key = key_;
216
- // Ensure structures exist
217
- ctx.cache[key] = ctx.cache[key] || {};
218
- ctx.mutations[key] = ctx.mutations[key] || {};
219
- // Special handling for pre-keys
220
- if (key === 'pre-key') {
221
- await preKeyManager.processOperations(data, key, ctx.cache, ctx.mutations, true);
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
- // Normal key types
225
- Object.assign(ctx.cache[key], data[key]);
226
- Object.assign(ctx.mutations[key], data[key]);
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
- transaction: async (work, key) => {
232
- const existing = txStorage.getStore();
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
- return await mutex.runExclusive(async () => {
243
- const ctx = {
244
- cache: {},
245
- mutations: {},
246
- dbQueries: 0
247
- };
248
- logger.trace('entering transaction');
249
- try {
250
- const result = await txStorage.run(ctx, work);
251
- // Commit mutations
252
- await commitWithRetry(ctx.mutations);
253
- logger.trace({ dbQueries: ctx.dbQueries }, 'transaction completed');
254
- return result;
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
- finally {
263
- releaseTxMutexRef(key);
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
- return id;
278
- };
279
- export const initAuthCreds = () => {
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
- additionalData: undefined
300
- };
301
- };
302
- //# sourceMappingURL=auth-utils.js.map
553
+ routingInfo: undefined
554
+ }
555
+ }
556
+
557
+ module.exports = {
558
+ makeCacheableSignalKeyStore,
559
+ addTransactionCapability,
560
+ initAuthCreds
561
+ }