monitor/core/modules/Database/dbUtils.ts
2025-04-16 22:30:27 +07:00

125 lines
4.1 KiB
TypeScript

const modulename = 'IDGen';
import fsp from 'node:fs/promises';
import * as nanoidSecure from 'nanoid';
import * as nanoidNonSecure from 'nanoid/non-secure';
import consts from '@shared/consts';
import getOsDistro from '@lib/host/getOsDistro.js';
import { txEnv, txHostConfig } from '@core/globalData';
import type { DatabaseObjectType } from './instance';
import consoleFactory from '@lib/console';
import { msToDuration } from '@lib/misc';
const console = consoleFactory(modulename);
//Consts
type IdStorageTypes = DatabaseObjectType | Set<string>;
const maxAttempts = 10;
const noIdErrorMessage = 'Unnable to generate new Random ID possibly due to the decreased available entropy. Please send a screenshot of the detailed information in the terminal for the txAdmin devs.';
/**
* Prints a diagnostics message to the console that should help us identify what is the problem and the potential solution
*/
const printDiagnostics = async () => {
let uptime;
let entropy;
try {
uptime = msToDuration(process.uptime() * 1000);
entropy = (await fsp.readFile('/proc/sys/kernel/random/entropy_avail', 'utf8')).trim();
} catch (error) {
entropy = (error as Error).message;
}
const secureStorage = new Set();
for (let i = 0; i < 100; i++) {
const randID = nanoidSecure.customAlphabet(consts.actionIdAlphabet, 4)();
if (!secureStorage.has(randID)) secureStorage.add(randID);
}
const nonsecureStorage = new Set();
for (let i = 0; i < 100; i++) {
const randID = nanoidNonSecure.customAlphabet(consts.actionIdAlphabet, 4)();
if (!nonsecureStorage.has(randID)) nonsecureStorage.add(randID);
}
const osDistro = await getOsDistro();
console.error(noIdErrorMessage);
console.error(`Uptime: ${uptime}`);
console.error(`Entropy: ${entropy}`);
console.error(`Distro: ${osDistro}`);
console.error(`txAdmin: ${txEnv.txaVersion}`);
console.error(`FXServer: ${txEnv.fxsVersionTag}`);
console.error(`Provider: ${txHostConfig.providerName ?? 'none'}`);
console.error(`Unique Test: secure ${secureStorage.size}/100, non-secure ${nonsecureStorage.size}/100`);
};
/**
* Check in a storage weather the ID is unique or not.
* @param storage the Set or lowdb instance
* @param id the ID to check
* @param lowdbTable the lowdb table to check
* @returns if is unique
*/
const checkUniqueness = (storage: IdStorageTypes, id: string, lowdbTable: string) => {
if (storage instanceof Set) {
return !storage.has(id);
} else {
//@ts-ignore: typing as ('actions' | 'whitelistRequests') did not work
return !storage.chain.get(lowdbTable).find({ id }).value();
}
};
/**
* Generates an unique whitelist ID, or throws an error
* @param storage set or lowdb instance
* @returns id
*/
export const genWhitelistRequestID = (storage: IdStorageTypes) => {
let attempts = 0;
while (attempts < maxAttempts) {
attempts++;
const randFunc = (attempts <= 5) ? nanoidSecure : nanoidNonSecure;
const id = 'R' + randFunc.customAlphabet(consts.actionIdAlphabet, 4)();
if (checkUniqueness(storage, id, 'whitelistRequests')) {
return id;
}
}
printDiagnostics().catch((e) => { });
throw new Error(noIdErrorMessage);
};
/**
* Generates an unique action ID, or throws an error
*/
export const genActionID = (storage: IdStorageTypes, actionType: string) => {
let attempts = 0;
while (attempts < maxAttempts) {
attempts++;
const randFunc = (attempts <= 5) ? nanoidSecure : nanoidNonSecure;
const id = actionType[0].toUpperCase()
+ randFunc.customAlphabet(consts.actionIdAlphabet, 3)()
+ '-'
+ randFunc.customAlphabet(consts.actionIdAlphabet, 4)();
if (checkUniqueness(storage, id, 'actions')) {
return id;
}
}
printDiagnostics().catch((e) => { });
throw new Error(noIdErrorMessage);
};
/**
* Error class for key uniqueness violations
*/
export class DuplicateKeyError extends Error {
readonly code = 'DUPLICATE_KEY';
constructor(message: string) {
super(message);
}
}