From 8816634c84ef368919f6526d8e6df4ffa48865ab Mon Sep 17 00:00:00 2001 From: funkyFangs Date: Sat, 26 Oct 2024 19:02:09 -0500 Subject: [PATCH] :alien: manifest updates, add service worker --- src/app.html | 2 +- src/lib/api/GenerationResource.ts | 10 +--- src/lib/api/PokeAPI.ts | 13 ----- src/lib/api/PokemonSpeciesResource.ts | 6 +-- src/lib/storage/Cache.ts | 20 ------- src/service-worker.js | 73 ++++++++++++++++++++++++++ static/favicon.png | Bin 12389 -> 0 bytes static/manifest.json | 4 +- 8 files changed, 81 insertions(+), 47 deletions(-) delete mode 100644 src/lib/storage/Cache.ts create mode 100644 src/service-worker.js delete mode 100644 static/favicon.png diff --git a/src/app.html b/src/app.html index f18895b..882b776 100644 --- a/src/app.html +++ b/src/app.html @@ -3,7 +3,7 @@ Shiny Hunter - + diff --git a/src/lib/api/GenerationResource.ts b/src/lib/api/GenerationResource.ts index d00cb39..e94eeda 100644 --- a/src/lib/api/GenerationResource.ts +++ b/src/lib/api/GenerationResource.ts @@ -1,6 +1,6 @@ import type { LinkResource } from '$lib/api/LinkResource' import { titleCase } from '$lib/utilities/Strings' -import { type FetchFunction, getResourceWithCache, type Identifier } from '$lib/api/PokeAPI' +import { type FetchFunction, getResource, type Identifier } from '$lib/api/PokeAPI' import type { PokemonSpecies } from '$lib/api/PokemonSpeciesResource' import type { VersionGroup } from '$lib/api/VersionGroupResource' @@ -8,7 +8,6 @@ export const MAX_GENERATION = 7 export const MIN_GENERATION = 2 export const GENERATION_ENDPOINT = 'generation' -export const GENERATION_CACHE = 'generations' export interface GenerationResource { name: string @@ -33,10 +32,5 @@ export async function getGenerationResource( identifier: Identifier, fetchCallback: FetchFunction = fetch ) { - return getResourceWithCache( - GENERATION_ENDPOINT, - GENERATION_CACHE, - identifier, - fetchCallback - ) + return getResource(GENERATION_ENDPOINT, identifier, fetchCallback) } diff --git a/src/lib/api/PokeAPI.ts b/src/lib/api/PokeAPI.ts index 4547559..cc39fda 100644 --- a/src/lib/api/PokeAPI.ts +++ b/src/lib/api/PokeAPI.ts @@ -1,18 +1,5 @@ -import { fetchWithCache } from '$lib/storage/Cache' - export const API_URL = 'https://pokeapi.co/api/v2' -export async function getResourceWithCache( - endpoint: string, - cacheName: string, - identifier: Identifier, - fetchCallback: FetchFunction = fetch -): Promise { - return fetchWithCache(buildUrl(endpoint, identifier), cacheName, fetchCallback).then((response) => - response.json() - ) -} - export async function getResource( endpoint: string, identifier: Identifier, diff --git a/src/lib/api/PokemonSpeciesResource.ts b/src/lib/api/PokemonSpeciesResource.ts index 5b51eb9..cc9dcda 100644 --- a/src/lib/api/PokemonSpeciesResource.ts +++ b/src/lib/api/PokemonSpeciesResource.ts @@ -1,7 +1,7 @@ import type { LinkResource } from '$lib/api/LinkResource' import type { PokedexNumberResource } from '$lib/api/PokedexNumberResource' import type { VarietyResource } from '$lib/api/VarietyResource' -import { type FetchFunction, getResourceWithCache, type Identifier } from '$lib/api/PokeAPI' +import { type FetchFunction, getResource, type Identifier } from '$lib/api/PokeAPI' import { getPokemon, type Pokemon } from '$lib/api/PokemonResource' import { delimitedTitleCase } from '$lib/utilities/Strings' @@ -23,15 +23,13 @@ export interface PokemonSpecies { } export const POKEMON_SPECIES_ENDPOINT = 'pokemon-species' -export const POKEMON_SPECIES_CACHE = 'pokemonSpecies' export async function getPokemonSpecies( identifier: Identifier, fetchCallback: FetchFunction = fetch ): Promise { - return getResourceWithCache( + return getResource( POKEMON_SPECIES_ENDPOINT, - POKEMON_SPECIES_CACHE, identifier, fetchCallback ).then(async (pokemonSpeciesResource) => ({ diff --git a/src/lib/storage/Cache.ts b/src/lib/storage/Cache.ts deleted file mode 100644 index 7ce5902..0000000 --- a/src/lib/storage/Cache.ts +++ /dev/null @@ -1,20 +0,0 @@ -export async function fetchWithCache( - input: RequestInfo | URL, - cacheName: string, - fetchCallback: typeof fetch = fetch -): Promise { - const cache = await caches.open(cacheName) - const cachedResponse = await cache.match(input) - - if (cachedResponse) { - return cachedResponse - } else { - const fetchResponse = await fetchCallback(input) - if (fetchResponse.ok) { - await cache.put(input, fetchResponse.clone()) - return fetchResponse - } else { - throw new TypeError('bad response status') - } - } -} diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..3b5666a --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,73 @@ +import { build, files, version } from '$service-worker' + +const CACHE = `cache-${version}` + +const ASSETS = [...build, ...files] + +self.addEventListener('install', (event) => { + async function addFilesToCache() { + const cache = await caches.open(CACHE) + await cache.addAll(ASSETS) + } + + event.waitUntil(addFilesToCache()) +}) + +self.addEventListener('activate', (event) => { + // Remove previous cached data from disk + async function deleteOldCaches() { + for (const key of await caches.keys()) { + if (key !== CACHE) await caches.delete(key) + } + } + + event.waitUntil(deleteOldCaches()) +}) + +self.addEventListener('fetch', (event) => { + // ignore POST requests etc + if (event.request.method !== 'GET') return + + async function respond() { + const url = new URL(event.request.url) + const cache = await caches.open(CACHE) + + if (ASSETS.includes(url.pathname)) { + const response = await cache.match(url.pathname) + + if (response) { + return response + } + } + + // for everything else, try the network first, but + // fall back to the cache if we're offline + try { + const response = await fetch(event.request) + + // if we're offline, fetch can return a value that is not a Response + // instead of throwing - and we can't pass this non-Response to respondWith + if (!(response instanceof Response)) { + throw new Error('invalid response from fetch') + } + + if (response.status === 200) { + await cache.put(event.request, response.clone()) + } + + return response + } catch (err) { + const response = await cache.match(event.request) + + if (response) { + return response + } + + // if there's no cache, then just error out + // as there is nothing we can do to respond to this request + throw err + } + } + + event.respondWith(respond()) +}) diff --git a/static/favicon.png b/static/favicon.png deleted file mode 100644 index 783499e9a955121f3b215d5592ba23bb509a0552..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12389 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 d)awE0x9