index.ts (4960B)
1 export * from "./error.js"; 2 export * from "./types.js"; 3 export * from "./codec.js"; 4 5 import { backup_bundle_decode, backup_bundle_encode } from "./codec.js"; 6 import type { 7 BackupBundle, 8 BackupBundlePayload, 9 BackupDatastoreStore, 10 BackupKeystoreStore, 11 BackupSqlStore 12 } from "./types.js"; 13 import type { IWebCryptoService, KeyMaterialProvider } from "../crypto/types.js"; 14 import { DeviceKeyMaterialProvider } from "../crypto/provider.js"; 15 import { handle_err, type ResolveError } from "@radroots/utils"; 16 import { is_error } from "../utils/resolve.js"; 17 18 export type BackupBundleBuildOpts = { 19 sql_store?: BackupSqlStore; 20 keystore_store?: BackupKeystoreStore; 21 datastore_store?: BackupDatastoreStore; 22 app_version?: string; 23 crypto_service?: IWebCryptoService; 24 key_material_provider?: KeyMaterialProvider; 25 }; 26 27 export type BackupBundleImportOpts = { 28 sql_store?: BackupSqlStore; 29 keystore_store?: BackupKeystoreStore; 30 datastore_store?: BackupDatastoreStore; 31 crypto_service?: IWebCryptoService; 32 key_material_provider?: KeyMaterialProvider; 33 import_registry?: boolean; 34 }; 35 36 const collect_payloads = async (opts: BackupBundleBuildOpts): Promise<ResolveError<BackupBundlePayload[]>> => { 37 const payloads: BackupBundlePayload[] = []; 38 if (opts.sql_store) { 39 const data = await opts.sql_store.export_backup(); 40 if (is_error(data)) return data; 41 payloads.push({ 42 store_id: opts.sql_store.get_store_id(), 43 store_type: "sql", 44 data 45 }); 46 } 47 if (opts.keystore_store) { 48 const data = await opts.keystore_store.export_backup(); 49 if (is_error(data)) return data; 50 payloads.push({ 51 store_id: opts.keystore_store.get_store_id(), 52 store_type: "keystore", 53 data 54 }); 55 } 56 if (opts.datastore_store) { 57 const data = await opts.datastore_store.export_backup(); 58 if (is_error(data)) return data; 59 payloads.push({ 60 store_id: opts.datastore_store.get_store_id(), 61 store_type: "datastore", 62 data 63 }); 64 } 65 return payloads; 66 }; 67 68 export const backup_bundle_build = async (opts: BackupBundleBuildOpts): Promise<ResolveError<BackupBundle>> => { 69 const payloads = await collect_payloads(opts); 70 if (is_error(payloads)) return payloads; 71 const stores = payloads.map((payload) => ({ 72 store_id: payload.store_id, 73 store_type: payload.store_type 74 })); 75 const crypto_registry = opts.crypto_service 76 ? await opts.crypto_service.export_registry() 77 : { stores: [], keys: [] }; 78 if (is_error(crypto_registry)) return crypto_registry; 79 return { 80 manifest: { 81 version: 1, 82 created_at: Date.now(), 83 app_version: opts.app_version, 84 stores, 85 crypto_registry 86 }, 87 payloads 88 }; 89 }; 90 91 export const backup_bundle_export = async (opts: BackupBundleBuildOpts): Promise<ResolveError<Uint8Array>> => { 92 try { 93 const provider = opts.key_material_provider ?? new DeviceKeyMaterialProvider(); 94 const bundle = await backup_bundle_build(opts); 95 if (is_error(bundle)) return bundle; 96 return await backup_bundle_encode(bundle, provider); 97 } catch (e) { 98 return handle_err(e); 99 } 100 }; 101 102 export const backup_bundle_import = async (blob: Uint8Array, opts: BackupBundleImportOpts): Promise<ResolveError<BackupBundle>> => { 103 try { 104 const provider = opts.key_material_provider ?? new DeviceKeyMaterialProvider(); 105 const bundle = await backup_bundle_decode(blob, provider); 106 if (opts.import_registry && opts.crypto_service) { 107 const res = await opts.crypto_service.import_registry(bundle.manifest.crypto_registry); 108 if (is_error(res)) return res; 109 } 110 for (const payload of bundle.payloads) { 111 if (payload.store_type === "sql" && opts.sql_store) { 112 if (opts.sql_store.get_store_id() === payload.store_id) { 113 const res = await opts.sql_store.import_backup(payload.data); 114 if (is_error(res)) return res; 115 } 116 } 117 if (payload.store_type === "keystore" && opts.keystore_store) { 118 if (opts.keystore_store.get_store_id() === payload.store_id) { 119 const res = await opts.keystore_store.import_backup(payload.data); 120 if (is_error(res)) return res; 121 } 122 } 123 if (payload.store_type === "datastore" && opts.datastore_store) { 124 if (opts.datastore_store.get_store_id() === payload.store_id) { 125 const res = await opts.datastore_store.import_backup(payload.data); 126 if (is_error(res)) return res; 127 } 128 } 129 } 130 return bundle; 131 } catch (e) { 132 return handle_err(e); 133 } 134 };