From ce3b12aa21778e3baa6bd0715ee24ce78ff2ea5a Mon Sep 17 00:00:00 2001 From: lucamzanon Date: Thu, 1 Oct 2026 12:43:40 +0200 Subject: [PATCH] fix(settings): check push again when the user comes back to the tab The notification settings read the push state once, when they opened. Push turned off meanwhile - in the browser's site settings, or from another tab - still showed as on until the page was reloaded. The state is now read again when the window regains focus or the tab becomes visible, unless an enable or disable is in progress or has just failed. A failed check reads as off instead of leaving the panel unchanged. --- .../__tests__/notification-settings.test.tsx | 14 ++++++++ components/settings/notification-settings.tsx | 32 +++++++++++++++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/components/settings/__tests__/notification-settings.test.tsx b/components/settings/__tests__/notification-settings.test.tsx index a9db650ba..b6047870d 100644 --- a/components/settings/__tests__/notification-settings.test.tsx +++ b/components/settings/__tests__/notification-settings.test.tsx @@ -103,6 +103,20 @@ describe('NotificationSettings - pushNotifyInboxOnly', () => { ); }); + it('checks push again when the user comes back to the tab', async () => { + Object.defineProperty(document, 'visibilityState', { value: 'visible', configurable: true }); + const { findByText } = render(); + await findByText('email.inbox_only'); + const check = webPush.isWebPushEnabled as ReturnType; + await waitFor(() => expect(check).toHaveBeenCalled()); + const before = check.mock.calls.length; + + // Push may have been turned off elsewhere (site settings, another tab) meanwhile. + await act(async () => { window.dispatchEvent(new Event('focus')); }); + + await waitFor(() => expect(check.mock.calls.length).toBeGreaterThan(before)); + }); + it('does not re-sync when push is disabled on this device', async () => { (webPush.isWebPushEnabled as ReturnType).mockResolvedValueOnce(false); render(); diff --git a/components/settings/notification-settings.tsx b/components/settings/notification-settings.tsx index a7a63ab82..2ab9c809b 100644 --- a/components/settings/notification-settings.tsx +++ b/components/settings/notification-settings.tsx @@ -80,16 +80,37 @@ export function NotificationSettings() { const activeRelayLabel = relayOptions.find((option) => option.url === activeRelayUrl)?.label ?? activeRelayUrl; + const busy = pushStatus.kind === 'busy'; + useEffect(() => { if (!supported) return; if (!client) return; const accountId = client.getAccountId(); if (!accountId) return; - void (async () => { - const enabled = await isWebPushEnabled(accountId); - setPushStatus(enabled ? { kind: 'enabled' } : { kind: 'idle' }); - })(); - }, [supported, client]); + let cancelled = false; + const refreshStatus = async () => { + try { + const enabled = await isWebPushEnabled(accountId); + if (!cancelled) { + setPushStatus(enabled ? { kind: 'enabled' } : { kind: 'idle' }); + } + } catch { + if (!cancelled) setPushStatus({ kind: 'idle' }); + } + }; + const onVisible = () => { + if (document.visibilityState === 'visible') void refreshStatus(); + }; + if (pushStatus.kind === 'busy' || pushStatus.kind === 'error') return; + void refreshStatus(); + window.addEventListener('focus', onVisible); + document.addEventListener('visibilitychange', onVisible); + return () => { + cancelled = true; + window.removeEventListener('focus', onVisible); + document.removeEventListener('visibilitychange', onVisible); + }; + }, [supported, client, pushStatus.kind]); // The device list is worth loading even when push is off on this browser - // revoking a stale registration left on another device is exactly what you @@ -108,7 +129,6 @@ export function NotificationSettings() { void refreshDevices(); }, [refreshDevices]); - const busy = pushStatus.kind === 'busy'; const pushEnabled = pushStatus.kind === 'enabled'; // In the static Lite build push is off by design (no service worker, see // components/service-worker-registration.tsx), not a browser limitation.