Bug 258922 - Push notifications with same tag do not replace each other
Summary: Push notifications with same tag do not replace each other
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Service Workers (show other bugs)
Version: Safari 16
Hardware: Unspecified iOS 16
: P2 Normal
Assignee: Brady Eidson
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2023-07-06 05:54 PDT by ekzyis
Modified: 2024-01-11 00:56 PST (History)
6 users (show)

See Also:


Attachments
notifications with same tag do not replace each other (6.11 MB, image/png)
2023-07-06 06:02 PDT, ekzyis
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description ekzyis 2023-07-06 05:54:48 PDT
Hello!

I asked a question on Stack Overflow about this since I wasn't sure if I am doing something wrong. Someone replied that this may indeed be a bug and I should file a bug report with Apple or you. Since I didn't find another bug report about this on here, I am filing this one now. I didn't file a bug report with Apple since I have no Apple developer license. It looks like I need one or an Apple device before I can file a bug. [0]

What follows is the body of the question [1] + some changes to respect the Bug Writing Guidelines

---

**Overview**

We implemented push notifications for a PWA using this guide: https://web.dev/notifications/

We are using the `tag` property to group notifications together. For example, if there is one reply, the user will be shown "you have a new reply". But if there is another, it should show "you have 2 new replies" and replace the previous one.

However, this works on Android but not on iOS 16.4 with Safari. Notifications always show up immediately and do not replace each other even though they have the same exact tag.

This is the relevant code in our service worker:

```javascript
self.addEventListener('push', async function (event) {
  const payload = event.data?.json()
  if (!payload) return
  const { tag } = payload.options
  event.waitUntil((async () => {
    if (!['REPLY', 'MENTION'].includes(tag)) {
      // notifications with REPLY and MENTION as tags need updated title.
      // other notifications can just replace the old one
      return self.registration.showNotification(payload.title, payload.options)
    }

    const notifications = await self.registration.getNotifications({ tag })
    // since we used a tag filter, there should only be zero or one notification
    if (notifications.length > 1) {
      console.error(`more than one notification with tag ${tag} found`)
      return null
    }
    if (notifications.length === 0) {
      return self.registration.showNotification(payload.title, payload.options)
    }
    const currentNotification = notifications[0]
    const amount = currentNotification.data?.amount ? currentNotification.data.amount + 1 : 2
    let title = ''
    if (tag === 'REPLY') {
      title = `You have ${amount} new replies`
    } else if (tag === 'MENTION') {
      title = `You were mentioned ${amount} times`
    }
    currentNotification.close()
    const { icon } = currentNotification
    return self.registration.showNotification(title, { icon, tag, data: { url: '/notifications', amount } })
  })())
})
```

A notification that we send to the push service may look like this:

```json
{
  "title": "you have a new reply",
  "options": {
    "body": <text of reply>,
    "timestamp": <unix timestamp>,
    "icon": "/android-chrome-96x96.png",
    "tag": "REPLY",
    "data": { "url": "/items/124101" },
  }
}
```

This works on Android but for some reason does not on iOS 16.4 with Safari. Notifications never get replaced. So if a user gets 3 replies, they get three "you have a new reply" notification. I checked the browser compatibility for various related APIs but I couldn't find a reason why it shouldn't work (see reference).

**Steps to Reproduce**

Since I don't have access to an Apple device running iOS 16.4, I can't easily provide steps for reproduction at the moment. However, I think the following minimal service worker code should already show the bug:

```javascript
self.addEventListener('push', async function(event) {
  eventWaitUntil(self.registration.showNotification("tag test", { "tag": "TAG" })
}
```

This code above should always show a notification with the title "tag test" if a push message was received. If a new push message is received, it should replace the previous one.

**Actual Results**

Notifications with same tag show up individually

**Expected Results**

Notifications with same tag automatically replace each other according to the specs [2]

**Build Date & Hardware**

Build ??? on iOS 16.4

**Additional information**

I have looked through Mozilla MDN to check for browser compatibility for relevant APIs [3-7]. I haven't found a reason why this shouldn't work on iOS 16.4.

Since support for these features was only added recently (in 16.4 which shipped on 2023-03-27), I didn't find useful resources on the web about this. Mostly about people asking when support will come.

Since I have no access to a Apple device, I can not debug this properly.

We would be very happy if someone can help us with this.

---

Reference:

[0] https://developer.apple.com/bug-reporting/
[1] https://stackoverflow.com/q/76625248/13555687
[2] https://notifications.spec.whatwg.org/#show-steps
[3] https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification#browser_compatibility
[4] https://developer.mozilla.org/en-US/docs/Web/API/Notification/tag#browser_compatibility
[5] https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/getNotifications#browser_compatibility
[6] https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification#browser_compatibility
[7] https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API#browser_compatibility
Comment 1 ekzyis 2023-07-06 06:02:17 PDT
Created attachment 466943 [details]
notifications with same tag do not replace each other
Comment 2 Radar WebKit Bug Importer 2023-07-06 11:10:00 PDT
<rdar://problem/111859983>
Comment 3 Ben Nham 2023-07-06 15:21:43 PDT
We don't support tag yet, but we can use this bug to track the feature request. We might dup this to an existing bug tracking the same work (which I think exists, but I can't find it right now).
Comment 4 Ben Nham 2023-07-06 15:37:42 PDT
To be clear: the tag attribute on Notification is exposed. You can set it via the Notification constructor/ServiceWorkerRegistration.showNotification and access its value later on. But the browser currently doesn't use the property to coalesce notifications. That will require additional work.
Comment 5 ekzyis 2023-07-06 15:51:23 PDT
Ah, I see. Thanks for your fast reply and clarification!

I created a ticket for MDN to update their support declaration until this feature is supported: https://github.com/mdn/browser-compat-data/issues/20305

Any ETA on when this will be supported?
Comment 6 Mark Rigby-Jones 2023-08-22 10:05:41 PDT
An additional data point: this appears to be partially working on macOS Safari. While a second notification with the same tag appears as a new entry in the system notification center, it does replace an existing one in the array returned by ServiceWorkerRegistration.getNotifications(), and a notificationclose event is fired for the old/replaced notification. (For comparison, macOS Chrome replaces the notification in both places, and does not fire a notificationclose event for the replaced notification.)

On iOS Safari, ServiceWorkerRegistration.getNotifications() appears to always return an empty array, so there's no way to compare that behaviour.
Comment 7 Thomas 2023-11-23 01:03:47 PST
Do we have an estimate of when this will be supported?
Comment 8 ekzyis 2024-01-09 17:05:35 PST
Hey, I finally managed to get access to an iPhone running iOS 17.1 so I could debug this on Safari now.

I have noticed following things:

1. Notification.close() doesn't work. It seems to do nothing even though MDN says it's fully supported on Safari since 16.4. MDN only says that the 'close' event is not supported but that should be independent, no? [0, 1]

2. Contrary to this comment [2], ServiceWorkerRegistration.getNotifications() does return an array. It seems to correctly return the notifications that are currently displayed to the user. If a notification is swiped away, the next call to getNotifications() reflects that.

3. There is a 'onPush' race condition. If push notifications are received too fast, some calls receive a stale value from getNotifications(). For example, if you send 5 push notifications, the first two might see an empty array returned from getNotifications, while the next 3 push notifications receive an array of size 2. This is not the case on Android.

A detailed analysis can be found here: https://github.com/stackernews/stacker.news/issues/411#issuecomment-1882137442

Currently, we would be happy if we at least could close notifications in some way and if this is reflected by getNotifications(). Is that possible somehow?

[0] https://developer.mozilla.org/en-US/docs/Web/API/Notification/close
[1] https://developer.mozilla.org/en-US/docs/Web/API/Notification/close_event
[2] https://bugs.webkit.org/show_bug.cgi?id=258922#c6
Comment 9 ik 2024-01-11 00:56:36 PST
Regarding c#8 2): getNotifications() always returned an empty array until a later 16.x release. It has since been fixed and now returns the correct number of notifications - except if you try to replace a notification with `tag`, then you end up with two notifications but getNotifications() returns 1.

The current implementation of the Web Push / notification API in WebKit is incomplete but DOES pass tests, so sites that do proper feature detection will end up with unexpected behaviour. Also, web developers will expect these things to work as MDN indicates they are supported (again, because the tests pass).

Please prioritize fixing these problems. Web Push on iOS has been "implemented but incomplete" for almost a year now (feb 2023).