// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ import noop from 'lodash-es/noop' import type { RegisterSWOptions } from './types.ts' // should service worker be updated automatically without a prompt const auto = false // should servicer worker be destroyed - should be used only if something went wrong const autoDestroy = false // eslint-disable-next-line sonarjs/cognitive-complexity export const registerSW = (options: RegisterSWOptions) => { const { path, scope, immediate = false, onNeedRefresh, onOfflineReady, onRegistered, onRegisteredSW, onRegisterError, } = options if (VITE_TEST_MODE) { return noop } // service worker is disabled during normal development if (import.meta.env.DEV) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const sw = window.sw! // to trigger "Need refresh" run in console: `sw.triggerUpdate()` sw.ontriggerupdate = () => { onNeedRefresh?.() } // you can disable service worker in development mode by running in console: pwa.enable() // you can enable service worker, it will point to /public/assets/frontend/vite-dev/sw.js // don't forget to unregister service worker, when you are done in console: pwa.disable() if (!sw.isEnabled()) { return () => { console.log('Updating service worker...') window.location.reload() } } } let wb: import('workbox-window').Workbox | undefined let registration: ServiceWorkerRegistration | undefined let registerPromise: Promise let sendSkipWaitingMessage: () => Promise | undefined const updateServiceWorker = async (reloadPage = true) => { await registerPromise if (!auto) { // Assuming the user accepted the update, set up a listener // that will reload the page as soon as the previously waiting // service worker has taken control. if (reloadPage) { wb?.addEventListener('controlling', (event) => { if (event.isUpdate) window.location.reload() }) setTimeout(() => window.location.reload(), 1000) } await sendSkipWaitingMessage?.() } } const register = async () => { if (!('serviceWorker' in navigator)) { return } const { Workbox, messageSW } = await import('workbox-window') sendSkipWaitingMessage = async () => { if (registration && registration.waiting) { // Send a message to the waiting service worker, // instructing it to activate. // Note: for this to work, you have to add a message // listener in your service worker. See below. await messageSW(registration.waiting, { type: 'SKIP_WAITING' }) } } wb = new Workbox(path, { scope, type: 'classic' }) wb.addEventListener('activated', (event) => { // this will only controls the offline request. // event.isUpdate will be true if another version of the service // worker was controlling the page when this version was registered. if (event.isUpdate) { if (auto) window.location.reload() } else if (!autoDestroy) { onOfflineReady?.() } }) if (!auto) { const showSkipWaitingPrompt = () => { // \`event.wasWaitingBeforeRegister\` will be false if this is // the first time the updated service worker is waiting. // When \`event.wasWaitingBeforeRegister\` is true, a previously // updated service worker is still waiting. // You may want to customize the UI prompt accordingly. // Assumes your app has some sort of prompt UI element // that a user can either accept or reject. onNeedRefresh?.() } // Add an event listener to detect when the registered // service worker has installed but is waiting to activate. wb.addEventListener('waiting', showSkipWaitingPrompt) // @ts-expect-error event listener provided by workbox-window wb.addEventListener('externalwaiting', showSkipWaitingPrompt) } // register the service worker wb.register({ immediate }) .then((r) => { registration = r if (onRegisteredSW) onRegisteredSW(path, r) else onRegistered?.(r) }) .catch((e) => { onRegisterError?.(e) }) } registerPromise = register() return updateServiceWorker }