Skip to content

Instantly share code, notes, and snippets.

@Warr1024
Last active February 24, 2021 01:07
Show Gist options
  • Save Warr1024/1670c8a5d21f66ffc599a6f51867dbee to your computer and use it in GitHub Desktop.
Save Warr1024/1670c8a5d21f66ffc599a6f51867dbee to your computer and use it in GitHub Desktop.
TiddlyWiki5 Offline ServiceWorker
Service worker that implements caching and offline fallback for a
TiddlyWiki5 instance with tiddlyweb saving hosted on nodejs.
This gives you the best of both worlds: a shared, synchronized wiki that
you can edit from anywhere and automatically syncs across multiple
devices, AND a fallback local version that you can still access when
offline, and will queue up changes to be uploaded when you're back
online later.
When you're offline, the wiki will still load, and you'll still have
access to all the tiddlers you've already loaded, but the page will be
aware it's offline and will throw network connection errors, so you'll
want to tweak the presentation of errors inside TW5 so they're less
obtrusive and you can work around then, but you'll still want to be
aware that your changes aren't saved yet.
Saving works when you return online, but only if the page is actually
loaded a the time, i.e. the service worker can't queue up background
syncs and relies on the front-end page trying to push a save when
the network is actually available. Background sync for service workers
is not standards-track yet, and even if it were, it would probably
require more extensive changes to TW5 to make it
background-sync-API-aware.
As it is, this service worker provides a reasonable experience with a
completely unmodified TW5 nodejs instance.
'use strict';
const log = msg => console.log(`service-worker: ${msg}`);
const listener = (name, func) => self.addEventListener(name, evt => {
log(name);
evt.waitUntil((async () => await func(evt))());
});
listener('install', () => self.skipWaiting());
listener('activate', async () => {
await self.clients.claim();
for (let c of await self.clients.matchAll())
c.postMessage('reload');
});
listener('fetch', async evt => {
const req = evt.request;
const rpt = s => log(`fetch ${req.mode} ${req.method} ${req.url} -> ${s}`);
const fetching = fetch(req);
// API write requests go online only.
if (req.method !== 'GET') {
rpt('nocache');
return evt.respondWith(fetching);
}
// API read requests try online first.
if (/\/status$/.test(req.url) ||
/\/recipes\/.*\?/.test(req.url))
return evt.respondWith((async () => {
const cache = await caches.open('main');
let resp;
try { resp = await fetching; }
catch(e) { }
if(!resp || !resp.ok) {
const found = await cache.match(req);
rpt(found ? 'fallback' : 'fail');
return found || resp || new Response({ status: 400 });
}
rpt('online');
await cache.put(req, resp);
return await cache.match(req);
})());
// All other requests always use cache first.
return evt.respondWith((async () => {
const cache = await caches.open('main');
const found = await cache.match(req);
if (found) {
rpt('cached');
fetching.then(resp => {
rpt('update');
cache.put(req, resp);
});
return found;
}
const resp = await fetching;
if (!resp.ok) {
rpt('!ok');
return resp;
}
rpt('fresh');
await cache.put(req, resp);
return await cache.match(req);
})());
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment