Browse Source

Merge pull request 'allow to customize the push-server for testing' (#80) from set-push-server into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/80
adjust-note
trentlarson 12 months ago
parent
commit
feea1a1d3b
  1. 4
      CHANGELOG.md
  2. 7
      README.md
  3. 68
      src/App.vue
  4. 13
      src/constants/app.ts
  5. 3
      src/db/index.ts
  6. 2
      src/db/tables/settings.ts
  7. 71
      src/views/AccountViewView.vue
  8. 2
      src/views/ProjectViewView.vue
  9. 10
      web-push.md

4
CHANGELOG.md

@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Web push notifications
## [0.1.3] - 2023.11 ## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
### Added ### Added
- Contact name editing - Contact name editing
### Changed ### Changed

7
README.md

@ -94,10 +94,9 @@ For your own web-push tests, change the 'vapid' URL in App.vue, and install apps
### Clear/Reset data & restart ### Clear/Reset data & restart
* Data: Clear the browser cache for localhost. * Clear cache for localhost.
* Notifications: * Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`).
* Under browser settings, look for "notification" and remove this server. * Clear notification permission (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search).
* Under about:debugging, find the service worker and Unregister.

68
src/App.vue

@ -261,7 +261,7 @@
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-facing-decorator"; import { Vue, Component } from "vue-facing-decorator";
import axios, { AxiosError } from "axios"; import axios from "axios";
interface ServiceWorkerMessage { interface ServiceWorkerMessage {
type: string; type: string;
data: 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 @Component
export default class App extends Vue { export default class App extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
b64 = ""; b64 = "";
mounted() {
axios async mounted() {
.get("https://timesafari-pwa.anomalistlabs.com/web-push/vapid") try {
.then((response: VapidResponse) => { await db.open();
this.b64 = response.data.vapidKey; const settings = await db.settings.get(MASTER_SETTINGS_KEY);
console.log(this.b64); let pushUrl: string = AppString.DEFAULT_PUSH_SERVER;
navigator.serviceWorker.addEventListener("controllerchange", () => { if (settings?.webPushServer) {
console.log("New service worker is now controlling the page"); 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");
});
}); });
}) if (!this.b64) {
.catch((error: AxiosError) => { this.$notify(
console.error("API error", error); {
}); 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( private sendMessageToServiceWorker(

13
src/constants/app.ts

@ -4,20 +4,27 @@
* See also ../libs/veramo/setup.ts * See also ../libs/veramo/setup.ts
*/ */
export enum AppString { export enum AppString {
APP_NAME = "TimeSafari", APP_NAME = "Time Safari",
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,
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 { export interface NotificationIface {
group: string; group: string; // "alert" | "modal"
type: string; // "toast" | "info" | "success" | "warning" | "danger" type: string; // "toast" | "info" | "success" | "warning" | "danger"
title: string; title: string;
text: string; text: string;

3
src/db/index.ts

@ -45,5 +45,8 @@ 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,
}); });
}); });

2
src/db/tables/settings.ts

@ -20,9 +20,9 @@ export type Settings = {
lastViewedClaimId?: string; // Last viewed claim ID lastViewedClaimId?: string; // Last viewed claim ID
lastNotifiedClaimId?: string; // Last notified claim ID lastNotifiedClaimId?: string; // Last notified claim ID
isRegistered?: boolean; isRegistered?: boolean;
webPushServer?: string; // Web Push server URL
// Array of named search boxes defined by bounding boxes // Array of named search boxes defined by bounding boxes
searchBoxes?: Array<{ searchBoxes?: Array<{
name: string; name: string;
bbox: BoundingBox; bbox: BoundingBox;

71
src/views/AccountViewView.vue

@ -324,30 +324,71 @@
<fa icon="floppy-disk" class="fa-fw" color="white"></fa> <fa icon="floppy-disk" class="fa-fw" color="white"></fa>
</button> </button>
<button <button
class="px-4 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.PROD_ENDORSER_API_SERVER)" @click="apiServerInput = AppConstants.PROD_ENDORSER_API_SERVER"
> >
Use Prod Use Prod
</button> </button>
<button <button
class="px-4 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.TEST_ENDORSER_API_SERVER)" @click="apiServerInput = AppConstants.TEST_ENDORSER_API_SERVER"
> >
Use Test Use Test
</button> </button>
<button <button
class="px-4 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.LOCAL_ENDORSER_API_SERVER)" @click="apiServerInput = AppConstants.LOCAL_ENDORSER_API_SERVER"
> >
Use Local Use Local
</button> </button>
</div> </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> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { AxiosError } from "axios"; import { AxiosError, AxiosRequestConfig } from "axios";
import "dexie-export-import"; 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";
@ -381,7 +422,7 @@ interface IAccount {
export default class AccountViewView extends Vue { export default class AccountViewView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void; $notify!: (notification: Notification, timeout?: number) => void;
Constants = AppString; AppConstants = AppString;
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
@ -392,6 +433,8 @@ export default class AccountViewView extends Vue {
numAccounts = 0; numAccounts = 0;
publicHex = ""; publicHex = "";
publicBase64 = ""; publicBase64 = "";
webPushServer = "";
webPushServerInput = "";
limits: RateLimits | null = null; limits: RateLimits | null = null;
limitsMessage = ""; limitsMessage = "";
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message 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?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3 (settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
this.showContactGives = !!settings?.showContactGivesInline; this.showContactGives = !!settings?.showContactGivesInline;
} }
@ -740,7 +785,7 @@ export default class AccountViewView extends Vue {
private async fetchRateLimits(identity: IIdentifier) { private async fetchRateLimits(identity: IIdentifier) {
const url = `${this.apiServer}/api/report/rateLimits`; const url = `${this.apiServer}/api/report/rateLimits`;
const headers = await this.getHeaders(identity); 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; this.apiServer = this.apiServerInput;
} }
setApiServerInput(value: string) { async onClickSavePushServer() {
this.apiServerInput = value; await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
webPushServer: this.webPushServerInput,
});
this.webPushServer = this.webPushServerInput;
} }
} }
</script> </script>

2
src/views/ProjectViewView.vue

@ -625,7 +625,7 @@ export default class ProjectViewView extends Vue {
} }
openOfferDialog() { openOfferDialog() {
(this.$refs.customOfferDialog as OfferDialog).open(); (this.$refs.customOfferDialog as typeof OfferDialog).open();
} }
} }
</script> </script>

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). - "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. - "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.)

Loading…
Cancel
Save