@realvare/based 2.7.70 → 2.7.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": [2, 3000, 1031424117]
2
+ "version": [2, 3000, 1032094433]
3
3
  }
@@ -48,4 +48,6 @@ export declare const DEFAULT_CACHE_TTLS: {
48
48
  MSG_RETRY: number;
49
49
  CALL_OFFER: number;
50
50
  USER_DEVICES: number;
51
- };
51
+ LID_JID: number;
52
+ PROFILE_PIC: number;
53
+ };
@@ -18,7 +18,7 @@ exports.WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60;
18
18
  exports.NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0';
19
19
  exports.DICT_VERSION = 3;
20
20
  exports.KEY_BUNDLE_TYPE = Buffer.from([5]);
21
- exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION]); // last isambasedd "DICT_VERSION"
21
+ exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION]); // last is "DICT_VERSION"
22
22
  /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
23
23
  exports.URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g;
24
24
  exports.WA_CERT_DETAILS = {
@@ -103,4 +103,6 @@ exports.DEFAULT_CACHE_TTLS = {
103
103
  MSG_RETRY: 60 * 60, // 1 hour
104
104
  CALL_OFFER: 5 * 60, // 5 minutes
105
105
  USER_DEVICES: 5 * 60, // 5 minutes
106
+ LID_JID: 5 * 60, // 5 minutes
107
+ PROFILE_PIC: 60 * 60, // 1 hour
106
108
  };
@@ -6,4 +6,4 @@ export declare class CiphertextMessage {
6
6
  readonly SENDERKEY_TYPE: number;
7
7
  readonly SENDERKEY_DISTRIBUTION_TYPE: number;
8
8
  readonly ENCRYPTED_MESSAGE_OVERHEAD: number;
9
- }
9
+ }
@@ -12,4 +12,4 @@ class CiphertextMessage {
12
12
  this.ENCRYPTED_MESSAGE_OVERHEAD = 53;
13
13
  }
14
14
  }
15
- exports.CiphertextMessage = CiphertextMessage;
15
+ exports.CiphertextMessage = CiphertextMessage;
@@ -8,4 +8,4 @@ export declare class SenderMessageKey {
8
8
  getIv(): Uint8Array;
9
9
  getCipherKey(): Uint8Array;
10
10
  getSeed(): Uint8Array;
11
- }
11
+ }
@@ -26,4 +26,4 @@ class SenderMessageKey {
26
26
  return this.seed;
27
27
  }
28
28
  }
29
- exports.SenderMessageKey = SenderMessageKey;
29
+ exports.SenderMessageKey = SenderMessageKey;
@@ -1,3 +1,3 @@
1
1
  import { SignalAuthState } from '../Types';
2
2
  import { SignalRepository } from '../Types/Signal';
3
- export declare function makeLibSignalRepository(auth: SignalAuthState): SignalRepository;
3
+ export declare function makeLibSignalRepository(auth: SignalAuthState): SignalRepository;
@@ -169,4 +169,4 @@ export declare const makeBusinessSocket: (config: SocketConfig) => {
169
169
  requestPairingCode: (phoneNumber: any, pairKey?: string) => Promise<string>;
170
170
  waitForConnectionUpdate: (check: (u: Partial<import("../Types").ConnectionState>) => Promise<boolean | undefined>, timeoutMs?: number) => Promise<void>;
171
171
  sendWAMBuffer: (wamBuffer: Buffer) => Promise<any>;
172
- };
172
+ };
@@ -257,4 +257,4 @@ const makeBusinessSocket = (config) => {
257
257
  productUpdate
258
258
  };
259
259
  };
260
- exports.makeBusinessSocket = makeBusinessSocket;
260
+ exports.makeBusinessSocket = makeBusinessSocket;
@@ -28,6 +28,9 @@ export declare const makeChatsSocket: (config: SocketConfig) => {
28
28
  updateProfileStatus: (status: string) => Promise<void>;
29
29
  updateProfileName: (name: string) => Promise<void>;
30
30
  updateBlockStatus: (jid: string, action: "block" | "unblock") => Promise<void>;
31
+ reportUser: (jid: string, reason?: "spam" | "harassment" | "impersonation" | "scam" | "other", waitForAckMs?: number) => Promise<boolean>;
32
+ reportMessage: (jid: string, messageId: string, reason?: "spam" | "harassment" | "impersonation" | "scam" | "other", participantJid?: string, waitForAckMs?: number) => Promise<boolean>;
33
+ reportAndBlockUser: (jid: string, reason?: "spam" | "harassment" | "impersonation" | "scam" | "other", waitForAckMs?: number) => Promise<boolean>;
31
34
  updateCallPrivacy: (value: WAPrivacyCallValue) => Promise<void>;
32
35
  updateMessagesPrivacy: (value: WAPrivacyMessagesValue) => Promise<void>;
33
36
  updateLastSeenPrivacy: (value: WAPrivacyValue) => Promise<void>;
@@ -79,4 +82,4 @@ export declare const makeChatsSocket: (config: SocketConfig) => {
79
82
  requestPairingCode: (phoneNumber: any, pairKey?: string) => Promise<string>;
80
83
  waitForConnectionUpdate: (check: (u: Partial<import("../Types").ConnectionState>) => Promise<boolean | undefined>, timeoutMs?: number) => Promise<void>;
81
84
  sendWAMBuffer: (wamBuffer: Buffer) => Promise<any>;
82
- };
85
+ };
@@ -25,6 +25,8 @@ const makeChatsSocket = (config) => {
25
25
  let privacySettings;
26
26
  let needToFlushWithAppStateSync = false;
27
27
  let pendingAppStateSync = false;
28
+ let lastSelfPresenceType;
29
+ let lastSelfPresenceTs = 0;
28
30
  /** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
29
31
  const processingMutex = (0, make_mutex_1.makeMutex)();
30
32
  const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
@@ -34,12 +36,15 @@ const makeChatsSocket = (config) => {
34
36
  if (!config.placeholderResendCache) {
35
37
  config.placeholderResendCache = placeholderResendCache;
36
38
  }
37
- const resolveJid = (jid) => {
38
- if (typeof jid === 'string' && jid.endsWith('@lid')) {
39
- return (0, WABinary_1.lidToJid)(jid);
40
- }
41
- return jid;
42
- };
39
+ const profilePictureUrlCache = config.profilePictureUrlCache || new node_cache_1.default({
40
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.PROFILE_PIC,
41
+ useClones: false
42
+ });
43
+ if (!config.profilePictureUrlCache) {
44
+ config.profilePictureUrlCache = profilePictureUrlCache;
45
+ }
46
+ const inFlightProfilePictureUrl = new Map();
47
+ const resolveJid = WABinary_1.resolveJid;
43
48
  /** helper function to fetch the given app state sync key */
44
49
  const getAppStateSyncKey = async (keyId) => {
45
50
  const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId]);
@@ -295,6 +300,110 @@ const makeChatsSocket = (config) => {
295
300
  ]
296
301
  });
297
302
  };
303
+ const reportUser = async (jid, reason = 'spam', waitForAckMs = 5000) => {
304
+ jid = resolveJid(jid);
305
+ const reasonCodes = {
306
+ 'spam': 1,
307
+ 'harassment': 2,
308
+ 'impersonation': 3,
309
+ 'scam': 4,
310
+ 'other': 5
311
+ };
312
+ const reasonCode = reasonCodes[reason] || 1;
313
+ const reportTo = WABinary_1.S_WHATSAPP_NET.replace(/^@/, '');
314
+ const node = {
315
+ tag: 'iq',
316
+ attrs: {
317
+ id: generateMessageTag(),
318
+ xmlns: 'w:report',
319
+ to: reportTo,
320
+ type: 'set'
321
+ },
322
+ content: [
323
+ {
324
+ tag: 'user',
325
+ attrs: {
326
+ jid,
327
+ reason: reasonCode.toString()
328
+ }
329
+ }
330
+ ]
331
+ };
332
+ logger.debug({ node }, 'sending report user node');
333
+ try {
334
+ await query(node, waitForAckMs);
335
+ logger.info({ jid, reason }, 'reported user (ack received)');
336
+ return true;
337
+ }
338
+ catch (err) {
339
+ const statusCode = err?.output?.statusCode || err?.statusCode;
340
+ const isTimeout = statusCode === 408 || (err?.message && err.message.toLowerCase().includes('timed out'));
341
+ if (isTimeout) {
342
+ logger.warn({ jid, reason }, 'reported user (no ack; request likely sent)');
343
+ return false;
344
+ }
345
+ throw err;
346
+ }
347
+ };
348
+ const reportMessage = async (jid, messageId, reason = 'spam', participantJid, waitForAckMs = 5000) => {
349
+ jid = resolveJid(jid);
350
+ if (participantJid) {
351
+ participantJid = resolveJid(participantJid);
352
+ }
353
+ const reasonCodes = {
354
+ 'spam': 1,
355
+ 'harassment': 2,
356
+ 'impersonation': 3,
357
+ 'scam': 4,
358
+ 'other': 5
359
+ };
360
+ const reasonCode = reasonCodes[reason] || 1;
361
+ const reportTo = WABinary_1.S_WHATSAPP_NET.replace(/^@/, '');
362
+ const msgAttrs = {
363
+ id: messageId,
364
+ reason: reasonCode.toString()
365
+ };
366
+ if (participantJid) {
367
+ msgAttrs.participant = participantJid;
368
+ }
369
+ const node = {
370
+ tag: 'iq',
371
+ attrs: {
372
+ id: generateMessageTag(),
373
+ xmlns: 'w:report',
374
+ to: reportTo,
375
+ type: 'set'
376
+ },
377
+ content: [
378
+ {
379
+ tag: 'message',
380
+ attrs: msgAttrs
381
+ }
382
+ ]
383
+ };
384
+ logger.debug({ node }, 'sending report message node');
385
+ try {
386
+ await query(node, waitForAckMs);
387
+ logger.info({ jid, messageId, reason, participantJid }, 'reported message (ack received)');
388
+ return true;
389
+ }
390
+ catch (err) {
391
+ const statusCode = err?.output?.statusCode || err?.statusCode;
392
+ const isTimeout = statusCode === 408 || (err?.message && err.message.toLowerCase().includes('timed out'));
393
+ if (isTimeout) {
394
+ logger.warn({ jid, messageId, reason, participantJid }, 'reported message (no ack; request likely sent)');
395
+ return false;
396
+ }
397
+ throw err;
398
+ }
399
+ };
400
+ const reportAndBlockUser = async (jid, reason = 'spam', waitForAckMs = 5000) => {
401
+ jid = resolveJid(jid);
402
+ const ack = await reportUser(jid, reason, waitForAckMs);
403
+ await updateBlockStatus(jid, 'block');
404
+ logger.info({ jid, reason, ack }, 'reported and blocked user');
405
+ return ack;
406
+ };
298
407
  const getBusinessProfile = async (jid) => {
299
408
  jid = resolveJid(jid);
300
409
  var _a, _b, _c, _d, _e, _f, _g;
@@ -495,20 +604,40 @@ const makeChatsSocket = (config) => {
495
604
  jid = resolveJid(jid);
496
605
  var _a;
497
606
  jid = (0, WABinary_1.jidNormalizedUser)(jid);
498
- const result = await query({
499
- tag: 'iq',
500
- attrs: {
501
- target: jid,
502
- to: WABinary_1.S_WHATSAPP_NET,
503
- type: 'get',
504
- xmlns: 'w:profile:picture'
505
- },
506
- content: [
507
- { tag: 'picture', attrs: { type, query: 'url' } }
508
- ]
509
- }, timeoutMs);
510
- const child = (0, WABinary_1.getBinaryNodeChild)(result, 'picture');
511
- return (_a = child === null || child === void 0 ? void 0 : child.attrs) === null || _a === void 0 ? void 0 : _a.url;
607
+ const cacheKey = `${jid}:${type}`;
608
+ const cached = profilePictureUrlCache.get(cacheKey);
609
+ if (typeof cached !== 'undefined') {
610
+ return cached || undefined;
611
+ }
612
+ const inFlight = inFlightProfilePictureUrl.get(cacheKey);
613
+ if (inFlight) {
614
+ return inFlight;
615
+ }
616
+ const fetchPromise = (async () => {
617
+ const result = await query({
618
+ tag: 'iq',
619
+ attrs: {
620
+ target: jid,
621
+ to: WABinary_1.S_WHATSAPP_NET,
622
+ type: 'get',
623
+ xmlns: 'w:profile:picture'
624
+ },
625
+ content: [
626
+ { tag: 'picture', attrs: { type, query: 'url' } }
627
+ ]
628
+ }, timeoutMs);
629
+ const child = (0, WABinary_1.getBinaryNodeChild)(result, 'picture');
630
+ const url = (_a = child === null || child === void 0 ? void 0 : child.attrs) === null || _a === void 0 ? void 0 : _a.url;
631
+ profilePictureUrlCache.set(cacheKey, url || null);
632
+ return url;
633
+ })();
634
+ inFlightProfilePictureUrl.set(cacheKey, fetchPromise);
635
+ try {
636
+ return await fetchPromise;
637
+ }
638
+ finally {
639
+ inFlightProfilePictureUrl.delete(cacheKey);
640
+ }
512
641
  };
513
642
  const sendPresenceUpdate = async (type, toJid) => {
514
643
  toJid = resolveJid(toJid);
@@ -534,6 +663,14 @@ const makeChatsSocket = (config) => {
534
663
  logger.warn('no name present, ignoring presence update request...');
535
664
  return;
536
665
  }
666
+ if (!toJid) {
667
+ const now = Date.now();
668
+ if (lastSelfPresenceType === type && (now - lastSelfPresenceTs) < 3000) {
669
+ return;
670
+ }
671
+ lastSelfPresenceType = type;
672
+ lastSelfPresenceTs = now;
673
+ }
537
674
  ev.emit('connection.update', { isOnline: type === 'available' });
538
675
  await (0, retry_1.retryWithBackoff)(() => sendNode({
539
676
  tag: 'presence',
@@ -931,6 +1068,9 @@ const makeChatsSocket = (config) => {
931
1068
  updateProfileStatus,
932
1069
  updateProfileName,
933
1070
  updateBlockStatus,
1071
+ reportUser,
1072
+ reportMessage,
1073
+ reportAndBlockUser,
934
1074
  updateCallPrivacy,
935
1075
  updateMessagesPrivacy,
936
1076
  updateLastSeenPrivacy,
@@ -11,12 +11,9 @@ const { CacheManager } = require('../Utils/cache-manager');
11
11
  const makeGroupsSocket = (config) => {
12
12
  const sock = (0, chats_1.makeChatsSocket)(config);
13
13
  const { authState, ev, query, upsertMessage } = sock;
14
- const resolveJid = (jid) => {
15
- if (typeof jid === 'string' && jid.endsWith('@lid')) {
16
- return (0, WABinary_1.lidToJid)(jid);
17
- }
18
- return jid;
19
- };
14
+ const inFlightGroupMetadata = new Map();
15
+ const forbiddenGroupMetadataUntil = new Map();
16
+ const resolveJid = WABinary_1.resolveJid;
20
17
  const groupQuery = async (jid, type, content) => (query({
21
18
  tag: 'iq',
22
19
  attrs: {
@@ -29,6 +26,16 @@ const makeGroupsSocket = (config) => {
29
26
  const groupMetadata = async (jid) => {
30
27
  const config = getPerformanceConfig();
31
28
 
29
+ const forbiddenUntil = forbiddenGroupMetadataUntil.get(jid);
30
+ if (forbiddenUntil && forbiddenUntil > Date.now()) {
31
+ const err = new Error('forbidden');
32
+ err.data = 403;
33
+ throw err;
34
+ }
35
+ else if (forbiddenUntil) {
36
+ forbiddenGroupMetadataUntil.delete(jid);
37
+ }
38
+
32
39
  // Check cache first
33
40
  const cached = CacheManager.get('groupMetadataCache', jid);
34
41
  if (cached) {
@@ -36,31 +43,52 @@ const makeGroupsSocket = (config) => {
36
43
  return cached;
37
44
  }
38
45
 
39
- // Fetch from server with rate limiting and retry logic
40
- let retryCount = 0;
41
- const maxRetries = 3;
42
- const baseDelay = 1000; // 1 second
43
-
44
- while (retryCount <= maxRetries) {
45
- try {
46
- const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
47
- const metadata = (0, exports.extractGroupMetadata)(result);
46
+ // If a fetch is already in progress for this group, reuse it
47
+ const inFlight = inFlightGroupMetadata.get(jid);
48
+ if (inFlight) {
49
+ return inFlight;
50
+ }
51
+
52
+ const fetchPromise = (async () => {
53
+ // Fetch from server with rate limiting and retry logic
54
+ let retryCount = 0;
55
+ const maxRetries = 3;
56
+ const baseDelay = 1000; // 1 second
57
+
58
+ while (retryCount <= maxRetries) {
59
+ try {
60
+ const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
61
+ const metadata = (0, exports.extractGroupMetadata)(result);
48
62
 
49
- // Cache the result
50
- CacheManager.set('groupMetadataCache', jid, metadata, config.cache.groupMetadataCache.ttl);
63
+ // Cache the result
64
+ CacheManager.set('groupMetadataCache', jid, metadata, config.cache.groupMetadataCache.ttl);
51
65
 
52
- return metadata;
53
- } catch (error) {
54
- if (error.message === 'rate-overlimit' && retryCount < maxRetries) {
55
- const delay = baseDelay * Math.pow(2, retryCount); // Exponential backoff
56
- config.logger?.warn({ jid, retryCount, delay }, 'Rate limit hit, retrying group metadata fetch');
57
- await new Promise(resolve => setTimeout(resolve, delay));
58
- retryCount++;
59
- } else {
60
- // Re-throw non-rate-limit errors or if max retries exceeded
61
- throw error;
66
+ return metadata;
67
+ } catch (error) {
68
+ const statusCode = error?.output?.statusCode || error?.data;
69
+ if (statusCode === 403 || error?.message === 'forbidden') {
70
+ const cooldownMs = 5 * 60 * 1000;
71
+ forbiddenGroupMetadataUntil.set(jid, Date.now() + cooldownMs);
72
+ throw error;
73
+ }
74
+ if (error.message === 'rate-overlimit' && retryCount < maxRetries) {
75
+ const delay = baseDelay * Math.pow(2, retryCount); // Exponential backoff
76
+ config.logger?.warn({ jid, retryCount, delay }, 'Rate limit hit, retrying group metadata fetch');
77
+ await new Promise(resolve => setTimeout(resolve, delay));
78
+ retryCount++;
79
+ } else {
80
+ // Re-throw non-rate-limit errors or if max retries exceeded
81
+ throw error;
82
+ }
62
83
  }
63
84
  }
85
+ })();
86
+
87
+ inFlightGroupMetadata.set(jid, fetchPromise);
88
+ try {
89
+ return await fetchPromise;
90
+ } finally {
91
+ inFlightGroupMetadata.delete(jid);
64
92
  }
65
93
  };
66
94
  const groupFetchAllParticipating = async () => {
@@ -8,10 +8,19 @@ const Defaults_1 = require("../Defaults");
8
8
  const business_1 = require("./business");
9
9
  // export the last socket layer
10
10
  const makeWASocket = (config) => {
11
+ const inputConfig = config;
12
+ const hasBrowserOverride = Object.prototype.hasOwnProperty.call(inputConfig, 'browser');
11
13
  config = {
12
14
  ...Defaults_1.DEFAULT_CONNECTION_CONFIG,
13
15
  ...config,
14
16
  };
17
+ const creds = config.auth?.creds;
18
+ if (!hasBrowserOverride && creds?.browser) {
19
+ config.browser = creds.browser;
20
+ }
21
+ else if (creds && !creds.browser) {
22
+ creds.browser = config.browser;
23
+ }
15
24
  if (!config.lidCache) {
16
25
  config.lidCache = new node_cache_1.default({
17
26
  stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.LID_JID,