diff --git a/src/db/index.ts b/src/db/index.ts index 839094c3..444a98db 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -87,9 +87,79 @@ const DEFAULT_SETTINGS: Settings = { // Event handler to initialize the non-sensitive database with default settings db.on("populate", async () => { - await db.settings.add(DEFAULT_SETTINGS); + try { + await db.settings.add(DEFAULT_SETTINGS); + } catch (error) { + console.error("Error populating the database with default settings:", error); + } }); +// Helper function to safely open the database with retries +async function safeOpenDatabase(retries = 1, delay = 500): Promise<void> { + // console.log("Starting safeOpenDatabase with retries:", retries); + for (let i = 0; i < retries; i++) { + try { + // console.log(`Attempt ${i + 1}: Checking if database is open...`); + if (!db.isOpen()) { + // console.log(`Attempt ${i + 1}: Database is closed, attempting to open...`); + + // Create a promise that rejects after 5 seconds + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Database open timed out')), 500); + }); + + // Race between the open operation and the timeout + const openPromise = db.open(); + // console.log(`Attempt ${i + 1}: Waiting for db.open() promise...`); + await Promise.race([openPromise, timeoutPromise]); + + // If we get here, the open succeeded + // console.log(`Attempt ${i + 1}: Database opened successfully`); + return; + } + // console.log(`Attempt ${i + 1}: Database was already open`); + return; + } catch (error) { + console.error(`Attempt ${i + 1}: Database open failed:`, error); + if (i < retries - 1) { + console.log(`Attempt ${i + 1}: Waiting ${delay}ms before retry...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } else { + throw error; + } + } + } +} + +export async function updateDefaultSettings( + settingsChanges: Settings, +): Promise<number> { + delete settingsChanges.accountDid; // just in case + // ensure there is no "id" that would override the key + delete settingsChanges.id; + try { + try { + // console.log("Database state before open:", db.isOpen() ? "open" : "closed"); + // console.log("Database name:", db.name); + // console.log("Database version:", db.verno); + await safeOpenDatabase(); + } catch (openError: unknown) { + console.error("Failed to open database:", openError); + const errorMessage = openError instanceof Error ? openError.message : String(openError); + throw new Error(`Database connection failed: ${errorMessage}. Please try again or restart the app.`); + } + const result = await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges); + return result; + } catch (error) { + console.error("Error updating default settings:", error); + if (error instanceof Error) { + throw error; // Re-throw if it's already an Error with a message + } else { + throw new Error(`Failed to update settings: ${error}`); + } + } +} + // Manage the encryption key. // It's not really secure to maintain the secret next to the user's data. @@ -183,15 +253,6 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> { } } -export async function updateDefaultSettings( - settingsChanges: Settings, -): Promise<void> { - delete settingsChanges.accountDid; // just in case - // ensure there is no "id" that would override the key - delete settingsChanges.id; - await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges); -} - export async function updateAccountSettings( accountDid: string, settingsChanges: Settings, diff --git a/src/libs/util.ts b/src/libs/util.ts index 1d2fa031..b98f747c 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -539,20 +539,23 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => { const identity = JSON.stringify(newId); // one of the few times we use accountsDBPromise directly; try to avoid more usage - const accountsDB = await accountsDBPromise; - await accountsDB.accounts.add({ - dateCreated: new Date().toISOString(), - derivationPath: derivationPath, - did: newId.did, - identity: identity, - mnemonic: mnemonic, - publicKeyHex: newId.keys[0].publicKeyHex, - }); - - await updateDefaultSettings({ activeDid: newId.did }); - //console.log("Updated default settings in util"); + try { + const accountsDB = await accountsDBPromise; + await accountsDB.accounts.add({ + dateCreated: new Date().toISOString(), + derivationPath: derivationPath, + did: newId.did, + identity: identity, + mnemonic: mnemonic, + publicKeyHex: newId.keys[0].publicKeyHex, + }); + + await updateDefaultSettings({ activeDid: newId.did }); + } catch (error) { + console.error("Failed to update default settings:", error); + throw new Error("Failed to set default settings. Please try again or restart the app."); + } await updateAccountSettings(newId.did, { isRegistered: false }); - return newId.did; }; diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index c944e847..e2d7b0e2 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -618,6 +618,7 @@ leave-to-class="opacity-0" > <div v-if="showContactImport()" class="mt-4"> + <!-- Bulk import has an error <div class="flex justify-center"> <button class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6" @@ -628,6 +629,7 @@ (which doesn't include Identifier Data) </button> </div> + --> <div class="flex justify-center"> <button class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6" @@ -1623,7 +1625,7 @@ export default class AccountViewView extends Vue { async submitImportFile() { if (inputImportFileNameRef.value != null) { await db.delete() - .then(async () => { + .then(async (x) => { await Dexie.import(inputImportFileNameRef.value as Blob, { progressCallback: this.progressCallback, }) @@ -1635,9 +1637,9 @@ export default class AccountViewView extends Vue { group: "alert", type: "danger", title: "Error Importing", - text: "There was an error importing that file. Your contacts may have been affected, so you may want to use the other import method.", + text: "There was an error in the import. Your identities and contacts may have been affected, so you may have to restore your identifier and use the contact import method.", }, - 5000, + -1, ); }); } diff --git a/src/views/NewIdentifierView.vue b/src/views/NewIdentifierView.vue index 6a9b05b6..547c43ca 100644 --- a/src/views/NewIdentifierView.vue +++ b/src/views/NewIdentifierView.vue @@ -32,6 +32,13 @@ size="128" ></font-awesome> </div> + <div v-else-if="hitError"> + <span class="text-xl">Error Creating Identity</span> + <font-awesome icon="exclamation-triangle" class="fa-fw text-red-500 ml-2"></font-awesome> + <p class="text-sm text-gray-500"> + Try fully restarting the app. If that doesn't work, back up all data (identities and other data) and reinstall the app. + </p> + </div> <div v-else> <span class="text-xl">Created!</span> <font-awesome @@ -62,14 +69,24 @@ import QuickNav from "../components/QuickNav.vue"; @Component({ components: { QuickNav } }) export default class NewIdentifierView extends Vue { loading = true; + hitError = false; $router!: Router; async mounted() { - await generateSaveAndActivateIdentity(); - this.loading = false; - setTimeout(() => { - this.$router.push({ name: "home" }); - }, 1000); + this.loading = true; + this.hitError = false; + generateSaveAndActivateIdentity() + .then(() => { + this.loading = false; + setTimeout(() => { + this.$router.push({ name: "home" }); + }, 1000); + }) + .catch((error) => { + this.loading = false; + this.hitError = true; + console.error('Failed to generate identity:', error); + }); } } </script>