Files
crowd-funder-for-time-pwa/src/views/ImportDerivedAccountView.vue
Matthew Raymer ebc241eba0 WIP: restore database migration system and improve error handling
- Restore runMigrations functionality for database schema migrations
- Remove indexedDBMigrationService.ts (was for IndexedDB to SQLite migration)
- Recreate migrationService.ts and db-sql/migration.ts for schema management
- Add proper TypeScript error handling with type guards in AccountViewView
- Fix CreateAndSubmitClaimResult property access in QuickActionBvcBeginView
- Remove LeafletMouseEvent from Vue components array (it's a type, not component)
- Add null check for UserNameDialog callback to prevent undefined assignment
- Implement extractErrorMessage helper function for consistent error handling
- Update router to remove database-migration route

The migration system now properly handles database schema evolution
across app versions, while the IndexedDB to SQLite migration service
has been removed as it was specific to that one-time migration.
2025-06-23 08:25:10 +00:00

183 lines
5.8 KiB
Vue

<template>
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<button
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.go(-1)"
>
<font-awesome icon="chevron-left"></font-awesome>
</button>
Derive from Existing Identity
</h1>
</div>
<!-- Import Account Form -->
<div>
<p class="text-center text-xl mb-4 font-light">
Will increment the maximum known derivation path from the existing seed.
</p>
<p v-if="Object.keys(didArrays).length > 1">
Choose existing DIDs from same seed phrase to compute derivation.
</p>
<ul class="mb-4">
<li
v-for="dids in Object.values(didArrays)"
:key="dids[0].did"
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
@click="switchAccount(dids[0].did)"
>
<font-awesome
v-if="dids[0].did == selectedArrayFirstDid"
icon="circle"
class="fa-fw text-blue-500 text-xl mr-3"
></font-awesome>
<font-awesome
v-else
icon="circle"
class="fa-fw text-slate-400 text-xl mr-3"
></font-awesome>
<span class="overflow-hidden">
<div class="text-sm text-slate-500">
<code>{{ dids.map((d) => d.did).join(" ") }}</code>
</div>
</span>
</li>
</ul>
</div>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase 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-2 py-3 rounded-md"
@click="incrementDerivation()"
>
Increment and Import
</button>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="onCancelClick()"
>
Cancel
</button>
</div>
</div>
</section>
</template>
<script lang="ts">
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
import { Router, RouteLocationNormalizedLoaded } from "vue-router";
import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
nextDerivationPath,
} from "../libs/crypto";
import * as databaseUtil from "../db/databaseUtil";
import { db } from "../db/index";
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
import {
retrieveAllAccountsMetadata,
retrieveFullyDecryptedAccount,
saveNewIdentity,
} from "../libs/util";
import { logger } from "../utils/logger";
import { Account, AccountEncrypted } from "../db/tables/accounts";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
@Component({
components: {},
})
export default class ImportAccountView extends Vue {
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
didArrays: Record<string, Account[]> = {};
selectedArrayFirstDid = "";
async mounted() {
const accounts: AccountEncrypted[] = await retrieveAllAccountsMetadata();
const decryptedAccounts: (Account | undefined)[] = await Promise.all(
accounts.map(async (account) => {
return retrieveFullyDecryptedAccount(account.did);
}),
);
const filteredDecryptedAccounts: Account[] = decryptedAccounts.filter(
(account) => account !== undefined,
);
// group by account.mnemonic
const groupedAccounts: Record<string, Account[]> = R.groupBy(
(a) => a.mnemonic || "",
filteredDecryptedAccounts,
) as Record<string, Account[]>;
this.didArrays = groupedAccounts;
if (Object.keys(this.didArrays).length > 0) {
this.selectedArrayFirstDid = Object.values(this.didArrays)[0][0].did;
}
}
public onCancelClick() {
this.$router.back();
}
public switchAccount(did: string) {
this.selectedArrayFirstDid = did;
}
public async incrementDerivation() {
// find the maximum derivation path for the selected DIDs
const selectedArray: Array<Account> =
Object.values(this.didArrays).find(
(dids) => dids[0].did === this.selectedArrayFirstDid,
) || [];
// extract the derivationPath array and sort it
const derivationPaths = selectedArray.map(
(account) => account.derivationPath,
);
derivationPaths.sort((a, b) => {
const aParts = a?.split("/");
const aLast = aParts?.[aParts.length - 1];
const bParts = b?.split("/");
const bLast = bParts?.[bParts.length - 1];
return parseInt(aLast || "0") - parseInt(bLast || "0");
});
// we're sure there's at least one
const maxDerivPath: string = derivationPaths[
derivationPaths.length - 1
] as string;
const newDerivPath = nextDerivationPath(maxDerivPath);
const mne = selectedArray[0].mnemonic as string;
const [address, privateHex, publicHex] = deriveAddress(mne, newDerivPath);
const newId = newIdentifier(address, publicHex, privateHex, newDerivPath);
try {
await saveNewIdentity(newId, mne, newDerivPath);
// record that as the active DID
const platformService = PlatformServiceFactory.getInstance();
await platformService.dbExec("UPDATE settings SET activeDid = ?", [
newId.did,
]);
await databaseUtil.updateDidSpecificSettings(newId.did, {
isRegistered: false,
});
this.$router.push({ name: "account" });
} catch (err) {
logger.error("Error saving mnemonic & updating settings:", err);
}
}
}
</script>