diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/content/index.html | 22 | ||||
-rw-r--r-- | src/content/papo.js | 34 | ||||
-rw-r--r-- | src/content/style.css | 0 | ||||
-rw-r--r-- | src/content/sw.js | 131 | ||||
-rwxr-xr-x | src/exported.sh | 7 | ||||
-rw-r--r-- | src/sw-main.js | 5 |
6 files changed, 199 insertions, 0 deletions
diff --git a/src/content/index.html b/src/content/index.html new file mode 100644 index 0000000..ca012d4 --- /dev/null +++ b/src/content/index.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en" manifest="manifest.appcache"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <!-- + FIXME + - metadata + - canonical link + --> + <link rel="stylesheet" type="text/css" href="./style.css" /> + <link rel="shortcut icon" type="image/svg+xml" href="./img/favicon.svg" /> + <title>Chat with Freedom | Papo FIXME</title> + <script type="module"> + import { main } from "./papo.js"; + main(); + </script> + </head> + <body> + <p id="static-loading">Loading...</p> + </body> +</html> diff --git a/src/content/papo.js b/src/content/papo.js new file mode 100644 index 0000000..83dd1b1 --- /dev/null +++ b/src/content/papo.js @@ -0,0 +1,34 @@ +// import * as assert from "node:assert/strict"; + + +// assert.deepEqual({a: 1}, {a: 1}); +// console.log(123); +// console.log({ navigator }); + + +const f1 = x => x + 1; +const f2 = x => x - 1; + +const registerServiceWorker = async ({ serviceWorkerPath, err }) => { + try { + await navigator.serviceWorker?.register(path) + } catch (e) { + err(`Service worker registration failed: ${e}`); + } +}; + +export const main = async ({ + serviceWorkerPath = "./service-worker.js", + out = console.log, + err = console.warn, +} = {}) => { + const env = { + serviceWorkerPath, + out, + err, + }; + await registerServiceWorker(env); + out("main called"); +}; + +// <body onload="setOnline()" ononline="setOnline()" onoffline="setOnline()" > diff --git a/src/content/style.css b/src/content/style.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/content/style.css diff --git a/src/content/sw.js b/src/content/sw.js new file mode 100644 index 0000000..9e17707 --- /dev/null +++ b/src/content/sw.js @@ -0,0 +1,131 @@ +const CACHE_NAME = "static-shared-assets"; + +const FALLBACK_PATHS = { + img: "/fallback-image-FIXME.svg", + data: { + static: "/fallback-data-FIXME.json", + }, +}; + +const leafValues = tree => + Object.values(tree).map(x => + typeof x !== "object" + ? x + : leafValues(x) + ); + +const collectLeaves = tree => + leafValues(tree).flat(Infinity); + +const DEFAULT_INSTALL_PATHS = [ + "/", + "index.html", + "style.css", + "img/favicon.svg", + "papo.js", +].concat(collectLeaves(FALLBACK_PATHS)); + +const mkInstallHandler = ({ self, caches }) => event => { + self.skipWaiting(); + event.waitUntil( + caches.open(CACHE_NAME).then( + cache => cache.addAll(DEFAULT_INSTALL_PATHS), + ), + ); +}; + +const store = async (caches, request, response) => { + const cache = await caches.open(CACHE_NAME); + await cache.put(request, response); +}; + +const getPrefixIn = (paths, segments) => { + if (paths === undefined) { + return null; + } + + if (segments.length === 0) { + return null; + } + + if (typeof paths === "string") { + return paths; + } + + return getPrefixIn(paths[segments[0]], segments.slice(1)); +}; + +const maybeFallback = async (caches, request, paths = FALLBACK_PATHS) => { + const url = new URL(request.url) + const segments = url.pathname.split("/").filter(s => !!s) + const fallbackPath = getPrefixIn(paths, segments); + if (fallbackPath) { + return await caches.match(fallbackPath); + } + + return null; +} + +const fromCache = async (caches, fetch, { request, preloadResponse }) => { + const cachedResponse = await caches.match(request); + if (cachedResponse) { + fetch(request).then(async freshResponse => + store(caches, request, freshResponse)); + return cachedResponse; + } + + const preloadedResponse = await preloadResponse; + if (preloadedResponse) { + store(caches, request, preloadeResponse.clone()); + return preloadedResponse; + } + + try { + // FIXME: do integration test with real lack of internet. + const fetchedResponse = await fetch(request); + store(caches, request, fetchedResponse.clone()); + return fetchedResponse; + } catch (e) { + const fallbackResponse = await maybeFallback(caches, request); + if (fallbackResponse) { + return fallbackResponse; + } + + throw e; + } +}; + +const mkFetchHandler = ({ caches }) => (event, mkfetch = () => fetch) => { + if (event.request.method !== "GET") { + return; + } + + event.respondWith(fromCache(caches, mkfetch(), event)); +}; + +const mkActivateHandler = ({ self, clients }) => event => + event.waitUntil(Promise.all([ + clients.claim(), + self.registration.navigationPreload?.enable(), + ])); + +const registerListeners = env => { + env.self.addEventListener("install", mkInstallHandler(env)); + env.self.addEventListener("activate", mkActivateHandler(env)); + env.self.addEventListener("fetch", mkFetchHandler(env)); +}; + +const main = ({ + self, + caches, + clients, + out = console.log, + err = console.warn, +} = {}) => + registerListeners({ + self, + caches, + clients, + out, + err, + }); diff --git a/src/exported.sh b/src/exported.sh new file mode 100755 index 0000000..15e99f6 --- /dev/null +++ b/src/exported.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +cat "$1" +printf '%s\n\n\nexport {\n' +awk '/^const / { printf "\t%s,\n", $2 }' "$1" +printf '}\n' diff --git a/src/sw-main.js b/src/sw-main.js new file mode 100644 index 0000000..be45ba4 --- /dev/null +++ b/src/sw-main.js @@ -0,0 +1,5 @@ +main({ + self, + caches, + clients, +}); |