198 lines
5.3 KiB
TypeScript
198 lines
5.3 KiB
TypeScript
import { Boom } from "@hapi/boom";
|
|
import { Server } from "@hapi/hapi";
|
|
import { randomBytes } from "crypto";
|
|
import type { Logger } from "pino";
|
|
import {
|
|
proto,
|
|
BufferJSON,
|
|
generateRegistrationId,
|
|
Curve,
|
|
signedKeyPair,
|
|
AuthenticationCreds,
|
|
AuthenticationState,
|
|
AccountSettings,
|
|
SignalDataSet,
|
|
SignalDataTypeMap,
|
|
SignalKeyStore,
|
|
SignalKeyStoreWithTransaction,
|
|
} from "@adiwajshing/baileys";
|
|
import { SavedWhatsappBot as Bot } from "@digiresilience/metamigo-db";
|
|
|
|
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] || {};
|
|
// @ts-expect-error
|
|
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) {
|
|
// @ts-expect-error
|
|
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) {
|
|
// @ts-expect-error
|
|
transactionCache[key] = transactionCache[key] || {};
|
|
// @ts-expect-error
|
|
Object.assign(transactionCache[key], data[key]);
|
|
// @ts-expect-error
|
|
mutations[key] = mutations[key] || {};
|
|
// @ts-expect-error
|
|
Object.assign(mutations[key], data[key]);
|
|
}
|
|
} else {
|
|
return state.set(data);
|
|
}
|
|
},
|
|
isInTransaction: () => inTransaction,
|
|
// @ts-expect-error
|
|
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,
|
|
|
|
processedHistoryMessages: [],
|
|
accountSettings: {
|
|
unarchiveChats: false,
|
|
},
|
|
} as any;
|
|
};
|
|
|
|
export const useDatabaseAuthState = (
|
|
bot: Bot,
|
|
server: Server
|
|
): { state: AuthenticationState; saveState: () => void } => {
|
|
let { logger }: any = server;
|
|
let creds: AuthenticationCreds;
|
|
let keys: any = {};
|
|
|
|
const saveState = async () => {
|
|
logger && logger.trace("saving auth state");
|
|
const authInfo = JSON.stringify({ creds, keys }, BufferJSON.replacer, 2);
|
|
await server.db().whatsappBots.updateAuthInfo(bot, authInfo);
|
|
};
|
|
|
|
if (bot.authInfo) {
|
|
console.log("Auth info exists");
|
|
const result = JSON.parse(bot.authInfo, BufferJSON.reviver);
|
|
creds = result.creds;
|
|
keys = result.keys;
|
|
} else {
|
|
console.log("Auth info does not exist");
|
|
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") {
|
|
// @ts-expect-error
|
|
value = proto.AppStateSyncKeyData.fromObject(value);
|
|
}
|
|
// @ts-expect-error
|
|
dict[id] = value;
|
|
}
|
|
|
|
return dict;
|
|
}, {});
|
|
},
|
|
set: (data) => {
|
|
for (const _key in data) {
|
|
const key = KEY_MAP[_key as keyof SignalDataTypeMap];
|
|
keys[key] = keys[key] || {};
|
|
// @ts-expect-error
|
|
Object.assign(keys[key], data[_key]);
|
|
}
|
|
|
|
saveState();
|
|
},
|
|
},
|
|
},
|
|
saveState,
|
|
};
|
|
};
|