Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Add the ability for any standard to have a service worker
Part of whatwg/meta#3. This allows any standard using the shared deploy script to automatically get a service worker generated, which delegates all of its logic to a shared file on resources.whatwg.org. It allows room for future expansions of the deploy script to include extra resources.

This still requires registering the generated service worker in each spec's HTML, however.
  • Loading branch information
domenic committed Jul 18, 2017
1 parent fd7655c commit bd7900e6d3028954fb9e5fe3eca53eea82a59718
Showing with 133 additions and 0 deletions.
  1. +11 −0 build/deploy.sh
  2. +122 −0 standard-service-worker.js
@@ -47,6 +47,11 @@ if [[ "$TRAVIS" == "true" ]]; then # For some reason the above does not work on
BRANCH=$TRAVIS_BRANCH
fi

SERVICE_WORKER_SHA=$(curl https://api.github.com/repos/whatwg/resources.whatwg.org/contents/standard-service-worker.js \
-H "Accept: application/vnd.github.v3+json" \
| grep -Po '(?<="sha": ")[^"]*') # Hacky JSON parsing but works for SHAs


BACK_TO_LS_LINK="<a href=\"/\" id=\"commit-snapshot-link\">Go to the living standard</a>"
SNAPSHOT_LINK="<a href=\"/commit-snapshots/$SHA/\" id=\"commit-snapshot-link\">Snapshot as of this commit</a>"

@@ -82,6 +87,12 @@ else
curl https://api.csswg.org/bikeshed/ -f -F file=@"$INPUT_FILE" \
-F md-Text-Macro="SNAPSHOT-LINK $SNAPSHOT_LINK" \
> "$WEB_ROOT/index.html"

echo "\"use strict\";
importScripts(\"https://resources.whatwg.org/standard-service-worker.js\");
// Version (for service worker freshness check): $SERVICE_WORKER_SHA" \
> "$WEB_ROOT/service-worker.js"

echo "Living standard output to $WEB_ROOT"
fi

@@ -0,0 +1,122 @@
"use strict";
/* USAGE:
self.extraResources = ["https://example.com/..."]; // optional
importScripts("https://resources.whatwg.org/standard-service-worker.js");
*/

const standardShortname = location.host.split(".")[0];

const cacheKey = "v3";
const toCache = [
location.origin + "/",
"https://resources.whatwg.org/standard.css",
"https://resources.whatwg.org/bikeshed.css",
"https://resources.whatwg.org/file-issue.js",
"https://resources.whatwg.org/commit-snapshot-shortcut-key.js",
standardShortname === "html" ? "https://resources.whatwg.org/logo.svg"
: "https://resources.whatwg.org/logo-" + standardShortname + ".svg"
].concat(self.extraResources || []);

self.oninstall = e => {
e.waitUntil(caches.open(cacheKey).then(cache => cache.addAll(toCache)));
};

self.onfetch = e => {
if (e.request.method !== "GET") {
return;
}

if (needsToBeFresh(e.request)) {
// Since this is a Living Standard, it is imperative that you see the freshest content, so we use a
// network-then-cache strategy for the main content.
e.respondWith(
fetch(e.request).then(res => {
e.waitUntil(refreshCacheFromNetworkResponse(e.request, res));
return res;
})
.catch(() => {
return caches.match(e.request);
})
);
} else {
// For auxiliary resources, we can use a cache-then-network strategy; it is OK to not get the freshest.
e.respondWith(
caches.match(e.request).then(cachedResponse => {
const networkFetchPromise = fetch(e.request);

// Ignore network fetch or caching errors; they just mean we won't be able to refresh the cache.
e.waitUntil(
networkFetchPromise
.then(res => refreshCacheFromNetworkResponse(e.request, res))
.catch(() => {})
);

return cachedResponse || networkFetchPromise;
})
);
}
};

self.onactivate = e => {
e.waitUntil(caches.keys().then(keys => {
return Promise.all(keys.filter(key => key !== cacheKey).map(key => caches.delete(key)));
}));
};

function refreshCacheFromNetworkResponse(req, res) {
if (!res.ok) {
throw new Error(`${res.url} is responding with ${res.status}`);
}

const resForCache = res.clone();

return caches.open(cacheKey).then(cache => cache.put(req, resForCache));
}

function needsToBeFresh(req) {
const requestURL = new URL(req.url);
return requestURL.origin === location.origin && requestURL.pathname === "/";
}

// From https://github.com/jakearchibald/async-waituntil-polyfill
// Apache 2 License: https://github.com/jakearchibald/async-waituntil-polyfill/blob/master/LICENSE
{
const waitUntil = ExtendableEvent.prototype.waitUntil;
const respondWith = FetchEvent.prototype.respondWith;
const promisesMap = new WeakMap();

ExtendableEvent.prototype.waitUntil = function(promise) {
const extendableEvent = this;
let promises = promisesMap.get(extendableEvent);

if (promises) {
promises.push(Promise.resolve(promise));
return;
}

promises = [Promise.resolve(promise)];
promisesMap.set(extendableEvent, promises);

// call original method
return waitUntil.call(extendableEvent, Promise.resolve().then(function processPromises() {
const len = promises.length;

// wait for all to settle
return Promise.all(promises.map(p => p.catch(()=>{}))).then(() => {
// have new items been added? If so, wait again
if (promises.length != len) return processPromises();
// we're done!
promisesMap.delete(extendableEvent);
// reject if one of the promises rejected
return Promise.all(promises);
});
}));
};

FetchEvent.prototype.respondWith = function(promise) {
this.waitUntil(promise);
return respondWith.call(this, promise);
};
}

0 comments on commit bd7900e

Please sign in to comment.