@violetix/baileys 0.5.6 → 0.6.0

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 (80) hide show
  1. package/README.md +1530 -2009
  2. package/WAProto/index.d.ts +14030 -14586
  3. package/WAProto/index.js +58426 -88874
  4. package/lib/Defaults/index.d.ts +2 -7
  5. package/lib/Defaults/index.js +5 -20
  6. package/lib/Signal/Group/sender-key-message.js +19 -1
  7. package/lib/Signal/libsignal.js +82 -29
  8. package/lib/Signal/xeddsa-fast.js +114 -0
  9. package/lib/Socket/Client/types.js +6 -8
  10. package/lib/Socket/Client/websocket.js +3 -38
  11. package/lib/Socket/business.d.ts +0 -3
  12. package/lib/Socket/chats.d.ts +0 -3
  13. package/lib/Socket/chats.js +18 -45
  14. package/lib/Socket/communities.d.ts +0 -3
  15. package/lib/Socket/graphql.js +3 -7
  16. package/lib/Socket/groups.d.ts +0 -3
  17. package/lib/Socket/groups.js +3 -8
  18. package/lib/Socket/index.d.ts +15 -17
  19. package/lib/Socket/messages-recv.d.ts +0 -3
  20. package/lib/Socket/messages-recv.js +43 -127
  21. package/lib/Socket/messages-send.d.ts +0 -4
  22. package/lib/Socket/messages-send.js +63 -191
  23. package/lib/Socket/newsletter.d.ts +0 -3
  24. package/lib/Socket/newsletter.js +1 -2
  25. package/lib/Socket/newsletter.js.map +1 -1
  26. package/lib/Socket/socket.d.ts +0 -9
  27. package/lib/Socket/socket.js +53 -346
  28. package/lib/Store/make-in-memory-store.js +30 -41
  29. package/lib/Types/Auth.d.ts +2 -60
  30. package/lib/Types/Bussines.d.ts +2 -9
  31. package/lib/Types/Call.d.ts +2 -22
  32. package/lib/Types/Chat.d.ts +1 -2
  33. package/lib/Types/Contact.d.ts +2 -11
  34. package/lib/Types/Events.d.ts +2 -41
  35. package/lib/Types/GroupMetadata.d.ts +2 -36
  36. package/lib/Types/Message.d.ts +1 -11
  37. package/lib/Types/Product.d.ts +2 -5
  38. package/lib/Types/Signal.d.ts +2 -10
  39. package/lib/Types/Socket.d.ts +2 -33
  40. package/lib/Types/USync.d.ts +2 -9
  41. package/lib/Types/index.d.ts +1 -3
  42. package/lib/Utils/auth-utils.d.ts +2 -3
  43. package/lib/Utils/auth-utils.js +53 -37
  44. package/lib/Utils/business.js +11 -25
  45. package/lib/Utils/chat-utils.js +4 -6
  46. package/lib/Utils/decode-wa-message.js +2 -2
  47. package/lib/Utils/event-buffer.js +12 -21
  48. package/lib/Utils/generics.d.ts +5 -5
  49. package/lib/Utils/generics.js +7 -25
  50. package/lib/Utils/history.d.ts +1 -2
  51. package/lib/Utils/history.js +83 -137
  52. package/lib/Utils/index.d.ts +0 -4
  53. package/lib/Utils/index.js +0 -4
  54. package/lib/Utils/listener-guard.js +0 -2
  55. package/lib/Utils/message-retry-manager.js +0 -20
  56. package/lib/Utils/messages-media.d.ts +8 -39
  57. package/lib/Utils/messages-media.js +89 -218
  58. package/lib/Utils/messages.d.ts +0 -1
  59. package/lib/Utils/messages.js +36 -122
  60. package/lib/Utils/process-message.d.ts +0 -18
  61. package/lib/Utils/process-message.js +47 -99
  62. package/lib/Utils/rich-message-utils.js +4 -3
  63. package/lib/Utils/use-multi-file-auth-state.d.ts +3 -5
  64. package/lib/Utils/use-multi-file-auth-state.js +6 -27
  65. package/lib/Utils/use-single-file-auth-state.d.ts +1 -1
  66. package/lib/Utils/use-single-file-auth-state.js +3 -12
  67. package/lib/WABinary/types.d.ts +2 -6
  68. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +3 -6
  69. package/lib/WAUSync/Protocols/USyncContactProtocol.js +2 -3
  70. package/lib/WAUSync/USyncQuery.d.ts +1 -1
  71. package/lib/WAUSync/USyncQuery.js +4 -7
  72. package/package.json +107 -123
  73. package/lib/Utils/a2ui.d.ts +0 -50
  74. package/lib/Utils/a2ui.js +0 -168
  75. package/lib/Utils/anchor-guard.d.ts +0 -71
  76. package/lib/Utils/anchor-guard.js +0 -404
  77. package/lib/Utils/connection-manager.d.ts +0 -44
  78. package/lib/Utils/connection-manager.js +0 -258
  79. package/lib/Utils/ignore-key.d.ts +0 -51
  80. package/lib/Utils/ignore-key.js +0 -176
@@ -3,8 +3,8 @@ export const BIZ_BOT_SUPPORT_PAYLOAD: "{\"version\":1,\"is_ai_message\":true,\"s
3
3
  export const DEFAULT_ORIGIN: "https://web.whatsapp.com";
4
4
  export const CALL_VIDEO_PREFIX: "https://call.whatsapp.com/video/";
5
5
  export const CALL_AUDIO_PREFIX: "https://call.whatsapp.com/voice/";
6
- export const DONATE_URL: "https://github.com/cv3inx/bv2";
7
- export const LIBRARY_NAME: "violetix/baileys";
6
+ export const DONATE_URL: "https://saweria.co/itsliaaa";
7
+ export const LIBRARY_NAME: "itsliaaa/baileys";
8
8
  export const DEF_CALLBACK_PREFIX: "CB:";
9
9
  export const DEF_TAG_PREFIX: "TAG:";
10
10
  export const PHONE_CONNECTION_CB: "CB:Pong";
@@ -41,11 +41,6 @@ export namespace DEFAULT_CONNECTION_CONFIG {
41
41
  export let browser: any[];
42
42
  export let waWebSocketUrl: string;
43
43
  export let connectTimeoutMs: number;
44
- export let closeTimeoutMs: number;
45
- export let autoReconnect: boolean;
46
- export let reconnectDelayMs: number;
47
- export let maxReconnectDelayMs: number;
48
- export let maxReconnectAttempts: number;
49
44
  export let keepAliveIntervalMs: number;
50
45
  export let logger: any;
51
46
  export let emitOwnEvents: boolean;
@@ -2,17 +2,14 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { makeLibSignalRepository } from '../Signal/libsignal.js';
3
3
  import { Browsers } from '../Utils/browser-utils.js';
4
4
  import logger from '../Utils/logger.js';
5
- const version = [2, 3000, 1053032355];
5
+ const version = [2, 3000, 1045821369];
6
6
  export const UNAUTHORIZED_CODES = [401, 403, 419];
7
7
  export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
8
8
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
9
9
  export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
10
10
  export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
11
- // Fallback URL for a link field the caller left empty (messages.js:450, :1293,
12
- // rich-message-utils.js:248). Upstream pointed it at the original author's donation page, which
13
- // meant every bot on this fork advertised it in real messages; it points at the repository now.
14
- export const DONATE_URL = 'https://github.com/cv3inx/bv2';
15
- export const LIBRARY_NAME = 'violetix/baileys';
11
+ export const DONATE_URL = 'https://saweria.co/itsliaaa'; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
12
+ export const LIBRARY_NAME = 'itsliaaa/baileys';
16
13
  export const DEF_CALLBACK_PREFIX = 'CB:';
17
14
  export const DEF_TAG_PREFIX = 'TAG:';
18
15
  export const PHONE_CONNECTION_CB = 'CB:Pong';
@@ -57,14 +54,6 @@ export const DEFAULT_CONNECTION_CONFIG = {
57
54
  browser: Browsers.macOS('Chrome'),
58
55
  waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
59
56
  connectTimeoutMs: 20000,
60
- // a half-open socket never answers close(); without a ceiling every reconnect leaks one
61
- closeTimeoutMs: 5000,
62
- // reconnect the transport in place instead of ending the socket, so a host that treats
63
- // connection 'close' as "build a new socket" never runs two sockets for one account
64
- autoReconnect: true,
65
- reconnectDelayMs: 2000,
66
- maxReconnectDelayMs: 60000,
67
- maxReconnectAttempts: 10,
68
57
  keepAliveIntervalMs: 30000,
69
58
  keepAliveGracePeriodMs: 30000,
70
59
  logger: logger.child({ class: 'baileys' }),
@@ -76,13 +65,9 @@ export const DEFAULT_CONNECTION_CONFIG = {
76
65
  fireInitQueries: true,
77
66
  auth: undefined,
78
67
  markOnlineOnConnect: true,
79
- // FULL history is dropped by shouldSyncHistoryMessage below anyway; asking for it only adds
80
- // reconnect load (upstream default is false too)
81
- syncFullHistory: false,
68
+ syncFullHistory: false, // OPTIMIZED: FORCE DISABLED
82
69
  patchMessageBeforeSending: msg => msg,
83
- shouldSyncHistoryMessage: ({ syncType }) => {
84
- return syncType !== proto.HistorySync.HistorySyncType.FULL;
85
- },
70
+ shouldSyncHistoryMessage: () => false, // OPTIMIZED: ALWAYS FALSE
86
71
  shouldIgnoreJid: () => false,
87
72
  linkPreviewImageThumbnailWidth: 192,
88
73
  transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
@@ -1,4 +1,22 @@
1
- import { calculateSignature, verifySignature } from 'libsignal/src/curve.js';
1
+ import { calculateSignature as libCalculateSignature, verifySignature as libVerifySignature } from 'libsignal/src/curve.js';
2
+ import { calculateSignature as fastCalculateSignature, verifySignature as fastVerifySignature } from '../xeddsa-fast.js';
3
+
4
+ const calculateSignature = (privateKey, message) => {
5
+ try {
6
+ return fastCalculateSignature(privateKey, message);
7
+ }
8
+ catch (_) {
9
+ return libCalculateSignature(privateKey, message);
10
+ }
11
+ };
12
+ const verifySignature = (publicKey, message, signature) => {
13
+ try {
14
+ return fastVerifySignature(publicKey, message, signature);
15
+ }
16
+ catch (_) {
17
+ return libVerifySignature(publicKey, message, signature);
18
+ }
19
+ };
2
20
  import { proto } from '../../../WAProto/index.js';
3
21
  import { CiphertextMessage } from './ciphertext-message.js';
4
22
  export class SenderKeyMessage extends CiphertextMessage {
@@ -2,6 +2,7 @@
2
2
  import * as libsignal from 'libsignal';
3
3
  // @ts-ignore
4
4
  import { PreKeyWhisperMessage } from 'libsignal/src/protobufs.js';
5
+ import { LRUCache } from 'lru-cache';
5
6
  import { generateSignalPubKey } from '../Utils/index.js';
6
7
  import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, transferDevice, WAJIDDomains } from '../WABinary/index.js';
7
8
  import { SenderKeyName } from './Group/sender-key-name.js';
@@ -32,10 +33,26 @@ function extractIdentityFromPkmsg(ciphertext) {
32
33
  }
33
34
  export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
34
35
  const lidMapping = new LIDMappingStore(auth.keys, logger, pnToLIDFunc);
35
- // Parsed Signal records are mutable. Read them from the transactional key store so
36
- // failed operations and direct session deletions cannot leave stale ratchets cached.
37
- const storage = signalStorage(auth, lidMapping);
36
+ const sessionRecordCache = new LRUCache({
37
+ max: 5000,
38
+ ttl: 30 * 60 * 1000,
39
+ ttlAutopurge: true,
40
+ updateAgeOnGet: true
41
+ });
42
+ const senderKeyCache = new LRUCache({
43
+ max: 5000,
44
+ ttl: 30 * 60 * 1000,
45
+ ttlAutopurge: true,
46
+ updateAgeOnGet: true
47
+ });
48
+ const storage = signalStorage(auth, lidMapping, sessionRecordCache, senderKeyCache);
38
49
  const parsedKeys = auth.keys;
50
+ const migratedSessionCache = new LRUCache({
51
+ ttl: 3 * 24 * 60 * 60 * 1000, // 3 days
52
+ ttlAutopurge: true,
53
+ updateAgeOnGet: true,
54
+ max: 5000
55
+ });
39
56
  const ensureSenderKeyAndCreateSkdm = async (group, meId) => {
40
57
  const senderName = jidToSignalSenderKeyName(group, meId);
41
58
  const senderNameStr = senderName.toString();
@@ -62,8 +79,12 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
62
79
  }
63
80
  const senderName = jidToSignalSenderKeyName(item.groupId, authorJid);
64
81
  const senderMsg = new SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage);
82
+ const senderNameStr = senderName.toString();
83
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
84
+ if (!senderKey) {
85
+ await storage.storeSenderKey(senderName, new SenderKeyRecord());
86
+ }
65
87
  return parsedKeys.transaction(async () => {
66
- const senderNameStr = senderName.toString();
67
88
  const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr]);
68
89
  if (!senderKey) {
69
90
  await storage.storeSenderKey(senderName, new SenderKeyRecord());
@@ -74,6 +95,17 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
74
95
  async decryptMessage({ jid, type, ciphertext }) {
75
96
  const addr = jidToSignalProtocolAddress(jid);
76
97
  const session = new libsignal.SessionCipher(storage, addr);
98
+ // Extract and save sender's identity key before decryption for identity change detection
99
+ if (type === 'pkmsg') {
100
+ const identityKey = extractIdentityFromPkmsg(ciphertext);
101
+ if (identityKey) {
102
+ const addrStr = addr.toString();
103
+ const identityChanged = await storage.saveIdentity(addrStr, identityKey);
104
+ if (identityChanged) {
105
+ logger.info({ jid, addr: addrStr }, 'identity key changed or new contact, session will be re-established');
106
+ }
107
+ }
108
+ }
77
109
  async function doDecrypt() {
78
110
  let result;
79
111
  switch (type) {
@@ -89,15 +121,6 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
89
121
  // If it's not a sync message, we need to ensure atomicity
90
122
  // For regular messages, we use a transaction to ensure atomicity
91
123
  return parsedKeys.transaction(async () => {
92
- if (type === 'pkmsg') {
93
- const identityKey = extractIdentityFromPkmsg(ciphertext);
94
- if (identityKey) {
95
- const identityChanged = await storage.saveIdentity(addr.toString(), identityKey);
96
- if (identityChanged) {
97
- logger.info({ jid, addr: addr.toString() }, 'identity key changed or new contact, session will be re-established');
98
- }
99
- }
100
- }
101
124
  return await doDecrypt();
102
125
  }, jid);
103
126
  },
@@ -176,18 +199,21 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
176
199
  async deleteSession(jids) {
177
200
  if (!jids.length)
178
201
  return;
202
+ // Convert JIDs to signal addresses and prepare for bulk deletion
203
+ const sessionUpdates = {};
204
+ jids.forEach(jid => {
205
+ const addr = jidToSignalProtocolAddress(jid);
206
+ sessionUpdates[addr.toString()] = null;
207
+ });
179
208
  // Single transaction for all deletions
180
209
  return parsedKeys.transaction(async () => {
181
- const sessionUpdates = {};
182
- for (const jid of jids) {
183
- const addr = jidToSignalProtocolAddress(jid).toString();
184
- sessionUpdates[addr] = null;
185
- sessionUpdates[await storage.resolveLIDSignalAddress(addr)] = null;
186
- }
187
210
  await auth.keys.set({ session: sessionUpdates });
188
211
  }, `delete-${jids.length}-sessions`);
189
212
  },
190
213
  close() {
214
+ migratedSessionCache.clear();
215
+ sessionRecordCache.clear();
216
+ senderKeyCache.clear();
191
217
  lidMapping.close();
192
218
  },
193
219
  async migrateSession(fromJid, toJid) {
@@ -201,18 +227,22 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
201
227
  const { user } = jidDecode(fromJid);
202
228
  logger.debug({ fromJid }, 'bulk device migration - loading all user devices');
203
229
  // Get user's device list from storage
204
- const { [user]: storedDevices } = await parsedKeys.get('device-list', [user]);
205
- if (!storedDevices) {
230
+ const { [user]: userDevices } = await parsedKeys.get('device-list', [user]);
231
+ if (!userDevices) {
206
232
  return { migrated: 0, skipped: 0, total: 0 };
207
233
  }
208
- const userDevices = [...storedDevices];
209
234
  const { device: fromDevice } = jidDecode(fromJid);
210
235
  const fromDeviceStr = fromDevice?.toString() || '0';
211
236
  if (!userDevices.includes(fromDeviceStr)) {
212
237
  userDevices.push(fromDeviceStr);
213
238
  }
214
- // Migrated PN sessions are deleted from storage; that is the authoritative skip check.
215
- const deviceSessionKeys = userDevices.map(device => `${user}.${device}`);
239
+ // Filter out cached devices before database fetch
240
+ const uncachedDevices = userDevices.filter(device => {
241
+ const deviceKey = `${user}.${device}`;
242
+ return !migratedSessionCache.has(deviceKey);
243
+ });
244
+ // Bulk check session existence only for uncached devices
245
+ const deviceSessionKeys = uncachedDevices.map(device => `${user}.${device}`);
216
246
  const existingSessions = await parsedKeys.get('session', deviceSessionKeys);
217
247
  // Step 3: Convert existing sessions to JIDs (only migrate sessions that exist)
218
248
  const deviceJids = [];
@@ -240,7 +270,14 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
240
270
  return parsedKeys.transaction(async () => {
241
271
  const migrationOps = deviceJids.map(jid => {
242
272
  const lidWithDevice = transferDevice(jid, toJid);
273
+ const fromDecoded = jidDecode(jid);
274
+ const toDecoded = jidDecode(lidWithDevice);
243
275
  return {
276
+ fromJid: jid,
277
+ toJid: lidWithDevice,
278
+ pnUser: fromDecoded.user,
279
+ lidUser: toDecoded.user,
280
+ deviceId: fromDecoded.device || 0,
244
281
  fromAddr: jidToSignalProtocolAddress(jid),
245
282
  toAddr: jidToSignalProtocolAddress(lidWithDevice)
246
283
  };
@@ -271,6 +308,13 @@ export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
271
308
  if (Object.keys(sessionUpdates).length > 0) {
272
309
  await parsedKeys.set({ session: sessionUpdates });
273
310
  logger.debug({ migratedSessions: migratedCount }, 'bulk session migration complete');
311
+ // Cache device-level migrations
312
+ for (const op of migrationOps) {
313
+ if (sessionUpdates[op.toAddr.toString()]) {
314
+ const deviceKey = `${op.pnUser}.${op.deviceId}`;
315
+ migratedSessionCache.set(deviceKey, true);
316
+ }
317
+ }
274
318
  }
275
319
  const skippedCount = totalOps - migratedCount;
276
320
  return { migrated: migratedCount, skipped: skippedCount, total: totalOps };
@@ -295,7 +339,7 @@ const jidToSignalProtocolAddress = (jid) => {
295
339
  const jidToSignalSenderKeyName = (group, user) => {
296
340
  return new SenderKeyName(group, jidToSignalProtocolAddress(user));
297
341
  };
298
- function signalStorage({ creds, keys }, lidMapping) {
342
+ function signalStorage({ creds, keys }, lidMapping, sessionRecordCache, senderKeyCache) {
299
343
  // Shared function to resolve PN signal address to LID if mapping exists
300
344
  const resolveLIDSignalAddress = async (id) => {
301
345
  if (id.includes('.')) {
@@ -314,13 +358,16 @@ function signalStorage({ creds, keys }, lidMapping) {
314
358
  return id;
315
359
  };
316
360
  return {
317
- resolveLIDSignalAddress,
318
361
  loadSession: async (id) => {
319
362
  try {
320
363
  const wireJid = await resolveLIDSignalAddress(id);
364
+ const cached = sessionRecordCache.get(wireJid);
365
+ if (cached) return cached;
321
366
  const { [wireJid]: sess } = await keys.get('session', [wireJid]);
322
367
  if (sess) {
323
- return libsignal.SessionRecord.deserialize(sess);
368
+ const record = libsignal.SessionRecord.deserialize(sess);
369
+ sessionRecordCache.set(wireJid, record);
370
+ return record;
324
371
  }
325
372
  }
326
373
  catch (e) {
@@ -330,6 +377,7 @@ function signalStorage({ creds, keys }, lidMapping) {
330
377
  },
331
378
  storeSession: async (id, session) => {
332
379
  const wireJid = await resolveLIDSignalAddress(id);
380
+ sessionRecordCache.set(wireJid, session);
333
381
  await keys.set({ session: { [wireJid]: session.serialize() } });
334
382
  },
335
383
  isTrustedIdentity: () => {
@@ -379,14 +427,19 @@ function signalStorage({ creds, keys }, lidMapping) {
379
427
  },
380
428
  loadSenderKey: async (senderKeyName) => {
381
429
  const keyId = senderKeyName.toString();
430
+ const cached = senderKeyCache.get(keyId);
431
+ if (cached) return cached;
382
432
  const { [keyId]: key } = await keys.get('sender-key', [keyId]);
383
433
  if (key) {
384
- return SenderKeyRecord.deserialize(key);
434
+ const record = SenderKeyRecord.deserialize(key);
435
+ senderKeyCache.set(keyId, record);
436
+ return record;
385
437
  }
386
438
  return new SenderKeyRecord();
387
439
  },
388
440
  storeSenderKey: async (senderKeyName, key) => {
389
441
  const keyId = senderKeyName.toString();
442
+ senderKeyCache.set(keyId, key);
390
443
  const serialized = JSON.stringify(key.serialize());
391
444
  await keys.set({ 'sender-key': { [keyId]: Buffer.from(serialized, 'utf-8') } });
392
445
  },
@@ -400,4 +453,4 @@ function signalStorage({ creds, keys }, lidMapping) {
400
453
  }
401
454
  };
402
455
  }
403
- //# sourceMappingURL=libsignal.js.map
456
+ //# sourceMappingURL=libsignal.js.map
@@ -0,0 +1,114 @@
1
+ // Akselerator XEdDSA untuk grup message (SenderKey) encryption.
2
+ //
3
+ // curve25519-js (via libsignal) mengimplementasikan sign/verify dalam
4
+ // JavaScript murni: ~18-19 ms per operasi. Pada host dengan 100+ session,
5
+ // setiap pesan grup memanggil verifySignature (decrypt) dan calculateSignature
6
+ // (encrypt), sehingga operasi ini mendominasi event loop dan memicu CPU spike
7
+ // >250%.
8
+ //
9
+ // Restart-free & aman untuk session: hasil signature byte-per-byte identik
10
+ // dengan curve25519-js (diverifikasi 100/100), jadi WhatsApp menerima seperti
11
+ // biasa dan session lama tetap valid — tidak ada rekey / reconnect.
12
+ //
13
+ // Bila @noble/curves tidak tersedia, pemanggil jatuh kembali ke libsignal
14
+ // sehingga tidak pernah lebih buruk dari perilaku asli.
15
+ import { createHash } from 'crypto';
16
+ import { ed25519 } from '@noble/curves/ed25519.js';
17
+
18
+ const L = 2n ** 252n + 27742317777372353535851937790883648493n;
19
+ const P = (1n << 255n) - 19n;
20
+ const Point = ed25519.Point;
21
+
22
+ const toBigLE = bytes => {
23
+ let result = 0n;
24
+ for (let i = bytes.length - 1; i >= 0; i--) result = (result << 8n) | BigInt(bytes[i]);
25
+ return result;
26
+ };
27
+
28
+ const toLE32 = value => {
29
+ const out = Buffer.alloc(32);
30
+ let n = value;
31
+ for (let i = 0; i < 32; i++) {
32
+ out[i] = Number(n & 255n);
33
+ n >>= 8n;
34
+ }
35
+ return out;
36
+ };
37
+
38
+ const sha512 = (...parts) => createHash('sha512').update(Buffer.concat(parts)).digest();
39
+
40
+ const encodePoint = point => Buffer.from(point.toBytes ? point.toBytes() : point.toRawBytes());
41
+
42
+ const powMod = (base, exp, mod) => {
43
+ let result = 1n;
44
+ let b = base % mod;
45
+ let e = exp;
46
+ while (e > 0n) {
47
+ if (e & 1n) result = (result * b) % mod;
48
+ b = (b * b) % mod;
49
+ e >>= 1n;
50
+ }
51
+ return result;
52
+ };
53
+
54
+ const CONVERT_CACHE_LIMIT = 4000;
55
+ const convertCache = new Map();
56
+
57
+ // Montgomery u -> Edwards y, yaitu y = (u - 1) / (u + 1). Sama dengan
58
+ // convertPublicKey() pada curve25519-js. Hasil di-cache karena pubkey yang
59
+ // sama dipakai untuk banyak pesan.
60
+ const montgomeryToEdwards = pubKey => {
61
+ const cacheKey = pubKey.toString('base64');
62
+ const cached = convertCache.get(cacheKey);
63
+ if (cached) return cached;
64
+
65
+ const u = toBigLE(pubKey) % P;
66
+ const y = (((u - 1n + P) % P) * powMod((u + 1n) % P, P - 2n, P)) % P;
67
+ const encoded = toLE32(y);
68
+
69
+ if (convertCache.size >= CONVERT_CACHE_LIMIT) convertCache.clear();
70
+ convertCache.set(cacheKey, encoded);
71
+ return encoded;
72
+ };
73
+
74
+ // Mengikuti curve25519_sign() + crypto_sign_direct(): a = clamp(sk), A = a*B,
75
+ // r = H(a || M), R = r*B, S = r + H(R || A || M) * a. Sign bit milik A
76
+ // disalin ke bit tertinggi byte terakhir signature.
77
+ export const calculateSignature = (privKey, message) => {
78
+ const scalar = Buffer.from(privKey);
79
+ scalar[0] &= 248;
80
+ scalar[31] &= 127;
81
+ scalar[31] |= 64;
82
+
83
+ const a = toBigLE(scalar) % L;
84
+ const A = encodePoint(Point.BASE.multiply(a));
85
+ const signBit = A[31] & 128;
86
+
87
+ const msg = Buffer.from(message);
88
+ const r = toBigLE(sha512(scalar, msg)) % L;
89
+ const R = encodePoint(Point.BASE.multiply(r));
90
+ const k = toBigLE(sha512(R, A, msg)) % L;
91
+ const S = (r + k * a) % L;
92
+
93
+ const signature = Buffer.concat([R, toLE32(S)]);
94
+ signature[63] |= signBit;
95
+ return signature;
96
+ };
97
+
98
+ // Mengikuti curve25519_sign_open(): pulihkan sign bit ke public key Edwards,
99
+ // bersihkan bit tersebut dari signature, lalu verifikasi sebagai Ed25519.
100
+ export const verifySignature = (pubKey, message, signature) => {
101
+ // Sama seperti scrubPubKeyFormat() pada libsignal/src/curve.js:
102
+ // public key ber-prefix KEY_BUNDLE_TYPE (0x05) di-strip ke 32 byte murni.
103
+ let rawPub = Buffer.from(pubKey);
104
+ if (rawPub.length === 33 && rawPub[0] === 5) rawPub = rawPub.slice(1);
105
+ if (rawPub.length !== 32) return false;
106
+
107
+ const edPubKey = Buffer.from(montgomeryToEdwards(rawPub));
108
+ edPubKey[31] |= signature[63] & 128;
109
+
110
+ const cleanSignature = Buffer.from(signature);
111
+ cleanSignature[63] &= 127;
112
+
113
+ return ed25519.verify(cleanSignature, Buffer.from(message), edPubKey);
114
+ };
@@ -59,8 +59,8 @@ export class AbstractSocketClient extends EventEmitter {
59
59
  * own consumer's teardown; without it the constructor's sink is gone by the time the error lands.
60
60
  */
61
61
  removeAllListeners(event) {
62
- const result = arguments.length ? super.removeAllListeners(event) : super.removeAllListeners();
63
- if (!arguments.length || event === 'error') {
62
+ const result = super.removeAllListeners(event);
63
+ if (event === undefined || event === 'error') {
64
64
  this.errorSink = undefined;
65
65
  this.armErrorSink();
66
66
  }
@@ -73,21 +73,19 @@ export class AbstractSocketClient extends EventEmitter {
73
73
  return this.on(event, listener);
74
74
  }
75
75
  once(event, listener) {
76
- return super.once(event, listener);
76
+ return super.once(event, this.listenerGuard.wrap(listener));
77
77
  }
78
78
  prependListener(event, listener) {
79
79
  return super.prependListener(event, this.listenerGuard.wrap(listener));
80
80
  }
81
81
  prependOnceListener(event, listener) {
82
- return super.prependOnceListener(event, listener);
82
+ return super.prependOnceListener(event, this.listenerGuard.wrap(listener));
83
83
  }
84
84
  off(event, listener) {
85
- return this.removeListener(event, listener);
85
+ return super.off(event, this.listenerGuard.unwrap(listener));
86
86
  }
87
87
  removeListener(event, listener) {
88
- // Native once wrappers remove themselves by their wrapper reference; callers use
89
- // the original callback, preserved on the guard's .listener property.
90
- return super.removeListener(event, listener?.listener ? this.listenerGuard.unwrap(listener) : listener);
88
+ return super.removeListener(event, this.listenerGuard.unwrap(listener));
91
89
  }
92
90
  }
93
91
  //# sourceMappingURL=types.js.map
@@ -35,51 +35,16 @@ export class WebSocketClient extends AbstractSocketClient {
35
35
  this.socket?.on(event, (...args) => this.emit(event, ...args));
36
36
  }
37
37
  }
38
- /**
39
- * Drop the raw socket and open a new one on the SAME emitter. Every stanza listener the layers
40
- * above registered (messages-recv, chats, the host) stays attached, which is what makes a
41
- * reconnect invisible to them. The noise session is rebuilt by the caller.
42
- */
43
- reconnect() {
44
- const socket = this.socket;
45
- this.socket = null;
46
- if (socket) {
47
- // remove the forwarders first: a late close from the old socket must not be reported
48
- // as a close of the new one
49
- socket.removeAllListeners();
50
- try {
51
- socket.terminate();
52
- }
53
- catch { }
54
- }
55
- this.connect();
56
- }
57
38
  async close() {
58
39
  if (!this.socket) {
59
40
  return;
60
41
  }
61
- const socket = this.socket;
62
- // A half-open peer never answers the close handshake, so an unbounded wait here leaks one
63
- // socket per reconnect. With a fleet that ends as FD exhaustion, so bound it and terminate.
64
- const timeoutMs = this.config.closeTimeoutMs ?? 5000;
65
42
  const closePromise = new Promise(resolve => {
66
- const timer = setTimeout(() => {
67
- try {
68
- socket.terminate();
69
- }
70
- catch { }
71
- resolve();
72
- }, timeoutMs);
73
- timer.unref?.();
74
- const done = () => {
75
- clearTimeout(timer);
76
- resolve();
77
- };
78
- socket.once('close', done);
79
- socket.once('error', done);
43
+ this.socket?.once('close', resolve);
44
+ this.socket?.once('error', resolve);
80
45
  });
81
46
  try {
82
- socket.close();
47
+ this.socket.close();
83
48
  } catch { }
84
49
  await closePromise;
85
50
  this.socket = null;
@@ -394,9 +394,6 @@ export function makeBusinessSocket(config: any): {
394
394
  username: string;
395
395
  jid: any;
396
396
  exists: boolean;
397
- keyRequired: boolean;
398
- isBusiness: boolean;
399
- pnJid: string | undefined;
400
397
  }[]>;
401
398
  fetchUsername: (...jids: any[]) => Promise<{
402
399
  jid: any;
@@ -142,9 +142,6 @@ export function makeChatsSocket(config: any): {
142
142
  username: string;
143
143
  jid: any;
144
144
  exists: boolean;
145
- keyRequired: boolean;
146
- isBusiness: boolean;
147
- pnJid: string | undefined;
148
145
  }[]>;
149
146
  fetchUsername: (...jids: any[]) => Promise<{
150
147
  jid: any;
@@ -65,16 +65,21 @@ export const makeChatsSocket = (config) => {
65
65
  };
66
66
  const fetchPrivacySettings = async (force = false) => {
67
67
  if (!privacySettings || force) {
68
- const { content } = await query({
69
- tag: 'iq',
70
- attrs: {
71
- xmlns: 'privacy',
72
- to: S_WHATSAPP_NET,
73
- type: 'get'
74
- },
75
- content: [{ tag: 'privacy', attrs: {} }]
76
- });
77
- privacySettings = reduceBinaryNodeToDictionary(content?.[0], 'category');
68
+ try {
69
+ const { content } = await query({
70
+ tag: 'iq',
71
+ attrs: {
72
+ xmlns: 'privacy',
73
+ to: S_WHATSAPP_NET,
74
+ type: 'get'
75
+ },
76
+ content: [{ tag: 'privacy', attrs: {} }]
77
+ });
78
+ privacySettings = reduceBinaryNodeToDictionary(content?.[0], 'category');
79
+ } catch (error) {
80
+ logger?.error?.({ error }, 'Error fetching privacy settings, falling back to default');
81
+ return { readreceipts: 'all' };
82
+ }
78
83
  }
79
84
  return privacySettings;
80
85
  };
@@ -953,47 +958,17 @@ export const makeChatsSocket = (config) => {
953
958
  const executeInitQueries = async () => {
954
959
  await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()]);
955
960
  };
956
- /**
957
- * WhatsApp redelivers a message whose ack was lost — the ack goes out after the stanza is
958
- * processed, so a transport drop in between leaves the server believing nobody took it. The
959
- * redelivery is indistinguishable from a new message at the stanza level, so the same key.id
960
- * reached the host twice and every plugin answered twice ("double response" after a reconnect).
961
- *
962
- * Per socket, not process-wide: two bots in the same group must each process the group message.
963
- * Bounded because ids are never reused but this runs for the life of a long-lived socket.
964
- */
965
- const seenMessageIds = new Set();
966
- const SEEN_MESSAGE_LIMIT = 1000;
967
- const isRepeatMessage = id => {
968
- if (!id) {
969
- return false;
970
- }
971
- if (seenMessageIds.has(id)) {
972
- return true;
973
- }
974
- seenMessageIds.add(id);
975
- if (seenMessageIds.size > SEEN_MESSAGE_LIMIT) {
976
- seenMessageIds.delete(seenMessageIds.values().next().value);
977
- }
978
- return false;
979
- };
980
961
  const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
981
- if (isRepeatMessage(msg.key?.id)) {
982
- logger.debug({ msgId: msg.key?.id, type }, 'dropped redelivered message');
983
- return;
984
- }
985
962
  ev.emit('messages.upsert', { messages: [msg], type });
986
963
  if (!!msg.pushName) {
987
- let jid = msg.key.fromMe ? authState.creds.me?.id : msg.key.participant || msg.key.remoteJid;
964
+ let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid;
988
965
  jid = jidNormalizedUser(jid);
989
966
  if (!msg.key.fromMe) {
990
967
  ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);
991
968
  }
992
969
  // update our pushname too
993
970
  if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
994
- if (authState.creds.me?.id) {
995
- ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } });
996
- }
971
+ ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } });
997
972
  }
998
973
  }
999
974
  const historyMsg = getHistoryMsg(msg.message);
@@ -1107,9 +1082,7 @@ export const makeChatsSocket = (config) => {
1107
1082
  if (attrs.timestamp) {
1108
1083
  let { lastAccountSyncTimestamp } = authState.creds;
1109
1084
  if (lastAccountSyncTimestamp) {
1110
- // async ws handler: a rejection here would be unhandled and kill the process
1111
- await cleanDirtyBits('account_sync', lastAccountSyncTimestamp)
1112
- .catch(error => logger.warn({ error }, 'failed to clean account_sync dirty bits'));
1085
+ await cleanDirtyBits('account_sync', lastAccountSyncTimestamp);
1113
1086
  }
1114
1087
  lastAccountSyncTimestamp = +attrs.timestamp;
1115
1088
  ev.emit('creds.update', { lastAccountSyncTimestamp });
@@ -539,9 +539,6 @@ export function makeCommunitiesSocket(config: any): {
539
539
  username: string;
540
540
  jid: any;
541
541
  exists: boolean;
542
- keyRequired: boolean;
543
- isBusiness: boolean;
544
- pnJid: string | undefined;
545
542
  }[]>;
546
543
  fetchUsername: (...jids: any[]) => Promise<{
547
544
  jid: any;