@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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3203 -2
  3. package/WAProto/WAProtoCompile.js +291 -0
  4. package/WAProto/fetcher.js +69 -0
  5. package/WAProto/index.js +88 -0
  6. package/WAProto/parser.js +313 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Builders/AIRich.js +590 -0
  9. package/lib/Builders/base.js +130 -0
  10. package/lib/Builders/builders.js +448 -0
  11. package/lib/Builders/index.js +3 -0
  12. package/lib/Defaults/index.js +225 -0
  13. package/lib/Defaults/phonenumber-mcc.json +223 -0
  14. package/lib/Signal/index.js +2 -0
  15. package/lib/Signal/libsignal.js +523 -0
  16. package/lib/Signal/lid-mapping.js +283 -0
  17. package/lib/Socket/Client/index.js +2 -0
  18. package/lib/Socket/Client/types.js +10 -0
  19. package/lib/Socket/Client/websocket.js +84 -0
  20. package/lib/Socket/business.js +152 -0
  21. package/lib/Socket/chats.js +855 -0
  22. package/lib/Socket/communities.js +147 -0
  23. package/lib/Socket/groups.js +294 -0
  24. package/lib/Socket/index.js +15 -0
  25. package/lib/Socket/messages-recv.js +904 -0
  26. package/lib/Socket/messages-send.js +693 -0
  27. package/lib/Socket/mex.js +34 -0
  28. package/lib/Socket/newsletter.js +752 -0
  29. package/lib/Socket/nexus-handler.js +55 -0
  30. package/lib/Socket/registration.js +155 -0
  31. package/lib/Socket/socket.js +798 -0
  32. package/lib/Socket/spam-report.js +65 -0
  33. package/lib/Socket/username.js +82 -0
  34. package/lib/Store/index.js +6 -0
  35. package/lib/Store/make-cache-manager-store.js +81 -0
  36. package/lib/Store/make-in-memory-store.js +425 -0
  37. package/lib/Store/make-ordered-dictionary.js +82 -0
  38. package/lib/Store/object-repository.js +31 -0
  39. package/lib/Store/reporting-info-store.js +38 -0
  40. package/lib/Types/Auth.js +2 -0
  41. package/lib/Types/Bussines.js +2 -0
  42. package/lib/Types/Call.js +2 -0
  43. package/lib/Types/Chat.js +8 -0
  44. package/lib/Types/Contact.js +2 -0
  45. package/lib/Types/Events.js +2 -0
  46. package/lib/Types/GroupMetadata.js +2 -0
  47. package/lib/Types/Label.js +25 -0
  48. package/lib/Types/LabelAssociation.js +7 -0
  49. package/lib/Types/Message.js +15 -0
  50. package/lib/Types/Newsletter.js +100 -0
  51. package/lib/Types/Product.js +2 -0
  52. package/lib/Types/Signal.js +2 -0
  53. package/lib/Types/Socket.js +14 -0
  54. package/lib/Types/State.js +26 -0
  55. package/lib/Types/USync.js +2 -0
  56. package/lib/Types/index.js +30 -0
  57. package/lib/Utils/auth-utils.js +142 -0
  58. package/lib/Utils/baileys-event-stream.js +33 -0
  59. package/lib/Utils/bot-capabilities.js +40 -0
  60. package/lib/Utils/bot-planning-replay.js +44 -0
  61. package/lib/Utils/browser-utils.js +26 -0
  62. package/lib/Utils/business.js +75 -0
  63. package/lib/Utils/chat-utils.js +316 -0
  64. package/lib/Utils/companion-reg-client-utils.js +20 -0
  65. package/lib/Utils/consumer-application.js +38 -0
  66. package/lib/Utils/crypto.js +52 -0
  67. package/lib/Utils/decode-wa-message.js +197 -0
  68. package/lib/Utils/event-buffer.js +273 -0
  69. package/lib/Utils/extras.js +186 -0
  70. package/lib/Utils/generics.js +195 -0
  71. package/lib/Utils/history.js +51 -0
  72. package/lib/Utils/identity-change-handler.js +19 -0
  73. package/lib/Utils/index.js +39 -0
  74. package/lib/Utils/key-store.js +3 -0
  75. package/lib/Utils/link-preview.js +51 -0
  76. package/lib/Utils/logger.js +2 -0
  77. package/lib/Utils/lt-hash.js +3 -0
  78. package/lib/Utils/make-mutex.js +16 -0
  79. package/lib/Utils/message-composer.js +59 -0
  80. package/lib/Utils/message-retry-manager.js +48 -0
  81. package/lib/Utils/messages-media.js +520 -0
  82. package/lib/Utils/messages.js +742 -0
  83. package/lib/Utils/meta-ai-msmsg.js +122 -0
  84. package/lib/Utils/meta-compositing.js +75 -0
  85. package/lib/Utils/modern-messages.js +235 -0
  86. package/lib/Utils/native-flow.js +55 -0
  87. package/lib/Utils/noise-handler.js +161 -0
  88. package/lib/Utils/pre-key-manager.js +76 -0
  89. package/lib/Utils/process-message.js +399 -0
  90. package/lib/Utils/reporting-utils.js +171 -0
  91. package/lib/Utils/signal.js +167 -0
  92. package/lib/Utils/spam-report-utils.js +142 -0
  93. package/lib/Utils/sync-action-utils.js +33 -0
  94. package/lib/Utils/tc-token-utils.js +102 -0
  95. package/lib/Utils/use-multi-file-auth-state.js +307 -0
  96. package/lib/Utils/validate-connection.js +181 -0
  97. package/lib/WABinary/constants.js +1321 -0
  98. package/lib/WABinary/decode.js +152 -0
  99. package/lib/WABinary/encode.js +140 -0
  100. package/lib/WABinary/generic-utils.js +179 -0
  101. package/lib/WABinary/index.js +6 -0
  102. package/lib/WABinary/jid-utils.js +235 -0
  103. package/lib/WABinary/types.js +4 -0
  104. package/lib/WAM/BinaryInfo.js +10 -0
  105. package/lib/WAM/constants.js +22853 -0
  106. package/lib/WAM/encode.js +150 -0
  107. package/lib/WAM/index.js +3 -0
  108. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
  109. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
  110. package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
  111. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
  112. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
  113. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
  114. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
  115. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
  116. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
  117. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
  118. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
  119. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
  120. package/lib/WAUSync/Protocols/index.js +12 -0
  121. package/lib/WAUSync/USyncQuery.js +100 -0
  122. package/lib/WAUSync/USyncUser.js +20 -0
  123. package/lib/WAUSync/index.js +3 -0
  124. package/lib/index.js +68 -0
  125. 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