@nexustechpro/baileys 2.0.2 → 2.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/LICENSE +1 -1
- package/README.md +924 -1299
- package/lib/Defaults/baileys-version.json +6 -2
- package/lib/Defaults/index.js +172 -172
- package/lib/Signal/libsignal.js +380 -292
- package/lib/Signal/lid-mapping.js +264 -171
- package/lib/Socket/Client/index.js +2 -2
- package/lib/Socket/Client/types.js +10 -10
- package/lib/Socket/Client/websocket.js +45 -310
- package/lib/Socket/business.js +375 -375
- package/lib/Socket/chats.js +909 -963
- package/lib/Socket/communities.js +430 -430
- package/lib/Socket/groups.js +342 -342
- package/lib/Socket/index.js +22 -22
- package/lib/Socket/messages-recv.js +777 -743
- package/lib/Socket/messages-send.js +295 -305
- package/lib/Socket/mex.js +50 -50
- package/lib/Socket/newsletter.js +148 -148
- package/lib/Socket/nexus-handler.js +75 -261
- package/lib/Socket/socket.js +709 -1201
- package/lib/Store/index.js +5 -5
- package/lib/Store/make-cache-manager-store.js +81 -81
- package/lib/Store/make-in-memory-store.js +416 -416
- package/lib/Store/make-ordered-dictionary.js +81 -81
- package/lib/Store/object-repository.js +30 -30
- package/lib/Types/Auth.js +1 -1
- package/lib/Types/Bussines.js +1 -1
- package/lib/Types/Call.js +1 -1
- package/lib/Types/Chat.js +7 -7
- package/lib/Types/Contact.js +1 -1
- package/lib/Types/Events.js +1 -1
- package/lib/Types/GroupMetadata.js +1 -1
- package/lib/Types/Label.js +24 -24
- package/lib/Types/LabelAssociation.js +6 -6
- package/lib/Types/Message.js +10 -10
- package/lib/Types/Newsletter.js +28 -28
- package/lib/Types/Product.js +1 -1
- package/lib/Types/Signal.js +1 -1
- package/lib/Types/Socket.js +2 -2
- package/lib/Types/State.js +12 -12
- package/lib/Types/USync.js +1 -1
- package/lib/Types/index.js +25 -25
- package/lib/Utils/auth-utils.js +264 -256
- package/lib/Utils/baileys-event-stream.js +55 -55
- package/lib/Utils/browser-utils.js +27 -27
- package/lib/Utils/business.js +228 -230
- package/lib/Utils/chat-utils.js +694 -764
- package/lib/Utils/crypto.js +109 -135
- package/lib/Utils/decode-wa-message.js +310 -314
- package/lib/Utils/event-buffer.js +547 -547
- package/lib/Utils/generics.js +297 -297
- package/lib/Utils/history.js +91 -83
- package/lib/Utils/index.js +21 -20
- package/lib/Utils/key-store.js +17 -0
- package/lib/Utils/link-preview.js +97 -98
- package/lib/Utils/logger.js +2 -2
- package/lib/Utils/lt-hash.js +47 -47
- package/lib/Utils/make-mutex.js +39 -39
- package/lib/Utils/message-retry-manager.js +148 -148
- package/lib/Utils/messages-media.js +534 -534
- package/lib/Utils/messages.js +705 -705
- package/lib/Utils/noise-handler.js +255 -255
- package/lib/Utils/pre-key-manager.js +105 -105
- package/lib/Utils/process-message.js +412 -412
- package/lib/Utils/signal.js +160 -158
- package/lib/Utils/use-multi-file-auth-state.js +120 -120
- package/lib/Utils/validate-connection.js +194 -194
- package/lib/WABinary/constants.js +1300 -1300
- package/lib/WABinary/decode.js +237 -237
- package/lib/WABinary/encode.js +232 -232
- package/lib/WABinary/generic-utils.js +252 -211
- package/lib/WABinary/index.js +5 -5
- package/lib/WABinary/jid-utils.js +279 -95
- package/lib/WABinary/types.js +1 -1
- package/lib/WAM/BinaryInfo.js +9 -9
- package/lib/WAM/constants.js +22852 -22852
- package/lib/WAM/encode.js +149 -149
- package/lib/WAM/index.js +3 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
- package/lib/WAUSync/Protocols/index.js +4 -4
- package/lib/WAUSync/USyncQuery.js +93 -93
- package/lib/WAUSync/USyncUser.js +22 -22
- package/lib/WAUSync/index.js +3 -3
- package/lib/index.js +66 -66
- package/package.json +171 -144
- package/lib/Signal/Group/ciphertext-message.js +0 -12
- package/lib/Signal/Group/group-session-builder.js +0 -30
- package/lib/Signal/Group/group_cipher.js +0 -100
- package/lib/Signal/Group/index.js +0 -12
- package/lib/Signal/Group/keyhelper.js +0 -18
- package/lib/Signal/Group/sender-chain-key.js +0 -26
- package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
- package/lib/Signal/Group/sender-key-message.js +0 -66
- package/lib/Signal/Group/sender-key-name.js +0 -48
- package/lib/Signal/Group/sender-key-record.js +0 -41
- package/lib/Signal/Group/sender-key-state.js +0 -84
- package/lib/Signal/Group/sender-message-key.js +0 -26
package/lib/Signal/libsignal.js
CHANGED
|
@@ -1,293 +1,381 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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 { LRUCache } from 'lru-cache'
|
|
12
|
+
import { generateSignalPubKey, migrateIndexKey } from '../Utils/index.js'
|
|
13
|
+
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, transferDevice, WAJIDDomains } from '../WABinary/index.js'
|
|
14
|
+
import { LIDMappingStore } from './lid-mapping.js'
|
|
15
|
+
|
|
16
|
+
// ─── Address Helpers ──────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const jidToAddr = (jid) => {
|
|
19
|
+
const { user, device, server, domainType } = jidDecode(jid)
|
|
20
|
+
if (!user) throw new Error(`Invalid JID: "${jid}"`)
|
|
21
|
+
if (device === 99 && server !== 'hosted' && server !== 'hosted.lid') throw new Error('Invalid device 99: ' + jid)
|
|
22
|
+
const signalUser = domainType !== WAJIDDomains.WHATSAPP ? `${user}_${domainType}` : user
|
|
23
|
+
return new ProtocolAddress(signalUser, device || 0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const jidToSenderKeyName = (group, user) => new SenderKeyName(group, jidToAddr(user))
|
|
27
|
+
|
|
28
|
+
const v2Key = (addr) => `${addr}:v2` // v2 slot holds the actual serialized SessionRecord bytes
|
|
29
|
+
|
|
30
|
+
// ─── Identity Extraction ──────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function extractIdentityFromPkmsg(ciphertext) {
|
|
33
|
+
try {
|
|
34
|
+
if (!ciphertext || ciphertext.length < 2) return undefined
|
|
35
|
+
if ((ciphertext[0] & 0xf) !== 3) return undefined
|
|
36
|
+
const buf = ciphertext.slice(1)
|
|
37
|
+
let i = 0
|
|
38
|
+
while (i < buf.length) {
|
|
39
|
+
const tag = buf[i++]
|
|
40
|
+
const fieldNum = tag >> 3
|
|
41
|
+
const wireType = tag & 0x7
|
|
42
|
+
if (wireType === 2) {
|
|
43
|
+
let len = 0, shift = 0
|
|
44
|
+
while (i < buf.length) { const b = buf[i++]; len |= (b & 0x7f) << shift; if (!(b & 0x80)) break; shift += 7 }
|
|
45
|
+
if (fieldNum === 4 && len === 33) return new Uint8Array(buf.slice(i, i + len))
|
|
46
|
+
i += len
|
|
47
|
+
} else if (wireType === 0) {
|
|
48
|
+
while (i < buf.length && buf[i++] & 0x80) { }
|
|
49
|
+
} else if (wireType === 5) { i += 4 }
|
|
50
|
+
else if (wireType === 1) { i += 8 }
|
|
51
|
+
else break
|
|
52
|
+
}
|
|
53
|
+
} catch { }
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Buffer Utils ─────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
// universal deserializer — handles all shapes written by any previous version
|
|
60
|
+
const toBuffer = (raw) => {
|
|
61
|
+
if (!raw) return null
|
|
62
|
+
if (raw instanceof Uint8Array) return raw
|
|
63
|
+
if (Buffer.isBuffer(raw)) return raw
|
|
64
|
+
if (raw?.type === 'Buffer' && Array.isArray(raw?.data)) return Buffer.from(raw.data)
|
|
65
|
+
if (Array.isArray(raw)) return Buffer.from(raw)
|
|
66
|
+
if (typeof raw === 'string') return Buffer.from(raw, 'base64')
|
|
67
|
+
if (raw?.data) return Buffer.from(raw.data)
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// detects old libsignal JS JSON format — not deserializable by whatsapp-rust-bridge
|
|
72
|
+
const isOldJson = (raw) => {
|
|
73
|
+
if (!raw || raw instanceof Uint8Array || Buffer.isBuffer(raw)) return false
|
|
74
|
+
if (typeof raw === 'object') return 'version' in raw || '_sessions' in raw
|
|
75
|
+
if (typeof raw === 'string') { try { const p = JSON.parse(raw); return 'version' in p || '_sessions' in p } catch { return false } }
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Main Factory ─────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
82
|
+
const lidMapping = new LIDMappingStore(auth.keys, logger, pnToLIDFunc)
|
|
83
|
+
const storage = signalStorage(auth, lidMapping, logger)
|
|
84
|
+
const parsedKeys = auth.keys
|
|
85
|
+
const migratedCache = new LRUCache({ ttl: 7 * 24 * 60 * 60 * 1000, ttlAutopurge: true, updateAgeOnGet: true })
|
|
86
|
+
const txn = (fn, key) => parsedKeys.transaction(fn, key)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
decryptGroupMessage({ group, authorJid, msg }) {
|
|
90
|
+
return txn(() => new GroupCipher(storage, group, jidToAddr(authorJid)).decrypt(msg), group)
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async processSenderKeyDistributionMessage({ item, authorJid }) {
|
|
94
|
+
if (!item.groupId) throw new Error('Group ID required')
|
|
95
|
+
const senderName = jidToSenderKeyName(item.groupId, authorJid)
|
|
96
|
+
const senderMsg = SenderKeyDistributionMessage.deserialize(item.axolotlSenderKeyDistributionMessage)
|
|
97
|
+
return txn(() => new GroupSessionBuilder(storage).process(senderName, senderMsg), item.groupId)
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async decryptMessage({ jid, type, ciphertext }) {
|
|
101
|
+
const addr = jidToAddr(jid)
|
|
102
|
+
const cipher = new SessionCipher(storage, addr)
|
|
103
|
+
if (type === 'pkmsg') {
|
|
104
|
+
const identityKey = extractIdentityFromPkmsg(ciphertext)
|
|
105
|
+
if (identityKey) {
|
|
106
|
+
const changed = await storage.saveIdentity(addr.toString(), identityKey)
|
|
107
|
+
if (changed) logger?.info?.({ jid }, '[Signal] Identity key changed, session cleared')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const doDecrypt = (c, t) => {
|
|
111
|
+
if (t === 'pkmsg') return c.decryptPreKeyWhisperMessage(ciphertext)
|
|
112
|
+
if (t === 'msg') return c.decryptWhisperMessage(ciphertext)
|
|
113
|
+
throw new Error(`Unknown type: ${t}`)
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return await txn(() => doDecrypt(cipher, type), jid)
|
|
117
|
+
} catch (e) {
|
|
118
|
+
if (e?.message?.includes('DuplicatedMessage')) { logger?.debug?.({ jid }, '[Signal] Duplicate message ignored — offline replay'); return null }
|
|
119
|
+
throw e
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
encryptMessage({ jid, data }) {
|
|
124
|
+
return txn(async () => {
|
|
125
|
+
const { type: sigType, body } = await new SessionCipher(storage, jidToAddr(jid)).encrypt(data)
|
|
126
|
+
return { type: sigType === 3 ? 'pkmsg' : 'msg', ciphertext: Buffer.from(body) }
|
|
127
|
+
}, jid)
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
encryptGroupMessage({ group, meId, data }) {
|
|
131
|
+
return txn(async () => {
|
|
132
|
+
const senderName = jidToSenderKeyName(group, meId)
|
|
133
|
+
const senderKeyDistributionMessage = await new GroupSessionBuilder(storage).create(senderName)
|
|
134
|
+
return { ciphertext: await new GroupCipher(storage, group, jidToAddr(meId)).encrypt(data), senderKeyDistributionMessage: senderKeyDistributionMessage.serialize() }
|
|
135
|
+
}, group)
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
injectE2ESession({ jid, session }) {
|
|
139
|
+
return txn(() => new SessionBuilder(storage, jidToAddr(jid)).processPreKeyBundle(session), jid)
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
jidToSignalProtocolAddress: jid => jidToAddr(jid).toString(),
|
|
143
|
+
|
|
144
|
+
lidMapping,
|
|
145
|
+
|
|
146
|
+
async validateSession(jid) {
|
|
147
|
+
try {
|
|
148
|
+
const addr = jidToAddr(jid).toString()
|
|
149
|
+
const batch = await migrateIndexKey(parsedKeys, 'session')
|
|
150
|
+
const raw = toBuffer(batch[v2Key(addr)]) || toBuffer(batch[addr]) // v2 slot first, fall back to plain
|
|
151
|
+
if (!raw || isOldJson(raw)) return { exists: false, reason: 'no session' }
|
|
152
|
+
const sess = SessionRecord.deserialize(raw)
|
|
153
|
+
if (!sess.haveOpenSession()) return { exists: false, reason: 'no open session' }
|
|
154
|
+
return { exists: true }
|
|
155
|
+
} catch { return { exists: false, reason: 'error' } }
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
async deleteSession(jids) {
|
|
159
|
+
if (!jids.length) return
|
|
160
|
+
return txn(async () => {
|
|
161
|
+
const batch = await migrateIndexKey(parsedKeys, 'session')
|
|
162
|
+
for (const jid of jids) { const addr = jidToAddr(jid).toString(); delete batch[addr]; delete batch[v2Key(addr)] }
|
|
163
|
+
await parsedKeys.set({ session: { 'index': batch } })
|
|
164
|
+
}, `del-${jids.length}`)
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async migrateSession(fromJid, toJid) {
|
|
168
|
+
if (!fromJid || (!isLidUser(toJid) && !isHostedLidUser(toJid))) return { migrated: 0, skipped: 0, total: 0 }
|
|
169
|
+
if (!isPnUser(fromJid) && !isHostedPnUser(fromJid)) return { migrated: 0, skipped: 0, total: 1 }
|
|
170
|
+
const { user } = jidDecode(fromJid)
|
|
171
|
+
const deviceListBatch = await migrateIndexKey(parsedKeys, 'device-list')
|
|
172
|
+
const userDevices = deviceListBatch[user]
|
|
173
|
+
if (!userDevices?.length) return { migrated: 0, skipped: 0, total: 0 }
|
|
174
|
+
const fromDeviceStr = jidDecode(fromJid).device?.toString() || '0'
|
|
175
|
+
if (!userDevices.includes(fromDeviceStr)) userDevices.push(fromDeviceStr)
|
|
176
|
+
const uncachedDevices = userDevices.filter(d => !migratedCache.has(`${user}.${d}`))
|
|
177
|
+
const sessionBatch = await migrateIndexKey(parsedKeys, 'session')
|
|
178
|
+
const deviceJids = uncachedDevices.map(d => {
|
|
179
|
+
const num = parseInt(d)
|
|
180
|
+
return { addr: `${user}.${d || 0}`, jid: num === 99 ? `${user}:99@hosted` : num === 0 ? `${user}@s.whatsapp.net` : `${user}:${num}@s.whatsapp.net` }
|
|
181
|
+
}).filter(({ addr }) => sessionBatch[v2Key(addr)] || sessionBatch[addr])
|
|
182
|
+
return txn(async () => {
|
|
183
|
+
const updated = { ...sessionBatch }
|
|
184
|
+
let migrated = 0
|
|
185
|
+
for (const { jid } of deviceJids) {
|
|
186
|
+
const pnAddr = jidToAddr(jid).toString()
|
|
187
|
+
const lidAddr = jidToAddr(transferDevice(jid, toJid)).toString()
|
|
188
|
+
const raw = toBuffer(updated[v2Key(pnAddr)]) || toBuffer(updated[pnAddr]) // prefer v2 slot
|
|
189
|
+
if (!raw || isOldJson(raw)) continue
|
|
190
|
+
const sess = SessionRecord.deserialize(raw)
|
|
191
|
+
if (!sess.haveOpenSession()) continue
|
|
192
|
+
updated[v2Key(lidAddr)] = sess.serialize()
|
|
193
|
+
updated[lidAddr] = { version: 'v1', _sessions: {} } // plain slot marker for compat
|
|
194
|
+
delete updated[v2Key(pnAddr)]
|
|
195
|
+
delete updated[pnAddr]
|
|
196
|
+
migrated++
|
|
197
|
+
migratedCache.set(`${user}.${jidDecode(jid).device || 0}`, true)
|
|
198
|
+
}
|
|
199
|
+
if (migrated > 0) await parsedKeys.set({ session: { 'index': updated } })
|
|
200
|
+
return { migrated, skipped: deviceJids.length - migrated, total: deviceJids.length }
|
|
201
|
+
}, `migrate-${deviceJids.length}`)
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Batch-migrate all PN-addressed sessions to their LID equivalents.
|
|
205
|
+
// Called once on CB:success before offline messages are processed — one read, one remap, one write.
|
|
206
|
+
async migrateAllPNSessionsToLID() {
|
|
207
|
+
return txn(async () => {
|
|
208
|
+
const sessionBatch = await migrateIndexKey(parsedKeys, 'session')
|
|
209
|
+
const sessionKeys = Object.keys(sessionBatch)
|
|
210
|
+
if (!sessionKeys.length) return 0
|
|
211
|
+
|
|
212
|
+
// collect plain (non-v2) PN-domain keys only — v2 slots are handled via their plain counterpart
|
|
213
|
+
const pnAddrs = sessionKeys.filter(addr => {
|
|
214
|
+
if (addr.endsWith(':v2')) return false
|
|
215
|
+
if (!addr.includes('.')) return false
|
|
216
|
+
const [deviceId] = addr.split('.')
|
|
217
|
+
const [, dt] = deviceId.split('_')
|
|
218
|
+
const domainType = parseInt(dt || '0')
|
|
219
|
+
return domainType === WAJIDDomains.WHATSAPP || domainType === WAJIDDomains.HOSTED
|
|
220
|
+
})
|
|
221
|
+
if (!pnAddrs.length) return 0
|
|
222
|
+
|
|
223
|
+
// batch-fetch LID mappings directly from key store — same format storeLIDPNMappings writes
|
|
224
|
+
const pnUserSet = new Set(pnAddrs.map(addr => addr.split('.')[0].split('_')[0]))
|
|
225
|
+
const stored = await parsedKeys.get('lid-mapping', [...pnUserSet])
|
|
226
|
+
|
|
227
|
+
const pnToLidUserMap = new Map()
|
|
228
|
+
for (const pnUser of pnUserSet) {
|
|
229
|
+
const lidUser = stored[pnUser]
|
|
230
|
+
if (lidUser && typeof lidUser === 'string') pnToLidUserMap.set(pnUser, lidUser)
|
|
231
|
+
}
|
|
232
|
+
if (!pnToLidUserMap.size) return 0
|
|
233
|
+
|
|
234
|
+
let migrated = 0
|
|
235
|
+
const updated = { ...sessionBatch }
|
|
236
|
+
|
|
237
|
+
for (const addr of pnAddrs) {
|
|
238
|
+
const [deviceId, device] = addr.split('.')
|
|
239
|
+
const [user, dt] = deviceId.split('_')
|
|
240
|
+
const domainType = parseInt(dt || '0')
|
|
241
|
+
const lidUser = pnToLidUserMap.get(user)
|
|
242
|
+
if (!lidUser) continue
|
|
243
|
+
const lidDomainType = domainType === WAJIDDomains.HOSTED ? WAJIDDomains.HOSTED_LID : WAJIDDomains.LID
|
|
244
|
+
const lidAddr = `${lidUser}_${lidDomainType}.${device}`
|
|
245
|
+
if (updated[v2Key(lidAddr)]) continue // LID session already exists, skip
|
|
246
|
+
const raw = toBuffer(updated[v2Key(addr)]) || toBuffer(updated[addr]) // prefer v2 slot
|
|
247
|
+
if (!raw || isOldJson(raw)) continue
|
|
248
|
+
const sess = SessionRecord.deserialize(raw)
|
|
249
|
+
if (!sess.haveOpenSession()) continue
|
|
250
|
+
updated[v2Key(lidAddr)] = sess.serialize()
|
|
251
|
+
updated[lidAddr] = { version: 'v1', _sessions: {} } // plain slot marker for compat
|
|
252
|
+
delete updated[v2Key(addr)]
|
|
253
|
+
delete updated[addr]
|
|
254
|
+
migrated++
|
|
255
|
+
migratedCache.set(`${user}.${device}`, true)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (migrated > 0) {
|
|
259
|
+
await parsedKeys.set({ session: { 'index': updated } })
|
|
260
|
+
logger?.info?.({ migrated, totalPN: pnAddrs.length, mappingsFound: pnToLidUserMap.size }, '[Signal] Batch-migrated PN sessions to LID on connect')
|
|
261
|
+
}
|
|
262
|
+
return migrated
|
|
263
|
+
}, 'migrate-all-pn-to-lid')
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Storage Adapter ──────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function signalStorage({ creds, keys }, lidMapping, logger) {
|
|
271
|
+
const lidCache = new LRUCache({ max: 500, ttl: 5 * 60 * 1000 }) // cache PN→LID resolutions for 5 min
|
|
272
|
+
|
|
273
|
+
const resolveLID = async (id) => {
|
|
274
|
+
if (!id.includes('.')) return id
|
|
275
|
+
const cached = lidCache.get(id)
|
|
276
|
+
if (cached) return cached
|
|
277
|
+
const [deviceId, device] = id.split('.')
|
|
278
|
+
const [user, dt] = deviceId.split('_')
|
|
279
|
+
const domainType = parseInt(dt || '0')
|
|
280
|
+
if (domainType === WAJIDDomains.LID || domainType === WAJIDDomains.HOSTED_LID) return id
|
|
281
|
+
const pnJid = `${user}${device !== '0' ? `:${device}` : ''}@${domainType === WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`
|
|
282
|
+
const lid = await lidMapping.getLIDForPN(pnJid)
|
|
283
|
+
const result = lid ? jidToAddr(lid).toString() : id
|
|
284
|
+
lidCache.set(id, result)
|
|
285
|
+
return result
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const getIndex = () => migrateIndexKey(keys, 'session')
|
|
289
|
+
const setIndex = (batch) => keys.set({ session: { 'index': batch } })
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
loadSession: async (id) => {
|
|
293
|
+
try {
|
|
294
|
+
const addr = await resolveLID(id)
|
|
295
|
+
const batch = await getIndex()
|
|
296
|
+
const v2 = batch[v2Key(addr)]
|
|
297
|
+
if (v2) {
|
|
298
|
+
if (isOldJson(v2)) { logger?.debug?.(`[Signal] Corrupt v2 for ${addr}, will fresh handshake`); return null }
|
|
299
|
+
const buf = toBuffer(v2)
|
|
300
|
+
if (buf) return buf
|
|
301
|
+
}
|
|
302
|
+
const plain = batch[addr]
|
|
303
|
+
if (!plain || isOldJson(plain)) { // old JS JSON format — not usable by rust bridge
|
|
304
|
+
if (plain) logger?.debug?.(`[Signal] Old JSON session for ${addr}, will fresh handshake`)
|
|
305
|
+
return null
|
|
306
|
+
}
|
|
307
|
+
return toBuffer(plain)
|
|
308
|
+
} catch (e) { logger?.error?.(`[Signal] loadSession error: ${e.message}`); return null }
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
storeSession: async (id, session) => {
|
|
312
|
+
const addr = await resolveLID(id)
|
|
313
|
+
const batch = await getIndex()
|
|
314
|
+
batch[v2Key(addr)] = session.serialize() // always write to v2 slot
|
|
315
|
+
batch[addr] = { version: 'v1', _sessions: {} } // plain slot marker so old code sees something
|
|
316
|
+
await setIndex(batch)
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
isTrustedIdentity: () => true,
|
|
320
|
+
|
|
321
|
+
loadIdentityKey: async (id) => {
|
|
322
|
+
const addr = await resolveLID(id)
|
|
323
|
+
const { [addr]: key } = await keys.get('identity-key', [addr])
|
|
324
|
+
const buf = toBuffer(key)
|
|
325
|
+
return buf ? new Uint8Array(buf) : undefined
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
saveIdentity: async (id, identityKey) => {
|
|
329
|
+
const addr = await resolveLID(id)
|
|
330
|
+
const { [addr]: raw } = await keys.get('identity-key', [addr])
|
|
331
|
+
const buf = toBuffer(raw)
|
|
332
|
+
const existing = buf ? new Uint8Array(buf) : null
|
|
333
|
+
const match = existing && existing.length === identityKey.length && existing.every((b, i) => b === identityKey[i])
|
|
334
|
+
if (existing && !match) {
|
|
335
|
+
await keys.set({ session: { [addr]: null }, 'identity-key': { [addr]: identityKey } })
|
|
336
|
+
return true
|
|
337
|
+
}
|
|
338
|
+
if (!existing) {
|
|
339
|
+
await keys.set({ 'identity-key': { [addr]: identityKey } })
|
|
340
|
+
return true
|
|
341
|
+
}
|
|
342
|
+
return false
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
loadPreKey: async (id) => {
|
|
346
|
+
const { [id.toString()]: key } = await keys.get('pre-key', [id.toString()])
|
|
347
|
+
if (!key) return null
|
|
348
|
+
return { pubKey: new Uint8Array(Buffer.from(key.public)), privKey: new Uint8Array(Buffer.from(key.private)) }
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
removePreKey: (id) => keys.set({ 'pre-key': { [id]: null } }),
|
|
352
|
+
|
|
353
|
+
loadSignedPreKey: () => {
|
|
354
|
+
const { signedPreKey: key } = creds
|
|
355
|
+
return { keyId: key.keyId, keyPair: { pubKey: new Uint8Array(Buffer.from(key.keyPair.public)), privKey: new Uint8Array(Buffer.from(key.keyPair.private)) }, signature: new Uint8Array(Buffer.from(key.signature)) }
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
loadSenderKey: async (keyId) => {
|
|
359
|
+
try {
|
|
360
|
+
const id = keyId.toString()
|
|
361
|
+
const { [id]: key } = await keys.get('sender-key', [id])
|
|
362
|
+
return toBuffer(key)
|
|
363
|
+
} catch (e) { logger?.error?.(`[Signal] loadSenderKey error: ${e.message}`); return null }
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
storeSenderKey: async (keyId, record) => {
|
|
367
|
+
const id = keyId.toString()
|
|
368
|
+
const bytes = record instanceof Uint8Array ? record : Buffer.isBuffer(record) ? record : record.serialize()
|
|
369
|
+
await keys.set({ 'sender-key': { [id]: bytes } })
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
getOurRegistrationId: () => creds.registrationId,
|
|
373
|
+
|
|
374
|
+
getOurIdentity: () => {
|
|
375
|
+
const { signedIdentityKey } = creds
|
|
376
|
+
return { pubKey: new Uint8Array(generateSignalPubKey(Buffer.from(signedIdentityKey.public))), privKey: new Uint8Array(Buffer.from(signedIdentityKey.private)) }
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
293
381
|
export default makeLibSignalRepository
|