@pontasockets/baileys 1.0.1 → 1.0.3
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.
- package/README.md +21 -0
- package/lib/Defaults/index.js +1 -1
- package/lib/Signal/libsignal.js +18 -0
- package/lib/Socket/messages-recv.js +116 -28
- package/lib/Socket/messages-send.js +1 -1
- package/lib/Utils/use-multi-file-auth-state.js +41 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -242,6 +242,27 @@ sock.sendMessage(jid, content, options?)
|
|
|
242
242
|
|
|
243
243
|
#### Standard Messages
|
|
244
244
|
|
|
245
|
+
<details>
|
|
246
|
+
<summary><b>📦 Sticker Pack</b></summary>
|
|
247
|
+
<br>
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
sock.sendMessage(jid, {
|
|
251
|
+
stickerPack: {
|
|
252
|
+
name: 'Koleksi Ponta',
|
|
253
|
+
publisher: 'PontaCT',
|
|
254
|
+
description: 'Sticker pack pertama',
|
|
255
|
+
cover: { url: 'https://example.jpg' },
|
|
256
|
+
stickers: [
|
|
257
|
+
{ sticker: { url: 'https://example.webp' }, emojis: ['😂'] },
|
|
258
|
+
{ sticker: { url: 'https://example.webp' }, emojis: ['🔥'] }
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
</details>
|
|
265
|
+
|
|
245
266
|
<details>
|
|
246
267
|
<summary><b>📝 Text Message</b></summary>
|
|
247
268
|
<br>
|
package/lib/Defaults/index.js
CHANGED
|
@@ -79,7 +79,7 @@ exports.DEFAULT_CONNECTION_CONFIG = {
|
|
|
79
79
|
linkPreviewImageThumbnailWidth: 192,
|
|
80
80
|
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
|
|
81
81
|
generateHighQualityLinkPreview: false,
|
|
82
|
-
enableAutoSessionRecreation:
|
|
82
|
+
enableAutoSessionRecreation: true,
|
|
83
83
|
enableRecentMessageCache: false,
|
|
84
84
|
options: {},
|
|
85
85
|
appStateMacVerification: {
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -111,6 +111,24 @@ function makeLibSignalRepository(auth, onWhatsAppFunc, logger) {
|
|
|
111
111
|
},
|
|
112
112
|
jidToSignalProtocolAddress(jid) {
|
|
113
113
|
return jidToSignalProtocolAddress(jid).toString();
|
|
114
|
+
},
|
|
115
|
+
// Dipakai oleh sendRetryRequest (messages-recv.js) untuk enableAutoSessionRecreation.
|
|
116
|
+
// Sebelumnya dipanggil tapi tidak pernah diimplementasikan di sini, jadi fitur auto
|
|
117
|
+
// session recreation diam-diam selalu gagal (ketangkep try/catch, cuma log warning).
|
|
118
|
+
async validateSession(jid) {
|
|
119
|
+
try {
|
|
120
|
+
const addr = jidToSignalProtocolAddress(jid);
|
|
121
|
+
const session = await storage.loadSession(addr.toString());
|
|
122
|
+
if (!session) {
|
|
123
|
+
return { exists: false, reason: 'no session' };
|
|
124
|
+
}
|
|
125
|
+
if (typeof session.haveOpenSession === 'function' && !session.haveOpenSession()) {
|
|
126
|
+
return { exists: false, reason: 'no open session' };
|
|
127
|
+
}
|
|
128
|
+
return { exists: true };
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return { exists: false, reason: 'validation error' };
|
|
131
|
+
}
|
|
114
132
|
}
|
|
115
133
|
};
|
|
116
134
|
}
|
|
@@ -55,6 +55,13 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
55
55
|
} = sock
|
|
56
56
|
|
|
57
57
|
const retryMutex = make_mutex_1.makeMutex()
|
|
58
|
+
// Mutex terpisah dari processingMutex (dipakai chats.js/messages-send.js) supaya
|
|
59
|
+
// handleMessage/handleReceipt/handleNotification tidak saling antre di satu antrean
|
|
60
|
+
// yang sama. Kalau satu pesan lambat diproses (nunggu plugin/group metadata), receipt
|
|
61
|
+
// & notification lain tetap bisa jalan tanpa nunggu giliran.
|
|
62
|
+
const messageMutex = make_mutex_1.makeMutex()
|
|
63
|
+
const receiptMutex = make_mutex_1.makeMutex()
|
|
64
|
+
const notificationMutex = make_mutex_1.makeMutex()
|
|
58
65
|
|
|
59
66
|
const groupDataCache = new Map()
|
|
60
67
|
global.groupMetadataCache = async (jid) => {
|
|
@@ -674,7 +681,17 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
674
681
|
|
|
675
682
|
const handleMexNotification = (id, node) => {
|
|
676
683
|
const operation = node?.attrs?.op_name
|
|
677
|
-
|
|
684
|
+
let content
|
|
685
|
+
try {
|
|
686
|
+
content = JSON.parse(node?.content)
|
|
687
|
+
} catch (parseErr) {
|
|
688
|
+
logger.debug({ parseErr, node }, 'failed to parse mex notification content, skipping')
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
if (!content?.data) {
|
|
692
|
+
logger.debug({ operation, node }, 'mex notification missing content.data, skipping')
|
|
693
|
+
return
|
|
694
|
+
}
|
|
678
695
|
|
|
679
696
|
let contentPath
|
|
680
697
|
let action
|
|
@@ -716,6 +733,17 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
716
733
|
contentPath = content.data[Types_1.XWAPaths.DEMOTE]
|
|
717
734
|
}
|
|
718
735
|
|
|
736
|
+
// FIX: sebelumnya contentPath.actor.pn diakses langsung tanpa cek dulu.
|
|
737
|
+
// Kalau operation-nya bukan promote/demote yang beneran (WA ngirim tipe
|
|
738
|
+
// operasi MEX baru yang belum dikenal library ini), content.data[path]
|
|
739
|
+
// bisa undefined, dan ini crash dengan "Cannot read properties of
|
|
740
|
+
// undefined (reading 'actor')". Sekarang di-skip aja kalau gak lengkap,
|
|
741
|
+
// gak bikin seluruh proses notification gagal.
|
|
742
|
+
if (!contentPath || !contentPath.actor || !contentPath.user) {
|
|
743
|
+
logger.debug({ operation, node }, 'unhandled/incomplete mex notification, skipping')
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
|
|
719
747
|
ev.emit('newsletter-participants.update', {
|
|
720
748
|
id,
|
|
721
749
|
author: contentPath.actor.pn,
|
|
@@ -1105,7 +1133,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1105
1133
|
|
|
1106
1134
|
try {
|
|
1107
1135
|
await Promise.all([
|
|
1108
|
-
|
|
1136
|
+
receiptMutex.mutex(async () => {
|
|
1109
1137
|
const status = Utils_1.getStatusFromReceiptType(attrs.type)
|
|
1110
1138
|
|
|
1111
1139
|
if (typeof status !== 'undefined' && (
|
|
@@ -1195,7 +1223,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1195
1223
|
|
|
1196
1224
|
try {
|
|
1197
1225
|
await Promise.all([
|
|
1198
|
-
|
|
1226
|
+
notificationMutex.mutex(async () => {
|
|
1199
1227
|
const msg = await processNotification(node)
|
|
1200
1228
|
|
|
1201
1229
|
if (msg) {
|
|
@@ -1241,7 +1269,13 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1241
1269
|
|
|
1242
1270
|
if (WABinary_1.getBinaryNodeChild(node, 'unavailable') && !encNode) {
|
|
1243
1271
|
await sendMessageAck(node)
|
|
1244
|
-
|
|
1272
|
+
let key
|
|
1273
|
+
try {
|
|
1274
|
+
key = Utils_1.decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage.key
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
logger.error({ err: error, node }, 'failed to decode unavailable message node, skipping placeholder resend')
|
|
1277
|
+
return
|
|
1278
|
+
}
|
|
1245
1279
|
response = await requestPlaceholderResend(key);
|
|
1246
1280
|
if (response === 'RESOLVED') {
|
|
1247
1281
|
return
|
|
@@ -1253,12 +1287,30 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1253
1287
|
}
|
|
1254
1288
|
}
|
|
1255
1289
|
|
|
1290
|
+
let decodeResult
|
|
1291
|
+
try {
|
|
1292
|
+
// decryptMessageNode bisa throw sebelum sempat masuk try/catch utama di bawah
|
|
1293
|
+
// (contoh: bug upstream baileys "No participant in group message" pada node
|
|
1294
|
+
// yang malformed/gak lengkap). Kalau gak ditangkep di sini, exception ini lolos
|
|
1295
|
+
// sebagai unhandled rejection dari handleMessage - baik pas diproses langsung
|
|
1296
|
+
// (live message) maupun lewat offline queue.
|
|
1297
|
+
decodeResult = Utils_1.decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger)
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
logger.error({ err: error, node }, 'failed to decrypt/decode message node, dropping')
|
|
1300
|
+
try {
|
|
1301
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError)
|
|
1302
|
+
} catch (ackError) {
|
|
1303
|
+
logger.error({ err: ackError }, 'failed to ack undecodable message node')
|
|
1304
|
+
}
|
|
1305
|
+
return
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1256
1308
|
const {
|
|
1257
1309
|
fullMessage: msg,
|
|
1258
1310
|
category,
|
|
1259
1311
|
author,
|
|
1260
1312
|
decrypt
|
|
1261
|
-
} =
|
|
1313
|
+
} = decodeResult
|
|
1262
1314
|
if (response && msg?.messageStubParameters?.[0] === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1263
1315
|
msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response]
|
|
1264
1316
|
}
|
|
@@ -1285,7 +1337,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1285
1337
|
|
|
1286
1338
|
try {
|
|
1287
1339
|
await Promise.all([
|
|
1288
|
-
|
|
1340
|
+
messageMutex.mutex(async () => {
|
|
1289
1341
|
await decrypt()
|
|
1290
1342
|
if (msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT) {
|
|
1291
1343
|
if (msg?.messageStubParameters?.[0] === Utils_1.MISSING_KEYS_ERROR_TEXT) {
|
|
@@ -1296,6 +1348,12 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1296
1348
|
const isPreKeyError = errorMessage.includes('PreKey')
|
|
1297
1349
|
logger.debug(`[handleMessage] Attempting retry request for failed decryption`)
|
|
1298
1350
|
|
|
1351
|
+
// .catch() wajib ada: call ini sengaja tidak di-await (biar retry
|
|
1352
|
+
// request gak ngeblok proses pesan lain), tapi kalau dibiarkan tanpa
|
|
1353
|
+
// .catch() dan mutex-nya reject, itu jadi unhandled promise rejection
|
|
1354
|
+
// yang bisa nge-crash seluruh proses Node (default Node behavior sejak
|
|
1355
|
+
// v15). Ini yang paling mungkin jadi penyebab bot "tiba-tiba disconnect
|
|
1356
|
+
// dan gak recover" setelah jalan beberapa jam.
|
|
1299
1357
|
retryMutex.mutex(async () => {
|
|
1300
1358
|
try {
|
|
1301
1359
|
if (!ws.isOpen) {
|
|
@@ -1321,7 +1379,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1321
1379
|
await Utils_1.delay(1000)
|
|
1322
1380
|
} catch (uploadErr) {
|
|
1323
1381
|
logger.error({
|
|
1324
|
-
uploadErr
|
|
1382
|
+
err: uploadErr
|
|
1325
1383
|
}, 'Pre-key upload failed, proceeding with retry anyway')
|
|
1326
1384
|
}
|
|
1327
1385
|
}
|
|
@@ -1341,10 +1399,12 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1341
1399
|
await sendRetryRequest(node, !encNode)
|
|
1342
1400
|
} catch (retryErr) {
|
|
1343
1401
|
logger.error({
|
|
1344
|
-
retryErr
|
|
1402
|
+
err: retryErr
|
|
1345
1403
|
}, 'Failed to send retry after error handling')
|
|
1346
1404
|
}
|
|
1347
1405
|
}
|
|
1406
|
+
}).catch((mutexErr) => {
|
|
1407
|
+
logger.error({ err: mutexErr }, 'retryMutex task failed unexpectedly')
|
|
1348
1408
|
})
|
|
1349
1409
|
} else {
|
|
1350
1410
|
let type = undefined
|
|
@@ -1490,9 +1550,17 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1490
1550
|
])
|
|
1491
1551
|
} catch (error) {
|
|
1492
1552
|
logger.error({
|
|
1493
|
-
error,
|
|
1553
|
+
err: error,
|
|
1494
1554
|
node
|
|
1495
1555
|
}, 'error in handling message')
|
|
1556
|
+
// Kalau exception terjadi sebelum sempat ack (misal error di plugin/group
|
|
1557
|
+
// metadata sebelum baris sendMessageAck), stanza ini wajib tetap di-ack.
|
|
1558
|
+
// Tanpa ini, server WA bisa terus retry/redeliver pesan yang sama berulang-ulang.
|
|
1559
|
+
try {
|
|
1560
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError)
|
|
1561
|
+
} catch (ackError) {
|
|
1562
|
+
logger.error({ err: ackError }, 'failed to ack message after error')
|
|
1563
|
+
}
|
|
1496
1564
|
}
|
|
1497
1565
|
}
|
|
1498
1566
|
|
|
@@ -1697,19 +1765,36 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1697
1765
|
isProcessing = true
|
|
1698
1766
|
|
|
1699
1767
|
const promise = async () => {
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1768
|
+
// PENTING: sebelumnya kalau nodeProcessor(node) throw (misal bug upstream
|
|
1769
|
+
// baileys "No participant in group message" pas decrypt node offline yang
|
|
1770
|
+
// malformed), exception itu keluar dari while-loop ini SEBELUM sempat
|
|
1771
|
+
// `isProcessing = false`. Akibatnya isProcessing kepentok true SELAMANYA,
|
|
1772
|
+
// dan enqueue() di bawah selalu langsung `return` tanpa pernah mulai
|
|
1773
|
+
// proses lagi -> semua pesan sesudahnya cuma numpuk di array `nodes` dan
|
|
1774
|
+
// gak pernah diproses lagi sampai bot di-restart total. Ini kemungkinan
|
|
1775
|
+
// besar penyebab "reconnect tapi gak pernah nerima pesan lagi".
|
|
1776
|
+
try {
|
|
1777
|
+
while (nodes.length && ws.isOpen) {
|
|
1778
|
+
const {
|
|
1779
|
+
type,
|
|
1780
|
+
node
|
|
1781
|
+
} = nodes.shift()
|
|
1782
|
+
const nodeProcessor = nodeProcessorMap.get(type)
|
|
1783
|
+
if (!nodeProcessor) {
|
|
1784
|
+
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node')
|
|
1785
|
+
continue
|
|
1786
|
+
}
|
|
1787
|
+
try {
|
|
1788
|
+
await nodeProcessor(node)
|
|
1789
|
+
} catch (error) {
|
|
1790
|
+
// Satu node gagal (misal pesan corrupt/participant kosong) TIDAK
|
|
1791
|
+
// boleh ngeblok node-node lain di belakangnya dalam antrian.
|
|
1792
|
+
onUnexpectedError(error, 'processing offline node')
|
|
1793
|
+
}
|
|
1709
1794
|
}
|
|
1710
|
-
|
|
1795
|
+
} finally {
|
|
1796
|
+
isProcessing = false
|
|
1711
1797
|
}
|
|
1712
|
-
isProcessing = false
|
|
1713
1798
|
}
|
|
1714
1799
|
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'))
|
|
1715
1800
|
}
|
|
@@ -1721,28 +1806,31 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1721
1806
|
|
|
1722
1807
|
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
|
1723
1808
|
|
|
1724
|
-
const processNode = (type, node, identifier, exec) => {
|
|
1809
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
1725
1810
|
const isOffline = !!node.attrs.offline
|
|
1726
1811
|
|
|
1727
1812
|
if (isOffline) {
|
|
1728
1813
|
offlineNodeProcessor.enqueue(type, node)
|
|
1729
1814
|
} else {
|
|
1730
|
-
processNodeWithBuffer(node, identifier, exec)
|
|
1815
|
+
await processNodeWithBuffer(node, identifier, exec)
|
|
1731
1816
|
}
|
|
1732
1817
|
}
|
|
1733
1818
|
|
|
1734
1819
|
// recv a message
|
|
1735
|
-
|
|
1736
|
-
|
|
1820
|
+
// Semua handler di-await supaya event 'CB:message'/'CB:call'/'CB:receipt'/'CB:notification'
|
|
1821
|
+
// yang datang beruntun tidak diproses bersamaan tanpa urutan (race condition di state
|
|
1822
|
+
// bersama seperti session & cache).
|
|
1823
|
+
ws.on('CB:message', async (node) => {
|
|
1824
|
+
await processNode('message', node, 'processing message', handleMessage)
|
|
1737
1825
|
})
|
|
1738
1826
|
ws.on('CB:call', async (node) => {
|
|
1739
|
-
processNode('call', node, 'handling call', handleCall)
|
|
1827
|
+
await processNode('call', node, 'handling call', handleCall)
|
|
1740
1828
|
})
|
|
1741
|
-
ws.on('CB:receipt', node => {
|
|
1742
|
-
processNode('receipt', node, 'handling receipt', handleReceipt)
|
|
1829
|
+
ws.on('CB:receipt', async (node) => {
|
|
1830
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt)
|
|
1743
1831
|
})
|
|
1744
1832
|
ws.on('CB:notification', async (node) => {
|
|
1745
|
-
processNode('notification', node, 'handling notification', handleNotification)
|
|
1833
|
+
await processNode('notification', node, 'handling notification', handleNotification)
|
|
1746
1834
|
})
|
|
1747
1835
|
ws.on('CB:ack,class:message', (node) => {
|
|
1748
1836
|
handleBadAck(node)
|
|
@@ -23,13 +23,19 @@ const fileLocks = new Map()
|
|
|
23
23
|
const getFileLock = (path) => {
|
|
24
24
|
let mutex = fileLocks.get(path)
|
|
25
25
|
if (!mutex) {
|
|
26
|
-
mutex = new async_mutex_1.Mutex()
|
|
26
|
+
mutex = new async_mutex_1.Mutex()
|
|
27
27
|
fileLocks.set(path, mutex)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return mutex
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Lepas mutex dari Map begitu tidak dipakai lagi supaya tidak numpuk terus (memory leak)
|
|
34
|
+
// selama proses hidup lama (bot jalan berbulan-bulan dengan ribuan file sesi berbeda).
|
|
35
|
+
const releaseFileLock = (path) => {
|
|
36
|
+
fileLocks.delete(path)
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* stores the full authentication state in a single folder.
|
|
35
41
|
* Far more efficient than singlefileauthstate
|
|
@@ -38,51 +44,56 @@ const getFileLock = (path) => {
|
|
|
38
44
|
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
|
39
45
|
* */
|
|
40
46
|
const useMultiFileAuthState = async (folder) => {
|
|
41
|
-
// Debounce timers per file to avoid rapid successive writes hammering disk
|
|
42
|
-
const writeDebounceTimers = new Map()
|
|
43
|
-
const WRITE_DEBOUNCE_MS = 300
|
|
44
|
-
|
|
45
47
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
48
|
const writeData = async (data, file) => {
|
|
47
49
|
const filePath = path_1.join(folder, fixFileName(file))
|
|
48
50
|
const mutex = getFileLock(filePath)
|
|
49
51
|
return mutex.acquire().then(async (release) => {
|
|
50
52
|
try {
|
|
51
|
-
// Atomic write: write to temp file then rename
|
|
53
|
+
// Atomic write: write to temp file then rename, agar file tidak pernah
|
|
54
|
+
// kebaca dalam kondisi setengah tertulis kalau proses mati di tengah jalan.
|
|
52
55
|
const tmpPath = filePath + '.tmp'
|
|
53
56
|
await promises_1.writeFile(tmpPath, JSON.stringify(data, generics_1.BufferJSON.replacer))
|
|
54
57
|
await fsRename(tmpPath, filePath)
|
|
55
58
|
} finally {
|
|
56
59
|
release()
|
|
60
|
+
releaseFileLock(filePath)
|
|
57
61
|
}
|
|
58
62
|
})
|
|
59
63
|
}
|
|
60
64
|
const readData = async (file) => {
|
|
65
|
+
const filePath = path_1.join(folder, fixFileName(file))
|
|
61
66
|
try {
|
|
62
|
-
const filePath = path_1.join(folder, fixFileName(file))
|
|
63
67
|
const mutex = getFileLock(filePath)
|
|
64
68
|
const data = await mutex.acquire().then(async (release) => {
|
|
65
69
|
try {
|
|
66
70
|
return await promises_1.readFile(filePath, { encoding: 'utf-8' })
|
|
67
71
|
} finally {
|
|
68
72
|
release()
|
|
73
|
+
releaseFileLock(filePath)
|
|
69
74
|
}
|
|
70
75
|
})
|
|
71
76
|
|
|
72
77
|
return JSON.parse(data, generics_1.BufferJSON.reviver)
|
|
73
78
|
} catch (error) {
|
|
79
|
+
// ENOENT (file belum ada) itu normal, tapi kalau isinya rusak/corrupt (JSON parse
|
|
80
|
+
// gagal) itu wajib kelihatan di log, bukan diam-diam dianggap "null"/hilang.
|
|
81
|
+
if (error?.code !== 'ENOENT') {
|
|
82
|
+
console.warn(`[useMultiFileAuthState] Gagal membaca/parse ${filePath}:`, error?.message || error)
|
|
83
|
+
}
|
|
74
84
|
return null
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
const removeData = async (file) => {
|
|
88
|
+
const filePath = path_1.join(folder, fixFileName(file))
|
|
78
89
|
try {
|
|
79
|
-
const filePath = path_1.join(folder, fixFileName(file))
|
|
80
90
|
const mutex = getFileLock(filePath)
|
|
81
91
|
await mutex.acquire().then(async (release) => {
|
|
82
92
|
try {
|
|
83
93
|
await promises_1.unlink(filePath)
|
|
84
94
|
} finally {
|
|
85
95
|
release()
|
|
96
|
+
releaseFileLock(filePath)
|
|
86
97
|
}
|
|
87
98
|
})
|
|
88
99
|
} catch {}
|
|
@@ -96,9 +107,22 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
96
107
|
else {
|
|
97
108
|
await promises_1.mkdir(folder, { recursive: true })
|
|
98
109
|
}
|
|
99
|
-
const fixFileName = (file) => {
|
|
100
|
-
return file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
|
110
|
+
const fixFileName = (file) => {
|
|
111
|
+
return file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
|
101
112
|
}
|
|
113
|
+
|
|
114
|
+
// Bersihkan file .tmp "yatim" yang tertinggal dari proses sebelumnya yang mati
|
|
115
|
+
// di tengah proses rename (atomic write). File-file ini tidak berguna dan kalau
|
|
116
|
+
// dibiarkan menumpuk, cuma buang-buang disk.
|
|
117
|
+
try {
|
|
118
|
+
const entries = await promises_1.readdir(folder)
|
|
119
|
+
await Promise.all(
|
|
120
|
+
entries
|
|
121
|
+
.filter((name) => name.endsWith('.tmp'))
|
|
122
|
+
.map((name) => promises_1.unlink(path_1.join(folder, name)).catch(() => {}))
|
|
123
|
+
)
|
|
124
|
+
} catch {}
|
|
125
|
+
|
|
102
126
|
const creds = await readData('creds.json') || auth_utils_1.initAuthCreds()
|
|
103
127
|
return {
|
|
104
128
|
state: {
|
|
@@ -128,24 +152,15 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
128
152
|
}
|
|
129
153
|
}
|
|
130
154
|
},
|
|
131
|
-
// saveCreds
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const timer = setTimeout(() => {
|
|
137
|
-
writeDebounceTimers.delete('creds.json')
|
|
138
|
-
writeData(creds, 'creds.json').catch(err => {
|
|
139
|
-
console.error('[useMultiFileAuthState] Failed to save creds.json:', err)
|
|
140
|
-
})
|
|
141
|
-
}, WRITE_DEBOUNCE_MS)
|
|
142
|
-
writeDebounceTimers.set('creds.json', timer)
|
|
155
|
+
// saveCreds sekarang selalu langsung menulis (tidak didebounce). creds.json adalah
|
|
156
|
+
// file paling kritis — kalau proses mati tepat di jendela debounce, sesi bisa hilang
|
|
157
|
+
// atau balik ke state lama sehingga semua pesan gagal didekripsi setelah reconnect.
|
|
158
|
+
saveCreds: async () => {
|
|
159
|
+
return writeData(creds, 'creds.json')
|
|
143
160
|
},
|
|
144
|
-
//
|
|
161
|
+
// Tetap disediakan sebagai alias untuk kompatibilitas kode lama yang memanggil
|
|
162
|
+
// saveCredsNow() secara eksplisit (misal di handler disconnect/cleanup).
|
|
145
163
|
saveCredsNow: async () => {
|
|
146
|
-
const existing = writeDebounceTimers.get('creds.json')
|
|
147
|
-
if (existing) clearTimeout(existing)
|
|
148
|
-
writeDebounceTimers.delete('creds.json')
|
|
149
164
|
return writeData(creds, 'creds.json')
|
|
150
165
|
}
|
|
151
166
|
}
|