103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
export const usePush = () => {
|
|
const isSupported = ref(false)
|
|
const subscription = ref<PushSubscription | null>(null)
|
|
|
|
const checkSupport = () => {
|
|
isSupported.value = 'serviceWorker' in navigator && 'PushManager' in window
|
|
}
|
|
|
|
const getRegistration = async (): Promise<ServiceWorkerRegistration | null> => {
|
|
if (!('serviceWorker' in navigator)) return null
|
|
return navigator.serviceWorker.ready
|
|
}
|
|
|
|
const subscribe = async (): Promise<boolean> => {
|
|
const registration = await getRegistration()
|
|
if (!registration) return false
|
|
|
|
try {
|
|
const existing = await registration.pushManager.getSubscription()
|
|
if (existing) {
|
|
subscription.value = existing
|
|
await sendSubscriptionToServer(existing)
|
|
return true
|
|
}
|
|
|
|
// In production, get VAPID public key from server
|
|
const vapidKey = await $fetch<string>('/api/push/vapid-public-key').catch(() => null)
|
|
const applicationServerKey = vapidKey
|
|
? (urlBase64ToUint8Array(vapidKey) as unknown as ArrayBuffer)
|
|
: undefined
|
|
|
|
const sub = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey
|
|
})
|
|
|
|
subscription.value = sub
|
|
await sendSubscriptionToServer(sub)
|
|
return true
|
|
} catch (err) {
|
|
console.error('Push subscription failed:', err)
|
|
return false
|
|
}
|
|
}
|
|
|
|
const sendSubscriptionToServer = async (sub: PushSubscription) => {
|
|
await $fetch('/api/push/subscribe', {
|
|
method: 'POST',
|
|
body: { subscription: sub.toJSON() }
|
|
})
|
|
}
|
|
|
|
const unsubscribe = async (): Promise<boolean> => {
|
|
const registration = await getRegistration()
|
|
if (!registration) return false
|
|
|
|
const existing = await registration.pushManager.getSubscription()
|
|
if (existing) {
|
|
await existing.unsubscribe()
|
|
subscription.value = null
|
|
}
|
|
return true
|
|
}
|
|
|
|
const urlBase64ToUint8Array = (base64String: string): Uint8Array => {
|
|
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
|
|
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
|
|
const rawData = atob(base64)
|
|
return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)))
|
|
}
|
|
|
|
const showLocalNotification = (title: string, options?: NotificationOptions) => {
|
|
if ('Notification' in window && Notification.permission === 'granted') {
|
|
navigator.serviceWorker.ready.then(reg => {
|
|
reg.showNotification(title, {
|
|
icon: '/pwa-192x192.svg',
|
|
badge: '/favicon.svg',
|
|
...options as any
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
const requestPermission = async (): Promise<boolean> => {
|
|
if (!('Notification' in window)) return false
|
|
const permission = await Notification.requestPermission()
|
|
return permission === 'granted'
|
|
}
|
|
|
|
onMounted(() => {
|
|
checkSupport()
|
|
})
|
|
|
|
return {
|
|
isSupported,
|
|
subscription,
|
|
subscribe,
|
|
unsubscribe,
|
|
requestPermission,
|
|
showLocalNotification
|
|
}
|
|
}
|