export.ts (3707B)
1 import { app_init, datastore, db, nostr_keys, notif } from "$lib/utils/app"; 2 import { ls } from "$lib/utils/i18n"; 3 import { download_json, get_store, handle_err } from "@radroots/apps-lib"; 4 import type { ExportedAppState } from "@radroots/apps-lib-pwa/types/app"; 5 import { throw_err } from "@radroots/utils"; 6 import { createStore, get as idb_get, keys as idb_keys, type UseStore } from "idb-keyval"; 7 import { app_cfg } from "../app/config"; 8 9 const ls_val = get_store(ls); 10 11 const parse_idb_entry = async (key: IDBValidKey, store: UseStore): Promise<[string, string] | undefined> => { 12 if (typeof key !== "string") return undefined; 13 const value = await idb_get(key, store); 14 if (typeof value === "undefined") return undefined; 15 return [key, value]; 16 }; 17 18 const export_datastore_state = async (): Promise<ExportedAppState["datastore"]> => { 19 await datastore.init(); 20 const ds_cfg = datastore.get_config(); 21 if (!ds_cfg) throw_err(ls_val(`error.app.export.missing_datastore_config`)); 22 const ds_store = createStore(ds_cfg.database, ds_cfg.store); 23 const ds_keys = await idb_keys(ds_store); 24 const entries: Record<string, unknown> = {}; 25 for (const key of ds_keys) { 26 const entry = await parse_idb_entry(key, ds_store); 27 if (entry) entries[entry[0]] = entry[1]; 28 } 29 return { config: ds_cfg, entries }; 30 }; 31 32 const export_nostr_keystore_state = async (): Promise<ExportedAppState["nostr_keystore"]> => { 33 const nostr_cfg = nostr_keys.get_config(); 34 if (!nostr_cfg) throw_err(ls_val(`error.app.export.missing_nostr_keystore_config`)); 35 36 const keys: ExportedAppState["nostr_keystore"]["keys"] = []; 37 try { 38 const nostr_keys_all = await nostr_keys.keys(); 39 if ("err" in nostr_keys_all) throw_err(nostr_keys_all.err); 40 for (const public_key of nostr_keys_all.results) { 41 const secret_key = await nostr_keys.read(public_key); 42 if ("err" in secret_key) throw_err(secret_key.err); 43 keys.push({ 44 public_key, 45 secret_key: secret_key.secret_key 46 }); 47 } 48 } catch { } 49 return { 50 config: nostr_cfg, 51 keys 52 }; 53 }; 54 55 const export_replica_db_state = async (): Promise<ExportedAppState["database"]> => { 56 await app_init(); 57 const store_key = db.get_store_key(); 58 const backup = await db.export_json(); 59 if ("err" in backup) throw_err(backup); 60 return { store_key, backup }; 61 }; 62 63 export const export_app_state = async (): Promise<void> => { 64 try { 65 if (typeof window === "undefined") return; 66 const [ 67 datastore_state, 68 nostr_keystore_state, 69 replica_db_state 70 ] = await Promise.all([ 71 export_datastore_state(), 72 export_nostr_keystore_state(), 73 export_replica_db_state() 74 ]); 75 const payload: ExportedAppState = { 76 backup_version: app_cfg.backup.version, 77 exported_at: new Date().toISOString(), 78 versions: { 79 app: app_cfg.version, 80 replica_db: replica_db_state.backup.replica_db_version, 81 backup_format: replica_db_state.backup.format_version 82 }, 83 datastore: datastore_state, 84 nostr_keystore: nostr_keystore_state, 85 database: replica_db_state 86 }; 87 const ts = payload.exported_at.replace(/[:.]/g, "-"); 88 const filename_prefix = ls_val(`common.radroots_app_state_filename_prefix`); 89 download_json(payload, `${filename_prefix}-${ts}.json`); 90 } catch (e) { 91 handle_err(e, `export_app_state`); 92 await notif.alert(`${ls_val(`error.backup.export_failure`)}`); 93 } 94 };