Simplify web push UX and updates

- Use a single endpoint
- Use a declarative web push sync hook. This thus handles all edge cases
  that had to be manually handled before: logout, login, account sync,
  etc.
- Simplify UX: browser notifications are always enabled (unless denied),
  web push toggle only shows up if permissions are already granted.
This commit is contained in:
nimbleghost 2023-06-02 13:22:54 +02:00
parent 4944e3ae4b
commit 47ad024ec7
20 changed files with 294 additions and 427 deletions

View file

@ -1,16 +1,40 @@
import { useState, useEffect } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import notifier from "./Notifier";
import subscriptionManager from "./SubscriptionManager";
const onMessage = () => {
notifier.playSound();
export const useWebPushUpdateWorker = () => {
const topics = useLiveQuery(() => subscriptionManager.webPushTopics());
const [lastTopics, setLastTopics] = useState();
useEffect(() => {
if (!notifier.pushPossible() || JSON.stringify(topics) === JSON.stringify(lastTopics)) {
return;
}
(async () => {
try {
console.log("[useWebPushUpdateWorker] Refreshing web push subscriptions");
await subscriptionManager.refreshWebPushSubscriptions(topics);
setLastTopics(topics);
} catch (e) {
console.error("[useWebPushUpdateWorker] Error refreshing web push subscriptions", e);
}
})();
}, [topics, lastTopics]);
};
const delayMillis = 2000; // 2 seconds
const intervalMillis = 300000; // 5 minutes
const intervalMillis = 5 * 60 * 1_000; // 5 minutes
const updateIntervalMillis = 60 * 60 * 1_000; // 1 hour
class WebPushWorker {
class WebPushRefreshWorker {
constructor() {
this.timer = null;
this.lastUpdate = null;
this.messageHandler = this.onMessage.bind(this);
this.visibilityHandler = this.onVisibilityChange.bind(this);
}
startWorker() {
@ -19,28 +43,42 @@ class WebPushWorker {
}
this.timer = setInterval(() => this.updateSubscriptions(), intervalMillis);
setTimeout(() => this.updateSubscriptions(), delayMillis);
this.broadcastChannel = new BroadcastChannel("web-push-broadcast");
this.broadcastChannel.addEventListener("message", onMessage);
this.broadcastChannel.addEventListener("message", this.messageHandler);
document.addEventListener("visibilitychange", this.visibilityHandler);
}
stopWorker() {
clearTimeout(this.timer);
this.broadcastChannel.removeEventListener("message", onMessage);
this.broadcastChannel.removeEventListener("message", this.messageHandler);
this.broadcastChannel.close();
document.removeEventListener("visibilitychange", this.visibilityHandler);
}
onMessage() {
notifier.playSound();
}
onVisibilityChange() {
if (document.visibilityState === "visible") {
this.updateSubscriptions();
}
}
async updateSubscriptions() {
try {
console.log("[WebPushBroadcastListener] Refreshing web push subscriptions");
if (!notifier.pushPossible()) {
return;
}
if (!this.lastUpdate || Date.now() - this.lastUpdate > updateIntervalMillis) {
await subscriptionManager.refreshWebPushSubscriptions();
} catch (e) {
console.error("[WebPushBroadcastListener] Error refreshing web push subscriptions", e);
this.lastUpdate = Date.now();
}
}
}
export default new WebPushWorker();
export const webPushRefreshWorker = new WebPushRefreshWorker();