Browse Source

switch the encryption secret from localStorage to IndexedDB (because localStorage gets lost so often)

split_build_process
Trent Larson 1 month ago
parent
commit
bb3807a805
  1. 2
      playwright.config-local.ts
  2. 7
      src/components/GiftedDialog.vue
  3. 12
      src/components/PushNotificationPermission.vue
  4. 111
      src/db/index.ts
  5. 4
      src/db/tables/accounts.ts
  6. 4
      src/db/tables/temp.ts
  7. 7
      src/libs/endorserServer.ts
  8. 60
      src/libs/util.ts
  9. 4
      src/router/index.ts
  10. 81
      src/views/AccountViewView.vue
  11. 12
      src/views/ClaimView.vue
  12. 10
      src/views/ConfirmGiftView.vue
  13. 6
      src/views/ContactAmountsView.vue
  14. 8
      src/views/ContactQRScanShowView.vue
  15. 12
      src/views/DIDView.vue
  16. 8
      src/views/DiscoverView.vue
  17. 13
      src/views/GiftedDetailsView.vue
  18. 9
      src/views/HomeView.vue
  19. 14
      src/views/IdentitySwitcherView.vue
  20. 12
      src/views/ImportAccountView.vue
  21. 8
      src/views/ImportDerivedAccountView.vue
  22. 9
      src/views/NewActivityView.vue
  23. 15
      src/views/NewEditProjectView.vue
  24. 13
      src/views/OfferDetailsView.vue
  25. 27
      src/views/ProjectViewView.vue
  26. 10
      src/views/ProjectsView.vue
  27. 2
      src/views/QuickActionBvcEndView.vue
  28. 9
      src/views/RecentOffersToUserProjectsView.vue
  29. 9
      src/views/RecentOffersToUserView.vue
  30. 13
      src/views/SeedBackupView.vue
  31. 8
      src/views/ShareMyContactInfoView.vue
  32. 10
      src/views/StartView.vue
  33. 14
      src/views/TestView.vue
  34. 2
      sw_scripts/additional-scripts.js
  35. 1
      sw_scripts/safari-notifications.js

2
playwright.config-local.ts

@ -74,7 +74,7 @@ export default defineConfig({
/* Configure global timeout; default is 30000 milliseconds */ /* Configure global timeout; default is 30000 milliseconds */
// the image upload will often not succeed at 5 seconds // the image upload will often not succeed at 5 seconds
timeout: 25000, // various tests fail at various times with 20000 timeout: 30000, // various tests fail at various times with 25000
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
/** /**

7
src/components/GiftedDialog.vue

@ -92,8 +92,9 @@ import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer"; import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { retrieveAccountDids } from "@/libs/util";
@Component @Component
export default class GiftedDialog extends Vue { export default class GiftedDialog extends Vue {
@ -145,9 +146,7 @@ export default class GiftedDialog extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
if (this.giver && !this.giver.name) { if (this.giver && !this.giver.name) {
this.giver.name = didInfo( this.giver.name = didInfo(

12
src/components/PushNotificationPermission.vue

@ -101,7 +101,12 @@
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index"; import {
logConsoleAndDb,
retrieveSettingsForActiveAccount,
secretDB,
} from "@/db/index";
import { MASTER_SECRET_KEY } from "@/db/tables/secret";
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util"; import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
@ -270,7 +275,7 @@ export default class PushNotificationPermission extends Vue {
}); });
} }
private askPermission(): Promise<NotificationPermission> { private async askPermission(): Promise<NotificationPermission> {
logConsoleAndDb( logConsoleAndDb(
"Requesting permission for notifications: " + JSON.stringify(navigator), "Requesting permission for notifications: " + JSON.stringify(navigator),
); );
@ -280,7 +285,8 @@ export default class PushNotificationPermission extends Vue {
return Promise.reject("Service worker not available."); return Promise.reject("Service worker not available.");
} }
const secret = localStorage.getItem("secret"); await secretDB.open();
const secret = (await secretDB.secret.get(MASTER_SECRET_KEY))?.secret;
if (!secret) { if (!secret) {
return Promise.reject("No secret found."); return Promise.reject("No secret found.");
} }

111
src/db/index.ts

@ -5,6 +5,7 @@ import * as R from "ramda";
import { Account, AccountsSchema } from "./tables/accounts"; import { Account, AccountsSchema } from "./tables/accounts";
import { Contact, ContactSchema } from "./tables/contacts"; import { Contact, ContactSchema } from "./tables/contacts";
import { Log, LogSchema } from "./tables/logs"; import { Log, LogSchema } from "./tables/logs";
import { MASTER_SECRET_KEY, Secret, SecretSchema } from "./tables/secret";
import { import {
MASTER_SETTINGS_KEY, MASTER_SETTINGS_KEY,
Settings, Settings,
@ -14,6 +15,7 @@ import { Temp, TempSchema } from "./tables/temp";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
// Define types for tables that hold sensitive and non-sensitive data // Define types for tables that hold sensitive and non-sensitive data
type SecretTable = { secret: Table<Secret> };
type SensitiveTables = { accounts: Table<Account> }; type SensitiveTables = { accounts: Table<Account> };
type NonsensitiveTables = { type NonsensitiveTables = {
contacts: Table<Contact>; contacts: Table<Contact>;
@ -23,25 +25,39 @@ type NonsensitiveTables = {
}; };
// Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings // Using 'unknown' instead of 'any' for stricter typing and to avoid TypeScript warnings
export type SecretDexie<T extends unknown = SecretTable> = BaseDexie & T;
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T; export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> = export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
BaseDexie & T; BaseDexie & T;
// Initialize Dexie databases for sensitive and non-sensitive data //// Initialize the DBs, starting with the sensitive ones.
export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie; // Initialize Dexie database for secret, which is then used to encrypt accountsDB
export const secretDB = new BaseDexie("TimeSafariSecret") as SecretDexie;
secretDB.version(1).stores(SecretSchema);
// Initialize Dexie database for accounts
const accountsDexie = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
// Manage the encryption key. If not present in localStorage, create and store it. // Instead of accountsDBPromise, use libs/util retrieveAccountMetadata or retrieveFullyDecryptedAccount
const secret = // so that it's clear whether the usage needs the private key inside.
localStorage.getItem("secret") || Encryption.createRandomEncryptionKey(); //
if (!localStorage.getItem("secret")) localStorage.setItem("secret", secret); // This is a promise because the decryption key comes from IndexedDB
// and someday it may come from a password or keystore or external wallet.
// It's important that usages take into account that there may be a delay due
// to a user action required to unlock the data.
export const accountsDBPromise = useSecretAndInitializeAccountsDB(
secretDB,
accountsDexie,
);
// Apply encryption to the sensitive database using the secret key //// Now initialize the other DB.
encrypted(accountsDB, { secretKey: secret });
// Initialize Dexie databases for non-sensitive data
export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
// Define the schemas for our databases
// Only the tables with index modifications need listing. https://dexie.org/docs/Tutorial/Design#database-versioning // Only the tables with index modifications need listing. https://dexie.org/docs/Tutorial/Design#database-versioning
accountsDB.version(1).stores(AccountsSchema);
// v1 also had contacts & settings // v1 also had contacts & settings
// v2 added Log // v2 added Log
db.version(2).stores({ db.version(2).stores({
@ -73,6 +89,79 @@ db.on("populate", async () => {
await db.settings.add(DEFAULT_SETTINGS); await db.settings.add(DEFAULT_SETTINGS);
}); });
// Manage the encryption key.
// It's not really secure to maintain the secret next to the user's data.
// However, until we have better hooks into a real wallet or reliable secure
// storage, we'll do this for user convenience. As they sign more records
// and integrate with more people, they'll value it more and want to be more
// secure, so we'll prompt them to take steps to back it up, properly encrypt,
// etc. At the beginning, we'll prompt for a password, then we'll prompt for a
// PWA so it's not in a browser... and then we hope to be integrated with a
// real wallet or something else more secure.
// One might ask: why encrypt at all? We figure a basic encryption is better
// than none. Plus, we expect to support their own password or keystore or
// external wallet as better signing options in the future, so it's gonna be
// important to have the structure where each account access might require
// user action.
// (Once upon a time we stored the secret in localStorage, but it frequently
// got erased, even though the IndexedDB still had the identity data. This
// ended up throwing lots of errors to the user... and they'd end up in a state
// where they couldn't take action because they couldn't unlock that identity.)
// check for the secret in storage
async function useSecretAndInitializeAccountsDB(
secretDB: SecretDexie,
accountsDB: SensitiveDexie,
): Promise<SensitiveDexie> {
return secretDB
.open()
.then(() => {
return secretDB.secret.get(MASTER_SECRET_KEY);
})
.then((secretRow?: Secret) => {
let secret = secretRow?.secret;
if (secret != null) {
// they already have it in IndexedDB, so just pass it along
return secret;
} else {
// check localStorage (for users before v 0.3.37)
const localSecret = localStorage.getItem("secret");
if (localSecret != null) {
// they had one, so we want to move it to IndexedDB
secret = localSecret;
} else {
// they didn't have one, so let's generate one
secret = Encryption.createRandomEncryptionKey();
}
// it is not in IndexedDB, so add it now
return secretDB.secret
.add({ id: MASTER_SECRET_KEY, secret })
.then(() => {
return secret;
});
}
})
.then((secret?: string) => {
if (secret == null) {
throw new Error("No secret found or created.");
} else {
// apply encryption to the sensitive database using the secret key
encrypted(accountsDB, { secretKey: secret });
accountsDB.version(1).stores(AccountsSchema);
accountsDB.open();
return accountsDB;
}
})
.catch((error) => {
logConsoleAndDb("Error processing secret & encrypted accountsDB.", error);
// alert("There was an error processing encrypted data. See the Help page.");
throw error;
});
}
// retrieves default settings // retrieves default settings
// calls db.open() // calls db.open()
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> { export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {

4
src/db/tables/accounts.ts

@ -5,7 +5,7 @@ export type Account = {
/** /**
* Auto-generated ID by Dexie * Auto-generated ID by Dexie
*/ */
id?: number; id?: number; // this is only blank on input, when the database assigns it
/** /**
* The date the account was created * The date the account was created
@ -48,7 +48,7 @@ export type Account = {
/** /**
* Schema for the accounts table in the database. * Schema for the accounts table in the database.
* Fields starting with a $ character are encrypted. * Fields starting with a $ character are encrypted.
* @see {@link https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon} * @see {@link https://github.com/PVermeer/dexie-addon-suite-monorepo/tree/master/packages/dexie-encrypted-addon#added-schema-syntax}
*/ */
export const AccountsSchema = { export const AccountsSchema = {
accounts: accounts:

4
src/db/tables/temp.ts

@ -9,6 +9,4 @@ export type Temp = {
/** /**
* Schema for the Temp table in the database. * Schema for the Temp table in the database.
*/ */
export const TempSchema = { export const TempSchema = { temp: "id" };
temp: "id",
};

7
src/libs/endorserServer.ts

@ -9,7 +9,8 @@ import { Contact } from "@/db/tables/contacts";
import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto"; import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto";
import { NonsensitiveDexie } from "@/db/index"; import { NonsensitiveDexie } from "@/db/index";
import { import {
getAccount, retrieveAccountMetadata,
retrieveFullyDecryptedAccount,
getPasskeyExpirationSeconds, getPasskeyExpirationSeconds,
GiverReceiverInputInfo, GiverReceiverInputInfo,
} from "@/libs/util"; } from "@/libs/util";
@ -506,7 +507,7 @@ export async function getHeaders(did?: string) {
}; };
if (did) { if (did) {
let token; let token;
const account = await getAccount(did); const account = await retrieveAccountMetadata(did);
if (account?.passkeyCredIdHex) { if (account?.passkeyCredIdHex) {
if ( if (
passkeyAccessToken && passkeyAccessToken &&
@ -1054,7 +1055,7 @@ export async function createEndorserJwtForDid(
payload: object, payload: object,
expiresIn?: number, expiresIn?: number,
) { ) {
const account = await getAccount(issuerDid); const account = await retrieveFullyDecryptedAccount(issuerDid);
return createEndorserJwtForKey(account as KeyMeta, payload, expiresIn); return createEndorserJwtForKey(account as KeyMeta, payload, expiresIn);
} }

60
src/libs/util.ts

@ -7,7 +7,7 @@ import { useClipboard } from "@vueuse/core";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { import {
accountsDB, accountsDBPromise,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings, updateAccountSettings,
updateDefaultSettings, updateDefaultSettings,
@ -422,10 +422,51 @@ export function findAllVisibleToDids(
export interface AccountKeyInfo extends Account, KeyMeta {} export interface AccountKeyInfo extends Account, KeyMeta {}
export const getAccount = async ( export const retrieveAccountCount = async (): Promise<number> => {
const accountsDB = await accountsDBPromise;
return await accountsDB.accounts.count();
};
export const retrieveAccountDids = async (): Promise<string[]> => {
const accountsDB = await accountsDBPromise;
const allAccounts = await accountsDB.accounts.toArray();
const allDids = allAccounts.map((acc) => acc.did);
return allDids;
};
// This is provided and recommended when the full key is not necessary so that
// future work could separate this info from the sensitive key material.
export const retrieveAccountMetadata = async (
activeDid: string,
): Promise<AccountKeyInfo | undefined> => {
const accountsDB = await accountsDBPromise;
const account = (await accountsDB.accounts
.where("did")
.equals(activeDid)
.first()) as Account;
if (account) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { identity, mnemonic, ...metadata } = account;
return metadata;
} else {
return undefined;
}
};
export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => {
const accountsDB = await accountsDBPromise;
const array = await accountsDB.accounts.toArray();
return array.map((account) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { identity, mnemonic, ...metadata } = account;
return metadata;
});
};
export const retrieveFullyDecryptedAccount = async (
activeDid: string, activeDid: string,
): Promise<AccountKeyInfo | undefined> => { ): Promise<AccountKeyInfo | undefined> => {
await accountsDB.open(); const accountsDB = await accountsDBPromise;
const account = (await accountsDB.accounts const account = (await accountsDB.accounts
.where("did") .where("did")
.equals(activeDid) .equals(activeDid)
@ -433,6 +474,15 @@ export const getAccount = async (
return account; return account;
}; };
// let's try and eliminate this
export const retrieveAllFullyDecryptedAccounts = async (): Promise<
Array<AccountKeyInfo>
> => {
const accountsDB = await accountsDBPromise;
const allAccounts = await accountsDB.accounts.toArray();
return allAccounts;
};
/** /**
* Generates a new identity, saves it to the database, and sets it as the active identity. * Generates a new identity, saves it to the database, and sets it as the active identity.
* @return {Promise<string>} with the DID of the new identity * @return {Promise<string>} with the DID of the new identity
@ -446,7 +496,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
const newId = newIdentifier(address, publicHex, privateHex, derivationPath); const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
const identity = JSON.stringify(newId); const identity = JSON.stringify(newId);
await accountsDB.open(); const accountsDB = await accountsDBPromise;
await accountsDB.accounts.add({ await accountsDB.accounts.add({
dateCreated: new Date().toISOString(), dateCreated: new Date().toISOString(),
derivationPath: derivationPath, derivationPath: derivationPath,
@ -477,7 +527,7 @@ export const registerAndSavePasskey = async (
passkeyCredIdHex, passkeyCredIdHex,
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"), publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
}; };
await accountsDB.open(); const accountsDB = await accountsDBPromise;
await accountsDB.accounts.add(account); await accountsDB.accounts.add(account);
return account; return account;
}; };

4
src/router/index.ts

@ -5,7 +5,7 @@ import {
RouteLocationNormalized, RouteLocationNormalized,
RouteRecordRaw, RouteRecordRaw,
} from "vue-router"; } from "vue-router";
import { accountsDB } from "@/db/index"; import { accountsDBPromise } from "@/db/index";
/** /**
* *
@ -18,7 +18,7 @@ const enterOrStart = async (
from: RouteLocationNormalized, from: RouteLocationNormalized,
next: NavigationGuardNext, next: NavigationGuardNext,
) => { ) => {
await accountsDB.open(); const accountsDB = await accountsDBPromise;
const num_accounts = await accountsDB.accounts.count(); const num_accounts = await accountsDB.accounts.count();
if (num_accounts > 0) { if (num_accounts > 0) {
next(); next();

81
src/views/AccountViewView.vue

@ -819,7 +819,6 @@ import {
} from "@/constants/app"; } from "@/constants/app";
import { import {
db, db,
accountsDB,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings, updateAccountSettings,
} from "@/db/index"; } from "@/db/index";
@ -839,7 +838,11 @@ import {
ImageRateLimits, ImageRateLimits,
tokenExpiryTimeDescription, tokenExpiryTimeDescription,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE, getAccount } from "@/libs/util"; import {
DAILY_CHECK_TITLE,
DIRECT_PUSH_TITLE,
retrieveAccountMetadata,
} from "@/libs/util";
const inputImportFileNameRef = ref<Blob>(); const inputImportFileNameRef = ref<Blob>();
@ -941,7 +944,7 @@ export default class AccountViewView extends Vue {
); );
// this sometimes gives different information on the error // this sometimes gives different information on the error
console.error( console.error(
"Telling user to clear cache at page create because (error added): " + "To repeat with concatenated error: telling user to clear cache at page create because: " +
error, error,
); );
this.$notify( this.$notify(
@ -1055,7 +1058,9 @@ export default class AccountViewView extends Vue {
* Processes the identity and updates the component's state. * Processes the identity and updates the component's state.
*/ */
async processIdentity() { async processIdentity() {
const account: Account | undefined = await getAccount(this.activeDid); const account: Account | undefined = await retrieveAccountMetadata(
this.activeDid,
);
if (account?.identity) { if (account?.identity) {
const identity = JSON.parse(account.identity as string) as IIdentifier; const identity = JSON.parse(account.identity as string) as IIdentifier;
this.publicHex = identity.keys[0].publicKeyHex; this.publicHex = identity.keys[0].publicKeyHex;
@ -1495,74 +1500,6 @@ export default class AccountViewView extends Vue {
} }
} }
/**
* Asynchronously switches the active account based on the provided account number.
*
* @param {number} accountNum - The account number to switch to. 0 means none.
*/
public async switchAccount(accountNum: number) {
await db.open(); // Assumes db needs to be open for both cases
if (accountNum === 0) {
this.switchToNoAccount();
} else {
await this.switchToAccountNumber(accountNum);
}
}
/**
* Switches to no active account and clears relevant properties.
*/
private async switchToNoAccount() {
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: undefined });
this.clearActiveAccountProperties();
}
/**
* Clears properties related to the active account.
*/
private clearActiveAccountProperties() {
this.activeDid = "";
this.derivationPath = "";
this.publicHex = "";
this.publicBase64 = "";
}
/**
* Switches to an account based on its number in the list.
*
* @param {number} accountNum - The account number to switch to.
*/
private async switchToAccountNumber(accountNum: number) {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = accounts[accountNum - 1];
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, { activeDid: account.did });
this.updateActiveAccountProperties(account);
}
/**
* Updates properties related to the active account.
*
* @param {AccountType} account - The account object.
*/
private updateActiveAccountProperties(account: Account) {
this.activeDid = account.did;
this.derivationPath = account.derivationPath || "";
this.publicHex = account.publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
}
public showContactGivesClassNames() {
return {
"bg-slate-900": !this.showContactGives,
"bg-green-600": this.showContactGives,
};
}
async onClickSaveApiServer() { async onClickSaveApiServer() {
await db.open(); await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, { await db.settings.update(MASTER_SETTINGS_KEY, {

12
src/views/ClaimView.vue

@ -482,17 +482,16 @@ import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
import { import {
GenericCredWrapper, GenericCredWrapper,
OfferVerifiableCredential, OfferVerifiableCredential,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
interface ProviderInfo { interface ProviderInfo {
identifier: string; // could be a DID or a handleId identifier: string; // could be a DID or a handleId
@ -560,10 +559,7 @@ export default class ClaimView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = settings.isRegistered || false; this.isRegistered = settings.isRegistered || false;
await accountsDB.open(); this.allMyDids = await libsUtil.retrieveAccountDids();
const accounts = accountsDB.accounts;
const accountsArr: Array<Account> = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const pathParam = window.location.pathname.substring("/claim/".length); const pathParam = window.location.pathname.substring("/claim/".length);
let claimId; let claimId;

10
src/views/ConfirmGiftView.vue

@ -412,13 +412,12 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { displayAmount, GiveSummaryRecord } from "@/libs/endorserServer"; import { displayAmount, GiveSummaryRecord } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { isGiveAction } from "@/libs/util"; import { isGiveAction, retrieveAccountDids } from "@/libs/util";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
@Component({ @Component({
@ -476,10 +475,7 @@ export default class ClaimView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = settings.isRegistered || false; this.isRegistered = settings.isRegistered || false;
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const accounts = accountsDB.accounts;
const accountsArr: Array<Account> = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const pathParam = window.location.pathname.substring( const pathParam = window.location.pathname.substring(
"/confirm-gift/".length, "/confirm-gift/".length,

6
src/views/ContactAmountsView.vue

@ -112,7 +112,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
AgreeVerifiableCredential, AgreeVerifiableCredential,
@ -123,6 +123,7 @@ import {
GiveVerifiableCredential, GiveVerifiableCredential,
SCHEMA_ORG_CONTEXT, SCHEMA_ORG_CONTEXT,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { retrieveAccountCount } from "@/libs/util";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class ContactAmountssView extends Vue { export default class ContactAmountssView extends Vue {
@ -137,8 +138,7 @@ export default class ContactAmountssView extends Vue {
displayAmount = displayAmount; displayAmount = displayAmount;
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); this.numAccounts = await retrieveAccountCount();
this.numAccounts = await accountsDB.accounts.count();
} }
async created() { async created() {

8
src/views/ContactQRScanShowView.vue

@ -91,7 +91,6 @@
<script lang="ts"> <script lang="ts">
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import QRCodeVue3 from "qr-code-generator-vue3"; import QRCodeVue3 from "qr-code-generator-vue3";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { QrcodeStream } from "vue-qrcode-reader"; import { QrcodeStream } from "vue-qrcode-reader";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -99,7 +98,7 @@ import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import UserNameDialog from "@/components/UserNameDialog.vue"; import UserNameDialog from "@/components/UserNameDialog.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { getContactPayloadFromJwtUrl } from "@/libs/crypto"; import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
@ -110,6 +109,7 @@ import {
setVisibilityUtil, setVisibilityUtil,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { ETHR_DID_PREFIX } from "@/libs/crypto/vc"; import { ETHR_DID_PREFIX } from "@/libs/crypto/vc";
import { retrieveAccountMetadata } from "@/libs/util";
@Component({ @Component({
components: { components: {
@ -140,9 +140,7 @@ export default class ContactQRScanShow extends Vue {
!!settings.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
await accountsDB.open(); const account = await retrieveAccountMetadata(this.activeDid);
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts);
if (account) { if (account) {
const name = const name =
(settings.firstName || "") + (settings.firstName || "") +

12
src/views/DIDView.vue

@ -254,7 +254,7 @@ import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { BoundingBox } from "@/db/tables/settings"; import { BoundingBox } from "@/db/tables/settings";
import { import {
@ -320,14 +320,8 @@ export default class DIDView extends Vue {
} }
await this.loadClaimsAbout(); await this.loadClaimsAbout();
await accountsDB.open(); const allAccountDids = await libsUtil.retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray(); this.isMyDid = allAccountDids.includes(this.viewingDid);
for (const account of allAccounts) {
if (account.did === this.viewingDid) {
this.isMyDid = true;
break;
}
}
} }
} }

8
src/views/DiscoverView.vue

@ -153,11 +153,11 @@ import ProjectIcon from "@/components/ProjectIcon.vue";
import OnboardingDialog from "@/components/OnboardingDialog.vue"; import OnboardingDialog from "@/components/OnboardingDialog.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { BoundingBox } from "@/db/tables/settings"; import { BoundingBox } from "@/db/tables/settings";
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer"; import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
import { OnboardPage } from "@/libs/util"; import { OnboardPage, retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { components: {
@ -195,9 +195,7 @@ export default class DiscoverView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
this.searchTerms = (this.$route as Router).query["searchText"] || ""; this.searchTerms = (this.$route as Router).query["searchText"] || "";

13
src/views/GiftedDetailsView.vue

@ -267,7 +267,7 @@ import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { import {
createAndSubmitGive, createAndSubmitGive,
didInfo, didInfo,
@ -279,7 +279,7 @@ import {
hydrateGive, hydrateGive,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { Contact } from "@/db/tables/contacts"; import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { components: {
@ -432,17 +432,12 @@ export default class GiftedDetails extends Vue {
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
let allContacts: Contact[] = [];
let allMyDids: string[] = [];
if ( if (
(this.giverDid && !this.giverName) || (this.giverDid && !this.giverName) ||
(this.recipientDid && !this.recipientName) (this.recipientDid && !this.recipientName)
) { ) {
allContacts = await db.contacts.toArray(); const allContacts = await db.contacts.toArray();
const allMyDids = await retrieveAccountDids();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
allMyDids = allAccounts.map((acc) => acc.did);
if (this.giverDid && !this.giverName) { if (this.giverDid && !this.giverName) {
this.giverName = didInfo( this.giverName = didInfo(
this.giverDid, this.giverDid,

9
src/views/HomeView.vue

@ -388,7 +388,6 @@ import {
PASSKEYS_ENABLED, PASSKEYS_ENABLED,
} from "@/constants/app"; } from "@/constants/app";
import { import {
accountsDB,
db, db,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings, updateAccountSettings,
@ -412,6 +411,7 @@ import {
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { import {
generateSaveAndActivateIdentity, generateSaveAndActivateIdentity,
retrieveAccountDids,
GiverReceiverInputInfo, GiverReceiverInputInfo,
OnboardPage, OnboardPage,
registerSaveAndActivatePasskey, registerSaveAndActivatePasskey,
@ -486,11 +486,8 @@ export default class HomeView extends Vue {
async mounted() { async mounted() {
try { try {
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray(); if (this.allMyDids.length === 0) {
if (allAccounts.length > 0) {
this.allMyDids = allAccounts.map((acc) => acc.did);
} else {
this.isCreatingIdentifier = true; this.isCreatingIdentifier = true;
const newDid = await generateSaveAndActivateIdentity(); const newDid = await generateSaveAndActivateIdentity();
this.isCreatingIdentifier = false; this.isCreatingIdentifier = false;

14
src/views/IdentitySwitcherView.vue

@ -101,10 +101,15 @@
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { db, accountsDB, retrieveSettingsForActiveAccount } from "@/db/index"; import {
accountsDBPromise,
db,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import QuickNav from "@/components/QuickNav.vue"; import { retrieveAllAccountsMetadata } from "@/libs/util";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class IdentitySwitcherView extends Vue { export default class IdentitySwitcherView extends Vue {
@ -123,8 +128,7 @@ export default class IdentitySwitcherView extends Vue {
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || ""; this.apiServerInput = settings.apiServer || "";
await accountsDB.open(); const accounts = await retrieveAllAccountsMetadata();
const accounts = await accountsDB.accounts.toArray();
for (let n = 0; n < accounts.length; n++) { for (let n = 0; n < accounts.length; n++) {
const acct = accounts[n]; const acct = accounts[n];
this.otherIdentities.push({ id: acct.id as string, did: acct.did }); this.otherIdentities.push({ id: acct.id as string, did: acct.did });
@ -166,7 +170,7 @@ export default class IdentitySwitcherView extends Vue {
title: "Delete Identity?", title: "Delete Identity?",
text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)", text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)",
onYes: async () => { onYes: async () => {
await accountsDB.open(); const accountsDB = await accountsDBPromise;
await accountsDB.accounts.delete(id); await accountsDB.accounts.delete(id);
this.otherIdentities = this.otherIdentities.filter( this.otherIdentities = this.otherIdentities.filter(
(ident) => ident.id !== id, (ident) => ident.id !== id,

12
src/views/ImportAccountView.vue

@ -87,13 +87,18 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { AppString, NotificationIface } from "@/constants/app"; import { AppString, NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import {
accountsDBPromise,
db,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { import {
DEFAULT_ROOT_DERIVATION_PATH, DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress, deriveAddress,
newIdentifier, newIdentifier,
} from "@/libs/crypto"; } from "@/libs/crypto";
import { retrieveAccountCount } from "@/libs/util";
@Component({ @Component({
components: {}, components: {},
@ -118,8 +123,7 @@ export default class ImportAccountView extends Vue {
shouldErase = false; shouldErase = false;
async created() { async created() {
await accountsDB.open(); this.numAccounts = await retrieveAccountCount();
this.numAccounts = await accountsDB.accounts.count();
// get the server, to help with import on the test server // get the server, to help with import on the test server
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
@ -148,7 +152,7 @@ export default class ImportAccountView extends Vue {
this.derivationPath, this.derivationPath,
); );
await accountsDB.open(); const accountsDB = await accountsDBPromise;
if (this.shouldErase) { if (this.shouldErase) {
await accountsDB.accounts.clear(); await accountsDB.accounts.clear();
} }

8
src/views/ImportDerivedAccountView.vue

@ -78,8 +78,9 @@ import {
newIdentifier, newIdentifier,
nextDerivationPath, nextDerivationPath,
} from "@/libs/crypto"; } from "@/libs/crypto";
import { accountsDB, db } from "@/db/index"; import { accountsDBPromise, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { retrieveAllFullyDecryptedAccounts } from "@/libs/util";
@Component({ @Component({
components: {}, components: {},
@ -90,8 +91,7 @@ export default class ImportAccountView extends Vue {
selectedArrayFirstDid = ""; selectedArrayFirstDid = "";
async mounted() { async mounted() {
await accountsDB.open(); const accounts = await retrieveAllFullyDecryptedAccounts(); // let's match derived accounts differently so we don't need the private info
const accounts = await accountsDB.accounts.toArray();
const seedDids: Record<string, Array<string>> = {}; const seedDids: Record<string, Array<string>> = {};
accounts.forEach((account) => { accounts.forEach((account) => {
const prevDids: Array<string> = seedDids[account.mnemonic] || []; const prevDids: Array<string> = seedDids[account.mnemonic] || [];
@ -110,11 +110,11 @@ export default class ImportAccountView extends Vue {
} }
public async incrementDerivation() { public async incrementDerivation() {
await accountsDB.open();
// find the maximum derivation path for the selected DIDs // find the maximum derivation path for the selected DIDs
const selectedArray: Array<string> = const selectedArray: Array<string> =
this.didArrays.find((dids) => dids[0] === this.selectedArrayFirstDid) || this.didArrays.find((dids) => dids[0] === this.selectedArrayFirstDid) ||
[]; [];
const accountsDB = await accountsDBPromise; // let's match derived accounts differently so we don't need the private info
const allMatchingAccounts = await accountsDB.accounts const allMatchingAccounts = await accountsDB.accounts
.where("did") .where("did")
.anyOf(...selectedArray) .anyOf(...selectedArray)

9
src/views/NewActivityView.vue

@ -149,7 +149,6 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { import {
accountsDB,
db, db,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings, updateAccountSettings,
@ -163,6 +162,7 @@ import {
OfferSummaryRecord, OfferSummaryRecord,
OfferToPlanSummaryRecord, OfferToPlanSummaryRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { GiftedDialog, QuickNav, EntityIcon }, components: { GiftedDialog, QuickNav, EntityIcon },
@ -196,12 +196,7 @@ export default class NewActivityView extends Vue {
settings.lastAckedOfferToUserProjectsJwtId || ""; settings.lastAckedOfferToUserProjectsJwtId || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.allMyDids = await retrieveAccountDids();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
if (allAccounts.length > 0) {
this.allMyDids = allAccounts.map((acc) => acc.did);
}
const offersToUserData = await getNewOffersToUser( const offersToUserData = await getNewOffersToUser(
this.axios, this.axios,

15
src/views/NewEditProjectView.vue

@ -209,13 +209,17 @@ import {
DEFAULT_PARTNER_API_SERVER, DEFAULT_PARTNER_API_SERVER,
NotificationIface, NotificationIface,
} from "@/constants/app"; } from "@/constants/app";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { import {
createEndorserJwtVcFromClaim, createEndorserJwtVcFromClaim,
getHeaders, getHeaders,
PlanVerifiableCredential, PlanVerifiableCredential,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { getAccount } from "@/libs/util"; import {
retrieveAccountCount,
retrieveAccountMetadata,
retrieveFullyDecryptedAccount,
} from "@/libs/util";
@Component({ @Component({
components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav }, components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav },
@ -258,8 +262,7 @@ export default class NewEditProjectView extends Vue {
zoom = 2; zoom = 2;
async mounted() { async mounted() {
await accountsDB.open(); this.numAccounts = await retrieveAccountCount();
this.numAccounts = await accountsDB.accounts.count();
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
@ -539,7 +542,7 @@ export default class NewEditProjectView extends Vue {
} }
private async signPayload(): Promise<VerifiedEvent> { private async signPayload(): Promise<VerifiedEvent> {
const account = await getAccount(this.activeDid); const account = await retrieveFullyDecryptedAccount(this.activeDid);
// get the last number of the derivationPath // get the last number of the derivationPath
const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0]; const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0];
// remove any trailing ' // remove any trailing '
@ -572,7 +575,7 @@ export default class NewEditProjectView extends Vue {
signedPayload: VerifiedEvent, signedPayload: VerifiedEvent,
) { ) {
// first, get the public key for nostr // first, get the public key for nostr
const account = await getAccount(this.activeDid); const account = await retrieveAccountMetadata(this.activeDid);
// get the last number of the derivationPath // get the last number of the derivationPath
const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0]; const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0];
// remove any trailing ' // remove any trailing '

13
src/views/OfferDetailsView.vue

@ -181,7 +181,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { import {
createAndSubmitOffer, createAndSubmitOffer,
didInfo, didInfo,
@ -192,7 +192,7 @@ import {
OfferVerifiableCredential, OfferVerifiableCredential,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { Contact } from "@/db/tables/contacts"; import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { components: {
@ -301,14 +301,9 @@ export default class OfferDetailsView extends Vue {
this.activeDid = settings.activeDid ?? ""; this.activeDid = settings.activeDid ?? "";
this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false; this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;
let allContacts: Contact[] = [];
let allMyDids: string[] = [];
if (this.recipientDid && !this.recipientName) { if (this.recipientDid && !this.recipientName) {
allContacts = await db.contacts.toArray(); const allContacts = await db.contacts.toArray();
const allMyDids = await retrieveAccountDids();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
allMyDids = allAccounts.map((acc) => acc.did);
this.recipientName = didInfo( this.recipientName = didInfo(
this.recipientDid, this.recipientDid,
this.activeDid, this.activeDid,

27
src/views/ProjectViewView.vue

@ -489,14 +489,11 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { import {
BLANK_GENERIC_SERVER_RECORD,
GenericCredWrapper, GenericCredWrapper,
getHeaders,
GiveSummaryRecord, GiveSummaryRecord,
GiveVerifiableCredential, GiveVerifiableCredential,
OfferSummaryRecord, OfferSummaryRecord,
@ -504,6 +501,7 @@ import {
PlanSummaryRecord, PlanSummaryRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { components: {
@ -559,10 +557,7 @@ export default class ProjectViewView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const accounts = accountsDB.accounts;
const accountsArr: Account[] = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const pathParam = window.location.pathname.substring("/project/".length); const pathParam = window.location.pathname.substring("/project/".length);
if (pathParam) { if (pathParam) {
@ -593,7 +588,7 @@ export default class ProjectViewView extends Vue {
const url = const url =
this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(projectId); this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(projectId);
const headers = await getHeaders(userDid); const headers = await serverUtil.getHeaders(userDid);
try { try {
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
@ -698,7 +693,7 @@ export default class ProjectViewView extends Vue {
} }
const givesInUrl = givesUrl + postfix; const givesInUrl = givesUrl + postfix;
const headers = await getHeaders(this.activeDid); const headers = await serverUtil.getHeaders(this.activeDid);
try { try {
const resp = await this.axios.get(givesInUrl, { headers }); const resp = await this.axios.get(givesInUrl, { headers });
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {
@ -745,7 +740,7 @@ export default class ProjectViewView extends Vue {
} }
const offersInUrl = offersUrl + postfix; const offersInUrl = offersUrl + postfix;
const headers = await getHeaders(this.activeDid); const headers = await serverUtil.getHeaders(this.activeDid);
try { try {
const resp = await this.axios.get(offersInUrl, { headers }); const resp = await this.axios.get(offersInUrl, { headers });
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {
@ -793,7 +788,7 @@ export default class ProjectViewView extends Vue {
} }
const fulfillsInUrl = fulfillsUrl + postfix; const fulfillsInUrl = fulfillsUrl + postfix;
const headers = await getHeaders(this.activeDid); const headers = await serverUtil.getHeaders(this.activeDid);
try { try {
const resp = await this.axios.get(fulfillsInUrl, { headers }); const resp = await this.axios.get(fulfillsInUrl, { headers });
if (resp.status === 200) { if (resp.status === 200) {
@ -841,7 +836,7 @@ export default class ProjectViewView extends Vue {
} }
const providedByFullUrl = providedByUrl + postfix; const providedByFullUrl = providedByUrl + postfix;
const headers = await getHeaders(this.activeDid); const headers = await serverUtil.getHeaders(this.activeDid);
try { try {
const resp = await this.axios.get(providedByFullUrl, { headers }); const resp = await this.axios.get(providedByFullUrl, { headers });
if (resp.status === 200) { if (resp.status === 200) {
@ -945,7 +940,7 @@ export default class ProjectViewView extends Vue {
checkIsFulfillable(offer: OfferSummaryRecord) { checkIsFulfillable(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper<OfferVerifiableCredential> = { const offerRecord: GenericCredWrapper<OfferVerifiableCredential> = {
...BLANK_GENERIC_SERVER_RECORD, ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim, claim: offer.fullClaim,
claimType: "Offer", claimType: "Offer",
issuer: offer.offeredByDid, issuer: offer.offeredByDid,
@ -955,7 +950,7 @@ export default class ProjectViewView extends Vue {
onClickFulfillGiveToOffer(offer: OfferSummaryRecord) { onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
const offerRecord: GenericCredWrapper<OfferVerifiableCredential> = { const offerRecord: GenericCredWrapper<OfferVerifiableCredential> = {
...BLANK_GENERIC_SERVER_RECORD, ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim, claim: offer.fullClaim,
issuer: offer.offeredByDid, issuer: offer.offeredByDid,
}; };
@ -1003,7 +998,7 @@ export default class ProjectViewView extends Vue {
*/ */
checkIsConfirmable(give: GiveSummaryRecord, confirmerIdList?: string[]) { checkIsConfirmable(give: GiveSummaryRecord, confirmerIdList?: string[]) {
const giveDetails: GenericCredWrapper<GiveVerifiableCredential> = { const giveDetails: GenericCredWrapper<GiveVerifiableCredential> = {
...BLANK_GENERIC_SERVER_RECORD, ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
claim: give.fullClaim, claim: give.fullClaim,
claimType: "GiveAction", claimType: "GiveAction",
issuer: give.issuerDid, issuer: give.issuerDid,

10
src/views/ProjectsView.vue

@ -263,8 +263,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import * as libsUtil from "@/libs/util";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@ -280,6 +279,7 @@ import {
OfferSummaryRecord, OfferSummaryRecord,
PlanData, PlanData,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { OnboardPage } from "@/libs/util"; import { OnboardPage } from "@/libs/util";
@Component({ @Component({
@ -328,9 +328,7 @@ export default class ProjectsView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.allMyDids = await libsUtil.retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
if (!settings.finishedOnboarding) { if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open( (this.$refs.onboardingDialog as OnboardingDialog).open(
@ -338,7 +336,7 @@ export default class ProjectsView extends Vue {
); );
} }
if (allAccounts.length === 0) { if (this.allMyDids.length === 0) {
console.error("No accounts found."); console.error("No accounts found.");
this.errNote("You need an identifier to load your projects."); this.errNote("You need an identifier to load your projects.");
} else { } else {

2
src/views/QuickActionBvcEndView.vue

@ -145,7 +145,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
BVC_MEETUPS_PROJECT_CLAIM_ID, BVC_MEETUPS_PROJECT_CLAIM_ID,

9
src/views/RecentOffersToUserProjectsView.vue

@ -81,7 +81,7 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
didInfo, didInfo,
@ -89,6 +89,7 @@ import {
getNewOffersToUserProjects, getNewOffersToUserProjects,
OfferToPlanSummaryRecord, OfferToPlanSummaryRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { EntityIcon, GiftedDialog, InfiniteScroll, QuickNav }, components: { EntityIcon, GiftedDialog, InfiniteScroll, QuickNav },
@ -119,11 +120,7 @@ export default class RecentOffersToUserView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray();
if (allAccounts.length > 0) {
this.allMyDids = allAccounts.map((acc) => acc.did);
}
const offersToUserProjectsData = await getNewOffersToUserProjects( const offersToUserProjectsData = await getNewOffersToUserProjects(
this.axios, this.axios,

9
src/views/RecentOffersToUserView.vue

@ -74,7 +74,7 @@ import EntityIcon from "@/components/EntityIcon.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
didInfo, didInfo,
@ -82,6 +82,7 @@ import {
getNewOffersToUser, getNewOffersToUser,
OfferSummaryRecord, OfferSummaryRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { retrieveAccountDids } from "@/libs/util";
@Component({ @Component({
components: { EntityIcon, GiftedDialog, InfiniteScroll, QuickNav }, components: { EntityIcon, GiftedDialog, InfiniteScroll, QuickNav },
@ -111,11 +112,7 @@ export default class RecentOffersToUserView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.allMyDids = await retrieveAccountDids();
const allAccounts = await accountsDB.accounts.toArray();
if (allAccounts.length > 0) {
this.allMyDids = allAccounts.map((acc) => acc.did);
}
const offersToUserData = await getNewOffersToUser( const offersToUserData = await getNewOffersToUser(
this.axios, this.axios,

13
src/views/SeedBackupView.vue

@ -99,14 +99,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import {
retrieveAccountCount,
retrieveFullyDecryptedAccount,
} from "@/libs/util";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class SeedBackupView extends Vue { export default class SeedBackupView extends Vue {
@ -124,10 +127,8 @@ export default class SeedBackupView extends Vue {
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
const activeDid = settings.activeDid || ""; const activeDid = settings.activeDid || "";
await accountsDB.open(); this.numAccounts = await retrieveAccountCount();
const accounts = await accountsDB.accounts.toArray(); this.activeAccount = await retrieveFullyDecryptedAccount(activeDid);
this.numAccounts = accounts.length;
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
} catch (err: unknown) { } catch (err: unknown) {
console.error("Got an error loading an identifier:", err); console.error("Got an error loading an identifier:", err);
this.$notify( this.$notify(

8
src/views/ShareMyContactInfoView.vue

@ -41,7 +41,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -49,8 +48,9 @@ import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { generateEndorserJwtForAccount } from "@/libs/endorserServer"; import { generateEndorserJwtForAccount } from "@/libs/endorserServer";
import { retrieveAccountMetadata } from "@/libs/util";
@Component({ @Component({
components: { QuickNav, TopMessage }, components: { QuickNav, TopMessage },
@ -65,9 +65,7 @@ export default class ShareMyContactInfoView extends Vue {
const isRegistered = !!settings.isRegistered; const isRegistered = !!settings.isRegistered;
const profileImageUrl = settings.profileImageUrl || ""; const profileImageUrl = settings.profileImageUrl || "";
await accountsDB.open(); const account = await retrieveAccountMetadata(activeDid);
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === activeDid, accounts);
const numContacts = await db.contacts.count(); const numContacts = await db.contacts.count();

10
src/views/StartView.vue

@ -92,8 +92,11 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { AppString, PASSKEYS_ENABLED } from "@/constants/app"; import { AppString, PASSKEYS_ENABLED } from "@/constants/app";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { registerSaveAndActivatePasskey } from "@/libs/util"; import {
registerSaveAndActivatePasskey,
retrieveAccountCount,
} from "@/libs/util";
@Component({ @Component({
components: {}, components: {},
@ -108,8 +111,7 @@ export default class StartView extends Vue {
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.givenName = settings.firstName || ""; this.givenName = settings.firstName || "";
await accountsDB.open(); this.numAccounts = await retrieveAccountCount();
this.numAccounts = await accountsDB.accounts.count();
} }
public onClickNewSeed() { public onClickNewSeed() {

14
src/views/TestView.vue

@ -247,7 +247,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { AppString, NotificationIface } from "@/constants/app"; import { AppString, NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import * as vcLib from "@/libs/crypto/vc"; import * as vcLib from "@/libs/crypto/vc";
import { import {
PeerSetup, PeerSetup,
@ -258,7 +258,7 @@ import {
import { import {
AccountKeyInfo, AccountKeyInfo,
blobToBase64, blobToBase64,
getAccount, retrieveAccountMetadata,
registerAndSavePasskey, registerAndSavePasskey,
SHARED_PHOTO_BASE64_KEY, SHARED_PHOTO_BASE64_KEY,
} from "@/libs/util"; } from "@/libs/util";
@ -294,11 +294,7 @@ export default class Help extends Vue {
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
this.userName = settings.firstName; this.userName = settings.firstName;
await accountsDB.open(); const account = await retrieveAccountMetadata(this.activeDid);
const account: { identity?: string } | undefined = await accountsDB.accounts
.where("did")
.equals(this.activeDid)
.first();
if (this.activeDid) { if (this.activeDid) {
if (account) { if (account) {
this.credIdHex = account.passkeyCredIdHex as string; this.credIdHex = account.passkeyCredIdHex as string;
@ -368,7 +364,7 @@ export default class Help extends Vue {
} }
public async createJwtSimplewebauthn() { public async createJwtSimplewebauthn() {
const account: AccountKeyInfo | undefined = await getAccount( const account: AccountKeyInfo | undefined = await retrieveAccountMetadata(
this.activeDid || "", this.activeDid || "",
); );
if (!vcLib.isFromPasskey(account)) { if (!vcLib.isFromPasskey(account)) {
@ -385,7 +381,7 @@ export default class Help extends Vue {
} }
public async createJwtNavigator() { public async createJwtNavigator() {
const account: AccountKeyInfo | undefined = await getAccount( const account: AccountKeyInfo | undefined = await retrieveAccountMetadata(
this.activeDid || "", this.activeDid || "",
); );
if (!vcLib.isFromPasskey(account)) { if (!vcLib.isFromPasskey(account)) {

2
sw_scripts/additional-scripts.js

@ -115,7 +115,7 @@ self.addEventListener("push", function (event) {
self.addEventListener("message", (event) => { self.addEventListener("message", (event) => {
logConsoleAndDb("Service worker got a message...", event); logConsoleAndDb("Service worker got a message...", event);
if (event.data && event.data.type === "SEND_LOCAL_DATA") { if (event.data && event.data.type === "SEND_LOCAL_DATA") {
self.secret = event.data.data; self.secret = event.data.data; // used in safari-notifications.js to decrypt the account identity
event.ports[0].postMessage({ success: true }); event.ports[0].postMessage({ success: true });
} }
logConsoleAndDb("Service worker posted a message."); logConsoleAndDb("Service worker posted a message.");

1
sw_scripts/safari-notifications.js

@ -515,6 +515,7 @@ async function getNotificationCount() {
const identity = activeAccount && activeAccount["identity"]; const identity = activeAccount && activeAccount["identity"];
if (identity && "secret" in self) { if (identity && "secret" in self) {
// get the "secret" pulled in additional-scripts.js to decrypt the "identity" inside the IndexedDB; see account.ts
const secret = self.secret; const secret = self.secret;
const secretUint8Array = self.decodeBase64(secret); const secretUint8Array = self.decodeBase64(secret);
const messageWithNonceAsUint8Array = self.decodeBase64(identity); const messageWithNonceAsUint8Array = self.decodeBase64(identity);

Loading…
Cancel
Save