@charape/baileys 1.0.9 → 1.0.10

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.
@@ -406,6 +406,7 @@ export const makeSocket = (config) => {
406
406
  let lastDateRecv;
407
407
  let epoch = 1;
408
408
  let keepAliveReq;
409
+ let missedKeepAlives = 0;
409
410
  let qrTimer;
410
411
  let closed = false;
411
412
  const socketEndHandlers = [];
@@ -610,8 +611,11 @@ export const makeSocket = (config) => {
610
611
  }
611
612
  closed = true;
612
613
  logger.info({ trace: error?.stack }, error ? 'connection errored' : 'connection closed');
614
+ missedKeepAlives = 0;
613
615
  clearInterval(keepAliveReq);
616
+ keepAliveReq = undefined;
614
617
  clearTimeout(qrTimer);
618
+ qrTimer = undefined;
615
619
  ws.removeAllListeners('close');
616
620
  ws.removeAllListeners('open');
617
621
  ws.removeAllListeners('message');
@@ -661,20 +665,13 @@ export const makeSocket = (config) => {
661
665
  ws.off('error', onClose);
662
666
  });
663
667
  };
664
- const startKeepAliveRequest = () => (keepAliveReq = setInterval(() => {
665
- if (!lastDateRecv) {
666
- lastDateRecv = new Date();
667
- }
668
- const diff = Date.now() - lastDateRecv.getTime();
669
- /*
670
- check if it's been a suspicious amount of time since the server responded with our last seen
671
- it could be that the network is down
672
- */
673
- if (diff > keepAliveIntervalMs + 5000) {
674
- void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }));
675
- }
676
- else if (ws.isOpen) {
677
- // if its all good, send a keep alive request
668
+ const startKeepAliveRequest = () => {
669
+ missedKeepAlives = 0;
670
+ const sendKeepAlivePing = () => {
671
+ if (!ws.isOpen) {
672
+ logger.warn('keep alive called when WS not open');
673
+ return;
674
+ }
678
675
  query({
679
676
  tag: 'iq',
680
677
  attrs: {
@@ -684,14 +681,38 @@ export const makeSocket = (config) => {
684
681
  xmlns: 'w:p'
685
682
  },
686
683
  content: [{ tag: 'ping', attrs: {} }]
684
+ }).then(() => {
685
+ lastDateRecv = new Date();
686
+ missedKeepAlives = 0;
687
687
  }).catch(err => {
688
- logger.error({ trace: err.stack }, 'error in sending keep alive');
688
+ logger.error({ trace: err?.stack }, 'error in sending keep alive');
689
689
  });
690
- }
691
- else {
692
- logger.warn('keep alive called when WS not open');
693
- }
694
- }, keepAliveIntervalMs));
690
+ };
691
+ keepAliveReq = setInterval(() => {
692
+ if (!lastDateRecv) {
693
+ lastDateRecv = new Date();
694
+ }
695
+ const diff = Date.now() - lastDateRecv.getTime();
696
+ const maxGraceDiff = Math.max(keepAliveIntervalMs * 2.5, keepAliveIntervalMs + 20000);
697
+ if (diff > keepAliveIntervalMs + 5000) {
698
+ missedKeepAlives++;
699
+ if (missedKeepAlives >= 2 || diff > maxGraceDiff) {
700
+ void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }));
701
+ } else {
702
+ logger.debug({ diff, missedKeepAlives }, 'keep-alive latency tolerance, sending retry ping');
703
+ sendKeepAlivePing();
704
+ }
705
+ }
706
+ else if (ws.isOpen) {
707
+ missedKeepAlives = 0;
708
+ sendKeepAlivePing();
709
+ }
710
+ else {
711
+ logger.warn('keep alive called when WS not open');
712
+ }
713
+ }, keepAliveIntervalMs);
714
+ return keepAliveReq;
715
+ };
695
716
  /** i have no idea why this exists. pls enlighten me */
696
717
  const sendPassiveIq = (tag) => query({
697
718
  tag: 'iq',
@@ -1,6 +1,24 @@
1
1
  import { createRequire } from "module";
2
- const _require = typeof require !== "undefined" ? require : createRequire(import.meta.url);
3
- const cacheManager = _require("cache-manager");
2
+ import { join } from "path";
3
+ import { pathToFileURL } from "url";
4
+
5
+ let cacheManager;
6
+ const loadCacheManager = async () => {
7
+ if (cacheManager) return cacheManager;
8
+ if (typeof require === "function") {
9
+ cacheManager = require("cache-manager");
10
+ return cacheManager;
11
+ }
12
+ try {
13
+ const mod = await import("cache-manager");
14
+ cacheManager = mod?.default || mod;
15
+ return cacheManager;
16
+ } catch {
17
+ const _require = createRequire(pathToFileURL(join(process.cwd(), "package.json")).href);
18
+ cacheManager = _require("cache-manager");
19
+ return cacheManager;
20
+ }
21
+ };
4
22
 
5
23
  import * as WAProto_1 from "../../WAProto/index.js";
6
24
  import * as Utils_1 from "../Utils/index.js";
@@ -8,7 +26,8 @@ import logger from "../Utils/logger.js";
8
26
 
9
27
  export default async function makeCacheManagerAuthState(store, sessionKey) {
10
28
  const defaultKey = (file) => `${sessionKey}:${file}`;
11
- const databaseConn = await cacheManager.caching(store);
29
+ const cm = await loadCacheManager();
30
+ const databaseConn = await cm.caching(store);
12
31
 
13
32
  const writeData = async (file, data) => {
14
33
  let ttl = undefined;
@@ -0,0 +1,230 @@
1
+ import { jidNormalizedUser, isLidUser, isHostedLidUser, isPnUser, isHostedPnUser } from '../WABinary/index.js';
2
+
3
+ const ANSI = {
4
+ reset: '\x1b[0m',
5
+ bold: '\x1b[1m',
6
+ dim: '\x1b[2m',
7
+ cyan: '\x1b[36m',
8
+ blue: '\x1b[34m',
9
+ green: '\x1b[32m',
10
+ magenta: '\x1b[35m',
11
+ yellow: '\x1b[33m',
12
+ white: '\x1b[37m'
13
+ };
14
+
15
+ /**
16
+ * Insert or refresh an entry in a Map while enforcing an upper bound (FIFO/LRU eviction).
17
+ */
18
+ export const setBoundedMapEntry = (map, key, value, maxEntries = 2000) => {
19
+ if (!map || key === undefined || key === null) return map;
20
+ if (map.has(key)) {
21
+ map.delete(key);
22
+ }
23
+ map.set(key, value);
24
+ while (map.size > maxEntries) {
25
+ const oldestKey = map.keys().next().value;
26
+ if (oldestKey === undefined) break;
27
+ map.delete(oldestKey);
28
+ }
29
+ return map;
30
+ };
31
+
32
+ /**
33
+ * Insert or refresh an entry in a Set while enforcing an upper bound.
34
+ */
35
+ export const addBoundedSetEntry = (set, value, maxEntries = 2000) => {
36
+ if (!set || value === undefined || value === null) return set;
37
+ if (set.has(value)) {
38
+ set.delete(value);
39
+ }
40
+ set.add(value);
41
+ while (set.size > maxEntries) {
42
+ const oldestValue = set.values().next().value;
43
+ if (oldestValue === undefined) break;
44
+ set.delete(oldestValue);
45
+ }
46
+ return set;
47
+ };
48
+
49
+ /**
50
+ * Resolve PN (@s.whatsapp.net) or LID (@lid) JID safely through lidMapping without throwing.
51
+ */
52
+ export const resolveJidWithMapping = async (jid, lidMapping, target = 'pn') => {
53
+ if (!jid || typeof jid !== 'string') return jid;
54
+ const normalized = jidNormalizedUser(jid);
55
+ if (!lidMapping) return normalized;
56
+
57
+ try {
58
+ if (target === 'pn') {
59
+ if (isPnUser(normalized) || isHostedPnUser(normalized)) return normalized;
60
+ if ((isLidUser(normalized) || isHostedLidUser(normalized)) && typeof lidMapping.getPNForLID === 'function') {
61
+ const pn = await lidMapping.getPNForLID(normalized);
62
+ return pn ? jidNormalizedUser(pn) : normalized;
63
+ }
64
+ } else if (target === 'lid') {
65
+ if (isLidUser(normalized) || isHostedLidUser(normalized)) return normalized;
66
+ if ((isPnUser(normalized) || isHostedPnUser(normalized)) && typeof lidMapping.getLIDForPN === 'function') {
67
+ const lid = await lidMapping.getLIDForPN(normalized);
68
+ return lid ? jidNormalizedUser(lid) : normalized;
69
+ }
70
+ }
71
+ } catch {
72
+ // Fallback silently to normalized JID so caller handlers never crash
73
+ }
74
+ return normalized;
75
+ };
76
+
77
+ const SKIPPED_LOG_CONTENT_TYPES = new Set([
78
+ 'protocolMessage',
79
+ 'senderKeyDistributionMessage',
80
+ 'fastRatchetSenderKeyDistributionMessage',
81
+ 'messageContextInfo',
82
+ 'historySyncNotification',
83
+ 'initialSecurityNotificationSettingSync',
84
+ 'appStateSyncKeyShare',
85
+ 'appStateFatalExceptionNotification'
86
+ ]);
87
+
88
+ const WRAPPER_KEYS = [
89
+ 'ephemeralMessage',
90
+ 'viewOnceMessage',
91
+ 'viewOnceMessageV2',
92
+ 'viewOnceMessageV2Extension',
93
+ 'documentWithCaptionMessage',
94
+ 'editedMessage'
95
+ ];
96
+
97
+ const unwrapMessageShape = (message) => {
98
+ let curr = message;
99
+ for (let depth = 0; depth < 4 && curr && typeof curr === 'object'; depth++) {
100
+ let unwrapped = false;
101
+ for (const key of WRAPPER_KEYS) {
102
+ if (curr[key]?.message) {
103
+ curr = curr[key].message;
104
+ unwrapped = true;
105
+ break;
106
+ }
107
+ }
108
+ if (!unwrapped) break;
109
+ }
110
+ return curr;
111
+ };
112
+
113
+ const detectDisplayTypeAndPreview = (message) => {
114
+ const content = unwrapMessageShape(message);
115
+ if (!content || typeof content !== 'object') return null;
116
+
117
+ const keys = Object.keys(content).filter(k => k !== 'messageContextInfo' && content[k] !== undefined && content[k] !== null);
118
+ if (keys.length === 0) return null;
119
+
120
+ const primaryKey = keys.find(k => !SKIPPED_LOG_CONTENT_TYPES.has(k)) || keys[0];
121
+ if (SKIPPED_LOG_CONTENT_TYPES.has(primaryKey)) return null;
122
+
123
+ const val = content[primaryKey];
124
+ let textPreview = '';
125
+ if (primaryKey === 'conversation' && typeof val === 'string') {
126
+ textPreview = val;
127
+ } else if (val && typeof val === 'object') {
128
+ textPreview = val.text || val.caption || val.fileName || val.title || val.body?.text || val.header?.title || val.name || '';
129
+ }
130
+
131
+ const cleanPreview = String(textPreview || '').replace(/[\r\n\t]+/g, ' ').trim();
132
+ const byteSize = cleanPreview ? Buffer.byteLength(cleanPreview, 'utf8') : 0;
133
+ const typeLabel = primaryKey
134
+ .replace(/Message$/i, '')
135
+ .replace(/([A-Z])/g, ' $1')
136
+ .replace(/^./, s => s.toUpperCase())
137
+ .trim();
138
+
139
+ return {
140
+ rawType: primaryKey,
141
+ typeLabel: typeLabel || 'Message',
142
+ preview: cleanPreview.length > 48 ? cleanPreview.slice(0, 45) + '...' : cleanPreview,
143
+ byteSize
144
+ };
145
+ };
146
+
147
+ let totalLoggedMessages = 0;
148
+ let burstWindowStart = 0;
149
+ let burstCountInWindow = 0;
150
+ const printedLogIds = new Set();
151
+ const MAX_PRINTS_PER_SECOND = 25;
152
+
153
+ /**
154
+ * Format an iconic CHARAPE terminal chat card inspired by the clean MESSAGE INFO layout.
155
+ */
156
+ export const formatCharapeChatLog = (direction = 'IN', msg = {}, count = totalLoggedMessages) => {
157
+ const info = detectDisplayTypeAndPreview(msg?.message);
158
+ if (!info) return null;
159
+
160
+ const isOut = String(direction).toUpperCase().startsWith('OUT');
161
+ const key = msg?.key || {};
162
+ const remoteJid = key.remoteJid || '-';
163
+ const senderRaw = key.participantAlt || key.remoteJidAlt || key.participant || key.remoteJid || '-';
164
+ const senderId = String(senderRaw).split('@')[0].split(':')[0] || '-';
165
+ const senderName = msg.pushName || (isOut ? 'CHARAPE BOT' : senderId);
166
+ const tsRaw = Number(msg.messageTimestamp || 0);
167
+ const tsDate = tsRaw > 0 ? new Date(tsRaw > 1e12 ? tsRaw : tsRaw * 1000) : new Date();
168
+ const sentTime = Number.isNaN(tsDate.getTime()) ? new Date().toLocaleString() : tsDate.toLocaleString();
169
+ const sizeInfo = info.preview ? `${info.byteSize} bytes (${info.preview})` : `${info.typeLabel}`;
170
+
171
+ const border = `${ANSI.blue}════════════════════════════════════════════════════${ANSI.reset}`;
172
+ const badgeColor = isOut ? ANSI.magenta : ANSI.green;
173
+ const dirLabel = isOut ? 'OUTGOING CHAT' : 'INCOMING CHAT';
174
+
175
+ return [
176
+ border,
177
+ ` ${badgeColor}${ANSI.bold}⚡ CHARAPE${ANSI.reset} ${ANSI.dim}•${ANSI.reset} ${ANSI.magenta}${ANSI.bold}${dirLabel}${ANSI.reset}`,
178
+ border,
179
+ ` ${ANSI.cyan}📌 Message Type ${ANSI.reset}: ${ANSI.white}${info.typeLabel}${ANSI.reset}`,
180
+ ` ${ANSI.cyan}🆔 Message ID ${ANSI.reset}: ${ANSI.white}${key.id || '-'}${ANSI.reset}`,
181
+ ` ${ANSI.cyan}⏰ Sent Time ${ANSI.reset}: ${ANSI.white}${sentTime}${ANSI.reset}`,
182
+ ` ${ANSI.cyan}📁 Content/Size ${ANSI.reset}: ${ANSI.white}${sizeInfo}${ANSI.reset}`,
183
+ ` ${ANSI.cyan}👤 Sender ID ${ANSI.reset}: ${ANSI.white}${senderId}${ANSI.reset}`,
184
+ ` ${ANSI.cyan}🎭 Sender Name ${ANSI.reset}: ${ANSI.white}${senderName}${ANSI.reset}`,
185
+ ` ${ANSI.cyan}💬 Chat ID ${ANSI.reset}: ${ANSI.white}${remoteJid}${ANSI.reset}`,
186
+ ` ${ANSI.cyan}📊 Total Logs ${ANSI.reset}: ${ANSI.yellow}${count}${ANSI.reset}`,
187
+ border
188
+ ].join('\n');
189
+ };
190
+
191
+ /**
192
+ * Safely print iconic CHARAPE terminal chat card without blocking or flooding stdout.
193
+ */
194
+ export const printCharapeChatLog = (direction = 'IN', msg = {}, options = {}) => {
195
+ try {
196
+ if (options.enabled === false) return false;
197
+ if (!options.ignoreEnvSilent && process.env.CHARAPE_SILENT === 'true') return false;
198
+ if (!msg || typeof msg !== 'object' || !msg.message) return false;
199
+
200
+ const dir = String(direction || 'IN').toUpperCase().startsWith('OUT') ? 'OUT' : 'IN';
201
+ const msgId = msg.key?.id;
202
+ if (msgId && !options.force) {
203
+ const dedupeKey = `${dir}:${msgId}`;
204
+ if (printedLogIds.has(dedupeKey)) return false;
205
+ addBoundedSetEntry(printedLogIds, dedupeKey, 600);
206
+ }
207
+
208
+ const now = Date.now();
209
+ if (now - burstWindowStart >= 1000) {
210
+ burstWindowStart = now;
211
+ burstCountInWindow = 0;
212
+ }
213
+ if (!options.force && burstCountInWindow >= MAX_PRINTS_PER_SECOND) {
214
+ return false;
215
+ }
216
+
217
+ const nextCount = totalLoggedMessages + 1;
218
+ const formatted = formatCharapeChatLog(dir, msg, nextCount);
219
+ if (!formatted) return false;
220
+
221
+ totalLoggedMessages = nextCount;
222
+ burstCountInWindow++;
223
+
224
+ const writer = typeof options.writer === 'function' ? options.writer : console.log;
225
+ writer(formatted);
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ };
@@ -4,6 +4,7 @@ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJid
4
4
  // transferDevice
5
5
  } from '../WABinary/index.js';
6
6
  import { unpadRandomMax16 } from './generics.js';
7
+ import { setBoundedMapEntry } from './charape-runtime.js';
7
8
  export const getDecryptionJid = async (sender, repository) => {
8
9
  if (isLidUser(sender) || isHostedLidUser(sender)) {
9
10
  return sender;
@@ -97,6 +98,8 @@ const MAX_SECRETS_PER_CHAT = 20
97
98
  // Module-level map: outgoing @bot message ID → messageSecret
98
99
  // Populated when we receive the outgoing pkmsg/msg to @bot (which contains messageContextInfo.messageSecret)
99
100
  // Consumed when the msmsg response from @bot arrives and needs decryption
101
+ const MAX_BOT_MESSAGE_SECRETS = 5000
102
+ const MAX_BOT_RECENT_CHATS = 1000
100
103
  const botMessageSecrets = new Map()
101
104
  const botRecentSecretsByChat = new Map()
102
105
  const pushRecentChatSecret = (chatJid, id, secretBuf) => {
@@ -107,7 +110,7 @@ const pushRecentChatSecret = (chatJid, id, secretBuf) => {
107
110
  if (filtered.length > MAX_SECRETS_PER_CHAT) {
108
111
  filtered.length = MAX_SECRETS_PER_CHAT
109
112
  }
110
- botRecentSecretsByChat.set(chatJid, filtered)
113
+ setBoundedMapEntry(botRecentSecretsByChat, chatJid, filtered, MAX_BOT_RECENT_CHATS)
111
114
  }
112
115
  export const setBotMessageSecret = (id, secret, chatJid) => {
113
116
  if (!id || !secret) return
@@ -121,7 +124,7 @@ export const setBotMessageSecret = (id, secret, chatJid) => {
121
124
  } else {
122
125
  return
123
126
  }
124
- botMessageSecrets.set(id, buf)
127
+ setBoundedMapEntry(botMessageSecrets, id, buf, MAX_BOT_MESSAGE_SECRETS)
125
128
  if (chatJid) {
126
129
  pushRecentChatSecret(chatJid, id, buf)
127
130
  }
@@ -23,4 +23,5 @@ export * from './stanza-ack.js';
23
23
  export * from './message-composer.js';
24
24
  export * from './clean-session.js';
25
25
  export * from './use-single-file-auth-state.js';
26
+ export * from './charape-runtime.js';
26
27
  //# sourceMappingURL=index.js.map