forked from trent_larson/crowd-funder-for-time-pwa
add minute to notification scheduling & fix a bug, plus other tweaks
This commit is contained in:
97
src/App.vue
97
src/App.vue
@@ -260,9 +260,16 @@
|
|||||||
<span class="mt-2">Yes, tell me at: </span>
|
<span class="mt-2">Yes, tell me at: </span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
|
@change="checkHourInput"
|
||||||
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
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"
|
v-model="hourInput"
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
@change="checkMinuteInput"
|
||||||
|
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
|
||||||
|
v-model="minuteInput"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
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"
|
@click="hourAm = !hourAm"
|
||||||
@@ -377,7 +384,7 @@ import axios from "axios";
|
|||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
||||||
import { retrieveSettingsForActiveAccount } from "@/db/index";
|
import { addLogMessage, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
|
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
|
||||||
|
|
||||||
@@ -405,7 +412,8 @@ interface VapidResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
||||||
notifyTime: { utcHour: number };
|
notifyTime: { utcHour: number; minute: number };
|
||||||
|
notifyType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -416,6 +424,7 @@ export default class App extends Vue {
|
|||||||
b64 = "";
|
b64 = "";
|
||||||
hourAm = true;
|
hourAm = true;
|
||||||
hourInput = "8";
|
hourInput = "8";
|
||||||
|
minuteInput = "00";
|
||||||
serviceWorkerReady = true;
|
serviceWorkerReady = true;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -429,14 +438,19 @@ export default class App extends Vue {
|
|||||||
if (pushUrl.startsWith("http://localhost")) {
|
if (pushUrl.startsWith("http://localhost")) {
|
||||||
console.log("Not checking for VAPID in this local environment.");
|
console.log("Not checking for VAPID in this local environment.");
|
||||||
} else {
|
} else {
|
||||||
|
let responseData = "";
|
||||||
await axios
|
await axios
|
||||||
.get(pushUrl + "/web-push/vapid")
|
.get(pushUrl + "/web-push/vapid")
|
||||||
.then((response: VapidResponse) => {
|
.then((response: VapidResponse) => {
|
||||||
this.b64 = response.data?.vapidKey || "";
|
this.b64 = response.data?.vapidKey || "";
|
||||||
console.log("Got vapid key:", this.b64);
|
console.log("Got vapid key:", this.b64);
|
||||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
responseData = JSON.stringify(response.data);
|
||||||
console.log("New service worker is now controlling the page");
|
navigator.serviceWorker?.addEventListener(
|
||||||
});
|
"controllerchange",
|
||||||
|
() => {
|
||||||
|
console.log("New service worker is now controlling the page");
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (!this.b64) {
|
if (!this.b64) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -446,7 +460,11 @@ export default class App extends Vue {
|
|||||||
title: "Error Setting Notifications",
|
title: "Error Setting Notifications",
|
||||||
text: "Could not set notifications.",
|
text: "Could not set notifications.",
|
||||||
},
|
},
|
||||||
-1,
|
5000,
|
||||||
|
);
|
||||||
|
await addLogMessage(
|
||||||
|
"Error Setting Notifications: web push server response didn't have vapidKey: " +
|
||||||
|
responseData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +480,11 @@ export default class App extends Vue {
|
|||||||
title: "Error Setting Notifications",
|
title: "Error Setting Notifications",
|
||||||
text: "Got an error setting notifications.",
|
text: "Got an error setting notifications.",
|
||||||
},
|
},
|
||||||
-1,
|
5000,
|
||||||
|
);
|
||||||
|
await addLogMessage(
|
||||||
|
"Error Setting Notifications: some error occurred: " +
|
||||||
|
JSON.stringify(error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,7 +498,7 @@ export default class App extends Vue {
|
|||||||
message: ServiceWorkerMessage,
|
message: ServiceWorkerMessage,
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker?.controller) {
|
||||||
const messageChannel = new MessageChannel();
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
messageChannel.port1.onmessage = (event: MessageEvent) => {
|
messageChannel.port1.onmessage = (event: MessageEvent) => {
|
||||||
@@ -487,7 +509,7 @@ export default class App extends Vue {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
navigator.serviceWorker.controller.postMessage(message, [
|
navigator.serviceWorker?.controller.postMessage(message, [
|
||||||
messageChannel.port2,
|
messageChannel.port2,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
@@ -498,7 +520,9 @@ export default class App extends Vue {
|
|||||||
|
|
||||||
private askPermission(): Promise<NotificationPermission> {
|
private askPermission(): Promise<NotificationPermission> {
|
||||||
console.log("Requesting permission for notifications:", navigator);
|
console.log("Requesting permission for notifications:", navigator);
|
||||||
if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
|
if (
|
||||||
|
!("serviceWorker" in navigator && navigator.serviceWorker?.controller)
|
||||||
|
) {
|
||||||
return Promise.reject("Service worker not available.");
|
return Promise.reject("Service worker not available.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +572,36 @@ export default class App extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkHourInput() {
|
||||||
|
const hourNum = parseInt(this.hourInput);
|
||||||
|
if (isNaN(hourNum)) {
|
||||||
|
this.hourInput = "12";
|
||||||
|
} else if (hourNum < 1) {
|
||||||
|
this.hourInput = "12";
|
||||||
|
this.hourAm = !this.hourAm;
|
||||||
|
} else if (hourNum > 12) {
|
||||||
|
this.hourInput = "1";
|
||||||
|
this.hourAm = !this.hourAm;
|
||||||
|
} else {
|
||||||
|
this.hourInput = hourNum.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkMinuteInput() {
|
||||||
|
const minuteNum = parseInt(this.minuteInput);
|
||||||
|
if (isNaN(minuteNum)) {
|
||||||
|
this.minuteInput = "00";
|
||||||
|
} else if (minuteNum < 0) {
|
||||||
|
this.minuteInput = "59";
|
||||||
|
} else if (minuteNum < 10) {
|
||||||
|
this.minuteInput = "0" + minuteNum;
|
||||||
|
} else if (minuteNum > 59) {
|
||||||
|
this.minuteInput = "00";
|
||||||
|
} else {
|
||||||
|
this.minuteInput = minuteNum.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this allows us to show an error without closing the dialog
|
// this allows us to show an error without closing the dialog
|
||||||
checkHour() {
|
checkHour() {
|
||||||
if (!libsUtil.isNumeric(this.hourInput)) {
|
if (!libsUtil.isNumeric(this.hourInput)) {
|
||||||
@@ -617,14 +671,29 @@ export default class App extends Vue {
|
|||||||
);
|
);
|
||||||
// we already checked that this is a valid hour number
|
// we already checked that this is a valid hour number
|
||||||
const rawHourNum = libsUtil.numberOrZero(this.hourInput);
|
const rawHourNum = libsUtil.numberOrZero(this.hourInput);
|
||||||
const adjHourNum = rawHourNum + (this.hourAm ? 0 : 12);
|
const adjHourNum = this.hourAm
|
||||||
const hourNum = adjHourNum % 24;
|
? // If it's AM, then we'll change it to 0 for 12 AM but otherwise use rawHourNum
|
||||||
|
rawHourNum === 12
|
||||||
|
? 0
|
||||||
|
: rawHourNum
|
||||||
|
: // Otherwise it's PM, so keep a 12 but otherwise add 12
|
||||||
|
rawHourNum === 12
|
||||||
|
? 12
|
||||||
|
: rawHourNum + 12;
|
||||||
|
const hourNum = adjHourNum % 24; // probably unnecessary now
|
||||||
const utcHour =
|
const utcHour =
|
||||||
hourNum + Math.round(new Date().getTimezoneOffset() / 60);
|
hourNum + Math.round(new Date().getTimezoneOffset() / 60);
|
||||||
const finalUtcHour = (utcHour + (utcHour < 0 ? 24 : 0)) % 24;
|
const finalUtcHour = (utcHour + (utcHour < 0 ? 24 : 0)) % 24;
|
||||||
|
console.log("utc hour:", utcHour, "final utc:", finalUtcHour);
|
||||||
|
const minuteNum = libsUtil.numberOrZero(this.minuteInput);
|
||||||
|
const utcMinute =
|
||||||
|
minuteNum + Math.round(new Date().getTimezoneOffset() % 60);
|
||||||
|
const finalUtcMinute =
|
||||||
|
(utcMinute + (utcMinute < 0 ? 60 : 0)) % 60;
|
||||||
|
|
||||||
const subscriptionWithTime: PushSubscriptionWithTime = {
|
const subscriptionWithTime: PushSubscriptionWithTime = {
|
||||||
notifyTime: { utcHour: finalUtcHour },
|
notifyTime: { utcHour: finalUtcHour, minute: finalUtcMinute },
|
||||||
|
notifyType: "DAILY_CHECK",
|
||||||
...subscription.toJSON(),
|
...subscription.toJSON(),
|
||||||
};
|
};
|
||||||
await this.sendSubscriptionToServer(subscriptionWithTime);
|
await this.sendSubscriptionToServer(subscriptionWithTime);
|
||||||
@@ -687,7 +756,7 @@ export default class App extends Vue {
|
|||||||
applicationServerKey: applicationServerKey,
|
applicationServerKey: applicationServerKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker?.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
return registration.pushManager.subscribe(options);
|
return registration.pushManager.subscribe(options);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -131,3 +131,16 @@ export async function updateAccountSettings(
|
|||||||
settings.accountDid = accountDid;
|
settings.accountDid = accountDid;
|
||||||
await updateSettings(settings);
|
await updateSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addLogMessage(message: string): Promise<void> {
|
||||||
|
await db.open();
|
||||||
|
const todayKey = new Date().toDateString();
|
||||||
|
// only keep one day's worth of logs
|
||||||
|
const previous = await db.logs.get(todayKey);
|
||||||
|
if (!previous) {
|
||||||
|
// when this is today's first log, clear out everything previous
|
||||||
|
await db.logs.clear();
|
||||||
|
}
|
||||||
|
const fullMessage = (previous && previous.message) || "";
|
||||||
|
await db.logs.update(todayKey, { message: fullMessage + "\n" + message });
|
||||||
|
}
|
||||||
|
|||||||
@@ -374,8 +374,8 @@ export const sendTestThroughPushServer = async (
|
|||||||
pushUrl = settings.webPushServer;
|
pushUrl = settings.webPushServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
|
||||||
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
||||||
|
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||||
// 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";
|
||||||
|
|
||||||
|
|||||||
@@ -595,6 +595,7 @@
|
|||||||
|
|
||||||
<div id="sectionPartnerServerURL" class="mt-2">
|
<div id="sectionPartnerServerURL" class="mt-2">
|
||||||
<span class="text-slate-500 text-sm font-bold">Partner Server URL</span>
|
<span class="text-slate-500 text-sm font-bold">Partner Server URL</span>
|
||||||
|
|
||||||
<span class="text-sm">{{ DEFAULT_PARTNER_API_SERVER }}</span>
|
<span class="text-sm">{{ DEFAULT_PARTNER_API_SERVER }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -830,7 +831,7 @@ export default class AccountViewView extends Vue {
|
|||||||
/**
|
/**
|
||||||
* Beware! I've seen where this "ready" never resolves.
|
* Beware! I've seen where this "ready" never resolves.
|
||||||
*/
|
*/
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker?.ready;
|
||||||
this.subscription = await registration.pushManager.getSubscription();
|
this.subscription = await registration.pushManager.getSubscription();
|
||||||
this.isSubscribed = !!this.subscription;
|
this.isSubscribed = !!this.subscription;
|
||||||
// console.log("Got to the end of 'mounted' call in AccountViewView.");
|
// console.log("Got to the end of 'mounted' call in AccountViewView.");
|
||||||
|
|||||||
@@ -305,8 +305,8 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker?.ready;
|
||||||
const fullSub = await registration.pushManager.getSubscription();
|
const fullSub = await registration?.pushManager.getSubscription();
|
||||||
this.subscriptionJSON = fullSub?.toJSON();
|
this.subscriptionJSON = fullSub?.toJSON();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Mount error:", error);
|
console.error("Mount error:", error);
|
||||||
@@ -366,7 +366,7 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
|
|
||||||
showTestNotification() {
|
showTestNotification() {
|
||||||
const TEST_NOTIFICATION_TITLE = "It Worked";
|
const TEST_NOTIFICATION_TITLE = "It Worked";
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker?.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
return registration.showNotification(TEST_NOTIFICATION_TITLE, {
|
return registration.showNotification(TEST_NOTIFICATION_TITLE, {
|
||||||
body: "This is your test notification.",
|
body: "This is your test notification.",
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ self.addEventListener("push", function (event) {
|
|||||||
// See https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/c1ed026662e754348a5f91542680bd4f57e5b81e/app.py#L217
|
// See https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/c1ed026662e754348a5f91542680bd4f57e5b81e/app.py#L217
|
||||||
const DAILY_UPDATE_TITLE = "DAILY_CHECK";
|
const DAILY_UPDATE_TITLE = "DAILY_CHECK";
|
||||||
|
|
||||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
|
||||||
// This is shared with the notification-test code and should be a constant. Look for the same name in HelpNotificationsView.vue
|
// This is shared with the notification-test code and should be a constant. Look for the same name in HelpNotificationsView.vue
|
||||||
// Make sure it is something other than the DAILY_UPDATE_TITLE.
|
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||||
|
// Make sure it is something different from the DAILY_UPDATE_TITLE.
|
||||||
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
||||||
|
|
||||||
let title;
|
let title;
|
||||||
@@ -133,7 +133,8 @@ self.addEventListener("notificationclick", (event) => {
|
|||||||
|
|
||||||
// This is invoked when the user chooses this as a share_target, mapped to share-target in the manifest.
|
// This is invoked when the user chooses this as a share_target, mapped to share-target in the manifest.
|
||||||
self.addEventListener("fetch", (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
logConsoleAndDb("Service worker got fetch event.", event);
|
// Skipping this because we get so many of them, at startup and other times.
|
||||||
|
//logConsoleAndDb("Service worker got fetch event.", event);
|
||||||
|
|
||||||
// Bypass any regular requests not related to Web Share Target
|
// Bypass any regular requests not related to Web Share Target
|
||||||
// and also requests that are not exactly to the timesafari.app
|
// and also requests that are not exactly to the timesafari.app
|
||||||
|
|||||||
Reference in New Issue
Block a user