feat: enhance push notification handling and service worker registration with improved error handling and subscription management
This commit is contained in:
parent
452c1a4209
commit
2efc13643b
38
public/sw.js
38
public/sw.js
@ -1,26 +1,14 @@
|
||||
// DST Collection — Service Worker
|
||||
// Push Notification Handler
|
||||
|
||||
const CACHE_NAME = 'dst-v1';
|
||||
// Push Notification + Install/Activate Handler
|
||||
|
||||
// ── Install ──────────────────────────────────────────────────────────────────
|
||||
self.addEventListener('install', () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
// ── Activate ─────────────────────────────────────────────────────────────────
|
||||
// ── Activate ──────────────────────────────────────────────────────────────────
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map((k) => caches.delete(k)));
|
||||
|
||||
await self.registration.unregister();
|
||||
|
||||
const clientList = await self.clients.matchAll({ type: 'window' });
|
||||
clientList.forEach((client) => client.navigate(client.url));
|
||||
})(),
|
||||
);
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
// ── Push Notification ─────────────────────────────────────────────────────────
|
||||
@ -30,19 +18,18 @@ self.addEventListener('push', (event) => {
|
||||
let data = {};
|
||||
try {
|
||||
data = event.data.json();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
data = { title: 'DST Collection', body: event.data.text() };
|
||||
}
|
||||
|
||||
const title = data.title || 'DST Collection';
|
||||
const title = data.title ?? 'DST Collection';
|
||||
const options = {
|
||||
body: data.body || '',
|
||||
icon: data.icon || '/assets/pwa-192x192.png',
|
||||
body: data.body ?? '',
|
||||
icon: data.icon ?? '/assets/pwa-192x192.png',
|
||||
badge: '/assets/pwa-64x64.png',
|
||||
data: { url: data.url || '/admin/master/categories' },
|
||||
data: { url: data.url ?? '/admin/master/categories' },
|
||||
tag: 'dst-notification',
|
||||
renotify: true,
|
||||
requireInteraction: false,
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
@ -52,16 +39,17 @@ self.addEventListener('push', (event) => {
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
|
||||
const targetUrl = (event.notification.data && event.notification.data.url)
|
||||
? event.notification.data.url
|
||||
: '/admin/dashboard';
|
||||
const targetUrl =
|
||||
(event.notification.data && event.notification.data.url)
|
||||
? event.notification.data.url
|
||||
: '/admin/dashboard';
|
||||
|
||||
event.waitUntil(
|
||||
self.clients
|
||||
.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
.then((clientList) => {
|
||||
for (const client of clientList) {
|
||||
if ('navigate' in client) {
|
||||
if ('focus' in client) {
|
||||
return client.navigate(targetUrl).then((c) => c && c.focus());
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,9 +11,23 @@ function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
||||
}
|
||||
|
||||
|
||||
async function getSwRegistration(timeoutMs = 10_000): Promise<ServiceWorkerRegistration> {
|
||||
return Promise.race([
|
||||
navigator.serviceWorker.ready,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error('[SW] Service worker not ready within timeout')),
|
||||
timeoutMs,
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
export function usePushNotification() {
|
||||
const isSupported = computed(
|
||||
() =>
|
||||
typeof window !== 'undefined' &&
|
||||
'Notification' in window &&
|
||||
'serviceWorker' in navigator &&
|
||||
'PushManager' in window,
|
||||
@ -28,13 +42,12 @@ export function usePushNotification() {
|
||||
|
||||
async function checkSubscriptionStatus(): Promise<void> {
|
||||
if (!isSupported.value) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription =
|
||||
await registration.pushManager.getSubscription();
|
||||
const registration = await getSwRegistration();
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
isSubscribed.value = !!subscription;
|
||||
} catch {
|
||||
isSubscribed.value = false;
|
||||
@ -43,19 +56,19 @@ return;
|
||||
|
||||
async function subscribe(): Promise<void> {
|
||||
if (!isSupported.value || isLoading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const permission = await Notification.requestPermission();
|
||||
|
||||
if (permission !== 'granted') {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const permission = await Notification.requestPermission();
|
||||
|
||||
if (permission !== 'granted') {
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await getSwRegistration();
|
||||
const publicKey =
|
||||
vapidPublicKey ?? import.meta.env.VITE_VAPID_PUBLIC_KEY ?? '';
|
||||
|
||||
@ -94,15 +107,14 @@ return;
|
||||
|
||||
async function unsubscribe(): Promise<void> {
|
||||
if (!isSupported.value || isLoading.value) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription =
|
||||
await registration.pushManager.getSubscription();
|
||||
const registration = await getSwRegistration();
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (!subscription) {
|
||||
isSubscribed.value = false;
|
||||
|
||||
@ -128,13 +128,14 @@ return;
|
||||
|
||||
try {
|
||||
if (perm.key === 'notification') {
|
||||
const result = await Notification.requestPermission();
|
||||
await subscribe();
|
||||
|
||||
if (result === 'denied') {
|
||||
if (isSubscribed.value) {
|
||||
perm.state = 'granted';
|
||||
} else if (typeof Notification !== 'undefined' && Notification.permission === 'denied') {
|
||||
perm.state = 'denied';
|
||||
} else if (result === 'granted') {
|
||||
await subscribe();
|
||||
perm.state = isSubscribed.value ? 'granted' : 'denied';
|
||||
} else {
|
||||
perm.state = 'prompt';
|
||||
}
|
||||
} else if (perm.key === 'camera') {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
@ -305,10 +306,10 @@ onMounted(() => {
|
||||
<Button
|
||||
v-else-if="perm.state === 'prompt'"
|
||||
size="sm"
|
||||
:disabled="perm.requesting"
|
||||
:disabled="perm.requesting || (perm.key === 'notification' && isLoading)"
|
||||
@click="requestPermission(perm)"
|
||||
>
|
||||
{{ perm.requesting ? 'Meminta...' : 'Izinkan' }}
|
||||
{{ (perm.requesting || (perm.key === 'notification' && isLoading)) ? 'Meminta...' : 'Izinkan' }}
|
||||
</Button>
|
||||
|
||||
<span
|
||||
|
||||
Loading…
Reference in New Issue
Block a user