owltide/components/PushNotification.vue
Hornwitser 52dfde95d1 Tie push subscriptions to current session
If a user logs out from a device the expectation should be that device
no longer having any association with the user's account.  Any existing
push notifications should thefore be removed on server.  For this reason
tie push notifications to a session, and remove them when the session is
deleted.
2025-03-07 15:47:48 +01:00

119 lines
3.3 KiB
Vue

<template>
<section>
Notifications are: <b>{{ subscription ? "Enabled" : "Disabled" }}</b>
<br />
<button
:disabled="unsupported"
@click="onClick"
>
{{ unsupported === undefined ? "Checking for support" : null }}
{{ unsupported === true ? "Notifications are not supported :(." : null }}
{{ unsupported === false ? (subscription ? "Disable notifications" : "Enable notifications") : null }}
</button>
<details>
<summary>Debug</summary>
<pre><code>{{ JSON.stringify(subscription?.toJSON(), undefined, 4) ?? "No subscription set" }}</code></pre>
</details>
</section>
</template>
<script setup lang="ts">
function notificationUnsupported() {
return (
!("serviceWorker" in navigator)
|| !("PushManager" in window)
|| !("showNotification" in ServiceWorkerRegistration.prototype)
)
}
async function registerAndSubscribe(
vapidPublicKey: string,
onSubscribe: (subs: PushSubscription | null ) => void,
) {
try {
await navigator.serviceWorker.register("/sw.js");
await subscribe(vapidPublicKey, onSubscribe);
} catch (err) {
console.error("Failed to register service worker:", err);
}
}
async function subscribe(
vapidPublicKey: string,
onSubscribe: (subs: PushSubscription | null) => void
) {
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey,
});
console.log("Got subscription object", subscription.toJSON());
await submitSubscription(subscription);
onSubscribe(subscription);
} catch (err) {
console.error("Failed to subscribe:" , err);
}
}
async function unsubscribe(
subscription: PushSubscription,
onUnsubscribed: () => void,
) {
const body = JSON.stringify({ subscription });
await subscription.unsubscribe();
const res = await fetch("/api/unsubscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
});
const result = await res.json();
console.log("/api/unsubscribe returned", result);
onUnsubscribed();
}
async function submitSubscription(subscription: PushSubscription) {
const res = await fetch("/api/subscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ subscription }),
});
const result = await res.json();
console.log("/api/subscribe returned", result);
}
async function getSubscription(
onSubscribe: (subs: PushSubscription | null) => void,
) {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
onSubscribe(subscription);
}
}
const unsupported = ref<boolean | undefined>(undefined);
const subscription = ref<PushSubscription | null>(null);
const runtimeConfig = useRuntimeConfig();
const { refresh: refreshSession } = useAccountSession();
async function onClick() {
if (!subscription.value)
await registerAndSubscribe(runtimeConfig.public.vapidPublicKey, (subs) => { subscription.value = subs })
else
await unsubscribe(subscription.value, () => { subscription.value = null })
refreshSession();
}
onMounted(() => {
unsupported.value = notificationUnsupported()
if (unsupported.value) {
return;
}
getSubscription(subs => { subscription.value = subs });
})
</script>