@ikyyjee/ikyysinggle 1.7.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 (142) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/WAProto.proto +8083 -0
  3. package/WAProto/fix-imports.js +85 -0
  4. package/WAProto/index.d.ts +29095 -0
  5. package/WAProto/index.js +172336 -0
  6. package/engine-requirements.js +10 -0
  7. package/lib/Defaults/index.js +194 -0
  8. package/lib/Signal/Group/ciphertext-message.js +12 -0
  9. package/lib/Signal/Group/group-session-builder.js +30 -0
  10. package/lib/Signal/Group/group_cipher.js +82 -0
  11. package/lib/Signal/Group/index.js +12 -0
  12. package/lib/Signal/Group/keyhelper.js +18 -0
  13. package/lib/Signal/Group/sender-chain-key.js +26 -0
  14. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  15. package/lib/Signal/Group/sender-key-message.js +66 -0
  16. package/lib/Signal/Group/sender-key-name.js +48 -0
  17. package/lib/Signal/Group/sender-key-record.js +41 -0
  18. package/lib/Signal/Group/sender-key-state.js +84 -0
  19. package/lib/Signal/Group/sender-message-key.js +26 -0
  20. package/lib/Signal/libsignal.js +431 -0
  21. package/lib/Signal/lid-mapping.js +277 -0
  22. package/lib/Socket/Client/index.js +3 -0
  23. package/lib/Socket/Client/types.js +11 -0
  24. package/lib/Socket/Client/websocket.js +102 -0
  25. package/lib/Socket/aigroups.js +221 -0
  26. package/lib/Socket/business.js +379 -0
  27. package/lib/Socket/chats.js +1193 -0
  28. package/lib/Socket/communities.js +431 -0
  29. package/lib/Socket/graphql.js +524 -0
  30. package/lib/Socket/groups.js +408 -0
  31. package/lib/Socket/index.js +49 -0
  32. package/lib/Socket/interop.js +341 -0
  33. package/lib/Socket/luxu.js +510 -0
  34. package/lib/Socket/managed-account.js +99 -0
  35. package/lib/Socket/messages-recv.js +2009 -0
  36. package/lib/Socket/messages-send.js +1608 -0
  37. package/lib/Socket/mex.js +41 -0
  38. package/lib/Socket/newsletter.js +399 -0
  39. package/lib/Socket/privacy.js +128 -0
  40. package/lib/Socket/registration.js +238 -0
  41. package/lib/Socket/socket.js +1000 -0
  42. package/lib/Socket/text-router.js +67 -0
  43. package/lib/Socket/username.js +234 -0
  44. package/lib/Store/index.js +10 -0
  45. package/lib/Store/keyed-db.js +108 -0
  46. package/lib/Store/make-cache-manager-store.js +85 -0
  47. package/lib/Store/make-in-memory-store.js +198 -0
  48. package/lib/Store/make-ordered-dictionary.js +75 -0
  49. package/lib/Store/object-repository.js +32 -0
  50. package/lib/Types/Auth.js +2 -0
  51. package/lib/Types/Bussines.js +2 -0
  52. package/lib/Types/Call.js +2 -0
  53. package/lib/Types/Chat.js +8 -0
  54. package/lib/Types/Contact.js +2 -0
  55. package/lib/Types/Events.js +2 -0
  56. package/lib/Types/GroupMetadata.js +2 -0
  57. package/lib/Types/Label.js +25 -0
  58. package/lib/Types/LabelAssociation.js +7 -0
  59. package/lib/Types/Message.js +11 -0
  60. package/lib/Types/Mex.js +114 -0
  61. package/lib/Types/Product.js +2 -0
  62. package/lib/Types/Signal.js +2 -0
  63. package/lib/Types/Socket.js +3 -0
  64. package/lib/Types/State.js +56 -0
  65. package/lib/Types/USync.js +2 -0
  66. package/lib/Types/index.js +26 -0
  67. package/lib/Utils/adaptive-healing.js +53 -0
  68. package/lib/Utils/auth-utils.js +302 -0
  69. package/lib/Utils/browser-utils.js +50 -0
  70. package/lib/Utils/business.js +231 -0
  71. package/lib/Utils/chat-utils.js +872 -0
  72. package/lib/Utils/command-loader.js +108 -0
  73. package/lib/Utils/companion-reg-client-utils.js +35 -0
  74. package/lib/Utils/consumer-application.js +106 -0
  75. package/lib/Utils/crypto.js +137 -0
  76. package/lib/Utils/curve25519-js.js +262 -0
  77. package/lib/Utils/decode-wa-message.js +498 -0
  78. package/lib/Utils/event-buffer.js +622 -0
  79. package/lib/Utils/generics.js +403 -0
  80. package/lib/Utils/group-history.js +47 -0
  81. package/lib/Utils/history.js +134 -0
  82. package/lib/Utils/identity-change-handler.js +50 -0
  83. package/lib/Utils/index.js +38 -0
  84. package/lib/Utils/jid-display-normalization.js +198 -0
  85. package/lib/Utils/link-preview.js +85 -0
  86. package/lib/Utils/logger.js +3 -0
  87. package/lib/Utils/lt-hash.js +8 -0
  88. package/lib/Utils/make-mutex.js +33 -0
  89. package/lib/Utils/message-composer.js +273 -0
  90. package/lib/Utils/message-retry-manager.js +267 -0
  91. package/lib/Utils/messages-media.js +791 -0
  92. package/lib/Utils/messages.js +1260 -0
  93. package/lib/Utils/meta-ai-msmsg.js +271 -0
  94. package/lib/Utils/native-bridge.js +77 -0
  95. package/lib/Utils/noise-handler.js +201 -0
  96. package/lib/Utils/offline-node-processor.js +40 -0
  97. package/lib/Utils/optimizer.js +90 -0
  98. package/lib/Utils/pre-key-manager.js +106 -0
  99. package/lib/Utils/process-message.js +630 -0
  100. package/lib/Utils/reporting-utils.js +258 -0
  101. package/lib/Utils/session-pool.js +73 -0
  102. package/lib/Utils/signal.js +207 -0
  103. package/lib/Utils/stanza-ack.js +38 -0
  104. package/lib/Utils/sticker.js +139 -0
  105. package/lib/Utils/sync-action-utils.js +49 -0
  106. package/lib/Utils/tc-token-utils.js +163 -0
  107. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  108. package/lib/Utils/use-sqlite-auth-state.js +168 -0
  109. package/lib/Utils/validate-connection.js +203 -0
  110. package/lib/Utils/view-once-cache.js +79 -0
  111. package/lib/Utils/voip-rekey.js +25 -0
  112. package/lib/Utils/warmup.js +117 -0
  113. package/lib/WABinary/constants.js +1301 -0
  114. package/lib/WABinary/decode.js +262 -0
  115. package/lib/WABinary/encode.js +220 -0
  116. package/lib/WABinary/generic-utils.js +204 -0
  117. package/lib/WABinary/index.js +6 -0
  118. package/lib/WABinary/jid-utils.js +98 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +10 -0
  121. package/lib/WAM/constants.js +22853 -0
  122. package/lib/WAM/encode.js +150 -0
  123. package/lib/WAM/index.js +4 -0
  124. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
  125. package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
  126. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  128. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
  129. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
  130. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
  131. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  132. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
  133. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  134. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  135. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  136. package/lib/WAUSync/Protocols/index.js +13 -0
  137. package/lib/WAUSync/USyncQuery.js +127 -0
  138. package/lib/WAUSync/USyncUser.js +31 -0
  139. package/lib/WAUSync/index.js +4 -0
  140. package/lib/antiban.js +4083 -0
  141. package/lib/index.js +24 -0
  142. package/package.json +147 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * lib/Utils/meta-ai-msmsg.js — decrypts the "msmsg" message envelope used
3
+ * when a bot (e.g. Meta AI in a chat/group) sends a response. This envelope
4
+ * is encrypted differently from normal 1:1/group messages (it's keyed off
5
+ * a `messageSecret` your own earlier message carried, not the usual Signal
6
+ * session), so it needs its own decrypt path — see decode-wa-message.js's
7
+ * `case 'msmsg':` for where this gets called from.
8
+ *
9
+ * Ported (CommonJS -> ES modules only, logic unchanged) from a sibling
10
+ * fork; wired in here because the previous behavior was to silently drop
11
+ * every incoming msmsg message instead of decrypting it (see the removed
12
+ * "TODO: temporary fix" block in messages-recv.js's handleMessage).
13
+ */
14
+ import { aesDecryptGCM } from './crypto.js';
15
+ import { proto } from '../../WAProto/index.js';
16
+ import nodeCrypto from 'crypto';
17
+
18
+ // crypto.js's `hkdf` re-export (backed by the optional whatsapp-rust-bridge
19
+ // native addon) only accepts a *string* for its `info` option. msmsg
20
+ // decryption needs raw-byte `info`/`aad` (concatenated id + jid bytes), so
21
+ // this file uses its own small, standard RFC 5869 HKDF-HMAC-SHA256
22
+ // implementation instead — self-contained, doesn't touch or replace the
23
+ // native hkdf used anywhere else in the codebase.
24
+ const hkdfExtractJS = (salt, ikm) => nodeCrypto.createHmac('sha256', salt).update(ikm).digest();
25
+ const hkdfExpandJS = (prk, length, info) => {
26
+ const hashLen = 32;
27
+ const n = Math.ceil(length / hashLen);
28
+ let t = Buffer.alloc(0);
29
+ let okm = Buffer.alloc(0);
30
+ for (let i = 1; i <= n; i++) {
31
+ t = nodeCrypto.createHmac('sha256', prk).update(Buffer.concat([t, info, Buffer.from([i])])).digest();
32
+ okm = Buffer.concat([okm, t]);
33
+ }
34
+ return okm.subarray(0, length);
35
+ };
36
+ const hkdf = (ikm, length, options = {}) => {
37
+ const ikmBuf = Buffer.isBuffer(ikm) ? ikm : Buffer.from(ikm);
38
+ const saltBuf = options.salt ? (Buffer.isBuffer(options.salt) ? options.salt : Buffer.from(options.salt)) : Buffer.alloc(32);
39
+ const infoBuf = options.info ? (Buffer.isBuffer(options.info) ? options.info : Buffer.from(options.info)) : Buffer.alloc(0);
40
+ const prk = hkdfExtractJS(saltBuf, ikmBuf);
41
+ return hkdfExpandJS(prk, length, infoBuf);
42
+ };
43
+
44
+ const BOT_MESSAGE_INFO = 'Bot Message';
45
+ const KEY_LENGTH = 32;
46
+ const AUTH_TAG_LENGTH = 16;
47
+ const MSG_ID_HEX_RE = /^[0-9A-Fa-f]{32}$/;
48
+
49
+ const unpadRandomMax16 = (value) => {
50
+ const bytes = new Uint8Array(value);
51
+ if (!bytes.length) {
52
+ throw new Error('unpadPkcs7 given empty bytes');
53
+ }
54
+ const padLength = bytes[bytes.length - 1];
55
+ if (padLength > bytes.length) {
56
+ throw new Error(`unpad given ${bytes.length} bytes, but pad is ${padLength}`);
57
+ }
58
+ return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.length - padLength);
59
+ };
60
+
61
+ const toBuffer = (value) => {
62
+ if (Buffer.isBuffer(value)) {
63
+ return value;
64
+ }
65
+ if (value instanceof Uint8Array) {
66
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
67
+ }
68
+ return Buffer.from(value);
69
+ };
70
+
71
+ const normalizeLidJid = (jid) => {
72
+ if (!jid || !jid.endsWith('@lid') || !jid.includes(':')) {
73
+ return jid;
74
+ }
75
+ return `${jid.split(':')[0]}@lid`;
76
+ };
77
+
78
+ const buildMessageIdRepresentations = (messageId) => {
79
+ const ascii = Buffer.from(messageId);
80
+ const binary = MSG_ID_HEX_RE.test(messageId) ? Buffer.from(messageId, 'hex') : ascii;
81
+ return [
82
+ { label: 'msgIdAscii', value: ascii },
83
+ ...(binary.equals(ascii) ? [] : [{ label: 'msgIdBinary', value: binary }])
84
+ ];
85
+ };
86
+
87
+ const pushUnique = (items, seen, item) => {
88
+ const key = JSON.stringify([
89
+ item.messageId,
90
+ item.idSource,
91
+ item.idSources,
92
+ item.infoSource,
93
+ item.aadSource,
94
+ item.info.toString('hex'),
95
+ item.aad.toString('hex')
96
+ ]);
97
+ if (!seen.has(key)) {
98
+ seen.add(key);
99
+ items.push(item);
100
+ }
101
+ };
102
+
103
+ const getCandidateIds = (messageKey) => {
104
+ const orderedCandidates = [
105
+ messageKey?.botType === 'full'
106
+ ? { source: 'stanzaId', messageId: messageKey?.stanzaId }
107
+ : { source: 'botEditTargetId', messageId: messageKey?.botEditTargetId },
108
+ { source: 'targetId', messageId: messageKey?.targetId },
109
+ { source: 'metaTargetId', messageId: messageKey?.metaTargetId },
110
+ { source: 'stanzaId', messageId: messageKey?.stanzaId }
111
+ ];
112
+ const targetIdCandidates = Array.isArray(messageKey?.targetIdCandidates) ? messageKey.targetIdCandidates : [];
113
+ for (let index = 0; index < targetIdCandidates.length; index += 1) {
114
+ orderedCandidates.push({
115
+ source: `targetIdCandidates[${index}]`,
116
+ messageId: targetIdCandidates[index]
117
+ });
118
+ }
119
+ const grouped = new Map();
120
+ for (const candidate of orderedCandidates) {
121
+ if (!candidate.messageId) {
122
+ continue;
123
+ }
124
+ const messageId = String(candidate.messageId);
125
+ const existing = grouped.get(messageId);
126
+ if (existing) {
127
+ if (!existing.idSources.includes(candidate.source)) {
128
+ existing.idSources.push(candidate.source);
129
+ }
130
+ }
131
+ else {
132
+ grouped.set(messageId, {
133
+ messageId,
134
+ idSource: candidate.source,
135
+ idSources: [candidate.source]
136
+ });
137
+ }
138
+ }
139
+ return Array.from(grouped.values());
140
+ };
141
+
142
+ const getJidCandidates = (messageKey) => {
143
+ const ordered = [
144
+ { source: 'meId', jid: messageKey?.meId },
145
+ { source: 'conversationJid', jid: messageKey?.conversationJid },
146
+ { source: 'senderJid', jid: messageKey?.senderJid },
147
+ { source: 'meLidNormalized', jid: normalizeLidJid(messageKey?.meLid) }
148
+ ];
149
+ const seen = new Set();
150
+ const candidates = [];
151
+ for (const candidate of ordered) {
152
+ if (!candidate.jid) {
153
+ continue;
154
+ }
155
+ const jid = String(candidate.jid);
156
+ if (!seen.has(jid)) {
157
+ seen.add(jid);
158
+ candidates.push({ source: candidate.source, jid, value: Buffer.from(jid) });
159
+ }
160
+ }
161
+ return candidates;
162
+ };
163
+
164
+ const buildMsmsgDecryptionStrategies = (messageKey) => {
165
+ const botJid = String(messageKey?.participant || '');
166
+ const botJidBuffer = Buffer.from(botJid);
167
+ const targetIds = getCandidateIds(messageKey);
168
+ const jidCandidates = getJidCandidates(messageKey);
169
+ const primaryJid = jidCandidates[0];
170
+ const alternateJid = jidCandidates.find((candidate) => candidate.source !== primaryJid?.source && candidate.jid !== botJid);
171
+ const strategies = [];
172
+ const seen = new Set();
173
+ for (const idCandidate of targetIds) {
174
+ const idForms = buildMessageIdRepresentations(idCandidate.messageId);
175
+ for (const idForm of idForms) {
176
+ pushUnique(strategies, seen, {
177
+ mode: '2step',
178
+ idSource: idCandidate.idSource,
179
+ idSources: idCandidate.idSources,
180
+ infoSource: `${idForm.label}+meId+botJid`,
181
+ aadSource: `${idForm.label}+0+botJid`,
182
+ authTagLayout: 'trailing',
183
+ messageId: idCandidate.messageId,
184
+ info: Buffer.concat([idForm.value, primaryJid.value, botJidBuffer, Buffer.alloc(0)]),
185
+ aad: Buffer.concat([idForm.value, Buffer.from([0]), botJidBuffer]),
186
+ attemptLabel: `${idCandidate.idSource}:${idForm.label}:primary`
187
+ });
188
+ if (alternateJid) {
189
+ pushUnique(strategies, seen, {
190
+ mode: '2step',
191
+ idSource: idCandidate.idSource,
192
+ idSources: idCandidate.idSources,
193
+ infoSource: `${idForm.label}+${alternateJid.source}+botJid`,
194
+ aadSource: `${idForm.label}+0+${alternateJid.source}`,
195
+ authTagLayout: 'trailing',
196
+ messageId: idCandidate.messageId,
197
+ info: Buffer.concat([idForm.value, alternateJid.value, botJidBuffer, Buffer.alloc(0)]),
198
+ aad: Buffer.concat([idForm.value, Buffer.from([0]), alternateJid.value]),
199
+ attemptLabel: `${idCandidate.idSource}:${idForm.label}:${alternateJid.source}`
200
+ });
201
+ }
202
+ }
203
+ }
204
+ return strategies.slice(0, 12);
205
+ };
206
+
207
+ const assertRequired = (value, label) => {
208
+ if (!value ||
209
+ (Buffer.isBuffer(value) && value.length === 0) ||
210
+ (value instanceof Uint8Array && value.byteLength === 0)) {
211
+ throw new Error(`Missing required ${label} for msmsg decryption`);
212
+ }
213
+ };
214
+
215
+ const decryptWithStrategy = (messageSecret, msMsg, strategy) => {
216
+ const baseSecret = Buffer.from(hkdf(toBuffer(messageSecret), KEY_LENGTH, { info: BOT_MESSAGE_INFO }));
217
+ const key = Buffer.from(hkdf(baseSecret, KEY_LENGTH, { info: strategy.info }));
218
+ const payload = toBuffer(msMsg.encPayload);
219
+ const ciphertextWithTag = Buffer.concat([payload.slice(0, -AUTH_TAG_LENGTH), payload.slice(-AUTH_TAG_LENGTH)]);
220
+ return Buffer.from(aesDecryptGCM(ciphertextWithTag, key, toBuffer(msMsg.encIv), strategy.aad));
221
+ };
222
+
223
+ export const decodeDecryptedMsmsgMessage = (decrypted) => {
224
+ const messageBuffer = toBuffer(decrypted);
225
+ try {
226
+ const unpadded = Buffer.from(unpadRandomMax16(messageBuffer));
227
+ const decoded = proto.Message.decode(unpadded);
228
+ const hasContent = Object.keys(decoded).some((key) => key !== 'messageContextInfo' && decoded[key] != null);
229
+ if (hasContent) {
230
+ return decoded;
231
+ }
232
+ }
233
+ catch {
234
+ // fall through to decoding the raw buffer below
235
+ }
236
+ return proto.Message.decode(messageBuffer);
237
+ };
238
+
239
+ export const decryptMsmsgBotMessage = async (messageSecret, messageKey, msMsg) => {
240
+ assertRequired(messageSecret, 'messageSecret');
241
+ assertRequired(messageKey?.participant, 'participant');
242
+ assertRequired(messageKey?.meId, 'meId');
243
+ assertRequired(msMsg?.encIv, 'encIv');
244
+ assertRequired(msMsg?.encPayload, 'encPayload');
245
+ if (getCandidateIds(messageKey).length === 0) {
246
+ throw new Error('Missing required target message id for msmsg decryption');
247
+ }
248
+ const strategies = buildMsmsgDecryptionStrategies(messageKey);
249
+ const attemptedStrategies = [];
250
+ let lastError;
251
+ for (const strategy of strategies) {
252
+ const attempt = {
253
+ idSource: strategy.idSource,
254
+ idSources: strategy.idSources,
255
+ infoSource: strategy.infoSource,
256
+ aadSource: strategy.aadSource,
257
+ messageId: strategy.messageId
258
+ };
259
+ try {
260
+ return await decryptWithStrategy(messageSecret, msMsg, strategy);
261
+ }
262
+ catch (error) {
263
+ attemptedStrategies.push(attempt);
264
+ lastError = error;
265
+ }
266
+ }
267
+ const error = new Error('Failed to decrypt msmsg with bounded deterministic strategies');
268
+ error.attemptedStrategies = attemptedStrategies;
269
+ error.cause = lastError;
270
+ throw error;
271
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lib/Utils/native-bridge.js — best-effort loader for the optional
3
+ * `whatsapp-rust-bridge` native addon. If it isn't installed (it's an
4
+ * optional dependency), every function here just returns null instead of
5
+ * throwing, so nothing breaks for people who don't need it.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { createRequire } from 'module';
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const BRIDGE_NAME = 'whatsapp-rust-bridge';
13
+ let cached;
14
+ let attempted = false;
15
+
16
+ const findPackageDir = (pkgName) => {
17
+ let searchPaths;
18
+ try {
19
+ searchPaths = require.resolve.paths(pkgName) || [];
20
+ }
21
+ catch {
22
+ searchPaths = [];
23
+ }
24
+ for (const dir of searchPaths) {
25
+ const candidate = path.join(dir, pkgName);
26
+ if (fs.existsSync(path.join(candidate, 'package.json'))) {
27
+ return candidate;
28
+ }
29
+ }
30
+ return null;
31
+ };
32
+
33
+ export const loadRustBridge = () => {
34
+ if (attempted) {
35
+ return cached;
36
+ }
37
+ attempted = true;
38
+ try {
39
+ cached = require(BRIDGE_NAME);
40
+ return cached;
41
+ }
42
+ catch (error) {
43
+ const recoverableCodes = new Set(['ERR_PACKAGE_PATH_NOT_EXPORTED', 'ERR_PACKAGE_IMPORT_NOT_DEFINED']);
44
+ if (!recoverableCodes.has(error?.code)) {
45
+ cached = null;
46
+ return null;
47
+ }
48
+ try {
49
+ const dir = findPackageDir(BRIDGE_NAME);
50
+ if (!dir) {
51
+ cached = null;
52
+ return null;
53
+ }
54
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
55
+ const candidates = [
56
+ pkgJson.main,
57
+ 'index.js',
58
+ 'index.node',
59
+ 'dist/index.js',
60
+ 'lib/index.js',
61
+ 'build/index.js'
62
+ ].filter(Boolean);
63
+ for (const rel of candidates) {
64
+ const fullPath = path.join(dir, rel);
65
+ if (fs.existsSync(fullPath)) {
66
+ cached = require(fullPath);
67
+ return cached;
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // fall through to returning null below
73
+ }
74
+ cached = null;
75
+ return null;
76
+ }
77
+ };
@@ -0,0 +1,201 @@
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);
8
+ const generateIV = (counter) => {
9
+ const iv = new ArrayBuffer(IV_LENGTH);
10
+ new DataView(iv).setUint32(8, counter);
11
+ return new Uint8Array(iv);
12
+ };
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 }) => {
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
+ }
65
+ const authenticate = (data) => {
66
+ if (!transport) {
67
+ hash = sha256(Buffer.concat([hash, data]));
68
+ }
69
+ };
70
+ const encrypt = (plaintext) => {
71
+ if (transport) {
72
+ return transport.encrypt(plaintext);
73
+ }
74
+ const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash);
75
+ authenticate(result);
76
+ return result;
77
+ };
78
+ const decrypt = (ciphertext) => {
79
+ if (transport) {
80
+ return transport.decrypt(ciphertext);
81
+ }
82
+ const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash);
83
+ authenticate(ciphertext);
84
+ return result;
85
+ };
86
+ const localHKDF = (data) => {
87
+ const key = hkdf(Buffer.from(data), 64, { salt, info: '' });
88
+ return [key.subarray(0, 32), key.subarray(32)];
89
+ };
90
+ const mixIntoKey = (data) => {
91
+ const [write, read] = localHKDF(data);
92
+ salt = write;
93
+ encKey = read;
94
+ decKey = read;
95
+ counter = 0;
96
+ };
97
+ const finishInit = async () => {
98
+ isWaitingForTransport = true;
99
+ const [write, read] = localHKDF(new Uint8Array(0));
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
+ }
128
+ };
129
+ authenticate(NOISE_HEADER);
130
+ authenticate(publicKey);
131
+ return {
132
+ encrypt,
133
+ decrypt,
134
+ authenticate,
135
+ mixIntoKey,
136
+ finishInit,
137
+ processHandshake: ({ serverHello }, noiseKey) => {
138
+ authenticate(serverHello.ephemeral);
139
+ mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
140
+ const decStaticContent = decrypt(serverHello.static);
141
+ mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
142
+ const certDecoded = decrypt(serverHello.payload);
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 });
147
+ }
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 });
163
+ }
164
+ const keyEnc = encrypt(noiseKey.public);
165
+ mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
166
+ return keyEnc;
167
+ },
168
+ encodeFrame: (data) => {
169
+ if (transport) {
170
+ data = transport.encrypt(data);
171
+ }
172
+ const dataLen = data.byteLength;
173
+ const introSize = sentIntro ? 0 : introHeader.length;
174
+ const frame = Buffer.allocUnsafe(introSize + 3 + dataLen);
175
+ if (!sentIntro) {
176
+ frame.set(introHeader);
177
+ sentIntro = true;
178
+ }
179
+ frame[introSize] = (dataLen >>> 16) & 0xff;
180
+ frame[introSize + 1] = (dataLen >>> 8) & 0xff;
181
+ frame[introSize + 2] = dataLen & 0xff;
182
+ frame.set(data, introSize + 3);
183
+ return frame;
184
+ },
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);
193
+ }
194
+ else {
195
+ inBytes = Buffer.concat([inBytes, newData]);
196
+ }
197
+ await processData(onFrame);
198
+ }
199
+ };
200
+ };
201
+ //# sourceMappingURL=noise-handler.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Creates a processor for offline stanza nodes that:
3
+ * - Queues nodes for sequential processing
4
+ * - Yields to the event loop periodically to avoid blocking
5
+ * - Catches handler errors to prevent the processing loop from crashing
6
+ */
7
+ export function makeOfflineNodeProcessor(nodeProcessorMap, deps, batchSize = 10) {
8
+ const nodes = [];
9
+ let isProcessing = false;
10
+ const enqueue = (type, node) => {
11
+ nodes.push({ type, node });
12
+ if (isProcessing) {
13
+ return;
14
+ }
15
+ isProcessing = true;
16
+ const promise = async () => {
17
+ let processedInBatch = 0;
18
+ while (nodes.length && deps.isWsOpen()) {
19
+ const { type, node } = nodes.shift();
20
+ const nodeProcessor = nodeProcessorMap.get(type);
21
+ if (!nodeProcessor) {
22
+ deps.onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
23
+ continue;
24
+ }
25
+ await nodeProcessor(node).catch(err => deps.onUnexpectedError(err, `processing offline ${type}`));
26
+ processedInBatch++;
27
+ // Yield to event loop after processing a batch
28
+ // This prevents blocking the event loop for too long when there are many offline nodes
29
+ if (processedInBatch >= batchSize) {
30
+ processedInBatch = 0;
31
+ await deps.yieldToEventLoop();
32
+ }
33
+ }
34
+ isProcessing = false;
35
+ };
36
+ promise().catch(error => deps.onUnexpectedError(error, 'processing offline nodes'));
37
+ };
38
+ return { enqueue };
39
+ }
40
+ //# sourceMappingURL=offline-node-processor.js.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * lib/Utils/optimizer.js — opt-in resource-usage tuning ("optiMazer").
3
+ *
4
+ * OFF by default. This does NOT touch the safety caps that already exist
5
+ * unconditionally elsewhere (userDevicesCache, message-retry caches, and
6
+ * the guard log arrays all have sane hard limits regardless of this
7
+ * setting — those are bug fixes, not something you should be able to turn
8
+ * off). What `optiMazer` adds on top, only when enabled, is:
9
+ *
10
+ * - Tighter cache limits than the defaults (trades a little more
11
+ * re-fetching on cache misses for meaningfully less resident memory on
12
+ * a long-running, high-traffic process).
13
+ * - A periodic background trim/stats tick.
14
+ * - If Node was started with `--expose-gc`, a periodic proactive GC pass
15
+ * during that same tick (a safe no-op if `--expose-gc` wasn't used).
16
+ *
17
+ * It never changes protocol behavior, message content, or connection
18
+ * behavior — purely memory bookkeeping. See README.md → "optiMazer".
19
+ */
20
+
21
+ export const DEFAULT_OPTIMIZER_CONFIG = {
22
+ userDevicesCacheMaxKeys: 2000, // vs the always-on default of 10000
23
+ retryCountersMax: 1500, // vs the always-on default of 5000
24
+ sessionRecreateHistoryMax: 500, // vs the always-on default of 2000
25
+ guardLogMax: 50, // vs the always-on default of 200
26
+ gcIntervalMs: 5 * 60 * 1000, // only used if the process was started with --expose-gc
27
+ tickIntervalMs: 60 * 1000
28
+ };
29
+
30
+ /** Merge whatever the caller passed (boolean or partial config object) with the defaults above. */
31
+ export const resolveOptimizerConfig = (optiMazer) => {
32
+ if (!optiMazer) {
33
+ return null;
34
+ }
35
+ const overrides = typeof optiMazer === 'object' ? optiMazer : {};
36
+ return { ...DEFAULT_OPTIMIZER_CONFIG, ...overrides };
37
+ };
38
+
39
+ export class OptiMazer {
40
+ constructor(config = {}) {
41
+ this.config = { ...DEFAULT_OPTIMIZER_CONFIG, ...config };
42
+ this.stats = { ticks: 0, gcRuns: 0, startedAt: Date.now() };
43
+ this._timers = [];
44
+ }
45
+
46
+ /** Call once after building your socket. Safe to call multiple times (no-op if already attached). */
47
+ attach(sock) {
48
+ if (this._attached) {
49
+ return this;
50
+ }
51
+ this._attached = true;
52
+ const tick = () => {
53
+ this.stats.ticks++;
54
+ if (typeof global.gc === 'function') {
55
+ try {
56
+ global.gc();
57
+ this.stats.gcRuns++;
58
+ }
59
+ catch {
60
+ // --expose-gc wasn't actually set up correctly; just skip silently
61
+ }
62
+ }
63
+ };
64
+ const timer = setInterval(tick, this.config.tickIntervalMs);
65
+ timer.unref?.();
66
+ this._timers.push(timer);
67
+ this._sock = sock;
68
+ return this;
69
+ }
70
+
71
+ getStats() {
72
+ return {
73
+ ...this.stats,
74
+ uptimeMs: Date.now() - this.stats.startedAt,
75
+ gcAvailable: typeof global.gc === 'function',
76
+ memory: process.memoryUsage()
77
+ };
78
+ }
79
+
80
+ stop() {
81
+ for (const t of this._timers) {
82
+ clearInterval(t);
83
+ }
84
+ this._timers = [];
85
+ this._attached = false;
86
+ }
87
+ }
88
+
89
+ /** Convenience factory so you can also do `import { optiMazer } from '@xayz/baileys'`. */
90
+ export const optiMazer = (config) => new OptiMazer(config);