v0.12.30 add notifications for iOS
This commit is contained in:
@@ -97,14 +97,71 @@ export default function Chat() {
|
||||
return () => window.removeEventListener('rosterchirp:settings-changed', loadFeatures);
|
||||
}, [loadFeatures]);
|
||||
|
||||
// Register / refresh FCM push subscription
|
||||
// Register / refresh push subscription — FCM for Android/Chrome, Web Push for iOS
|
||||
useEffect(() => {
|
||||
if (!('serviceWorker' in navigator)) return;
|
||||
|
||||
const registerPush = async () => {
|
||||
try {
|
||||
if (Notification.permission === 'denied') return;
|
||||
// Convert a URL-safe base64 string to Uint8Array for the VAPID applicationServerKey
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||
const raw = atob(base64);
|
||||
return Uint8Array.from(raw, c => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
// ── iOS / Web Push path ───────────────────────────────────────────────────
|
||||
// iOS 16.4+ PWAs use the standard W3C Web Push API via pushManager.subscribe().
|
||||
// FCM tokens are Google-specific and are not accepted by Apple's push service.
|
||||
const registerWebPush = async () => {
|
||||
try {
|
||||
const configRes = await fetch('/api/push/vapid-public-key');
|
||||
if (!configRes.ok) { console.warn('[Push] VAPID key not available'); return; }
|
||||
const { vapidPublicKey } = await configRes.json();
|
||||
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
|
||||
// Re-use any existing subscription so we don't lose it on every page load
|
||||
let subscription = await reg.pushManager.getSubscription();
|
||||
if (subscription) {
|
||||
// Check if it's already registered with the server
|
||||
const cachedEndpoint = localStorage.getItem('rc_webpush_endpoint');
|
||||
if (cachedEndpoint === subscription.endpoint) {
|
||||
console.log('[Push] WebPush subscription unchanged — skipping subscribe');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
subscription = await reg.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[Push] WebPush subscription obtained');
|
||||
const subJson = subscription.toJSON();
|
||||
const token = localStorage.getItem('tc_token') || sessionStorage.getItem('tc_token');
|
||||
const subRes = await fetch('/api/push/subscribe-webpush', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({ endpoint: subJson.endpoint, keys: subJson.keys }),
|
||||
});
|
||||
if (!subRes.ok) {
|
||||
const err = await subRes.json().catch(() => ({}));
|
||||
console.warn('[Push] WebPush subscribe failed:', err.error || subRes.status);
|
||||
localStorage.setItem('rc_fcm_error', `WebPush subscribe failed: ${err.error || subRes.status}`);
|
||||
} else {
|
||||
localStorage.setItem('rc_webpush_endpoint', subJson.endpoint);
|
||||
localStorage.removeItem('rc_fcm_error');
|
||||
console.log('[Push] WebPush subscription registered successfully');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Push] WebPush registration failed:', e.message);
|
||||
localStorage.setItem('rc_fcm_error', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Android / Chrome FCM path ─────────────────────────────────────────────
|
||||
const registerFCM = async () => {
|
||||
try {
|
||||
// Fetch Firebase config from backend (returns 503 if FCM not configured)
|
||||
const configRes = await fetch('/api/push/firebase-config');
|
||||
if (!configRes.ok) return;
|
||||
@@ -121,10 +178,6 @@ export default function Chat() {
|
||||
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
|
||||
// Never auto-request permission — that triggers a dialog on PWA launch.
|
||||
// Permission is requested explicitly from the Notifications tab in the profile modal.
|
||||
if (Notification.permission !== 'granted') return;
|
||||
|
||||
// Do NOT call deleteToken() here. Deleting the token on every page load (or
|
||||
// every visibility-change) forces Chrome to create a new Web Push subscription
|
||||
// each time. During the brief window between delete and re-register the server
|
||||
@@ -183,6 +236,26 @@ export default function Chat() {
|
||||
}
|
||||
};
|
||||
|
||||
const registerPush = async () => {
|
||||
try {
|
||||
if (Notification.permission === 'denied') return;
|
||||
// Never auto-request permission — that triggers a dialog on PWA launch.
|
||||
// Permission is requested explicitly from the Notifications tab in the profile modal.
|
||||
if (Notification.permission !== 'granted') return;
|
||||
// Respect the user's explicit opt-out from the user menu toggle
|
||||
if (localStorage.getItem('rc_push_enabled') === 'false') return;
|
||||
|
||||
const isIOS = /iphone|ipad/i.test(navigator.userAgent);
|
||||
if (isIOS) {
|
||||
await registerWebPush();
|
||||
} else {
|
||||
await registerFCM();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Push] registerPush failed:', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
registerPush();
|
||||
|
||||
const handleVisibility = () => {
|
||||
|
||||
Reference in New Issue
Block a user