import type { mastodon } from 'masto' import type { CreatePushNotification, PushNotificationPolicy, PushNotificationRequest, SubscriptionResult, } from '~/composables/push-notifications/types' import { STORAGE_KEY_NOTIFICATION, STORAGE_KEY_NOTIFICATION_POLICY } from '~/constants' const supportsPushNotifications = typeof window !== 'undefined' && 'serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype export function usePushManager() { const { client } = useMasto() const isSubscribed = ref(false) const notificationPermission = ref( Notification.permission === 'denied' ? 'denied' : Notification.permission === 'granted' ? 'granted' : Notification.permission === 'default' ? 'prompt' : undefined, ) const isSupported = computed(() => supportsPushNotifications) const hiddenNotification = useLocalStorage(STORAGE_KEY_NOTIFICATION, {}) const configuredPolicy = useLocalStorage(STORAGE_KEY_NOTIFICATION_POLICY, {}) const pushNotificationData = ref(createRawSettings( currentUser.value?.pushSubscription, configuredPolicy.value[currentUser.value?.account?.acct ?? ''], )) const oldPushNotificationData = ref(createRawSettings( currentUser.value?.pushSubscription, configuredPolicy.value[currentUser.value?.account?.acct ?? ''], )) const saveEnabled = computed(() => { const current = pushNotificationData.value const previous = oldPushNotificationData.value return current.favourite !== previous.favourite || current.reblog !== previous.reblog || current.mention !== previous.mention || current.follow !== previous.follow || current.poll !== previous.poll || current.policy !== previous.policy }) watch(() => currentUser.value?.pushSubscription, (subscription) => { isSubscribed.value = !!subscription pushNotificationData.value = createRawSettings( subscription, configuredPolicy.value[currentUser.value?.account?.acct ?? ''], ) oldPushNotificationData.value = createRawSettings( subscription, configuredPolicy.value[currentUser.value?.account?.acct ?? ''], ) }, { immediate: true, flush: 'post' }) const subscribe = async ( notificationData?: CreatePushNotification, policy?: mastodon.v1.WebPushSubscriptionPolicy, force?: boolean, ): Promise => { if (!isSupported) return 'not-supported' if (!currentUser.value) return 'no-user' const { pushSubscription, server, token, vapidKey, account: { acct } } = currentUser.value if (!token || !server || !vapidKey) return 'invalid-vapid-key' // always request permission, browsers should remember user decision const permission = await Promise.resolve(Notification.requestPermission()).then((p) => { return p === 'default' ? 'prompt' : p }) if (permission === 'denied') { notificationPermission.value = permission return 'notification-denied' } currentUser.value.pushSubscription = await createPushSubscription( { pushSubscription, server, token, vapidKey, }, notificationData ?? { alerts: { follow: true, favourite: true, reblog: true, mention: true, poll: true, }, }, policy ?? 'all', force, ) await nextTick() notificationPermission.value = permission hiddenNotification.value[acct] = true return 'subscribed' } const unsubscribe = async () => { if (!isSupported || !isSubscribed || !currentUser.value) return false await removePushNotifications(currentUser.value) await removePushNotificationData(currentUser.value) } const saveSettings = async (policy?: mastodon.v1.WebPushSubscriptionPolicy) => { if (policy) pushNotificationData.value.policy = policy const current = pushNotificationData.value oldPushNotificationData.value = { favourite: current.favourite, reblog: current.reblog, mention: current.mention, follow: current.follow, poll: current.poll, policy: current.policy, } if (policy) configuredPolicy.value[currentUser.value!.account.acct ?? ''] = policy else configuredPolicy.value[currentUser.value!.account.acct ?? ''] = pushNotificationData.value.policy await nextTick() } const undoChanges = () => { const previous = oldPushNotificationData.value pushNotificationData.value = { favourite: previous.favourite, reblog: previous.reblog, mention: previous.mention, follow: previous.follow, poll: previous.poll, policy: previous.policy, } configuredPolicy.value[currentUser.value!.account.acct ?? ''] = previous.policy } const updateSubscription = async () => { if (currentUser.value) { const previous = oldPushNotificationData.value // const previous = history.value[0].snapshot const data = { alerts: { follow: pushNotificationData.value.follow, favourite: pushNotificationData.value.favourite, reblog: pushNotificationData.value.reblog, mention: pushNotificationData.value.mention, poll: pushNotificationData.value.poll, }, } const policy = pushNotificationData.value.policy const policyChanged = previous.policy !== policy // to change policy we need to resubscribe if (policyChanged) await subscribe(data, policy, true) else currentUser.value.pushSubscription = await client.value.v1.push.subscription.update({ data }) policyChanged && await nextTick() // force change policy when changed: watch is resetting it on push subscription update await saveSettings(policyChanged ? policy : undefined) } } return { pushNotificationData, saveEnabled, undoChanges, hiddenNotification, isSupported, isSubscribed, notificationPermission, updateSubscription, subscribe, unsubscribe, } } function createRawSettings( pushSubscription?: mastodon.v1.WebPushSubscription, subscriptionPolicy?: mastodon.v1.WebPushSubscriptionPolicy, ) { return { follow: pushSubscription?.alerts.follow ?? true, favourite: pushSubscription?.alerts.favourite ?? true, reblog: pushSubscription?.alerts.reblog ?? true, mention: pushSubscription?.alerts.mention ?? true, poll: pushSubscription?.alerts.poll ?? true, policy: subscriptionPolicy ?? 'all', } }