@periskope/baileys 6.7.18-17-7 → 6.7.18-17-9

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.
@@ -8,7 +8,6 @@ export declare class GroupCipher {
8
8
  private readonly senderKeyStore;
9
9
  private readonly senderKeyName;
10
10
  constructor(senderKeyStore: SenderKeyStore, senderKeyName: SenderKeyName);
11
- private queueJob;
12
11
  encrypt(paddedPlaintext: Uint8Array | string): Promise<Uint8Array>;
13
12
  decrypt(senderKeyMessageBytes: Uint8Array): Promise<Uint8Array>;
14
13
  private getSenderKey;
@@ -1,55 +1,45 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.GroupCipher = void 0;
4
+ /* @ts-ignore */
7
5
  const crypto_1 = require("libsignal/src/crypto");
8
- const queue_job_1 = __importDefault(require("./queue-job"));
9
6
  const sender_key_message_1 = require("./sender-key-message");
10
7
  class GroupCipher {
11
8
  constructor(senderKeyStore, senderKeyName) {
12
9
  this.senderKeyStore = senderKeyStore;
13
10
  this.senderKeyName = senderKeyName;
14
11
  }
15
- queueJob(awaitable) {
16
- return (0, queue_job_1.default)(this.senderKeyName.toString(), awaitable);
17
- }
18
12
  async encrypt(paddedPlaintext) {
19
- return await this.queueJob(async () => {
20
- const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
21
- if (!record) {
22
- throw new Error('No SenderKeyRecord found for encryption');
23
- }
24
- const senderKeyState = record.getSenderKeyState();
25
- if (!senderKeyState) {
26
- throw new Error('No session to encrypt message');
27
- }
28
- const iteration = senderKeyState.getSenderChainKey().getIteration();
29
- const senderKey = this.getSenderKey(senderKeyState, iteration === 0 ? 0 : iteration + 1);
30
- const ciphertext = await this.getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext);
31
- const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(senderKeyState.getKeyId(), senderKey.getIteration(), ciphertext, senderKeyState.getSigningKeyPrivate());
32
- await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
33
- return senderKeyMessage.serialize();
34
- });
13
+ const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
14
+ if (!record) {
15
+ throw new Error('No SenderKeyRecord found for encryption');
16
+ }
17
+ const senderKeyState = record.getSenderKeyState();
18
+ if (!senderKeyState) {
19
+ throw new Error('No session to encrypt message');
20
+ }
21
+ const iteration = senderKeyState.getSenderChainKey().getIteration();
22
+ const senderKey = this.getSenderKey(senderKeyState, iteration === 0 ? 0 : iteration + 1);
23
+ const ciphertext = await this.getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext);
24
+ const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(senderKeyState.getKeyId(), senderKey.getIteration(), ciphertext, senderKeyState.getSigningKeyPrivate());
25
+ await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
26
+ return senderKeyMessage.serialize();
35
27
  }
36
28
  async decrypt(senderKeyMessageBytes) {
37
- return await this.queueJob(async () => {
38
- const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
39
- if (!record) {
40
- throw new Error('No SenderKeyRecord found for decryption');
41
- }
42
- const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(null, null, null, null, senderKeyMessageBytes);
43
- const senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
44
- if (!senderKeyState) {
45
- throw new Error('No session found to decrypt message');
46
- }
47
- senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
48
- const senderKey = this.getSenderKey(senderKeyState, senderKeyMessage.getIteration());
49
- const plaintext = await this.getPlainText(senderKey.getIv(), senderKey.getCipherKey(), senderKeyMessage.getCipherText());
50
- await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
51
- return plaintext;
52
- });
29
+ const record = await this.senderKeyStore.loadSenderKey(this.senderKeyName);
30
+ if (!record) {
31
+ throw new Error('No SenderKeyRecord found for decryption');
32
+ }
33
+ const senderKeyMessage = new sender_key_message_1.SenderKeyMessage(null, null, null, null, senderKeyMessageBytes);
34
+ const senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId());
35
+ if (!senderKeyState) {
36
+ throw new Error('No session found to decrypt message');
37
+ }
38
+ senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic());
39
+ const senderKey = this.getSenderKey(senderKeyState, senderKeyMessage.getIteration());
40
+ const plaintext = await this.getPlainText(senderKey.getIv(), senderKey.getCipherKey(), senderKeyMessage.getCipherText());
41
+ await this.senderKeyStore.storeSenderKey(this.senderKeyName, record);
42
+ return plaintext;
53
43
  }
54
44
  getSenderKey(senderKeyState, iteration) {
55
45
  let senderChainKey = senderKeyState.getSenderChainKey();
@@ -51,11 +51,26 @@ function makeLibSignalRepository(auth) {
51
51
  max: 500,
52
52
  ttl: 5 * 60 * 1000
53
53
  });
54
+ const parsedKeys = auth.keys;
55
+ function isLikelySyncMessage(addr) {
56
+ const key = addr.toString();
57
+ // Only bypass for WhatsApp system addresses, not regular user contacts
58
+ // Be very specific about sync service patterns
59
+ return (key.includes('@lid.whatsapp.net') || // WhatsApp system messages
60
+ key.includes('@broadcast') || // Broadcast messages
61
+ key.includes('@newsletter') || // Newsletter messages
62
+ key === 'status@broadcast' || // Status updates
63
+ key.includes('@g.us.history') || // Group history sync
64
+ key.includes('.whatsapp.net.history'));
65
+ }
54
66
  const repository = {
55
67
  decryptGroupMessage({ group, authorJid, msg }) {
56
68
  const senderName = jidToSignalSenderKeyName(group, authorJid);
57
69
  const cipher = new Group_1.GroupCipher(storage, senderName);
58
- return cipher.decrypt(msg);
70
+ // Use transaction to ensure atomicity
71
+ return parsedKeys.transaction(async () => {
72
+ return cipher.decrypt(msg);
73
+ }, group);
59
74
  },
60
75
  async processSenderKeyDistributionMessage({ item, authorJid }) {
61
76
  const builder = new Group_1.GroupSessionBuilder(storage);
@@ -69,23 +84,39 @@ function makeLibSignalRepository(auth) {
69
84
  if (!senderKey) {
70
85
  await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
71
86
  }
72
- await builder.process(senderName, senderMsg);
87
+ return parsedKeys.transaction(async () => {
88
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
89
+ if (!senderKey) {
90
+ await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
91
+ }
92
+ await builder.process(senderName, senderMsg);
93
+ }, item.groupId);
73
94
  },
74
95
  async decryptMessage({ jid, type, ciphertext }) {
75
96
  const addr = jidToSignalProtocolAddress(jid);
76
97
  const session = new libsignal.SessionCipher(storage, addr);
77
- let result;
78
- switch (type) {
79
- case 'pkmsg':
80
- result = await session.decryptPreKeyWhisperMessage(ciphertext);
81
- break;
82
- case 'msg':
83
- result = await session.decryptWhisperMessage(ciphertext);
84
- break;
85
- default:
86
- throw new Error(`Unknown message type: ${type}`);
98
+ async function doDecrypt() {
99
+ let result;
100
+ switch (type) {
101
+ case 'pkmsg':
102
+ result = await session.decryptPreKeyWhisperMessage(ciphertext);
103
+ break;
104
+ case 'msg':
105
+ result = await session.decryptWhisperMessage(ciphertext);
106
+ break;
107
+ }
108
+ return result;
109
+ }
110
+ if (isLikelySyncMessage(addr)) {
111
+ // If it's a sync message, we can skip the transaction
112
+ // as it is likely to be a system message that doesn't require strict atomicity
113
+ return await doDecrypt();
87
114
  }
88
- return result;
115
+ // If it's not a sync message, we need to ensure atomicity
116
+ // For regular messages, we use a transaction to ensure atomicity
117
+ return parsedKeys.transaction(async () => {
118
+ return await doDecrypt();
119
+ }, jid);
89
120
  },
90
121
  async encryptMessage({ jid, data }) {
91
122
  // LID SINGLE SOURCE OF TRUTH: Always prefer LID when available
@@ -114,29 +145,36 @@ function makeLibSignalRepository(auth) {
114
145
  }
115
146
  const addr = jidToSignalProtocolAddress(encryptionJid);
116
147
  const cipher = new libsignal.SessionCipher(storage, addr);
117
- const { type: sigType, body } = await cipher.encrypt(data);
118
- const type = sigType === 3 ? 'pkmsg' : 'msg';
119
- return { type, ciphertext: Buffer.from(body, 'binary') };
148
+ // Use transaction to ensure atomicity
149
+ return parsedKeys.transaction(async () => {
150
+ const { type: sigType, body } = await cipher.encrypt(data);
151
+ const type = sigType === 3 ? 'pkmsg' : 'msg';
152
+ return { type, ciphertext: Buffer.from(body, 'binary') };
153
+ }, jid);
120
154
  },
121
155
  async encryptGroupMessage({ group, meId, data }) {
122
156
  const senderName = jidToSignalSenderKeyName(group, meId);
123
157
  const builder = new Group_1.GroupSessionBuilder(storage);
124
158
  const senderNameStr = senderName.toString();
125
- const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
126
- if (!senderKey) {
127
- await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
128
- }
129
- const senderKeyDistributionMessage = await builder.create(senderName);
130
- const session = new Group_1.GroupCipher(storage, senderName);
131
- const ciphertext = await session.encrypt(data);
132
- return {
133
- ciphertext,
134
- senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
135
- };
159
+ return parsedKeys.transaction(async () => {
160
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
161
+ if (!senderKey) {
162
+ await storage.storeSenderKey(senderName, new sender_key_record_1.SenderKeyRecord());
163
+ }
164
+ const senderKeyDistributionMessage = await builder.create(senderName);
165
+ const session = new Group_1.GroupCipher(storage, senderName);
166
+ const ciphertext = await session.encrypt(data);
167
+ return {
168
+ ciphertext,
169
+ senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
170
+ };
171
+ }, group);
136
172
  },
137
173
  async injectE2ESession({ jid, session }) {
138
174
  const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid));
139
- await cipher.initOutgoing(session);
175
+ return parsedKeys.transaction(async () => {
176
+ await cipher.initOutgoing(session);
177
+ }, jid);
140
178
  },
141
179
  jidToSignalProtocolAddress(jid) {
142
180
  return jidToSignalProtocolAddress(jid).toString();
@@ -165,9 +203,9 @@ function makeLibSignalRepository(auth) {
165
203
  },
166
204
  async deleteSession(jid) {
167
205
  const addr = jidToSignalProtocolAddress(jid);
168
- return auth.keys.transaction(async () => {
206
+ return parsedKeys.transaction(async () => {
169
207
  await auth.keys.set({ session: { [addr.toString()]: null } });
170
- });
208
+ }, jid);
171
209
  },
172
210
  async migrateSession(fromJid, toJid) {
173
211
  // Only migrate PN → LID
@@ -191,7 +229,7 @@ function makeLibSignalRepository(auth) {
191
229
  recentMigrations.set(migrationKey, true);
192
230
  return;
193
231
  }
194
- return auth.keys.transaction(async () => {
232
+ return parsedKeys.transaction(async () => {
195
233
  // Store mapping
196
234
  await lidMapping.storeLIDPNMapping(toJid, fromJid);
197
235
  // Load and copy session
@@ -207,7 +245,7 @@ function makeLibSignalRepository(auth) {
207
245
  await auth.keys.set({ session: { [fromAddr.toString()]: null } });
208
246
  }
209
247
  recentMigrations.set(migrationKey, true);
210
- });
248
+ }, fromJid);
211
249
  },
212
250
  async encryptMessageWithWire({ encryptionJid, wireJid, data }) {
213
251
  const result = await repository.encryptMessage({ jid: encryptionJid, data });
@@ -36,7 +36,7 @@ class LIDMappingStore {
36
36
  [`${lidUser}_reverse`]: pnUser // "102765716062358_reverse" -> "554396160286"
37
37
  }
38
38
  });
39
- });
39
+ }, pnJid);
40
40
  logger_1.default.trace(`USER LID mapping stored: PN ${pnUser} → LID ${lidUser}`);
41
41
  }
42
42
  /**
@@ -356,6 +356,7 @@ const makeChatsSocket = (config) => {
356
356
  };
357
357
  };
358
358
  const resyncAppState = ev.createBufferedFunction(async (collections, isInitialSync) => {
359
+ var _a, _b;
359
360
  // we use this to determine which events to fire
360
361
  // otherwise when we resync from scratch -- all notifications will fire
361
362
  const initialVersionMap = {};
@@ -455,7 +456,7 @@ const makeChatsSocket = (config) => {
455
456
  }
456
457
  }
457
458
  }
458
- });
459
+ }, ((_b = (_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.me) === null || _b === void 0 ? void 0 : _b.id) || 'resync-app-state');
459
460
  const { onMutation } = newAppStateChunkHandler(isInitialSync);
460
461
  for (const key in globalMutationMap) {
461
462
  onMutation(globalMutationMap[key]);
@@ -578,6 +579,7 @@ const makeChatsSocket = (config) => {
578
579
  let initial;
579
580
  let encodeResult;
580
581
  await processingMutex.mutex(async () => {
582
+ var _a, _b;
581
583
  await authState.keys.transaction(async () => {
582
584
  logger.debug({ patch: patchCreate }, 'applying app patch');
583
585
  await resyncAppState([name], false);
@@ -618,7 +620,7 @@ const makeChatsSocket = (config) => {
618
620
  };
619
621
  await query(node);
620
622
  await authState.keys.set({ 'app-state-sync-version': { [name]: state } });
621
- });
623
+ }, ((_b = (_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.me) === null || _b === void 0 ? void 0 : _b.id) || 'app-patch');
622
624
  });
623
625
  if (config.emitOwnEvents) {
624
626
  const { onMutation } = newAppStateChunkHandler(false);
@@ -110,6 +110,7 @@ const makeMessagesRecvSocket = (config) => {
110
110
  await query(stanza);
111
111
  };
112
112
  const sendRetryRequest = async (node, forceIncludeKeys = false) => {
113
+ var _a, _b;
113
114
  const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
114
115
  const { key: msgKey } = fullMessage;
115
116
  const msgId = msgKey.id;
@@ -123,7 +124,7 @@ const makeMessagesRecvSocket = (config) => {
123
124
  retryCount += 1;
124
125
  msgRetryCache.set(key, retryCount);
125
126
  const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
126
- if (retryCount === 1) {
127
+ if (retryCount <= 2) {
127
128
  //request a resend via phone
128
129
  const msgId = await requestPlaceholderResend(msgKey);
129
130
  logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
@@ -180,7 +181,7 @@ const makeMessagesRecvSocket = (config) => {
180
181
  }
181
182
  await sendNode(receipt);
182
183
  logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
183
- });
184
+ }, ((_b = (_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.me) === null || _b === void 0 ? void 0 : _b.id) || 'sendRetryRequest');
184
185
  };
185
186
  const handleEncryptNotification = async (node) => {
186
187
  const from = node.attrs.from;
@@ -872,7 +872,7 @@ const makeMessagesSocket = (config) => {
872
872
  }
873
873
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
874
874
  await sendNode(stanza);
875
- });
875
+ }, meId);
876
876
  return msgId;
877
877
  };
878
878
  const getMessageType = (message) => {
@@ -192,13 +192,14 @@ const makeSocket = (config) => {
192
192
  };
193
193
  /** generates and uploads a set of pre-keys to the server */
194
194
  const uploadPreKeys = async (count = Defaults_1.INITIAL_PREKEY_COUNT) => {
195
+ var _a, _b;
195
196
  await keys.transaction(async () => {
196
197
  logger.info({ count }, 'uploading pre-keys');
197
198
  const { update, node } = await (0, Utils_1.getNextPreKeysNode)({ creds, keys }, count);
198
199
  await query(node);
199
200
  ev.emit('creds.update', update);
200
201
  logger.info({ count }, 'uploaded pre-keys');
201
- });
202
+ }, ((_b = (_a = authState === null || authState === void 0 ? void 0 : authState.creds) === null || _a === void 0 ? void 0 : _a.me) === null || _b === void 0 ? void 0 : _b.id) || 'pre-keys');
202
203
  };
203
204
  const uploadPreKeysToServerIfRequired = async () => {
204
205
  const preKeyCount = await getAvailablePreKeysOnServer();
@@ -87,7 +87,7 @@ export type SignalKeyStore = {
87
87
  };
88
88
  export type SignalKeyStoreWithTransaction = SignalKeyStore & {
89
89
  isInTransaction: () => boolean;
90
- transaction<T>(exec: () => Promise<T>): Promise<T>;
90
+ transaction<T>(exec: () => Promise<T>, key: string): Promise<T>;
91
91
  };
92
92
  export type TransactionCapabilityOptions = {
93
93
  maxCommitRetries: number;
@@ -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
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.initAuthCreds = exports.addTransactionCapability = void 0;
7
7
  exports.makeCacheableSignalKeyStore = makeCacheableSignalKeyStore;
8
8
  const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
9
+ const async_mutex_1 = require("async-mutex");
9
10
  const crypto_1 = require("crypto");
10
11
  const Defaults_1 = require("../Defaults");
11
12
  const crypto_2 = require("./crypto");
@@ -23,45 +24,51 @@ function makeCacheableSignalKeyStore(store, logger, _cache) {
23
24
  useClones: false,
24
25
  deleteOnExpire: true
25
26
  });
27
+ // Mutex for protecting cache operations
28
+ const cacheMutex = new async_mutex_1.Mutex();
26
29
  function getUniqueId(type, id) {
27
30
  return `${type}.${id}`;
28
31
  }
29
32
  return {
30
33
  async get(type, ids) {
31
- const data = {};
32
- const idsToFetch = [];
33
- for (const id of ids) {
34
- const item = cache.get(getUniqueId(type, id));
35
- if (typeof item !== 'undefined') {
36
- data[id] = item;
37
- }
38
- else {
39
- idsToFetch.push(id);
40
- }
41
- }
42
- if (idsToFetch.length) {
43
- logger === null || logger === void 0 ? void 0 : logger.trace({ items: idsToFetch.length }, 'loading from store');
44
- const fetched = await store.get(type, idsToFetch);
45
- for (const id of idsToFetch) {
46
- const item = fetched[id];
47
- if (item) {
34
+ return cacheMutex.runExclusive(async () => {
35
+ const data = {};
36
+ const idsToFetch = [];
37
+ for (const id of ids) {
38
+ const item = cache.get(getUniqueId(type, id));
39
+ if (typeof item !== 'undefined') {
48
40
  data[id] = item;
49
- cache.set(getUniqueId(type, id), item);
41
+ }
42
+ else {
43
+ idsToFetch.push(id);
50
44
  }
51
45
  }
52
- }
53
- return data;
46
+ if (idsToFetch.length) {
47
+ logger === null || logger === void 0 ? void 0 : logger.trace({ items: idsToFetch.length }, 'loading from store');
48
+ const fetched = await store.get(type, idsToFetch);
49
+ for (const id of idsToFetch) {
50
+ const item = fetched[id];
51
+ if (item) {
52
+ data[id] = item;
53
+ cache.set(getUniqueId(type, id), item);
54
+ }
55
+ }
56
+ }
57
+ return data;
58
+ });
54
59
  },
55
60
  async set(data) {
56
- let keys = 0;
57
- for (const type in data) {
58
- for (const id in data[type]) {
59
- cache.set(getUniqueId(type, id), data[type][id]);
60
- keys += 1;
61
+ return cacheMutex.runExclusive(async () => {
62
+ let keys = 0;
63
+ for (const type in data) {
64
+ for (const id in data[type]) {
65
+ cache.set(getUniqueId(type, id), data[type][id]);
66
+ keys += 1;
67
+ }
61
68
  }
62
- }
63
- logger === null || logger === void 0 ? void 0 : logger.trace({ keys }, 'updated cache');
64
- await store.set(data);
69
+ logger === null || logger === void 0 ? void 0 : logger.trace({ keys }, 'updated cache');
70
+ await store.set(data);
71
+ });
65
72
  },
66
73
  async clear() {
67
74
  var _a;
@@ -70,6 +77,145 @@ function makeCacheableSignalKeyStore(store, logger, _cache) {
70
77
  }
71
78
  };
72
79
  }
80
+ // Module-level specialized mutexes for pre-key operations
81
+ const preKeyMutex = new async_mutex_1.Mutex();
82
+ const signedPreKeyMutex = new async_mutex_1.Mutex();
83
+ /**
84
+ * Get the appropriate mutex for the key type
85
+ */
86
+ const getPreKeyMutex = (keyType) => {
87
+ return keyType === 'signed-pre-key' ? signedPreKeyMutex : preKeyMutex;
88
+ };
89
+ /**
90
+ * Handles pre-key operations with mutex protection
91
+ */
92
+ async function handlePreKeyOperations(data, keyType, transactionCache, mutations, logger, isInTransaction, state) {
93
+ const mutex = getPreKeyMutex(keyType);
94
+ await mutex.runExclusive(async () => {
95
+ const keyData = data[keyType];
96
+ if (!keyData)
97
+ return;
98
+ // Ensure structures exist
99
+ transactionCache[keyType] = transactionCache[keyType] || {};
100
+ mutations[keyType] = mutations[keyType] || {};
101
+ // Separate deletions from updates for batch processing
102
+ const deletionKeys = [];
103
+ const updateKeys = [];
104
+ for (const keyId in keyData) {
105
+ if (keyData[keyId] === null) {
106
+ deletionKeys.push(keyId);
107
+ }
108
+ else {
109
+ updateKeys.push(keyId);
110
+ }
111
+ }
112
+ // Process updates first (no validation needed)
113
+ for (const keyId of updateKeys) {
114
+ if (transactionCache[keyType]) {
115
+ transactionCache[keyType][keyId] = keyData[keyId];
116
+ }
117
+ if (mutations[keyType]) {
118
+ mutations[keyType][keyId] = keyData[keyId];
119
+ }
120
+ }
121
+ // Process deletions with validation
122
+ if (deletionKeys.length === 0)
123
+ return;
124
+ if (isInTransaction) {
125
+ // In transaction, only allow deletion if key exists in cache
126
+ for (const keyId of deletionKeys) {
127
+ if (transactionCache[keyType]) {
128
+ transactionCache[keyType][keyId] = null;
129
+ if (mutations[keyType]) {
130
+ // Mark for deletion in mutations
131
+ mutations[keyType][keyId] = null;
132
+ }
133
+ }
134
+ else {
135
+ logger.warn(`Skipping deletion of non-existent ${keyType} in transaction: ${keyId}`);
136
+ }
137
+ }
138
+ return;
139
+ }
140
+ // Outside transaction, batch validate all deletions
141
+ if (!state)
142
+ return;
143
+ const existingKeys = await state.get(keyType, deletionKeys);
144
+ for (const keyId of deletionKeys) {
145
+ if (existingKeys[keyId]) {
146
+ if (transactionCache[keyType])
147
+ transactionCache[keyType][keyId] = null;
148
+ if (mutations[keyType])
149
+ mutations[keyType][keyId] = null;
150
+ }
151
+ else {
152
+ logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
153
+ }
154
+ }
155
+ });
156
+ }
157
+ /**
158
+ * Handles normal key operations for transactions
159
+ */
160
+ function handleNormalKeyOperations(data, key, transactionCache, mutations) {
161
+ Object.assign(transactionCache[key], data[key]);
162
+ mutations[key] = mutations[key] || {};
163
+ Object.assign(mutations[key], data[key]);
164
+ }
165
+ /**
166
+ * Process pre-key deletions with validation
167
+ */
168
+ async function processPreKeyDeletions(data, keyType, state, logger) {
169
+ const mutex = getPreKeyMutex(keyType);
170
+ await mutex.runExclusive(async () => {
171
+ const keyData = data[keyType];
172
+ if (!keyData)
173
+ return;
174
+ // Validate deletions
175
+ for (const keyId in keyData) {
176
+ if (keyData[keyId] === null) {
177
+ const existingKeys = await state.get(keyType, [keyId]);
178
+ if (!existingKeys[keyId]) {
179
+ logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
180
+ if (data[keyType])
181
+ delete data[keyType][keyId];
182
+ }
183
+ }
184
+ }
185
+ });
186
+ }
187
+ /**
188
+ * Executes a function with mutexes acquired for given key types
189
+ * Uses async-mutex's runExclusive with efficient batching
190
+ */
191
+ async function withMutexes(keyTypes, getKeyTypeMutex, fn) {
192
+ if (keyTypes.length === 0) {
193
+ return fn();
194
+ }
195
+ if (keyTypes.length === 1) {
196
+ return getKeyTypeMutex(keyTypes[0]).runExclusive(fn);
197
+ }
198
+ // For multiple mutexes, sort by key type to prevent deadlocks
199
+ // Then acquire all mutexes in order using Promise.all for better efficiency
200
+ const sortedKeyTypes = [...keyTypes].sort();
201
+ const mutexes = sortedKeyTypes.map(getKeyTypeMutex);
202
+ // Acquire all mutexes in order to prevent deadlocks
203
+ const releases = [];
204
+ try {
205
+ for (const mutex of mutexes) {
206
+ releases.push(await mutex.acquire());
207
+ }
208
+ return await fn();
209
+ }
210
+ finally {
211
+ // Release in reverse order
212
+ while (releases.length > 0) {
213
+ const release = releases.pop();
214
+ if (release)
215
+ release();
216
+ }
217
+ }
218
+ }
73
219
  /**
74
220
  * Adds DB like transaction capability (https://en.wikipedia.org/wiki/Database_transaction) to the SignalKeyStore,
75
221
  * this allows batch read & write operations & improves the performance of the lib
@@ -83,7 +229,119 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
83
229
  let dbQueriesInTransaction = 0;
84
230
  let transactionCache = {};
85
231
  let mutations = {};
232
+ // Map to hold mutexes for different key types
233
+ const mutexMap = new Map();
234
+ // Track last usage time for sender key mutexes (for cleanup)
235
+ const mutexLastUsed = new Map();
236
+ // Mutex expiration time: 1 hour in milliseconds
237
+ const SENDER_KEY_MUTEX_EXPIRY_MS = 60 * 60 * 1000;
238
+ // Cleanup interval: every 30 minutes
239
+ const CLEANUP_INTERVAL_MS = 30 * 60 * 1000;
240
+ // Cleanup timer
241
+ let cleanupTimer = null;
242
+ // Start cleanup timer if not already running
243
+ function startCleanupTimer() {
244
+ if (!cleanupTimer) {
245
+ cleanupTimer = setInterval(() => {
246
+ cleanupExpiredMutexes();
247
+ }, CLEANUP_INTERVAL_MS);
248
+ }
249
+ }
250
+ startCleanupTimer();
251
+ // Clean up expired mutexes
252
+ function cleanupExpiredMutexes() {
253
+ const now = Date.now();
254
+ const expiredKeys = [];
255
+ for (const [key, lastUsed] of mutexLastUsed.entries()) {
256
+ if (now - lastUsed > SENDER_KEY_MUTEX_EXPIRY_MS) {
257
+ const mutex = mutexMap.get(key);
258
+ if (mutex && !mutex.isLocked()) {
259
+ expiredKeys.push(key);
260
+ }
261
+ }
262
+ }
263
+ if (expiredKeys.length > 0) {
264
+ for (const key of expiredKeys) {
265
+ mutexMap.delete(key);
266
+ mutexLastUsed.delete(key);
267
+ }
268
+ logger.info({ expiredKeys: expiredKeys.length }, 'cleaned up expired mutexes');
269
+ }
270
+ }
86
271
  let transactionsInProgress = 0;
272
+ function getKeyTypeMutex(type) {
273
+ return getMutex(`keytype:${type}`);
274
+ }
275
+ function getSenderKeyMutex(senderKeyName) {
276
+ return getMutex(`senderkey:${senderKeyName}`);
277
+ }
278
+ function getTransactionMutex(key) {
279
+ return getMutex(`transaction:${key}`);
280
+ }
281
+ // Get or create a mutex for a specific key name
282
+ function getMutex(key) {
283
+ let mutex = mutexMap.get(key);
284
+ if (!mutex) {
285
+ mutex = new async_mutex_1.Mutex();
286
+ mutexMap.set(key, mutex);
287
+ if (mutexMap.size === 1) {
288
+ startCleanupTimer();
289
+ }
290
+ logger.info({ key }, 'created new mutex');
291
+ }
292
+ // Atualizar último uso para cleanup
293
+ mutexLastUsed.set(key, Date.now());
294
+ return mutex;
295
+ }
296
+ // Sender key operations with proper mutex sequencing
297
+ function queueSenderKeyOperation(senderKeyName, operation) {
298
+ return getSenderKeyMutex(senderKeyName).runExclusive(operation);
299
+ }
300
+ // Check if we are currently in a transaction
301
+ function isInTransaction() {
302
+ return transactionsInProgress > 0;
303
+ }
304
+ // Helper function to handle transaction commit with retries
305
+ async function commitTransaction() {
306
+ if (!Object.keys(mutations).length) {
307
+ logger.trace('no mutations in transaction');
308
+ return;
309
+ }
310
+ logger.trace('committing transaction');
311
+ let tries = maxCommitRetries;
312
+ while (tries > 0) {
313
+ tries -= 1;
314
+ try {
315
+ await state.set(mutations);
316
+ logger.trace({ dbQueriesInTransaction }, 'committed transaction');
317
+ return;
318
+ }
319
+ catch (error) {
320
+ logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
321
+ if (tries > 0) {
322
+ await (0, generics_1.delay)(delayBetweenTriesMs);
323
+ }
324
+ }
325
+ }
326
+ }
327
+ // Helper function to clean up transaction state
328
+ function cleanupTransactionState() {
329
+ transactionsInProgress -= 1;
330
+ if (transactionsInProgress === 0) {
331
+ transactionCache = {};
332
+ mutations = {};
333
+ dbQueriesInTransaction = 0;
334
+ }
335
+ }
336
+ // Helper function to execute work within transaction
337
+ async function executeTransactionWork(work) {
338
+ const result = await work();
339
+ // commit if this is the outermost transaction
340
+ if (transactionsInProgress === 1) {
341
+ await commitTransaction();
342
+ }
343
+ return result;
344
+ }
87
345
  return {
88
346
  get: async (type, ids) => {
89
347
  if (isInTransaction()) {
@@ -92,9 +350,30 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
92
350
  // only fetch if there are any items to fetch
93
351
  if (idsRequiringFetch.length) {
94
352
  dbQueriesInTransaction += 1;
95
- const result = await state.get(type, idsRequiringFetch);
96
- transactionCache[type] || (transactionCache[type] = {});
97
- Object.assign(transactionCache[type], result);
353
+ // Use per-sender-key queue for sender-key operations when possible
354
+ if (type === 'sender-key') {
355
+ logger.info({ idsRequiringFetch }, 'processing sender keys in transaction');
356
+ // For sender keys, process each one with queued operations to maintain serialization
357
+ for (const senderKeyName of idsRequiringFetch) {
358
+ await queueSenderKeyOperation(senderKeyName, async () => {
359
+ logger.info({ senderKeyName }, 'fetching sender key in transaction');
360
+ const result = await state.get(type, [senderKeyName]);
361
+ // Update transaction cache
362
+ transactionCache[type] || (transactionCache[type] = {});
363
+ Object.assign(transactionCache[type], result);
364
+ logger.info({ senderKeyName, hasResult: !!result[senderKeyName] }, 'sender key fetch complete');
365
+ });
366
+ }
367
+ }
368
+ else {
369
+ // Use runExclusive for cleaner mutex handling
370
+ await getKeyTypeMutex(type).runExclusive(async () => {
371
+ const result = await state.get(type, idsRequiringFetch);
372
+ // Update transaction cache
373
+ transactionCache[type] || (transactionCache[type] = {});
374
+ Object.assign(transactionCache[type], result);
375
+ });
376
+ }
98
377
  }
99
378
  return ids.reduce((dict, id) => {
100
379
  var _a;
@@ -106,10 +385,22 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
106
385
  }, {});
107
386
  }
108
387
  else {
109
- return state.get(type, ids);
388
+ // Not in transaction, fetch directly with queue protection
389
+ if (type === 'sender-key') {
390
+ // For sender keys, use individual queues to maintain per-key serialization
391
+ const results = {};
392
+ for (const senderKeyName of ids) {
393
+ const result = await queueSenderKeyOperation(senderKeyName, async () => await state.get(type, [senderKeyName]));
394
+ Object.assign(results, result);
395
+ }
396
+ return results;
397
+ }
398
+ else {
399
+ return await getKeyTypeMutex(type).runExclusive(() => state.get(type, ids));
400
+ }
110
401
  }
111
402
  },
112
- set: data => {
403
+ set: async (data) => {
113
404
  if (isInTransaction()) {
114
405
  logger.trace({ types: Object.keys(data) }, 'caching in transaction');
115
406
  for (const key in data) {
@@ -120,58 +411,83 @@ const addTransactionCapability = (state, logger, { maxCommitRetries, delayBetwee
120
411
  }
121
412
  }
122
413
  else {
123
- return state.set(data);
414
+ // Not in transaction, apply directly with mutex protection
415
+ const hasSenderKeys = 'sender-key' in data;
416
+ const senderKeyNames = hasSenderKeys ? Object.keys(data['sender-key'] || {}) : [];
417
+ if (hasSenderKeys) {
418
+ logger.info({ senderKeyNames }, 'processing sender key set operations');
419
+ // Handle sender key operations with per-key queues
420
+ for (const senderKeyName of senderKeyNames) {
421
+ await queueSenderKeyOperation(senderKeyName, async () => {
422
+ // Create data subset for this specific sender key
423
+ const senderKeyData = {
424
+ 'sender-key': {
425
+ [senderKeyName]: data['sender-key'][senderKeyName]
426
+ }
427
+ };
428
+ logger.trace({ senderKeyName }, 'storing sender key');
429
+ // Apply changes to the store
430
+ await state.set(senderKeyData);
431
+ logger.trace({ senderKeyName }, 'sender key stored');
432
+ });
433
+ }
434
+ // Handle any non-sender-key data with regular mutexes
435
+ const nonSenderKeyData = { ...data };
436
+ delete nonSenderKeyData['sender-key'];
437
+ if (Object.keys(nonSenderKeyData).length > 0) {
438
+ await withMutexes(Object.keys(nonSenderKeyData), getKeyTypeMutex, async () => {
439
+ // Process pre-keys and signed-pre-keys separately with specialized mutexes
440
+ for (const key_ in nonSenderKeyData) {
441
+ const keyType = key_;
442
+ if (keyType === 'pre-key') {
443
+ await processPreKeyDeletions(nonSenderKeyData, keyType, state, logger);
444
+ }
445
+ }
446
+ // Apply changes to the store
447
+ await state.set(nonSenderKeyData);
448
+ });
449
+ }
450
+ }
451
+ else {
452
+ // No sender keys - use original logic
453
+ await withMutexes(Object.keys(data), getKeyTypeMutex, async () => {
454
+ // Process pre-keys and signed-pre-keys separately with specialized mutexes
455
+ for (const key_ in data) {
456
+ const keyType = key_;
457
+ if (keyType === 'pre-key') {
458
+ await processPreKeyDeletions(data, keyType, state, logger);
459
+ }
460
+ }
461
+ // Apply changes to the store
462
+ await state.set(data);
463
+ });
464
+ }
124
465
  }
125
466
  },
126
467
  isInTransaction,
127
- async transaction(work) {
128
- let result;
129
- transactionsInProgress += 1;
130
- if (transactionsInProgress === 1) {
131
- logger.trace('entering transaction');
132
- }
468
+ async transaction(work, key) {
469
+ const releaseTxMutex = await getTransactionMutex(key).acquire();
133
470
  try {
134
- result = await work();
135
- // commit if this is the outermost transaction
471
+ transactionsInProgress += 1;
136
472
  if (transactionsInProgress === 1) {
137
- if (Object.keys(mutations).length) {
138
- logger.trace('committing transaction');
139
- // retry mechanism to ensure we've some recovery
140
- // in case a transaction fails in the first attempt
141
- let tries = maxCommitRetries;
142
- while (tries) {
143
- tries -= 1;
144
- //eslint-disable-next-line max-depth
145
- try {
146
- await state.set(mutations);
147
- logger.trace({ dbQueriesInTransaction }, 'committed transaction');
148
- break;
149
- }
150
- catch (error) {
151
- logger.warn(`failed to commit ${Object.keys(mutations).length} mutations, tries left=${tries}`);
152
- await (0, generics_1.delay)(delayBetweenTriesMs);
153
- }
154
- }
155
- }
156
- else {
157
- logger.trace('no mutations in transaction');
158
- }
473
+ logger.trace('entering transaction');
159
474
  }
160
- }
161
- finally {
162
- transactionsInProgress -= 1;
163
- if (transactionsInProgress === 0) {
164
- transactionCache = {};
165
- mutations = {};
166
- dbQueriesInTransaction = 0;
475
+ // Release the transaction mutex now that we've updated the counter
476
+ // This allows other transactions to start preparing
477
+ releaseTxMutex();
478
+ try {
479
+ return await executeTransactionWork(work);
480
+ }
481
+ finally {
482
+ cleanupTransactionState();
167
483
  }
168
484
  }
169
- return result;
485
+ catch (error) {
486
+ releaseTxMutex();
487
+ throw error;
488
+ }
170
489
  }
171
490
  };
172
- function isInTransaction() {
173
- return transactionsInProgress > 0;
174
- }
175
491
  };
176
492
  exports.addTransactionCapability = addTransactionCapability;
177
493
  const initAuthCreds = () => {
@@ -3,6 +3,11 @@ import { BinaryNode } from '../WABinary';
3
3
  import { ILogger } from './logger';
4
4
  export declare const NO_MESSAGE_FOUND_ERROR_TEXT = "Message absent from node";
5
5
  export declare const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled";
6
+ export declare const DECRYPTION_RETRY_CONFIG: {
7
+ maxRetries: number;
8
+ baseDelayMs: number;
9
+ sessionRecordErrors: string[];
10
+ };
6
11
  export declare const NACK_REASONS: {
7
12
  ParsingError: number;
8
13
  UnrecognizedStanza: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.decryptMessageNode = exports.extractAddressingContext = exports.NACK_REASONS = exports.MISSING_KEYS_ERROR_TEXT = exports.NO_MESSAGE_FOUND_ERROR_TEXT = void 0;
3
+ exports.decryptMessageNode = exports.extractAddressingContext = exports.NACK_REASONS = exports.DECRYPTION_RETRY_CONFIG = exports.MISSING_KEYS_ERROR_TEXT = exports.NO_MESSAGE_FOUND_ERROR_TEXT = void 0;
4
4
  exports.decodeMessageNode = decodeMessageNode;
5
5
  const boom_1 = require("@hapi/boom");
6
6
  const WAProto_1 = require("../../WAProto");
@@ -34,6 +34,12 @@ const storeMappingFromEnvelope = async (stanza, sender, decryptionJid, repositor
34
34
  };
35
35
  exports.NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
36
36
  exports.MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled';
37
+ // Retry configuration for failed decryption
38
+ exports.DECRYPTION_RETRY_CONFIG = {
39
+ maxRetries: 3,
40
+ baseDelayMs: 100,
41
+ sessionRecordErrors: ['No session record', 'SessionError: No session record']
42
+ };
37
43
  exports.NACK_REASONS = {
38
44
  ParsingError: 487,
39
45
  UnrecognizedStanza: 488,
@@ -186,30 +192,30 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
186
192
  let msgBuffer;
187
193
  try {
188
194
  const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type;
189
- switch (e2eType) {
190
- case 'skmsg':
191
- msgBuffer = await repository.decryptGroupMessage({
192
- group: sender,
193
- authorJid: author,
194
- msg: content
195
- });
196
- break;
197
- case 'pkmsg':
198
- case 'msg':
199
- const user = (0, WABinary_1.isJidUser)(sender) ? sender : author;
200
- const decryptionJid = await getDecryptionJid(user, repository);
201
- msgBuffer = await repository.decryptMessage({
202
- jid: decryptionJid,
203
- type: e2eType,
204
- ciphertext: content
205
- });
206
- await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger);
207
- break;
208
- case 'plaintext':
209
- msgBuffer = content;
210
- break;
211
- default:
212
- throw new Error(`Unknown e2e type: ${e2eType}`);
195
+ if (e2eType !== 'plaintext') {
196
+ msgBuffer = await decryptWithRetry(async () => {
197
+ switch (e2eType) {
198
+ case 'skmsg':
199
+ return await repository.decryptGroupMessage({
200
+ group: sender,
201
+ authorJid: author,
202
+ msg: content
203
+ });
204
+ case 'pkmsg':
205
+ case 'msg':
206
+ const user = (0, WABinary_1.isJidUser)(sender) ? sender : author;
207
+ return await repository.decryptMessage({
208
+ jid: user,
209
+ type: e2eType,
210
+ ciphertext: content
211
+ });
212
+ default:
213
+ throw new Error(`Unknown e2e type: ${e2eType}`);
214
+ }
215
+ }, logger, fullMessage.key, e2eType);
216
+ }
217
+ else {
218
+ msgBuffer = content;
213
219
  }
214
220
  let msg = WAProto_1.proto.Message.decode(e2eType !== 'plaintext' ? (0, generics_1.unpadRandomMax16)(msgBuffer) : msgBuffer);
215
221
  msg = ((_a = msg.deviceSentMessage) === null || _a === void 0 ? void 0 : _a.message) || msg;
@@ -222,7 +228,7 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
222
228
  });
223
229
  }
224
230
  catch (err) {
225
- logger.error({ key: fullMessage.key, err }, 'failed to decrypt message');
231
+ logger.error({ key: fullMessage.key, err }, 'failed to process sender key distribution message');
226
232
  }
227
233
  }
228
234
  if (fullMessage.message) {
@@ -233,7 +239,15 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
233
239
  }
234
240
  }
235
241
  catch (err) {
236
- logger.error({ key: fullMessage.key, err }, 'failed to decrypt message');
242
+ const errorContext = {
243
+ key: fullMessage.key,
244
+ err,
245
+ messageType: tag === 'plaintext' ? 'plaintext' : attrs.type,
246
+ sender,
247
+ author,
248
+ isSessionRecordError: isSessionRecordError(err)
249
+ };
250
+ logger.error(errorContext, 'failed to decrypt message');
237
251
  fullMessage.messageStubType = WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT;
238
252
  fullMessage.messageStubParameters = [err.message];
239
253
  }
@@ -248,3 +262,51 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
248
262
  };
249
263
  };
250
264
  exports.decryptMessageNode = decryptMessageNode;
265
+ /**
266
+ * Utility function to check if an error is related to missing session record
267
+ */
268
+ function isSessionRecordError(error) {
269
+ const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.toString()) || '';
270
+ return exports.DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern));
271
+ }
272
+ /**
273
+ * Sleep utility for retry delays
274
+ */
275
+ function sleep(ms) {
276
+ return new Promise(resolve => setTimeout(resolve, ms));
277
+ }
278
+ /**
279
+ * Decrypt a single message with retry logic for session record errors
280
+ */
281
+ async function decryptWithRetry(decryptFn, logger, messageKey, messageType) {
282
+ let lastError;
283
+ for (let attempt = 0; attempt <= exports.DECRYPTION_RETRY_CONFIG.maxRetries; attempt++) {
284
+ try {
285
+ return await decryptFn();
286
+ }
287
+ catch (error) {
288
+ lastError = error;
289
+ // Only retry for session record errors
290
+ if (!isSessionRecordError(error)) {
291
+ throw error;
292
+ }
293
+ // Don't retry on the last attempt
294
+ if (attempt === exports.DECRYPTION_RETRY_CONFIG.maxRetries) {
295
+ break;
296
+ }
297
+ // Calculate delay with exponential backoff
298
+ const delay = exports.DECRYPTION_RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
299
+ logger.warn({
300
+ key: messageKey,
301
+ attempt: attempt + 1,
302
+ maxRetries: exports.DECRYPTION_RETRY_CONFIG.maxRetries + 1,
303
+ error: error.message,
304
+ messageType,
305
+ delayMs: delay
306
+ }, 'Session record error detected, retrying decryption');
307
+ await sleep(delay);
308
+ }
309
+ }
310
+ // If all retries failed, throw the last error
311
+ throw lastError;
312
+ }
@@ -165,7 +165,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
165
165
  newAppStateSyncKeyId = strKeyId;
166
166
  }
167
167
  logger === null || logger === void 0 ? void 0 : logger.info({ newAppStateSyncKeyId, newKeys }, 'injecting new app state sync keys');
168
- });
168
+ }, meId);
169
169
  ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId });
170
170
  }
171
171
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@periskope/baileys",
3
- "version": "6.7.18-17-7",
3
+ "version": "6.7.18-17-9",
4
4
  "description": "WhatsApp API",
5
5
  "keywords": [
6
6
  "periskope",