mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
181 lines
4.7 KiB
TypeScript
181 lines
4.7 KiB
TypeScript
import { Boom } from '@hapi/boom'
|
|
import { randomBytes } from 'crypto'
|
|
import type { Logger } from 'pino'
|
|
import { proto } from '../../WAProto'
|
|
import type { AuthenticationCreds, AuthenticationState, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction } from '../Types'
|
|
import { Curve, signedKeyPair } from './crypto'
|
|
import { BufferJSON, generateRegistrationId } from './generics'
|
|
|
|
const KEY_MAP: { [T in keyof SignalDataTypeMap]: string } = {
|
|
'pre-key': 'preKeys',
|
|
'session': 'sessions',
|
|
'sender-key': 'senderKeys',
|
|
'app-state-sync-key': 'appStateSyncKeys',
|
|
'app-state-sync-version': 'appStateVersions',
|
|
'sender-key-memory': 'senderKeyMemory'
|
|
}
|
|
|
|
export const addTransactionCapability = (state: SignalKeyStore, logger: Logger): SignalKeyStoreWithTransaction => {
|
|
let inTransaction = false
|
|
let transactionCache: SignalDataSet = { }
|
|
let mutations: SignalDataSet = { }
|
|
|
|
const prefetch = async(type: keyof SignalDataTypeMap, ids: string[]) => {
|
|
if(!inTransaction) {
|
|
throw new Boom('Cannot prefetch without transaction')
|
|
}
|
|
|
|
const dict = transactionCache[type]
|
|
const idsRequiringFetch = dict ? ids.filter(item => !(item in dict)) : ids
|
|
// only fetch if there are any items to fetch
|
|
if(idsRequiringFetch.length) {
|
|
const result = await state.get(type, idsRequiringFetch)
|
|
|
|
transactionCache[type] = transactionCache[type] || { }
|
|
Object.assign(transactionCache[type], result)
|
|
}
|
|
}
|
|
|
|
return {
|
|
get: async(type, ids) => {
|
|
if(inTransaction) {
|
|
await prefetch(type, ids)
|
|
return ids.reduce(
|
|
(dict, id) => {
|
|
const value = transactionCache[type]?.[id]
|
|
if(value) {
|
|
dict[id] = value
|
|
}
|
|
|
|
return dict
|
|
}, { }
|
|
)
|
|
} else {
|
|
return state.get(type, ids)
|
|
}
|
|
},
|
|
set: data => {
|
|
if(inTransaction) {
|
|
logger.trace({ types: Object.keys(data) }, 'caching in transaction')
|
|
for(const key in data) {
|
|
transactionCache[key] = transactionCache[key] || { }
|
|
Object.assign(transactionCache[key], data[key])
|
|
|
|
mutations[key] = mutations[key] || { }
|
|
Object.assign(mutations[key], data[key])
|
|
}
|
|
} else {
|
|
return state.set(data)
|
|
}
|
|
},
|
|
isInTransaction: () => inTransaction,
|
|
prefetch: (type, ids) => {
|
|
logger.trace({ type, ids }, 'prefetching')
|
|
return prefetch(type, ids)
|
|
},
|
|
transaction: async(work) => {
|
|
if(inTransaction) {
|
|
await work()
|
|
} else {
|
|
logger.debug('entering transaction')
|
|
inTransaction = true
|
|
try {
|
|
await work()
|
|
if(Object.keys(mutations).length) {
|
|
logger.debug('committing transaction')
|
|
await state.set(mutations)
|
|
} else {
|
|
logger.debug('no mutations in transaction')
|
|
}
|
|
} finally {
|
|
inTransaction = false
|
|
transactionCache = { }
|
|
mutations = { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export const initAuthCreds = (): AuthenticationCreds => {
|
|
const identityKey = Curve.generateKeyPair()
|
|
return {
|
|
noiseKey: Curve.generateKeyPair(),
|
|
signedIdentityKey: identityKey,
|
|
signedPreKey: signedKeyPair(identityKey, 1),
|
|
registrationId: generateRegistrationId(),
|
|
advSecretKey: randomBytes(32).toString('base64'),
|
|
|
|
nextPreKeyId: 1,
|
|
firstUnuploadedPreKeyId: 1,
|
|
serverHasPreKeys: false,
|
|
accountSettings: {
|
|
unarchiveChats: false
|
|
}
|
|
}
|
|
}
|
|
|
|
/** stores the full authentication state in a single JSON file */
|
|
export const useSingleFileAuthState = (filename: string, logger?: Logger): { state: AuthenticationState, saveState: () => void } => {
|
|
// require fs here so that in case "fs" is not available -- the app does not crash
|
|
const { readFileSync, writeFileSync, existsSync } = require('fs')
|
|
let creds: AuthenticationCreds
|
|
let keys: any = { }
|
|
|
|
// save the authentication state to a file
|
|
const saveState = () => {
|
|
logger && logger.trace('saving auth state')
|
|
writeFileSync(
|
|
filename,
|
|
// BufferJSON replacer utility saves buffers nicely
|
|
JSON.stringify({ creds, keys }, BufferJSON.replacer, 2)
|
|
)
|
|
}
|
|
|
|
if(existsSync(filename)) {
|
|
const result = JSON.parse(
|
|
readFileSync(filename, { encoding: 'utf-8' }),
|
|
BufferJSON.reviver
|
|
)
|
|
creds = result.creds
|
|
keys = result.keys
|
|
} else {
|
|
creds = initAuthCreds()
|
|
keys = { }
|
|
}
|
|
|
|
return {
|
|
state: {
|
|
creds,
|
|
keys: {
|
|
get: (type, ids) => {
|
|
const key = KEY_MAP[type]
|
|
return ids.reduce(
|
|
(dict, id) => {
|
|
let value = keys[key]?.[id]
|
|
if(value) {
|
|
if(type === 'app-state-sync-key') {
|
|
value = proto.AppStateSyncKeyData.fromObject(value)
|
|
}
|
|
|
|
dict[id] = value
|
|
}
|
|
|
|
return dict
|
|
}, { }
|
|
)
|
|
},
|
|
set: (data) => {
|
|
for(const _key in data) {
|
|
const key = KEY_MAP[_key as keyof SignalDataTypeMap]
|
|
keys[key] = keys[key] || { }
|
|
Object.assign(keys[key], data[_key])
|
|
}
|
|
|
|
saveState()
|
|
}
|
|
}
|
|
},
|
|
saveState
|
|
}
|
|
} |