diff --git a/components/serviceworker.js b/components/serviceworker.js index 77edabee..218ca297 100644 --- a/components/serviceworker.js +++ b/components/serviceworker.js @@ -7,11 +7,8 @@ const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY const ServiceWorkerContext = createContext() // message types for communication between app and service worker -export const ACTION_PORT = 'ACTION_PORT' // message to exchange action channel on which service worker will send actions back to app -export const SYNC_SUBSCRIPTION = 'SYNC_SUBSCRIPTION' // trigger onPushSubscriptionChange event in service worker manually -export const RESUBSCRIBE = 'RESUBSCRIBE' // trigger resubscribing to push notifications (sw -> app) -export const DELETE_SUBSCRIPTION = 'DELETE_SUBSCRIPTION' // delete subscription in IndexedDB (app -> sw) -export const STORE_SUBSCRIPTION = 'STORE_SUBSCRIPTION' // store subscription in IndexedDB (app -> sw) +export const DELETE_SUBSCRIPTION = 'DELETE_SUBSCRIPTION' +export const STORE_SUBSCRIPTION = 'STORE_SUBSCRIPTION' export const ServiceWorkerProvider = ({ children }) => { const [registration, setRegistration] = useState(null) @@ -35,12 +32,12 @@ export const ServiceWorkerProvider = ({ children }) => { `) const [deletePushSubscription] = useMutation( gql` - mutation deletePushSubscription($endpoint: String!) { - deletePushSubscription(endpoint: $endpoint) { - id - } + mutation deletePushSubscription($endpoint: String!) { + deletePushSubscription(endpoint: $endpoint) { + id } - `) + } + `) // I am not entirely sure if this is needed since at least in Brave, // using `registration.pushManager.subscribe` also prompts the user. @@ -94,8 +91,6 @@ export const ServiceWorkerProvider = ({ children }) => { await subscription.unsubscribe() const { endpoint } = subscription await deletePushSubscription({ variables: { endpoint } }) - // also delete push subscription in IndexedDB so we can tell if the user disabled push subscriptions - // or we lost the push subscription due to a bug navigator.serviceWorker.controller.postMessage({ action: DELETE_SUBSCRIPTION }) } @@ -130,23 +125,6 @@ export const ServiceWorkerProvider = ({ children }) => { }) }, []) - useEffect(() => { - // wait until successful registration - if (!registration) return - // setup channel between app and service worker - const channel = new MessageChannel() - navigator?.serviceWorker?.controller?.postMessage({ action: ACTION_PORT }, [channel.port2]) - channel.port1.onmessage = (event) => { - if (event.data.action === RESUBSCRIBE && permission.notification === 'granted') { - return subscribeToPushNotifications() - } - } - // since (a lot of) browsers don't support the pushsubscriptionchange event, - // we sync with server manually by checking on every page reload if the push subscription changed. - // see https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f - navigator?.serviceWorker?.controller?.postMessage?.({ action: SYNC_SUBSCRIPTION }) - }, [registration, permission.notification]) - const contextValue = useMemo(() => ({ registration, support, diff --git a/sw/eventListener.js b/sw/eventListener.js index c5a79f6c..272edc44 100644 --- a/sw/eventListener.js +++ b/sw/eventListener.js @@ -1,15 +1,11 @@ import ServiceWorkerStorage from 'serviceworker-storage' import { numWithUnits } from '@/lib/format' import { CLEAR_NOTIFICATIONS, clearAppBadge, setAppBadge } from '@/lib/badge' -import { ACTION_PORT, DELETE_SUBSCRIPTION, STORE_SUBSCRIPTION, SYNC_SUBSCRIPTION } from '@/components/serviceworker' +import { DELETE_SUBSCRIPTION, STORE_SUBSCRIPTION } from '@/components/serviceworker' -// we store existing push subscriptions and OS to keep them in sync with server +// we store existing push subscriptions for the onpushsubscriptionchange event const storage = new ServiceWorkerStorage('sw:storage', 1) -// for communication between app and service worker -// see https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel -let actionChannelPort - // current push notification count for badge purposes let activeCount = 0 @@ -130,28 +126,14 @@ export function onNotificationClick (sw) { export function onPushSubscriptionChange (sw) { // https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f - // `isSync` is passed if function was called because of 'SYNC_SUBSCRIPTION' event - // this makes sure we can differentiate between 'pushsubscriptionchange' events and our custom 'SYNC_SUBSCRIPTION' event - return async (event, isSync) => { + return async (event) => { let { oldSubscription, newSubscription } = event // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/pushsubscriptionchange_event // fallbacks since browser may not set oldSubscription and newSubscription oldSubscription ??= await storage.getItem('subscription') newSubscription ??= await sw.registration.pushManager.getSubscription() - if (!newSubscription) { - if (isSync && oldSubscription?.swVersion === 2) { - // service worker lost the push subscription somehow, we assume this is a bug -> resubscribe - // see https://github.com/stackernews/stacker.news/issues/411#issuecomment-1790675861 - // NOTE: this is only run on IndexedDB subscriptions stored under service worker version 2 since this is not backwards compatible - // see discussion in https://github.com/stackernews/stacker.news/pull/597 - actionChannelPort?.postMessage({ action: 'RESUBSCRIBE' }) - return - } - // no subscription exists at the moment - return - } - if (oldSubscription?.endpoint === newSubscription.endpoint) { - // subscription did not change. no need to sync with server + if (!newSubscription || oldSubscription?.endpoint === newSubscription.endpoint) { + // no subscription exists at the moment or subscription did not change return } // convert keys from ArrayBuffer to string @@ -182,16 +164,9 @@ export function onPushSubscriptionChange (sw) { export function onMessage (sw) { return async (event) => { - if (event.data.action === ACTION_PORT) { - actionChannelPort = event.ports[0] - return - } if (event.data.action === STORE_SUBSCRIPTION) { return event.waitUntil(storage.setItem('subscription', { ...event.data.subscription, swVersion: 2 })) } - if (event.data.action === SYNC_SUBSCRIPTION) { - return event.waitUntil(onPushSubscriptionChange(sw)(event, true)) - } if (event.data.action === DELETE_SUBSCRIPTION) { return event.waitUntil(storage.removeItem('subscription')) }