diff --git a/src/app/features/settings/notifications/PushNotifications.tsx b/src/app/features/settings/notifications/PushNotifications.tsx index 7f510b444..be9e627fb 100644 --- a/src/app/features/settings/notifications/PushNotifications.tsx +++ b/src/app/features/settings/notifications/PushNotifications.tsx @@ -57,7 +57,7 @@ export async function enablePushNotifications( kind: 'http' as const, app_id: clientConfig.pushNotificationDetails?.webPushAppID, pushkey: keys.p256dh, - app_display_name: 'Cinny', + app_display_name: 'Sable', device_display_name: 'This Browser', lang: navigator.language || 'en', data: { @@ -104,7 +104,7 @@ export async function enablePushNotifications( kind: 'http' as const, app_id: clientConfig.pushNotificationDetails?.webPushAppID, pushkey: keys.p256dh, - app_display_name: 'Cinny', + app_display_name: 'Sable', device_display_name: (await mx.getDevice(mx.getDeviceId() ?? '')).display_name ?? 'Unknown Device', lang: navigator.language || 'en', diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 70ba9221e..72fa5ef3f 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -205,7 +205,7 @@ export function ClientRoot({ children }: ClientRootProps) { log.log('initClient for', activeSession.userId); const newMx = await initClient(activeSession); loadedUserIdRef.current = activeSession.userId; - pushSessionToSW(activeSession.baseUrl, activeSession.accessToken); + pushSessionToSW(activeSession.baseUrl, activeSession.accessToken, activeSession.userId); return newMx; }, [activeSession, activeSessionId, setActiveSessionId]) ); @@ -234,7 +234,7 @@ export function ClientRoot({ children }: ClientRootProps) { activeSession.userId, '— reloading client' ); - pushSessionToSW(activeSession.baseUrl, activeSession.accessToken); + pushSessionToSW(activeSession.baseUrl, activeSession.accessToken, activeSession.userId); if (mx?.clientRunning) { stopClient(mx); } @@ -259,6 +259,28 @@ export function ClientRoot({ children }: ClientRootProps) { useLogoutListener(mx); useAppVisibility(mx); + // Keep the SW session warm so media fetches and push notifications work + // reliably after iOS kills and restarts the SW in the background. + // - Immediate resync whenever the tab comes back to the foreground. + // - Periodic heartbeat (10 min) keeps the persisted session up to date + // while the app is running. + const swSessionBaseUrl = activeSession?.baseUrl; + const swSessionAccessToken = activeSession?.accessToken; + const swSessionUserId = activeSession?.userId; + useEffect(() => { + if (!swSessionBaseUrl || !swSessionAccessToken) return undefined; + const resync = () => pushSessionToSW(swSessionBaseUrl, swSessionAccessToken, swSessionUserId); + const handleVisibility = () => { + if (document.visibilityState === 'visible') resync(); + }; + document.addEventListener('visibilitychange', handleVisibility); + const timer = setInterval(resync, 10 * 60 * 1000); + return () => { + document.removeEventListener('visibilitychange', handleVisibility); + clearInterval(timer); + }; + }, [swSessionBaseUrl, swSessionAccessToken, swSessionUserId]); + useEffect( () => () => { if (mx?.clientRunning) { diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index 9e0496ee3..b35c8a4a7 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -802,5 +802,13 @@ export const clearLoginData = async () => { if (name) window.indexedDB.deleteDatabase(name); }); window.localStorage.clear(); + + // Unregister all service workers so the next load starts fresh. + // Especially important on iOS/mobile where stale SWs can persist. + if ('serviceWorker' in navigator) { + const registrations = await navigator.serviceWorker.getRegistrations(); + await Promise.all(registrations.map((r) => r.unregister())); + } + window.location.reload(); };