diff --git a/src/serviceworker/src/strategy.ts b/src/serviceworker/src/strategy.ts new file mode 100644 index 0000000..3500bc6 --- /dev/null +++ b/src/serviceworker/src/strategy.ts @@ -0,0 +1,74 @@ +export class Strategy { + #cache: Cache + + constructor(cache: Cache) { + this.#cache = cache + } + + cacheFirst({ request, preloadResponse }: FetchEvent) { + const fetchRequest = async () => { + const response = await fetch(request) + if (response.ok) { + this.#cache.put(request, response) + } + } + + // Get navigator preload request + preloadResponse + .then((preload: Response | undefined) => { + if (preload?.ok) { + return this.#cache.put(request, preload) + } + throw new Error() + }) + // Else fetch request + .catch(fetchRequest) + + return this.#cache.match(request) + } + + async networkFirst( + { request, preloadResponse }: FetchEvent, + { fallbackUrl, timeout }: { fallbackUrl?: string; timeout?: number }, + ) { + const signal = timeout ? AbortSignal.timeout(timeout) : undefined + + try { + // Get navigator preload (see: https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/preloadResponse) + const preload = await getPreload(preloadResponse) + if (preload?.ok) { + this.#cache.put(request, preload.clone()) + return preload + } + } catch { + try { + // Else fetch request + const response = await fetch(request, { signal }) + if (response.ok) { + this.#cache.put(request, response.clone()) + return response + } + } catch { + // Else return fallback or cached response + return this.#cache.match(fallbackUrl ?? request) + } + } + } +} + +function getPreload( + preloadResponse: FetchEvent['preloadResponse'], + ac?: { signal?: AbortSignal }, +): Promise { + const { promise, resolve, reject } = Promise.withResolvers< + Response | undefined + >() + + ac?.signal?.addEventListener('abort', () => { + reject(new Error('aborted by signal')) + }) + + resolve(preloadResponse) + + return promise +}