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(