@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,307 @@
1
+ import { mkdir, readFile, rename, stat, unlink, writeFile, readdir } from 'fs/promises'
2
+ import { join } from 'path'
3
+ import { Keyv } from 'keyv'
4
+ import { proto } from '../../WAProto/index.js'
5
+ import { initAuthCreds } from './auth-utils.js'
6
+ import { BufferJSON } from './generics.js'
7
+ import { PROTOCOL_ADAPTERS } from '../WABinary/index.js'
8
+
9
+ const CURRENT_VERSION = 1
10
+ const DEFAULT_PREKEY_RETENTION = 150
11
+ const DEFAULT_CLEANUP_THRESHOLD = 50
12
+ const CLEANUP_INTERVAL_MS = 10 * 60 * 1000
13
+
14
+ const bootstrapCreds = (raw) => {
15
+ const c = raw ?? initAuthCreds()
16
+ if (!c.__version || c.__version < CURRENT_VERSION) c.__version = CURRENT_VERSION
17
+ return c
18
+ }
19
+
20
+ const patchAppStateKey = (type, v) =>
21
+ type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v
22
+
23
+ // ── File-based ────────────────────────────────────────────────────────────────
24
+
25
+ export const useMultiFileAuthState = async (folder, options = {}) => {
26
+ const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger } = options
27
+
28
+ const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
29
+ const filePath = (file) => join(folder, fixFileName(file))
30
+ const tmpPath = (file) => filePath(file) + '.tmp'
31
+
32
+ const folderInfo = await stat(folder).catch(() => null)
33
+ if (folderInfo) {
34
+ if (!folderInfo.isDirectory()) throw new Error(`Path exists but is not a directory: ${folder}`)
35
+ } else {
36
+ await mkdir(folder, { recursive: true })
37
+ }
38
+
39
+ const writeData = async (data, file) => {
40
+ const fp = filePath(file)
41
+ const tp = tmpPath(file)
42
+ const payload = JSON.stringify(data, BufferJSON.replacer)
43
+ await writeFile(tp, payload)
44
+ try {
45
+ await rename(tp, fp)
46
+ } catch {
47
+ await writeFile(fp, payload)
48
+ await unlink(tp).catch(() => { })
49
+ }
50
+ }
51
+
52
+ const readData = async (file) => {
53
+ const raw = await readFile(filePath(file), { encoding: 'utf-8' }).catch(() => null)
54
+ if (!raw) return null
55
+ try {
56
+ const parsed = JSON.parse(raw, BufferJSON.reviver)
57
+ if (parsed && typeof parsed === 'object' && '__checksum' in parsed && 'data' in parsed) return parsed.data
58
+ return parsed
59
+ } catch (err) {
60
+ logger?.warn({ file, err: err.message }, 'failed to read auth file — reinitializing')
61
+ await unlink(filePath(file)).catch(() => { })
62
+ return null
63
+ }
64
+ }
65
+
66
+ const removeData = (file) => unlink(filePath(file)).catch(() => { })
67
+
68
+ let creds = bootstrapCreds(await readData('creds.json'))
69
+ let cleanupRunning = false
70
+ let lastCleanupAt = 0
71
+ let lastCleanedPreKeyId = creds.nextPreKeyId
72
+
73
+ const cleanOldPreKeys = async () => {
74
+ const now = Date.now()
75
+ if (cleanupRunning || now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
76
+ cleanupRunning = true
77
+ try {
78
+ const minId = creds.nextPreKeyId - preKeyRetention
79
+ if (minId <= 0) return
80
+ const targets = (await readdir(folder))
81
+ .map(f => f.match(/^pre-key-(\d+)\.json(\.tmp)?$/))
82
+ .filter(m => m && parseInt(m[1], 10) < minId)
83
+ .map(m => join(folder, m[0]))
84
+ if (!targets.length) return
85
+ await Promise.all(targets.map(f => unlink(f).catch(() => { })))
86
+ lastCleanupAt = Date.now()
87
+ lastCleanedPreKeyId = creds.nextPreKeyId
88
+ } catch (err) {
89
+ logger?.warn({ err }, 'prekey cleanup failed')
90
+ } finally {
91
+ cleanupRunning = false
92
+ }
93
+ }
94
+
95
+ cleanOldPreKeys().catch(() => { })
96
+
97
+ const getStats = async () => {
98
+ const files = await readdir(folder).catch(() => [])
99
+ const preKeyFiles = files.filter(f => /^pre-key-\d+\.json$/.test(f))
100
+ return {
101
+ totalFiles: files.length,
102
+ preKeyCount: preKeyFiles.length,
103
+ nextPreKeyId: creds.nextPreKeyId,
104
+ lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null,
105
+ }
106
+ }
107
+
108
+ return {
109
+ state: {
110
+ creds,
111
+ keys: {
112
+ get: async (type, ids) => {
113
+ const data = {}
114
+ await Promise.all(ids.map(async id => {
115
+ let value = await readData(`${type}-${id}.json`)
116
+ if (type === 'app-state-sync-key' && value) value = proto.Message.AppStateSyncKeyData.fromObject(value)
117
+ data[id] = value
118
+ }))
119
+ return data
120
+ },
121
+ set: async (data) => {
122
+ const tasks = []
123
+ for (const category in data) {
124
+ for (const id in data[category]) {
125
+ const value = data[category][id]
126
+ tasks.push(value ? writeData(value, `${category}-${id}.json`) : removeData(`${category}-${id}.json`))
127
+ }
128
+ }
129
+ await Promise.all(tasks)
130
+ },
131
+ },
132
+ },
133
+ saveCreds: async () => {
134
+ if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
135
+ return writeData(creds, 'creds.json')
136
+ },
137
+ getStats,
138
+ }
139
+ }
140
+
141
+ // ── Keyv-backed ───────────────────────────────────────────────────────────────
142
+
143
+ const ser = (v) => v == null ? null : JSON.parse(JSON.stringify(v, BufferJSON.replacer))
144
+
145
+ const unwrapNestedValue = (obj) => {
146
+ let cur = obj
147
+ while (cur && typeof cur === 'object' && !Array.isArray(cur) && Object.keys(cur).length === 1 && 'value' in cur) cur = cur.value
148
+ return cur
149
+ }
150
+
151
+ const deser = (raw, logger) => {
152
+ if (raw == null) return null
153
+ let parsed
154
+ if (typeof raw === 'string') {
155
+ try { parsed = JSON.parse(raw) } catch (err) {
156
+ logger?.warn?.({ err: err.message }, '[Auth] failed to parse stored value — treating as missing')
157
+ return null
158
+ }
159
+ } else {
160
+ parsed = raw
161
+ }
162
+ parsed = unwrapNestedValue(parsed)
163
+ if (parsed == null) return null
164
+ if (typeof parsed === 'object' && '__checksum' in parsed && 'data' in parsed) parsed = parsed.data
165
+ return JSON.parse(JSON.stringify(parsed), BufferJSON.reviver)
166
+ }
167
+
168
+ const COLLECTION_OPTION_KEY = {
169
+ '@keyv/mongo': 'collection',
170
+ '@keyv/postgres': 'table',
171
+ '@keyv/mysql': 'table',
172
+ '@keyv/sqlite': 'table',
173
+ }
174
+
175
+ const _adapterCache = new Map()
176
+ export const clearAdapterCache = () => _adapterCache.clear()
177
+
178
+ const resolveAdapter = async (connectionString, collectionName) => {
179
+ const cacheKey = collectionName ? `${connectionString}::${collectionName}` : connectionString
180
+ if (_adapterCache.has(cacheKey)) return _adapterCache.get(cacheKey)
181
+
182
+ let protocol
183
+ try { protocol = new URL(connectionString).protocol } catch { throw new Error(`[Auth] Invalid connection string: "${connectionString}"`) }
184
+
185
+ const pkg = PROTOCOL_ADAPTERS[protocol]
186
+ if (!pkg) throw new Error(`[Auth] Unsupported protocol "${protocol}" — supported: redis, mongodb, postgresql, mysql, sqlite, etcd, memcache. Pass a pre-built KeyvStoreAdapter instance for anything else.`)
187
+
188
+ let mod
189
+ try { mod = await import(pkg) } catch (e) {
190
+ if (e.code === 'ERR_MODULE_NOT_FOUND') throw new Error(`[Auth] "${pkg}" is required for this backend — install it: npm i ${pkg}`)
191
+ throw e
192
+ }
193
+
194
+ const Adapter = mod.default ?? mod[Object.keys(mod)[0]]
195
+ const optionKey = COLLECTION_OPTION_KEY[pkg]
196
+ const adapter = (collectionName && optionKey)
197
+ ? new Adapter({ url: connectionString, uri: connectionString, [optionKey]: collectionName })
198
+ : new Adapter(connectionString)
199
+ adapter.setMaxListeners?.(0)
200
+ _adapterCache.set(cacheKey, adapter)
201
+ return adapter
202
+ }
203
+
204
+ const isAdapterLike = (v) =>
205
+ v != null &&
206
+ typeof v === 'object' &&
207
+ typeof v.get === 'function' &&
208
+ typeof v.set === 'function' &&
209
+ typeof v.delete === 'function' &&
210
+ typeof v.clear === 'function'
211
+
212
+ const resolveStore = async (backend, collectionName) => {
213
+ if (backend == null) return null
214
+ if (typeof backend === 'string') return resolveAdapter(backend, collectionName)
215
+ if (backend instanceof Keyv) return backend.opts?.store ?? backend.store ?? null
216
+ if (isAdapterLike(backend)) return backend
217
+ throw new Error('[Auth] backend must be a connection string, Keyv instance, or KeyvStoreAdapter')
218
+ }
219
+
220
+ export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
221
+ const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger, collectionName } = opts
222
+
223
+ const store = await resolveStore(backend, collectionName)
224
+ const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
225
+ keyv.on('error', (err) => logger?.warn?.({ err: err?.message }, '[Auth] store error'))
226
+
227
+ const keyName = (type, id) => `${type}-${id}`
228
+
229
+ let creds = bootstrapCreds(deser(await keyv.get('creds'), logger))
230
+ let cleanupRunning = false
231
+ let lastCleanupAt = 0
232
+ let lastCleanedPreKeyId = creds.nextPreKeyId
233
+
234
+ const cleanOldPreKeys = async () => {
235
+ const now = Date.now()
236
+ if (cleanupRunning || now - lastCleanupAt < CLEANUP_INTERVAL_MS) return
237
+ cleanupRunning = true
238
+ try {
239
+ const minId = creds.nextPreKeyId - preKeyRetention
240
+ if (minId <= 0) return
241
+ const ids = []
242
+ for (let id = Math.max(0, lastCleanedPreKeyId - preKeyRetention); id < minId; id++) ids.push(keyName('pre-key', id))
243
+ if (!ids.length) return
244
+ await Promise.all(ids.map(k => keyv.delete(k).catch(() => { })))
245
+ lastCleanupAt = Date.now()
246
+ lastCleanedPreKeyId = creds.nextPreKeyId
247
+ } catch (e) {
248
+ logger?.warn?.({ err: e.message }, '[Auth] prekey cleanup failed')
249
+ } finally {
250
+ cleanupRunning = false
251
+ }
252
+ }
253
+
254
+ cleanOldPreKeys().catch(() => { })
255
+
256
+ const keys = {
257
+ get: async (type, ids) => {
258
+ const data = {}
259
+ if (!ids.length) return data
260
+ const rawKeys = ids.map(id => keyName(type, id))
261
+ const rawValues = await keyv.getMany(rawKeys)
262
+ const rewrites = []
263
+ ids.forEach((id, i) => {
264
+ data[id] = patchAppStateKey(type, deser(rawValues[i], logger))
265
+ })
266
+ if (rewrites.length) await Promise.all(rewrites).catch(() => { })
267
+ return data
268
+ },
269
+ set: async (data) => {
270
+ const setEntries = []
271
+ const delKeys = []
272
+ for (const type in data) {
273
+ for (const id in data[type]) {
274
+ const value = data[type][id]
275
+ const key = keyName(type, id)
276
+ if (value != null) setEntries.push({ key, value: ser(value) })
277
+ else delKeys.push(key)
278
+ }
279
+ }
280
+ await Promise.all([
281
+ setEntries.length ? keyv.setMany(setEntries) : null,
282
+ delKeys.length ? keyv.deleteMany(delKeys) : null,
283
+ ])
284
+ },
285
+ }
286
+
287
+ const saveCreds = async () => {
288
+ if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
289
+ await keyv.set('creds', ser(creds))
290
+ }
291
+
292
+ const clearSession = async () => {
293
+ const s = keyv.opts?.store ?? keyv.store
294
+ if (s && typeof s.deleteMany === 'function' && typeof s.iterator === 'function') {
295
+ const toDelete = []
296
+ for await (const [key] of s.iterator(sessionId)) toDelete.push(key)
297
+ if (toDelete.length) await s.deleteMany(toDelete)
298
+ } else {
299
+ await keyv.clear()
300
+ }
301
+ keyv.removeAllListeners?.()
302
+ }
303
+
304
+ return { state: { creds, keys }, saveCreds, clearSession }
305
+ }
306
+
307
+ export { Keyv }
@@ -0,0 +1,181 @@
1
+ import { Boom } from '@hapi/boom'
2
+ import { createHash } from 'crypto'
3
+ import { proto } from '../../WAProto/index.js'
4
+ import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX } from '../Defaults/index.js'
5
+ import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js'
6
+ import { Curve, hmacSign } from './crypto.js'
7
+ import { encodeBigEndian } from './generics.js'
8
+ import { createSignalIdentity } from './signal.js'
9
+
10
+ const PLATFORM_MAP = {
11
+ 'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
12
+ Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32,
13
+ }
14
+
15
+ const getUserAgent = (config) => ({
16
+ appVersion: {
17
+ primary: config.version[0],
18
+ secondary: config.version[1],
19
+ tertiary: config.version[2],
20
+ },
21
+ platform: config.browser[1].toLocaleLowerCase().includes('android')
22
+ ? proto.ClientPayload.UserAgent.Platform.MACOS
23
+ : proto.ClientPayload.UserAgent.Platform.WEB,
24
+ releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
25
+ osVersion: '0.1',
26
+ device: 'Desktop',
27
+ osBuildNumber: '0.1',
28
+ localeLanguageIso6391: 'en',
29
+ mnc: '000',
30
+ mcc: '000',
31
+ localeCountryIso31661Alpha2: config.countryCode,
32
+ })
33
+
34
+ const getWebInfo = (config) => {
35
+ let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER
36
+ if (config.syncFullHistory && PLATFORM_MAP[config.browser[0]] && config.browser[1] === 'Desktop')
37
+ webSubPlatform = PLATFORM_MAP[config.browser[0]]
38
+ return { webSubPlatform }
39
+ }
40
+
41
+ const getClientPayload = (config) => {
42
+ const payload = {
43
+ connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
44
+ connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
45
+ userAgent: getUserAgent(config),
46
+ }
47
+ if (!config.browser[1].toLowerCase().includes('android')) payload.webInfo = getWebInfo(config)
48
+ return payload
49
+ }
50
+
51
+ const getPlatformType = (platform) => {
52
+ const platformType = platform.toUpperCase()
53
+ if (platformType === 'ANDROID') return proto.DeviceProps.PlatformType.ANDROID_PHONE
54
+ return proto.DeviceProps.PlatformType[platformType] ?? proto.DeviceProps.PlatformType.CHROME
55
+ }
56
+
57
+ export const generateLoginNode = (userJid, config) => {
58
+ const { user, device } = jidDecode(userJid)
59
+ return proto.ClientPayload.fromObject({
60
+ ...getClientPayload(config),
61
+ passive: true,
62
+ pull: true,
63
+ username: +user,
64
+ device,
65
+ lidDbMigrated: false, // hard-set; behaviour under true is unknown
66
+ })
67
+ }
68
+
69
+ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedIdentityKey }, config) => {
70
+ const appVersionBuf = createHash('md5').update(config.version.join('.')).digest()
71
+ const companionProto = proto.DeviceProps.encode({
72
+ os: config.browser[0],
73
+ platformType: getPlatformType(config.browser[1]),
74
+ requireFullSync: config.syncFullHistory,
75
+ historySyncConfig: {
76
+ storageQuotaMb: 569150,
77
+ inlineInitialPayloadInE2EeMsg: true,
78
+ recentSyncDaysLimit: undefined,
79
+ supportCallLogHistory: false,
80
+ supportBotUserAgentChatHistory: true,
81
+ supportCagReactionsAndPolls: true,
82
+ supportBizHostedMsg: true,
83
+ supportRecentSyncChunkMessageCountTuning: true,
84
+ supportHostedGroupMsg: true,
85
+ supportFbidBotChatHistory: true,
86
+ supportAddOnHistorySyncMigration: undefined,
87
+ supportMessageAssociation: true,
88
+ supportGroupHistory: false,
89
+ onDemandReady: undefined,
90
+ supportGuestChat: undefined,
91
+ },
92
+ version: { primary: 10, secondary: 15, tertiary: 7 },
93
+ }).finish()
94
+
95
+ return proto.ClientPayload.fromObject({
96
+ ...getClientPayload(config),
97
+ passive: false,
98
+ pull: false,
99
+ devicePairingData: {
100
+ buildHash: appVersionBuf,
101
+ deviceProps: companionProto,
102
+ eRegid: encodeBigEndian(registrationId),
103
+ eKeytype: KEY_BUNDLE_TYPE,
104
+ eIdent: signedIdentityKey.public,
105
+ eSkeyId: encodeBigEndian(signedPreKey.keyId, 3),
106
+ eSkeyVal: signedPreKey.keyPair.public,
107
+ eSkeySig: signedPreKey.signature,
108
+ },
109
+ })
110
+ }
111
+
112
+ export const configureSuccessfulPairing = (stanza, { advSecretKey, signedIdentityKey, signalIdentities }) => {
113
+ const msgId = stanza.attrs.id
114
+ const pairSuccessNode = getBinaryNodeChild(stanza, 'pair-success')
115
+ const deviceIdentityNode = getBinaryNodeChild(pairSuccessNode, 'device-identity')
116
+ const platformNode = getBinaryNodeChild(pairSuccessNode, 'platform')
117
+ const deviceNode = getBinaryNodeChild(pairSuccessNode, 'device')
118
+ const businessNode = getBinaryNodeChild(pairSuccessNode, 'biz')
119
+
120
+ if (!deviceIdentityNode || !deviceNode)
121
+ throw new Boom('Missing device-identity or device in pair success node', { data: stanza })
122
+
123
+ const jid = deviceNode.attrs.jid
124
+ const lid = deviceNode.attrs.lid
125
+ const bizName = businessNode?.attrs.name
126
+
127
+ const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content)
128
+ const hmacPrefix = (accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED)
129
+ ? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
130
+ : Buffer.from([])
131
+
132
+ if (Buffer.compare(hmac, hmacSign(Buffer.concat([hmacPrefix, details]), Buffer.from(advSecretKey, 'base64'))) !== 0)
133
+ throw new Boom('Invalid account signature')
134
+
135
+ const account = proto.ADVSignedDeviceIdentity.decode(details)
136
+ const { accountSignatureKey, accountSignature, details: deviceDetails } = account
137
+ const deviceIdentity = proto.ADVDeviceIdentity.decode(deviceDetails)
138
+
139
+ const accountSignaturePrefix = deviceIdentity.deviceType === proto.ADVEncryptionType.HOSTED
140
+ ? WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX
141
+ : WA_ADV_ACCOUNT_SIG_PREFIX
142
+
143
+ if (!Curve.verify(accountSignatureKey, Buffer.concat([accountSignaturePrefix, deviceDetails, signedIdentityKey.public]), accountSignature))
144
+ throw new Boom('Failed to verify account signature')
145
+
146
+ account.deviceSignature = Curve.sign(
147
+ signedIdentityKey.private,
148
+ Buffer.concat([WA_ADV_DEVICE_SIG_PREFIX, deviceDetails, signedIdentityKey.public, accountSignatureKey])
149
+ )
150
+
151
+ const identity = createSignalIdentity(lid, accountSignatureKey)
152
+ const accountEnc = encodeSignedDeviceIdentity(account, false)
153
+
154
+ return {
155
+ creds: {
156
+ account,
157
+ me: { id: jid, name: bizName, lid },
158
+ signalIdentities: [...(signalIdentities ?? []), identity],
159
+ platform: platformNode?.attrs.name,
160
+ },
161
+ reply: {
162
+ tag: 'iq',
163
+ attrs: { to: S_WHATSAPP_NET, type: 'result', id: msgId },
164
+ content: [{
165
+ tag: 'pair-device-sign',
166
+ attrs: {},
167
+ content: [{
168
+ tag: 'device-identity',
169
+ attrs: { 'key-index': deviceIdentity.keyIndex.toString() },
170
+ content: accountEnc,
171
+ }],
172
+ }],
173
+ },
174
+ }
175
+ }
176
+
177
+ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
178
+ account = { ...account }
179
+ if (!includeSignatureKey || !account.accountSignatureKey?.length) account.accountSignatureKey = null
180
+ return proto.ADVSignedDeviceIdentity.encode(account).finish()
181
+ }