From 9142c509228f90d91a98186e679c35f5ce20a6bc Mon Sep 17 00:00:00 2001
From: Y-PLONI <7353755@gmail.com>
Date: Tue, 27 Jan 2026 13:59:13 +0200
Subject: [PATCH 1/7] Add Hebrew translation
---
public/languages/he-IL/web-push.json | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 public/languages/he-IL/web-push.json
diff --git a/public/languages/he-IL/web-push.json b/public/languages/he-IL/web-push.json
new file mode 100644
index 0000000..7d63066
--- /dev/null
+++ b/public/languages/he-IL/web-push.json
@@ -0,0 +1,11 @@
+{
+ "profile.label": "התראות דחיפה",
+ "profile.introduction": "בנוסף להתראות בתוך האפליקציה ולהתראות בדוא״ל, ניתן לבחור לקבל גם התראות דחיפה. כך תוכלו לקבל התראות גם כשהאפליקציה אינה פתוחה במכשיר.",
+ "profile.option": "הפעלת התראות דחיפה במכשיר זה",
+ "profile.devices": "כרגע נשלחות התראות ל־%1 מכשיר(ים).",
+ "profile.permissionBlocked": "המכשיר שלך אינו מאפשר כרגע לקבל התראות מאתר זה. יש לאשר את הרשאת ההתראות כדי להמשיך.",
+ "profile.send-test": "שליחת התראת בדיקה",
+
+ "toast.test_success": "התראת הבדיקה נשלחה.",
+ "toast.test_unavailable": "לא ניתן לשלוח התראת בדיקה כי התראות דחיפה אינן מופעלות במכשיר זה."
+}
From 552619b815c705563fdc557fe5ff4b8551e8fb5f Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Wed, 4 Feb 2026 13:52:14 +0200
Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=D7=A2=D7=93=D7=9B=D7=95=D7=A0?=
=?UTF-8?q?=D7=99=D7=9D=20=D7=9E=D7=99=D7=A0=D7=95=D7=A8=D7=99=D7=99=D7=9D?=
=?UTF-8?q?=20=D7=9C=D7=94=D7=92=D7=93=D7=A8=D7=95=D7=AA=20=D7=95=D7=AA?=
=?UTF-8?q?=D7=A8=D7=92=D7=95=D7=9D=20web-push?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- הוספת nodebb-plugin-web-push-1.xml ל-.gitignore כדי למנוע מעקב אחר קבצי תצורה או יומנים שנוצרו על ידי הפלאגין, מה שמסייע בשמירה על ניקיון המאגר
- תיקון רווח קטן בטקסט ההתראה בקובץ השפה העברית כדי לשפר את הקריאות והעיצוב, ללא שינוי בתוכן הפונקציונלי
---
.gitignore | 1 +
public/languages/he-IL/web-push.json | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index d77e539..f1ff4ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -216,3 +216,4 @@ pip-log.txt
sftp-config.json
node_modules/
+nodebb-plugin-web-push-1.xml
diff --git a/public/languages/he-IL/web-push.json b/public/languages/he-IL/web-push.json
index 7d63066..1a5ee43 100644
--- a/public/languages/he-IL/web-push.json
+++ b/public/languages/he-IL/web-push.json
@@ -2,7 +2,7 @@
"profile.label": "התראות דחיפה",
"profile.introduction": "בנוסף להתראות בתוך האפליקציה ולהתראות בדוא״ל, ניתן לבחור לקבל גם התראות דחיפה. כך תוכלו לקבל התראות גם כשהאפליקציה אינה פתוחה במכשיר.",
"profile.option": "הפעלת התראות דחיפה במכשיר זה",
- "profile.devices": "כרגע נשלחות התראות ל־%1 מכשיר(ים).",
+ "profile.devices": "כרגע נשלחות התראות ל־ %1 מכשיר(ים).",
"profile.permissionBlocked": "המכשיר שלך אינו מאפשר כרגע לקבל התראות מאתר זה. יש לאשר את הרשאת ההתראות כדי להמשיך.",
"profile.send-test": "שליחת התראת בדיקה",
From 98b9b72c5b2c4c25c2af79cb004b18bacc073b2f Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Mon, 27 Apr 2026 07:50:11 +0300
Subject: [PATCH 3/7] =?UTF-8?q?chore:=20=D7=A9=D7=99=D7=A0=D7=95=D7=99=20?=
=?UTF-8?q?=D7=A9=D7=9D=20=D7=AA=D7=99=D7=A7=D7=99=D7=99=D7=AA=20=D7=94?=
=?UTF-8?q?=D7=9C=D7=95=D7=A7=D7=9C=D7=99=D7=96=D7=A6=D7=99=D7=94=20=D7=9E?=
=?UTF-8?q?-he-IL=20=D7=9C-he?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- שינוי שם התיקיה public/languages/he-IL ל-public/languages/he עבור קבצי השפה העברית (ללא שינוי בתוכן)
---
public/languages/{he-IL => he}/web-push.json | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename public/languages/{he-IL => he}/web-push.json (100%)
diff --git a/public/languages/he-IL/web-push.json b/public/languages/he/web-push.json
similarity index 100%
rename from public/languages/he-IL/web-push.json
rename to public/languages/he/web-push.json
From d1d5e14460960679d683cfbb6f9c5eea76a55327 Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Fri, 1 May 2026 13:09:15 +0300
Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=D7=94=D7=95=D7=A1=D7=A4=D7=AA=20?=
=?UTF-8?q?=D7=98=D7=99=D7=A4=D7=95=D7=9C=20=D7=91=D7=94=D7=A8=D7=A9=D7=90?=
=?UTF-8?q?=D7=95=D7=AA=20=D7=94=D7=AA=D7=A8=D7=90=D7=95=D7=AA=20=D7=95?=
=?UTF-8?q?=D7=A9=D7=92=D7=99=D7=90=D7=95=D7=AA=20=D7=94=D7=A8=D7=A9=D7=9E?=
=?UTF-8?q?=D7=94=20=D7=9C=D7=A4=D7=95=D7=A9=20=D7=A0=D7=95=D7=98=D7=99?=
=?UTF-8?q?=D7=A4=D7=99=D7=A7=D7=99=D7=99=D7=A9=D7=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- הוספת בדיקת 'denied' לפני ניסיון הרשמה להתראות ב-settings.js
- טיפול ב-iOS Safari: שמירה על activation לפני בקשת הרשמה
- הוספת rollback אם הרשמה נכשלה אחרי יצירת subscription בדפדפן
- הוספת מפתחות תרגום חדשים: permission_denied ו-subscribe_failed באנגלית, עברית וסינית
---
public/languages/en-GB/web-push.json | 4 +++-
public/languages/he/web-push.json | 4 +++-
public/languages/zh-CN/web-push.json | 4 +++-
public/lib/settings.js | 33 +++++++++++++++++++++++++++-
4 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/public/languages/en-GB/web-push.json b/public/languages/en-GB/web-push.json
index 2e8f3ab..44f106c 100644
--- a/public/languages/en-GB/web-push.json
+++ b/public/languages/en-GB/web-push.json
@@ -7,5 +7,7 @@
"profile.send-test": "Send Test Notification",
"toast.test_success": "Test notification sent.",
- "toast.test_unavailable": "Cannot send test notification as push notifications are not enabled on this device."
+ "toast.test_unavailable": "Cannot send test notification as push notifications are not enabled on this device.",
+ "toast.permission_denied": "Notification permission was denied. Please enable notifications for this site in your browser settings.",
+ "toast.subscribe_failed": "Could not enable push notifications on this device. Please try again."
}
\ No newline at end of file
diff --git a/public/languages/he/web-push.json b/public/languages/he/web-push.json
index 1a5ee43..c55f550 100644
--- a/public/languages/he/web-push.json
+++ b/public/languages/he/web-push.json
@@ -7,5 +7,7 @@
"profile.send-test": "שליחת התראת בדיקה",
"toast.test_success": "התראת הבדיקה נשלחה.",
- "toast.test_unavailable": "לא ניתן לשלוח התראת בדיקה כי התראות דחיפה אינן מופעלות במכשיר זה."
+ "toast.test_unavailable": "לא ניתן לשלוח התראת בדיקה כי התראות דחיפה אינן מופעלות במכשיר זה.",
+ "toast.permission_denied": "הרשאת ההתראות נדחתה. יש לאפשר התראות עבור אתר זה בהגדרות הדפדפן.",
+ "toast.subscribe_failed": "לא ניתן להפעיל התראות דחיפה במכשיר זה. נסו שוב."
}
diff --git a/public/languages/zh-CN/web-push.json b/public/languages/zh-CN/web-push.json
index ab3abcd..be81cf3 100644
--- a/public/languages/zh-CN/web-push.json
+++ b/public/languages/zh-CN/web-push.json
@@ -7,5 +7,7 @@
"profile.send-test": "发送测试通知",
"toast.test_success": "测试通知已发送。",
- "toast.test_unavailable": "由于此设备未启用推送通知,因此无法发送测试通知。"
+ "toast.test_unavailable": "由于此设备未启用推送通知,因此无法发送测试通知。",
+ "toast.permission_denied": "通知权限已被拒绝。请在浏览器设置中允许此站点发送通知。",
+ "toast.subscribe_failed": "无法在此设备上启用推送通知。请重试。"
}
\ No newline at end of file
diff --git a/public/lib/settings.js b/public/lib/settings.js
index 5c5cee4..6daf084 100644
--- a/public/lib/settings.js
+++ b/public/lib/settings.js
@@ -33,7 +33,30 @@ export async function init() {
case 'toggle': {
const countEl = document.querySelector('#deviceCount strong');
if (!subscription) {
+ // iOS Safari is strict about user activation: subscribe() must be
+ // called from the same synchronous task as the click. We branch BEFORE
+ // any await: if permission is already granted, call subscribe() first
+ // (no awaits in between). Otherwise request permission, which itself
+ // preserves activation on Chrome but may lose it on iOS — the user can
+ // just tap again.
+ if (Notification.permission === 'denied') {
+ subselector.checked = false;
+ warning('[[web-push:toast.permission_denied]]');
+ document.getElementById('permission-warning').classList.remove('d-none');
+ break;
+ }
+
try {
+ if (Notification.permission !== 'granted') {
+ const permission = await Notification.requestPermission();
+ if (permission !== 'granted') {
+ subselector.checked = false;
+ warning('[[web-push:toast.permission_denied]]');
+ document.getElementById('permission-warning').classList.remove('d-none');
+ break;
+ }
+ }
+
subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey,
@@ -45,8 +68,16 @@ export async function init() {
let count = parseInt(countEl.textContent, 10);
count += 1;
countEl.innerText = count;
- } catch (e) {
+ } catch (err) {
+ console.error('[web-push] subscribe failed:', err);
subselector.checked = false;
+ // Roll back any browser-level subscription created before the failure.
+ const stale = await registration.pushManager.getSubscription();
+ if (stale) {
+ await stale.unsubscribe();
+ }
+ subscription = null;
+ warning('[[web-push:toast.subscribe_failed]]');
}
} else {
await subscription.unsubscribe();
From 9f1b30f767dfa7fab48428e938449900eaeaafad Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Sun, 3 May 2026 11:12:34 +0300
Subject: [PATCH 5/7] feat: add browser support detection and service worker
timeout handling
Add feature detection for Service Worker and Push API to display user-friendly error messages when unsupported. Implement 5-second timeout for service worker registration to prevent UI hangs when SW fails to register, particularly common on iOS when the PWA isn't installed to Home Screen.
---
public/languages/en-GB/web-push.json | 4 +++-
public/languages/he/web-push.json | 4 +++-
public/languages/zh-CN/web-push.json | 4 +++-
public/lib/settings.js | 26 +++++++++++++++++++++++++-
4 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/public/languages/en-GB/web-push.json b/public/languages/en-GB/web-push.json
index 44f106c..295c83d 100644
--- a/public/languages/en-GB/web-push.json
+++ b/public/languages/en-GB/web-push.json
@@ -9,5 +9,7 @@
"toast.test_success": "Test notification sent.",
"toast.test_unavailable": "Cannot send test notification as push notifications are not enabled on this device.",
"toast.permission_denied": "Notification permission was denied. Please enable notifications for this site in your browser settings.",
- "toast.subscribe_failed": "Could not enable push notifications on this device. Please try again."
+ "toast.subscribe_failed": "Could not enable push notifications on this device. Please try again.",
+ "toast.unsupported": "This browser does not support push notifications.",
+ "toast.sw_not_registered": "Push notifications are unavailable: the background service is not registered. On iOS, install this site to your Home Screen first."
}
\ No newline at end of file
diff --git a/public/languages/he/web-push.json b/public/languages/he/web-push.json
index c55f550..f82bc76 100644
--- a/public/languages/he/web-push.json
+++ b/public/languages/he/web-push.json
@@ -9,5 +9,7 @@
"toast.test_success": "התראת הבדיקה נשלחה.",
"toast.test_unavailable": "לא ניתן לשלוח התראת בדיקה כי התראות דחיפה אינן מופעלות במכשיר זה.",
"toast.permission_denied": "הרשאת ההתראות נדחתה. יש לאפשר התראות עבור אתר זה בהגדרות הדפדפן.",
- "toast.subscribe_failed": "לא ניתן להפעיל התראות דחיפה במכשיר זה. נסו שוב."
+ "toast.subscribe_failed": "לא ניתן להפעיל התראות דחיפה במכשיר זה. נסו שוב.",
+ "toast.unsupported": "הדפדפן הזה אינו תומך בהתראות דחיפה.",
+ "toast.sw_not_registered": "התראות דחיפה אינן זמינות: שירות הרקע לא נרשם. באייפון, יש להתקין את האתר תחילה למסך הבית."
}
diff --git a/public/languages/zh-CN/web-push.json b/public/languages/zh-CN/web-push.json
index be81cf3..34f098d 100644
--- a/public/languages/zh-CN/web-push.json
+++ b/public/languages/zh-CN/web-push.json
@@ -9,5 +9,7 @@
"toast.test_success": "测试通知已发送。",
"toast.test_unavailable": "由于此设备未启用推送通知,因此无法发送测试通知。",
"toast.permission_denied": "通知权限已被拒绝。请在浏览器设置中允许此站点发送通知。",
- "toast.subscribe_failed": "无法在此设备上启用推送通知。请重试。"
+ "toast.subscribe_failed": "无法在此设备上启用推送通知。请重试。",
+ "toast.unsupported": "此浏览器不支持推送通知。",
+ "toast.sw_not_registered": "推送通知不可用:后台服务未注册。在 iOS 上,请先将本站添加到主屏幕。"
}
\ No newline at end of file
diff --git a/public/lib/settings.js b/public/lib/settings.js
index 6daf084..d97f9d2 100644
--- a/public/lib/settings.js
+++ b/public/lib/settings.js
@@ -10,7 +10,31 @@ export async function init() {
console.error('Web Push form container not found');
return;
}
- const registration = await navigator.serviceWorker.ready;
+
+ if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
+ console.error('[web-push] Service workers or Push API not supported in this browser');
+ warning('[[web-push:toast.unsupported]]');
+ return;
+ }
+
+ // navigator.serviceWorker.ready never rejects — it hangs forever if no SW is registered.
+ // On iOS this is a common failure mode (PWA not installed, scope mismatch, core SW failed
+ // to register). Race it against a timeout so we surface the problem instead of hanging.
+ const registration = await Promise.race([
+ navigator.serviceWorker.ready,
+ new Promise((_, reject) => setTimeout(
+ () => reject(new Error('Service worker not ready after 5s — likely not registered')),
+ 5000
+ )),
+ ]).catch((err) => {
+ console.error('[web-push]', err);
+ warning('[[web-push:toast.sw_not_registered]]');
+ return null;
+ });
+ if (!registration) {
+ return;
+ }
+
let subscription = await registration.pushManager.getSubscription();
const convertedVapidKey = urlBase64ToUint8Array(config['web-push'].vapidKey);
From 465fe5044076dc877c53aaff99fdceea44ade10e Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Sun, 3 May 2026 11:27:17 +0300
Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=D7=94=D7=95=D7=A1=D7=A4=D7=AA=20?=
=?UTF-8?q?=D7=AA=D7=9E=D7=99=D7=9B=D7=94=20=D7=91-Service=20Worker=20?=
=?UTF-8?q?=D7=9C=D7=93=D7=A4=D7=93=D7=A0=D7=99=20Safari=20=D7=95-iOS?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- plugin.json: הוספת הגדרת scripts שמפנה לקובץ main.js כדי לטעון את הסקריפט
- public/lib/main.js: מימוש רישום Service Worker ידני לדפדני Safari ו-iOS, מאחר ש-NodeBB מדלג על רישום אוטומטי בפלטפורמות אלו וגורם להשתקת דף ההגדרות
- הוספת לוג מפורט לכל אירועי מחזור החיים של ה-Service Worker כדי שכשלונות יופיעו ב-Web Inspector במקום להיעלם בשקט
- זיהוי Safari ו-iOS דרך User-Agent ותכונות maxTouchPoints, וביצוע רישום ידני רק בפלטפורמות אלו כדי לשמור על תאימות עם דפדפנים אחרים
---
plugin.json | 3 ++
public/lib/main.js | 78 ++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/plugin.json b/plugin.json
index 85ee949..713949e 100644
--- a/plugin.json
+++ b/plugin.json
@@ -13,6 +13,9 @@
{ "hook": "filter:service-worker.scripts", "method": "registerServiceWorker" }
],
"languages": "public/languages",
+ "scripts": [
+ "public/lib/main.js"
+ ],
"modules": {
"../client/account/web-push.js": "./public/lib/settings.js",
"../admin/plugins/web-push.js": "./public/lib/admin.js"
diff --git a/public/lib/main.js b/public/lib/main.js
index d51436f..fc6e500 100644
--- a/public/lib/main.js
+++ b/public/lib/main.js
@@ -1,11 +1,83 @@
-// this file here as placeholder in case needed. Add back to plugin.json to use
-
'use strict';
+// NodeBB core skips serviceWorker.register() on Safari (see public/src/app.js).
+// That predates iOS 16.4, where Safari/PWA does support Web Push. Without an SW,
+// navigator.serviceWorker.ready hangs forever and our settings page silently fails.
+// This script registers the SW ourselves on Safari/iOS, and logs every lifecycle
+// event so failures surface in Web Inspector instead of disappearing.
+
(async () => {
const [hooks] = await app.require(['hooks']);
hooks.on('action:app.load', async () => {
- // ...
+ if (!('serviceWorker' in navigator)) {
+ console.warn('[web-push] serviceWorker not supported');
+ return;
+ }
+
+ const ua = navigator.userAgent;
+ const isSafari = /^((?!chrome|android).)*safari/i.test(ua);
+ const isIOS = /iPad|iPhone|iPod/.test(ua) ||
+ (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
+
+ if (!isSafari && !isIOS) {
+ // Core handles registration on non-Safari browsers.
+ return;
+ }
+
+ // Core already tried (and skipped) registration by this point. If something
+ // is registered, leave it alone.
+ const existing = await navigator.serviceWorker.getRegistration();
+ if (existing) {
+ console.info('[web-push] SW already registered:', existing);
+ return;
+ }
+
+ const swUrl = (config.relative_path || '') + '/service-worker.js';
+ const scope = (config.relative_path || '') + '/';
+
+ console.info('[web-push] Registering service worker (Safari/iOS fallback):', { swUrl, scope });
+
+ let registration;
+ try {
+ registration = await navigator.serviceWorker.register(swUrl, { scope });
+ console.info('[web-push] register() resolved:', registration);
+ } catch (err) {
+ console.error('[web-push] register() FAILED:', err);
+ console.error('[web-push] Failure name:', err && err.name);
+ console.error('[web-push] Failure message:', err && err.message);
+ return;
+ }
+
+ const logState = (worker, label) => {
+ if (!worker) return;
+ console.info(`[web-push] ${label} initial state:`, worker.state);
+ worker.addEventListener('statechange', () => {
+ console.info(`[web-push] ${label} state →`, worker.state);
+ });
+ worker.addEventListener('error', (e) => {
+ console.error(`[web-push] ${label} error event:`, e);
+ });
+ };
+
+ logState(registration.installing, 'installing');
+ logState(registration.waiting, 'waiting');
+ logState(registration.active, 'active');
+
+ registration.addEventListener('updatefound', () => {
+ console.info('[web-push] updatefound — new worker installing');
+ logState(registration.installing, 'installing(updatefound)');
+ });
+
+ navigator.serviceWorker.addEventListener('error', (e) => {
+ console.error('[web-push] navigator.serviceWorker error:', e);
+ });
+
+ try {
+ const ready = await navigator.serviceWorker.ready;
+ console.info('[web-push] serviceWorker.ready resolved:', ready);
+ } catch (err) {
+ console.error('[web-push] serviceWorker.ready rejected:', err);
+ }
});
})();
From b037aa96e92bf56c834b5787710ff51286e7b26b Mon Sep 17 00:00:00 2001
From: ClickAndGoScript
Date: Sun, 3 May 2026 13:23:14 +0300
Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=D7=94=D7=95=D7=A1=D7=A4=D7=AA=20?=
=?UTF-8?q?=D7=AA=D7=9E=D7=99=D7=9B=D7=94=20=D7=91=D7=9E=D7=99=D7=96=D7=95?=
=?UTF-8?q?=D7=92=20=D7=94=D7=AA=D7=A8=D7=90=D7=95=D7=AA=20=D7=91=D7=90?=
=?UTF-8?q?=D7=9E=D7=A6=D7=A2=D7=95=D7=AA=20mergeId=20=D7=9C=D7=AA=D7=90?=
=?UTF-8?q?=D7=99=D7=9E=D7=95=D7=AA=20=D7=A2=D7=9D=20Safari?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- עדכון library.js: הוספת שדה mergeId לאובייקט הנתונים שנשלח בהתראה
- עדכון web-push.js: הוספת לוגיקה לסגירת התראות קיימות עם אותו mergeId לפני הצגת התראה חדשה
- פתרון בעיית תאימות ל-Safari שאינו תומך כראוי בתכונת 'tag' להחלפת התראות
Co-authored-by: Copilot
---
library.js | 2 +-
static/web-push.js | 18 ++++++++++++++++--
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/library.js b/library.js
index ce9cea6..13ed45b 100644
--- a/library.js
+++ b/library.js
@@ -244,7 +244,7 @@ async function constructPayload(notification, uid, lang) {
tag,
lang,
dir,
- data: { url, icon, badge },
+ data: { url, icon, badge, mergeId },
};
}
diff --git a/static/web-push.js b/static/web-push.js
index baa1f34..a2967d7 100644
--- a/static/web-push.js
+++ b/static/web-push.js
@@ -7,13 +7,27 @@ self.addEventListener('push', (event) => {
const { title, body, tag, data } = event.data.json();
if (title && body) {
- const { icon } = data;
+ const { icon, mergeId } = data;
delete data.icon;
const { badge } = data;
delete data.badge;
+ // Close any existing notifications with the same mergeId (for Safari compatibility)
+ // Safari doesn't properly support the 'tag' property for replacing notifications
+ const closePromise = mergeId
+ ? self.registration.getNotifications().then((notifications) => {
+ notifications.forEach((notification) => {
+ if (notification.data && notification.data.mergeId === mergeId) {
+ notification.close();
+ }
+ });
+ })
+ : Promise.resolve();
+
event.waitUntil(
- self.registration.showNotification(title, { body, tag, data, icon, badge })
+ closePromise.then(() => {
+ return self.registration.showNotification(title, { body, tag, data, icon, badge });
+ })
);
} else if (tag) {
event.waitUntil(