forked from trent_larson/crowd-funder-for-time-pwa
catch more errors if something catastrophic happens to encrypted data
This commit is contained in:
22
src/App.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>
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,35 +501,72 @@ 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) {
|
||||||
let token;
|
try {
|
||||||
const account = await retrieveAccountMetadata(did);
|
let token;
|
||||||
if (account?.passkeyCredIdHex) {
|
const account = await retrieveAccountMetadata(did);
|
||||||
if (
|
if (account?.passkeyCredIdHex) {
|
||||||
passkeyAccessToken &&
|
if (
|
||||||
passkeyTokenExpirationEpochSeconds > Date.now() / 1000
|
passkeyAccessToken &&
|
||||||
) {
|
passkeyTokenExpirationEpochSeconds > Date.now() / 1000
|
||||||
// there's an active current passkey token
|
) {
|
||||||
token = passkeyAccessToken;
|
// there's an active current passkey token
|
||||||
} else {
|
token = passkeyAccessToken;
|
||||||
// there's no current passkey token or it's expired
|
} else {
|
||||||
token = await accessToken(did);
|
// there's no current passkey token or it's expired
|
||||||
|
token = await accessToken(did);
|
||||||
|
|
||||||
passkeyAccessToken = token;
|
passkeyAccessToken = token;
|
||||||
const passkeyExpirationSeconds = await getPasskeyExpirationSeconds();
|
const passkeyExpirationSeconds = await getPasskeyExpirationSeconds();
|
||||||
passkeyTokenExpirationEpochSeconds =
|
passkeyTokenExpirationEpochSeconds =
|
||||||
Date.now() / 1000 + passkeyExpirationSeconds;
|
Date.now() / 1000 + passkeyExpirationSeconds;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token = await accessToken(did);
|
||||||
|
}
|
||||||
|
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 {
|
|
||||||
token = await accessToken(did);
|
|
||||||
}
|
}
|
||||||
headers["Authorization"] = "Bearer " + token;
|
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
this.allMyDids = await libsUtil.retrieveAccountDids();
|
try {
|
||||||
|
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;
|
||||||
|
|||||||
@@ -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=" +
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -486,12 +487,21 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
this.allMyDids = await retrieveAccountDids();
|
try {
|
||||||
if (this.allMyDids.length === 0) {
|
this.allMyDids = await retrieveAccountDids();
|
||||||
this.isCreatingIdentifier = true;
|
if (this.allMyDids.length === 0) {
|
||||||
const newDid = await generateSaveAndActivateIdentity();
|
this.isCreatingIdentifier = true;
|
||||||
this.isCreatingIdentifier = false;
|
const newDid = await generateSaveAndActivateIdentity();
|
||||||
this.allMyDids = [newDid];
|
this.isCreatingIdentifier = false;
|
||||||
|
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();
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
this.allMyDids = await retrieveAccountDids();
|
try {
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user