violetics 7.0.0-alpha → 7.0.2-alpha

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 (114) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +1001 -232
  3. package/WAProto/index.js +75379 -142631
  4. package/engine-requirements.js +11 -8
  5. package/lib/Defaults/index.js +132 -144
  6. package/lib/Signal/Group/ciphertext-message.js +2 -6
  7. package/lib/Signal/Group/group-session-builder.js +7 -42
  8. package/lib/Signal/Group/group_cipher.js +37 -52
  9. package/lib/Signal/Group/index.js +11 -57
  10. package/lib/Signal/Group/keyhelper.js +7 -45
  11. package/lib/Signal/Group/sender-chain-key.js +7 -16
  12. package/lib/Signal/Group/sender-key-distribution-message.js +8 -12
  13. package/lib/Signal/Group/sender-key-message.js +9 -13
  14. package/lib/Signal/Group/sender-key-name.js +2 -6
  15. package/lib/Signal/Group/sender-key-record.js +9 -22
  16. package/lib/Signal/Group/sender-key-state.js +27 -43
  17. package/lib/Signal/Group/sender-message-key.js +4 -8
  18. package/lib/Signal/libsignal.js +319 -94
  19. package/lib/Signal/lid-mapping.js +224 -139
  20. package/lib/Socket/Client/index.js +2 -19
  21. package/lib/Socket/Client/types.js +10 -0
  22. package/lib/Socket/Client/websocket.js +53 -0
  23. package/lib/Socket/business.js +162 -44
  24. package/lib/Socket/chats.js +477 -442
  25. package/lib/Socket/communities.js +430 -0
  26. package/lib/Socket/groups.js +110 -99
  27. package/lib/Socket/index.js +10 -10
  28. package/lib/Socket/messages-recv.js +878 -552
  29. package/lib/Socket/messages-send.js +859 -428
  30. package/lib/Socket/mex.js +41 -0
  31. package/lib/Socket/newsletter.js +195 -390
  32. package/lib/Socket/socket.js +463 -289
  33. package/lib/Store/index.js +3 -10
  34. package/lib/Store/make-in-memory-store.js +73 -79
  35. package/lib/Store/make-ordered-dictionary.js +4 -7
  36. package/lib/Store/object-repository.js +2 -6
  37. package/lib/Types/Auth.js +1 -2
  38. package/lib/Types/Bussines.js +1 -0
  39. package/lib/Types/Call.js +1 -2
  40. package/lib/Types/Chat.js +7 -4
  41. package/lib/Types/Contact.js +1 -2
  42. package/lib/Types/Events.js +1 -2
  43. package/lib/Types/GroupMetadata.js +1 -2
  44. package/lib/Types/Label.js +2 -5
  45. package/lib/Types/LabelAssociation.js +2 -5
  46. package/lib/Types/Message.js +17 -9
  47. package/lib/Types/Newsletter.js +33 -38
  48. package/lib/Types/Product.js +1 -2
  49. package/lib/Types/Signal.js +1 -2
  50. package/lib/Types/Socket.js +2 -2
  51. package/lib/Types/State.js +12 -2
  52. package/lib/Types/USync.js +1 -2
  53. package/lib/Types/index.js +14 -31
  54. package/lib/Utils/auth-utils.js +228 -145
  55. package/lib/Utils/browser-utils.js +28 -0
  56. package/lib/Utils/business.js +66 -70
  57. package/lib/Utils/chat-utils.js +331 -249
  58. package/lib/Utils/crypto.js +57 -91
  59. package/lib/Utils/decode-wa-message.js +168 -84
  60. package/lib/Utils/event-buffer.js +138 -80
  61. package/lib/Utils/generics.js +180 -297
  62. package/lib/Utils/history.js +83 -49
  63. package/lib/Utils/identity-change-handler.js +48 -0
  64. package/lib/Utils/index.js +19 -33
  65. package/lib/Utils/link-preview.js +14 -23
  66. package/lib/Utils/logger.js +2 -7
  67. package/lib/Utils/lt-hash.js +2 -46
  68. package/lib/Utils/make-mutex.js +24 -35
  69. package/lib/Utils/message-retry-manager.js +224 -0
  70. package/lib/Utils/messages-media.js +501 -496
  71. package/lib/Utils/messages.js +1428 -362
  72. package/lib/Utils/noise-handler.js +145 -100
  73. package/lib/Utils/pre-key-manager.js +105 -0
  74. package/lib/Utils/process-message.js +356 -150
  75. package/lib/Utils/reporting-utils.js +257 -0
  76. package/lib/Utils/signal.js +78 -73
  77. package/lib/Utils/sync-action-utils.js +47 -0
  78. package/lib/Utils/tc-token-utils.js +17 -0
  79. package/lib/Utils/use-multi-file-auth-state.js +32 -34
  80. package/lib/Utils/validate-connection.js +91 -107
  81. package/lib/WABinary/constants.js +1300 -1304
  82. package/lib/WABinary/decode.js +26 -48
  83. package/lib/WABinary/encode.js +109 -155
  84. package/lib/WABinary/generic-utils.js +161 -149
  85. package/lib/WABinary/index.js +5 -21
  86. package/lib/WABinary/jid-utils.js +73 -40
  87. package/lib/WABinary/types.js +1 -2
  88. package/lib/WAM/BinaryInfo.js +2 -6
  89. package/lib/WAM/constants.js +19070 -11568
  90. package/lib/WAM/encode.js +17 -23
  91. package/lib/WAM/index.js +3 -19
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +8 -12
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -15
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -13
  95. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -14
  96. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -23
  97. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -9
  98. package/lib/WAUSync/Protocols/index.js +4 -20
  99. package/lib/WAUSync/USyncQuery.js +40 -36
  100. package/lib/WAUSync/USyncUser.js +2 -6
  101. package/lib/WAUSync/index.js +3 -19
  102. package/lib/index.js +11 -44
  103. package/package.json +75 -108
  104. package/lib/Defaults/baileys-version.json +0 -3
  105. package/lib/Defaults/phonenumber-mcc.json +0 -223
  106. package/lib/Signal/Group/queue-job.js +0 -57
  107. package/lib/Socket/Client/abstract-socket-client.js +0 -13
  108. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  109. package/lib/Socket/Client/web-socket-client.js +0 -111
  110. package/lib/Socket/groupStatus.js +0 -637
  111. package/lib/Socket/registration.js +0 -166
  112. package/lib/Socket/usync.js +0 -70
  113. package/lib/Store/make-cache-manager-store.js +0 -83
  114. package/lib/Utils/baileys-event-stream.js +0 -63
@@ -1,74 +1,131 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.makeNoiseHandler = void 0;
4
- const boom_1 = require("@hapi/boom");
5
- const WAProto_1 = require("../../WAProto");
6
- const Defaults_1 = require("../Defaults");
7
- const WABinary_1 = require("../WABinary");
8
- const crypto_1 = require("./crypto");
1
+ import { Boom } from '@hapi/boom';
2
+ import { proto } from '../../WAProto/index.js';
3
+ import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js';
4
+ import { decodeBinaryNode } from '../WABinary/index.js';
5
+ import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js';
6
+ const IV_LENGTH = 12;
7
+ const EMPTY_BUFFER = Buffer.alloc(0);
9
8
  const generateIV = (counter) => {
10
- const iv = new ArrayBuffer(12);
9
+ const iv = new ArrayBuffer(IV_LENGTH);
11
10
  new DataView(iv).setUint32(8, counter);
12
11
  return new Uint8Array(iv);
13
12
  };
14
- const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, mobile, logger, routingInfo }) => {
13
+ class TransportState {
14
+ constructor(encKey, decKey) {
15
+ this.encKey = encKey;
16
+ this.decKey = decKey;
17
+ this.readCounter = 0;
18
+ this.writeCounter = 0;
19
+ this.iv = new Uint8Array(IV_LENGTH);
20
+ }
21
+ encrypt(plaintext) {
22
+ const c = this.writeCounter++;
23
+ this.iv[8] = (c >>> 24) & 0xff;
24
+ this.iv[9] = (c >>> 16) & 0xff;
25
+ this.iv[10] = (c >>> 8) & 0xff;
26
+ this.iv[11] = c & 0xff;
27
+ return aesEncryptGCM(plaintext, this.encKey, this.iv, EMPTY_BUFFER);
28
+ }
29
+ decrypt(ciphertext) {
30
+ const c = this.readCounter++;
31
+ this.iv[8] = (c >>> 24) & 0xff;
32
+ this.iv[9] = (c >>> 16) & 0xff;
33
+ this.iv[10] = (c >>> 8) & 0xff;
34
+ this.iv[11] = c & 0xff;
35
+ return aesDecryptGCM(ciphertext, this.decKey, this.iv, EMPTY_BUFFER);
36
+ }
37
+ }
38
+ export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
15
39
  logger = logger.child({ class: 'ns' });
40
+ const data = Buffer.from(NOISE_MODE);
41
+ let hash = data.byteLength === 32 ? data : sha256(data);
42
+ let salt = hash;
43
+ let encKey = hash;
44
+ let decKey = hash;
45
+ let counter = 0;
46
+ let sentIntro = false;
47
+ let inBytes = Buffer.alloc(0);
48
+ let transport = null;
49
+ let isWaitingForTransport = false;
50
+ let pendingOnFrame = null;
51
+ let introHeader;
52
+ if (routingInfo) {
53
+ introHeader = Buffer.alloc(7 + routingInfo.byteLength + NOISE_HEADER.length);
54
+ introHeader.write('ED', 0, 'utf8');
55
+ introHeader.writeUint8(0, 2);
56
+ introHeader.writeUint8(1, 3);
57
+ introHeader.writeUint8(routingInfo.byteLength >> 16, 4);
58
+ introHeader.writeUint16BE(routingInfo.byteLength & 65535, 5);
59
+ introHeader.set(routingInfo, 7);
60
+ introHeader.set(NOISE_HEADER, 7 + routingInfo.byteLength);
61
+ }
62
+ else {
63
+ introHeader = Buffer.from(NOISE_HEADER);
64
+ }
16
65
  const authenticate = (data) => {
17
- if (!isFinished) {
18
- hash = (0, crypto_1.sha256)(Buffer.concat([hash, data]));
66
+ if (!transport) {
67
+ hash = sha256(Buffer.concat([hash, data]));
19
68
  }
20
69
  };
21
70
  const encrypt = (plaintext) => {
22
- const result = (0, crypto_1.aesEncryptGCM)(plaintext, encKey, generateIV(writeCounter), hash);
23
- writeCounter += 1;
71
+ if (transport) {
72
+ return transport.encrypt(plaintext);
73
+ }
74
+ const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash);
24
75
  authenticate(result);
25
76
  return result;
26
77
  };
27
78
  const decrypt = (ciphertext) => {
28
- // before the handshake is finished, we use the same counter
29
- // after handshake, the counters are different
30
- const iv = generateIV(isFinished ? readCounter : writeCounter);
31
- const result = (0, crypto_1.aesDecryptGCM)(ciphertext, decKey, iv, hash);
32
- if (isFinished) {
33
- readCounter += 1;
34
- }
35
- else {
36
- writeCounter += 1;
79
+ if (transport) {
80
+ return transport.decrypt(ciphertext);
37
81
  }
82
+ const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash);
38
83
  authenticate(ciphertext);
39
84
  return result;
40
85
  };
41
86
  const localHKDF = (data) => {
42
- const key = (0, crypto_1.hkdf)(Buffer.from(data), 64, { salt, info: '' });
43
- return [key.slice(0, 32), key.slice(32)];
87
+ const key = hkdf(Buffer.from(data), 64, { salt, info: '' });
88
+ return [key.subarray(0, 32), key.subarray(32)];
44
89
  };
45
90
  const mixIntoKey = (data) => {
46
91
  const [write, read] = localHKDF(data);
47
92
  salt = write;
48
93
  encKey = read;
49
94
  decKey = read;
50
- readCounter = 0;
51
- writeCounter = 0;
95
+ counter = 0;
52
96
  };
53
- const finishInit = () => {
97
+ const finishInit = async () => {
98
+ isWaitingForTransport = true;
54
99
  const [write, read] = localHKDF(new Uint8Array(0));
55
- encKey = write;
56
- decKey = read;
57
- hash = Buffer.from([]);
58
- readCounter = 0;
59
- writeCounter = 0;
60
- isFinished = true;
100
+ transport = new TransportState(write, read);
101
+ isWaitingForTransport = false;
102
+ logger.trace('Noise handler transitioned to Transport state');
103
+ if (pendingOnFrame) {
104
+ logger.trace({ length: inBytes.length }, 'Flushing buffered frames after transport ready');
105
+ await processData(pendingOnFrame);
106
+ pendingOnFrame = null;
107
+ }
108
+ };
109
+ const processData = async (onFrame) => {
110
+ let size;
111
+ while (true) {
112
+ if (inBytes.length < 3)
113
+ return;
114
+ size = (inBytes[0] << 16) | (inBytes[1] << 8) | inBytes[2];
115
+ if (inBytes.length < size + 3)
116
+ return;
117
+ let frame = inBytes.subarray(3, size + 3);
118
+ inBytes = inBytes.subarray(size + 3);
119
+ if (transport) {
120
+ const result = transport.decrypt(frame);
121
+ frame = await decodeBinaryNode(result);
122
+ }
123
+ if (logger.level === 'trace') {
124
+ logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
125
+ }
126
+ onFrame(frame);
127
+ }
61
128
  };
62
- const data = Buffer.from(Defaults_1.NOISE_MODE);
63
- let hash = Buffer.from(data.byteLength === 32 ? data : (0, crypto_1.sha256)(data));
64
- let salt = hash;
65
- let encKey = hash;
66
- let decKey = hash;
67
- let readCounter = 0;
68
- let writeCounter = 0;
69
- let isFinished = false;
70
- let sentIntro = false;
71
- let inBytes = Buffer.alloc(0);
72
129
  authenticate(NOISE_HEADER);
73
130
  authenticate(publicKey);
74
131
  return {
@@ -79,77 +136,65 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
79
136
  finishInit,
80
137
  processHandshake: ({ serverHello }, noiseKey) => {
81
138
  authenticate(serverHello.ephemeral);
82
- mixIntoKey(crypto_1.Curve.sharedKey(privateKey, serverHello.ephemeral));
139
+ mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
83
140
  const decStaticContent = decrypt(serverHello.static);
84
- mixIntoKey(crypto_1.Curve.sharedKey(privateKey, decStaticContent));
141
+ mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
85
142
  const certDecoded = decrypt(serverHello.payload);
86
- if (mobile) {
87
- WAProto_1.proto.CertChain.NoiseCertificate.decode(certDecoded);
143
+ const { intermediate: certIntermediate, leaf } = proto.CertChain.decode(certDecoded);
144
+ // leaf
145
+ if (!leaf?.details || !leaf?.signature) {
146
+ throw new Boom('invalid noise leaf certificate', { statusCode: 400 });
88
147
  }
89
- else {
90
- const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
91
- const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
92
- if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
93
- throw new boom_1.Boom('certification match failed', { statusCode: 400 });
94
- }
148
+ if (!certIntermediate?.details || !certIntermediate?.signature) {
149
+ throw new Boom('invalid noise intermediate certificate', { statusCode: 400 });
150
+ }
151
+ const details = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
152
+ const { issuerSerial } = details;
153
+ const verify = Curve.verify(details.key, leaf.details, leaf.signature);
154
+ const verifyIntermediate = Curve.verify(WA_CERT_DETAILS.PUBLIC_KEY, certIntermediate.details, certIntermediate.signature);
155
+ if (!verify) {
156
+ throw new Boom('noise certificate signature invalid', { statusCode: 400 });
157
+ }
158
+ if (!verifyIntermediate) {
159
+ throw new Boom('noise intermediate certificate signature invalid', { statusCode: 400 });
160
+ }
161
+ if (issuerSerial !== WA_CERT_DETAILS.SERIAL) {
162
+ throw new Boom('certification match failed', { statusCode: 400 });
95
163
  }
96
164
  const keyEnc = encrypt(noiseKey.public);
97
- mixIntoKey(crypto_1.Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
165
+ mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
98
166
  return keyEnc;
99
167
  },
100
168
  encodeFrame: (data) => {
101
- if (isFinished) {
102
- data = encrypt(data);
169
+ if (transport) {
170
+ data = transport.encrypt(data);
103
171
  }
104
- let header;
105
- if (routingInfo) {
106
- header = Buffer.alloc(7);
107
- header.write('ED', 0, 'utf8');
108
- header.writeUint8(0, 2);
109
- header.writeUint8(1, 3);
110
- header.writeUint8(routingInfo.byteLength >> 16, 4);
111
- header.writeUint16BE(routingInfo.byteLength & 65535, 5);
112
- header = Buffer.concat([header, routingInfo, NOISE_HEADER]);
113
- }
114
- else {
115
- header = Buffer.from(NOISE_HEADER);
116
- }
117
- const introSize = sentIntro ? 0 : header.length;
118
- const frame = Buffer.alloc(introSize + 3 + data.byteLength);
172
+ const dataLen = data.byteLength;
173
+ const introSize = sentIntro ? 0 : introHeader.length;
174
+ const frame = Buffer.allocUnsafe(introSize + 3 + dataLen);
119
175
  if (!sentIntro) {
120
- frame.set(header);
176
+ frame.set(introHeader);
121
177
  sentIntro = true;
122
178
  }
123
- frame.writeUInt8(data.byteLength >> 16, introSize);
124
- frame.writeUInt16BE(65535 & data.byteLength, introSize + 1);
179
+ frame[introSize] = (dataLen >>> 16) & 0xff;
180
+ frame[introSize + 1] = (dataLen >>> 8) & 0xff;
181
+ frame[introSize + 2] = dataLen & 0xff;
125
182
  frame.set(data, introSize + 3);
126
183
  return frame;
127
184
  },
128
- decodeFrame: (newData, onFrame) => {
129
- var _a;
130
- // the binary protocol uses its own framing mechanism
131
- // on top of the WS frames
132
- // so we get this data and separate out the frames
133
- const getBytesSize = () => {
134
- if (inBytes.length >= 3) {
135
- return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1);
136
- }
137
- };
138
- inBytes = Buffer.concat([inBytes, newData]);
139
- logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
140
- let size = getBytesSize();
141
- while (size && inBytes.length >= size + 3) {
142
- let frame = inBytes.slice(3, size + 3);
143
- inBytes = inBytes.slice(size + 3);
144
- if (isFinished) {
145
- const result = decrypt(frame);
146
- frame = (0, WABinary_1.decodeBinaryNode)(result);
147
- }
148
- logger.trace({ msg: (_a = frame === null || frame === void 0 ? void 0 : frame.attrs) === null || _a === void 0 ? void 0 : _a.id }, 'recv frame');
149
- onFrame(frame);
150
- size = getBytesSize();
185
+ decodeFrame: async (newData, onFrame) => {
186
+ if (isWaitingForTransport) {
187
+ inBytes = Buffer.concat([inBytes, newData]);
188
+ pendingOnFrame = onFrame;
189
+ return;
190
+ }
191
+ if (inBytes.length === 0) {
192
+ inBytes = Buffer.from(newData);
151
193
  }
194
+ else {
195
+ inBytes = Buffer.concat([inBytes, newData]);
196
+ }
197
+ await processData(onFrame);
152
198
  }
153
199
  };
154
- };
155
- exports.makeNoiseHandler = makeNoiseHandler;
200
+ };
@@ -0,0 +1,105 @@
1
+ import PQueue from 'p-queue';
2
+ /**
3
+ * Manages pre-key operations with proper concurrency control
4
+ */
5
+ export class PreKeyManager {
6
+ constructor(store, logger) {
7
+ this.store = store;
8
+ this.logger = logger;
9
+ this.queues = new Map();
10
+ }
11
+ /**
12
+ * Get or create a queue for a specific key type
13
+ */
14
+ getQueue(keyType) {
15
+ if (!this.queues.has(keyType)) {
16
+ this.queues.set(keyType, new PQueue({ concurrency: 1 }));
17
+ }
18
+ return this.queues.get(keyType);
19
+ }
20
+ /**
21
+ * Process pre-key operations (updates and deletions)
22
+ */
23
+ async processOperations(data, keyType, transactionCache, mutations, isInTransaction) {
24
+ const keyData = data[keyType];
25
+ if (!keyData)
26
+ return;
27
+ return this.getQueue(keyType).add(async () => {
28
+ // Ensure structures exist
29
+ transactionCache[keyType] = transactionCache[keyType] || {};
30
+ mutations[keyType] = mutations[keyType] || {};
31
+ // Separate deletions from updates
32
+ const deletions = [];
33
+ const updates = {};
34
+ for (const keyId in keyData) {
35
+ if (keyData[keyId] === null) {
36
+ deletions.push(keyId);
37
+ }
38
+ else {
39
+ updates[keyId] = keyData[keyId];
40
+ }
41
+ }
42
+ // Process updates (no validation needed)
43
+ if (Object.keys(updates).length > 0) {
44
+ Object.assign(transactionCache[keyType], updates);
45
+ Object.assign(mutations[keyType], updates);
46
+ }
47
+ // Process deletions with validation
48
+ if (deletions.length > 0) {
49
+ await this.processDeletions(keyType, deletions, transactionCache, mutations, isInTransaction);
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * Process deletions with validation
55
+ */
56
+ async processDeletions(keyType, ids, transactionCache, mutations, isInTransaction) {
57
+ if (isInTransaction) {
58
+ // In transaction, only allow deletion if key exists in cache
59
+ for (const keyId of ids) {
60
+ if (transactionCache[keyType]?.[keyId]) {
61
+ transactionCache[keyType][keyId] = null;
62
+ mutations[keyType][keyId] = null;
63
+ }
64
+ else {
65
+ this.logger.warn(`Skipping deletion of non-existent ${keyType} in transaction: ${keyId}`);
66
+ }
67
+ }
68
+ }
69
+ else {
70
+ // Outside transaction, validate against store
71
+ const existingKeys = await this.store.get(keyType, ids);
72
+ for (const keyId of ids) {
73
+ if (existingKeys[keyId]) {
74
+ transactionCache[keyType][keyId] = null;
75
+ mutations[keyType][keyId] = null;
76
+ }
77
+ else {
78
+ this.logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Validate and process pre-key deletions outside transactions
85
+ */
86
+ async validateDeletions(data, keyType) {
87
+ const keyData = data[keyType];
88
+ if (!keyData)
89
+ return;
90
+ return this.getQueue(keyType).add(async () => {
91
+ // Find all deletion requests
92
+ const deletionIds = Object.keys(keyData).filter(id => keyData[id] === null);
93
+ if (deletionIds.length === 0)
94
+ return;
95
+ // Validate deletions
96
+ const existingKeys = await this.store.get(keyType, deletionIds);
97
+ for (const keyId of deletionIds) {
98
+ if (!existingKeys[keyId]) {
99
+ this.logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`);
100
+ delete data[keyType][keyId];
101
+ }
102
+ }
103
+ });
104
+ }
105
+ }