Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Commit

Permalink
Add the ability for any spec to have a service worker
Browse files Browse the repository at this point in the history
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 Apr 4, 2017
1 parent 1bec4a7 commit 9059df7
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ 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\");"
>> "$WEB_ROOT"/service-worker.js
echo "Living standard output to $WEB_ROOT"
fi

Expand Down
121 changes: 121 additions & 0 deletions standard-service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"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",
"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 9059df7

Please sign in to comment.