@pontalabs/baileys 1.0.3 → 1.0.5
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/lib/Signal/libsignal.js +25 -13
- package/lib/Socket/socket.js +17 -0
- package/lib/Utils/auth-utils.js +18 -0
- package/lib/Utils/decode-wa-message.js +18 -1
- package/lib/Utils/index.d.ts +2 -1
- package/lib/Utils/index.js +2 -1
- package/lib/Utils/signal-housekeeping.d.ts +19 -0
- package/lib/Utils/signal-housekeeping.js +73 -0
- package/package.json +1 -1
package/lib/Signal/libsignal.js
CHANGED
|
@@ -116,20 +116,29 @@ function makeLibSignalRepository(auth, onWhatsAppFunc, logger) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
try {
|
|
120
|
+
return await auth.keys.transaction(async () => {
|
|
121
|
+
let result;
|
|
122
|
+
switch (type) {
|
|
123
|
+
case 'pkmsg':
|
|
124
|
+
result = await session.decryptPreKeyWhisperMessage(ciphertext);
|
|
125
|
+
break;
|
|
126
|
+
case 'msg':
|
|
127
|
+
result = await session.decryptWhisperMessage(ciphertext);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unknown message type: ${type}`);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}, jid);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const errText = String(error?.message || error || '')
|
|
136
|
+
if (/bad mac|no session|invalid message|duplicate message|cannot decrypt/i.test(errText)) {
|
|
137
|
+
await storage.removeSession(addr.toString()).catch(() => null)
|
|
138
|
+
logger?.warn?.({ jid, type, errText }, 'reset stale session after decrypt failure')
|
|
130
139
|
}
|
|
131
|
-
|
|
132
|
-
}
|
|
140
|
+
throw error
|
|
141
|
+
}
|
|
133
142
|
},
|
|
134
143
|
async encryptMessage({ jid, data }) {
|
|
135
144
|
const addr = jidToSignalProtocolAddress(jid);
|
|
@@ -203,6 +212,9 @@ function signalStorage({ creds, keys }) {
|
|
|
203
212
|
storeSession: async (id, session) => {
|
|
204
213
|
await keys.set({ session: { [id]: session.serialize() } });
|
|
205
214
|
},
|
|
215
|
+
removeSession: async (id) => {
|
|
216
|
+
await keys.set({ session: { [id]: null } });
|
|
217
|
+
},
|
|
206
218
|
isTrustedIdentity: () => {
|
|
207
219
|
return true;
|
|
208
220
|
},
|
package/lib/Socket/socket.js
CHANGED
|
@@ -197,6 +197,12 @@ const makeSocket = (config) => {
|
|
|
197
197
|
let lastDateRecv
|
|
198
198
|
let epoch = 1
|
|
199
199
|
let keepAliveReq
|
|
200
|
+
// FIX: sebelumnya uploadPreKeysToServerIfRequired() cuma dipanggil SEKALI pas
|
|
201
|
+
// 'CB:success' (baru connect/reconnect). Kalau proses bot jalan lama (berhari-hari)
|
|
202
|
+
// tanpa reconnect, prekey yang kepakai server gak pernah dicek ulang sampai jumlahnya
|
|
203
|
+
// abis - baru ketahuan pas sudah kejadian error decrypt di penerima. Interval ini
|
|
204
|
+
// bikin prekey selalu "terupdate", dicek ulang berkala tanpa perlu nunggu reconnect.
|
|
205
|
+
let preKeyCheckInterval
|
|
200
206
|
let qrTimer
|
|
201
207
|
let closed = false
|
|
202
208
|
|
|
@@ -516,6 +522,7 @@ const makeSocket = (config) => {
|
|
|
516
522
|
trace: error?.stack
|
|
517
523
|
}, error ? 'connection errored' : 'connection closed')
|
|
518
524
|
clearInterval(keepAliveReq)
|
|
525
|
+
clearInterval(preKeyCheckInterval)
|
|
519
526
|
clearTimeout(qrTimer)
|
|
520
527
|
ws.removeAllListeners('close')
|
|
521
528
|
ws.removeAllListeners('open')
|
|
@@ -822,6 +829,16 @@ const makeSocket = (config) => {
|
|
|
822
829
|
ws.on('CB:success', async (node) => {
|
|
823
830
|
try {
|
|
824
831
|
await uploadPreKeysToServerIfRequired()
|
|
832
|
+
// Jalankan ulang pengecekan prekey tiap 6 jam selama koneksi hidup, bukan
|
|
833
|
+
// cuma sekali pas connect. uploadPreKeysToServerIfRequired() sendiri sudah
|
|
834
|
+
// pintar (cuma benar-benar upload kalau count di server rendah/ada yang
|
|
835
|
+
// hilang), jadi interval ini murah dan aman dijalankan berkala.
|
|
836
|
+
clearInterval(preKeyCheckInterval)
|
|
837
|
+
preKeyCheckInterval = setInterval(() => {
|
|
838
|
+
uploadPreKeysToServerIfRequired().catch((err) => {
|
|
839
|
+
logger.warn({ err }, 'periodic pre-key check failed')
|
|
840
|
+
})
|
|
841
|
+
}, 6 * 60 * 60 * 1000)
|
|
825
842
|
await sendPassiveIq('active')
|
|
826
843
|
} catch (err) {
|
|
827
844
|
logger.warn({
|
package/lib/Utils/auth-utils.js
CHANGED
|
@@ -14,6 +14,16 @@ const crypto_2 = require("./crypto")
|
|
|
14
14
|
const generics_1 = require("./generics")
|
|
15
15
|
const mutex_1 = require("async-mutex")
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
// Cache bypass for signal-sensitive keys so runtime state doesn't keep stale
|
|
19
|
+
// session/pre-key material alive after decrypt failures or identity rotation.
|
|
20
|
+
const NO_CACHE_TYPES = new Set([
|
|
21
|
+
'pre-key',
|
|
22
|
+
'session',
|
|
23
|
+
'signed-pre-key',
|
|
24
|
+
'identity-key'
|
|
25
|
+
])
|
|
26
|
+
|
|
17
27
|
/**
|
|
18
28
|
* Adds caching capability to a SignalKeyStore
|
|
19
29
|
* @param store the store to add caching to
|
|
@@ -35,6 +45,11 @@ function makeCacheableSignalKeyStore(store, logger, _cache) {
|
|
|
35
45
|
}
|
|
36
46
|
return {
|
|
37
47
|
async get(type, ids) {
|
|
48
|
+
if (NO_CACHE_TYPES.has(type)) {
|
|
49
|
+
logger?.trace({ type, items: ids.length }, 'bypassing cache for sensitive key type')
|
|
50
|
+
return await store.get(type, ids)
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
return cacheMutex.runExclusive(async () => {
|
|
39
54
|
const data = {}
|
|
40
55
|
const idsToFetch = []
|
|
@@ -65,6 +80,9 @@ function makeCacheableSignalKeyStore(store, logger, _cache) {
|
|
|
65
80
|
return cacheMutex.runExclusive(async () => {
|
|
66
81
|
let keys = 0
|
|
67
82
|
for (const type in data) {
|
|
83
|
+
if (NO_CACHE_TYPES.has(type)) {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
68
86
|
for (const id in data[type]) {
|
|
69
87
|
await cache.set(getUniqueId(type, id), data[type][id])
|
|
70
88
|
keys += 1
|
|
@@ -222,7 +222,24 @@ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
|
|
|
222
222
|
async decrypt() {
|
|
223
223
|
let decryptables = 0
|
|
224
224
|
if (Array.isArray(stanza.content)) {
|
|
225
|
-
|
|
225
|
+
// FIX: proses node non-skmsg (msg/pkmsg/plaintext) LEBIH DULU sebelum node
|
|
226
|
+
// skmsg. Root cause "bot diam/nunggu" pas ada member baru join grup lalu
|
|
227
|
+
// langsung kirim pesan: satu stanza berisi 2 node <enc> - satu tipe
|
|
228
|
+
// pkmsg/msg (isinya SenderKeyDistributionMessage buat kamu) dan satu lagi
|
|
229
|
+
// tipe skmsg (isi pesan asli, dienkripsi pakai sender-key grup). Urutan
|
|
230
|
+
// array stanza.content dari server TIDAK dijamin naruh pkmsg/msg duluan.
|
|
231
|
+
// Kalau skmsg kebetulan diproses duluan, sender-key belum ada di store ->
|
|
232
|
+
// decryptGroupMessage() gagal "No SenderKeyRecord found" -> pesan pertama
|
|
233
|
+
// dari member baru selalu gagal decrypt & jadi stub CIPHERTEXT, padahal
|
|
234
|
+
// SKDM-nya baru "sampai" sepersekian detik kemudian di iterasi berikutnya.
|
|
235
|
+
// Reorder di sini (stable, gak ubah urutan relatif node lain seperti
|
|
236
|
+
// verified_name/unavailable) supaya skmsg selalu diproses PALING TERAKHIR,
|
|
237
|
+
// setelah semua kemungkinan SKDM sudah didaftarkan ke sender-key store.
|
|
238
|
+
const orderedContent = [
|
|
239
|
+
...stanza.content.filter(({ tag, attrs }) => !(tag === 'enc' && attrs.type === 'skmsg')),
|
|
240
|
+
...stanza.content.filter(({ tag, attrs }) => tag === 'enc' && attrs.type === 'skmsg')
|
|
241
|
+
]
|
|
242
|
+
for (const { tag, attrs, content } of orderedContent) {
|
|
226
243
|
if (tag === 'verified_name' && content instanceof Uint8Array) {
|
|
227
244
|
const cert = WAProto_1.proto.VerifiedNameCertificate.decode(content)
|
|
228
245
|
const details = WAProto_1.proto.VerifiedNameCertificate.Details.decode(cert.details)
|
package/lib/Utils/index.d.ts
CHANGED
|
@@ -17,4 +17,5 @@ export * from './use-single-file-auth-state'
|
|
|
17
17
|
export * from './use-multi-file-auth-state'
|
|
18
18
|
export * from './link-preview'
|
|
19
19
|
export * from './event-buffer'
|
|
20
|
-
export * from './process-message'
|
|
20
|
+
export * from './process-message'
|
|
21
|
+
export * from './signal-housekeeping'
|
package/lib/Utils/index.js
CHANGED
|
@@ -37,4 +37,5 @@ __exportStar(require("./use-single-file-auth-state"), exports)
|
|
|
37
37
|
__exportStar(require("./use-multi-file-auth-state"), exports)
|
|
38
38
|
__exportStar(require("./link-preview"), exports)
|
|
39
39
|
__exportStar(require("./event-buffer"), exports)
|
|
40
|
-
__exportStar(require("./process-message"), exports)
|
|
40
|
+
__exportStar(require("./process-message"), exports)
|
|
41
|
+
__exportStar(require("./signal-housekeeping"), exports)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Logger } from 'pino'
|
|
2
|
+
|
|
3
|
+
export interface CleanupSignalAuthFolderOptions {
|
|
4
|
+
/** File yang mtime-nya lebih tua dari ini (dalam hari) akan dihapus. Default 30. */
|
|
5
|
+
maxAgeDays?: number
|
|
6
|
+
/** Prefix nama file yang boleh dibersihkan. Default ['session-', 'sender-key-']. */
|
|
7
|
+
prefixes?: string[]
|
|
8
|
+
logger?: Logger
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CleanupSignalAuthFolderResult {
|
|
12
|
+
removed: string[]
|
|
13
|
+
scanned: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function cleanupSignalAuthFolder(
|
|
17
|
+
folder: string,
|
|
18
|
+
options?: CleanupSignalAuthFolderOptions
|
|
19
|
+
): Promise<CleanupSignalAuthFolderResult>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true })
|
|
4
|
+
exports.cleanupSignalAuthFolder = cleanupSignalAuthFolder
|
|
5
|
+
|
|
6
|
+
const fs = require("fs/promises")
|
|
7
|
+
const path = require("path")
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bersihkan file session/sender-key yang sudah usang di folder auth yang dipakai
|
|
11
|
+
* bareng useMultiFileAuthState(). Tujuannya supaya store signal tidak numpuk terus
|
|
12
|
+
* selama bot jalan berbulan-bulan dengan ribuan kontak/grup yang sudah gak aktif.
|
|
13
|
+
*
|
|
14
|
+
* Aman dipakai karena: mtime sebuah file session-*.json / sender-key-*.json selalu
|
|
15
|
+
* ke-update setiap kali ada pertukaran pesan dengan kontak/grup itu (storeSession /
|
|
16
|
+
* storeSenderKey menulis ulang filenya). Jadi kalau sebuah file gak pernah disentuh
|
|
17
|
+
* lagi lebih dari `maxAgeDays`, itu tandanya kontak/grup itu memang sudah gak aktif -
|
|
18
|
+
* hapus filenya aman, nanti kalau ada pesan baru sesi/sender-key otomatis dibuat ulang
|
|
19
|
+
* dari prekey bundle / SenderKeyDistributionMessage yang baru.
|
|
20
|
+
*
|
|
21
|
+
* creds.json, app-state-sync-key-*.json, dan pre-key-*.json SENGAJA tidak disentuh
|
|
22
|
+
* di sini secara default - itu bukan sumber "numpuk", dan prekey yang sudah dipakai
|
|
23
|
+
* memang sudah dihapus otomatis lewat removePreKey() di libsignal.js.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} folder - folder yang sama dipakai di useMultiFileAuthState()
|
|
26
|
+
* @param {object} [options]
|
|
27
|
+
* @param {number} [options.maxAgeDays=30] - file yang mtime-nya lebih tua dari ini akan dihapus
|
|
28
|
+
* @param {string[]} [options.prefixes] - prefix nama file yang boleh dibersihkan
|
|
29
|
+
* @param {import('pino').Logger} [options.logger]
|
|
30
|
+
* @returns {Promise<{ removed: string[], scanned: number }>}
|
|
31
|
+
*/
|
|
32
|
+
async function cleanupSignalAuthFolder(folder, options = {}) {
|
|
33
|
+
const {
|
|
34
|
+
maxAgeDays = 30,
|
|
35
|
+
prefixes = ['session-', 'sender-key-'],
|
|
36
|
+
logger
|
|
37
|
+
} = options
|
|
38
|
+
|
|
39
|
+
const removed = []
|
|
40
|
+
let scanned = 0
|
|
41
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000
|
|
42
|
+
const now = Date.now()
|
|
43
|
+
|
|
44
|
+
let entries
|
|
45
|
+
try {
|
|
46
|
+
entries = await fs.readdir(folder)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
logger?.warn?.({ err, folder }, '[cleanupSignalAuthFolder] gagal baca folder auth')
|
|
49
|
+
return { removed, scanned }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const name of entries) {
|
|
53
|
+
if (!prefixes.some((p) => name.startsWith(p))) {
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
scanned++
|
|
57
|
+
const filePath = path.join(folder, name)
|
|
58
|
+
try {
|
|
59
|
+
const stat = await fs.stat(filePath)
|
|
60
|
+
if (now - stat.mtimeMs > maxAgeMs) {
|
|
61
|
+
await fs.unlink(filePath)
|
|
62
|
+
removed.push(name)
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// File mungkin sudah kehapus proses lain (race antar-worker), aman diabaikan.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
logger?.info?.({ scanned, removedCount: removed.length, maxAgeDays }, '[cleanupSignalAuthFolder] selesai')
|
|
70
|
+
return { removed, scanned }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { cleanupSignalAuthFolder }
|