Refactor push subscription logic into a composable
This commit is contained in:
parent
52dfde95d1
commit
b2a5b67096
3 changed files with 128 additions and 97 deletions
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
Notifications are: <b>{{ subscription ? "Enabled" : "Disabled" }}</b>
|
Notifications are: <b>{{ subscribed ? "Enabled" : "Disabled" }}</b>
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
:disabled="unsupported"
|
:disabled="!supported"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
{{ unsupported === undefined ? "Checking for support" : null }}
|
{{ supported === undefined ? "Checking for support" : null }}
|
||||||
{{ unsupported === true ? "Notifications are not supported :(." : null }}
|
{{ supported === false ? "Notifications are not supported :(." : null }}
|
||||||
{{ unsupported === false ? (subscription ? "Disable notifications" : "Enable notifications") : null }}
|
{{ supported === true ? (subscribed ? "Disable notifications" : "Enable notifications") : null }}
|
||||||
</button>
|
</button>
|
||||||
<details>
|
<details>
|
||||||
<summary>Debug</summary>
|
<summary>Debug</summary>
|
||||||
|
@ -18,102 +18,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
function notificationUnsupported() {
|
const { data: session, refresh: refreshSession } = await useAccountSession();
|
||||||
return (
|
const { supported, subscription, getSubscription, subscribe, unsubscribe } = usePushNotification();
|
||||||
!("serviceWorker" in navigator)
|
const subscribed = computed(() => Boolean(subscription.value && session.value?.push))
|
||||||
|| !("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() {
|
async function onClick() {
|
||||||
if (!subscription.value)
|
if (!subscribed.value)
|
||||||
await registerAndSubscribe(runtimeConfig.public.vapidPublicKey, (subs) => { subscription.value = subs })
|
await subscribe();
|
||||||
else
|
else
|
||||||
await unsubscribe(subscription.value, () => { subscription.value = null })
|
await unsubscribe();
|
||||||
refreshSession();
|
await refreshSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
unsupported.value = notificationUnsupported()
|
getSubscription();
|
||||||
if (unsupported.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getSubscription(subs => { subscription.value = subs });
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
109
composables/push-notification.ts
Normal file
109
composables/push-notification.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
function notificationSupported() {
|
||||||
|
return (
|
||||||
|
import.meta.client
|
||||||
|
&& "serviceWorker" in navigator
|
||||||
|
&& "PushManager" in window
|
||||||
|
&& "showNotification" in ServiceWorkerRegistration.prototype
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePushNotification = () => {
|
||||||
|
const subscription = ref<PushSubscription | null>(null);
|
||||||
|
const supported = ref<boolean | undefined>(undefined);
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
function checkSupport() {
|
||||||
|
return supported.value = notificationSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postSubscription(subscription: PushSubscriptionJSON) {
|
||||||
|
const result = await $fetch("/api/subscribe", {
|
||||||
|
method: "POST",
|
||||||
|
body: { subscription },
|
||||||
|
});
|
||||||
|
console.log("/api/subscribe returned", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postUnsubscription() {
|
||||||
|
const result = await $fetch("/api/unsubscribe", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
console.log("/api/unsubscribe returned", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function subscribe() {
|
||||||
|
if (!checkSupport())
|
||||||
|
return;
|
||||||
|
|
||||||
|
let registration;
|
||||||
|
try {
|
||||||
|
registration = await navigator.serviceWorker.register("/sw.js");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to register service worker:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we already have a subscription.
|
||||||
|
if (!subscription.value) {
|
||||||
|
subscription.value = await registration.pushManager.getSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new push subscription if none exists.
|
||||||
|
if (!subscription.value) {
|
||||||
|
try {
|
||||||
|
subscription.value = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: runtimeConfig.public.vapidPublicKey,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to subscribe:" , err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Subscribing with", subscription.value);
|
||||||
|
|
||||||
|
// Tell server about the new subscription.
|
||||||
|
try {
|
||||||
|
await postSubscription(subscription.value.toJSON());
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Failed to post subscription to server", err)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unsubscribe() {
|
||||||
|
if (!checkSupport())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Fetch subscription if it hasn't already been fetched.
|
||||||
|
if (!subscription.value) {
|
||||||
|
await getSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subscription.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await subscription.value.unsubscribe();
|
||||||
|
subscription.value = null;
|
||||||
|
await postUnsubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSubscription() {
|
||||||
|
if (!checkSupport())
|
||||||
|
return;
|
||||||
|
const registration = await navigator.serviceWorker.getRegistration("./sw.js");
|
||||||
|
if (!registration)
|
||||||
|
return;
|
||||||
|
return subscription.value = await registration.pushManager.getSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
supported,
|
||||||
|
subscription,
|
||||||
|
getSubscription,
|
||||||
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { data: session, refresh: sessionRefresh } = useAccountSession();
|
const { data: session, refresh: sessionRefresh } = useAccountSession();
|
||||||
|
const { getSubscription, subscribe } = usePushNotification();
|
||||||
|
|
||||||
const name = ref("");
|
const name = ref("");
|
||||||
const result = ref("")
|
const result = ref("")
|
||||||
|
@ -22,6 +23,11 @@ async function logIn() {
|
||||||
body: { name: name.value },
|
body: { name: name.value },
|
||||||
});
|
});
|
||||||
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
||||||
|
// Resubscribe push notifications if the user was subscribed before.
|
||||||
|
const subscription = await getSubscription();
|
||||||
|
if (subscription) {
|
||||||
|
await subscribe();
|
||||||
|
}
|
||||||
await sessionRefresh();
|
await sessionRefresh();
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue