@zervonex/baileys 0.0.0-stage → 3.0.1
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/LICENSE +21 -0
- package/README.md +3203 -2
- package/WAProto/WAProtoCompile.js +291 -0
- package/WAProto/fetcher.js +69 -0
- package/WAProto/index.js +88 -0
- package/WAProto/parser.js +313 -0
- package/engine-requirements.js +10 -0
- package/lib/Builders/AIRich.js +590 -0
- package/lib/Builders/base.js +130 -0
- package/lib/Builders/builders.js +448 -0
- package/lib/Builders/index.js +3 -0
- package/lib/Defaults/index.js +225 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/index.js +2 -0
- package/lib/Signal/libsignal.js +523 -0
- package/lib/Signal/lid-mapping.js +283 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.js +84 -0
- package/lib/Socket/business.js +152 -0
- package/lib/Socket/chats.js +855 -0
- package/lib/Socket/communities.js +147 -0
- package/lib/Socket/groups.js +294 -0
- package/lib/Socket/index.js +15 -0
- package/lib/Socket/messages-recv.js +904 -0
- package/lib/Socket/messages-send.js +693 -0
- package/lib/Socket/mex.js +34 -0
- package/lib/Socket/newsletter.js +752 -0
- package/lib/Socket/nexus-handler.js +55 -0
- package/lib/Socket/registration.js +155 -0
- package/lib/Socket/socket.js +798 -0
- package/lib/Socket/spam-report.js +65 -0
- package/lib/Socket/username.js +82 -0
- package/lib/Store/index.js +6 -0
- package/lib/Store/make-cache-manager-store.js +81 -0
- package/lib/Store/make-in-memory-store.js +425 -0
- package/lib/Store/make-ordered-dictionary.js +82 -0
- package/lib/Store/object-repository.js +31 -0
- package/lib/Store/reporting-info-store.js +38 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +8 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +25 -0
- package/lib/Types/LabelAssociation.js +7 -0
- package/lib/Types/Message.js +15 -0
- package/lib/Types/Newsletter.js +100 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +14 -0
- package/lib/Types/State.js +26 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +30 -0
- package/lib/Utils/auth-utils.js +142 -0
- package/lib/Utils/baileys-event-stream.js +33 -0
- package/lib/Utils/bot-capabilities.js +40 -0
- package/lib/Utils/bot-planning-replay.js +44 -0
- package/lib/Utils/browser-utils.js +26 -0
- package/lib/Utils/business.js +75 -0
- package/lib/Utils/chat-utils.js +316 -0
- package/lib/Utils/companion-reg-client-utils.js +20 -0
- package/lib/Utils/consumer-application.js +38 -0
- package/lib/Utils/crypto.js +52 -0
- package/lib/Utils/decode-wa-message.js +197 -0
- package/lib/Utils/event-buffer.js +273 -0
- package/lib/Utils/extras.js +186 -0
- package/lib/Utils/generics.js +195 -0
- package/lib/Utils/history.js +51 -0
- package/lib/Utils/identity-change-handler.js +19 -0
- package/lib/Utils/index.js +39 -0
- package/lib/Utils/key-store.js +3 -0
- package/lib/Utils/link-preview.js +51 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.js +3 -0
- package/lib/Utils/make-mutex.js +16 -0
- package/lib/Utils/message-composer.js +59 -0
- package/lib/Utils/message-retry-manager.js +48 -0
- package/lib/Utils/messages-media.js +520 -0
- package/lib/Utils/messages.js +742 -0
- package/lib/Utils/meta-ai-msmsg.js +122 -0
- package/lib/Utils/meta-compositing.js +75 -0
- package/lib/Utils/modern-messages.js +235 -0
- package/lib/Utils/native-flow.js +55 -0
- package/lib/Utils/noise-handler.js +161 -0
- package/lib/Utils/pre-key-manager.js +76 -0
- package/lib/Utils/process-message.js +399 -0
- package/lib/Utils/reporting-utils.js +171 -0
- package/lib/Utils/signal.js +167 -0
- package/lib/Utils/spam-report-utils.js +142 -0
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +102 -0
- package/lib/Utils/use-multi-file-auth-state.js +307 -0
- package/lib/Utils/validate-connection.js +181 -0
- package/lib/WABinary/constants.js +1321 -0
- package/lib/WABinary/decode.js +152 -0
- package/lib/WABinary/encode.js +140 -0
- package/lib/WABinary/generic-utils.js +179 -0
- package/lib/WABinary/index.js +6 -0
- package/lib/WABinary/jid-utils.js +235 -0
- package/lib/WABinary/types.js +4 -0
- package/lib/WAM/BinaryInfo.js +10 -0
- package/lib/WAM/constants.js +22853 -0
- package/lib/WAM/encode.js +150 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.js +100 -0
- package/lib/WAUSync/USyncUser.js +20 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/index.js +68 -0
- package/package.json +216 -5
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SessionCipher,
|
|
3
|
+
SessionBuilder,
|
|
4
|
+
SessionRecord,
|
|
5
|
+
ProtocolAddress,
|
|
6
|
+
GroupCipher,
|
|
7
|
+
GroupSessionBuilder,
|
|
8
|
+
SenderKeyName,
|
|
9
|
+
SenderKeyDistributionMessage,
|
|
10
|
+
} from 'whatsapp-rust-bridge'
|
|
11
|
+
import { proto } from '../../WAProto/index.js'
|
|
12
|
+
import { LRUCache } from 'lru-cache'
|
|
13
|
+
import { generateSignalPubKey } from '../Utils/index.js'
|
|
14
|
+
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, transferDevice, WAJIDDomains } from '../WABinary/index.js'
|
|
15
|
+
import { LIDMappingStore } from './lid-mapping.js'
|
|
16
|
+
|
|
17
|
+
// ─── Address Helpers ──────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const jidToAddr = (jid) => {
|
|
20
|
+
const { user, device, server, domainType } = jidDecode(jid)
|
|
21
|
+
if (!user) throw new Error(`Invalid JID: "${jid}"`)
|
|
22
|
+
if (device === 99 && server !== 'hosted' && server !== 'hosted.lid') throw new Error('Invalid device 99: ' + jid)
|
|
23
|
+
return new ProtocolAddress(
|
|
24
|
+
domainType !== WAJIDDomains.WHATSAPP ? `${user}_${domainType}` : user,
|
|
25
|
+
device || 0
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const jidToSenderKeyName = (group, user) => new SenderKeyName(group, jidToAddr(user))
|
|
30
|
+
|
|
31
|
+
// ─── Buffer Utils ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const toBuffer = (raw) => {
|
|
34
|
+
if (!raw) return null
|
|
35
|
+
if (raw instanceof Uint8Array) return raw
|
|
36
|
+
if (Buffer.isBuffer(raw)) return raw
|
|
37
|
+
if (raw?.type === 'Buffer' && Array.isArray(raw?.data)) return Buffer.from(raw.data)
|
|
38
|
+
if (Array.isArray(raw)) return Buffer.from(raw)
|
|
39
|
+
if (typeof raw === 'string') return Buffer.from(raw, 'base64')
|
|
40
|
+
if (raw?.data) return Buffer.from(raw.data)
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const toU8 = (raw) => {
|
|
45
|
+
const buf = toBuffer(raw)
|
|
46
|
+
if (!buf) return null
|
|
47
|
+
return buf instanceof Uint8Array && buf.constructor === Uint8Array
|
|
48
|
+
? buf
|
|
49
|
+
: new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isOldJson = (raw) => {
|
|
53
|
+
if (!raw || raw instanceof Uint8Array || Buffer.isBuffer(raw)) return false
|
|
54
|
+
if (typeof raw === 'object') return 'version' in raw || '_sessions' in raw
|
|
55
|
+
if (typeof raw === 'string') {
|
|
56
|
+
try { const p = JSON.parse(raw); return 'version' in p || '_sessions' in p } catch { return false }
|
|
57
|
+
}
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const bufEqual = (a, b) =>
|
|
62
|
+
a && b && a.length === b.length && a.every((byte, i) => byte === b[i])
|
|
63
|
+
|
|
64
|
+
// ─── Identity Key Extraction ──────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
const extractIdentityFromPkmsg = (ciphertext) => {
|
|
67
|
+
try {
|
|
68
|
+
if (!ciphertext || ciphertext.length < 2) return undefined
|
|
69
|
+
if ((ciphertext[0] & 0xf) !== 3) return undefined
|
|
70
|
+
const decoded = proto.PreKeySignalMessage.decode(ciphertext.slice(1))
|
|
71
|
+
const key = decoded.identityKey
|
|
72
|
+
if (key && key.length === 33) return new Uint8Array(key)
|
|
73
|
+
return undefined
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Main Factory ─────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
82
|
+
const lidMapping = new LIDMappingStore(auth.keys, logger, pnToLIDFunc)
|
|
83
|
+
const parsedKeys = auth.keys
|
|
84
|
+
|
|
85
|
+
// LRU cache for PN→LID address resolution to avoid repeated DB lookups
|
|
86
|
+
const lidCache = new LRUCache({ max: 500, ttl: 5 * 60 * 1000 })
|
|
87
|
+
|
|
88
|
+
// Per-address session read cache: avoids a DB round-trip on every decrypt.
|
|
89
|
+
// Keyed by resolved LID addr string. Value is Uint8Array | null.
|
|
90
|
+
const sessionReadCache = new LRUCache({ max: 1000, ttl: 5 * 60 * 1000, ttlAutopurge: true })
|
|
91
|
+
|
|
92
|
+
// Per-address identity read cache: avoids a DB round-trip on every encrypt.
|
|
93
|
+
const identityReadCache = new LRUCache({ max: 1000, ttl: 5 * 60 * 1000, ttlAutopurge: true })
|
|
94
|
+
|
|
95
|
+
const resolveLID = async (id) => {
|
|
96
|
+
if (!id.includes('.')) return id
|
|
97
|
+
const cached = lidCache.get(id)
|
|
98
|
+
if (cached) return cached
|
|
99
|
+
const [deviceId, device] = id.split('.')
|
|
100
|
+
const [user, dt] = deviceId.split('_')
|
|
101
|
+
const domainType = parseInt(dt || '0')
|
|
102
|
+
if (domainType === WAJIDDomains.LID || domainType === WAJIDDomains.HOSTED_LID) return id
|
|
103
|
+
const pnJid = `${user}${device !== '0' ? `:${device}` : ''}@${domainType === WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`
|
|
104
|
+
const lid = await lidMapping.getLIDForPN(pnJid)
|
|
105
|
+
const result = lid ? jidToAddr(lid).toString() : id
|
|
106
|
+
lidCache.set(id, result)
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const storage = signalStorage(auth, lidMapping, logger, lidCache, sessionReadCache, identityReadCache, resolveLID)
|
|
111
|
+
|
|
112
|
+
// Tracks which PN→LID session migrations have already been done this process
|
|
113
|
+
// lifetime so migrateSession is idempotent even if called repeatedly.
|
|
114
|
+
const migratedCache = new LRUCache({ ttl: 7 * 24 * 60 * 60 * 1000, ttlAutopurge: true, updateAgeOnGet: true })
|
|
115
|
+
|
|
116
|
+
const txn = (fn, key) => parsedKeys.transaction(fn, key)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
// ── Group messaging ───────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
decryptGroupMessage({ group, authorJid, msg }) {
|
|
122
|
+
return txn(
|
|
123
|
+
() => new GroupCipher(storage, group, jidToAddr(authorJid)).decrypt(toU8(msg)),
|
|
124
|
+
group
|
|
125
|
+
)
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async processSenderKeyDistributionMessage({ item, authorJid }) {
|
|
129
|
+
if (!item.groupId) throw new Error('Group ID required')
|
|
130
|
+
const senderName = jidToSenderKeyName(item.groupId, authorJid)
|
|
131
|
+
const senderMsg = SenderKeyDistributionMessage.deserialize(
|
|
132
|
+
toU8(item.axolotlSenderKeyDistributionMessage)
|
|
133
|
+
)
|
|
134
|
+
return txn(() => new GroupSessionBuilder(storage).process(senderName, senderMsg), item.groupId)
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
encryptGroupMessage({ group, meId, data }) {
|
|
138
|
+
return txn(async () => {
|
|
139
|
+
const senderName = jidToSenderKeyName(group, meId)
|
|
140
|
+
const skdm = await new GroupSessionBuilder(storage).create(senderName)
|
|
141
|
+
const ciphertext = await new GroupCipher(storage, group, jidToAddr(meId)).encrypt(toU8(data))
|
|
142
|
+
return { ciphertext, senderKeyDistributionMessage: skdm.serialize() }
|
|
143
|
+
}, group)
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
getSenderKeyDistributionMessage({ group, meId }) {
|
|
147
|
+
return txn(async () => {
|
|
148
|
+
const senderName = jidToSenderKeyName(group, meId)
|
|
149
|
+
return (await new GroupSessionBuilder(storage).create(senderName)).serialize()
|
|
150
|
+
}, group)
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async hasSenderKey({ group, meId }) {
|
|
154
|
+
const name = jidToSenderKeyName(group, meId).toString()
|
|
155
|
+
const { [name]: raw } = await parsedKeys.get('sender-key', [name])
|
|
156
|
+
const buf = toU8(raw)
|
|
157
|
+
if (!buf) return false
|
|
158
|
+
try {
|
|
159
|
+
return !SenderKeyRecord.deserialize(buf).isEmpty()
|
|
160
|
+
} catch {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
deleteSenderKey(group, authorJid) {
|
|
166
|
+
return parsedKeys.set({ 'sender-key': { [jidToSenderKeyName(group, authorJid).toString()]: null } })
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// ── 1:1 messaging ─────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
async decryptMessage({ jid, type, ciphertext }) {
|
|
172
|
+
const addr = jidToAddr(jid)
|
|
173
|
+
const addrStr = addr.toString()
|
|
174
|
+
const cipher = new SessionCipher(storage, addr)
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
return await txn(async () => {
|
|
178
|
+
if (type === 'pkmsg') {
|
|
179
|
+
const identityKey = extractIdentityFromPkmsg(ciphertext)
|
|
180
|
+
if (identityKey) {
|
|
181
|
+
const changed = await storage.saveIdentity(addrStr, identityKey)
|
|
182
|
+
if (changed) logger?.info?.({ jid }, '[Signal] Identity key changed — session cleared for re-handshake')
|
|
183
|
+
} else {
|
|
184
|
+
logger?.warn?.({ jid }, '[Signal] pkmsg: could not extract identity key from envelope')
|
|
185
|
+
}
|
|
186
|
+
return cipher.decryptPreKeyWhisperMessage(toU8(ciphertext))
|
|
187
|
+
}
|
|
188
|
+
if (type === 'msg') {
|
|
189
|
+
return cipher.decryptWhisperMessage(toU8(ciphertext))
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`[Signal] Unknown message type: ${type}`)
|
|
192
|
+
}, jid)
|
|
193
|
+
} catch (e) {
|
|
194
|
+
if (e?.message?.includes('DuplicatedMessage')) {
|
|
195
|
+
logger?.debug?.({ jid }, '[Signal] Duplicate message ignored')
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
if (e?.message?.includes('UntrustedIdentity') || e?.message?.includes('InvalidMessage')) {
|
|
199
|
+
logger?.warn?.({ jid, err: e.message }, '[Signal] Session error — wiping session for re-handshake')
|
|
200
|
+
await storage.wipeSession(addrStr)
|
|
201
|
+
sessionReadCache.delete(addrStr)
|
|
202
|
+
}
|
|
203
|
+
throw e
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
encryptMessage({ jid, data }) {
|
|
208
|
+
return txn(async () => {
|
|
209
|
+
const { type: sigType, body } = await new SessionCipher(storage, jidToAddr(jid)).encrypt(toU8(data))
|
|
210
|
+
return { type: sigType === 3 ? 'pkmsg' : 'msg', ciphertext: Buffer.from(body) }
|
|
211
|
+
}, jid)
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
injectE2ESession({ jid, session }) {
|
|
215
|
+
return txn(() => new SessionBuilder(storage, jidToAddr(jid)).processPreKeyBundle(session), jid)
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// ── Session utilities ──────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
jidToSignalProtocolAddress: (jid) => jidToAddr(jid).toString(),
|
|
221
|
+
|
|
222
|
+
lidMapping,
|
|
223
|
+
|
|
224
|
+
async validateSession(jid) {
|
|
225
|
+
try {
|
|
226
|
+
const addr = jidToAddr(jid).toString()
|
|
227
|
+
const raw = await storage.loadSession(addr)
|
|
228
|
+
if (!raw) return { exists: false, reason: 'no session' }
|
|
229
|
+
try {
|
|
230
|
+
return SessionRecord.deserialize(raw).haveOpenSession()
|
|
231
|
+
? { exists: true }
|
|
232
|
+
: { exists: false, reason: 'no open session' }
|
|
233
|
+
} catch {
|
|
234
|
+
return { exists: false, reason: 'deserialize error' }
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
return { exists: false, reason: 'error' }
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async deleteSession(jids) {
|
|
242
|
+
if (!jids?.length) return
|
|
243
|
+
return txn(async () => {
|
|
244
|
+
const updates = {}
|
|
245
|
+
for (const jid of jids) {
|
|
246
|
+
const addr = jidToAddr(jid).toString()
|
|
247
|
+
updates[addr] = null
|
|
248
|
+
sessionReadCache.delete(addr)
|
|
249
|
+
}
|
|
250
|
+
await parsedKeys.set({ session: updates })
|
|
251
|
+
}, `del-${jids.length}`)
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// ── Session migration (PN → LID) ──────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
async migrateSession(fromJid, toJid) {
|
|
257
|
+
if (!fromJid || (!isLidUser(toJid) && !isHostedLidUser(toJid))) return { migrated: 0, skipped: 0, total: 0 }
|
|
258
|
+
if (!isPnUser(fromJid) && !isHostedPnUser(fromJid)) return { migrated: 0, skipped: 0, total: 1 }
|
|
259
|
+
const { user } = jidDecode(fromJid)
|
|
260
|
+
|
|
261
|
+
const { [user]: userDevices_ } = await parsedKeys.get('device-list', [user])
|
|
262
|
+
const userDevices = userDevices_ ? [...userDevices_] : []
|
|
263
|
+
const fromDeviceStr = jidDecode(fromJid).device?.toString() || '0'
|
|
264
|
+
if (!userDevices.includes(fromDeviceStr)) userDevices.push(fromDeviceStr)
|
|
265
|
+
|
|
266
|
+
// Build addr strings for each device, skip already-migrated
|
|
267
|
+
const candidates = userDevices
|
|
268
|
+
.filter(d => !migratedCache.has(`${user}.${d}`))
|
|
269
|
+
.map(d => {
|
|
270
|
+
const num = parseInt(d)
|
|
271
|
+
const jid = num === 99 ? `${user}:99@hosted`
|
|
272
|
+
: num === 0 ? `${user}@s.whatsapp.net`
|
|
273
|
+
: `${user}:${num}@s.whatsapp.net`
|
|
274
|
+
return { cacheKey: `${user}.${d}`, jid, addr: jidToAddr(jid).toString() }
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
if (!candidates.length) return { migrated: 0, skipped: 0, total: 0 }
|
|
278
|
+
|
|
279
|
+
// Bulk-fetch only the session keys we actually need
|
|
280
|
+
const addrs = candidates.map(c => c.addr)
|
|
281
|
+
const existing = await parsedKeys.get('session', addrs)
|
|
282
|
+
|
|
283
|
+
const toMigrate = candidates.filter(c => {
|
|
284
|
+
const raw = toU8(existing[c.addr])
|
|
285
|
+
return raw && !isOldJson(raw)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
if (!toMigrate.length) return { migrated: 0, skipped: candidates.length, total: candidates.length }
|
|
289
|
+
|
|
290
|
+
return txn(async () => {
|
|
291
|
+
// Re-fetch inside txn for freshness
|
|
292
|
+
const fresh = await parsedKeys.get('session', toMigrate.map(c => c.addr))
|
|
293
|
+
const updates = {}
|
|
294
|
+
let migrated = 0
|
|
295
|
+
|
|
296
|
+
for (const { jid, addr, cacheKey } of toMigrate) {
|
|
297
|
+
const raw = toU8(fresh[addr])
|
|
298
|
+
if (!raw || isOldJson(raw)) continue
|
|
299
|
+
let sess
|
|
300
|
+
try { sess = SessionRecord.deserialize(raw) } catch { continue }
|
|
301
|
+
if (!sess.haveOpenSession()) continue
|
|
302
|
+
const lidAddr = jidToAddr(transferDevice(jid, toJid)).toString()
|
|
303
|
+
updates[lidAddr] = sess.serialize()
|
|
304
|
+
updates[addr] = null
|
|
305
|
+
lidCache.delete(addr)
|
|
306
|
+
sessionReadCache.delete(addr)
|
|
307
|
+
migrated++
|
|
308
|
+
migratedCache.set(cacheKey, true)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (migrated > 0) {
|
|
312
|
+
await parsedKeys.set({ session: updates })
|
|
313
|
+
}
|
|
314
|
+
return { migrated, skipped: toMigrate.length - migrated, total: candidates.length }
|
|
315
|
+
}, `migrate-${jidDecode(toJid)?.user}`)
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
async warmLIDCache(mappings) {
|
|
319
|
+
for (const { pn, lid } of mappings) {
|
|
320
|
+
try {
|
|
321
|
+
const pnAddr = jidToAddr(pn).toString()
|
|
322
|
+
const lidAddr = jidToAddr(lid).toString()
|
|
323
|
+
lidCache.set(pnAddr, lidAddr)
|
|
324
|
+
} catch { }
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
async preLoadKeys(jids) {
|
|
329
|
+
if (!jids?.length) return
|
|
330
|
+
try {
|
|
331
|
+
const addrs = await Promise.all(
|
|
332
|
+
jids.map(jid => resolveLID(jidToAddr(jid).toString()))
|
|
333
|
+
)
|
|
334
|
+
const [sessions, identityKeys] = await Promise.all([
|
|
335
|
+
parsedKeys.get('session', addrs),
|
|
336
|
+
parsedKeys.get('identity-key', addrs)
|
|
337
|
+
])
|
|
338
|
+
for (const addr of addrs) {
|
|
339
|
+
if (sessions[addr] !== undefined) {
|
|
340
|
+
sessionReadCache.set(addr, sessions[addr] ? toU8(sessions[addr]) : null)
|
|
341
|
+
}
|
|
342
|
+
if (identityKeys[addr] !== undefined) {
|
|
343
|
+
identityReadCache.set(addr, identityKeys[addr] ? toU8(identityKeys[addr]) : null)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
logger?.warn?.({ err: error?.message || error }, '[Signal] preLoadKeys failed')
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
close() {
|
|
352
|
+
migratedCache.clear()
|
|
353
|
+
sessionReadCache.clear()
|
|
354
|
+
identityReadCache.clear()
|
|
355
|
+
lidCache.clear()
|
|
356
|
+
lidMapping.close?.()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ─── Storage Adapter ──────────────────────────────────────────────────────────
|
|
362
|
+
// Implements the SignalStorage interface consumed by whatsapp-rust-bridge WASM.
|
|
363
|
+
//
|
|
364
|
+
// One key per record — exactly as original Baileys does:
|
|
365
|
+
// session: addr string → Uint8Array (SessionRecord.serialize())
|
|
366
|
+
// identity-key: addr string → Uint8Array (33-byte identity key)
|
|
367
|
+
// pre-key: id string → { public, private }
|
|
368
|
+
// sender-key: keyId string → Buffer
|
|
369
|
+
|
|
370
|
+
function signalStorage({ creds, keys }, lidMapping, logger, lidCache, sessionReadCache, identityReadCache, resolveLID) {
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
// ── Sessions ─────────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
loadSession: async (id) => {
|
|
376
|
+
try {
|
|
377
|
+
const addr = await resolveLID(id)
|
|
378
|
+
const cached = sessionReadCache.get(addr)
|
|
379
|
+
if (cached !== undefined) return cached === null ? null : toU8(cached)
|
|
380
|
+
const { [addr]: raw } = await keys.get('session', [addr])
|
|
381
|
+
if (!raw || isOldJson(raw)) {
|
|
382
|
+
sessionReadCache.set(addr, null)
|
|
383
|
+
return null
|
|
384
|
+
}
|
|
385
|
+
const buf = toU8(raw)
|
|
386
|
+
sessionReadCache.set(addr, buf)
|
|
387
|
+
return buf
|
|
388
|
+
} catch (e) {
|
|
389
|
+
logger?.error?.(`[Signal] loadSession error for ${id}: ${e.message}`)
|
|
390
|
+
return null
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
storeSession: async (id, record) => {
|
|
395
|
+
const addr = await resolveLID(id)
|
|
396
|
+
const serialized = record.serialize()
|
|
397
|
+
sessionReadCache.set(addr, toU8(serialized))
|
|
398
|
+
await keys.set({ session: { [addr]: serialized } })
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
// ── Identity keys ─────────────────────────────────────────────────────────
|
|
402
|
+
|
|
403
|
+
isTrustedIdentity: async (id, identityKey) => {
|
|
404
|
+
try {
|
|
405
|
+
const addr = await resolveLID(id)
|
|
406
|
+
let existing = identityReadCache.get(addr)
|
|
407
|
+
if (existing === undefined) {
|
|
408
|
+
const { [addr]: raw } = await keys.get('identity-key', [addr])
|
|
409
|
+
existing = toU8(raw) ?? null
|
|
410
|
+
identityReadCache.set(addr, existing)
|
|
411
|
+
}
|
|
412
|
+
if (!existing) return true
|
|
413
|
+
const a = existing
|
|
414
|
+
const b = identityKey instanceof Uint8Array ? identityKey : toU8(identityKey)
|
|
415
|
+
return !!bufEqual(a, b)
|
|
416
|
+
} catch {
|
|
417
|
+
return true
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
loadIdentityKey: async (id) => {
|
|
422
|
+
const addr = await resolveLID(id)
|
|
423
|
+
let key = identityReadCache.get(addr)
|
|
424
|
+
if (key === undefined) {
|
|
425
|
+
const { [addr]: raw } = await keys.get('identity-key', [addr])
|
|
426
|
+
key = toU8(raw) ?? null
|
|
427
|
+
identityReadCache.set(addr, key)
|
|
428
|
+
}
|
|
429
|
+
return key === null ? undefined : key
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
saveIdentity: async (id, identityKey) => {
|
|
433
|
+
const addr = await resolveLID(id)
|
|
434
|
+
let existing = identityReadCache.get(addr)
|
|
435
|
+
if (existing === undefined) {
|
|
436
|
+
const { [addr]: raw } = await keys.get('identity-key', [addr])
|
|
437
|
+
existing = toU8(raw) ?? null
|
|
438
|
+
identityReadCache.set(addr, existing)
|
|
439
|
+
}
|
|
440
|
+
const a = existing
|
|
441
|
+
const b = identityKey instanceof Uint8Array ? identityKey : toU8(identityKey)
|
|
442
|
+
if (a && !bufEqual(a, b)) {
|
|
443
|
+
// Identity changed — wipe session and update key atomically
|
|
444
|
+
sessionReadCache.delete(addr)
|
|
445
|
+
identityReadCache.set(addr, b)
|
|
446
|
+
lidCache.delete(id)
|
|
447
|
+
await keys.set({
|
|
448
|
+
session: { [addr]: null },
|
|
449
|
+
'identity-key': { [addr]: b }
|
|
450
|
+
})
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
if (!a) {
|
|
454
|
+
identityReadCache.set(addr, b)
|
|
455
|
+
await keys.set({ 'identity-key': { [addr]: b } })
|
|
456
|
+
return true
|
|
457
|
+
}
|
|
458
|
+
return false
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
wipeSession: async (addr) => {
|
|
462
|
+
sessionReadCache.delete(addr)
|
|
463
|
+
await keys.set({ session: { [addr]: null } })
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
// ── PreKeys ───────────────────────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
loadPreKey: async (id) => {
|
|
469
|
+
const { [id.toString()]: key } = await keys.get('pre-key', [id.toString()])
|
|
470
|
+
if (!key) return null
|
|
471
|
+
return {
|
|
472
|
+
pubKey: new Uint8Array(Buffer.from(key.public)),
|
|
473
|
+
privKey: new Uint8Array(Buffer.from(key.private))
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
removePreKey: (id) => keys.set({ 'pre-key': { [id]: null } }),
|
|
478
|
+
|
|
479
|
+
loadSignedPreKey: (id) => {
|
|
480
|
+
const key = creds.signedPreKey
|
|
481
|
+
if (key.keyId !== id) {
|
|
482
|
+
logger?.warn?.({ requested: id, current: key.keyId }, '[Signal] loadSignedPreKey id mismatch')
|
|
483
|
+
return null
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
keyId: key.keyId,
|
|
487
|
+
keyPair: {
|
|
488
|
+
pubKey: new Uint8Array(Buffer.from(key.keyPair.public)),
|
|
489
|
+
privKey: new Uint8Array(Buffer.from(key.keyPair.private))
|
|
490
|
+
},
|
|
491
|
+
signature: new Uint8Array(Buffer.from(key.signature))
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
// ── Sender keys ───────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
loadSenderKey: async (keyId) => {
|
|
498
|
+
try {
|
|
499
|
+
const id = keyId.toString()
|
|
500
|
+
const { [id]: key } = await keys.get('sender-key', [id])
|
|
501
|
+
return toU8(key) ?? null
|
|
502
|
+
} catch (e) {
|
|
503
|
+
logger?.error?.(`[Signal] loadSenderKey error: ${e.message}`)
|
|
504
|
+
return null
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
storeSenderKey: async (keyId, record) => {
|
|
509
|
+
await keys.set({ 'sender-key': { [keyId.toString()]: Buffer.from(record) } })
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
// ── Own identity ──────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
getOurRegistrationId: () => creds.registrationId,
|
|
515
|
+
|
|
516
|
+
getOurIdentity: () => ({
|
|
517
|
+
pubKey: new Uint8Array(generateSignalPubKey(Buffer.from(creds.signedIdentityKey.public))),
|
|
518
|
+
privKey: new Uint8Array(Buffer.from(creds.signedIdentityKey.private))
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export default makeLibSignalRepository
|