diff --git a/src/register-service-worker.ts b/src/register-service-worker.ts index 5024189eb..a6396d832 100644 --- a/src/register-service-worker.ts +++ b/src/register-service-worker.ts @@ -6,6 +6,8 @@ export function registerServiceWorker() { if (isNativeApp()) return; if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return; + let hadController = navigator.serviceWorker.controller !== null; + window.addEventListener('load', () => { navigator.serviceWorker.register(SERVICE_WORKER_URL) .then((registration) => { @@ -25,6 +27,10 @@ export function registerServiceWorker() { }); navigator.serviceWorker.addEventListener('controllerchange', () => { + if (!hadController) { + hadController = true; + return; + } if (sessionStorage.getItem('pwa-refreshing') === 'true') return; sessionStorage.setItem('pwa-refreshing', 'true'); window.location.reload(); diff --git a/src/test/register-service-worker.test.ts b/src/test/register-service-worker.test.ts new file mode 100644 index 000000000..7c2cc49d4 --- /dev/null +++ b/src/test/register-service-worker.test.ts @@ -0,0 +1,48 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@/lib/native', () => ({ isNativeApp: () => false })); + +import { registerServiceWorker } from '@/register-service-worker'; + +describe('registerServiceWorker', () => { + const originalServiceWorker = Object.getOwnPropertyDescriptor(navigator, 'serviceWorker'); + let controllerChange: EventListener | undefined; + + beforeEach(() => { + vi.stubEnv('PROD', true); + sessionStorage.clear(); + controllerChange = undefined; + Object.defineProperty(navigator, 'serviceWorker', { + configurable: true, + value: { + controller: null, + register: vi.fn().mockResolvedValue({ addEventListener: vi.fn() }), + addEventListener: vi.fn((type: string, listener: EventListener) => { + if (type === 'controllerchange') controllerChange = listener; + }), + }, + }); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + if (originalServiceWorker) { + Object.defineProperty(navigator, 'serviceWorker', originalServiceWorker); + } else { + Reflect.deleteProperty(navigator, 'serviceWorker'); + } + }); + + it('skips the initial claim but refreshes for a later controller change', () => { + registerServiceWorker(); + + expect(controllerChange).toBeDefined(); + controllerChange?.(new Event('controllerchange')); + expect(sessionStorage.getItem('pwa-refreshing')).toBeNull(); + + vi.spyOn(console, 'error').mockImplementation(() => undefined); + controllerChange?.(new Event('controllerchange')); + expect(sessionStorage.getItem('pwa-refreshing')).toBe('true'); + }); +});