@neelegirl/baileys 2.1.4 → 2.1.6

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.
@@ -1,123 +1,331 @@
1
1
  "use strict"
2
2
 
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k
5
- var desc = Object.getOwnPropertyDescriptor(m, k)
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k] } }
8
- }
9
- Object.defineProperty(o, k2, desc)
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k
12
- o[k2] = m[k]
13
- }))
14
-
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v })
17
- }) : function(o, v) {
18
- o["default"] = v
19
- })
20
-
21
- var __importStar = (this && this.__importStar) || function (mod) {
22
- if (mod && mod.__esModule) return mod
23
- var result = {}
24
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k)
25
- __setModuleDefault(result, mod)
26
- return result
27
- }
28
3
  Object.defineProperty(exports, "__esModule", { value: true })
29
4
 
30
- const libsignal = __importStar(require("@neelegirl/libsignal"))
5
+ const libsignal = require("@neelegirl/libsignal")
6
+ const { PreKeyWhisperMessage } = require("@neelegirl/libsignal/src/protobufs")
7
+ const { LRUCache } = require("lru-cache")
31
8
  const WASignalGroup_1 = require("./WASignalGroup")
9
+ const lid_mapping_1 = require("./lid-mapping")
32
10
  const Utils_1 = require("../Utils")
33
11
  const WABinary_1 = require("../WABinary")
34
12
 
35
- function makeLibSignalRepository(auth) {
36
- const storage = signalStorage(auth)
37
- return {
13
+ function extractIdentityFromPkmsg(ciphertext) {
14
+ try {
15
+ if (!ciphertext || ciphertext.length < 2) {
16
+ return undefined
17
+ }
18
+ const version = ciphertext[0]
19
+ if ((version & 0xf) !== 3) {
20
+ return undefined
21
+ }
22
+ const preKeyProto = PreKeyWhisperMessage.decode(ciphertext.slice(1))
23
+ if (preKeyProto.identityKey?.length === 33) {
24
+ return new Uint8Array(preKeyProto.identityKey)
25
+ }
26
+ }
27
+ catch (_error) { }
28
+ }
29
+
30
+ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
31
+ const parsedKeys = auth.keys
32
+ const lidMapping = new lid_mapping_1.LIDMappingStore(parsedKeys, logger, pnToLIDFunc)
33
+ const storage = signalStorage(auth, lidMapping)
34
+ const migratedSessionCache = new LRUCache({
35
+ ttl: 3 * 24 * 60 * 60 * 1000,
36
+ ttlAutopurge: true,
37
+ updateAgeOnGet: true
38
+ })
39
+ const repository = {
38
40
  decryptGroupMessage({ group, authorJid, msg }) {
39
41
  const senderName = jidToSignalSenderKeyName(group, authorJid)
40
42
  const cipher = new WASignalGroup_1.GroupCipher(storage, senderName)
41
- return cipher.decrypt(msg)
43
+ return parsedKeys.transaction(async () => {
44
+ return cipher.decrypt(msg)
45
+ }, group)
42
46
  },
43
47
  async processSenderKeyDistributionMessage({ item, authorJid }) {
48
+ if (!item.groupId) {
49
+ throw new Error('group ID is required for sender key distribution message')
50
+ }
44
51
  const builder = new WASignalGroup_1.GroupSessionBuilder(storage)
45
52
  const senderName = jidToSignalSenderKeyName(item.groupId, authorJid)
46
- const senderMsg = new WASignalGroup_1.SenderKeyDistributionMessage(null, null, null, null, item.axolotlSenderKeyDistributionMessage)
47
- const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName])
48
- if (!senderKey) {
49
- await storage.storeSenderKey(senderName, new WASignalGroup_1.SenderKeyRecord())
50
- }
51
- await builder.process(senderName, senderMsg)
53
+ const senderNameStr = senderName.toString()
54
+ const senderMsg = new WASignalGroup_1.SenderKeyDistributionMessage(
55
+ null,
56
+ null,
57
+ null,
58
+ null,
59
+ item.axolotlSenderKeyDistributionMessage
60
+ )
61
+ return parsedKeys.transaction(async () => {
62
+ const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr])
63
+ if (!senderKey) {
64
+ await storage.storeSenderKey(senderName, new WASignalGroup_1.SenderKeyRecord())
65
+ }
66
+ await builder.process(senderName, senderMsg)
67
+ }, item.groupId)
52
68
  },
53
69
  async decryptMessage({ jid, type, ciphertext }) {
54
70
  const addr = jidToSignalProtocolAddress(jid)
55
71
  const session = new libsignal.SessionCipher(storage, addr)
56
- let result
57
- switch (type) {
58
- case 'pkmsg':
59
- result = await session.decryptPreKeyWhisperMessage(ciphertext)
60
- break
61
- case 'msg':
62
- result = await session.decryptWhisperMessage(ciphertext)
63
- break
72
+ if (type === 'pkmsg') {
73
+ const identityKey = extractIdentityFromPkmsg(ciphertext)
74
+ if (identityKey) {
75
+ const addrStr = addr.toString()
76
+ const identityChanged = await storage.saveIdentity(addrStr, identityKey)
77
+ if (identityChanged) {
78
+ logger?.info?.({ jid, addr: addrStr }, 'identity key changed or contact was first seen')
79
+ }
80
+ }
64
81
  }
65
- return result
82
+ return parsedKeys.transaction(async () => {
83
+ switch (type) {
84
+ case 'pkmsg':
85
+ return session.decryptPreKeyWhisperMessage(ciphertext)
86
+ case 'msg':
87
+ default:
88
+ return session.decryptWhisperMessage(ciphertext)
89
+ }
90
+ }, jid)
66
91
  },
67
92
  async encryptMessage({ jid, data }) {
68
93
  const addr = jidToSignalProtocolAddress(jid)
69
94
  const cipher = new libsignal.SessionCipher(storage, addr)
70
- const { type: sigType, body } = await cipher.encrypt(data)
71
- const type = sigType === 3 ? 'pkmsg' : 'msg'
72
- return { type, ciphertext: Buffer.from(body, 'binary') }
95
+ return parsedKeys.transaction(async () => {
96
+ const { type: sigType, body } = await cipher.encrypt(data)
97
+ const type = sigType === 3 ? 'pkmsg' : 'msg'
98
+ return {
99
+ type,
100
+ ciphertext: Buffer.from(body, 'binary')
101
+ }
102
+ }, jid)
73
103
  },
74
104
  async encryptGroupMessage({ group, meId, data }) {
75
105
  const senderName = jidToSignalSenderKeyName(group, meId)
76
106
  const builder = new WASignalGroup_1.GroupSessionBuilder(storage)
77
- const { [senderName]: senderKey } = await auth.keys.get('sender-key', [senderName])
78
- if (!senderKey) {
79
- await storage.storeSenderKey(senderName, new WASignalGroup_1.SenderKeyRecord())
80
- }
81
- const senderKeyDistributionMessage = await builder.create(senderName)
82
- const session = new WASignalGroup_1.GroupCipher(storage, senderName)
83
- const ciphertext = await session.encrypt(data)
84
- return {
85
- ciphertext,
86
- senderKeyDistributionMessage: senderKeyDistributionMessage.serialize(),
87
- }
107
+ return parsedKeys.transaction(async () => {
108
+ const senderKeyDistributionMessage = await builder.create(senderName)
109
+ const session = new WASignalGroup_1.GroupCipher(storage, senderName)
110
+ const ciphertext = await session.encrypt(data)
111
+ return {
112
+ ciphertext,
113
+ senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
114
+ }
115
+ }, group)
88
116
  },
89
117
  async injectE2ESession({ jid, session }) {
118
+ logger?.trace?.({ jid }, 'injecting E2EE session')
90
119
  const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid))
91
- await cipher.initOutgoing(session)
120
+ return parsedKeys.transaction(async () => {
121
+ await cipher.initOutgoing(session)
122
+ }, jid)
92
123
  },
93
124
  jidToSignalProtocolAddress(jid) {
94
125
  return jidToSignalProtocolAddress(jid).toString()
95
126
  },
127
+ lidMapping,
128
+ async validateSession(jid) {
129
+ try {
130
+ const addr = jidToSignalProtocolAddress(jid)
131
+ const session = await storage.loadSession(addr.toString())
132
+ if (!session) {
133
+ return { exists: false, reason: 'no session' }
134
+ }
135
+ if (!session.haveOpenSession()) {
136
+ return { exists: false, reason: 'no open session' }
137
+ }
138
+ return { exists: true }
139
+ }
140
+ catch (_error) {
141
+ return { exists: false, reason: 'validation error' }
142
+ }
143
+ },
144
+ async deleteSession(jids) {
145
+ if (!jids?.length) {
146
+ return
147
+ }
148
+ const sessionUpdates = {}
149
+ for (const jid of jids) {
150
+ const addr = jidToSignalProtocolAddress(jid)
151
+ sessionUpdates[addr.toString()] = null
152
+ }
153
+ await parsedKeys.transaction(async () => {
154
+ await auth.keys.set({ session: sessionUpdates })
155
+ }, `delete-${jids.length}-sessions`)
156
+ },
157
+ async migrateSession(fromJid, toJid) {
158
+ if (!fromJid || (!(0, WABinary_1.isLidUser)(toJid) && !(0, WABinary_1.isHostedLidUser)(toJid))) {
159
+ return { migrated: 0, skipped: 0, total: 0 }
160
+ }
161
+ if (!(0, WABinary_1.isPnUser)(fromJid) && !(0, WABinary_1.isHostedPnUser)(fromJid)) {
162
+ return { migrated: 0, skipped: 0, total: 1 }
163
+ }
164
+ const fromDecoded = (0, WABinary_1.jidDecode)(fromJid)
165
+ const user = fromDecoded?.user
166
+ if (!user) {
167
+ return { migrated: 0, skipped: 0, total: 0 }
168
+ }
169
+ const { [user]: userDevices } = await parsedKeys.get('device-list', [user])
170
+ if (!userDevices?.length) {
171
+ return { migrated: 0, skipped: 0, total: 0 }
172
+ }
173
+ const fromDeviceStr = (fromDecoded?.device || 0).toString()
174
+ if (!userDevices.includes(fromDeviceStr)) {
175
+ userDevices.push(fromDeviceStr)
176
+ }
177
+ const uncachedDevices = userDevices.filter(device => !migratedSessionCache.has(`${user}.${device}`))
178
+ const deviceSessionKeys = uncachedDevices.map(device => `${user}.${device}`)
179
+ const existingSessions = deviceSessionKeys.length
180
+ ? await parsedKeys.get('session', deviceSessionKeys)
181
+ : {}
182
+ const deviceJids = []
183
+ for (const [sessionKey, sessionData] of Object.entries(existingSessions)) {
184
+ if (!sessionData) {
185
+ continue
186
+ }
187
+ const deviceStr = sessionKey.split('.')[1]
188
+ if (!deviceStr) {
189
+ continue
190
+ }
191
+ const deviceNum = parseInt(deviceStr, 10)
192
+ let jid = `${user}${deviceNum ? `:${deviceNum}` : ''}@s.whatsapp.net`
193
+ if (deviceNum === 99) {
194
+ jid = `${user}:99@hosted`
195
+ }
196
+ deviceJids.push(jid)
197
+ }
198
+ return parsedKeys.transaction(async () => {
199
+ const migrationOps = deviceJids.map(jid => {
200
+ const lidWithDevice = (0, WABinary_1.transferDevice)(jid, toJid)
201
+ const fromDecoded = (0, WABinary_1.jidDecode)(jid)
202
+ return {
203
+ fromJid: jid,
204
+ toJid: lidWithDevice,
205
+ pnUser: fromDecoded?.user,
206
+ deviceId: fromDecoded?.device || 0,
207
+ fromAddr: jidToSignalProtocolAddress(jid),
208
+ toAddr: jidToSignalProtocolAddress(lidWithDevice)
209
+ }
210
+ })
211
+ const totalOps = migrationOps.length
212
+ let migratedCount = 0
213
+ const pnAddrStrings = [...new Set(migrationOps.map(op => op.fromAddr.toString()))]
214
+ const pnSessions = pnAddrStrings.length
215
+ ? await parsedKeys.get('session', pnAddrStrings)
216
+ : {}
217
+ const sessionUpdates = {}
218
+ for (const op of migrationOps) {
219
+ const pnAddrStr = op.fromAddr.toString()
220
+ const lidAddrStr = op.toAddr.toString()
221
+ const pnSession = pnSessions[pnAddrStr]
222
+ if (!pnSession) {
223
+ continue
224
+ }
225
+ const fromSession = libsignal.SessionRecord.deserialize(pnSession)
226
+ if (!fromSession.haveOpenSession()) {
227
+ continue
228
+ }
229
+ sessionUpdates[lidAddrStr] = fromSession.serialize()
230
+ sessionUpdates[pnAddrStr] = null
231
+ migratedCount += 1
232
+ }
233
+ if (Object.keys(sessionUpdates).length) {
234
+ await parsedKeys.set({ session: sessionUpdates })
235
+ for (const op of migrationOps) {
236
+ if (sessionUpdates[op.toAddr.toString()] && op.pnUser) {
237
+ migratedSessionCache.set(`${op.pnUser}.${op.deviceId}`, true)
238
+ }
239
+ }
240
+ }
241
+ return {
242
+ migrated: migratedCount,
243
+ skipped: totalOps - migratedCount,
244
+ total: totalOps
245
+ }
246
+ }, `migrate-${deviceJids.length}-sessions-${(0, WABinary_1.jidDecode)(toJid)?.user || 'unknown'}`)
247
+ }
96
248
  }
249
+ return repository
97
250
  }
98
251
 
99
252
  const jidToSignalProtocolAddress = (jid) => {
100
- const { user, device } = WABinary_1.jidDecode(jid)
101
- return new libsignal.ProtocolAddress(user, device || 0)
253
+ const decoded = (0, WABinary_1.jidDecode)(jid)
254
+ const { user, device, server, domainType } = decoded || {}
255
+ if (!user) {
256
+ throw new Error(`JID decoded but user is empty: ${jid}`)
257
+ }
258
+ const signalUser = domainType !== WABinary_1.WAJIDDomains.WHATSAPP ? `${user}_${domainType}` : user
259
+ const finalDevice = device || 0
260
+ if (device === 99 && server !== 'hosted' && server !== 'hosted.lid') {
261
+ throw new Error(`unexpected non-hosted device 99 JID: ${jid}`)
262
+ }
263
+ return new libsignal.ProtocolAddress(signalUser, finalDevice)
102
264
  }
103
265
 
104
266
  const jidToSignalSenderKeyName = (group, user) => {
105
- return new WASignalGroup_1.SenderKeyName(group, jidToSignalProtocolAddress(user)).toString()
267
+ return new WASignalGroup_1.SenderKeyName(group, jidToSignalProtocolAddress(user))
106
268
  }
107
269
 
108
- function signalStorage({ creds, keys }) {
270
+ function signalStorage({ creds, keys }, lidMapping) {
271
+ const resolveLIDSignalAddress = async (id) => {
272
+ if (id.includes('.')) {
273
+ const [deviceId, device] = id.split('.')
274
+ const [user, domainType_] = deviceId.split('_')
275
+ const domainType = parseInt(domainType_ || '0', 10)
276
+ if (domainType === WABinary_1.WAJIDDomains.LID || domainType === WABinary_1.WAJIDDomains.HOSTED_LID) {
277
+ return id
278
+ }
279
+ const pnJid = `${user}${device !== '0' ? `:${device}` : ''}@${domainType === WABinary_1.WAJIDDomains.HOSTED ? 'hosted' : 's.whatsapp.net'}`
280
+ const lidForPN = await lidMapping.getLIDForPN(pnJid)
281
+ if (lidForPN) {
282
+ return jidToSignalProtocolAddress(lidForPN).toString()
283
+ }
284
+ }
285
+ return id
286
+ }
109
287
  return {
110
288
  loadSession: async (id) => {
111
- const { [id]: sess } = await keys.get('session', [id])
112
- if (sess) {
113
- return libsignal.SessionRecord.deserialize(sess)
289
+ try {
290
+ const wireJid = await resolveLIDSignalAddress(id)
291
+ const { [wireJid]: sess } = await keys.get('session', [wireJid])
292
+ if (sess) {
293
+ return libsignal.SessionRecord.deserialize(sess)
294
+ }
114
295
  }
296
+ catch (_error) {
297
+ return null
298
+ }
299
+ return null
115
300
  },
116
301
  storeSession: async (id, session) => {
117
- await keys.set({ 'session': { [id]: session.serialize() } })
302
+ const wireJid = await resolveLIDSignalAddress(id)
303
+ await keys.set({ session: { [wireJid]: session.serialize() } })
304
+ },
305
+ isTrustedIdentity: () => true,
306
+ loadIdentityKey: async (id) => {
307
+ const wireJid = await resolveLIDSignalAddress(id)
308
+ const { [wireJid]: key } = await keys.get('identity-key', [wireJid])
309
+ return key || undefined
118
310
  },
119
- isTrustedIdentity: () => {
120
- return true
311
+ saveIdentity: async (id, identityKey) => {
312
+ const wireJid = await resolveLIDSignalAddress(id)
313
+ const { [wireJid]: existingKey } = await keys.get('identity-key', [wireJid])
314
+ const keysMatch = existingKey
315
+ && existingKey.length === identityKey.length
316
+ && existingKey.every((byte, i) => byte === identityKey[i])
317
+ if (existingKey && !keysMatch) {
318
+ await keys.set({
319
+ session: { [wireJid]: null },
320
+ 'identity-key': { [wireJid]: identityKey }
321
+ })
322
+ return true
323
+ }
324
+ if (!existingKey) {
325
+ await keys.set({ 'identity-key': { [wireJid]: identityKey } })
326
+ return true
327
+ }
328
+ return false
121
329
  },
122
330
  loadPreKey: async (id) => {
123
331
  const keyId = id.toString()
@@ -137,26 +345,26 @@ function signalStorage({ creds, keys }) {
137
345
  pubKey: Buffer.from(key.keyPair.public)
138
346
  }
139
347
  },
140
- loadSenderKey: async (keyId) => {
348
+ loadSenderKey: async (senderKeyName) => {
349
+ const keyId = senderKeyName.toString()
141
350
  const { [keyId]: key } = await keys.get('sender-key', [keyId])
142
- if (key) {
143
- return new WASignalGroup_1.SenderKeyRecord(key)
144
- }
351
+ return key ? new WASignalGroup_1.SenderKeyRecord(key) : new WASignalGroup_1.SenderKeyRecord()
145
352
  },
146
- storeSenderKey: async (keyId, key) => {
353
+ storeSenderKey: async (senderKeyName, key) => {
354
+ const keyId = senderKeyName.toString()
147
355
  await keys.set({ 'sender-key': { [keyId]: key.serialize() } })
148
356
  },
149
- getOurRegistrationId: () => (creds.registrationId),
357
+ getOurRegistrationId: () => creds.registrationId,
150
358
  getOurIdentity: () => {
151
359
  const { signedIdentityKey } = creds
152
360
  return {
153
361
  privKey: Buffer.from(signedIdentityKey.private),
154
- pubKey: Utils_1.generateSignalPubKey(signedIdentityKey.public),
362
+ pubKey: Buffer.from((0, Utils_1.generateSignalPubKey)(signedIdentityKey.public))
155
363
  }
156
364
  }
157
365
  }
158
366
  }
159
367
 
160
368
  module.exports = {
161
- makeLibSignalRepository
162
- }
369
+ makeLibSignalRepository
370
+ }
@@ -0,0 +1,23 @@
1
+ import type { LIDMapping, SignalKeyStoreWithTransaction } from '../Types'
2
+ import type { ILogger } from '../Utils/logger'
3
+
4
+ export declare class LIDMappingStore {
5
+ private readonly mappingCache
6
+ private readonly keys
7
+ private readonly logger
8
+ private readonly inflightLIDLookups
9
+ private readonly inflightPNLookups
10
+ private pnToLIDFunc?
11
+ constructor(
12
+ keys: SignalKeyStoreWithTransaction,
13
+ logger: ILogger,
14
+ pnToLIDFunc?: (jids: string[]) => Promise<LIDMapping[] | undefined>
15
+ )
16
+ storeLIDPNMappings(pairs: LIDMapping[]): Promise<void>
17
+ getLIDForPN(pn: string): Promise<string | null>
18
+ getLIDsForPNs(pns: string[]): Promise<LIDMapping[] | null>
19
+ getPNForLID(lid: string): Promise<string | null>
20
+ getPNsForLIDs(lids: string[]): Promise<LIDMapping[] | null>
21
+ private _getLIDsForPNsImpl
22
+ private _getPNsForLIDsImpl
23
+ }