Browse Source

Merge pull request 'send a time for notifications to the push server' (#112) from notify-time into master

Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/112
kb/add-usage-guide
trentlarson 7 months ago
parent
commit
8f4289c14d
  1. 18
      project.task.yaml
  2. 107
      src/App.vue
  3. 34
      src/libs/util.ts
  4. 4
      src/main.ts
  5. 13
      src/views/HelpNotificationsView.vue

18
project.task.yaml

@ -1,20 +1,24 @@
tasks : tasks :
- finish push server:
- get utcHour parameter working
- add back the explicit wait for browser subscription timing problems
- 01 change scanning flow - allow them to stay on the QR/scanning screen after scanning someone
- 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw - 24 contextual tutorials https://docs.google.com/document/d/11C_K3RM0rgo0onih20KFhcIzukZyq_CRWqaWX5om_kM/edit#heading=h.iwiwcydou5hw
- 24 Move to Vite - 24 Move to Vite assignee:jason
- .1 add shortcut from project (etc?) to the public project page in a browser
- .1 add KindSpring link to ideas - .1 add KindSpring link to ideas
- .1 on feed, don't show "to someone anonymous" if it's to a project - .1 on feed, don't show "to someone anonymous" if it's to a project
- .1 on ideas, put an "x" to close it assignee-group:ui
- 16 save data backups in Google - 16 save data backups in Google
- 16 generate and use passkeys for identities - 16 generate and use passkeys for identities
- .5 show "give" buttons (eg. from anonymous) even if they can't give, greyed out, and give them a warning and instructions - .5 show "give" buttons (eg. from anonymous) even if they can't give, greyed out, and give them a warning and instructions
- .2 when adding a claim on home screen, push that claim to the top of the list - .2 when adding a claim on home screen, push that claim to the top of the list
- .2 fix give dialog from "more contacts" off home page to allow giving to this user - .2 fix give dialog from "more contacts" off home page to allow giving to this user
- .2 fix bottom of project selection map, where the icons are hidden but a tap goes to the icon's page assignee-group:ui
- .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window) - .5 stop from seeing an error on the first page when browser doesn't support service workers (which I've seen on iPhone; visible in Firefox private window)
- .2 don't show a warning on a totally new project when the authorized agent is set - .2 don't show a warning on a totally new project when the authorized agent is set
- .2 anchor hash into BTC - .2 anchor hash into BTC
@ -24,8 +28,9 @@ tasks :
- 08 add image on profile - 08 add image on profile
- ask to detect location & record it in settings - 01 ask to detect location & record it in settings
- if personal location is set, show potential local affiliations - 01 if personal location is set, show potential local affiliations
- 02 refactor the buttons for chosing a search location so that the actions are clear assignee-group:ui
- 24 compelling UI for credential presentations - 24 compelling UI for credential presentations
- discover who in my network has activity on a project - discover who in my network has activity on a project
@ -94,7 +99,7 @@ tasks :
- .3 check that Android shows "back" buttons on screens without bottom tray - .3 check that Android shows "back" buttons on screens without bottom tray
- .1 Make give description text box into something that expands as they type? - .1 Make give description text box into something that expands as they type?
- .2 Show a warning if both giver and recipient are the same (but still allow?) - .2 Show a warning if both giver and recipient are the same (but still allow?)
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui - .5 Shrink the buttons on project pages so they don't expand to the width of the screen assignee-group:ui
- .5 Display a more appealing confirmation on the map when erasing the marker - .5 Display a more appealing confirmation on the map when erasing the marker
- .5 remove references to localStorage for projectId (now that it's pulling from the path) - .5 remove references to localStorage for projectId (now that it's pulling from the path)
- switch some checks for activeDid to check for isRegistered - switch some checks for activeDid to check for isRegistered
@ -142,7 +147,6 @@ tasks :
- for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances - for subtasks: fulfills (is it really the same?), feeds, contributes to, supplies, boosts, advances
- for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning) - for blocking: blocks, precedes, comes before, is sought by -- vs follows, seeks, builds on ("contributes to" isn't specific enough, "succeeds" has different, possibly confusing meaning)
- .5 fit as many icons as possible on home & project view screens but only going halfway down the page assignee-group:ui
- .5 Replace Gifted/Give in ContactsView with GiftedDialog - .5 Replace Gifted/Give in ContactsView with GiftedDialog
- Stats : - Stats :

107
src/App.vue

@ -191,7 +191,7 @@
> >
<div class="w-full px-6 py-6 text-slate-900 text-center"> <div class="w-full px-6 py-6 text-slate-900 text-center">
<p v-if="serviceWorkerReady" class="text-lg mb-4"> <p v-if="serviceWorkerReady" class="text-lg mb-4">
Would you like to <b>turn on</b> notifications for this app? Would you like to be notified of new activity once a day?
</p> </p>
<p v-else class="text-lg mb-4"> <p v-else class="text-lg mb-4">
Waiting for system initialization, which may take up to 10 Waiting for system initialization, which may take up to 10
@ -199,22 +199,42 @@
<fa icon="spinner" spin /> <fa icon="spinner" spin />
</p> </p>
<div v-if="serviceWorkerReady">
<span class="flex flex-row justify-center">
<span class="mt-2">Yes, tell me at: </span>
<input
type="number"
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
v-model="hourInput"
/>
<span
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
@click="hourAm = !hourAm"
>
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
<span v-else> PM <fa icon="chevron-up" /> </span>
</span>
</span>
<button <button
v-if="serviceWorkerReady" class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
@click=" @click="
() => {
if (checkHour()) {
close(notification.id); close(notification.id);
turnOnNotifications(); turnOnNotifications();
}
}
" "
> >
Turn on Notifications Turn on Daily Message
</button> </button>
</div>
<button <button
@click="close(notification.id)" @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" class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white mt-4 px-2 py-2 rounded-md"
> >
Maybe Later No, Not Now
</button> </button>
</div> </div>
</div> </div>
@ -297,8 +317,11 @@
<style></style> <style></style>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import axios from "axios"; import axios from "axios";
import { Vue, Component } from "vue-facing-decorator";
import * as libsUtil from "@/libs/util";
interface ServiceWorkerMessage { interface ServiceWorkerMessage {
type: string; type: string;
data: string; data: string;
@ -322,6 +345,10 @@ interface VapidResponse {
}; };
} }
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
notifyTime: { utcHour: number };
}
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app"; import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index"; import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@ -332,7 +359,9 @@ export default class App extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
b64 = ""; b64 = "";
serviceWorkerReady = false; hourAm = true;
hourInput = "8";
serviceWorkerReady = true;
async mounted() { async mounted() {
try { try {
@ -461,6 +490,48 @@ export default class App extends Vue {
}); });
} }
// this allows us to show an error without closing the dialog
checkHour() {
if (!libsUtil.isNumeric(this.hourInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Number",
text: "The time must be an hour number.",
},
5000,
);
return false;
}
const hourNum = libsUtil.numberOrZero(this.hourInput);
if (!Number.isInteger(hourNum)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Whole Number",
text: "The time must be a whole hour number.",
},
5000,
);
return false;
}
if (hourNum < 1 || 12 < hourNum) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not a Whole Number",
text: "The time must be an hour between 1 and 12.",
},
5000,
);
return false;
}
return true;
}
public async turnOnNotifications() { public async turnOnNotifications() {
return this.askPermission() return this.askPermission()
.then((permission) => { .then((permission) => {
@ -486,13 +557,25 @@ export default class App extends Vue {
}, },
-1, -1,
); );
this.sendSubscriptionToServer(subscription); // we already checked that this is a valid hour number
return subscription; const rawHourNum = libsUtil.numberOrZero(this.hourInput);
const adjHourNum = rawHourNum + (this.hourAm ? 0 : 12);
const hourNum = adjHourNum % 24;
const utcHour =
hourNum + Math.round(new Date().getTimezoneOffset() / 60);
const finalUtcHour = (utcHour + (utcHour < 0 ? 24 : 0)) % 24;
const subscriptionWithTime: PushSubscriptionWithTime = {
notifyTime: { utcHour: finalUtcHour },
...subscription.toJSON(),
};
await this.sendSubscriptionToServer(subscriptionWithTime);
return subscriptionWithTime;
} else { } else {
throw new Error("Subscription object is not available."); throw new Error("Subscription object is not available.");
} }
}) })
.then(async (subscription) => { .then(async (subscription: PushSubscriptionWithTime) => {
console.log( console.log(
"Subscription data sent to server and all finished successfully.", "Subscription data sent to server and all finished successfully.",
); );
@ -583,7 +666,7 @@ export default class App extends Vue {
} }
private sendSubscriptionToServer( private sendSubscriptionToServer(
subscription: PushSubscription, subscription: PushSubscriptionWithTime,
): Promise<void> { ): Promise<void> {
console.log("About to send subscription...", subscription); console.log("About to send subscription...", subscription);
return fetch("/web-push/subscribe", { return fetch("/web-push/subscribe", {

34
src/libs/util.ts

@ -12,9 +12,6 @@ import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer"; import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
export const PRIVACY_MESSAGE = export const PRIVACY_MESSAGE =
"The data you send be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to those you allow."; "The data you send be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to those you allow.";
@ -56,9 +53,13 @@ export function iconForUnitCode(unitCode: string) {
} }
// from https://stackoverflow.com/a/175787/845494 // from https://stackoverflow.com/a/175787/845494
// ... though it appears even this isn't precisely right so keep doing "|| 0" or something in sensitive places
// //
export function isNumeric(str: string): boolean { export function isNumeric(str: string): boolean {
return !isNaN(+str); // This ignore commentary is because typescript complains when you pass a string to isNaN.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return !isNaN(str) && !isNaN(parseFloat(str));
} }
export function numberOrZero(str: string): number { export function numberOrZero(str: string): number {
@ -237,7 +238,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
}; };
export const sendTestThroughPushServer = async ( export const sendTestThroughPushServer = async (
subscription: PushSubscription, subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean, skipFilter: boolean,
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
await db.open(); await db.open();
@ -252,28 +253,11 @@ export const sendTestThroughPushServer = async (
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213 // Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION"; const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
const auth = Buffer.from(subscription.getKey("auth"));
const authB64 = auth
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const p256dh = Buffer.from(subscription.getKey("p256dh"));
const p256dhB64 = p256dh
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const newPayload = { const newPayload = {
endpoint: subscription.endpoint, // eslint-disable-next-line prettier/prettier
keys: { message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
auth: authB64,
p256dh: p256dhB64,
},
message: `Test, where you will see this message ${
skipFilter ? "un" : ""
}filtered.`,
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push", title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
...subscriptionJSON,
}; };
console.log("Sending a test web push message:", newPayload); console.log("Sending a test web push message:", newPayload);
const payloadStr = JSON.stringify(newPayload); const payloadStr = JSON.stringify(newPayload);

4
src/main.ts

@ -20,8 +20,10 @@ import {
faCalendar, faCalendar,
faCamera, faCamera,
faCheck, faCheck,
faChevronDown,
faChevronLeft, faChevronLeft,
faChevronRight, faChevronRight,
faChevronUp,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo, faCircleInfo,
@ -81,8 +83,10 @@ library.add(
faCalendar, faCalendar,
faCamera, faCamera,
faCheck, faCheck,
faChevronDown,
faChevronLeft, faChevronLeft,
faChevronRight, faChevronRight,
faChevronUp,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo, faCircleInfo,

13
src/views/HelpNotificationsView.vue

@ -301,12 +301,13 @@ import { sendTestThroughPushServer } from "@/libs/util";
export default class HelpNotificationsView extends Vue { export default class HelpNotificationsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; $notify!: (notification: NotificationIface, timeout?: number) => void;
subscription: PushSubscription | null = null; subscriptionJSON?: PushSubscriptionJSON;
async mounted() { async mounted() {
try { try {
const registration = await navigator.serviceWorker.ready; const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription(); const fullSub = await registration.pushManager.getSubscription();
this.subscriptionJSON = fullSub?.toJSON();
} catch (error) { } catch (error) {
console.error("Mount error:", error); console.error("Mount error:", error);
} }
@ -315,13 +316,13 @@ export default class HelpNotificationsView extends Vue {
alertWebPushSubscription() { alertWebPushSubscription() {
console.log( console.log(
"Web push subscription:", "Web push subscription:",
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
); );
alert(JSON.stringify(this.subscription)); alert(JSON.stringify(this.subscriptionJSON));
} }
async sendTestWebPushMessage(skipFilter: boolean = false) { async sendTestWebPushMessage(skipFilter: boolean = false) {
if (!this.subscription) { if (!this.subscriptionJSON) {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
@ -336,7 +337,7 @@ export default class HelpNotificationsView extends Vue {
} }
try { try {
await sendTestThroughPushServer(this.subscription, skipFilter); await sendTestThroughPushServer(this.subscriptionJSON, skipFilter);
this.$notify( this.$notify(
{ {

Loading…
Cancel
Save