Browse Source

add more error handling and messaging when there are bad DB errors

qrcode-reboot
Trent Larson 2 days ago
parent
commit
1129a13e20
  1. 81
      src/db/index.ts
  2. 29
      src/libs/util.ts
  3. 8
      src/views/AccountViewView.vue
  4. 27
      src/views/NewIdentifierView.vue

81
src/db/index.ts

@ -87,9 +87,79 @@ const DEFAULT_SETTINGS: Settings = {
// Event handler to initialize the non-sensitive database with default settings // Event handler to initialize the non-sensitive database with default settings
db.on("populate", async () => { 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. // Manage the encryption key.
// It's not really secure to maintain the secret next to the user's data. // 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( export async function updateAccountSettings(
accountDid: string, accountDid: string,
settingsChanges: Settings, settingsChanges: Settings,

29
src/libs/util.ts

@ -539,20 +539,23 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
const identity = JSON.stringify(newId); const identity = JSON.stringify(newId);
// one of the few times we use accountsDBPromise directly; try to avoid more usage // one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; try {
await accountsDB.accounts.add({ const accountsDB = await accountsDBPromise;
dateCreated: new Date().toISOString(), await accountsDB.accounts.add({
derivationPath: derivationPath, dateCreated: new Date().toISOString(),
did: newId.did, derivationPath: derivationPath,
identity: identity, did: newId.did,
mnemonic: mnemonic, identity: identity,
publicKeyHex: newId.keys[0].publicKeyHex, mnemonic: mnemonic,
}); publicKeyHex: newId.keys[0].publicKeyHex,
});
await updateDefaultSettings({ activeDid: newId.did });
//console.log("Updated default settings in util"); 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 }); await updateAccountSettings(newId.did, { isRegistered: false });
return newId.did; return newId.did;
}; };

8
src/views/AccountViewView.vue

@ -618,6 +618,7 @@
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<div v-if="showContactImport()" class="mt-4"> <div v-if="showContactImport()" class="mt-4">
<!-- Bulk import has an error
<div class="flex justify-center"> <div class="flex justify-center">
<button <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" 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) (which doesn't include Identifier Data)
</button> </button>
</div> </div>
-->
<div class="flex justify-center"> <div class="flex justify-center">
<button <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" 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() { async submitImportFile() {
if (inputImportFileNameRef.value != null) { if (inputImportFileNameRef.value != null) {
await db.delete() await db.delete()
.then(async () => { .then(async (x) => {
await Dexie.import(inputImportFileNameRef.value as Blob, { await Dexie.import(inputImportFileNameRef.value as Blob, {
progressCallback: this.progressCallback, progressCallback: this.progressCallback,
}) })
@ -1635,9 +1637,9 @@ export default class AccountViewView extends Vue {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error Importing", 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,
); );
}); });
} }

27
src/views/NewIdentifierView.vue

@ -32,6 +32,13 @@
size="128" size="128"
></font-awesome> ></font-awesome>
</div> </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> <div v-else>
<span class="text-xl">Created!</span> <span class="text-xl">Created!</span>
<font-awesome <font-awesome
@ -62,14 +69,24 @@ import QuickNav from "../components/QuickNav.vue";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class NewIdentifierView extends Vue { export default class NewIdentifierView extends Vue {
loading = true; loading = true;
hitError = false;
$router!: Router; $router!: Router;
async mounted() { async mounted() {
await generateSaveAndActivateIdentity(); this.loading = true;
this.loading = false; this.hitError = false;
setTimeout(() => { generateSaveAndActivateIdentity()
this.$router.push({ name: "home" }); .then(() => {
}, 1000); 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> </script>

Loading…
Cancel
Save