Compare commits

...

2 Commits

  1. 7
      CHANGELOG.md
  2. 11
      README.md
  3. 13
      src/App.vue
  4. 11
      src/components/FeedFilters.vue
  5. 10
      src/components/GiftedDialog.vue
  6. 10
      src/components/OfferDialog.vue
  7. 8
      src/components/PhotoDialog.vue
  8. 11
      src/components/TopMessage.vue
  9. 9
      src/components/UserNameDialog.vue
  10. 10
      src/components/World/components/objects/landmarks.js
  11. 84
      src/db/index.ts
  12. 16
      src/db/tables/settings.ts
  13. 6
      src/libs/endorserServer.ts
  14. 40
      src/libs/util.ts
  15. 14
      src/test/index.ts
  16. 77
      src/views/AccountViewView.vue
  17. 10
      src/views/ClaimAddRawView.vue
  18. 12
      src/views/ClaimView.vue
  19. 20
      src/views/ConfirmGiftView.vue
  20. 10
      src/views/ContactAmountsView.vue
  21. 10
      src/views/ContactGiftingView.vue
  22. 7
      src/views/ContactImportView.vue
  23. 40
      src/views/ContactQRScanShowView.vue
  24. 26
      src/views/ContactsView.vue
  25. 120
      src/views/DIDView.vue
  26. 13
      src/views/DiscoverView.vue
  27. 10
      src/views/GiftedDetailsView.vue
  28. 50
      src/views/HomeView.vue
  29. 13
      src/views/IdentitySwitcherView.vue
  30. 11
      src/views/NewEditAccountView.vue
  31. 10
      src/views/NewEditProjectView.vue
  32. 10
      src/views/OfferDetailsView.vue
  33. 12
      src/views/ProjectViewView.vue
  34. 66
      src/views/ProjectsView.vue
  35. 10
      src/views/QuickActionBvcBeginView.vue
  36. 10
      src/views/QuickActionBvcEndView.vue
  37. 7
      src/views/SearchAreaView.vue
  38. 8
      src/views/SeedBackupView.vue
  39. 14
      src/views/ShareMyContactInfoView.vue
  40. 7
      src/views/SharedPhotoView.vue
  41. 8
      src/views/StartView.vue
  42. 10
      src/views/TestView.vue
  43. 31
      test-playwright/00-noid-tests.spec.ts
  44. 32
      test-playwright/40-add-contact.spec.ts
  45. 22
      test-playwright/testUtils.ts

7
CHANGELOG.md

@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.?]
### Added
- Separate 'isRegistered' flag for each account
### Fixed
- Alert when looking at one's own activity if not in contacts.
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82 ## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
### Added ### Added
- "Ideas" now jumps directly to giving prompt or contact list. - "Ideas" now jumps directly to giving prompt or contact list.

11
README.md

@ -97,7 +97,7 @@ It's possible to use the global test Endorser (ledger) server (but currently the
It's possible to run with a minimal set of data: the following starts with the bare minimum of test data (but currently the tests don't all succeed): It's possible to run with a minimal set of data; the following starts with the bare minimum of test data:
``` ```
rm ../endorser-ch-test-local.sqlite3 rm ../endorser-ch-test-local.sqlite3
NODE_ENV=test-local npm run flyway migrate NODE_ENV=test-local npm run flyway migrate
@ -106,6 +106,15 @@ NODE_ENV=test-local npm run dev
``` ```
To run a single test with the screenshots, use the following:
```
npx playwright test test-playwright/40-add-contact.spec.ts --trace on
```
... with the `-c playwright.config-local.ts` to get the same results as above.
### Register new user on test server ### Register new user on test server
On the test server, User #0 has rights to register others, so you can start On the test server, User #0 has rights to register others, so you can start

13
src/App.vue

@ -375,7 +375,10 @@
<script lang="ts"> <script lang="ts">
import axios from "axios"; import axios from "axios";
import { Vue, Component } from "vue-facing-decorator"; import { Vue, Component } from "vue-facing-decorator";
import { Router } from "vue-router";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { retrieveSettingsForActiveAccount } from "@/db/index";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
interface ServiceWorkerMessage { interface ServiceWorkerMessage {
@ -405,11 +408,6 @@ interface PushSubscriptionWithTime extends PushSubscriptionJSON {
notifyTime: { utcHour: number }; notifyTime: { utcHour: number };
} }
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { sendTestThroughPushServer } from "@/libs/util";
@Component @Component
export default class App extends Vue { export default class App extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
@ -422,8 +420,7 @@ export default class App extends Vue {
async mounted() { async mounted() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl = DEFAULT_PUSH_SERVER; let pushUrl = DEFAULT_PUSH_SERVER;
if (settings?.webPushServer) { if (settings?.webPushServer) {
pushUrl = settings.webPushServer; pushUrl = settings.webPushServer;
@ -640,7 +637,7 @@ export default class App extends Vue {
console.log( console.log(
"Subscription data sent to server and all finished successfully.", "Subscription data sent to server and all finished successfully.",
); );
await sendTestThroughPushServer(subscription, true); await libsUtil.sendTestThroughPushServer(subscription, true);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",

11
src/components/FeedFilters.vue

@ -100,7 +100,7 @@ import {
} from "@vue-leaflet/vue-leaflet"; } from "@vue-leaflet/vue-leaflet";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { db } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
@Component({ @Component({
components: { components: {
@ -121,11 +121,10 @@ export default class FeedFilters extends Vue {
async open(onCloseIfChanged: () => void) { async open(onCloseIfChanged: () => void) {
this.onCloseIfChanged = onCloseIfChanged; this.onCloseIfChanged = onCloseIfChanged;
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.hasVisibleDid = !!settings.filterFeedByVisible;
this.hasVisibleDid = !!settings?.filterFeedByVisible; this.isNearby = !!settings.filterFeedByNearby;
this.isNearby = !!settings?.filterFeedByNearby; if (settings.searchBoxes && settings.searchBoxes.length > 0) {
if (settings?.searchBoxes && settings.searchBoxes.length > 0) {
this.hasSearchBox = true; this.hasSearchBox = true;
} }

10
src/components/GiftedDialog.vue

@ -91,8 +91,7 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
@Component @Component
@ -138,10 +137,9 @@ export default class GiftedDialog extends Vue {
this.offerId = offerId || ""; this.offerId = offerId || "";
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();

10
src/components/OfferDialog.vue

@ -85,8 +85,7 @@ import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { createAndSubmitOffer } from "@/libs/endorserServer"; import { createAndSubmitOffer } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { db } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@Component @Component
export default class OfferDialog extends Vue { export default class OfferDialog extends Vue {
@ -113,10 +112,9 @@ export default class OfferDialog extends Vue {
this.recipientDid = recipientDid; this.recipientDid = recipientDid;
this.recipientName = recipientName; this.recipientName = recipientName;
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {

8
src/components/PhotoDialog.vue

@ -126,8 +126,7 @@ import { Component, Vue } from "vue-facing-decorator";
import VuePictureCropper, { cropper } from "vue-picture-cropper"; import VuePictureCropper, { cropper } from "vue-picture-cropper";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
@Component({ components: { Camera, VuePictureCropper } }) @Component({ components: { Camera, VuePictureCropper } })
@ -151,9 +150,8 @@ export default class PhotoDialog extends Vue {
async mounted() { async mounted() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
console.error("Error retrieving settings from database:", err); console.error("Error retrieving settings from database:", err);

11
src/components/TopMessage.vue

@ -16,8 +16,7 @@
import { Component, Vue, Prop } from "vue-facing-decorator"; import { Component, Vue, Prop } from "vue-facing-decorator";
import { AppString, NotificationIface } from "@/constants/app"; import { AppString, NotificationIface } from "@/constants/app";
import { db } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component @Component
export default class TopMessage extends Vue { export default class TopMessage extends Vue {
@ -29,17 +28,15 @@ export default class TopMessage extends Vue {
async mounted() { async mounted() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if ( if (
settings?.warnIfTestServer && settings.warnIfTestServer &&
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
) { ) {
const didPrefix = settings.activeDid?.slice(11, 15); const didPrefix = settings.activeDid?.slice(11, 15);
this.message = "You're linked to a non-prod server, user " + didPrefix; this.message = "You're linked to a non-prod server, user " + didPrefix;
} else if ( } else if (
settings?.warnIfProdServer && settings.warnIfProdServer &&
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
) { ) {
const didPrefix = settings.activeDid?.slice(11, 15); const didPrefix = settings.activeDid?.slice(11, 15);

9
src/components/UserNameDialog.vue

@ -39,8 +39,8 @@
import { Vue, Component } from "vue-facing-decorator"; import { Vue, Component } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component @Component
export default class UserNameDialog extends Vue { export default class UserNameDialog extends Vue {
@ -52,9 +52,8 @@ export default class UserNameDialog extends Vue {
async open(aCallback?: (name?: string) => void) { async open(aCallback?: (name?: string) => void) {
this.callback = aCallback || this.callback; this.callback = aCallback || this.callback;
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.givenName = settings.firstName || "";
this.givenName = settings?.firstName || "";
this.visible = true; this.visible = true;
} }

10
src/components/World/components/objects/landmarks.js

@ -3,8 +3,7 @@ import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader"; import { GLTFLoader } from "three/addons/loaders/GLTFLoader";
import * as SkeletonUtils from "three/addons/utils/SkeletonUtils"; import * as SkeletonUtils from "three/addons/utils/SkeletonUtils";
import * as TWEEN from "@tweenjs/tween.js"; import * as TWEEN from "@tweenjs/tween.js";
import { db } from "@/db"; import { retrieveSettingsForActiveAccount } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { getHeaders } from "@/libs/endorserServer"; import { getHeaders } from "@/libs/endorserServer";
const ANIMATION_DURATION_SECS = 10; const ANIMATION_DURATION_SECS = 10;
@ -14,10 +13,9 @@ export async function loadLandmarks(vue, world, scene, loop) {
vue.setWorldProperty("animationDurationSeconds", ANIMATION_DURATION_SECS); vue.setWorldProperty("animationDurationSeconds", ANIMATION_DURATION_SECS);
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const activeDid = settings.activeDid || "";
const activeDid = settings?.activeDid || ""; const apiServer = settings.apiServer;
const apiServer = settings?.apiServer;
const headers = await getHeaders(activeDid); const headers = await getHeaders(activeDid);
const url = apiServer + "/api/v2/report/claims?claimType=GiveAction"; const url = apiServer + "/api/v2/report/claims?claimType=GiveAction";

84
src/db/index.ts

@ -1,5 +1,7 @@
import BaseDexie, { Table } from "dexie"; import BaseDexie, { Table } from "dexie";
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon"; import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
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";
@ -45,15 +47,87 @@ accountsDB.version(1).stores(AccountsSchema);
db.version(2).stores({ db.version(2).stores({
...ContactSchema, ...ContactSchema,
...LogSchema, ...LogSchema,
...SettingsSchema, ...{ settings: "id" }, // old Settings schema
}); });
// v3 added Temp // v3 added Temp
db.version(3).stores(TempSchema); db.version(3).stores(TempSchema);
db.version(4)
.stores(SettingsSchema)
.upgrade((tx) => {
return tx
.table("settings")
.toCollection()
.modify((settings) => {
settings.accountDid = ""; // make it non-null for the default master settings, but still indexable
});
});
// Event handler to initialize the non-sensitive database with default settings const DEFAULT_SETTINGS = {
db.on("populate", async () => {
await db.settings.add({
id: MASTER_SETTINGS_KEY, id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER, apiServer: DEFAULT_ENDORSER_API_SERVER,
}); };
// Event handler to initialize the non-sensitive database with default settings
db.on("populate", async () => {
await db.settings.add(DEFAULT_SETTINGS);
}); });
// retrieves default settings
// calls db.open()
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
await db.open();
return (await db.settings.get(MASTER_SETTINGS_KEY)) || DEFAULT_SETTINGS;
}
export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
const defaultSettings = await retrieveSettingsForDefaultAccount();
if (!defaultSettings.activeDid) {
return defaultSettings;
} else {
const overrideSettings =
(await db.settings
.where("accountDid")
.equals(defaultSettings.activeDid)
.first()) || {};
return R.mergeDeepRight(defaultSettings, overrideSettings);
}
}
// Update settings for the given account, or in MASTER_SETTINGS_KEY if no accountDid is provided.
// Don't expose this because we should be explicit on whether we're updating the default settings or account settings.
async function updateSettings(settingsChanges: Settings): Promise<void> {
await db.open();
if (!settingsChanges.accountDid) {
// ensure there is no "id" that would override the key
delete settingsChanges.id;
await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges);
} else {
const result = await db.settings
.where("accountDid")
.equals(settingsChanges.accountDid)
.modify(settingsChanges);
if (result === 0) {
if (!settingsChanges.id) {
// It is unfortunate that we have to set this explicitly.
// We didn't make id a "++id" at the beginning and Dexie won't let us change it,
// plus we made our first settings objects MASTER_SETTINGS_KEY = 1 instead of 0
settingsChanges.id = (await db.settings.count()) + 1;
}
await db.settings.add(settingsChanges);
}
}
}
export async function updateDefaultSettings(settings: Settings): Promise<void> {
delete settings.accountDid; // just in case
await updateSettings(settings);
}
export async function updateAccountSettings(
accountDid: string,
settings: Settings,
): Promise<void> {
settings.accountDid = accountDid;
await updateSettings(settings);
}

16
src/db/tables/settings.ts

@ -12,22 +12,28 @@ export type BoundingBox = {
* Settings type encompasses user-specific configuration details. * Settings type encompasses user-specific configuration details.
*/ */
export type Settings = { export type Settings = {
id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY // default entry is keyed with MASTER_SETTINGS_KEY; other entries are linked to an account with account ID
id?: number; // this is only blank on input, when the database assigns it
// if supplied, this settings record overrides the master record when the user switches to this account
accountDid?: string; // not used in the MASTER_SETTINGS_KEY entry
// active Decentralized ID
activeDid?: string; // only used in the MASTER_SETTINGS_KEY entry
activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL apiServer?: string; // API server URL
filterFeedByNearby?: boolean; // filter by nearby filterFeedByNearby?: boolean; // filter by nearby
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
firstName?: string; // user's full name firstName?: string; // user's full name, may be null if unwanted for a particular account
hideRegisterPromptOnNewContact?: boolean; hideRegisterPromptOnNewContact?: boolean;
isRegistered?: boolean; isRegistered?: boolean;
imageServer?: string;
lastName?: string; // deprecated - put all names in firstName lastName?: string; // deprecated - put all names in firstName
lastNotifiedClaimId?: string; lastNotifiedClaimId?: string;
lastViewedClaimId?: string; lastViewedClaimId?: string;
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
profileImageUrl?: string; profileImageUrl?: string; // may be null if unwanted for a particular account
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders reminderOn?: boolean; // Toggle to enable or disable reminders
@ -54,7 +60,7 @@ export function isAnyFeedFilterOn(settings: Settings): boolean {
* Schema for the Settings table in the database. * Schema for the Settings table in the database.
*/ */
export const SettingsSchema = { export const SettingsSchema = {
settings: "id", settings: "id, &accountDid",
}; };
/** /**

6
src/libs/endorserServer.ts

@ -84,7 +84,9 @@ export interface GiveSummaryRecord {
amountConfirmed: number; amountConfirmed: number;
description: string; description: string;
fullClaim: GiveVerifiableCredential; fullClaim: GiveVerifiableCredential;
fulfillsPlanHandleId: string; fulfillsHandleId: string;
fulfillsPlanHandleId?: string;
fulfillsType?: string;
handleId: string; handleId: string;
issuedAt: string; issuedAt: string;
issuerDid: string; issuerDid: string;
@ -519,7 +521,7 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
* @param apiServer * @param apiServer
*/ */
export async function getPlanFromCache( export async function getPlanFromCache(
handleId: string | null, handleId: string,
axios: Axios, axios: Axios,
apiServer: string, apiServer: string,
requesterDid?: string, requesterDid?: string,

40
src/libs/util.ts

@ -6,21 +6,23 @@ import * as R from "ramda";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { DEFAULT_PUSH_SERVER } from "@/constants/app"; import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { accountsDB, db } from "@/db/index"; import {
accountsDB,
retrieveSettingsForActiveAccount,
updateAccountSettings,
updateDefaultSettings,
} from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "@/db/tables/settings";
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY,
} from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import * as serverUtil from "@/libs/endorserServer";
import { import {
containsHiddenDid, containsHiddenDid,
GenericCredWrapper, GenericCredWrapper,
GenericVerifiableCredential, GenericVerifiableCredential,
OfferVerifiableCredential, OfferVerifiableCredential,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
import { KeyMeta } from "@/libs/crypto/vc"; import { KeyMeta } from "@/libs/crypto/vc";
import { createPeerDid } from "@/libs/crypto/vc/didPeer"; import { createPeerDid } from "@/libs/crypto/vc/didPeer";
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer"; import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
@ -311,9 +313,9 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
publicKeyHex: newId.keys[0].publicKeyHex, publicKeyHex: newId.keys[0].publicKeyHex,
}); });
await db.settings.update(MASTER_SETTINGS_KEY, { await updateDefaultSettings({ activeDid: newId.did });
activeDid: newId.did, console.log("Updated default settings in util");
}); await updateAccountSettings(newId.did, { isRegistered: false });
return newId.did; return newId.did;
}; };
@ -341,30 +343,24 @@ export const registerSaveAndActivatePasskey = async (
keyName: string, keyName: string,
): Promise<Account> => { ): Promise<Account> => {
const account = await registerAndSavePasskey(keyName); const account = await registerAndSavePasskey(keyName);
await updateDefaultSettings({ activeDid: account.did });
await db.open(); await updateAccountSettings(account.did, { isRegistered: false });
await db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: account.did,
});
return account; return account;
}; };
export const getPasskeyExpirationSeconds = async (): Promise<number> => { export const getPasskeyExpirationSeconds = async (): Promise<number> => {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); return (
const passkeyExpirationSeconds =
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) * (settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) *
60; 60
return passkeyExpirationSeconds; );
}; };
export const sendTestThroughPushServer = async ( export const sendTestThroughPushServer = async (
subscriptionJSON: PushSubscriptionJSON, subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean, skipFilter: boolean,
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
let pushUrl: string = DEFAULT_PUSH_SERVER as string; let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) { if (settings?.webPushServer) {
pushUrl = settings.webPushServer; pushUrl = settings.webPushServer;

14
src/test/index.ts

@ -1,10 +1,9 @@
import axios from "axios"; import axios from "axios";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { db } from "../db"; import { retrieveSettingsForActiveAccount } from "../db";
import { SERVICE_ID } from "../libs/endorserServer"; import { SERVICE_ID } from "@/libs/endorserServer";
import { deriveAddress, newIdentifier } from "../libs/crypto"; import { deriveAddress, newIdentifier } from "@/libs/crypto";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
/** /**
* Get User #0 to sign & submit a RegisterAction for the user's activeDid. * Get User #0 to sign & submit a RegisterAction for the user's activeDid.
@ -17,8 +16,7 @@ export async function testServerRegisterUser() {
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath); const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Make a claim // Make a claim
const vcClaim = { const vcClaim = {
@ -26,7 +24,7 @@ export async function testServerRegisterUser() {
"@type": "RegisterAction", "@type": "RegisterAction",
agent: { did: identity0.did }, agent: { did: identity0.did },
object: SERVICE_ID, object: SERVICE_ID,
participant: { did: settings?.activeDid }, participant: { did: settings.activeDid },
}; };
// Make a payload for the claim // Make a payload for the claim
const vcPayload = { const vcPayload = {
@ -53,7 +51,7 @@ export async function testServerRegisterUser() {
const payload = JSON.stringify({ jwtEncoded: vcJwt }); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const endorserApiServer = const endorserApiServer =
settings?.apiServer || AppString.TEST_ENDORSER_API_SERVER; settings.apiServer || AppString.TEST_ENDORSER_API_SERVER;
const url = endorserApiServer + "/api/claim"; const url = endorserApiServer + "/api/claim";
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",

77
src/views/AccountViewView.vue

@ -9,24 +9,11 @@
Your Identity Your Identity
</h1> </h1>
<div class="flex justify-between mb-2 mt-4">
<span />
<span class="whitespace-nowrap">
<router-link
:to="{ name: 'contact-qr' }"
class="text-xs bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw"></fa>
</router-link>
</span>
<span />
</div>
<!-- ID notice --> <!-- ID notice -->
<div <div
v-if="!activeDid" v-if="!activeDid"
id="noticeBeforeShare" id="noticeBeforeShare"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
> >
<p class="mb-4"> <p class="mb-4">
<b>Note:</b> Before you can share with others or take any action, you <b>Note:</b> Before you can share with others or take any action, you
@ -43,10 +30,18 @@
<!-- Identity Details --> <!-- Identity Details -->
<div <div
id="sectionIdentityDetails" id="sectionIdentityDetails"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4" class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"
> >
<div v-if="givenName"> <div v-if="givenName">
<h2 class="text-xl font-semibold mb-2"> <h2 class="text-xl font-semibold mb-2">
<span class="whitespace-nowrap">
<router-link
:to="{ name: 'contact-qr' }"
class="bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw text-xl"></fa>
</router-link>
</span>
{{ givenName }} {{ givenName }}
<router-link :to="{ name: 'new-edit-account' }"> <router-link :to="{ name: 'new-edit-account' }">
<fa icon="pen" class="text-xs text-blue-500 ml-2 mb-1"></fa> <fa icon="pen" class="text-xs text-blue-500 ml-2 mb-1"></fa>
@ -160,7 +155,7 @@
<div <div
v-if="!loadingLimits && !endorserLimits?.nextWeekBeginDateTime" v-if="!loadingLimits && !endorserLimits?.nextWeekBeginDateTime"
id="noticeBeforeAnnounce" id="noticeBeforeAnnounce"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
> >
<p class="mb-4"> <p class="mb-4">
<b>Note:</b> Before you can publicly announce a new project or time <b>Note:</b> Before you can publicly announce a new project or time
@ -312,7 +307,7 @@
> >
If no download happened yet, click again here to download now. If no download happened yet, click again here to download now.
</a> </a>
<div> <div class="mt-4">
<p> <p>
After the download, you can save the file in your preferred storage After the download, you can save the file in your preferred storage
location. location.
@ -731,13 +726,17 @@ import {
IMAGE_TYPE_PROFILE, IMAGE_TYPE_PROFILE,
NotificationIface, NotificationIface,
} from "@/constants/app"; } from "@/constants/app";
import { db, accountsDB } from "@/db/index"; import {
db,
accountsDB,
retrieveSettingsForActiveAccount,
updateAccountSettings,
} from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES, DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY, MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings"; } from "@/db/tables/settings";
import { import {
clearPasskeyToken, clearPasskeyToken,
@ -865,31 +864,29 @@ export default class AccountViewView extends Vue {
*/ */
async initializeState() { async initializeState() {
await db.open(); await db.open();
const settings: Settings | undefined = const settings = await retrieveSettingsForActiveAccount();
await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || ""; this.activeDid = settings.activeDid || "";
this.apiServer = (settings?.apiServer as string) || ""; this.apiServer = settings.apiServer || "";
this.apiServerInput = (settings?.apiServer as string) || ""; this.apiServerInput = settings.apiServer || "";
this.givenName = this.givenName =
(settings?.firstName || "") + (settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3 (settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
this.imageServer = (settings?.imageServer as string) || ""; this.imageServer = settings.imageServer || "";
this.profileImageUrl = settings?.profileImageUrl as string; this.profileImageUrl = settings.profileImageUrl;
this.showContactGives = !!settings?.showContactGivesInline; this.showContactGives = !!settings.showContactGivesInline;
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.passkeyExpirationMinutes = this.passkeyExpirationMinutes =
(settings?.passkeyExpirationMinutes as number) ?? settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
DEFAULT_PASSKEY_EXPIRATION_MINUTES;
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes; this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
this.showGeneralAdvanced = !!settings?.showGeneralAdvanced; this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
this.showShortcutBvc = !!settings?.showShortcutBvc; this.showShortcutBvc = !!settings.showShortcutBvc;
this.warnIfProdServer = !!settings?.warnIfProdServer; this.warnIfProdServer = !!settings.warnIfProdServer;
this.warnIfTestServer = !!settings?.warnIfTestServer; this.warnIfTestServer = !!settings.warnIfTestServer;
this.webPushServer = (settings?.webPushServer as string) || ""; this.webPushServer = settings.webPushServer || "";
this.webPushServerInput = (settings?.webPushServer as string) || ""; this.webPushServerInput = settings.webPushServer || "";
} }
// call fn, copy text to the clipboard, then redo fn after 2 seconds // call fn, copy text to the clipboard, then redo fn after 2 seconds
@ -1264,10 +1261,7 @@ export default class AccountViewView extends Vue {
if (!this.isRegistered) { if (!this.isRegistered) {
// the user was not known to be registered, but now they are (because we got no error) so let's record it // the user was not known to be registered, but now they are (because we got no error) so let's record it
try { try {
await db.open(); await updateAccountSettings(did, { isRegistered: true });
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: true,
});
this.isRegistered = true; this.isRegistered = true;
} catch (err) { } catch (err) {
console.error("Got an error updating settings:", err); console.error("Got an error updating settings:", err);
@ -1478,8 +1472,7 @@ export default class AccountViewView extends Vue {
if ((error as any).response.status === 404) { if ((error as any).response.status === 404) {
console.error("The image was already deleted:", error); console.error("The image was already deleted:", error);
await db.open(); await updateAccountSettings(this.activeDid, {
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: undefined, profileImageUrl: undefined,
}); });

10
src/views/ClaimAddRawView.vue

@ -33,8 +33,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 { db } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@ -50,10 +49,9 @@ export default class ClaimAddRawView extends Vue {
claimStr = ""; claimStr = "";
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || ""; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || "";
this.claimStr = (this.$route as Router).query["claim"]; this.claimStr = (this.$route as Router).query["claim"];
try { try {

12
src/views/ClaimView.vue

@ -454,9 +454,8 @@ import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@ -519,12 +518,11 @@ export default class ClaimView extends Vue {
} }
async created() { async created() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || ""; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || "";
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(); await accountsDB.open();
const accounts = accountsDB.accounts; const accounts = accountsDB.accounts;

20
src/views/ConfirmGiftView.vue

@ -65,7 +65,7 @@
<!-- Details --> <!-- Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4">
<div class="block flex gap-4 overflow-hidden"> <div class="flex gap-4 overflow-hidden">
<div class="overflow-hidden"> <div class="overflow-hidden">
<div class="text-sm"> <div class="text-sm">
<div> <div>
@ -100,7 +100,7 @@
<router-link <router-link
:to=" :to="
'/project/' + '/project/' +
encodeURIComponent(giveDetails?.fulfillsPlanHandleId) encodeURIComponent(giveDetails?.fulfillsPlanHandleId || '')
" "
class="text-blue-500 mt-2 cursor-pointer" class="text-blue-500 mt-2 cursor-pointer"
target="_blank" target="_blank"
@ -121,7 +121,7 @@
<router-link <router-link
:to=" :to="
'/claim/' + '/claim/' +
encodeURIComponent(giveDetails?.fulfillsHandleId) encodeURIComponent(giveDetails?.fulfillsHandleId || '')
" "
class="text-blue-500 mt-2 cursor-pointer" class="text-blue-500 mt-2 cursor-pointer"
target="_blank" target="_blank"
@ -129,7 +129,7 @@
This fulfills This fulfills
{{ {{
capitalizeAndInsertSpacesBeforeCapsWithAPrefix( capitalizeAndInsertSpacesBeforeCapsWithAPrefix(
giveDetails.fulfillsType, giveDetails?.fulfillsType || "",
) )
}} }}
<fa icon="arrow-up-right-from-square" class="fa-fw" /> <fa icon="arrow-up-right-from-square" class="fa-fw" />
@ -405,10 +405,9 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
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";
@ -464,12 +463,11 @@ export default class ClaimView extends Vue {
async mounted() { async mounted() {
this.isLoading = true; this.isLoading = true;
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || ""; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || "";
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(); await accountsDB.open();
const accounts = accountsDB.accounts; const accounts = accountsDB.accounts;

10
src/views/ContactAmountsView.vue

@ -112,9 +112,8 @@ 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 } from "@/db/index"; import { accountsDB, 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 { import {
AgreeVerifiableCredential, AgreeVerifiableCredential,
createEndorserJwtVcFromClaim, createEndorserJwtVcFromClaim,
@ -144,13 +143,12 @@ export default class ContactAmountssView extends Vue {
async created() { async created() {
try { try {
await db.open();
const contactDid = (this.$route as Router).query["contactDid"] as string; const contactDid = (this.$route as Router).query["contactDid"] as string;
this.contact = (await db.contacts.get(contactDid)) || null; this.contact = (await db.contacts.get(contactDid)) || null;
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await retrieveSettingsForActiveAccount();
this.activeDid = (settings?.activeDid as string) || ""; this.activeDid = settings?.activeDid || "";
this.apiServer = (settings?.apiServer as string) || ""; this.apiServer = settings?.apiServer || "";
if (this.activeDid && this.contact) { if (this.activeDid && this.contact) {
this.loadGives(this.activeDid, this.contact); this.loadGives(this.activeDid, this.contact);

10
src/views/ContactGiftingView.vue

@ -77,9 +77,8 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; 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 { db } 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, Settings } from "@/db/tables/settings";
import { GiverReceiverInputInfo } from "@/libs/util"; import { GiverReceiverInputInfo } from "@/libs/util";
@Component({ @Component({
@ -97,10 +96,9 @@ export default class ContactGiftingView extends Vue {
async created() { async created() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
// .orderBy("name") wouldn't retrieve any entries with a blank name // .orderBy("name") wouldn't retrieve any entries with a blank name
// .toCollection.sortBy("name") didn't sort in an order I understood // .toCollection.sortBy("name") didn't sort in an order I understood

7
src/views/ContactImportView.vue

@ -16,9 +16,9 @@
Contact Import Contact Import
</h1> </h1>
<span> <span class="flex justify-center">
Note that you will have to make them visible one-by-one in the list of <input type="checkbox" v-model="makeVisible" class="mr-2" />
Contacts. Make my activity visible to these contacts.
</span> </span>
<div v-if="sameCount > 0"> <div v-if="sameCount > 0">
<span v-if="sameCount == 1" <span v-if="sameCount == 1"
@ -115,6 +115,7 @@ export default class ContactImportView extends Vue {
Record<string, { new: string; old: string }> Record<string, { new: string; old: string }>
> = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key > = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
importing = false; importing = false;
makeVisible = true;
sameCount = 0; sameCount = 0;
async created() { async created() {

40
src/views/ContactQRScanShowView.vue

@ -101,7 +101,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 } from "@/db/index"; import { accountsDB, 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 { import {
@ -141,14 +141,13 @@ export default class ContactQRScanShow extends Vue {
ETHR_DID_PREFIX = ETHR_DID_PREFIX; ETHR_DID_PREFIX = ETHR_DID_PREFIX;
async created() { async created() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid || "";
this.activeDid = (settings?.activeDid as string) || ""; this.apiServer = settings.apiServer || "";
this.apiServer = (settings?.apiServer as string) || ""; this.givenName = settings.firstName || "";
this.givenName = (settings?.firstName as string) || "";
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings.isRegistered;
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
@ -162,22 +161,17 @@ export default class ContactQRScanShow extends Vue {
iss: this.activeDid, iss: this.activeDid,
own: { own: {
name: name:
(settings?.firstName || "") + (settings.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""), // lastName is deprecated, pre v 0.1.3 (settings.lastName ? ` ${settings.lastName}` : ""), // lastName is deprecated, pre v 0.1.3
publicEncKey, publicEncKey,
profileImageUrl: settings?.profileImageUrl, profileImageUrl: settings.profileImageUrl,
registered: settings?.isRegistered, registered: settings.isRegistered,
}, },
}; };
if (account?.mnemonic && account?.derivationPath) { if (account?.mnemonic && account?.derivationPath) {
const newDerivPath = nextDerivationPath( const newDerivPath = nextDerivationPath(account.derivationPath);
account.derivationPath as string, const nextPublicHex = deriveAddress(account.mnemonic, newDerivPath)[2];
);
const nextPublicHex = deriveAddress(
account.mnemonic as string,
newDerivPath,
)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex"); const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey); const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 = const nextPublicEncKeyHashBase64 =
@ -191,14 +185,14 @@ export default class ContactQRScanShow extends Vue {
viewPrefix + vcJwt; viewPrefix + vcJwt;
const name = const name =
(settings?.firstName || "") + (settings.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3 (settings.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3
this.qrValue = await generateEndorserJwtForAccount( this.qrValue = await generateEndorserJwtForAccount(
account, account,
!!settings?.isRegistered, !!settings.isRegistered,
name, name,
settings?.profileImageUrl as string, settings.profileImageUrl,
); );
} }
} }

26
src/views/ContactsView.vue

@ -292,9 +292,12 @@ import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { AppString, NotificationIface } from "@/constants/app"; import { AppString, NotificationIface } from "@/constants/app";
import { db } from "@/db/index"; import {
db,
retrieveSettingsForActiveAccount,
updateDefaultSettings,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { getContactPayloadFromJwtUrl } from "@/libs/crypto"; import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
import { import {
CONTACT_CSV_HEADER, CONTACT_CSV_HEADER,
@ -352,14 +355,14 @@ export default class ContactsView extends Vue {
public async created() { public async created() {
await db.open(); await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings?.activeDid || ""; this.activeDid = settings.activeDid || "";
this.apiServer = settings?.apiServer || ""; this.apiServer = settings.apiServer || "";
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings.isRegistered;
this.showGiveNumbers = !!settings?.showContactGivesInline; this.showGiveNumbers = !!settings.showContactGivesInline;
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
if (this.showGiveNumbers) { if (this.showGiveNumbers) {
this.loadGives(); this.loadGives();
@ -715,7 +718,7 @@ export default class ContactsView extends Vue {
text: "Do you want to register them?", text: "Do you want to register them?",
onCancel: async (stopAsking: boolean) => { onCancel: async (stopAsking: boolean) => {
if (stopAsking) { if (stopAsking) {
await db.settings.update(MASTER_SETTINGS_KEY, { await updateDefaultSettings({
hideRegisterPromptOnNewContact: stopAsking, hideRegisterPromptOnNewContact: stopAsking,
}); });
this.hideRegisterPromptOnNewContact = stopAsking; this.hideRegisterPromptOnNewContact = stopAsking;
@ -723,7 +726,7 @@ export default class ContactsView extends Vue {
}, },
onNo: async (stopAsking: boolean) => { onNo: async (stopAsking: boolean) => {
if (stopAsking) { if (stopAsking) {
await db.settings.update(MASTER_SETTINGS_KEY, { await updateDefaultSettings({
hideRegisterPromptOnNewContact: stopAsking, hideRegisterPromptOnNewContact: stopAsking,
}); });
this.hideRegisterPromptOnNewContact = stopAsking; this.hideRegisterPromptOnNewContact = stopAsking;
@ -1067,8 +1070,7 @@ export default class ContactsView extends Vue {
private async toggleShowContactAmounts() { private async toggleShowContactAmounts() {
const newShowValue = !this.showGiveNumbers; const newShowValue = !this.showGiveNumbers;
try { try {
await db.open(); await updateDefaultSettings({
await db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: newShowValue, showContactGivesInline: newShowValue,
}); });
} catch (err) { } catch (err) {

120
src/views/DIDView.vue

@ -19,14 +19,17 @@
</div> </div>
<!-- Identity Details --> <!-- Identity Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div
v-if="!!contactFromDid"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"
>
<div> <div>
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
{{ contact?.name || "(no name)" }} {{ contactFromDid?.name || "(no name)" }}
<button <button
@click=" @click="
contactEdit = true; contactEdit = true;
contactNewName = contact.name || ''; contactNewName = (contactFromDid?.name as string) || '';
" "
title="Edit" title="Edit"
> >
@ -49,12 +52,15 @@
> >
</div> </div>
<div class="flex justify-center mt-4"> <div class="flex justify-center mt-4">
<span v-if="contact?.profileImageUrl" class="flex justify-between"> <span
v-if="contactFromDid?.profileImageUrl"
class="flex justify-between"
>
<EntityIcon <EntityIcon
:icon-size="96" :icon-size="96"
:profileImageUrl="contact?.profileImageUrl" :profileImageUrl="contactFromDid?.profileImageUrl"
class="inline-block align-text-bottom border border-slate-300 rounded" class="inline-block align-text-bottom border border-slate-300 rounded"
@click="showLargeIdenticonUrl = contact?.profileImageUrl" @click="showLargeIdenticonUrl = contactFromDid?.profileImageUrl"
/> />
</span> </span>
</div> </div>
@ -63,17 +69,21 @@
<div v-if="activeDid" class="flex justify-between"> <div v-if="activeDid" class="flex justify-between">
<div> <div>
<button <button
v-if="contact?.seesMe && contact.did !== activeDid" v-if="
contactFromDid?.seesMe && contactFromDid.did !== activeDid
"
class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md" class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="confirmSetVisibility(contact, false)" @click="confirmSetVisibility(contactFromDid, false)"
title="They can see you" title="They can see you"
> >
<fa icon="eye" class="fa-fw" /> <fa icon="eye" class="fa-fw" />
</button> </button>
<button <button
v-else-if="!contact?.seesMe && contact?.did !== activeDid" v-else-if="
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
"
class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md" class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="confirmSetVisibility(contact, true)" @click="confirmSetVisibility(contactFromDid, true)"
title="They cannot see you" title="They cannot see you"
> >
<fa icon="eye-slash" class="fa-fw" /> <fa icon="eye-slash" class="fa-fw" />
@ -83,9 +93,9 @@
<button <button
class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md" class="text-sm 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 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="checkVisibility(contact)" @click="checkVisibility(contactFromDid)"
title="Check Visibility" title="Check Visibility"
v-if="contact?.did !== activeDid" v-if="contactFromDid?.did !== activeDid"
> >
<fa icon="rotate" class="fa-fw" /> <fa icon="rotate" class="fa-fw" />
</button> </button>
@ -94,13 +104,13 @@
</div> </div>
<button <button
@click="confirmRegister(contact)" @click="confirmRegister(contactFromDid)"
class="text-sm 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 ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md" class="text-sm 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 ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
v-if="contact?.did !== activeDid" v-if="contactFromDid?.did !== activeDid"
title="Registration" title="Registration"
> >
<fa <fa
v-if="contact?.registered" v-if="contactFromDid?.registered"
icon="person-circle-check" icon="person-circle-check"
class="fa-fw" class="fa-fw"
/> />
@ -111,14 +121,14 @@
</div> </div>
<button <button
@click="confirmDeleteContact(contact)" @click="confirmDeleteContact(contactFromDid)"
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md" class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
title="Delete" title="Delete"
> >
<fa icon="trash-can" class="fa-fw" /> <fa icon="trash-can" class="fa-fw" />
</button> </button>
</div> </div>
<div v-if="!contact?.profileImageUrl"> <div v-if="!contactFromDid?.profileImageUrl">
<div>Auto-Generated Icon</div> <div>Auto-Generated Icon</div>
<div class="flex justify-center"> <div class="flex justify-center">
<EntityIcon <EntityIcon
@ -150,6 +160,15 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<!-- !contactFromDid -->
<div>
<h2 class="text-xl font-semibold">
{{ isMyDid ? "You" : "(no name)" }}
</h2>
</div>
</div>
<div v-if="contactEdit" class="dialog-overlay"> <div v-if="contactEdit" class="dialog-overlay">
<div class="dialog"> <div class="dialog">
<h1 class="text-xl font-bold text-center mb-4">Edit Name</h1> <h1 class="text-xl font-bold text-center mb-4">Edit Name</h1>
@ -222,7 +241,8 @@
v-if="!isLoading && claims.length === 0" v-if="!isLoading && claims.length === 0"
class="flex justify-center mt-4" class="flex justify-center mt-4"
> >
<span>They are in no claims visible to you.</span> <span v-if="isMyDid">You have no claims yet.</span>
<span v-else>They are in no claims visible to you.</span>
</div> </div>
</section> </section>
</template> </template>
@ -237,9 +257,9 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { BoundingBox } from "@/db/tables/settings";
import { import {
capitalizeAndInsertSpacesBeforeCaps, capitalizeAndInsertSpacesBeforeCaps,
didInfoForContact, didInfoForContact,
@ -270,15 +290,15 @@ export default class DIDView extends Vue {
yaml = yaml; yaml = yaml;
activeDid = ""; activeDid = "";
allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = []; claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
contact: Contact; contactFromDid?: Contact;
contactEdit = false; contactEdit = false;
contactNewName?: string; contactNewName: string = "";
contactYaml = ""; contactYaml = "";
hitEnd = false; hitEnd = false;
isLoading = false; isLoading = false;
isMyDid = false;
searchBox: { name: string; bbox: BoundingBox } | null = null; searchBox: { name: string; bbox: BoundingBox } | null = null;
showDidDetails = false; showDidDetails = false;
showLargeIdenticonId?: string; showLargeIdenticonId?: string;
@ -290,38 +310,28 @@ export default class DIDView extends Vue {
displayAmount = displayAmount; displayAmount = displayAmount;
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid || "";
this.activeDid = (settings?.activeDid as string) || ""; this.apiServer = settings.apiServer || "";
this.apiServer = (settings?.apiServer as string) || "";
const pathParam = window.location.pathname.substring("/did/".length); const pathParam = window.location.pathname.substring("/did/".length);
let theContact: Contact | undefined;
if (pathParam) { if (pathParam) {
this.viewingDid = decodeURIComponent(pathParam); this.viewingDid = decodeURIComponent(pathParam);
theContact = await db.contacts.get(this.viewingDid); this.contactFromDid = await db.contacts.get(this.viewingDid);
} if (this.contactFromDid) {
if (theContact) { this.contactYaml = yaml.dump(this.contactFromDid);
this.contact = theContact;
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "No valid claim ID was provided.",
},
-1,
);
return;
} }
this.contactYaml = yaml.dump(this.contact);
await this.loadClaimsAbout(); await this.loadClaimsAbout();
await accountsDB.open(); await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray(); const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did); for (const account of allAccounts) {
if (account.did === this.viewingDid) {
this.isMyDid = true;
break;
}
}
}
} }
/** /**
@ -377,7 +387,7 @@ export default class DIDView extends Vue {
title: "Register", title: "Register",
text: text:
"Are you sure you want to register " + "Are you sure you want to register " +
libsUtil.nameForContact(this.contact, false) + libsUtil.nameForContact(this.contactFromDid, false) +
(contact.registered (contact.registered
? " -- especially since they are already marked as registered" ? " -- especially since they are already marked as registered"
: "") + : "") +
@ -558,9 +568,21 @@ export default class DIDView extends Vue {
} }
private async onClickSaveName(newName: string) { private async onClickSaveName(newName: string) {
this.contact.name = newName; if (!this.contactFromDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not A Contact",
text: "First add this on the contact page, then you can edit here.",
},
5000,
);
return;
}
this.contactFromDid.name = newName;
return db.contacts return db.contacts
.update(this.contact.did, { name: newName }) .update(this.contactFromDid.did, { name: newName })
.then(() => (this.contactEdit = false)); .then(() => (this.contactEdit = false));
} }

13
src/views/DiscoverView.vue

@ -146,9 +146,9 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { BoundingBox } from "@/db/tables/settings";
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer"; import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
@Component({ @Component({
@ -179,11 +179,10 @@ export default class DiscoverView extends Vue {
didInfo = didInfo; didInfo = didInfo;
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = (settings.activeDid as string) || "";
this.activeDid = (settings?.activeDid as string) || ""; this.apiServer = (settings.apiServer as string) || "";
this.apiServer = (settings?.apiServer as string) || ""; this.searchBox = settings.searchBoxes?.[0] || null;
this.searchBox = settings?.searchBoxes?.[0] || null;
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();

10
src/views/GiftedDetailsView.vue

@ -181,8 +181,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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { import {
createAndSubmitGive, createAndSubmitGive,
didInfo, didInfo,
@ -319,10 +318,9 @@ export default class GiftedDetails extends Vue {
} }
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
let allContacts: Contact[] = []; let allContacts: Contact[] = [];
let allMyDids: string[] = []; let allMyDids: string[] = [];

50
src/views/HomeView.vue

@ -84,11 +84,11 @@
id="noticeSomeoneMustRegisterYou" id="noticeSomeoneMustRegisterYou"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4" class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
> >
<!-- activeDid && !isRegistered --> <!-- !isCreatingIdentifier && !isRegistered -->
To share, someone must register you. To share, someone must register you.
<div class="block text-center"> <div class="block text-center">
<button <button
@click="showNameDialog()" @click="showNameThenIdDialog()"
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md" class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
> >
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
@ -107,7 +107,7 @@
</div> </div>
<div v-else id="sectionRecordSomethingGiven"> <div v-else id="sectionRecordSomethingGiven">
<!-- activeDid && isRegistered --> <!-- !isCreatingIdentifier && isRegistered -->
<!-- show the actions for recognizing a give --> <!-- show the actions for recognizing a give -->
<div class="flex justify-between"> <div class="flex justify-between">
@ -325,13 +325,17 @@ import {
NotificationIface, NotificationIface,
PASSKEYS_ENABLED, PASSKEYS_ENABLED,
} from "@/constants/app"; } from "@/constants/app";
import { db, accountsDB } from "@/db/index"; import {
db,
accountsDB,
updateAccountSettings,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
BoundingBox, BoundingBox,
isAnyFeedFilterOn, isAnyFeedFilterOn,
MASTER_SETTINGS_KEY, MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings"; } from "@/db/tables/settings";
import { import {
contactForDid, contactForDid,
@ -420,18 +424,17 @@ export default class HomeView extends Vue {
this.allMyDids = [newDid]; this.allMyDids = [newDid];
} }
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.feedLastViewedClaimId = settings?.lastViewedClaimId; this.feedLastViewedClaimId = settings.lastViewedClaimId;
this.givenName = settings?.firstName || ""; this.givenName = settings.firstName || "";
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible; this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings.isRegistered;
this.searchBoxes = settings?.searchBoxes || []; this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings?.showShortcutBvc; this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
@ -444,9 +447,7 @@ export default class HomeView extends Vue {
this.activeDid, this.activeDid,
); );
if (resp.status === 200) { if (resp.status === 200) {
// we just needed to know that they're registered await updateAccountSettings(this.activeDid, {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: true, isRegistered: true,
}); });
this.isRegistered = true; this.isRegistered = true;
@ -495,10 +496,9 @@ export default class HomeView extends Vue {
// only called when a setting was changed // only called when a setting was changed
async reloadFeedOnChange() { async reloadFeedOnChange() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible; this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
this.feedData = []; this.feedData = [];
@ -555,7 +555,7 @@ export default class HomeView extends Vue {
// This has indeed proven problematic. See loadMoreGives // This has indeed proven problematic. See loadMoreGives
// We should display it immediately and then get the plan later. // We should display it immediately and then get the plan later.
const plan = await getPlanFromCache( const plan = await getPlanFromCache(
record.fulfillsPlanHandleId, record.fulfillsPlanHandleId || "",
this.axios, this.axios,
this.apiServer, this.apiServer,
this.activeDid, this.activeDid,
@ -782,7 +782,7 @@ export default class HomeView extends Vue {
return known ? "text-slate-500" : "text-slate-100"; return known ? "text-slate-500" : "text-slate-100";
} }
showNameDialog() { showNameThenIdDialog() {
if (!this.givenName) { if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(() => { (this.$refs.userNameDialog as UserNameDialog).open(() => {
this.promptForShareMethod(); this.promptForShareMethod();

13
src/views/IdentitySwitcherView.vue

@ -102,8 +102,8 @@ 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 { db, accountsDB } from "@/db/index"; import { db, accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
@ -118,11 +118,10 @@ export default class IdentitySwitcherView extends Vue {
async created() { async created() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || ""; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.apiServerInput = settings.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();

11
src/views/NewEditAccountView.vue

@ -47,8 +47,8 @@
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 { db } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({ @Component({
components: {}, components: {},
@ -58,11 +58,10 @@ export default class NewEditAccountView extends Vue {
// 'created' hook runs when the Vue instance is first created // 'created' hook runs when the Vue instance is first created
async created() { async created() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.givenName = this.givenName =
(settings?.firstName || "") + (settings.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3 (settings.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
} }
async onClickSaveChanges() { async onClickSaveChanges() {

10
src/views/NewEditProjectView.vue

@ -185,8 +185,7 @@ import { Router } from "vue-router";
import ImageMethodDialog from "@/components/ImageMethodDialog.vue"; import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index"; import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { import {
createEndorserJwtVcFromClaim, createEndorserJwtVcFromClaim,
getHeaders, getHeaders,
@ -234,10 +233,9 @@ export default class NewEditProjectView extends Vue {
await accountsDB.open(); await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid || "";
this.activeDid = (settings?.activeDid as string) || ""; this.apiServer = settings.apiServer || "";
this.apiServer = (settings?.apiServer as string) || "";
this.projectId = (this.$route as Router).query["projectId"] || ""; this.projectId = (this.$route as Router).query["projectId"] || "";

10
src/views/OfferDetailsView.vue

@ -181,8 +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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { import {
createAndSubmitOffer, createAndSubmitOffer,
didInfo, didInfo,
@ -296,10 +295,9 @@ export default class OfferDetailsView extends Vue {
this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput; this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput;
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
let allContacts: Contact[] = []; let allContacts: Contact[] = [];
let allMyDids: string[] = []; let allMyDids: string[] = [];

12
src/views/ProjectViewView.vue

@ -431,10 +431,9 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { import {
BLANK_GENERIC_SERVER_RECORD, BLANK_GENERIC_SERVER_RECORD,
@ -494,12 +493,11 @@ export default class ProjectViewView extends Vue {
serverUtil = serverUtil; serverUtil = serverUtil;
async created() { async created() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || ""; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings.isRegistered;
await accountsDB.open(); await accountsDB.open();
const accounts = accountsDB.accounts; const accounts = accountsDB.accounts;

66
src/views/ProjectsView.vue

@ -208,6 +208,15 @@
/> />
button. You'll never know until you try. button. You'll never know until you try.
</div> </div>
<div v-else>
<button
@click="showNameThenIdDialog()"
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Get someone to onboard you.
</button>
<UserNameDialog ref="userNameDialog" />
</div>
</div> </div>
<ul id="listProjects" class="border-t border-slate-300"> <ul id="listProjects" class="border-t border-slate-300">
<li <li
@ -247,8 +256,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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@ -263,9 +271,17 @@ import {
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import UserNameDialog from "@/components/UserNameDialog.vue";
@Component({ @Component({
components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage }, components: {
EntityIcon,
InfiniteScroll,
QuickNav,
ProjectIcon,
TopMessage,
UserNameDialog,
},
}) })
export default class ProjectsView extends Vue { export default class ProjectsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
@ -280,11 +296,12 @@ export default class ProjectsView extends Vue {
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
allMyDids: Array<string> = []; allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
projects: PlanData[] = []; givenName = "";
isLoading = false; isLoading = false;
isRegistered = false; isRegistered = false;
offers: OfferSummaryRecord[] = []; offers: OfferSummaryRecord[] = [];
projectNameFromHandleId: Record<string, string> = {}; // mapping from handleId to description projectNameFromHandleId: Record<string, string> = {}; // mapping from handleId to description
projects: PlanData[] = [];
showOffers = true; showOffers = true;
showProjects = false; showProjects = false;
@ -293,11 +310,11 @@ export default class ProjectsView extends Vue {
async mounted() { async mounted() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid || "";
this.activeDid = (settings?.activeDid as string) || ""; this.apiServer = settings.apiServer || "";
this.apiServer = (settings?.apiServer as string) || ""; this.isRegistered = !!settings.isRegistered;
this.isRegistered = !!settings?.isRegistered; this.givenName = settings.firstName || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
@ -497,6 +514,37 @@ export default class ProjectsView extends Vue {
await this.offerDataLoader(url); await this.offerDataLoader(url);
} }
showNameThenIdDialog() {
if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(() => {
this.promptForShareMethod();
});
} else {
this.promptForShareMethod();
}
}
promptForShareMethod() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Are you nearby with cameras?",
text: "If so, we'll use those with QR codes to share.",
onCancel: async () => {},
onNo: async () => {
(this.$router as Router).push({ name: "share-my-contact-info" });
},
onYes: async () => {
(this.$router as Router).push({ name: "contact-qr" });
},
noText: "we will share another way",
yesText: "we are nearby with cameras",
},
-1,
);
}
public computedOfferTabClassNames() { public computedOfferTabClassNames() {
return { return {
"inline-block": true, "inline-block": true,

10
src/views/QuickActionBvcBeginView.vue

@ -72,7 +72,7 @@ import { Component, Vue } from "vue-facing-decorator";
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 { db } from "@/db/index"; import { retrieveSettingsForActiveAccount } from "@/db/index";
import { import {
BVC_MEETUPS_PROJECT_CLAIM_ID, BVC_MEETUPS_PROJECT_CLAIM_ID,
bvcMeetingJoinClaim, bvcMeetingJoinClaim,
@ -80,7 +80,6 @@ import {
createAndSubmitGive, createAndSubmitGive,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@Component({ @Component({
components: { components: {
@ -117,10 +116,9 @@ export default class QuickActionBvcBeginView extends Vue {
} }
async record() { async record() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; const activeDid = settings.activeDid || "";
const activeDid = settings?.activeDid || ""; const apiServer = settings.apiServer || "";
const apiServer = settings?.apiServer || "";
try { try {
const hoursNum = libsUtil.numberOrZero(this.hoursStr); const hoursNum = libsUtil.numberOrZero(this.hoursStr);

10
src/views/QuickActionBvcEndView.vue

@ -144,9 +144,8 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { import {
BVC_MEETUPS_PROJECT_CLAIM_ID, BVC_MEETUPS_PROJECT_CLAIM_ID,
claimSpecialDescription, claimSpecialDescription,
@ -182,10 +181,9 @@ export default class QuickActionBvcBeginView extends Vue {
someoneGave = false; someoneGave = false;
async created() { async created() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.apiServer = settings.apiServer || "";
this.apiServer = settings?.apiServer || ""; this.activeDid = settings.activeDid || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
} }

7
src/views/SearchAreaView.vue

@ -109,7 +109,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 { db } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
const DEFAULT_LAT_LONG_DIFF = 0.01; const DEFAULT_LAT_LONG_DIFF = 0.01;
@ -142,9 +142,8 @@ export default class DiscoverView extends Vue {
searchBox: { name: string; bbox: BoundingBox } | null = null; searchBox: { name: string; bbox: BoundingBox } | null = null;
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.searchBox = settings.searchBoxes?.[0] || null;
this.searchBox = settings?.searchBoxes?.[0] || null;
this.resetLatLong(); this.resetLatLong();
} }

8
src/views/SeedBackupView.vue

@ -105,9 +105,8 @@ 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, db } from "@/db/index"; import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class SeedBackupView extends Vue { export default class SeedBackupView extends Vue {
@ -122,9 +121,8 @@ export default class SeedBackupView extends Vue {
// 'created' hook runs when the Vue instance is first created // 'created' hook runs when the Vue instance is first created
async created() { async created() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const activeDid = settings.activeDid || "";
const activeDid = settings?.activeDid || "";
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();

14
src/views/ShareMyContactInfoView.vue

@ -49,8 +49,7 @@ 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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { generateEndorserJwtForAccount } from "@/libs/endorserServer"; import { generateEndorserJwtForAccount } from "@/libs/endorserServer";
@Component({ @Component({
@ -60,12 +59,11 @@ export default class ShareMyContactInfoView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
async onClickShare() { async onClickShare() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; const activeDid = settings.activeDid || "";
const activeDid = settings?.activeDid || ""; const givenName = settings.firstName || "";
const givenName = settings?.firstName || ""; const isRegistered = !!settings.isRegistered;
const isRegistered = !!settings?.isRegistered; const profileImageUrl = settings.profileImageUrl || "";
const profileImageUrl = settings?.profileImageUrl || "";
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();

7
src/views/SharedPhotoView.vue

@ -75,7 +75,7 @@ import {
IMAGE_TYPE_PROFILE, IMAGE_TYPE_PROFILE,
NotificationIface, NotificationIface,
} from "@/constants/app"; } from "@/constants/app";
import { db } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { base64ToBlob, SHARED_PHOTO_BASE64_KEY } from "@/libs/util"; import { base64ToBlob, SHARED_PHOTO_BASE64_KEY } from "@/libs/util";
@ -94,9 +94,8 @@ export default class SharedPhotoView extends Vue {
// 'created' hook runs when the Vue instance is first created // 'created' hook runs when the Vue instance is first created
async mounted() { async mounted() {
try { try {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid;
this.activeDid = settings?.activeDid as string;
const temp = await db.temp.get(SHARED_PHOTO_BASE64_KEY); const temp = await db.temp.get(SHARED_PHOTO_BASE64_KEY);
const imageB64 = temp?.blobB64 as string; const imageB64 = temp?.blobB64 as string;

8
src/views/StartView.vue

@ -92,8 +92,7 @@ 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, db } from "@/db/index"; import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { registerSaveAndActivatePasskey } from "@/libs/util"; import { registerSaveAndActivatePasskey } from "@/libs/util";
@Component({ @Component({
@ -106,9 +105,8 @@ export default class StartView extends Vue {
numAccounts = 0; numAccounts = 0;
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; this.givenName = settings.firstName || "";
this.givenName = settings?.firstName || "";
await accountsDB.open(); await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();

10
src/views/TestView.vue

@ -247,8 +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 } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import * as vcLib from "@/libs/crypto/vc"; import * as vcLib from "@/libs/crypto/vc";
import { import {
PeerSetup, PeerSetup,
@ -291,10 +290,9 @@ export default class Help extends Vue {
userName?: string; userName?: string;
async mounted() { async mounted() {
await db.open(); const settings = await retrieveSettingsForActiveAccount();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); this.activeDid = settings.activeDid || "";
this.activeDid = (settings?.activeDid as string) || ""; this.userName = settings.firstName;
this.userName = settings?.firstName as string;
await accountsDB.open(); await accountsDB.open();
const account: { identity?: string } | undefined = await accountsDB.accounts const account: { identity?: string } | undefined = await accountsDB.accounts

31
test-playwright/00-noid-tests.spec.ts

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
import { generateEthrUser, importUser } from './testUtils'; import { deleteContact, generateEthrUser, importUser } from './testUtils';
test('Check activity feed', async ({ page }) => { test('Check activity feed', async ({ page }) => {
// Load app homepage // Load app homepage
@ -37,6 +37,17 @@ test('Check no-ID messaging in account', async ({ page }) => {
await expect(page.locator('#sectionIdentityDetails code.truncate')).toBeEmpty(); await expect(page.locator('#sectionIdentityDetails code.truncate')).toBeEmpty();
}); });
test('Check ability to share contact', async ({ page }) => {
// Load Discover view
await page.goto('./discover');
// Check that initial 10 projects have been loaded
await page.locator('ul#listDiscoverResults li.border-b:nth-child(10)');
// Scroll down a bit to trigger loading additional projects
await page.locator('ul#listDiscoverResults li.border-b:nth-child(20)').scrollIntoViewIfNeeded();
});
test('Check ID generation', async ({ page }) => { test('Check ID generation', async ({ page }) => {
// Load Account view // Load Account view
await page.goto('./account'); await page.goto('./account');
@ -83,7 +94,7 @@ test('Check setting name & sharing info', async ({ page }) => {
await expect(page.getByText('your contacts')).toBeVisible(); await expect(page.getByText('your contacts')).toBeVisible();
}); });
test('Confirm usage of test API (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => { test('Confirm test API setting (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => {
// Load account view // Load account view
await page.goto('./account'); await page.goto('./account');
await page.getByRole('heading', { name: 'Advanced' }).click(); await page.getByRole('heading', { name: 'Advanced' }).click();
@ -112,4 +123,20 @@ test('Check User 0 can register a random person', async ({ page }) => {
// now ensure that alert goes away // now ensure that alert goes away
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.getByText('That gift was recorded.')).toBeHidden(); await expect(page.getByText('That gift was recorded.')).toBeHidden();
// now delete the contact to test that pages still do reasonable things
await deleteContact(page, newDid);
// go the activity page for this new person
await page.goto('./did/' + encodeURIComponent(newDid));
let error;
try {
await page.waitForSelector('div[role="alert"]', { timeout: 2000 });
error = new Error('Error alert should not show.');
} catch (error) {
// success
} finally {
if (error) {
throw error;
}
}
}); });

32
test-playwright/40-add-contact.spec.ts

@ -22,7 +22,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
const finalTitle = standardTitle + finalRandomString; const finalTitle = standardTitle + finalRandomString;
// Contact name // Contact name
const contactName = 'Contact #111'; const contactName = 'Contact #000 renamed';
// Import user 01 // Import user 01
await importUser(page, '01'); await importUser(page, '01');
@ -45,6 +45,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible(); await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible();
await page.getByPlaceholder('Name', { exact: true }).fill(contactName); await page.getByPlaceholder('Name', { exact: true }).fill(contactName);
await page.locator('.dialog > .flex > button').first().click(); await page.locator('.dialog > .flex > button').first().click();
// await page.locator('.dialog > .flex > button').first().click(); // close alert
// Confirm that home shows contact in "Record Something…" // Confirm that home shows contact in "Record Something…"
await page.goto('./'); await page.goto('./');
@ -59,6 +60,9 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
// Refresh home view and check gift // Refresh home view and check gift
await page.goto('./'); await page.goto('./');
// Firefox complains on load the initial feed here when we use the test server.
// It may be similar to the CORS problem below.
await page.locator('li').filter({ hasText: finalTitle }).locator('a').click(); await page.locator('li').filter({ hasText: finalTitle }).locator('a').click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible(); await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
@ -88,6 +92,24 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await expect(page.locator('div[role="alert"]')).toBeVisible(); await expect(page.locator('div[role="alert"]')).toBeVisible();
}); });
test('Without being registered, add contacts without registration', async ({ page, context }) => {
await page.goto('./account');
// wait until the DID shows on the page in the 'did' element
const didElem = await page.getByTestId('didWrapper').locator('code');
const newDid = await didElem.innerText();
expect(newDid.trim()).toEqual('');
// Add new contact without registering
await page.goto('./contacts');
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111');
await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// wait for the alert to disappear, which also ensures that there is no "Register" button waiting
await expect(page.locator('div[role="alert"]')).toBeHidden();
});
test('Add contact, copy details, delete, and import various ways', async ({ page, context }) => { test('Add contact, copy details, delete, and import various ways', async ({ page, context }) => {
await importUser(page, '00'); await importUser(page, '00');
@ -117,6 +139,7 @@ test('Add contact, copy details, delete, and import various ways', async ({ page
await page.getByTestId('contactCheckAllTop').click(); await page.getByTestId('contactCheckAllTop').click();
await page.getByTestId('copySelectedContactsButtonTop').click(); await page.getByTestId('copySelectedContactsButtonTop').click();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.locator('div[role="alert"]')).toBeHidden();
// I would prefer to copy from the clipboard, but the recommended approaches don't work. // I would prefer to copy from the clipboard, but the recommended approaches don't work.
// this seems to fail in non-chromium browsers // this seems to fail in non-chromium browsers
//await context.grantPermissions(['clipboard-read', 'clipboard-write']); //await context.grantPermissions(['clipboard-read', 'clipboard-write']);
@ -125,12 +148,19 @@ test('Add contact, copy details, delete, and import various ways', async ({ page
// see contact details on the second contact // see contact details on the second contact
await page.getByTestId('contactListItem').nth(1).locator('a').click(); await page.getByTestId('contactListItem').nth(1).locator('a').click();
await page.getByRole('heading', { name: 'Identifier Details' }).isVisible();
// remove contact // remove contact
await page.locator('button > svg.fa-trash-can').click(); await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click(); await page.locator('div[role="alert"] button:has-text("Yes")').click();
// for some reason, .isHidden() (without expect) doesn't work // for some reason, .isHidden() (without expect) doesn't work
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden(); await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
// Firefox has a problem when we run this against the test server. It doesn't load the feed.
// It says there's a CORS problem; maybe it's more strict than the other browsers.
// It works when we set the config to use a local server.
// Seems like we hit a similar problem above.
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.locator('div[role="alert"]')).toBeHidden();
// go to the contacts page and paste the copied contact details // go to the contacts page and paste the copied contact details
await page.goto('./contacts'); await page.goto('./contacts');

22
test-playwright/testUtils.ts

@ -40,6 +40,22 @@ export async function switchToUser(page: Page, did: string): Promise<void> {
await page.getByRole('code', { name: did }).click(); await page.getByRole('code', { name: did }).click();
} }
function createContactName(did: string): string {
return "User " + did.slice(11, 14);
}
export async function deleteContact(page: Page, did: string): Promise<void> {
await page.goto('./contacts');
const contactName = createContactName(did);
// go to the detail page for this contact
await page.locator(`li[data-testid="contactListItem"] h2:has-text("${contactName}") + a`).click();
// delete the contact
await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click();
// for some reason, .isHidden() (without expect) doesn't work
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
}
// Generate a new random user and register them. // Generate a new random user and register them.
// Note that this makes 000 the active user. Use switchToUser to switch to this DID. // Note that this makes 000 the active user. Use switchToUser to switch to this DID.
export async function generateEthrUser(page: Page): Promise<string> { export async function generateEthrUser(page: Page): Promise<string> {
@ -55,10 +71,10 @@ export async function generateEthrUser(page: Page): Promise<string> {
await importUser(page, '000'); // switch to user 000 await importUser(page, '000'); // switch to user 000
await page.goto('./contacts'); await page.goto('./contacts');
const threeChars = newDid.slice(11, 14); const contactName = createContactName(newDid);
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${newDid}, User ${threeChars}`); await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${newDid}, ${contactName}`);
await page.locator('button > svg.fa-plus').click(); await page.locator('button > svg.fa-plus').click();
await page.locator('li', { hasText: threeChars }).click(); await page.locator('li', { hasText: contactName }).click();
// register them // register them
await page.locator('div[role="alert"] button:has-text("Yes")').click(); await page.locator('div[role="alert"] button:has-text("Yes")').click();
// wait for it to disappear because the next steps may depend on alerts being gone // wait for it to disappear because the next steps may depend on alerts being gone

Loading…
Cancel
Save