@nexustechpro/baileys 2.0.2 → 2.0.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.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +924 -1299
  3. package/WAProto/index.js +22 -18
  4. package/lib/Defaults/baileys-version.json +6 -2
  5. package/lib/Defaults/index.js +173 -172
  6. package/lib/Signal/libsignal.js +395 -292
  7. package/lib/Signal/lid-mapping.js +264 -171
  8. package/lib/Socket/Client/index.js +2 -2
  9. package/lib/Socket/Client/types.js +10 -10
  10. package/lib/Socket/Client/websocket.js +45 -310
  11. package/lib/Socket/business.js +375 -375
  12. package/lib/Socket/chats.js +916 -963
  13. package/lib/Socket/communities.js +430 -430
  14. package/lib/Socket/groups.js +342 -342
  15. package/lib/Socket/index.js +21 -22
  16. package/lib/Socket/messages-recv.js +963 -743
  17. package/lib/Socket/messages-send.js +273 -321
  18. package/lib/Socket/mex.js +50 -50
  19. package/lib/Socket/newsletter.js +148 -148
  20. package/lib/Socket/nexus-handler.js +296 -247
  21. package/lib/Socket/registration.js +50 -33
  22. package/lib/Socket/socket.js +872 -1201
  23. package/lib/Store/index.js +5 -5
  24. package/lib/Store/make-cache-manager-store.js +81 -81
  25. package/lib/Store/make-in-memory-store.js +416 -416
  26. package/lib/Store/make-ordered-dictionary.js +81 -81
  27. package/lib/Store/object-repository.js +30 -30
  28. package/lib/Types/Auth.js +1 -1
  29. package/lib/Types/Bussines.js +1 -1
  30. package/lib/Types/Call.js +1 -1
  31. package/lib/Types/Chat.js +7 -7
  32. package/lib/Types/Contact.js +1 -1
  33. package/lib/Types/Events.js +1 -1
  34. package/lib/Types/GroupMetadata.js +1 -1
  35. package/lib/Types/Label.js +24 -24
  36. package/lib/Types/LabelAssociation.js +6 -6
  37. package/lib/Types/Message.js +10 -10
  38. package/lib/Types/Newsletter.js +37 -29
  39. package/lib/Types/Product.js +1 -1
  40. package/lib/Types/Signal.js +1 -1
  41. package/lib/Types/Socket.js +2 -2
  42. package/lib/Types/State.js +55 -12
  43. package/lib/Types/USync.js +1 -1
  44. package/lib/Types/index.js +25 -25
  45. package/lib/Utils/auth-utils.js +264 -256
  46. package/lib/Utils/baileys-event-stream.js +55 -55
  47. package/lib/Utils/browser-utils.js +27 -27
  48. package/lib/Utils/business.js +228 -230
  49. package/lib/Utils/chat-utils.js +726 -764
  50. package/lib/Utils/companion-reg-client-utils.js +34 -0
  51. package/lib/Utils/crypto.js +109 -135
  52. package/lib/Utils/decode-wa-message.js +342 -314
  53. package/lib/Utils/event-buffer.js +547 -547
  54. package/lib/Utils/generics.js +295 -297
  55. package/lib/Utils/history.js +91 -83
  56. package/lib/Utils/index.js +25 -20
  57. package/lib/Utils/key-store.js +17 -0
  58. package/lib/Utils/link-preview.js +107 -98
  59. package/lib/Utils/logger.js +2 -2
  60. package/lib/Utils/lt-hash.js +47 -47
  61. package/lib/Utils/make-mutex.js +39 -39
  62. package/lib/Utils/message-retry-manager.js +148 -148
  63. package/lib/Utils/messages-media.js +579 -535
  64. package/lib/Utils/messages.js +821 -706
  65. package/lib/Utils/noise-handler.js +255 -255
  66. package/lib/Utils/pre-key-manager.js +105 -105
  67. package/lib/Utils/process-message.js +430 -412
  68. package/lib/Utils/reporting-utils.js +155 -0
  69. package/lib/Utils/signal.js +191 -159
  70. package/lib/Utils/sync-action-utils.js +33 -0
  71. package/lib/Utils/tc-token-utils.js +162 -0
  72. package/lib/Utils/use-multi-file-auth-state.js +120 -120
  73. package/lib/Utils/validate-connection.js +194 -194
  74. package/lib/WABinary/constants.js +1306 -1300
  75. package/lib/WABinary/decode.js +237 -237
  76. package/lib/WABinary/encode.js +232 -232
  77. package/lib/WABinary/generic-utils.js +252 -211
  78. package/lib/WABinary/index.js +6 -5
  79. package/lib/WABinary/jid-utils.js +279 -95
  80. package/lib/WABinary/types.js +1 -1
  81. package/lib/WAM/BinaryInfo.js +9 -9
  82. package/lib/WAM/constants.js +22852 -22852
  83. package/lib/WAM/encode.js +149 -149
  84. package/lib/WAM/index.js +3 -3
  85. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
  86. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
  87. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
  88. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
  89. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
  90. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
  91. package/lib/WAUSync/Protocols/index.js +4 -4
  92. package/lib/WAUSync/USyncQuery.js +93 -93
  93. package/lib/WAUSync/USyncUser.js +22 -22
  94. package/lib/WAUSync/index.js +3 -3
  95. package/lib/index.js +65 -66
  96. package/package.json +172 -143
  97. package/lib/Signal/Group/ciphertext-message.js +0 -12
  98. package/lib/Signal/Group/group-session-builder.js +0 -30
  99. package/lib/Signal/Group/group_cipher.js +0 -100
  100. package/lib/Signal/Group/index.js +0 -12
  101. package/lib/Signal/Group/keyhelper.js +0 -18
  102. package/lib/Signal/Group/sender-chain-key.js +0 -26
  103. package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
  104. package/lib/Signal/Group/sender-key-message.js +0 -66
  105. package/lib/Signal/Group/sender-key-name.js +0 -48
  106. package/lib/Signal/Group/sender-key-record.js +0 -41
  107. package/lib/Signal/Group/sender-key-state.js +0 -84
  108. package/lib/Signal/Group/sender-message-key.js +0 -26
@@ -0,0 +1,155 @@
1
+ import { createHmac } from 'crypto'
2
+ import { hkdf } from './crypto.js'
3
+
4
+ const reportingFields = [
5
+ { f: 1 },
6
+ { f: 3, s: [{ f: 2 }, { f: 3 }, { f: 8 }, { f: 11 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 25 }] },
7
+ { f: 4, s: [{ f: 1 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
8
+ { f: 5, s: [{ f: 3 }, { f: 4 }, { f: 5 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
9
+ { f: 6, s: [{ f: 1 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 30 }] },
10
+ { f: 7, s: [{ f: 2 }, { f: 7 }, { f: 10 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
11
+ { f: 8, s: [{ f: 2 }, { f: 7 }, { f: 9 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 21 }] },
12
+ { f: 9, s: [{ f: 2 }, { f: 6 }, { f: 7 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
13
+ { f: 12, s: [{ f: 1 }, { f: 2 }, { f: 14, m: true }, { f: 15 }] },
14
+ { f: 18, s: [{ f: 6 }, { f: 16 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
15
+ { f: 26, s: [{ f: 4 }, { f: 5 }, { f: 8 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }] },
16
+ { f: 28, s: [{ f: 1 }, { f: 2 }, { f: 4 }, { f: 5 }, { f: 6 }, { f: 7, s: [{ f: 21 }, { f: 22 }] }] },
17
+ { f: 37, s: [{ f: 1, m: true }] },
18
+ { f: 49, s: [{ f: 2 }, { f: 3, s: [{ f: 1 }, { f: 2 }] }, { f: 5, s: [{ f: 21 }, { f: 22 }] }, { f: 8, s: [{ f: 1 }, { f: 2 }] }] },
19
+ { f: 53, s: [{ f: 1, m: true }] },
20
+ { f: 55, s: [{ f: 1, m: true }] },
21
+ { f: 58, s: [{ f: 1, m: true }] },
22
+ { f: 59, s: [{ f: 1, m: true }] },
23
+ { f: 60, s: [{ f: 2 }, { f: 3, s: [{ f: 1 }, { f: 2 }] }, { f: 5, s: [{ f: 21 }, { f: 22 }] }, { f: 8, s: [{ f: 1 }, { f: 2 }] }] },
24
+ { f: 64, s: [{ f: 2 }, { f: 3, s: [{ f: 1 }, { f: 2 }] }, { f: 5, s: [{ f: 21 }, { f: 22 }] }, { f: 8, s: [{ f: 1 }, { f: 2 }] }] },
25
+ { f: 66, s: [{ f: 2 }, { f: 6 }, { f: 7 }, { f: 13 }, { f: 17, s: [{ f: 21 }, { f: 22 }] }, { f: 20 }] },
26
+ { f: 74, s: [{ f: 1, m: true }] },
27
+ { f: 87, s: [{ f: 1, m: true }] },
28
+ { f: 88, s: [{ f: 1 }, { f: 2, s: [{ f: 1 }] }, { f: 3, s: [{ f: 21 }, { f: 22 }] }] },
29
+ { f: 92, s: [{ f: 1, m: true }] },
30
+ { f: 93, s: [{ f: 1, m: true }] },
31
+ { f: 94, s: [{ f: 1, m: true }] }
32
+ ]
33
+
34
+ const EMPTY_MAP = new Map()
35
+ const ENC_SECRET_REPORT_TOKEN = 'Report Token'
36
+ const WIRE = { VARINT: 0, FIXED64: 1, BYTES: 2, FIXED32: 5 }
37
+
38
+ const compileReportingFields = (fields) => {
39
+ const map = new Map()
40
+ for (const f of fields) map.set(f.f, { m: f.m, children: f.s ? compileReportingFields(f.s) : undefined })
41
+ return map
42
+ }
43
+
44
+ const compiledReportingFields = compileReportingFields(reportingFields)
45
+
46
+ const decodeVarint = (buffer, offset) => {
47
+ let value = 0, bytes = 0, shift = 0
48
+ while (offset + bytes < buffer.length) {
49
+ const current = buffer[offset + bytes]
50
+ value |= (current & 0x7f) << shift
51
+ bytes++
52
+ if ((current & 0x80) === 0) return { value, bytes, ok: true }
53
+ shift += 7
54
+ if (shift > 35) return { value: 0, bytes: 0, ok: false }
55
+ }
56
+ return { value: 0, bytes: 0, ok: false }
57
+ }
58
+
59
+ const encodeVarint = (value) => {
60
+ const parts = []
61
+ let remaining = value >>> 0
62
+ while (remaining > 0x7f) { parts.push((remaining & 0x7f) | 0x80); remaining >>>= 7 }
63
+ parts.push(remaining)
64
+ return Buffer.from(parts)
65
+ }
66
+
67
+ const extractReportingTokenContent = (data, cfg) => {
68
+ const out = []
69
+ let i = 0
70
+ while (i < data.length) {
71
+ const tag = decodeVarint(data, i)
72
+ if (!tag.ok) return null
73
+ const fieldNum = tag.value >> 3
74
+ const wireType = tag.value & 0x7
75
+ const fieldStart = i
76
+ i += tag.bytes
77
+ const fieldCfg = cfg.get(fieldNum)
78
+ const pushSlice = (end) => { if (end > data.length) return false; out.push({ num: fieldNum, bytes: data.subarray(fieldStart, end) }); i = end; return true }
79
+ const skip = (end) => { if (end > data.length) return false; i = end; return true }
80
+ if (wireType === WIRE.VARINT) {
81
+ const v = decodeVarint(data, i)
82
+ if (!v.ok) return null
83
+ const end = i + v.bytes
84
+ if (!fieldCfg) { if (!skip(end)) return null; continue }
85
+ if (!pushSlice(end)) return null
86
+ continue
87
+ }
88
+ if (wireType === WIRE.FIXED64) {
89
+ const end = i + 8
90
+ if (!fieldCfg) { if (!skip(end)) return null; continue }
91
+ if (!pushSlice(end)) return null
92
+ continue
93
+ }
94
+ if (wireType === WIRE.FIXED32) {
95
+ const end = i + 4
96
+ if (!fieldCfg) { if (!skip(end)) return null; continue }
97
+ if (!pushSlice(end)) return null
98
+ continue
99
+ }
100
+ if (wireType === WIRE.BYTES) {
101
+ const len = decodeVarint(data, i)
102
+ if (!len.ok) return null
103
+ const valStart = i + len.bytes
104
+ const valEnd = valStart + len.value
105
+ if (valEnd > data.length) return null
106
+ if (!fieldCfg) { i = valEnd; continue }
107
+ if (fieldCfg.m || fieldCfg.children) {
108
+ const sub = extractReportingTokenContent(data.subarray(valStart, valEnd), fieldCfg.children ?? EMPTY_MAP)
109
+ if (sub === null) return null
110
+ if (sub.length > 0) {
111
+ const newTag = encodeVarint(tag.value)
112
+ const newLen = encodeVarint(sub.length)
113
+ out.push({ num: fieldNum, bytes: Buffer.concat([newTag, newLen, sub]) })
114
+ }
115
+ i = valEnd
116
+ continue
117
+ }
118
+ out.push({ num: fieldNum, bytes: data.subarray(fieldStart, valEnd) })
119
+ i = valEnd
120
+ continue
121
+ }
122
+ return null
123
+ }
124
+ if (out.length === 0) return Buffer.alloc(0)
125
+ out.sort((a, b) => a.num - b.num)
126
+ return Buffer.concat(out.map(f => f.bytes))
127
+ }
128
+
129
+ const generateMsgSecretKey = (modificationType, origMsgId, origMsgSender, modificationSender, origMsgSecret) => {
130
+ const useCaseSecret = Buffer.concat([
131
+ Buffer.from(origMsgId, 'utf8'), Buffer.from(origMsgSender, 'utf8'),
132
+ Buffer.from(modificationSender, 'utf8'), Buffer.from(modificationType, 'utf8')
133
+ ])
134
+ return hkdf(origMsgSecret, 32, { info: useCaseSecret.toString('latin1') })
135
+ }
136
+
137
+ export const shouldIncludeReportingToken = (message) =>
138
+ !message.reactionMessage && !message.encReactionMessage &&
139
+ !message.encEventResponseMessage && !message.pollUpdateMessage
140
+
141
+ export const getMessageReportingToken = async (msgProtobuf, message, key) => {
142
+ const msgSecret = message.messageContextInfo?.messageSecret
143
+ if (!msgSecret || !key.id) return null
144
+ const from = key.fromMe ? key.remoteJid : key.participant || key.remoteJid
145
+ const to = key.fromMe ? key.participant || key.remoteJid : key.remoteJid
146
+ const reportingSecret = generateMsgSecretKey(ENC_SECRET_REPORT_TOKEN, key.id, from, to, msgSecret)
147
+ const content = extractReportingTokenContent(msgProtobuf, compiledReportingFields)
148
+ if (!content || content.length === 0) return null
149
+ const reportingToken = createHmac('sha256', reportingSecret).update(content).digest().subarray(0, 16)
150
+ return {
151
+ tag: 'reporting',
152
+ attrs: {},
153
+ content: [{ tag: 'reporting_token', attrs: { v: '2' }, content: reportingToken }]
154
+ }
155
+ }
@@ -1,159 +1,191 @@
1
- import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
2
- import { assertNodeErrorFree, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, getServerFromDomainType, jidDecode, S_WHATSAPP_NET, WAJIDDomains } from '../WABinary/index.js';
3
- import { Curve, generateSignalPubKey } from './crypto.js';
4
- import { encodeBigEndian } from './generics.js';
5
- function chunk(array, size) {
6
- const chunks = [];
7
- for (let i = 0; i < array.length; i += size) {
8
- chunks.push(array.slice(i, i + size));
9
- }
10
- return chunks;
11
- }
12
- export const createSignalIdentity = (wid, accountSignatureKey) => {
13
- return {
14
- identifier: { name: wid, deviceId: 0 },
15
- identifierKey: generateSignalPubKey(accountSignatureKey)
16
- };
17
- };
18
- export const getPreKeys = async ({ get }, min, limit) => {
19
- const idList = [];
20
- for (let id = min; id < limit; id++) {
21
- idList.push(id.toString());
22
- }
23
- return get('pre-key', idList);
24
- };
25
- export const generateOrGetPreKeys = (creds, range) => {
26
- const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId;
27
- const remaining = range - avaliable;
28
- const lastPreKeyId = creds.nextPreKeyId + remaining - 1;
29
- const newPreKeys = {};
30
- if (remaining > 0) {
31
- for (let i = creds.nextPreKeyId; i <= lastPreKeyId; i++) {
32
- newPreKeys[i] = Curve.generateKeyPair();
33
- }
34
- }
35
- return {
36
- newPreKeys,
37
- lastPreKeyId,
38
- preKeysRange: [creds.firstUnuploadedPreKeyId, range]
39
- };
40
- };
41
- export const xmppSignedPreKey = (key) => ({
42
- tag: 'skey',
43
- attrs: {},
44
- content: [
45
- { tag: 'id', attrs: {}, content: encodeBigEndian(key.keyId, 3) },
46
- { tag: 'value', attrs: {}, content: key.keyPair.public },
47
- { tag: 'signature', attrs: {}, content: key.signature }
48
- ]
49
- });
50
- export const xmppPreKey = (pair, id) => ({
51
- tag: 'key',
52
- attrs: {},
53
- content: [
54
- { tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) },
55
- { tag: 'value', attrs: {}, content: pair.public }
56
- ]
57
- });
58
- export const parseAndInjectE2ESessions = async (node, repository) => {
59
- const extractKey = (key) => key
60
- ? {
61
- keyId: getBinaryNodeChildUInt(key, 'id', 3),
62
- publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')),
63
- signature: getBinaryNodeChildBuffer(key, 'signature')
64
- }
65
- : undefined;
66
- const nodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user');
67
- for (const node of nodes) {
68
- assertNodeErrorFree(node);
69
- }
70
- // Most of the work in repository.injectE2ESession is CPU intensive, not IO
71
- // So Promise.all doesn't really help here,
72
- // but blocks even loop if we're using it inside keys.transaction, and it makes it "sync" actually
73
- // This way we chunk it in smaller parts and between those parts we can yield to the event loop
74
- // It's rare case when you need to E2E sessions for so many users, but it's possible
75
- const chunkSize = 100;
76
- const chunks = chunk(nodes, chunkSize);
77
- for (const nodesChunk of chunks) {
78
- for (const node of nodesChunk) {
79
- const signedKey = getBinaryNodeChild(node, 'skey');
80
- const key = getBinaryNodeChild(node, 'key');
81
- const identity = getBinaryNodeChildBuffer(node, 'identity');
82
- const jid = node.attrs.jid;
83
- const registrationId = getBinaryNodeChildUInt(node, 'registration', 4);
84
- await repository.injectE2ESession({
85
- jid,
86
- session: {
87
- registrationId: registrationId,
88
- identityKey: generateSignalPubKey(identity),
89
- signedPreKey: extractKey(signedKey),
90
- preKey: extractKey(key)
91
- }
92
- });
93
- }
94
- }
95
- };
96
- export const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
97
- const { user: myUser, device: myDevice } = jidDecode(myJid);
98
- const extracted = [];
99
- for (const userResult of result) {
100
- const { devices, id } = userResult;
101
- const decoded = jidDecode(id), { user, server } = decoded;
102
- let { domainType } = decoded;
103
- const deviceList = devices?.deviceList;
104
- if (!Array.isArray(deviceList))
105
- continue;
106
- for (const { id: device, keyIndex, isHosted } of deviceList) {
107
- if ((!excludeZeroDevices || device !== 0) && // if zero devices are not-excluded, or device is non zero
108
- ((myUser !== user && myLid !== user) || myDevice !== device) && // either different user or if me user, not this device
109
- (device === 0 || !!keyIndex) // ensure that "key-index" is specified for "non-zero" devices, produces a bad req otherwise
110
- ) {
111
- if (isHosted) {
112
- domainType = domainType === WAJIDDomains.LID ? WAJIDDomains.HOSTED_LID : WAJIDDomains.HOSTED;
113
- }
114
- extracted.push({
115
- user,
116
- device,
117
- domainType,
118
- server: getServerFromDomainType(server, domainType)
119
- });
120
- }
121
- }
122
- }
123
- return extracted;
124
- };
125
- /**
126
- * get the next N keys for upload or processing
127
- * @param count number of pre-keys to get or generate
128
- */
129
- export const getNextPreKeys = async ({ creds, keys }, count) => {
130
- const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(creds, count);
131
- const update = {
132
- nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
133
- firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1)
134
- };
135
- await keys.set({ 'pre-key': newPreKeys });
136
- const preKeys = await getPreKeys(keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1]);
137
- return { update, preKeys };
138
- };
139
- export const getNextPreKeysNode = async (state, count) => {
140
- const { creds } = state;
141
- const { update, preKeys } = await getNextPreKeys(state, count);
142
- const node = {
143
- tag: 'iq',
144
- attrs: {
145
- xmlns: 'encrypt',
146
- type: 'set',
147
- to: S_WHATSAPP_NET
148
- },
149
- content: [
150
- { tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) },
151
- { tag: 'type', attrs: {}, content: KEY_BUNDLE_TYPE },
152
- { tag: 'identity', attrs: {}, content: creds.signedIdentityKey.public },
153
- { tag: 'list', attrs: {}, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) },
154
- xmppSignedPreKey(creds.signedPreKey)
155
- ]
156
- };
157
- return { update, node };
158
- };
159
- //# sourceMappingURL=signal.js.map
1
+ import { KEY_BUNDLE_TYPE } from '../Defaults/index.js'
2
+ import { assertNodeErrorFree, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildUInt, getServerFromDomainType, jidDecode, S_WHATSAPP_NET, WAJIDDomains } from '../WABinary/index.js'
3
+ import { Curve, generateSignalPubKey } from './crypto.js'
4
+ import { encodeBigEndian } from './generics.js'
5
+
6
+ const chunk = (array, size) => {
7
+ const chunks = []
8
+ for (let i = 0; i < array.length; i += size) chunks.push(array.slice(i, i + size))
9
+ return chunks
10
+ }
11
+
12
+ export const createSignalIdentity = (wid, accountSignatureKey) => ({
13
+ identifier: { name: wid, deviceId: 0 },
14
+ identifierKey: generateSignalPubKey(accountSignatureKey)
15
+ })
16
+
17
+ export const getPreKeys = async ({ get }, min, limit) => {
18
+ const idList = []
19
+ for (let id = min; id < limit; id++) idList.push(id.toString())
20
+ return get('pre-key', idList)
21
+ }
22
+
23
+ export const generateOrGetPreKeys = (creds, range) => {
24
+ const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId
25
+ const remaining = range - avaliable
26
+ const lastPreKeyId = creds.nextPreKeyId + remaining - 1
27
+ const newPreKeys = {}
28
+ if (remaining > 0) {
29
+ for (let i = creds.nextPreKeyId; i <= lastPreKeyId; i++) newPreKeys[i] = Curve.generateKeyPair()
30
+ }
31
+ return { newPreKeys, lastPreKeyId, preKeysRange: [creds.firstUnuploadedPreKeyId, range] }
32
+ }
33
+
34
+ export const xmppSignedPreKey = (key) => ({
35
+ tag: 'skey',
36
+ attrs: {},
37
+ content: [
38
+ { tag: 'id', attrs: {}, content: encodeBigEndian(key.keyId, 3) },
39
+ { tag: 'value', attrs: {}, content: key.keyPair.public },
40
+ { tag: 'signature', attrs: {}, content: key.signature }
41
+ ]
42
+ })
43
+
44
+ export const xmppPreKey = (pair, id) => ({
45
+ tag: 'key',
46
+ attrs: {},
47
+ content: [
48
+ { tag: 'id', attrs: {}, content: encodeBigEndian(id, 3) },
49
+ { tag: 'value', attrs: {}, content: pair.public }
50
+ ]
51
+ })
52
+
53
+ const isValidUInt = (n) => typeof n === 'number' && Number.isInteger(n) && n >= 0
54
+
55
+ /**
56
+ * Extract a full E2E session bundle from a retry receipt's <keys> node.
57
+ * Returns null if the bundle is missing, malformed, or fails any integrity check.
58
+ * Used in sendMessagesAgain to inject the sender's fresh session on retry.
59
+ */
60
+ export const extractE2ESessionFromRetryReceipt = (receipt) => {
61
+ const keysNode = getBinaryNodeChild(receipt, 'keys')
62
+ if (!keysNode) return null
63
+
64
+ const typeBuf = getBinaryNodeChildBuffer(keysNode, 'type')
65
+ if (!typeBuf || typeBuf.length !== 1 || typeBuf[0] !== KEY_BUNDLE_TYPE[0]) return null
66
+
67
+ const identity = getBinaryNodeChildBuffer(keysNode, 'identity')
68
+ const skey = getBinaryNodeChild(keysNode, 'skey')
69
+ if (!identity || identity.length !== 32 || !skey) return null
70
+
71
+ const registrationId = getBinaryNodeChildUInt(receipt, 'registration', 4)
72
+ if (!isValidUInt(registrationId)) return null
73
+
74
+ const signedPubKey = getBinaryNodeChildBuffer(skey, 'value')
75
+ const signedSig = getBinaryNodeChildBuffer(skey, 'signature')
76
+ const signedKeyId = getBinaryNodeChildUInt(skey, 'id', 3)
77
+ if (!signedPubKey || signedPubKey.length !== 32 || !signedSig || !isValidUInt(signedKeyId)) return null
78
+
79
+ const preKeyNode = getBinaryNodeChild(keysNode, 'key')
80
+ let preKey
81
+ if (preKeyNode) {
82
+ const preKeyPub = getBinaryNodeChildBuffer(preKeyNode, 'value')
83
+ const preKeyId = getBinaryNodeChildUInt(preKeyNode, 'id', 3)
84
+ if (!preKeyPub || preKeyPub.length !== 32 || !isValidUInt(preKeyId)) return null
85
+ preKey = { keyId: preKeyId, publicKey: generateSignalPubKey(preKeyPub) }
86
+ }
87
+
88
+ return {
89
+ registrationId,
90
+ identityKey: generateSignalPubKey(identity),
91
+ signedPreKey: { keyId: signedKeyId, publicKey: generateSignalPubKey(signedPubKey), signature: signedSig },
92
+ preKey
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Parse and inject E2E sessions from a server key response node.
98
+ * Unlike upstream, we filter error nodes individually so one bad user
99
+ * never aborts the entire batch — all valid sessions still get injected.
100
+ * CPU-heavy work is chunked in groups of 100 to yield to the event loop between batches.
101
+ */
102
+ export const parseAndInjectE2ESessions = async (node, repository) => {
103
+ const extractKey = (key) => key ? {
104
+ keyId: getBinaryNodeChildUInt(key, 'id', 3),
105
+ publicKey: generateSignalPubKey(getBinaryNodeChildBuffer(key, 'value')),
106
+ signature: getBinaryNodeChildBuffer(key, 'signature')
107
+ } : undefined
108
+
109
+ const allNodes = getBinaryNodeChildren(getBinaryNodeChild(node, 'list'), 'user')
110
+ const nodes = allNodes.filter(node => {
111
+ try { assertNodeErrorFree(node); return true }
112
+ catch { return false }
113
+ })
114
+
115
+ const chunks = chunk(nodes, 100)
116
+ for (const nodesChunk of chunks) {
117
+ for (const node of nodesChunk) {
118
+ const signedKey = getBinaryNodeChild(node, 'skey')
119
+ const key = getBinaryNodeChild(node, 'key')
120
+ const identity = getBinaryNodeChildBuffer(node, 'identity')
121
+ const jid = node.attrs.jid
122
+ const registrationId = getBinaryNodeChildUInt(node, 'registration', 4)
123
+ await repository.injectE2ESession({
124
+ jid,
125
+ session: {
126
+ registrationId,
127
+ identityKey: generateSignalPubKey(identity),
128
+ signedPreKey: extractKey(signedKey),
129
+ preKey: extractKey(key)
130
+ }
131
+ })
132
+ }
133
+ // Yield to event loop between chunks to avoid blocking on large batches
134
+ await new Promise(resolve => setImmediate(resolve))
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Extract device JIDs from a USync result list.
140
+ * Skips your own current device, respects excludeZeroDevices flag,
141
+ * and correctly resolves hosted LID/PN domain types.
142
+ */
143
+ export const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
144
+ const { user: myUser, device: myDevice } = jidDecode(myJid)
145
+ const extracted = []
146
+ for (const userResult of result) {
147
+ const { devices, id } = userResult
148
+ const decoded = jidDecode(id)
149
+ const { user, server } = decoded
150
+ let { domainType } = decoded
151
+ const deviceList = devices?.deviceList
152
+ if (!Array.isArray(deviceList)) continue
153
+ for (const { id: device, keyIndex, isHosted } of deviceList) {
154
+ if ((!excludeZeroDevices || device !== 0) &&
155
+ ((myUser !== user && myLid !== user) || myDevice !== device) &&
156
+ (device === 0 || !!keyIndex)) {
157
+ if (isHosted) domainType = domainType === WAJIDDomains.LID ? WAJIDDomains.HOSTED_LID : WAJIDDomains.HOSTED
158
+ extracted.push({ user, device, domainType, server: getServerFromDomainType(server, domainType) })
159
+ }
160
+ }
161
+ }
162
+ return extracted
163
+ }
164
+
165
+ export const getNextPreKeys = async ({ creds, keys }, count) => {
166
+ const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(creds, count)
167
+ const update = {
168
+ nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
169
+ firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1)
170
+ }
171
+ await keys.set({ 'pre-key': newPreKeys })
172
+ const preKeys = await getPreKeys(keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1])
173
+ return { update, preKeys }
174
+ }
175
+
176
+ export const getNextPreKeysNode = async (state, count) => {
177
+ const { creds } = state
178
+ const { update, preKeys } = await getNextPreKeys(state, count)
179
+ const node = {
180
+ tag: 'iq',
181
+ attrs: { xmlns: 'encrypt', type: 'set', to: S_WHATSAPP_NET },
182
+ content: [
183
+ { tag: 'registration', attrs: {}, content: encodeBigEndian(creds.registrationId) },
184
+ { tag: 'type', attrs: {}, content: KEY_BUNDLE_TYPE },
185
+ { tag: 'identity', attrs: {}, content: creds.signedIdentityKey.public },
186
+ { tag: 'list', attrs: {}, content: Object.keys(preKeys).map(k => xmppPreKey(preKeys[+k], +k)) },
187
+ xmppSignedPreKey(creds.signedPreKey)
188
+ ]
189
+ }
190
+ return { update, node }
191
+ }
@@ -0,0 +1,33 @@
1
+ import { isLidUser, isPnUser } from '../WABinary/index.js'
2
+
3
+ export const processContactAction = (action, id, logger) => {
4
+ const results = []
5
+ if (!id) {
6
+ logger?.warn({ hasFullName: !!action.fullName, hasLidJid: !!action.lidJid, hasPnJid: !!action.pnJid }, 'contactAction sync: missing id in index')
7
+ return results
8
+ }
9
+ const lidJid = action.lidJid
10
+ const idIsPn = isPnUser(id)
11
+ const phoneNumber = idIsPn ? id : action.pnJid || undefined
12
+ results.push({
13
+ event: 'contacts.upsert',
14
+ data: [{
15
+ id,
16
+ name: action.fullName || action.firstName || action.username || undefined,
17
+ username: action.username || undefined,
18
+ lid: lidJid || undefined,
19
+ phoneNumber
20
+ }]
21
+ })
22
+ if (lidJid && isLidUser(lidJid) && idIsPn) {
23
+ results.push({ event: 'lid-mapping.update', data: { lid: lidJid, pn: id } })
24
+ }
25
+ return results
26
+ }
27
+
28
+ export const emitSyncActionResults = (ev, results) => {
29
+ for (const result of results) {
30
+ if (result.event === 'contacts.upsert') ev.emit('contacts.upsert', result.data)
31
+ else ev.emit('lid-mapping.update', result.data)
32
+ }
33
+ }