import BaseDexie, { Table } from "dexie"; import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon"; import * as R from "ramda"; import { Account, AccountsSchema } from "./tables/accounts"; import { Contact, ContactSchema } from "./tables/contacts"; import { Log, LogSchema } from "./tables/logs"; import { MASTER_SETTINGS_KEY, Settings, SettingsSchema, } from "./tables/settings"; import { Temp, TempSchema } from "./tables/temp"; import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; // Define types for tables that hold sensitive and non-sensitive data type SensitiveTables = { accounts: Table }; type NonsensitiveTables = { contacts: Table; logs: Table; settings: Table; temp: Table; }; // Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings export type SensitiveDexie = BaseDexie & T; export type NonsensitiveDexie = BaseDexie & T; // Initialize Dexie databases for sensitive and non-sensitive data export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie; export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie; // Manage the encryption key. If not present in localStorage, create and store it. const secret = localStorage.getItem("secret") || Encryption.createRandomEncryptionKey(); if (!localStorage.getItem("secret")) localStorage.setItem("secret", secret); // Apply encryption to the sensitive database using the secret key encrypted(accountsDB, { secretKey: secret }); // Define the schemas for our databases // Only the tables with index modifications need listing. https://dexie.org/docs/Tutorial/Design#database-versioning accountsDB.version(1).stores(AccountsSchema); // v1 also had contacts & settings // v2 added Log db.version(2).stores({ ...ContactSchema, ...LogSchema, ...{ settings: "id" }, // old Settings schema }); // v3 added Temp db.version(3).stores(TempSchema); db.version(4) .stores(SettingsSchema) .upgrade((tx) => { return tx .table("settings") .toCollection() .modify((settings) => { settings.accountDid = ""; // make it non-null for the default master settings, but still indexable }); }); const DEFAULT_SETTINGS = { id: MASTER_SETTINGS_KEY, activeDid: undefined, apiServer: DEFAULT_ENDORSER_API_SERVER, }; // Event handler to initialize the non-sensitive database with default settings db.on("populate", async () => { await db.settings.add(DEFAULT_SETTINGS); }); // retrieves default settings // calls db.open() export async function retrieveSettingsForDefaultAccount(): Promise { await db.open(); return (await db.settings.get(MASTER_SETTINGS_KEY)) || DEFAULT_SETTINGS; } export async function retrieveSettingsForActiveAccount(): Promise { const defaultSettings = await retrieveSettingsForDefaultAccount(); if (!defaultSettings.activeDid) { return defaultSettings; } else { const overrideSettings = (await db.settings .where("accountDid") .equals(defaultSettings.activeDid) .first()) || {}; return R.mergeDeepRight(defaultSettings, overrideSettings); } } // Update settings for the given account, or in MASTER_SETTINGS_KEY if no accountDid is provided. // Don't expose this because we should be explicit on whether we're updating the default settings or account settings. async function updateSettings(settingsChanges: Settings): Promise { await db.open(); if (!settingsChanges.accountDid) { // ensure there is no "id" that would override the key delete settingsChanges.id; await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges); } else { const result = await db.settings .where("accountDid") .equals(settingsChanges.accountDid) .modify(settingsChanges); if (result === 0) { if (!settingsChanges.id) { // It is unfortunate that we have to set this explicitly. // We didn't make id a "++id" at the beginning and Dexie won't let us change it, // plus we made our first settings objects MASTER_SETTINGS_KEY = 1 instead of 0 settingsChanges.id = (await db.settings.count()) + 1; } await db.settings.add(settingsChanges); } } } export async function updateDefaultSettings(settings: Settings): Promise { delete settings.accountDid; // just in case await updateSettings(settings); } export async function updateAccountSettings( accountDid: string, settings: Settings, ): Promise { settings.accountDid = accountDid; await updateSettings(settings); }