Only close notifications manually on iOS (#729)

* Only close notifications manually on iOS

* Use function instead of hardcoded string

---------

Co-authored-by: ekzyis <ek@stacker.news>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
ekzyis 2024-01-03 18:56:29 +01:00 committed by GitHub
parent ade35b9ea0
commit 31cef5a7b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 6 deletions

View File

@ -11,7 +11,7 @@ const generateFancyName = () => {
return `${adj}-${noun}-${id}`
}
function detectOS () {
export function detectOS () {
if (!window.navigator) return ''
const userAgent = window.navigator.userAgent

View File

@ -1,12 +1,15 @@
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
import { Workbox } from 'workbox-window'
import { gql, useMutation } from '@apollo/client'
import { useLogger } from './logger'
import { detectOS, useLogger } from './logger'
const applicationServerKey = process.env.NEXT_PUBLIC_VAPID_PUBKEY
const ServiceWorkerContext = createContext()
// message types for communication between app and service worker
export const STORE_OS = 'STORE_OS'
export const ServiceWorkerProvider = ({ children }) => {
const [registration, setRegistration] = useState(null)
const [support, setSupport] = useState({ serviceWorker: undefined, pushManager: undefined })
@ -151,6 +154,7 @@ export const ServiceWorkerProvider = ({ children }) => {
// see https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f
navigator?.serviceWorker?.controller?.postMessage?.({ action: 'SYNC_SUBSCRIPTION' })
logger.info('sent SYNC_SUBSCRIPTION to service worker')
navigator?.serviceWorker?.controller?.postMessage?.({ action: STORE_OS, os: detectOS() })
}, [registration])
const contextValue = useMemo(() => ({

View File

@ -1,6 +1,7 @@
import ServiceWorkerStorage from 'serviceworker-storage'
import { numWithUnits } from '../lib/format'
import { CLEAR_NOTIFICATIONS, clearAppBadge, setAppBadge } from '../lib/badge'
import { STORE_OS } from '../components/serviceworker'
// we store existing push subscriptions to keep them in sync with server
const storage = new ServiceWorkerStorage('sw:storage', 1)
@ -10,6 +11,10 @@ const storage = new ServiceWorkerStorage('sw:storage', 1)
let messageChannelPort
let actionChannelPort
// operating system. the value will be received via a STORE_OS message from app since service workers don't have access to window.navigator
let os = ''
const iOS = () => os === 'iOS'
// current push notification count for badge purposes
let activeCount = 0
@ -47,8 +52,16 @@ export function onPush (sw) {
// we therefore close them manually and then we display the notification.
log(`[sw:push] ${nid} - ${tag} notifications replace previous notifications`)
setAppBadge(sw, ++activeCount)
log(`[sw:push] ${nid} - closing existing notifications`)
filtered.forEach(n => n.close())
// due to missing proper tag support in Safari on iOS, we can't rely on the tag property to replace notifications.
// see https://bugs.webkit.org/show_bug.cgi?id=258922 for more information
// we therefore fetch all notifications with the same tag (+ manual filter),
// close them and then we display the notification.
const notifications = await sw.registration.getNotifications({ tag })
// we only close notifications manually on iOS because we don't want to degrade android UX just because iOS is behind in their support.
if (iOS()) {
log(`[sw:push] ${nid} - closing existing notifications`)
notifications.filter(({ tag: nTag }) => nTag === tag).forEach(n => n.close())
}
log(`[sw:push] ${nid} - show notification: ${payload.title} ${JSON.stringify(payload.options)}`)
return await sw.registration.showNotification(payload.title, payload.options)
}
@ -144,8 +157,11 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
log(`[sw:push] ${nid} - calculated title: ${title}`)
// close all current notifications before showing new one to "merge" notifications
log(`[sw:push] ${nid} - closing existing notifications`)
currentNotifications.forEach(n => n.close())
// we only do this on iOS because we don't want to degrade android UX just because iOS is behind in their support.
if (iOS()) {
log(`[sw:push] ${nid} - closing existing notifications`)
currentNotifications.forEach(n => n.close())
}
const options = { icon: payload.options?.icon, tag, data: { url: '/notifications', ...mergedPayload } }
log(`[sw:push] ${nid} - show notification: ${title} ${JSON.stringify(options)}`)
@ -231,6 +247,10 @@ export function onMessage (sw) {
actionChannelPort = event.ports[0]
return
}
if (event.data.action === STORE_OS) {
os = event.data.os
return
}
if (event.data.action === 'MESSAGE_PORT') {
messageChannelPort = event.ports[0]
}