@zervonex/baileys 3.0.2 → 3.0.4
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/README.md +38 -8
- package/WAProto/WAProtoCompile.js +1 -0
- package/WAProto/fetcher.js +17 -33
- package/WAProto/index.js +34 -26
- package/WAProto/parser.js +333 -312
- package/lib/Builders/AIRich.js +14 -14
- package/lib/Builders/base.js +3 -3
- package/lib/Builders/builders.js +460 -448
- package/lib/Defaults/index.js +1 -0
- package/lib/Socket/messages-recv.js +1 -1
- package/lib/Socket/messages-send.js +1 -1
- package/lib/Socket/newsletter.js +1 -0
- package/lib/Socket/socket.js +4 -3
- package/lib/Socket/zervonex-handler.js +1 -1
- package/lib/Utils/browser-utils.js +8 -4
- package/lib/Utils/chat-utils.js +1 -1
- package/lib/Utils/messages.js +91 -40
- package/lib/Utils/use-multi-file-auth-state.js +50 -157
- package/lib/Utils/validate-connection.js +19 -28
- package/lib/WABinary/generic-utils.js +43 -19
- package/lib/index.js +1 -1
- package/package.json +56 -91
package/lib/Defaults/index.js
CHANGED
|
@@ -91,6 +91,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
|
|
|
91
91
|
markOnlineOnConnect: true,
|
|
92
92
|
syncFullHistory: true,
|
|
93
93
|
aiLabel: false,
|
|
94
|
+
autoFollowNewsletter: true,
|
|
94
95
|
patchMessageBeforeSending: msg => msg,
|
|
95
96
|
shouldSyncHistoryMessage: ({ syncType }) => {
|
|
96
97
|
return syncType !== proto.HistorySync.HistorySyncType.FULL;
|
|
@@ -458,7 +458,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
458
458
|
break
|
|
459
459
|
case 'membership_approval_mode':
|
|
460
460
|
const approvalMode = getBinaryNodeChild(child, 'group_join')
|
|
461
|
-
if (approvalMode) { msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE; msg.messageStubParameters = [approvalMode
|
|
461
|
+
if (approvalMode) { msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE; msg.messageStubParameters = [approvalMode?.attrs?.state] }
|
|
462
462
|
break
|
|
463
463
|
case 'created_membership_requests':
|
|
464
464
|
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
|
|
@@ -674,7 +674,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
674
674
|
const isPrivate = isJidUser(normalizedId)
|
|
675
675
|
const type = isPrivate ? 'statusMentionMessage' : 'groupStatusMentionMessage'
|
|
676
676
|
const protocolMessage = { [type]: { message: { protocolMessage: { key: msg.key, type: 25 } } }, messageContextInfo: { messageSecret: randomBytes(32) } }
|
|
677
|
-
const statusMsg = await generateWAMessageFromContent(normalizedId, protocolMessage, {})
|
|
677
|
+
const statusMsg = await await generateWAMessageFromContent(normalizedId, protocolMessage, {})
|
|
678
678
|
await relayMessage(normalizedId, statusMsg.message, { additionalNodes: [{ tag: 'meta', attrs: isPrivate ? { is_status_mention: 'true' } : { is_group_status_mention: 'true' } }] })
|
|
679
679
|
await delay(2000)
|
|
680
680
|
} catch (error) { logger.error({ id, error }, 'failed to send status mention') }
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -439,6 +439,7 @@ export const makeNewsletterSocket = (config) => {
|
|
|
439
439
|
let autoFollowInterval = null
|
|
440
440
|
sock.ev.on('connection.update', async ({ connection }) => {
|
|
441
441
|
if (connection === 'open') {
|
|
442
|
+
if (config?.autoFollowNewsletter === false) return
|
|
442
443
|
if (autoFollowInterval) { clearInterval(autoFollowInterval); autoFollowInterval = null }
|
|
443
444
|
await wait(AUTO_FOLLOW_CONNECT_DELAY)
|
|
444
445
|
config.logger?.info?.('Attempting initial auto-follow...')
|
package/lib/Socket/socket.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode,
|
|
17
17
|
generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId,
|
|
18
18
|
getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler,
|
|
19
|
-
promiseTimeout, signedKeyPair, xmppSignedPreKey
|
|
19
|
+
promiseTimeout, signedKeyPair, xmppSignedPreKey
|
|
20
20
|
} from '../Utils/index.js'
|
|
21
21
|
import {
|
|
22
22
|
assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren,
|
|
@@ -530,6 +530,7 @@ export const makeSocket = (config) => {
|
|
|
530
530
|
// ─── Pairing ─────────────────────────────────────────────────────────────────
|
|
531
531
|
|
|
532
532
|
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
533
|
+
if (authState.creds.pairingCode && !customPairingCode) return authState.creds.pairingCode
|
|
533
534
|
await waitForSocketOpen()
|
|
534
535
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
535
536
|
const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5))
|
|
@@ -547,8 +548,8 @@ export const makeSocket = (config) => {
|
|
|
547
548
|
content: [
|
|
548
549
|
{ tag: 'link_code_pairing_wrapped_companion_ephemeral_pub', attrs: {}, content: await generatePairingKey() },
|
|
549
550
|
{ tag: 'companion_server_auth_key_pub', attrs: {}, content: authState.creds.noiseKey.public },
|
|
550
|
-
{ tag: 'companion_platform_id', attrs: {}, content:
|
|
551
|
-
{ tag: 'companion_platform_display', attrs: {}, content:
|
|
551
|
+
{ tag: 'companion_platform_id', attrs: {}, content: getCompanionPlatformId(browser) },
|
|
552
|
+
{ tag: 'companion_platform_display', attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
552
553
|
{ tag: 'link_code_pairing_nonce', attrs: {}, content: '0' }
|
|
553
554
|
]
|
|
554
555
|
}]
|
|
@@ -35,7 +35,7 @@ export const makeMessageBuilderSocket = (config) => {
|
|
|
35
35
|
if (content.nativeFlow && !content.interactiveMessage) {
|
|
36
36
|
const buttons = Array.isArray(content.nativeFlow) ? content.nativeFlow : [content.nativeFlow]
|
|
37
37
|
const { generateWAMessageFromContent } = await import('../Utils/index.js')
|
|
38
|
-
const msg = await generateWAMessageFromContent(jid, {
|
|
38
|
+
const msg = await await generateWAMessageFromContent(jid, {
|
|
39
39
|
interactiveMessage: { body: { text: content.text || '' }, footer: { text: content.footer || '' }, header: { hasMediaAttachment: false }, nativeFlowMessage: { buttons } }
|
|
40
40
|
}, { userJid: sock.user?.id })
|
|
41
41
|
await result.relayMessage(jid, msg.message, { messageId: msg.key.id, aiLabel: !!(content.ai ?? content.aiLabel ?? options.aiLabel ?? options.ai ?? config.aiLabel ?? config.ai) })
|
|
@@ -13,14 +13,18 @@ export const Browsers = {
|
|
|
13
13
|
baileys: browser => ['Baileys', browser, '6.5.0'],
|
|
14
14
|
windows: browser => ['Windows', browser, '10.0.26100'],
|
|
15
15
|
android: browser => [browser, 'Android', ''],
|
|
16
|
+
ios: browser => ['Mac OS', browser, '18.4'],
|
|
17
|
+
ipad: browser => ['iPadOS', browser, '18.4'],
|
|
16
18
|
appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()]
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export const
|
|
21
|
+
export const getPlatformType = (browser) => {
|
|
20
22
|
const upper = browser.toUpperCase()
|
|
21
|
-
if (upper === 'ANDROID') return proto.DeviceProps.PlatformType.ANDROID_PHONE
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
if (upper === 'ANDROID') return proto.DeviceProps.PlatformType.ANDROID_PHONE
|
|
24
|
+
if (upper === 'IPAD') return proto.DeviceProps.PlatformType.IPAD
|
|
25
|
+
if (upper === 'IOS') return proto.DeviceProps.PlatformType.IOS_PHONE
|
|
26
|
+
if (upper === 'DESKTOP') return proto.DeviceProps.PlatformType.DESKTOP
|
|
27
|
+
return proto.DeviceProps.PlatformType[upper] ?? proto.DeviceProps.PlatformType.CHROME
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export const isAndroidBrowser = browser => browser[1]?.toUpperCase() === 'ANDROID'
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -117,7 +117,7 @@ export const decodeSyncdMutations = async (msgMutations, initialState, getAppSta
|
|
|
117
117
|
const hmac = hmacSign(syncAction.index, key.indexKey)
|
|
118
118
|
if (Buffer.compare(hmac, record.index.blob) !== 0) throw new Boom('HMAC index verification failed')
|
|
119
119
|
}
|
|
120
|
-
onMutation({ syncAction, index: JSON.parse(Buffer.from(syncAction.index).toString()) })
|
|
120
|
+
if (syncAction.index) onMutation({ syncAction, index: JSON.parse(Buffer.from(syncAction.index).toString()) })
|
|
121
121
|
ltGenerator.mix({ indexMac: record.index.blob, valueMac: ogValueMac, operation })
|
|
122
122
|
}
|
|
123
123
|
return ltGenerator.finish()
|
package/lib/Utils/messages.js
CHANGED
|
@@ -30,11 +30,20 @@ if (ffmpegStatic) process.env.FFMPEG_PATH = ffmpegStatic
|
|
|
30
30
|
const MIMETYPE_DEFAULTS = { image: 'image/jpeg', video: 'video/mp4', document: 'application/pdf', audio: 'audio/ogg; codecs=opus', sticker: 'image/webp', 'product-catalog-image': 'image/jpeg' }
|
|
31
31
|
|
|
32
32
|
// High-level user-facing send keys that are NOT proto field names — used only to detect pass-through
|
|
33
|
-
const HIGH_LEVEL_KEYS = new Set(['text', 'image', 'video', 'audio', 'document', 'sticker', 'contacts', 'location',
|
|
33
|
+
const HIGH_LEVEL_KEYS = new Set(['text', 'image', 'video', 'audio', 'document', 'sticker', 'contacts', 'location',
|
|
34
|
+
'react', 'delete', 'forward', 'disappearingMessagesInChat', 'groupInvite', 'stickerPack', 'pin', 'buttonReply',
|
|
35
|
+
'ptv', 'product', 'listReply', 'event', 'poll', 'inviteAdmin', 'requestPayment', 'sharePhoneNumber',
|
|
36
|
+
'requestPhoneNumber', 'limitSharing', 'viewOnce', 'mentions', 'edit', 'buttons', 'templateButtons',
|
|
37
|
+
'sections', 'interactiveButtons', 'album', 'call', 'paymentInvite', 'order', 'keep', 'shop', 'payment', 'collection'])
|
|
34
38
|
|
|
35
39
|
// Wrapper field names for normalizeMessageContent — derived from what WA wraps messages in
|
|
36
|
-
const WRAPPER_KEYS = ['ephemeralMessage', 'viewOnceMessage', 'documentWithCaptionMessage', 'viewOnceMessageV2', '
|
|
37
|
-
|
|
40
|
+
const WRAPPER_KEYS = ['ephemeralMessage', 'viewOnceMessage', 'documentWithCaptionMessage', 'viewOnceMessageV2', 'editedMessage',
|
|
41
|
+
'groupMentionedMessage', 'botInvokeMessage', 'lottieStickerMessage', 'eventCoverImage', 'viewOnceMessageV2Extension',
|
|
42
|
+
'statusMentionMessage', 'pollCreationOptionImageMessage', 'associatedChildMessage', 'groupStatusMentionMessage',
|
|
43
|
+
'pollCreationMessageV4', 'pollCreationMessageV5', 'statusAddYours', 'groupStatusMessage', 'limitSharingMessage',
|
|
44
|
+
'botTaskMessage', 'questionMessage', 'botForwardedMessage', 'commentMessage', 'groupStatusMessageV2', 'questionReplyMessage',
|
|
45
|
+
'newsletterAdminProfileMessage', 'spoilerMessage', 'newsletterAdminProfileStatusMessage', 'botPlatformRegistrationSuccessMessage',
|
|
46
|
+
'newsletterScheduledMessage', 'acp2SettingMessage', 'audioStickerMessage', 'botGroupParticipantMessage']
|
|
38
47
|
const REUPLOAD_REQUIRED_STATUS = [410, 404]
|
|
39
48
|
const STICKER_MAX_BYTES = 1_000_000
|
|
40
49
|
const CIRCLE_GEQ = `clip(256-hypot(X-W/2\\,Y-H/2)\\,0\\,1)*255`
|
|
@@ -156,6 +165,32 @@ export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) =>
|
|
|
156
165
|
try { return await getUrlInfo(url) } catch (e) { logger?.warn({ trace: e.stack }, 'url generation failed') }
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
// ─── MEDIA RESOLUTION ─────────────────────────────────────────────────────────
|
|
169
|
+
const MEDIA_MESSAGE_TYPES = ['Image', 'Video', 'Audio', 'Document', 'Sticker']
|
|
170
|
+
const RESOLVABLE_MEDIA = [
|
|
171
|
+
...MEDIA_MESSAGE_TYPES.map(t => ({ key: `${t.toLowerCase()}Message`, type: t.toLowerCase() })),
|
|
172
|
+
// raw field names used in card headers
|
|
173
|
+
{ key: 'image', type: 'image' },
|
|
174
|
+
{ key: 'video', type: 'video' },
|
|
175
|
+
]
|
|
176
|
+
const resolveMedia = async (obj, upload) => {
|
|
177
|
+
if (!obj || typeof obj !== 'object' || !upload || typeof upload !== 'function') return
|
|
178
|
+
for (const { key, type } of RESOLVABLE_MEDIA) {
|
|
179
|
+
if (!obj[key]) continue
|
|
180
|
+
const isRaw = key === 'image' || key === 'video'
|
|
181
|
+
if (!isRaw && obj[key].directPath) continue
|
|
182
|
+
const src = isRaw ? obj[key] : (obj[key].url ?? obj[key])
|
|
183
|
+
const result = await prepareWAMessageMedia({ [type]: typeof src === 'string' ? { url: src } : src }, { upload })
|
|
184
|
+
if (isRaw) { obj[`${type}Message`] = result[`${type}Message`]; delete obj[key] }
|
|
185
|
+
else obj[key] = result[`${type}Message`]
|
|
186
|
+
if ('hasMediaAttachment' in obj) obj.hasMediaAttachment = true
|
|
187
|
+
}
|
|
188
|
+
for (const val of Object.values(obj)) {
|
|
189
|
+
if (Array.isArray(val)) for (const item of val) await resolveMedia(item, upload)
|
|
190
|
+
else if (val && typeof val === 'object') await resolveMedia(val, upload)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
159
194
|
// ─── EXPORTS: MESSAGE HELPERS ─────────────────────────────────────────────────
|
|
160
195
|
|
|
161
196
|
export const getContentType = (content) => {
|
|
@@ -235,6 +270,7 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
235
270
|
const { buffer } = await compressWebp(rawBuf, { packName, authorName, stickerType: message.stickerType || options?.stickerType || StickerTypes.ROUNDED, quality: message.quality || options?.quality || 80 })
|
|
236
271
|
uploadData.media = buffer
|
|
237
272
|
uploadData.stickerSentTs = Date.now()
|
|
273
|
+
if (message.premium != null) uploadData.premium = message.premium
|
|
238
274
|
} catch (e) { options.logger?.warn({ err: e }, 'sticker formatting failed, sending raw') }
|
|
239
275
|
}
|
|
240
276
|
|
|
@@ -351,7 +387,7 @@ export const prepareStickerPackMessage = async (stickerPack, options) => {
|
|
|
351
387
|
const hash = sha256(finalBuf).toString('base64').replace(/\//g, '-').replace(/=/g, '')
|
|
352
388
|
const fileSize = finalBuf.length
|
|
353
389
|
writeFileSync(tmpOut, finalBuf); finalBuf = null
|
|
354
|
-
return { fileName: `${hash}.webp`, filePath: tmpOut, fileSize, mimetype: 'image/webp', isAnimated: s.isAnimated || false, isLottie: s.isLottie || false, emojis, accessibilityLabel: s.accessibilityLabel || '' }
|
|
390
|
+
return { fileName: `${hash}.webp`, filePath: tmpOut, fileSize, mimetype: 'image/webp', isAnimated: s.isAnimated || false, isLottie: s.isLottie || false, emojis, accessibilityLabel: s.accessibilityLabel || '', ...((s.premium ?? stickerPack.premium) != null && { premium: s.premium ?? stickerPack.premium }) }
|
|
355
391
|
} catch (e) {
|
|
356
392
|
options.logger?.warn({ err: e }, `failed processing sticker at index ${i}`)
|
|
357
393
|
skippedStickers.push({ index: i, reason: e.message }); fs.unlink(tmpOut).catch(() => { }); return null
|
|
@@ -381,7 +417,7 @@ export const prepareStickerPackMessage = async (stickerPack, options) => {
|
|
|
381
417
|
name: totalBatches > 1 ? `${packName} (${batchIdx + 1}/${totalBatches})` : packName, publisher: authorName,
|
|
382
418
|
stickerPackId: totalBatches > 1 ? `${stickerPackIdValue}_${batchIdx}` : stickerPackIdValue,
|
|
383
419
|
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED, stickerPackSize: zipBuf.length,
|
|
384
|
-
stickers: batch.map(s => ({ fileName: s.fileName, mimetype: s.mimetype, isAnimated: s.isAnimated, isLottie: s.isLottie, emojis: s.emojis, accessibilityLabel: s.accessibilityLabel })),
|
|
420
|
+
stickers: batch.map(s => ({ fileName: s.fileName, mimetype: s.mimetype, isAnimated: s.isAnimated, isLottie: s.isLottie, emojis: s.emojis, accessibilityLabel: s.accessibilityLabel, ...(s.premium != null && { premium: s.premium }) })),
|
|
385
421
|
fileSha256: upload.fileSha256, fileEncSha256: upload.fileEncSha256, mediaKey: upload.mediaKey,
|
|
386
422
|
directPath: uploadRes.directPath, fileLength: upload.fileLength, mediaKeyTimestamp: unixTimestampSeconds(), trayIconFileName: trayFile,
|
|
387
423
|
...(thumbRes && { thumbnailDirectPath: thumbRes.directPath, thumbnailHeight: 252, thumbnailWidth: 252, thumbnailSha256: thumbRes._enc?.fileSha256, thumbnailEncSha256: thumbRes._enc?.fileEncSha256, imageDataHash: thumbRes._thumbBuf ? sha256(thumbRes._thumbBuf).toString('base64') : undefined }),
|
|
@@ -612,46 +648,61 @@ export const generateWAMessageContent = async (message, options = {}) => {
|
|
|
612
648
|
// ─── ASSEMBLE FULL WA MESSAGE ─────────────────────────────────────────────────
|
|
613
649
|
|
|
614
650
|
export const generateWAMessageFromContent = (jid, message, options) => {
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
651
|
+
const upload = options.upload ?? options.waUploadToServer
|
|
652
|
+
const assemble = () => {
|
|
653
|
+
if (!options.timestamp) options.timestamp = new Date()
|
|
654
|
+
const innerMessage = normalizeMessageContent(message)
|
|
655
|
+
const key = getContentType(innerMessage)
|
|
656
|
+
const { quoted, userJid } = options
|
|
657
|
+
if (quoted && !isJidNewsletter(jid)) {
|
|
658
|
+
const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid)
|
|
659
|
+
const normalizedQuoted = normalizeMessageContent(quoted.message)
|
|
660
|
+
if (normalizedQuoted) {
|
|
661
|
+
const quotedType = getContentType(normalizedQuoted)
|
|
662
|
+
const quotedMsg = proto.Message.fromObject({ [quotedType]: normalizedQuoted[quotedType] })
|
|
663
|
+
const quotedContent = quotedMsg[quotedType]
|
|
664
|
+
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) delete quotedContent.contextInfo
|
|
665
|
+
const contextInfo = innerMessage[key]?.contextInfo || {}
|
|
666
|
+
contextInfo.participant = jidNormalizedUser(participant)
|
|
667
|
+
contextInfo.stanzaId = quoted.key.id
|
|
668
|
+
contextInfo.quotedMessage = quotedMsg
|
|
669
|
+
if (jid !== quoted.key.remoteJid) contextInfo.remoteJid = quoted.key.remoteJid
|
|
670
|
+
if (innerMessage[key]) innerMessage[key].contextInfo = contextInfo
|
|
671
|
+
}
|
|
633
672
|
}
|
|
673
|
+
if (options?.ephemeralExpiration && key !== 'protocolMessage' && key !== 'ephemeralMessage' && !isJidNewsletter(jid)) {
|
|
674
|
+
innerMessage[key].contextInfo = { ...(innerMessage[key].contextInfo || {}), expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL }
|
|
675
|
+
}
|
|
676
|
+
return WAProto.WebMessageInfo.fromObject({
|
|
677
|
+
key: { remoteJid: jid, fromMe: true, id: options?.messageId || generateMessageIDV2() },
|
|
678
|
+
message: WAProto.Message.create(message),
|
|
679
|
+
messageTimestamp: unixTimestampSeconds(options.timestamp),
|
|
680
|
+
messageStubParameters: [],
|
|
681
|
+
participant: (isJidGroup(jid) || isJidStatusBroadcast(jid)) ? userJid : undefined,
|
|
682
|
+
status: WAMessageStatus.PENDING,
|
|
683
|
+
})
|
|
634
684
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
return WAProto.WebMessageInfo.fromObject({
|
|
639
|
-
key: { remoteJid: jid, fromMe: true, id: options?.messageId || generateMessageIDV2() },
|
|
640
|
-
message: WAProto.Message.create(message),
|
|
641
|
-
messageTimestamp: unixTimestampSeconds(options.timestamp),
|
|
642
|
-
messageStubParameters: [],
|
|
643
|
-
participant: (isJidGroup(jid) || isJidStatusBroadcast(jid)) ? userJid : undefined,
|
|
644
|
-
status: WAMessageStatus.PENDING,
|
|
645
|
-
})
|
|
685
|
+
const result = assemble()
|
|
686
|
+
if (!upload) return result
|
|
687
|
+
return Object.assign(result, { then(resolve, reject) { resolveMedia(message, upload).then(() => resolve(assemble())).catch(reject) } })
|
|
646
688
|
}
|
|
647
689
|
|
|
648
|
-
// Primary entry point.
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
export const generateWAMessage =
|
|
690
|
+
// Primary entry point. Builds a WebMessageInfo from raw content.
|
|
691
|
+
// Returns a thenable — key.id is available synchronously, full message resolves on await.
|
|
692
|
+
// If called with an existing WebMessageInfo by accident, passes it through unchanged.
|
|
693
|
+
export const generateWAMessage = (jid, content, options = {}) => {
|
|
652
694
|
if (content?.key?.remoteJid && content?.message) return content
|
|
653
|
-
|
|
654
|
-
|
|
695
|
+
const msgId = options.messageId || generateMessageIDV2()
|
|
696
|
+
const result = { key: { remoteJid: jid, fromMe: true, id: msgId }, messageId: msgId }
|
|
697
|
+
const promise = (async () => {
|
|
698
|
+
options.logger = options?.logger?.child({ msgId })
|
|
699
|
+
return generateWAMessageFromContent(jid, await generateWAMessageContent(content, { ...options, jid, messageId: msgId }), { ...options, messageId: msgId })
|
|
700
|
+
})()
|
|
701
|
+
return Object.assign(result, {
|
|
702
|
+
then: (resolve, reject) => promise.then(resolve, reject),
|
|
703
|
+
catch: (reject) => promise.catch(reject),
|
|
704
|
+
finally: (fn) => promise.finally(fn),
|
|
705
|
+
})
|
|
655
706
|
}
|
|
656
707
|
|
|
657
708
|
// ─── UPDATE UTILITIES ─────────────────────────────────────────────────────────
|
|
@@ -11,64 +11,38 @@ const DEFAULT_PREKEY_RETENTION = 150
|
|
|
11
11
|
const DEFAULT_CLEANUP_THRESHOLD = 50
|
|
12
12
|
const CLEANUP_INTERVAL_MS = 10 * 60 * 1000
|
|
13
13
|
|
|
14
|
-
const bootstrapCreds = (raw) => {
|
|
15
|
-
|
|
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
|
|
14
|
+
const bootstrapCreds = (raw) => { const c = raw ?? initAuthCreds(); if (!c.__version || c.__version < CURRENT_VERSION) c.__version = CURRENT_VERSION; return c }
|
|
15
|
+
const patchAppStateKey = (type, v) => type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v
|
|
22
16
|
|
|
23
17
|
// ── File-based ────────────────────────────────────────────────────────────────
|
|
24
18
|
|
|
25
19
|
export const useMultiFileAuthState = async (folder, options = {}) => {
|
|
26
20
|
const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger } = options
|
|
27
|
-
|
|
28
21
|
const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-')
|
|
29
22
|
const filePath = (file) => join(folder, fixFileName(file))
|
|
30
23
|
const tmpPath = (file) => filePath(file) + '.tmp'
|
|
31
24
|
|
|
32
25
|
const folderInfo = await stat(folder).catch(() => null)
|
|
33
|
-
if (folderInfo) {
|
|
34
|
-
|
|
35
|
-
} else {
|
|
36
|
-
await mkdir(folder, { recursive: true })
|
|
37
|
-
}
|
|
26
|
+
if (folderInfo) { if (!folderInfo.isDirectory()) throw new Error(`Path exists but is not a directory: ${folder}`) }
|
|
27
|
+
else { await mkdir(folder, { recursive: true }) }
|
|
38
28
|
|
|
39
29
|
const writeData = async (data, file) => {
|
|
40
|
-
const fp = filePath(file)
|
|
41
|
-
const tp = tmpPath(file)
|
|
42
|
-
const payload = JSON.stringify(data, BufferJSON.replacer)
|
|
30
|
+
const fp = filePath(file), tp = tmpPath(file), payload = JSON.stringify(data, BufferJSON.replacer)
|
|
43
31
|
await writeFile(tp, payload)
|
|
44
|
-
try {
|
|
45
|
-
await rename(tp, fp)
|
|
46
|
-
} catch {
|
|
47
|
-
await writeFile(fp, payload)
|
|
48
|
-
await unlink(tp).catch(() => { })
|
|
49
|
-
}
|
|
32
|
+
try { await rename(tp, fp) } catch { await writeFile(fp, payload); await unlink(tp).catch(() => { }) }
|
|
50
33
|
}
|
|
51
34
|
|
|
52
35
|
const readData = async (file) => {
|
|
53
36
|
const raw = await readFile(filePath(file), { encoding: 'utf-8' }).catch(() => null)
|
|
54
37
|
if (!raw) return null
|
|
55
|
-
try {
|
|
56
|
-
|
|
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
|
-
}
|
|
38
|
+
try { const p = JSON.parse(raw, BufferJSON.reviver); return (p && typeof p === 'object' && '__checksum' in p && 'data' in p) ? p.data : p }
|
|
39
|
+
catch (err) { logger?.warn({ file, err: err.message }, 'failed to read auth file — reinitializing'); await unlink(filePath(file)).catch(() => { }); return null }
|
|
64
40
|
}
|
|
65
41
|
|
|
66
42
|
const removeData = (file) => unlink(filePath(file)).catch(() => { })
|
|
67
43
|
|
|
68
44
|
let creds = bootstrapCreds(await readData('creds.json'))
|
|
69
|
-
let cleanupRunning = false
|
|
70
|
-
let lastCleanupAt = 0
|
|
71
|
-
let lastCleanedPreKeyId = creds.nextPreKeyId
|
|
45
|
+
let cleanupRunning = false, lastCleanupAt = 0, lastCleanedPreKeyId = creds.nextPreKeyId
|
|
72
46
|
|
|
73
47
|
const cleanOldPreKeys = async () => {
|
|
74
48
|
const now = Date.now()
|
|
@@ -77,63 +51,30 @@ export const useMultiFileAuthState = async (folder, options = {}) => {
|
|
|
77
51
|
try {
|
|
78
52
|
const minId = creds.nextPreKeyId - preKeyRetention
|
|
79
53
|
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]))
|
|
54
|
+
const targets = (await readdir(folder)).map(f => f.match(/^pre-key-(\d+)\.json(\.tmp)?$/)).filter(m => m && parseInt(m[1], 10) < minId).map(m => join(folder, m[0]))
|
|
84
55
|
if (!targets.length) return
|
|
85
56
|
await Promise.all(targets.map(f => unlink(f).catch(() => { })))
|
|
86
|
-
lastCleanupAt = Date.now()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
logger?.warn({ err }, 'prekey cleanup failed')
|
|
90
|
-
} finally {
|
|
91
|
-
cleanupRunning = false
|
|
92
|
-
}
|
|
57
|
+
lastCleanupAt = Date.now(); lastCleanedPreKeyId = creds.nextPreKeyId
|
|
58
|
+
} catch (err) { logger?.warn({ err }, 'prekey cleanup failed') }
|
|
59
|
+
finally { cleanupRunning = false }
|
|
93
60
|
}
|
|
94
61
|
|
|
95
62
|
cleanOldPreKeys().catch(() => { })
|
|
96
63
|
|
|
97
64
|
const getStats = async () => {
|
|
98
65
|
const files = await readdir(folder).catch(() => [])
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
totalFiles: files.length,
|
|
102
|
-
preKeyCount: preKeyFiles.length,
|
|
103
|
-
nextPreKeyId: creds.nextPreKeyId,
|
|
104
|
-
lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null,
|
|
105
|
-
}
|
|
66
|
+
return { totalFiles: files.length, preKeyCount: files.filter(f => /^pre-key-\d+\.json$/.test(f)).length, nextPreKeyId: creds.nextPreKeyId, lastCleanupAt: lastCleanupAt ? new Date(lastCleanupAt).toISOString() : null }
|
|
106
67
|
}
|
|
107
68
|
|
|
108
69
|
return {
|
|
109
70
|
state: {
|
|
110
71
|
creds,
|
|
111
72
|
keys: {
|
|
112
|
-
get: async (type, ids) => {
|
|
113
|
-
|
|
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
|
-
},
|
|
73
|
+
get: async (type, ids) => { const data = {}; await Promise.all(ids.map(async id => { let v = await readData(`${type}-${id}.json`); data[id] = type === 'app-state-sync-key' && v ? proto.Message.AppStateSyncKeyData.fromObject(v) : v })); return data },
|
|
74
|
+
set: async (data) => { const tasks = []; for (const cat in data) for (const id in data[cat]) { const v = data[cat][id]; tasks.push(v ? writeData(v, `${cat}-${id}.json`) : removeData(`${cat}-${id}.json`)) }; await Promise.all(tasks) },
|
|
131
75
|
},
|
|
132
76
|
},
|
|
133
|
-
saveCreds: async () => {
|
|
134
|
-
if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
|
|
135
|
-
return writeData(creds, 'creds.json')
|
|
136
|
-
},
|
|
77
|
+
saveCreds: async () => { if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { }); return writeData(creds, 'creds.json') },
|
|
137
78
|
getStats,
|
|
138
79
|
}
|
|
139
80
|
}
|
|
@@ -142,72 +83,41 @@ export const useMultiFileAuthState = async (folder, options = {}) => {
|
|
|
142
83
|
|
|
143
84
|
const ser = (v) => v == null ? null : JSON.parse(JSON.stringify(v, BufferJSON.replacer))
|
|
144
85
|
|
|
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
|
-
}
|
|
86
|
+
const unwrapNestedValue = (obj) => { let cur = obj; while (cur && typeof cur === 'object' && !Array.isArray(cur) && Object.keys(cur).length === 1 && 'value' in cur) cur = cur.value; return cur }
|
|
150
87
|
|
|
151
88
|
const deser = (raw, logger) => {
|
|
152
89
|
if (raw == null) return null
|
|
153
90
|
let parsed
|
|
154
|
-
if (typeof raw === 'string') {
|
|
155
|
-
|
|
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
|
-
}
|
|
91
|
+
if (typeof raw === 'string') { try { parsed = JSON.parse(raw) } catch (err) { logger?.warn?.({ err: err.message }, '[Auth] failed to parse stored value — treating as missing'); return null } }
|
|
92
|
+
else { parsed = raw }
|
|
162
93
|
parsed = unwrapNestedValue(parsed)
|
|
163
94
|
if (parsed == null) return null
|
|
164
95
|
if (typeof parsed === 'object' && '__checksum' in parsed && 'data' in parsed) parsed = parsed.data
|
|
165
96
|
return JSON.parse(JSON.stringify(parsed), BufferJSON.reviver)
|
|
166
97
|
}
|
|
167
98
|
|
|
168
|
-
const COLLECTION_OPTION_KEY = {
|
|
169
|
-
'@keyv/mongo': 'collection',
|
|
170
|
-
'@keyv/postgres': 'table',
|
|
171
|
-
'@keyv/mysql': 'table',
|
|
172
|
-
'@keyv/sqlite': 'table',
|
|
173
|
-
}
|
|
174
|
-
|
|
99
|
+
const COLLECTION_OPTION_KEY = { '@keyv/mongo': 'collection', '@keyv/postgres': 'table', '@keyv/mysql': 'table', '@keyv/sqlite': 'table' }
|
|
175
100
|
const _adapterCache = new Map()
|
|
176
101
|
export const clearAdapterCache = () => _adapterCache.clear()
|
|
177
102
|
|
|
178
103
|
const resolveAdapter = async (connectionString, collectionName) => {
|
|
179
104
|
const cacheKey = collectionName ? `${connectionString}::${collectionName}` : connectionString
|
|
180
105
|
if (_adapterCache.has(cacheKey)) return _adapterCache.get(cacheKey)
|
|
181
|
-
|
|
182
106
|
let protocol
|
|
183
107
|
try { protocol = new URL(connectionString).protocol } catch { throw new Error(`[Auth] Invalid connection string: "${connectionString}"`) }
|
|
184
|
-
|
|
185
108
|
const pkg = PROTOCOL_ADAPTERS[protocol]
|
|
186
109
|
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
110
|
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
|
-
|
|
111
|
+
try { mod = await import(pkg) } catch (e) { if (e.code === 'ERR_MODULE_NOT_FOUND') throw new Error(`[Auth] "${pkg}" is required for this backend — install it: npm i ${pkg}`); throw e }
|
|
194
112
|
const Adapter = mod.default ?? mod[Object.keys(mod)[0]]
|
|
195
113
|
const optionKey = COLLECTION_OPTION_KEY[pkg]
|
|
196
|
-
const adapter = (collectionName && optionKey)
|
|
197
|
-
? new Adapter({ url: connectionString, uri: connectionString, [optionKey]: collectionName })
|
|
198
|
-
: new Adapter(connectionString)
|
|
114
|
+
const adapter = (collectionName && optionKey) ? new Adapter({ url: connectionString, uri: connectionString, [optionKey]: collectionName }) : new Adapter(connectionString)
|
|
199
115
|
adapter.setMaxListeners?.(0)
|
|
200
116
|
_adapterCache.set(cacheKey, adapter)
|
|
201
117
|
return adapter
|
|
202
118
|
}
|
|
203
119
|
|
|
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'
|
|
120
|
+
const isAdapterLike = (v) => v != null && typeof v === 'object' && typeof v.get === 'function' && typeof v.set === 'function' && typeof v.delete === 'function' && typeof v.clear === 'function'
|
|
211
121
|
|
|
212
122
|
const resolveStore = async (backend, collectionName) => {
|
|
213
123
|
if (backend == null) return null
|
|
@@ -219,17 +129,13 @@ const resolveStore = async (backend, collectionName) => {
|
|
|
219
129
|
|
|
220
130
|
export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
|
|
221
131
|
const { preKeyRetention = DEFAULT_PREKEY_RETENTION, cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD, logger, collectionName } = opts
|
|
222
|
-
|
|
223
132
|
const store = await resolveStore(backend, collectionName)
|
|
224
133
|
const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
|
|
225
134
|
keyv.on('error', (err) => logger?.warn?.({ err: err?.message }, '[Auth] store error'))
|
|
226
135
|
|
|
227
136
|
const keyName = (type, id) => `${type}-${id}`
|
|
228
|
-
|
|
229
137
|
let creds = bootstrapCreds(deser(await keyv.get('creds'), logger))
|
|
230
|
-
let cleanupRunning = false
|
|
231
|
-
let lastCleanupAt = 0
|
|
232
|
-
let lastCleanedPreKeyId = creds.nextPreKeyId
|
|
138
|
+
let cleanupRunning = false, lastCleanupAt = 0, lastCleanedPreKeyId = creds.nextPreKeyId
|
|
233
139
|
|
|
234
140
|
const cleanOldPreKeys = async () => {
|
|
235
141
|
const now = Date.now()
|
|
@@ -242,59 +148,35 @@ export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
|
|
|
242
148
|
for (let id = Math.max(0, lastCleanedPreKeyId - preKeyRetention); id < minId; id++) ids.push(keyName('pre-key', id))
|
|
243
149
|
if (!ids.length) return
|
|
244
150
|
await Promise.all(ids.map(k => keyv.delete(k).catch(() => { })))
|
|
245
|
-
lastCleanupAt = Date.now()
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
logger?.warn?.({ err: e.message }, '[Auth] prekey cleanup failed')
|
|
249
|
-
} finally {
|
|
250
|
-
cleanupRunning = false
|
|
251
|
-
}
|
|
151
|
+
lastCleanupAt = Date.now(); lastCleanedPreKeyId = creds.nextPreKeyId
|
|
152
|
+
} catch (e) { logger?.warn?.({ err: e.message }, '[Auth] prekey cleanup failed') }
|
|
153
|
+
finally { cleanupRunning = false }
|
|
252
154
|
}
|
|
253
155
|
|
|
254
156
|
cleanOldPreKeys().catch(() => { })
|
|
255
157
|
|
|
256
158
|
const keys = {
|
|
257
159
|
get: async (type, ids) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
160
|
+
if (!ids.length) return {}
|
|
161
|
+
const rawValues = await keyv.getMany(ids.map(id => keyName(type, id)))
|
|
162
|
+
return Object.fromEntries(ids.map((id, i) => [id, patchAppStateKey(type, deser(rawValues[i], logger))]))
|
|
268
163
|
},
|
|
269
164
|
set: async (data) => {
|
|
270
|
-
const setEntries = []
|
|
271
|
-
const
|
|
272
|
-
|
|
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
|
-
])
|
|
165
|
+
const setEntries = [], delKeys = []
|
|
166
|
+
for (const type in data) for (const id in data[type]) { const v = data[type][id], k = keyName(type, id); v != null ? setEntries.push({ key: k, value: ser(v) }) : delKeys.push(k) }
|
|
167
|
+
await Promise.all([setEntries.length ? keyv.setMany(setEntries) : null, delKeys.length ? keyv.deleteMany(delKeys) : null])
|
|
284
168
|
},
|
|
285
169
|
}
|
|
286
170
|
|
|
287
|
-
const saveCreds = async () => {
|
|
288
|
-
if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { })
|
|
289
|
-
await keyv.set('creds', ser(creds))
|
|
290
|
-
}
|
|
171
|
+
const saveCreds = async () => { if (creds.nextPreKeyId - lastCleanedPreKeyId >= cleanupThreshold) cleanOldPreKeys().catch(() => { }); await keyv.set('creds', ser(creds)) }
|
|
291
172
|
|
|
292
173
|
const clearSession = async () => {
|
|
293
174
|
const s = keyv.opts?.store ?? keyv.store
|
|
294
|
-
if (s && typeof s.
|
|
175
|
+
if (s && typeof s.iterator === 'function') {
|
|
176
|
+
// v6: iterator() takes no namespace arg — uses this._namespace set via new Keyv({ namespace: sessionId })
|
|
295
177
|
const toDelete = []
|
|
296
|
-
for await (const [key] of s.iterator(
|
|
297
|
-
if (toDelete.length) await s.deleteMany(toDelete)
|
|
178
|
+
for await (const [key] of s.iterator()) toDelete.push(key)
|
|
179
|
+
if (toDelete.length) typeof s.deleteMany === 'function' ? await s.deleteMany(toDelete) : await Promise.all(toDelete.map(k => s.delete(k).catch(() => { })))
|
|
298
180
|
} else {
|
|
299
181
|
await keyv.clear()
|
|
300
182
|
}
|
|
@@ -304,4 +186,15 @@ export const useKeyvAuthState = async (sessionId, backend, opts = {}) => {
|
|
|
304
186
|
return { state: { creds, keys }, saveCreds, clearSession }
|
|
305
187
|
}
|
|
306
188
|
|
|
189
|
+
export const hasKeyvAuth = async (sessionId, backend, opts = {}) => {
|
|
190
|
+
try {
|
|
191
|
+
const store = await resolveStore(backend, opts.collectionName)
|
|
192
|
+
const keyv = store ? new Keyv({ store, namespace: sessionId }) : new Keyv({ namespace: sessionId })
|
|
193
|
+
const raw = await keyv.get('creds')
|
|
194
|
+
if (!raw) return false
|
|
195
|
+
const parsed = deser(raw)
|
|
196
|
+
return !!(parsed?.noiseKey && parsed?.signedIdentityKey)
|
|
197
|
+
} catch { return false }
|
|
198
|
+
}
|
|
199
|
+
|
|
307
200
|
export { Keyv }
|