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. 79
      src/db/index.ts
  2. 7
      src/libs/util.ts
  3. 8
      src/views/AccountViewView.vue
  4. 19
      src/views/NewIdentifierView.vue

79
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 () => {
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,

7
src/libs/util.ts

@ -539,6 +539,7 @@ 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
try {
const accountsDB = await accountsDBPromise;
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
@ -550,9 +551,11 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
});
await updateDefaultSettings({ activeDid: newId.did });
//console.log("Updated default settings in util");
} 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;
};

8
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,
);
});
}

19
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 = 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>

Loading…
Cancel
Save