add warning if on unexpected server #98

Merged
trentlarson merged 2 commits from server-warn into master 11 months ago
  1. 58
      src/components/TopMessage.vue
  2. 3
      src/constants/app.ts
  3. 3
      src/db/index.ts
  4. 15
      src/db/tables/settings.ts
  5. 217
      src/views/AccountViewView.vue
  6. 3
      src/views/DiscoverView.vue
  7. 5
      src/views/HomeView.vue
  8. 5
      src/views/ProjectViewView.vue
  9. 5
      src/views/ProjectsView.vue

58
src/components/TopMessage.vue

@ -0,0 +1,58 @@
<template>
<div class="text-center text-red-500">{{ message }}</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { AppString } from "@/constants/app";
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component
export default class TopMessage extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
@Prop selected = "";
message = "";
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (
settings?.warnIfTestServer &&
window.location.hostname !== AppString.PROD_TIME_SAFARI_SERVER
) {
const didPrefix = settings.activeDid?.slice(11, 14);
this.message = "You're linked to a test server, user " + didPrefix;
} else if (
settings?.warnIfProdServer &&
window.location.hostname === AppString.PROD_TIME_SAFARI_SERVER
) {
const didPrefix = settings.activeDid?.slice(1, 14);
this.message =
"You're linked to the production server, user " + didPrefix;
}
} catch (err: unknown) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Detecting Server",
text: JSON.stringify(err),
},
10000,
);
}
}
}
</script>

3
src/constants/app.ts

@ -6,12 +6,15 @@
export enum AppString { export enum AppString {
APP_NAME = "Time Safari", APP_NAME = "Time Safari",
PROD_TIME_SAFARI_SERVER = "https://timesafari.app",
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch", TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000", LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER, DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
PROD_PUSH_SERVER = "https://timesafari.app", PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.timesafari.app", TEST1_PUSH_SERVER = "https://test.timesafari.app",
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com", TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",

3
src/db/index.ts

@ -45,8 +45,5 @@ db.on("populate", () => {
db.settings.add({ db.settings.add({
id: MASTER_SETTINGS_KEY, id: MASTER_SETTINGS_KEY,
apiServer: AppString.DEFAULT_ENDORSER_API_SERVER, apiServer: AppString.DEFAULT_ENDORSER_API_SERVER,
// remember that things you add from now on aren't automatically in the DB for old users
webPushServer: AppString.DEFAULT_PUSH_SERVER,
}); });
}); });

15
src/db/tables/settings.ts

@ -12,15 +12,17 @@ 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 using MASTER_SETTINGS_KEY id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY
activeDid?: string; // Active Decentralized ID activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL apiServer?: string; // API server URL
firstName?: string; // User's first name firstName?: string; // User's first name
isRegistered?: boolean;
lastName?: string; // deprecated - put all names in firstName lastName?: string; // deprecated - put all names in firstName
lastViewedClaimId?: string; // Last viewed claim ID
lastNotifiedClaimId?: string; // Last notified claim ID lastNotifiedClaimId?: string; // Last notified claim ID
isRegistered?: boolean; lastViewedClaimId?: string; // Last viewed claim ID
webPushServer?: string; // Web Push server URL reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders
// Array of named search boxes defined by bounding boxes // Array of named search boxes defined by bounding boxes
searchBoxes?: Array<{ searchBoxes?: Array<{
@ -30,8 +32,9 @@ export type Settings = {
showContactGivesInline?: boolean; // Display contact inline or not showContactGivesInline?: boolean; // Display contact inline or not
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders warnIfProdServer?: boolean; // Warn if using a production server
reminderOn?: boolean; // Toggle to enable or disable reminders warnIfTestServer?: boolean; // Warn if using a testing server
webPushServer?: string; // Web Push server URL
}; };
/** /**

217
src/views/AccountViewView.vue

@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Profile"></QuickNav> <QuickNav selected="Profile"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
@ -215,7 +217,7 @@
<div v-if="showAdvanced"> <div v-if="showAdvanced">
<p class="text-rose-600 mb-8"> <p class="text-rose-600 mb-8">
Beware: the features here can be confusing and even change data in ways Beware: the features here can be confusing and even change data in ways
you do not expect. But we support your freedoms! you do not expect. But we support your freedom!
</p> </p>
<!-- Deep Identity Details --> <!-- Deep Identity Details -->
@ -357,6 +359,46 @@
</button> </button>
</div> </div>
<label
for="toggleProdWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleProdWarning"
>
<!-- label -->
<h2>Show warning if on prod server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<label
for="toggleTestWarningMessage"
class="flex items-center justify-between cursor-pointer my-4"
@click="toggleTestWarning"
>
<!-- label -->
<h2>Show warning if on test server</h2>
<!-- toggle -->
<div class="relative ml-2">
<!-- input -->
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
</label>
<div class="flex py-4"> <div class="flex py-4">
<h2 class="text-slate-500 text-sm font-bold mb-2"> <h2 class="text-slate-500 text-sm font-bold mb-2">
Notification Push Server Notification Push Server
@ -407,7 +449,8 @@ import "dexie-export-import";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav";
import TopMessage from "@/components/TopMessage";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db/index"; import { db, accountsDB } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@ -432,7 +475,7 @@ interface IAccount {
derivationPath: string; derivationPath: string;
} }
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav, TopMessage } })
export default class AccountViewView extends Vue { export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
@ -462,6 +505,8 @@ export default class AccountViewView extends Vue {
showAdvanced = false; showAdvanced = false;
subscription: PushSubscription | null = null; subscription: PushSubscription | null = null;
warnIfProdServer = false;
warnIfTestServer = false;
private isSubscribed = false; private isSubscribed = false;
get toggleNotifications() { get toggleNotifications() {
@ -471,6 +516,62 @@ export default class AccountViewView extends Vue {
this.isSubscribed = value; this.isSubscribed = value;
} }
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!this.subscription;
} catch (error) {
console.error("Mount error:", error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.showContactGives = !!settings?.showContactGivesInline;
this.warnIfProdServer = !!settings?.warnIfProdServer;
this.warnIfTestServer = !!settings?.warnIfTestServer;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
}
public async getIdentity(activeDid: string): Promise<IIdentifier | null> { public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
try { try {
// Open the accounts database // Open the accounts database
@ -536,6 +637,16 @@ export default class AccountViewView extends Vue {
this.updateShowContactAmounts(); this.updateShowContactAmounts();
} }
toggleProdWarning() {
this.warnIfProdServer = !this.warnIfProdServer;
this.updateWarnIfProdServer(this.warnIfProdServer);
}
toggleTestWarning() {
this.warnIfTestServer = !this.warnIfTestServer;
this.updateWarnIfTestServer(this.warnIfTestServer);
}
readableTime(timeStr: string) { readableTime(timeStr: string) {
return timeStr.substring(0, timeStr.indexOf("T")); return timeStr.substring(0, timeStr.indexOf("T"));
} }
@ -545,60 +656,6 @@ export default class AccountViewView extends Vue {
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();
} }
/**
* Async function executed when the component is created.
* Initializes the component's state with values from the database,
* handles identity-related tasks, and checks limitations.
*
* @throws Will display specific messages to the user based on different errors.
*/
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
// Initialize component state with values from the database or defaults
this.initializeState(settings);
// Get and process the identity
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.processIdentity(identity);
}
} catch (err: unknown) {
this.handleError(err);
}
}
async mounted() {
try {
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.toggleNotifications = !!this.subscription;
} catch (error) {
console.error("Mount error:", error);
this.toggleNotifications = false;
}
}
/**
* Initializes component state with values from the database or defaults.
* @param {SettingsType} settings - Object containing settings from the database.
*/
initializeState(settings: Settings | undefined) {
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
this.showContactGives = !!settings?.showContactGivesInline;
}
/** /**
* Processes the identity and updates the component's state. * Processes the identity and updates the component's state.
* @param {IdentityType} identity - Object containing identity information. * @param {IdentityType} identity - Object containing identity information.
@ -672,6 +729,52 @@ export default class AccountViewView extends Vue {
} }
} }
public async updateWarnIfProdServer(newSetting) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfProdServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Prod Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
public async updateWarnIfTestServer(newSetting) {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Test Warning",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache after contact setting update because:",
err,
);
}
}
/** /**
* Asynchronously exports the database into a downloadable JSON file. * Asynchronously exports the database into a downloadable JSON file.
* *

3
src/views/DiscoverView.vue

@ -1,5 +1,6 @@
<template> <template>
<QuickNav selected="Discover"></QuickNav> <QuickNav selected="Discover"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
@ -136,6 +137,7 @@ import { didInfo, ProjectData } from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage";
interface Notification { interface Notification {
group: string; group: string;
@ -149,6 +151,7 @@ interface Notification {
QuickNav, QuickNav,
InfiniteScroll, InfiniteScroll,
EntityIcon, EntityIcon,
TopMessage,
}, },
}) })
export default class DiscoverView extends Vue { export default class DiscoverView extends Vue {

5
src/views/HomeView.vue

@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Home"></QuickNav> <QuickNav selected="Home"></QuickNav>
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@ -146,6 +148,7 @@ import EntityIcon from "@/components/EntityIcon.vue";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage";
import { db, accountsDB } from "@/db/index"; import { db, accountsDB } 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";
@ -166,7 +169,7 @@ interface Notification {
} }
@Component({ @Component({
components: { GiftedDialog, QuickNav, EntityIcon, InfiniteScroll }, components: { GiftedDialog, QuickNav, EntityIcon, InfiniteScroll, TopMessage },
}) })
export default class HomeView extends Vue { export default class HomeView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;

5
src/views/ProjectViewView.vue

@ -1,5 +1,7 @@
<template> <template>
<QuickNav /> <QuickNav />
<TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Breadcrumb --> <!-- Breadcrumb -->
@ -282,6 +284,7 @@ import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue"; import OfferDialog from "@/components/OfferDialog.vue";
import TopMessage from "@/components/TopMessage";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db } 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 { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@ -306,7 +309,7 @@ interface Notification {
} }
@Component({ @Component({
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav }, components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav, TopMessage },
}) })
export default class ProjectViewView extends Vue { export default class ProjectViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;

5
src/views/ProjectsView.vue

@ -1,5 +1,7 @@
<template> <template>
<QuickNav selected="Projects"></QuickNav> <QuickNav selected="Projects"></QuickNav>
<TopMessage />
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
@ -79,6 +81,7 @@ import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import TopMessage from "@/components/TopMessage";
import { ProjectData } from "@/libs/endorserServer"; import { ProjectData } from "@/libs/endorserServer";
interface Notification { interface Notification {
@ -89,7 +92,7 @@ interface Notification {
} }
@Component({ @Component({
components: { InfiniteScroll, QuickNav, EntityIcon }, components: { InfiniteScroll, QuickNav, EntityIcon, TopMessage },
}) })
export default class ProjectsView extends Vue { export default class ProjectsView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;

Loading…
Cancel
Save