Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

src/client/sw-register.ts

55 lines · 1.6 KB · TypeScript
/** Service worker registration with stale-registration cleanup. */
import { readClientConfig } from './config.ts'

declare global {
	interface Window {
		pakRegisterServiceWorker?: () => Promise<ServiceWorkerRegistration | null>
	}
}

export function bootServiceWorkerRegistration() {
	const config = readClientConfig()

	async function unregisterStaleWorkers() {
		if (!('serviceWorker' in navigator)) return
		const regs = await navigator.serviceWorker.getRegistrations()
		const scopePrefix = `${config.appBase}/`
		await Promise.all(
			regs.map((reg) => {
				let scopePath = ''
				try {
					scopePath = new URL(reg.scope).pathname
				} catch {
					return null
				}
				const underApp =
					scopePath === config.appBase ||
					scopePath === scopePrefix ||
					scopePath.startsWith(scopePrefix)
				if (underApp) return null
				if (scopePath.startsWith('/packages/') && !scopePath.startsWith(scopePrefix)) {
					return reg.unregister()
				}
				return null
			}),
		)
	}

	async function register() {
		if (!('serviceWorker' in navigator)) return null
		await unregisterStaleWorkers()
		// Platform assets SW: register `${assetBasePath}/sw.js` with app mount scope.
		// Interim (no clientModuleUrl / older platform): Worker-served `/sw.js`.
		const scriptUrl = config.swUrl.startsWith('http')
			? config.swUrl
			: config.swUrl.startsWith('/')
				? config.swUrl
				: `${config.appBase}/${config.swUrl}`
		return navigator.serviceWorker.register(scriptUrl, {
			scope: `${config.appBase}/`,
			updateViaCache: 'none',
		})
	}

	window.pakRegisterServiceWorker = register
}