@periskope/baileys 6.7.18-17-2 → 6.7.18-17-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GroupCipher = void 0;
4
+ /* @ts-ignore */
4
5
  const crypto_1 = require("libsignal/src/crypto");
5
6
  const sender_key_message_1 = require("./sender-key-message");
6
7
  class GroupCipher {
@@ -30,13 +31,9 @@ class GroupCipher {
30
31
  throw new Error('No SenderKeyRecord found for decryption');
31
32
  }
32
33
  const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(null, null, null, null, senderKeyMessageBytes);
33
- let senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
34
- // Fallback: try to get the latest sender key state if specific keyId not found
34
+ const senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
35
35
  if (!senderKeyState) {
36
- senderKeyState = record.getSenderKeyState();
37
- if (!senderKeyState) {
38
- throw new Error('No session found to decrypt message');
39
- }
36
+ throw new Error('No session found to decrypt message');
40
37
  }
41
38
  senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
42
39
  const senderKey = this.getSenderKey(senderKeyState, senderKeyMessage.getIteration());
@@ -1,3 +1,3 @@
1
- import { SignalAuthState } from '../Types';
2
- import { SignalRepository } from '../Types/Signal';
1
+ import type { SignalAuthState } from '../Types';
2
+ import type { SignalRepository } from '../Types/Signal';
3
3
  export declare function makeLibSignalRepository(auth: SignalAuthState): SignalRepository;
@@ -42,12 +42,13 @@ const sender_key_record_1 = require("./Group/sender-key-record");
42
42
  const Group_1 = require("./Group");
43
43
  function makeLibSignalRepository(auth) {
44
44
  const storage = signalStorage(auth);
45
+ const parsedKeys = auth.keys;
45
46
  return {
46
47
  decryptGroupMessage({ group, authorJid, msg }) {
47
48
  const senderName = jidToSignalSenderKeyName(group, authorJid);
48
49
  const cipher = new Group_1.GroupCipher(storage, senderName);
49
50
  // Use transaction to ensure atomicity
50
- return auth.keys.transaction(async () => {
51
+ return parsedKeys.transaction(async () => {
51
52
  return cipher.decrypt(msg);
52
53
  });
53
54
  },
@@ -59,7 +60,11 @@ function makeLibSignalRepository(auth) {
59
60
  const senderName = jidToSignalSenderKeyName(item.groupId, authorJid);
60
61
  const senderMsg = new Group_1.SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage);
61
62
  const senderNameStr = senderName.toString();
62
- return auth.keys.transaction(async () => {
63
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
64
+ if (!senderKey) {
65
+ await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
66
+ }
67
+ return parsedKeys.transaction(async () => {
63
68
  const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
64
69
  if (!senderKey) {
65
70
  await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
@@ -70,8 +75,8 @@ function makeLibSignalRepository(auth) {
70
75
  async decryptMessage({ jid, type, ciphertext }) {
71
76
  const addr = jidToSignalProtocolAddress(jid);
72
77
  const session = new libsignal.SessionCipher(storage, addr);
73
- // Use transaction to ensure atomicityAdd commentMore actions
74
- return auth.keys.transaction(async () => {
78
+ // Use transaction to ensure atomicity
79
+ return parsedKeys.transaction(async () => {
75
80
  let result;
76
81
  switch (type) {
77
82
  case 'pkmsg':
@@ -80,6 +85,8 @@ function makeLibSignalRepository(auth) {
80
85
  case 'msg':
81
86
  result = await session.decryptWhisperMessage(ciphertext);
82
87
  break;
88
+ default:
89
+ throw new Error(`Unknown message type: ${type}`);
83
90
  }
84
91
  return result;
85
92
  });
@@ -87,8 +94,8 @@ function makeLibSignalRepository(auth) {
87
94
  async encryptMessage({ jid, data }) {
88
95
  const addr = jidToSignalProtocolAddress(jid);
89
96
  const cipher = new libsignal.SessionCipher(storage, addr);
90
- // Use transaction to ensure atomicityAdd commentMore actions
91
- return auth.keys.transaction(async () => {
97
+ // Use transaction to ensure atomicity
98
+ return parsedKeys.transaction(async () => {
92
99
  const { type: sigType, body } = await cipher.encrypt(data);
93
100
  const type = sigType === 3 ? 'pkmsg' : 'msg';
94
101
  return { type, ciphertext: Buffer.from(body, 'binary') };
@@ -98,8 +105,7 @@ function makeLibSignalRepository(auth) {
98
105
  const senderName = jidToSignalSenderKeyName(group, meId);
99
106
  const builder = new Group_1.GroupSessionBuilder(storage);
100
107
  const senderNameStr = senderName.toString();
101
- // Use transaction to ensure atomicity
102
- return auth.keys.transaction(async () => {
108
+ return parsedKeys.transaction(async () => {
103
109
  const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
104
110
  if (!senderKey) {
105
111
  await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
@@ -115,8 +121,7 @@ function makeLibSignalRepository(auth) {
115
121
  },
116
122
  async injectE2ESession({ jid, session }) {
117
123
  const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid));
118
- // Use transaction to ensure atomicity
119
- return auth.keys.transaction(async () => {
124
+ return parsedKeys.transaction(async () => {
120
125
  await cipher.initOutgoing(session);
121
126
  });
122
127
  },
@@ -140,6 +145,7 @@ function signalStorage({ creds, keys }) {
140
145
  return libsignal.SessionRecord.deserialize(sess);
141
146
  }
142
147
  },
148
+ // TODO: Replace with libsignal.SessionRecord when type exports are added to libsignal
143
149
  storeSession: async (id, session) => {
144
150
  await keys.set({ session: { [id]: session.serialize() } });
145
151
  },
@@ -1,5 +1,5 @@
1
1
  import type { AuthenticationCreds, CacheStore, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types';
2
- import { ILogger } from './logger';
2
+ import type { ILogger } from './logger';
3
3
  /**
4
4
  * Adds caching capability to a SignalKeyStore
5
5
  * @param store the store to add caching to
@@ -71,11 +71,9 @@ function makeCacheableSignalKeyStore(store, logger, _cache) {
71
71
  });
72
72
  },
73
73
  async clear() {
74
- return cacheMutex.runExclusive(async () => {
75
- var _a;
76
- cache.flushAll();
77
- await ((_a = store.clear) === null || _a === void 0 ? void 0 : _a.call(store));
78
- });
74
+ var _a;
75
+ cache.flushAll();
76
+ await ((_a = store.clear) === null || _a === void 0 ? void 0 : _a.call(store));
79
77
  }
80
78
  };
81
79
  }
@@ -113,8 +111,12 @@ async function handlePreKeyOperations(data, keyType, transactionCache, mutations
113
111
  }
114
112
  // Process updates first (no validation needed)
115
113
  for (const keyId of updateKeys) {
116
- transactionCache[keyType][keyId] = keyData[keyId];
117
- mutations[keyType][keyId] = keyData[keyId];
114
+ if (transactionCache[keyType]) {
115
+ transactionCache[keyType][keyId] = keyData[keyId];
116
+ }
117
+ if (mutations[keyType]) {
118
+ mutations[keyType][keyId] = keyData[keyId];
119
+ }
118
120
  }
119
121
  // Process deletions with validation
120
122
  if (deletionKeys.length === 0)
@@ -122,9 +124,12 @@ async function handlePreKeyOperations(data, keyType, transactionCache, mutations
122
124
  if (isInTransaction) {
123
125
  // In transaction, only allow deletion if key exists in cache
124
126
  for (const keyId of deletionKeys) {
125
- if (transactionCache[keyType][keyId]) {
127
+ if (transactionCache[keyType]) {
126
128
  transactionCache[keyType][keyId] = null;
127
- mutations[keyType][keyId] = null;
129
+ if (mutations[keyType]) {
130
+ // Mark for deletion in mutations
131
+ mutations[keyType][keyId] = null;
132
+ }
128
133
  }
129
134
  else {
130
135
  logger.warn(`Skipping deletion of non-existent ${keyType} in transaction: ${keyId}`);
@@ -138,8 +143,10 @@ async function handlePreKeyOperations(data, keyType, transactionCache, mutations
138
143
  const existingKeys = await state.get(keyType, deletionKeys);
139
144
  for (const keyId of deletionKeys) {
140
145
  if (existingKeys[keyId]) {
141
- transactionCache[keyType][keyId] = null;
142
- mutations[keyType][keyId] = null;
146
+ if (transactionCache[keyType])
147
+ transactionCache[keyType][keyId] = null;
148
+ if (mutations[keyType])
149
+ mutations[keyType][keyId] = null;
143
150
  }
144
151
  else {
145
152
  logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
@@ -170,7 +177,8 @@ async function processPreKeyDeletions(data, keyType, state, logger) {
170
177
  const existingKeys = await state.get(keyType, [keyId]);
171
178
  if (!existingKeys[keyId]) {
172
179
  logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
173
- delete data[keyType][keyId];
180
+ if (data[keyType])
181
+ delete data[keyType][keyId];
174
182
  }
175
183
  }
176
184
  }
@@ -208,30 +216,6 @@ async function withMutexes(keyTypes, getKeyTypeMutex, fn) {
208
216
  }
209
217
  }
210
218
  }
211
- /**
212
- * Attempts to commit transaction with retry mechanism
213
- * Uses async-mutex's withTimeout for better timeout handling
214
- */
215
- async function commitWithRetry(mutations, state, getKeyTypeMutex, maxRetries, delayMs, logger) {
216
- let tries = maxRetries;
217
- while (tries > 0) {
218
- tries -= 1;
219
- try {
220
- // Use basic withMutexes - withTimeout is for decorating mutexes, not functions
221
- await withMutexes(Object.keys(mutations), getKeyTypeMutex, async () => {
222
- await state.set(mutations);
223
- logger.trace('committed transaction');
224
- });
225
- break;
226
- }
227
- catch (error) {
228
- logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
229
- if (tries > 0) {
230
- await (0, generics_1.delay)(delayMs);
231
- }
232
- }
233
- }
234
- }
235
219
  /**
236
220
  * Adds DB like transaction capability (https://en.wikipedia.org/wiki/Database_transaction) to the SignalKeyStore,
237
221
  * this allows batch read & write operations & improves the performance of the lib
@@ -240,17 +224,52 @@ async function commitWithRetry(mutations, state, getKeyTypeMutex, maxRetries, de
240
224
  * @returns SignalKeyStore with transaction capability
241
225
  */
242
226
  const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetweenTriesMs }) => {
243
- // Mutex for each key type (session, pre-key, etc.)
244
- const keyTypeMutexes = new Map();
245
- // Per-sender-key-name mutexes for fine-grained serialization
246
- const senderKeyMutexes = new Map();
247
- // Global transaction mutex
248
- const transactionMutex = new async_mutex_1.Mutex();
249
227
  // number of queries made to the DB during the transaction
250
228
  // only there for logging purposes
251
229
  let dbQueriesInTransaction = 0;
252
230
  let transactionCache = {};
253
231
  let mutations = {};
232
+ // Mutex for each key type (session, pre-key, etc.)
233
+ const keyTypeMutexes = new Map();
234
+ // Per-sender-key-name mutexes for fine-grained serialization
235
+ const senderKeyMutexes = new Map();
236
+ // Track last usage time for sender key mutexes (for cleanup)
237
+ const senderKeyMutexLastUsed = new Map();
238
+ // Mutex expiration time: 1 hour in milliseconds
239
+ const SENDER_KEY_MUTEX_EXPIRY_MS = 60 * 60 * 1000;
240
+ // Cleanup interval: every 30 minutes
241
+ const CLEANUP_INTERVAL_MS = 30 * 60 * 1000;
242
+ // Cleanup timer
243
+ let cleanupTimer = null;
244
+ // Start cleanup timer if not already running
245
+ function startCleanupTimer() {
246
+ if (!cleanupTimer) {
247
+ cleanupTimer = setInterval(() => {
248
+ cleanupExpiredSenderKeyMutexes();
249
+ }, CLEANUP_INTERVAL_MS);
250
+ }
251
+ }
252
+ // Clean up expired sender key mutexes
253
+ function cleanupExpiredSenderKeyMutexes() {
254
+ const now = Date.now();
255
+ const expiredKeys = [];
256
+ for (const [senderKeyName, lastUsed] of senderKeyMutexLastUsed.entries()) {
257
+ if (now - lastUsed > SENDER_KEY_MUTEX_EXPIRY_MS) {
258
+ const mutex = senderKeyMutexes.get(senderKeyName);
259
+ // Only remove if mutex is not currently being used
260
+ if (mutex && !mutex.isLocked()) {
261
+ expiredKeys.push(senderKeyName);
262
+ }
263
+ }
264
+ }
265
+ if (expiredKeys.length > 0) {
266
+ for (const key of expiredKeys) {
267
+ senderKeyMutexes.delete(key);
268
+ senderKeyMutexLastUsed.delete(key);
269
+ }
270
+ logger.info({ expiredKeys: expiredKeys.length }, 'cleaned up expired sender key mutexes');
271
+ }
272
+ }
254
273
  let transactionsInProgress = 0;
255
274
  // Get or create a mutex for a specific key type
256
275
  function getKeyTypeMutex(type) {
@@ -267,9 +286,14 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
267
286
  let mutex = senderKeyMutexes.get(senderKeyName);
268
287
  if (!mutex) {
269
288
  mutex = new async_mutex_1.Mutex();
289
+ if (senderKeyMutexes.size === 0) {
290
+ startCleanupTimer();
291
+ }
270
292
  senderKeyMutexes.set(senderKeyName, mutex);
271
293
  logger.info({ senderKeyName }, 'created new sender key mutex');
272
294
  }
295
+ // Update last used time
296
+ senderKeyMutexLastUsed.set(senderKeyName, Date.now());
273
297
  return mutex;
274
298
  }
275
299
  // Sender key operations with proper mutex sequencing
@@ -341,10 +365,11 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
341
365
  set: async (data) => {
342
366
  if (isInTransaction()) {
343
367
  logger.trace({ types: Object.keys(data) }, 'caching in transaction');
344
- for (const key in data) {
368
+ for (const key_ in data) {
369
+ const key = key_;
345
370
  transactionCache[key] = transactionCache[key] || {};
346
371
  // Special handling for pre-keys and signed-pre-keys
347
- if (key === 'pre-key' || key === 'signed-pre-key') {
372
+ if (key === 'pre-key') {
348
373
  await handlePreKeyOperations(data, key, transactionCache, mutations, logger, true);
349
374
  }
350
375
  else {
@@ -363,6 +388,7 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
363
388
  for (const senderKeyName of senderKeyNames) {
364
389
  await queueSenderKeyOperation(senderKeyName, async () => {
365
390
  // Create data subset for this specific sender key
391
+ // @ts-ignore
366
392
  const senderKeyData = {
367
393
  'sender-key': {
368
394
  [senderKeyName]: data['sender-key'][senderKeyName]
@@ -380,8 +406,9 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
380
406
  if (Object.keys(nonSenderKeyData).length > 0) {
381
407
  await withMutexes(Object.keys(nonSenderKeyData), getKeyTypeMutex, async () => {
382
408
  // Process pre-keys and signed-pre-keys separately with specialized mutexes
383
- for (const keyType in nonSenderKeyData) {
384
- if (keyType === 'pre-key' || keyType === 'signed-pre-key') {
409
+ for (const key_ in nonSenderKeyData) {
410
+ const keyType = key_;
411
+ if (keyType === 'pre-key') {
385
412
  await processPreKeyDeletions(nonSenderKeyData, keyType, state, logger);
386
413
  }
387
414
  }
@@ -394,8 +421,9 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
394
421
  // No sender keys - use original logic
395
422
  await withMutexes(Object.keys(data), getKeyTypeMutex, async () => {
396
423
  // Process pre-keys and signed-pre-keys separately with specialized mutexes
397
- for (const keyType in data) {
398
- if (keyType === 'pre-key' || keyType === 'signed-pre-key') {
424
+ for (const key_ in data) {
425
+ const keyType = key_;
426
+ if (keyType === 'pre-key') {
399
427
  await processPreKeyDeletions(data, keyType, state, logger);
400
428
  }
401
429
  }
@@ -406,49 +434,49 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
406
434
  }
407
435
  },
408
436
  isInTransaction,
409
- ...(state.clear ? { clear: state.clear } : {}),
410
437
  async transaction(work) {
411
- return transactionMutex.acquire().then(async (releaseTxMutex) => {
412
- let result;
413
- try {
414
- transactionsInProgress += 1;
415
- if (transactionsInProgress === 1) {
416
- logger.trace('entering transaction');
417
- }
418
- // Release the transaction mutex now that we've updated the counter
419
- // This allows other transactions to start preparing
420
- releaseTxMutex();
421
- try {
422
- result = await work();
423
- // commit if this is the outermost transaction
424
- if (transactionsInProgress === 1) {
425
- const hasMutations = Object.keys(mutations).length > 0;
426
- if (hasMutations) {
427
- logger.trace('committing transaction');
428
- await commitWithRetry(mutations, state, getKeyTypeMutex, maxCommitRetries, delayBetweenTriesMs, logger);
429
- logger.trace({ dbQueriesInTransaction }, 'transaction completed');
438
+ let result;
439
+ transactionsInProgress += 1;
440
+ if (transactionsInProgress === 1) {
441
+ logger.trace('entering transaction');
442
+ }
443
+ try {
444
+ result = await work();
445
+ // commit if this is the outermost transaction
446
+ if (transactionsInProgress === 1) {
447
+ if (Object.keys(mutations).length) {
448
+ logger.trace('committing transaction');
449
+ // retry mechanism to ensure we've some recovery
450
+ // in case a transaction fails in the first attempt
451
+ let tries = maxCommitRetries;
452
+ while (tries) {
453
+ tries -= 1;
454
+ //eslint-disable-next-line max-depth
455
+ try {
456
+ await state.set(mutations);
457
+ logger.trace({ dbQueriesInTransaction }, 'committed transaction');
458
+ break;
430
459
  }
431
- else {
432
- logger.trace('no mutations in transaction');
460
+ catch (error) {
461
+ logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
462
+ await (0, generics_1.delay)(delayBetweenTriesMs);
433
463
  }
434
464
  }
435
465
  }
436
- finally {
437
- transactionsInProgress -= 1;
438
- if (transactionsInProgress === 0) {
439
- transactionCache = {};
440
- mutations = {};
441
- dbQueriesInTransaction = 0;
442
- }
466
+ else {
467
+ logger.trace('no mutations in transaction');
443
468
  }
444
- return result;
445
469
  }
446
- catch (error) {
447
- // If we haven't released the transaction mutex yet, release it
448
- releaseTxMutex();
449
- throw error;
470
+ }
471
+ finally {
472
+ transactionsInProgress -= 1;
473
+ if (transactionsInProgress === 0) {
474
+ transactionCache = {};
475
+ mutations = {};
476
+ dbQueriesInTransaction = 0;
450
477
  }
451
- });
478
+ }
479
+ return result;
452
480
  }
453
481
  };
454
482
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@periskope/baileys",
3
- "version": "6.7.18-17-2",
3
+ "version": "6.7.18-17-3",
4
4
  "description": "WhatsApp API",
5
5
  "keywords": [
6
6
  "periskope",