forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge pull request 'send a time for notifications to the push server' (#112) from notify-time into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#112
This commit is contained in:
@@ -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 :
|
||||||
|
|||||||
121
src/App.vue
121
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>
|
||||||
|
|
||||||
<button
|
<div v-if="serviceWorkerReady">
|
||||||
v-if="serviceWorkerReady"
|
<span class="flex flex-row justify-center">
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
<span class="mt-2">Yes, tell me at: </span>
|
||||||
@click="
|
<input
|
||||||
close(notification.id);
|
type="number"
|
||||||
turnOnNotifications();
|
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"
|
||||||
>
|
/>
|
||||||
Turn on Notifications
|
<span
|
||||||
</button>
|
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
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
if (checkHour()) {
|
||||||
|
close(notification.id);
|
||||||
|
turnOnNotifications();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Turn on Daily Message
|
||||||
|
</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", {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user