Browse Source

catch more errors if something catastrophic happens to encrypted data

split_build_process
Trent Larson 1 month ago
parent
commit
6ffbcfa9a1
  1. 22
      src/App.vue
  2. 10
      src/components/PushNotificationPermission.vue
  3. 6
      src/components/QuickNav.vue
  4. 46
      src/libs/endorserServer.ts
  5. 24
      src/libs/util.ts
  6. 4
      src/router/index.ts
  7. 4
      src/views/AccountViewView.vue
  8. 23
      src/views/ClaimView.vue
  9. 9
      src/views/ContactsView.vue
  10. 37
      src/views/HelpNotificationsView.vue
  11. 18
      src/views/HelpView.vue
  12. 21
      src/views/HomeView.vue
  13. 1
      src/views/IdentitySwitcherView.vue
  14. 23
      src/views/ProjectViewView.vue
  15. 2
      src/views/ProjectsView.vue
  16. 4
      src/views/SeedBackupView.vue

22
src/App.vue

@ -309,6 +309,28 @@
</div> </div>
</div> </div>
</div> </div>
<div
v-else
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
>
<div
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
>
<div class="w-full px-6 py-6 text-slate-900 text-center">
<p class="text-lg mb-4">
Something has gone very wrong. We'd appreciate if you'd
contact us and let us know how you got here. Thank you!
</p>
<button
@click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
>
Close
</button>
</div>
</div>
</div>
</div> </div>
</Notification> </Notification>
</div> </div>

10
src/components/PushNotificationPermission.vue

@ -9,7 +9,7 @@
> >
<div <div
v-if="isVisible" v-if="isVisible"
class="fixed z-[100] top-0 inset-x-0 w-full absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50" class="fixed z-[100] top-0 inset-x-0 w-full inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
> >
<div <div
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
@ -276,9 +276,9 @@ export default class PushNotificationPermission extends Vue {
} }
private async askPermission(): Promise<NotificationPermission> { private async askPermission(): Promise<NotificationPermission> {
logConsoleAndDb( // console.log(
"Requesting permission for notifications: " + JSON.stringify(navigator), // "Requesting permission for notifications: " + JSON.stringify(navigator),
); // );
if ( if (
!("serviceWorker" in navigator && navigator.serviceWorker?.controller) !("serviceWorker" in navigator && navigator.serviceWorker?.controller)
) { ) {
@ -344,7 +344,7 @@ export default class PushNotificationPermission extends Vue {
}, },
-1, -1,
); );
throw new Error("We weren't granted permission."); throw new Error("Permission was not granted to this app.");
} }
return permission; return permission;
}, },

6
src/components/QuickNav.vue

@ -90,6 +90,12 @@
> >
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<fa icon="circle-user" class="fa-fw" /> <fa icon="circle-user" class="fa-fw" />
<!--
We used to say "account", so we'll keep that in the code,
but it isn't accurate because we don't hold anything for them.
We'll say "profile" to the users.
(Or: settings, face, registry, cache, repo, vault... or separate preferences from identity.)
-->
<span class="text-xs mt-1">profile</span> <span class="text-xs mt-1">profile</span>
</div> </div>
</router-link> </router-link>

46
src/libs/endorserServer.ts

@ -4,10 +4,10 @@ import { sha256 } from "ethereum-cryptography/sha256";
import { LRUCache } from "lru-cache"; import { LRUCache } from "lru-cache";
import * as R from "ramda"; import * as R from "ramda";
import { DEFAULT_IMAGE_API_SERVER } from "@/constants/app"; import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto"; import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto";
import { NonsensitiveDexie } from "@/db/index"; import { logConsoleAndDb, NonsensitiveDexie } from "@/db/index";
import { import {
retrieveAccountMetadata, retrieveAccountMetadata,
retrieveFullyDecryptedAccount, retrieveFullyDecryptedAccount,
@ -501,11 +501,16 @@ export function tokenExpiryTimeDescription() {
/** /**
* Get the headers for a request, potentially including Authorization * Get the headers for a request, potentially including Authorization
*/ */
export async function getHeaders(did?: string) { export async function getHeaders(
did?: string,
$notify?: (notification: NotificationIface, timeout?: number) => void,
failureMessage?: string,
) {
const headers: { "Content-Type": string; Authorization?: string } = { const headers: { "Content-Type": string; Authorization?: string } = {
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
if (did) { if (did) {
try {
let token; let token;
const account = await retrieveAccountMetadata(did); const account = await retrieveAccountMetadata(did);
if (account?.passkeyCredIdHex) { if (account?.passkeyCredIdHex) {
@ -528,8 +533,40 @@ export async function getHeaders(did?: string) {
token = await accessToken(did); token = await accessToken(did);
} }
headers["Authorization"] = "Bearer " + token; headers["Authorization"] = "Bearer " + token;
} catch (error) {
// This rarely happens: we've seen it when they have account info but the
// encryption secret got lost. But in most cases we want users to at
// least see their feed -- and anything else that returns results for
// anonymous users.
// We'll continue with an anonymous request... still want to show feed and other things, but ideally let them know.
logConsoleAndDb(
"Something failed in getHeaders call (will proceed anonymously" +
($notify ? " and notify user" : "") +
"): " +
// IntelliJ type system complains about getCircularReplacer() with: Argument of type '(obj: any, key: string, value: any) => any' is not assignable to parameter of type '(this: any, key: string, value: any) => any'.
//JSON.stringify(error, getCircularReplacer()), // JSON.stringify(error) on a Dexie error throws another error about: Converting circular structure to JSON
error,
true,
);
if ($notify) {
// remember: only want to do this if they supplied a DID, expecting personal results
const notifyMessage =
failureMessage ||
"Showing anonymous data. See the Help page for help with personal data.";
$notify(
{
group: "alert",
type: "danger",
title: "Personal Data Error",
text: notifyMessage,
},
3000,
);
}
}
} else { } else {
// it's often OK to request without auth; we assume necessary checks are done earlier // it's usually OK to request without auth; we assume we're only here when allowed
} }
return headers; return headers;
} }
@ -611,6 +648,7 @@ export async function getNewOffersToUser(
url += "&beforeId=" + beforeOfferJwtId; url += "&beforeId=" + beforeOfferJwtId;
} }
const headers = await getHeaders(activeDid); const headers = await getHeaders(activeDid);
console.log("Using headers: ", headers);
const response = await axios.get(url, { headers }); const response = await axios.get(url, { headers });
return response.data; return response.data;
} }

24
src/libs/util.ts

@ -147,6 +147,23 @@ export interface ConfirmerData {
numConfsNotVisible: number; numConfsNotVisible: number;
} }
// // This is meant to be a second argument to JSON.stringify to avoid circular references.
// // Usage: JSON.stringify(error, getCircularReplacer())
// // Beware: we've seen this return "undefined" when there is actually a message, eg: DatabaseClosedError: Error DEXIE ENCRYPT ADDON: Encryption key has changed
// function getCircularReplacer() {
// const seen = new WeakSet();
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// return (obj: any, key: string, value: any): any => {
// if (typeof value === "object" && value !== null) {
// if (seen.has(value)) {
// return "[circular ref]";
// }
// seen.add(value);
// }
// return value;
// };
// }
/** /**
* @return only confirmers, excluding the issuer and hidden DIDs * @return only confirmers, excluding the issuer and hidden DIDs
*/ */
@ -423,11 +440,13 @@ export function findAllVisibleToDids(
export interface AccountKeyInfo extends Account, KeyMeta {} export interface AccountKeyInfo extends Account, KeyMeta {}
export const retrieveAccountCount = async (): Promise<number> => { export const retrieveAccountCount = async (): Promise<number> => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
return await accountsDB.accounts.count(); return await accountsDB.accounts.count();
}; };
export const retrieveAccountDids = async (): Promise<string[]> => { export const retrieveAccountDids = async (): Promise<string[]> => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
const allAccounts = await accountsDB.accounts.toArray(); const allAccounts = await accountsDB.accounts.toArray();
const allDids = allAccounts.map((acc) => acc.did); const allDids = allAccounts.map((acc) => acc.did);
@ -439,6 +458,7 @@ export const retrieveAccountDids = async (): Promise<string[]> => {
export const retrieveAccountMetadata = async ( export const retrieveAccountMetadata = async (
activeDid: string, activeDid: string,
): Promise<AccountKeyInfo | undefined> => { ): Promise<AccountKeyInfo | undefined> => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
const account = (await accountsDB.accounts const account = (await accountsDB.accounts
.where("did") .where("did")
@ -454,6 +474,7 @@ export const retrieveAccountMetadata = async (
}; };
export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => { export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
const array = await accountsDB.accounts.toArray(); const array = await accountsDB.accounts.toArray();
return array.map((account) => { return array.map((account) => {
@ -466,6 +487,7 @@ export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => {
export const retrieveFullyDecryptedAccount = async ( export const retrieveFullyDecryptedAccount = async (
activeDid: string, activeDid: string,
): Promise<AccountKeyInfo | undefined> => { ): Promise<AccountKeyInfo | undefined> => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
const account = (await accountsDB.accounts const account = (await accountsDB.accounts
.where("did") .where("did")
@ -496,6 +518,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
const newId = newIdentifier(address, publicHex, privateHex, derivationPath); const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
const identity = JSON.stringify(newId); const identity = JSON.stringify(newId);
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
await accountsDB.accounts.add({ await accountsDB.accounts.add({
dateCreated: new Date().toISOString(), dateCreated: new Date().toISOString(),
@ -527,6 +550,7 @@ export const registerAndSavePasskey = async (
passkeyCredIdHex, passkeyCredIdHex,
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"), publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
}; };
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
await accountsDB.accounts.add(account); await accountsDB.accounts.add(account);
return account; return account;

4
src/router/index.ts

@ -18,6 +18,7 @@ const enterOrStart = async (
from: RouteLocationNormalized, from: RouteLocationNormalized,
next: NavigationGuardNext, next: NavigationGuardNext,
) => { ) => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
const num_accounts = await accountsDB.accounts.count(); const num_accounts = await accountsDB.accounts.count();
if (num_accounts > 0) { if (num_accounts > 0) {
@ -263,6 +264,9 @@ const errorHandler = (
) => { ) => {
// Handle the error here // Handle the error here
console.error("Caught in top level error handler:", error, to, from); console.error("Caught in top level error handler:", error, to, from);
alert(
"Something is very wrong. We'd love if you contacted us and let us know how you got here. Thank you!",
);
// You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page // You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page
}; };

4
src/views/AccountViewView.vue

@ -951,8 +951,8 @@ export default class AccountViewView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error Loading Account", title: "Error Loading Profile",
text: "Clear your cache and start over (after data backup).", text: "See the Help page about errors with your personal data.",
}, },
-1, -1,
); );

23
src/views/ClaimView.vue

@ -484,7 +484,11 @@ import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { db, retrieveSettingsForActiveAccount } from "@/db/index"; import {
db,
logConsoleAndDb,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { import {
@ -559,7 +563,24 @@ export default class ClaimView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = settings.isRegistered || false; this.isRegistered = settings.isRegistered || false;
try {
this.allMyDids = await libsUtil.retrieveAccountDids(); this.allMyDids = await libsUtil.retrieveAccountDids();
} catch (error) {
// continue because we want to see claims, even anonymously
logConsoleAndDb(
"Error retrieving all account DIDs on home page:" + error,
true,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Profile",
text: "See the Help page for problems with your personal data.",
},
-1,
);
}
const pathParam = window.location.pathname.substring("/claim/".length); const pathParam = window.location.pathname.substring("/claim/".length);
let claimId; let claimId;

9
src/views/ContactsView.vue

@ -171,8 +171,9 @@
<fa icon="circle-info" class="text-xl text-blue-500 ml-4" /> <fa icon="circle-info" class="text-xl text-blue-500 ml-4" />
</router-link> </router-link>
<span class="ml-4 text-sm overflow-hidden" <span class="ml-4 text-sm overflow-hidden">{{
>{{ shortDid(contact.did) }}...</span shortDid(contact.did)
}}</span
><!-- The first 18 characters of did:peer are the same. --> ><!-- The first 18 characters of did:peer are the same. -->
</div> </div>
<div id="ContactActions" class="flex gap-1.5 mt-2"> <div id="ContactActions" class="flex gap-1.5 mt-2">
@ -425,7 +426,7 @@ export default class ContactsView extends Vue {
group: "alert", group: "alert",
type: "warning", type: "warning",
title: "Blank Invite", title: "Blank Invite",
text: "The invite was not included. This can happen when your device cuts off the link, so you might try pasting the full link into a browser.", text: "The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.",
}, },
7000, 7000,
); );
@ -601,7 +602,7 @@ export default class ContactsView extends Vue {
}; };
try { try {
const headers = await getHeaders(this.activeDid); const headers = await getHeaders(this.activeDid, this.$notify);
const givenByUrl = const givenByUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +

37
src/views/HelpNotificationsView.vue

@ -75,6 +75,7 @@
<button class="text-blue-500" @click="showNotificationChoice()"> <button class="text-blue-500" @click="showNotificationChoice()">
Click here. Click here.
</button> </button>
<PushNotificationPermission ref="pushNotificationPermission" />
</p> </p>
</div> </div>
@ -193,14 +194,18 @@
<h2 class="text-xl font-semibold mt-4">Reinstall</h2> <h2 class="text-xl font-semibold mt-4">Reinstall</h2>
<div> <div>
<p> <p>
If all else fails, uninstall the app, ensure all the browser tabs with If all else fails, it's best to start over.
it are closed, and clear out caches and storage.
</p> </p>
<p> <p>
Of course, you'll want to back up all your data first -- all seeds as Of course, you'll want to back up all your data first -- all seeds as
well as the contacts & settings -- on the Account well as the contacts & settings -- on the Profile
<fa icon="circle-user" /> page. <fa icon="circle-user" /> page.
</p> </p>
<p>
Here are instructions to uninstall the app and clear out caches and storage.
Note that you should first ensure check that the browser tabs with Time Safari are closed.
(If any are open then that will interfere with your refresh.)
</p>
<ul class="ml-4 list-disc"> <ul class="ml-4 list-disc">
<li> <li>
Clear cache. Clear cache.
@ -304,9 +309,12 @@ import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { sendTestThroughPushServer } from "@/libs/util"; import { DIRECT_PUSH_TITLE, sendTestThroughPushServer } from "@/libs/util";
import PushNotificationPermission from "@/components/PushNotificationPermission.vue";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({ components: { QuickNav } }) @Component({ components: { PushNotificationPermission, QuickNav } })
export default class HelpNotificationsView extends Vue { export default class HelpNotificationsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
@ -407,14 +415,19 @@ export default class HelpNotificationsView extends Vue {
} }
showNotificationChoice() { showNotificationChoice() {
this.$notify( (this.$refs.pushNotificationPermission as PushNotificationPermission).open(
{ DIRECT_PUSH_TITLE,
group: "modal", async (success: boolean, timeText: string, message?: string) => {
type: "notification-permission", if (success) {
title: "", // unused, only here to satisfy type check await db.settings.update(MASTER_SETTINGS_KEY, {
text: "", // unused, only here to satisfy type check notifyingReminderMessage: message,
notifyingReminderTime: timeText,
});
this.notifyingReminder = true;
this.notifyingReminderMessage = message || "";
this.notifyingReminderTime = timeText;
}
}, },
-1,
); );
} }
} }

18
src/views/HelpView.vue

@ -383,7 +383,7 @@
How do I access even more functionality? How do I access even more functionality?
</h2> </h2>
<p> <p>
There is an "Advanced" section at the bottom of the Account There is an "Advanced" section at the bottom of the Profile
<fa icon="circle-user" /> page. <fa icon="circle-user" /> page.
</p> </p>
<p> <p>
@ -422,19 +422,19 @@
</p> </p>
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
My app is misbehaving, like showing me a blank screen or failing to show a feed. This app is misbehaving, like showing me a blank screen or failing to show my personal data.
What can I do? What can I do?
</h2> </h2>
<p> <p>
First, note that clearing the cache will clear all your identity and contact info, First, note that clearing the cache will clear all your identity and contact info,
so we recommend doing other things first (unless you know you have your backups ready). so we recommend doing other things first -- and only clearing when have your backups ready.
</p> </p>
<ul class="list-disc list-outside ml-4"> <ul class="list-disc list-outside ml-4">
<li> <li>
Drag down on the screen to refresh it; do that multiple times, because Drag down on the screen to refresh it; do that multiple times, because
it sometimes takes multiple tries for the app to refresh to the current version. it sometimes takes multiple tries for the app to refresh to the latest version.
You can see the version information at the bottom of this page; the best You can see the version information at the bottom of this page; the best
way to determine the current version is to open this page in an incognito way to determine the latest version is to open this page in an incognito/private
browser window and look at the version there. browser window and look at the version there.
</li> </li>
<li> <li>
@ -498,7 +498,7 @@
</a> </a>
<br /> <br />
For notifications, this service stores push token data; that can be revoked at any time For notifications, this service stores push token data; that can be revoked at any time
by disabling notifications on the Account <fa icon="circle-user" class="fa-fw" /> page. by disabling notifications on the Profile <fa icon="circle-user" class="fa-fw" /> page.
<br /> <br />
For all other claim data, For all other claim data,
<a href="https://endorser.ch/privacy-policy" target="_blank" class="text-blue-500"> <a href="https://endorser.ch/privacy-policy" target="_blank" class="text-blue-500">
@ -520,9 +520,9 @@
class="text-blue-500 ml-2" class="text-blue-500 ml-2"
> >
bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma
<fa v-show="!showDidCopy" icon="copy" class="text-slate-400 fa-fw" /> <fa v-show="!showDidCopy" icon="copy" class="text-sm text-slate-400 fa-fw" />
<fa v-show="showDidCopy" icon="circle-check" class="text-sm text-green-500 fa-fw"/>
</button> </button>
<span v-show="showDidCopy" class="ml-2 text-sm text-green-500">Copied</span>
You can donate online via You can donate online via
<a href="https://www.patreon.com/TimeSafari" target="_blank" class="text-blue-500">Patreon here</a>. <a href="https://www.patreon.com/TimeSafari" target="_blank" class="text-blue-500">Patreon here</a>.
For other donations, contact us. For other donations, contact us.
@ -541,7 +541,7 @@
<p>{{ package.version }} ({{ commitHash }})</p> <p>{{ package.version }} ({{ commitHash }})</p>
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
I have other questions or feedback, like getting a new account or removing my data or requesting an improvement. I have other questions or feedback, like getting a new profile or removing my data or requesting an improvement.
</h2> </h2>
<p> <p>
Contact us at Contact us at

21
src/views/HomeView.vue

@ -389,6 +389,7 @@ import {
} from "@/constants/app"; } from "@/constants/app";
import { import {
db, db,
logConsoleAndDb,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings, updateAccountSettings,
} from "@/db/index"; } from "@/db/index";
@ -485,6 +486,7 @@ export default class HomeView extends Vue {
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
async mounted() { async mounted() {
try {
try { try {
this.allMyDids = await retrieveAccountDids(); this.allMyDids = await retrieveAccountDids();
if (this.allMyDids.length === 0) { if (this.allMyDids.length === 0) {
@ -493,6 +495,14 @@ export default class HomeView extends Vue {
this.isCreatingIdentifier = false; this.isCreatingIdentifier = false;
this.allMyDids = [newDid]; this.allMyDids = [newDid];
} }
} catch (error) {
// continue because we want the feed to work, even anonymously
logConsoleAndDb(
"Error retrieving all account DIDs on home page:" + error,
true,
);
// some other piece will display an error about personal info
}
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
@ -546,6 +556,7 @@ export default class HomeView extends Vue {
this.activeDid, this.activeDid,
this.lastAckedOfferToUserJwtId, this.lastAckedOfferToUserJwtId,
); );
console.log("offersToUserData", offersToUserData);
this.numNewOffersToUser = offersToUserData.data.length; this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit; this.newOffersToUserHitLimit = offersToUserData.hitLimit;
} }
@ -563,7 +574,7 @@ export default class HomeView extends Vue {
// 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 or feed.", err); logConsoleAndDb("Error retrieving settings or feed: " + err, true);
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
@ -760,13 +771,19 @@ export default class HomeView extends Vue {
*/ */
async retrieveGives(endorserApiServer: string, beforeId?: string) { async retrieveGives(endorserApiServer: string, beforeId?: string) {
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId; const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
const doNotShowErrorAgain = !!beforeId; // don't show error again if we're loading more
const headers = await getHeaders(
this.activeDid,
doNotShowErrorAgain ? undefined : this.$notify,
);
// retrieve headers for this user, but if an error happens then report it but proceed with the fetch with no header
const response = await fetch( const response = await fetch(
endorserApiServer + endorserApiServer +
"/api/v2/report/gives?giftNotTrade=true" + "/api/v2/report/gives?giftNotTrade=true" +
beforeQuery, beforeQuery,
{ {
method: "GET", method: "GET",
headers: await getHeaders(this.activeDid), headers: headers,
}, },
); );

1
src/views/IdentitySwitcherView.vue

@ -170,6 +170,7 @@ export default class IdentitySwitcherView extends Vue {
title: "Delete Identity?", title: "Delete Identity?",
text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)", text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)",
onYes: async () => { onYes: async () => {
// one of the few times we use accountsDBPromise directly; try to avoid more usage
const accountsDB = await accountsDBPromise; const accountsDB = await accountsDBPromise;
await accountsDB.accounts.delete(id); await accountsDB.accounts.delete(id);
this.otherIdentities = this.otherIdentities.filter( this.otherIdentities = this.otherIdentities.filter(

23
src/views/ProjectViewView.vue

@ -489,7 +489,11 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { db, retrieveSettingsForActiveAccount } from "@/db/index"; import {
db,
logConsoleAndDb,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { import {
@ -557,7 +561,24 @@ export default class ProjectViewView extends Vue {
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
this.isRegistered = !!settings.isRegistered; this.isRegistered = !!settings.isRegistered;
try {
this.allMyDids = await retrieveAccountDids(); this.allMyDids = await retrieveAccountDids();
} catch (error) {
// continue because we want to see claims, even anonymously
logConsoleAndDb(
"Error retrieving all account DIDs on home page:" + error,
true,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Profile",
text: "See the Help page to fix problems with your personal data.",
},
-1,
);
}
const pathParam = window.location.pathname.substring("/project/".length); const pathParam = window.location.pathname.substring("/project/".length);
if (pathParam) { if (pathParam) {

2
src/views/ProjectsView.vue

@ -355,7 +355,7 @@ export default class ProjectsView extends Vue {
**/ **/
async projectDataLoader(url: string) { async projectDataLoader(url: string) {
try { try {
const headers = await getHeaders(this.activeDid); const headers = await getHeaders(this.activeDid, this.$notify);
this.isLoading = true; this.isLoading = true;
const resp = await this.axios.get(url, { headers } as AxiosRequestConfig); const resp = await this.axios.get(url, { headers } as AxiosRequestConfig);
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {

4
src/views/SeedBackupView.vue

@ -94,7 +94,7 @@
</button> </button>
</div> </div>
</div> </div>
<div v-else>You do not have an active identifier.</div> <div v-else>You do not have an active identity.</div>
</section> </section>
</template> </template>
@ -135,7 +135,7 @@ export default class SeedBackupView extends Vue {
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error Loading Account", title: "Error Loading Profile",
text: "Got an error loading your seed data.", text: "Got an error loading your seed data.",
}, },
-1, -1,

Loading…
Cancel
Save