forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge pull request 'allow to customize the push-server for testing' (#80) from set-push-server into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#80
This commit is contained in:
@@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Web push notifications
|
||||
|
||||
## [0.1.3] - 2023.11
|
||||
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
||||
### Added
|
||||
- Contact name editing
|
||||
### Changed
|
||||
|
||||
@@ -94,10 +94,9 @@ For your own web-push tests, change the 'vapid' URL in App.vue, and install apps
|
||||
|
||||
### Clear/Reset data & restart
|
||||
|
||||
* Data: Clear the browser cache for localhost.
|
||||
* Notifications:
|
||||
* Under browser settings, look for "notification" and remove this server.
|
||||
* Under about:debugging, find the service worker and Unregister.
|
||||
* Clear cache for localhost.
|
||||
* Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`).
|
||||
* Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search).
|
||||
|
||||
|
||||
|
||||
|
||||
68
src/App.vue
68
src/App.vue
@@ -261,7 +261,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-facing-decorator";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import axios from "axios";
|
||||
interface ServiceWorkerMessage {
|
||||
type: string;
|
||||
data: string;
|
||||
@@ -285,22 +285,64 @@ interface VapidResponse {
|
||||
};
|
||||
}
|
||||
|
||||
import { AppString } from "@/constants/app";
|
||||
import { db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
|
||||
interface Notification {
|
||||
group: string;
|
||||
type: string;
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
$notify!: (notification: Notification, timeout?: number) => void;
|
||||
|
||||
b64 = "";
|
||||
mounted() {
|
||||
axios
|
||||
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid")
|
||||
.then((response: VapidResponse) => {
|
||||
this.b64 = response.data.vapidKey;
|
||||
console.log(this.b64);
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
console.log("New service worker is now controlling the page");
|
||||
|
||||
async mounted() {
|
||||
try {
|
||||
await db.open();
|
||||
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||
let pushUrl: string = AppString.DEFAULT_PUSH_SERVER;
|
||||
if (settings?.webPushServer) {
|
||||
pushUrl = settings.webPushServer;
|
||||
}
|
||||
|
||||
await axios
|
||||
.get(pushUrl + "/web-push/vapid")
|
||||
.then((response: VapidResponse) => {
|
||||
this.b64 = response.data?.vapidKey || "";
|
||||
console.log("Got vapid key:", this.b64);
|
||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||
console.log("New service worker is now controlling the page");
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
console.error("API error", error);
|
||||
});
|
||||
if (!this.b64) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error Setting Notifications",
|
||||
text: "Could not set notifications.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Got an error initializing notifications:", error);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error Setting Notifications",
|
||||
text: "Got an error setting notifications.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private sendMessageToServiceWorker(
|
||||
|
||||
@@ -4,20 +4,27 @@
|
||||
* See also ../libs/veramo/setup.ts
|
||||
*/
|
||||
export enum AppString {
|
||||
APP_NAME = "TimeSafari",
|
||||
APP_NAME = "Time Safari",
|
||||
|
||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
|
||||
|
||||
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
||||
|
||||
PROD_PUSH_SERVER = "https://timesafari.app",
|
||||
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
||||
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
||||
|
||||
DEFAULT_PUSH_SERVER = TEST1_PUSH_SERVER,
|
||||
}
|
||||
|
||||
/**
|
||||
* See notiwind package
|
||||
* The possible values for "group" and "type" are in App.vue.
|
||||
* From the notiwind package
|
||||
*/
|
||||
export interface NotificationIface {
|
||||
group: string;
|
||||
group: string; // "alert" | "modal"
|
||||
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
||||
title: string;
|
||||
text: string;
|
||||
|
||||
@@ -45,5 +45,8 @@ db.on("populate", () => {
|
||||
db.settings.add({
|
||||
id: MASTER_SETTINGS_KEY,
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@ export type Settings = {
|
||||
lastViewedClaimId?: string; // Last viewed claim ID
|
||||
lastNotifiedClaimId?: string; // Last notified claim ID
|
||||
isRegistered?: boolean;
|
||||
webPushServer?: string; // Web Push server URL
|
||||
|
||||
// Array of named search boxes defined by bounding boxes
|
||||
|
||||
searchBoxes?: Array<{
|
||||
name: string;
|
||||
bbox: BoundingBox;
|
||||
|
||||
@@ -324,30 +324,71 @@
|
||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
||||
</button>
|
||||
<button
|
||||
class="px-4 rounded bg-slate-200 border border-slate-400"
|
||||
@click="setApiServerInput(Constants.PROD_ENDORSER_API_SERVER)"
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="apiServerInput = AppConstants.PROD_ENDORSER_API_SERVER"
|
||||
>
|
||||
Use Prod
|
||||
</button>
|
||||
<button
|
||||
class="px-4 rounded bg-slate-200 border border-slate-400"
|
||||
@click="setApiServerInput(Constants.TEST_ENDORSER_API_SERVER)"
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="apiServerInput = AppConstants.TEST_ENDORSER_API_SERVER"
|
||||
>
|
||||
Use Test
|
||||
</button>
|
||||
<button
|
||||
class="px-4 rounded bg-slate-200 border border-slate-400"
|
||||
@click="setApiServerInput(Constants.LOCAL_ENDORSER_API_SERVER)"
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="apiServerInput = AppConstants.LOCAL_ENDORSER_API_SERVER"
|
||||
>
|
||||
Use Local
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex py-4">
|
||||
<h2 class="text-slate-500 text-sm font-bold mb-2">
|
||||
Notification Push Server
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||
v-model="webPushServerInput"
|
||||
/>
|
||||
<button
|
||||
v-if="webPushServerInput != webPushServer"
|
||||
class="px-4 rounded bg-red-500 border border-slate-400"
|
||||
@click="onClickSavePushServer()"
|
||||
>
|
||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="webPushServerInput = AppConstants.PROD_PUSH_SERVER"
|
||||
>
|
||||
Use Prod
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="webPushServerInput = AppConstants.TEST1_PUSH_SERVER"
|
||||
>
|
||||
Use Test 1
|
||||
</button>
|
||||
<button
|
||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||
@click="webPushServerInput = AppConstants.TEST2_PUSH_SERVER"
|
||||
>
|
||||
Use Test 2
|
||||
</button>
|
||||
</div>
|
||||
<span class="px-4 text-sm" v-if="!webPushServerInput">
|
||||
When that setting is blank, this app will use the default web push
|
||||
server URL:
|
||||
{{ AppConstants.DEFAULT_PUSH_SERVER }}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { AxiosError } from "axios";
|
||||
import { AxiosError, AxiosRequestConfig } from "axios";
|
||||
import "dexie-export-import";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
@@ -381,7 +422,7 @@ interface IAccount {
|
||||
export default class AccountViewView extends Vue {
|
||||
$notify!: (notification: Notification, timeout?: number) => void;
|
||||
|
||||
Constants = AppString;
|
||||
AppConstants = AppString;
|
||||
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
@@ -392,6 +433,8 @@ export default class AccountViewView extends Vue {
|
||||
numAccounts = 0;
|
||||
publicHex = "";
|
||||
publicBase64 = "";
|
||||
webPushServer = "";
|
||||
webPushServerInput = "";
|
||||
limits: RateLimits | null = null;
|
||||
limitsMessage = "";
|
||||
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message
|
||||
@@ -516,6 +559,8 @@ export default class AccountViewView extends Vue {
|
||||
(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;
|
||||
}
|
||||
|
||||
@@ -740,7 +785,7 @@ export default class AccountViewView extends Vue {
|
||||
private async fetchRateLimits(identity: IIdentifier) {
|
||||
const url = `${this.apiServer}/api/report/rateLimits`;
|
||||
const headers = await this.getHeaders(identity);
|
||||
return await this.axios.get(url, { headers });
|
||||
return await this.axios.get(url, { headers } as AxiosRequestConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -844,8 +889,12 @@ export default class AccountViewView extends Vue {
|
||||
this.apiServer = this.apiServerInput;
|
||||
}
|
||||
|
||||
setApiServerInput(value: string) {
|
||||
this.apiServerInput = value;
|
||||
async onClickSavePushServer() {
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
webPushServer: this.webPushServerInput,
|
||||
});
|
||||
this.webPushServer = this.webPushServerInput;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -625,7 +625,7 @@ export default class ProjectViewView extends Vue {
|
||||
}
|
||||
|
||||
openOfferDialog() {
|
||||
(this.$refs.customOfferDialog as OfferDialog).open();
|
||||
(this.$refs.customOfferDialog as typeof OfferDialog).open();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
10
web-push.md
10
web-push.md
@@ -390,3 +390,13 @@ While notifications are turned on, the user can tap on the App Notifications tog
|
||||
|
||||
- "Turn off Notifications" to fully turn them off (which means the user will need to go through the dialogs agains to turn them back on).
|
||||
- "Leave it On" to make no changes and dismiss the dialog.
|
||||
|
||||
# NOTIFICATION STATES
|
||||
|
||||
* Unpermissioned. Push server cannot send notifications to the user because it does not have permission.
|
||||
This may be the same as when the user gave permission in the past but has since revoked it at the OS or browser
|
||||
level, outside the app. (User can change to Permissioned when the user gives permission.)
|
||||
* Permissioned. (User can change to Unpermissioned via the OS or browser settings.)
|
||||
* Active. (User can change to Muted when the user mutes notifications.)
|
||||
* Muted. (User can change to Active when the user toggles it.)
|
||||
(Turning mute off automatically after some amount of time is not planned in version 1.)
|
||||
|
||||
Reference in New Issue
Block a user