@itsliaaa/baileys 0.1.33 → 0.2.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.
@@ -33,6 +33,7 @@ export const DECRYPTION_RETRY_CONFIG = {
33
33
  baseDelayMs: 100,
34
34
  sessionRecordErrors: ['No session record', 'SessionError: No session record']
35
35
  };
36
+ /** NACK reason codes we send to the server (client → server) */
36
37
  export const NACK_REASONS = {
37
38
  ParsingError: 487,
38
39
  UnrecognizedStanza: 488,
@@ -48,6 +49,17 @@ export const NACK_REASONS = {
48
49
  UnsupportedLIDGroup: 551,
49
50
  DBOperationFailed: 552
50
51
  };
52
+ /**
53
+ * Server-side error codes returned in ack stanzas (server → client) that we
54
+ * currently have dedicated handlers for. Extend as more handlers are added.
55
+ * Distinct from the client-side NackReason enum (WAWebCreateNackFromStanza).
56
+ */
57
+ export const SERVER_ERROR_CODES = {
58
+ /** 1:1 message missing privacy token (tctoken) */
59
+ MissingTcToken: '463',
60
+ /** Stanza validation failure (SMAX_INVALID) — likely stale device session */
61
+ SmaxInvalid: '479'
62
+ };
51
63
  export const extractAddressingContext = (stanza) => {
52
64
  let senderAlt;
53
65
  let recipientAlt;
@@ -148,10 +160,12 @@ export function decodeMessageNode(stanza, meId, meLid) {
148
160
  const key = {
149
161
  remoteJid: chatId,
150
162
  remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
163
+ remoteJidUsername: !isJidGroup(chatId) ? stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username : undefined,
151
164
  fromMe,
152
165
  id: msgId,
153
166
  participant,
154
167
  participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
168
+ participantUsername: stanza.attrs.participant ? stanza.attrs.participant_username : undefined,
155
169
  addressingMode: addressingContext.addressingMode,
156
170
  ...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
157
171
  };
@@ -252,6 +252,7 @@ eventData, logger) {
252
252
  data.historySets.empty = false;
253
253
  data.historySets.syncType = eventData.syncType;
254
254
  data.historySets.progress = eventData.progress;
255
+ data.historySets.chunkOrder = eventData.chunkOrder;
255
256
  data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId;
256
257
  data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest;
257
258
  break;
@@ -519,6 +520,7 @@ function consolidateEvents(data) {
519
520
  syncType: data.historySets.syncType,
520
521
  progress: data.historySets.progress,
521
522
  isLatest: data.historySets.isLatest,
523
+ chunkOrder: data.historySets.chunkOrder,
522
524
  peerDataRequestSessionId: data.historySets.peerDataRequestSessionId
523
525
  };
524
526
  }
@@ -316,6 +316,15 @@ export const getCallStatusFromNode = ({ tag, attrs }) => {
316
316
  status = 'terminate';
317
317
  }
318
318
  break;
319
+ case 'preaccept':
320
+ status = 'preaccept';
321
+ break;
322
+ case 'transport':
323
+ status = 'transport';
324
+ break;
325
+ case 'relaylatency':
326
+ status = 'relaylatency';
327
+ break;
319
328
  case 'reject':
320
329
  status = 'reject';
321
330
  break;
@@ -1,5 +1,6 @@
1
+ import { pipeline } from 'stream/promises';
1
2
  import { promisify } from 'util';
2
- import { inflate } from 'zlib';
3
+ import { createInflate, inflate } from 'zlib';
3
4
  import { proto } from '../../WAProto/index.js';
4
5
  import { WAMessageStubType } from '../Types/index.js';
5
6
  import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser } from '../WABinary/index.js';
@@ -24,13 +25,13 @@ const extractPnFromMessages = (messages) => {
24
25
  };
25
26
  export const downloadHistory = async (msg, options) => {
26
27
  const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options });
27
- const bufferArray = [];
28
- for await (const chunk of stream) {
29
- bufferArray.push(chunk);
30
- }
31
- let buffer = Buffer.concat(bufferArray);
32
- // decompress buffer
33
- buffer = await inflatePromise(buffer);
28
+ // Pipe decrypted stream directly through zlib inflate
29
+ // This avoids allocating an intermediate buffer for the compressed data
30
+ const inflater = createInflate();
31
+ const chunks = [];
32
+ inflater.on('data', (chunk) => chunks.push(chunk));
33
+ await pipeline(stream, inflater);
34
+ const buffer = Buffer.concat(chunks);
34
35
  const syncData = proto.HistorySync.decode(buffer);
35
36
  return syncData;
36
37
  };
@@ -55,6 +56,7 @@ export const processHistoryMessage = (item, logger) => {
55
56
  contacts.push({
56
57
  id: chat.id,
57
58
  name: chat.displayName || chat.name || chat.username || undefined,
59
+ username: chat.username || undefined,
58
60
  lid: chat.lidJid || chat.accountLid || undefined,
59
61
  phoneNumber: chat.pnJid || undefined
60
62
  });
@@ -95,7 +97,7 @@ export const processHistoryMessage = (item, logger) => {
95
97
  });
96
98
  }
97
99
  }
98
- chats.push({ ...chat });
100
+ chats.push(chat);
99
101
  }
100
102
  break;
101
103
  case proto.HistorySync.HistorySyncType.PUSH_NAME:
@@ -37,6 +37,7 @@ export async function handleIdentityChange(node, ctx) {
37
37
  ctx.logger.debug({ jid: from }, 'skipping session refresh during offline processing');
38
38
  return { action: 'skipped_offline' };
39
39
  }
40
+ ctx.onBeforeSessionRefresh?.(from);
40
41
  try {
41
42
  await ctx.assertSessions([from], true);
42
43
  return { action: 'session_refreshed' };
@@ -516,7 +516,7 @@ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, {
516
516
  };
517
517
  const output = new Transform({
518
518
  transform(chunk, _, callback) {
519
- let data = Buffer.concat([remainingBytes, chunk]);
519
+ let data = remainingBytes.length ? Buffer.concat([remainingBytes, chunk]) : chunk;
520
520
  const decryptLength = toSmallestChunkSize(data.length);
521
521
  remainingBytes = data.slice(decryptLength);
522
522
  data = data.slice(0, decryptLength);
@@ -674,6 +674,7 @@ export const generateWAMessageContent = async (message, options) => {
674
674
  extContent.description = urlInfo.description;
675
675
  extContent.title = urlInfo.title;
676
676
  extContent.previewType = urlInfo.previewType ?? 0;
677
+ extContent.linkPreviewMetadata = urlInfo.linkPreviewMetadata;
677
678
  const img = urlInfo.highQualityThumbnail;
678
679
  if (img) {
679
680
  extContent.thumbnailDirectPath = img.directPath;
@@ -685,6 +686,19 @@ export const generateWAMessageContent = async (message, options) => {
685
686
  extContent.thumbnailEncSha256 = img.fileEncSha256;
686
687
  }
687
688
  }
689
+ let faviconInfo = message.favicon;
690
+ if (faviconInfo) {
691
+ faviconInfo = await generateWAMessageMedia(faviconInfo, options);
692
+ extContent.faviconMMSMetadata = {
693
+ thumbnailDirectPath: faviconInfo.directPath,
694
+ mediaKey: faviconInfo.mediaKey,
695
+ mediaKeyTimestamp: faviconInfo.mediaKeyTimestamp,
696
+ thumbnailWidth: 32,
697
+ thumbnailHeight: 32,
698
+ thumbnailSha256: faviconInfo.fileSha256,
699
+ thumbnailEncSha256: faviconInfo.fileEncSha256
700
+ };
701
+ }
688
702
  if (options.backgroundColor) {
689
703
  extContent.backgroundArgb = await assertColor(options.backgroundColor);
690
704
  }
@@ -5,6 +5,7 @@ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJid
5
5
  import { aesDecryptGCM, hmacSign } from './crypto.js';
6
6
  import { getKeyAuthor, toNumber } from './generics.js';
7
7
  import { downloadAndProcessHistorySyncNotification } from './history.js';
8
+ import { buildMergedTcTokenIndexWrite, resolveTcTokenJid } from './tc-token-utils.js';
8
9
  const REAL_MSG_STUB_TYPES = new Set([
9
10
  WAMessageStubType.CALL_MISSED_GROUP_VIDEO,
10
11
  WAMessageStubType.CALL_MISSED_GROUP_VOICE,
@@ -12,6 +13,53 @@ const REAL_MSG_STUB_TYPES = new Set([
12
13
  WAMessageStubType.CALL_MISSED_VOICE
13
14
  ]);
14
15
  const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD]);
16
+ async function storeTcTokensFromHistorySync(chats, signalRepository, keyStore, logger) {
17
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
18
+ const candidates = [];
19
+ for (const chat of chats) {
20
+ const ts = chat.tcTokenTimestamp ? toNumber(chat.tcTokenTimestamp) : 0;
21
+ if (chat.tcToken?.length && ts > 0) {
22
+ const jid = jidNormalizedUser(chat.id);
23
+ const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
24
+ candidates.push({
25
+ storageJid,
26
+ token: Buffer.from(chat.tcToken),
27
+ ts,
28
+ senderTs: chat.tcTokenSenderTimestamp ? toNumber(chat.tcTokenSenderTimestamp) : undefined
29
+ });
30
+ }
31
+ }
32
+ if (!candidates.length) {
33
+ return;
34
+ }
35
+ const jids = candidates.map(c => c.storageJid);
36
+ const existing = await keyStore.get('tctoken', jids);
37
+ const entries = {};
38
+ for (const c of candidates) {
39
+ const existingEntry = existing[c.storageJid];
40
+ const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
41
+ if (existingTs > 0 && existingTs >= c.ts) {
42
+ continue;
43
+ }
44
+ entries[c.storageJid] = {
45
+ ...existingEntry,
46
+ token: c.token,
47
+ timestamp: String(c.ts),
48
+ ...(c.senderTs !== undefined ? { senderTimestamp: c.senderTs } : {})
49
+ };
50
+ }
51
+ if (Object.keys(entries).length) {
52
+ logger?.debug({ count: Object.keys(entries).length }, 'storing tctokens from history sync');
53
+ try {
54
+ // Include updated __index so cross-session pruning picks these JIDs up.
55
+ const indexWrite = await buildMergedTcTokenIndexWrite(keyStore, Object.keys(entries));
56
+ await keyStore.set({ tctoken: { ...entries, ...indexWrite } });
57
+ }
58
+ catch (err) {
59
+ logger?.warn({ err }, 'failed to store tctokens from history sync');
60
+ }
61
+ }
62
+ };
15
63
  /** Cleans a received message to further processing */
16
64
  export const cleanMessage = (message, meId, meLid) => {
17
65
  // ensure remoteJid and participant doesn't have device or agent in it
@@ -174,9 +222,11 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
174
222
  .storeLIDPNMappings(data.lidPnMappings)
175
223
  .catch(err => logger?.warn({ err }, 'failed to store LID-PN mappings from history sync'));
176
224
  }
225
+ await storeTcTokensFromHistorySync(data.chats, signalRepository, keyStore, logger);
177
226
  ev.emit('messaging-history.set', {
178
227
  ...data,
179
228
  isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined,
229
+ chunkOrder: histNotification.chunkOrder,
180
230
  peerDataRequestSessionId: histNotification.peerDataRequestSessionId
181
231
  });
182
232
  }
@@ -383,12 +433,13 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
383
433
  id: jid,
384
434
  author: message.key.participant,
385
435
  authorPn: message.key.participantAlt,
436
+ authorUsername: message.key.participantUsername,
386
437
  participants,
387
438
  action
388
439
  });
389
440
  const emitGroupUpdate = (update) => {
390
441
  ev.emit('groups.update', [
391
- { id: jid, ...update, author: message.key.participant ?? undefined, authorPn: message.key.participantAlt }
442
+ { id: jid, ...update, author: message.key.participant ?? undefined, authorPn: message.key.participantAlt, authorUsername: message.key.participantUsername }
392
443
  ]);
393
444
  };
394
445
  const emitGroupRequestJoin = (participant, action, method) => {
@@ -396,6 +447,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
396
447
  id: jid,
397
448
  author: message.key.participant,
398
449
  authorPn: message.key.participantAlt,
450
+ authorUsername: message.key.participantUsername,
399
451
  participant: participant.lid,
400
452
  participantPn: participant.pn,
401
453
  action,
@@ -291,7 +291,7 @@ export const prepareRichResponseMessage = (content) => {
291
291
  }));
292
292
  submessages.push({
293
293
  messageType: RichSubMessageType.TEXT,
294
- messageText: linkField.text + `{{${prefix}}}¹{{/${prefix}}}`,
294
+ messageText: linkField.text + ` {{${prefix}}}¹{{/${prefix}}} `,
295
295
  inlineEntities: [{
296
296
  key: prefix,
297
297
  metadata: {
@@ -21,6 +21,7 @@ export const processContactAction = (action, id, logger) => {
21
21
  {
22
22
  id,
23
23
  name: action.fullName || action.firstName || action.username || undefined,
24
+ username: action.username || undefined,
24
25
  lid: lidJid || undefined,
25
26
  phoneNumber
26
27
  }
@@ -1,9 +1,119 @@
1
- export async function buildTcTokenFromJid({ authState, jid, baseContent = [] }) {
1
+ import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidMetaAI, isLidUser, isPnUser, jidNormalizedUser } from '../WABinary/index.js';
2
+ // Same phone-number pattern as WABinary's isJidBot, applied against the user
3
+ // part so the check is invariant to @c.us ↔ @s.whatsapp.net normalization.
4
+ const BOT_PHONE_REGEX = /^1313555\d{4}$|^131655500\d{2}$/;
5
+ /**
6
+ * Mirrors WA Web's `Wid.isRegularUser()` (user ∧ ¬PSA ∧ ¬Bot). Used to gate tctoken
7
+ * storage against malformed notifications — WA Web filters server-side but we
8
+ * defend here for parity with `WAWebSetTcTokenChatAction.handleIncomingTcToken`.
9
+ * Works for both pre- and post-normalized JIDs (`@c.us` vs `@s.whatsapp.net`).
10
+ */
11
+ function isRegularUser(jid) {
12
+ if (!jid)
13
+ return false;
14
+ const user = jid.split('@')[0] ?? '';
15
+ if (user === '0')
16
+ return false; // PSA
17
+ if (BOT_PHONE_REGEX.test(user))
18
+ return false; // Bot by phone pattern
19
+ if (isJidMetaAI(jid))
20
+ return false; // MetaAI (@bot server)
21
+ return !!(isPnUser(jid) || isLidUser(jid) || isHostedPnUser(jid) || isHostedLidUser(jid) || jid.endsWith('@c.us'));
22
+ }
23
+ const TC_TOKEN_BUCKET_DURATION = 604800; // 7 days
24
+ const TC_TOKEN_NUM_BUCKETS = 4; // ~28-day rolling window
25
+ /** Sentinel key under `tctoken` store holding a JSON array of tracked storage JIDs for cross-session pruning. */
26
+ export const TC_TOKEN_INDEX_KEY = '__index';
27
+ /** Read the persisted tctoken JID index and return its entries (never contains the sentinel key itself). */
28
+ export async function readTcTokenIndex(keys) {
29
+ const data = await keys.get('tctoken', [TC_TOKEN_INDEX_KEY]);
30
+ const entry = data[TC_TOKEN_INDEX_KEY];
31
+ if (!entry?.token?.length)
32
+ return [];
2
33
  try {
3
- const tcTokenData = await authState.keys.get('tctoken', [jid]);
4
- const tcTokenBuffer = tcTokenData?.[jid]?.token;
5
- if (!tcTokenBuffer)
34
+ const parsed = JSON.parse(Buffer.from(entry.token).toString());
35
+ if (!Array.isArray(parsed))
36
+ return [];
37
+ return parsed.filter((j) => typeof j === 'string' && j.length > 0 && j !== TC_TOKEN_INDEX_KEY);
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ }
43
+ /** Build a SignalDataSet fragment that writes the merged index (persisted ∪ added) under the sentinel key. */
44
+ export async function buildMergedTcTokenIndexWrite(keys, addedJids) {
45
+ const persisted = await readTcTokenIndex(keys);
46
+ const merged = new Set(persisted);
47
+ for (const jid of addedJids) {
48
+ if (jid && jid !== TC_TOKEN_INDEX_KEY)
49
+ merged.add(jid);
50
+ }
51
+ return {
52
+ [TC_TOKEN_INDEX_KEY]: { token: Buffer.from(JSON.stringify([...merged])) }
53
+ };
54
+ }
55
+ // WA Web has separate sender/receiver AB props for these but they're identical today
56
+ export function isTcTokenExpired(timestamp) {
57
+ if (timestamp === null || timestamp === undefined)
58
+ return true;
59
+ const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp;
60
+ if (isNaN(ts))
61
+ return true;
62
+ const now = Math.floor(Date.now() / 1000);
63
+ const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION);
64
+ const cutoffBucket = currentBucket - (TC_TOKEN_NUM_BUCKETS - 1);
65
+ const cutoffTimestamp = cutoffBucket * TC_TOKEN_BUCKET_DURATION;
66
+ return ts < cutoffTimestamp;
67
+ }
68
+ export function shouldSendNewTcToken(senderTimestamp) {
69
+ if (senderTimestamp === undefined)
70
+ return true;
71
+ const now = Math.floor(Date.now() / 1000);
72
+ const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION);
73
+ const senderBucket = Math.floor(senderTimestamp / TC_TOKEN_BUCKET_DURATION);
74
+ return currentBucket > senderBucket;
75
+ }
76
+ /** Resolve JID to LID for tctoken storage (WA Web stores under LID) */
77
+ export async function resolveTcTokenJid(jid, getLIDForPN) {
78
+ if (isLidUser(jid))
79
+ return jid;
80
+ const lid = await getLIDForPN(jid);
81
+ return lid ?? jid;
82
+ }
83
+ /** Resolve target JID for issuing privacy token based on AB prop 14303 */
84
+ export async function resolveIssuanceJid(jid, issueToLid, getLIDForPN, getPNForLID) {
85
+ if (issueToLid) {
86
+ if (isLidUser(jid))
87
+ return jid;
88
+ const lid = await getLIDForPN(jid);
89
+ return lid ?? jid;
90
+ }
91
+ if (!isLidUser(jid))
92
+ return jid;
93
+ if (getPNForLID) {
94
+ const pn = await getPNForLID(jid);
95
+ return pn ?? jid;
96
+ }
97
+ return jid;
98
+ }
99
+ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], getLIDForPN }) {
100
+ try {
101
+ const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
102
+ const tcTokenData = await authState.keys.get('tctoken', [storageJid]);
103
+ const entry = tcTokenData?.[storageJid];
104
+ const tcTokenBuffer = entry?.token;
105
+ if (!tcTokenBuffer?.length || isTcTokenExpired(entry?.timestamp)) {
106
+ if (tcTokenBuffer) {
107
+ // Preserve senderTimestamp so shouldSendNewTcToken() keeps its dedupe state
108
+ // after we drop the unusable peer token. Only wipe the record entirely when
109
+ // there's nothing worth keeping.
110
+ const cleared = entry?.senderTimestamp !== undefined
111
+ ? { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp }
112
+ : null;
113
+ await authState.keys.set({ tctoken: { [storageJid]: cleared } });
114
+ }
6
115
  return baseContent.length > 0 ? baseContent : undefined;
116
+ }
7
117
  baseContent.push({
8
118
  tag: 'tctoken',
9
119
  attrs: {},
@@ -14,4 +124,40 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [] })
14
124
  catch (error) {
15
125
  return baseContent.length > 0 ? baseContent : undefined;
16
126
  }
17
- }
127
+ }
128
+ export async function storeTcTokensFromIqResult({ result, fallbackJid, keys, getLIDForPN, onNewJidStored }) {
129
+ const tokensNode = getBinaryNodeChild(result, 'tokens');
130
+ if (!tokensNode)
131
+ return;
132
+ const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
133
+ for (const tokenNode of tokenNodes) {
134
+ if (tokenNode.attrs.type !== 'trusted_contact' || !(tokenNode.content instanceof Uint8Array)) {
135
+ continue;
136
+ }
137
+ // In notifications tokenNode.attrs.jid is your own device JID, not the sender's
138
+ const rawJid = jidNormalizedUser(fallbackJid || tokenNode.attrs.jid);
139
+ if (!isRegularUser(rawJid))
140
+ continue;
141
+ const storageJid = await resolveTcTokenJid(rawJid, getLIDForPN);
142
+ const existingTcData = await keys.get('tctoken', [storageJid]);
143
+ const existingEntry = existingTcData[storageJid];
144
+ const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
145
+ const incomingTs = tokenNode.attrs.t ? Number(tokenNode.attrs.t) : 0;
146
+ // timestamp-less tokens would be immediately expired
147
+ if (!incomingTs)
148
+ continue;
149
+ if (existingTs > 0 && existingTs > incomingTs)
150
+ continue;
151
+ await keys.set({
152
+ tctoken: {
153
+ [storageJid]: {
154
+ ...existingEntry,
155
+ token: Buffer.from(tokenNode.content),
156
+ timestamp: tokenNode.attrs.t
157
+ }
158
+ }
159
+ });
160
+ onNewJidStored?.(storageJid);
161
+ }
162
+ }
163
+ //# sourceMappingURL=tc-token-utils.js.map
@@ -4,27 +4,26 @@ import { proto } from '../../WAProto/index.js';
4
4
  import { initAuthCreds } from './auth-utils.js';
5
5
  import { BufferJSON } from './generics.js';
6
6
  import { LRUCache } from 'lru-cache';
7
+ import { Mutex } from 'async-mutex';
7
8
  // Lia@Changes 25-03-26 --- Add useSingleFileAuthState with integrated cache
8
9
  const FLUSH_TIMEOUT_MS = 3000;
9
10
  // Lia@Changes 22-04-26 --- Enhanced useSingleFileAuthState with LRUCache
10
11
  export const useSingleFileAuthState = async (fileName) => {
11
12
  const cache = new LRUCache({
12
- max: 10000,
13
+ max: 15000,
13
14
  ttl: 1000 * DEFAULT_CACHE_TTLS.SIGNAL_STORE,
14
15
  updateAgeOnGet: false,
15
16
  updateAgeOnHas: false,
16
17
  ttlAutopurge: true
17
18
  });
18
- let fileData = {},
19
- isLoaded = false,
20
- isWriting = false,
21
- isNeedWrite = false,
22
- flushTimeout = null,
23
- loadPromise = null;
24
- const loadKey = () => {
25
- if (isLoaded) return;
26
- if (loadPromise) return loadPromise;
27
- loadPromise = (async () => {
19
+ // Lia@Changes 26-04-26 --- Add mutex for prevent race condition
20
+ const mutex = new Mutex();
21
+ let fileData = {};
22
+ let isLoaded = false;
23
+ let flushTimeout = null;
24
+ const loadKey = async () => {
25
+ return await mutex.runExclusive(async () => {
26
+ if (isLoaded) return;
28
27
  try {
29
28
  const data = JSON.parse(await readFile(fileName, 'utf-8'), BufferJSON.reviver);
30
29
  fileData = data || {};
@@ -36,26 +35,20 @@ export const useSingleFileAuthState = async (fileName) => {
36
35
  fileData = {};
37
36
  }
38
37
  isLoaded = true;
39
- loadPromise = null;
40
- })();
41
- return loadPromise;
38
+ });
42
39
  };
43
40
  const flushKey = () => {
44
41
  if (flushTimeout) return;
45
42
  flushTimeout = setTimeout(async () => {
46
43
  flushTimeout = null;
47
- if (isWriting) {
48
- isNeedWrite = true;
49
- return;
50
- }
51
- isWriting = true;
52
- do {
53
- isNeedWrite = false;
54
- const tempFile = fileName + '.temp';
55
- await writeFile(tempFile, JSON.stringify(fileData, BufferJSON.replacer));
56
- await rename(tempFile, fileName);
57
- } while (isNeedWrite);
58
- isWriting = false;
44
+ await mutex.runExclusive(async () => {
45
+ try {
46
+ const tempFile = fileName + '.temp';
47
+ await writeFile(tempFile, JSON.stringify(fileData, BufferJSON.replacer));
48
+ await rename(tempFile, fileName);
49
+ }
50
+ catch { }
51
+ });
59
52
  }, FLUSH_TIMEOUT_MS);
60
53
  };
61
54
  const writeKey = (keyName, value) => {
@@ -11,11 +11,34 @@ export class USyncContactProtocol {
11
11
  };
12
12
  }
13
13
  getUserElement(user) {
14
- //TODO: Implement type / username fields (not yet supported)
14
+ if (user.phone) {
15
+ return {
16
+ tag: 'contact',
17
+ attrs: {},
18
+ content: user.phone
19
+ };
20
+ }
21
+ if (user.username) {
22
+ return {
23
+ tag: 'contact',
24
+ attrs: {
25
+ username: user.username,
26
+ ...(user.usernameKey ? { pin: user.usernameKey } : {}),
27
+ ...(user.lid ? { lid: user.lid } : {})
28
+ }
29
+ };
30
+ }
31
+ if (user.type) {
32
+ return {
33
+ tag: 'contact',
34
+ attrs: {
35
+ type: user.type
36
+ }
37
+ };
38
+ }
15
39
  return {
16
40
  tag: 'contact',
17
- attrs: {},
18
- content: user.phone
41
+ attrs: {}
19
42
  };
20
43
  }
21
44
  parser(node) {
@@ -0,0 +1,22 @@
1
+ import { assertNodeErrorFree } from '../../WABinary/index.js';
2
+ import { USyncUser } from '../USyncUser.js';
3
+ export class USyncUsernameProtocol {
4
+ name = 'username';
5
+ getQueryElement() {
6
+ return {
7
+ tag: 'username',
8
+ attrs: {}
9
+ };
10
+ }
11
+ getUserElement(user) {
12
+ void user;
13
+ return null;
14
+ }
15
+ parser(node) {
16
+ if (node.tag === 'username') {
17
+ assertNodeErrorFree(node);
18
+ return typeof node.content === 'string' ? node.content : null;
19
+ }
20
+ return null;
21
+ }
22
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './USyncDeviceProtocol.js';
2
2
  export * from './USyncContactProtocol.js';
3
3
  export * from './USyncStatusProtocol.js';
4
- export * from './USyncDisappearingModeProtocol.js';
4
+ export * from './USyncDisappearingModeProtocol.js';
5
+ export * from './USyncUsernameProtocol.js';
@@ -1,7 +1,7 @@
1
1
  import { getBinaryNodeChild } from '../WABinary/index.js';
2
2
  import { USyncBotProfileProtocol } from './Protocols/UsyncBotProfileProtocol.js';
3
3
  import { USyncLIDProtocol } from './Protocols/UsyncLIDProtocol.js';
4
- import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol } from './Protocols/index.js';
4
+ import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol, USyncUsernameProtocol } from './Protocols/index.js';
5
5
  import { USyncUser } from './USyncUser.js';
6
6
  export class USyncQuery {
7
7
  constructor() {
@@ -90,4 +90,8 @@ export class USyncQuery {
90
90
  this.protocols.push(new USyncLIDProtocol());
91
91
  return this;
92
92
  }
93
+ withUsernameProtocol() {
94
+ this.protocols.push(new USyncUsernameProtocol());
95
+ return this;
96
+ }
93
97
  }
@@ -11,6 +11,14 @@ export class USyncUser {
11
11
  this.phone = phone;
12
12
  return this;
13
13
  }
14
+ withUsername(username) {
15
+ this.username = username;
16
+ return this;
17
+ }
18
+ withUsernameKey(usernameKey) {
19
+ this.usernameKey = usernameKey;
20
+ return this;
21
+ }
14
22
  withType(type) {
15
23
  this.type = type;
16
24
  return this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itsliaaa/baileys",
3
- "version": "0.1.33",
3
+ "version": "0.2.0",
4
4
  "description": "Enhanced Baileys v7 with fixed newsletter media upload, plus support for interactive messages, albums, and more message types.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",