Wayne
Wayne: Open Source Service Worker Routing library for in browser HTTP requests
Install / Use
/learn @jcubic/WayneREADME
Wayne: Open Source Service Worker Routing library for in-browser HTTP requests
It's like an Express inside Service Worker.
Most of the time Service Worker is used for caching HTTP requests and making the app work when there is no internet (mostly for PWA), but in fact, you can create completely new responses to requests that never leave the browser. This library makes that easier by adding a simple API similar to Express.
Usage
Installation from npm:
npm install @jcubic/wayne
yarn add @jcubic/wayne
The standard way of installing the service worker
if ('serviceWorker' in navigator) {
const scope = location.pathname.replace(/\/[^\/]+$/, '/');
navigator.serviceWorker.register('sw.js', { scope, type: 'module' })
.then(function(reg) {
reg.addEventListener('updatefound', function() {
const installingWorker = reg.installing;
console.log('A new service worker is being installed:',
installingWorker);
});
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
If you want to support browsers that don't support ES Modules in Service Worker use this instead:
if ('serviceWorker' in navigator) {
const scope = location.pathname.replace(/\/[^\/]+$/, '/');
navigator.serviceWorker.register('sw.js', { scope })
.then(function(reg) {
reg.addEventListener('updatefound', function() {
const installingWorker = reg.installing;
console.log('A new service worker is being installed:',
installingWorker);
});
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
Inside the same file you can send AJAX requests with standard fetch API.
function get(url) {
fetch(url)
.then(res => res.text())
.then(text => output.innerHTML = text);
}
input.addEventListener('click', () => {
get(`./user/${user_id.value}`);
});
error.addEventListener('click', () => {
get(`./error`);
});
Service worker - sw.js file
Importing Wayne module:
- when worker created as ES Module
import { Wayne } from 'https://cdn.jsdelivr.net/npm/@jcubic/wayne';
const app = new Wayne();
- When the Service Worker created as normal script
importScripts('https://cdn.jsdelivr.net/npm/@jcubic/wayne/index.umd.min.js');
const app = new wayne.Wayne();
- When using bundlers like Vite:
import { Wayne } from '@jcubic/wayne';
Using the library
const users = {
1: 'Jakub T. Jankiewicz',
2: 'John Doe',
3: 'Jane Doe'
};
app.get('/user/{id}', function(req, res) {
const user = users[req.params.id];
if (user) {
res.json({result: user});
} else {
res.json({error: 'User Not Found'});
}
});
app.get('/error', function(req, res) {
nonExisting();
});
app.get('/redirect', function(req, res) {
res.redirect(301, '/message');
});
app.get('/message', function(req, res) {
res.text('Lorem Ipsum');
});
app.get('/404', function(req, res) {
res.text('Not Found', { status: 404, statusText: 'Not Found' });
});
app.get('/external', function(req, res) {
// lorem ipsum API
res.redirect('https://api.buildable.dev/@62d55492951509001abc363e/live/lorem-ipsum');
});
Handle the same extension for all requests
importScripts(
'https://cdn.jsdelivr.net/npm/@jcubic/wayne/index.umd.min.js',
'https://cdn.jsdelivr.net/gh/jcubic/static@master/js/path.js'
);
const app = new Wayne();
app.get('*', function(req, res) {
const url = new URL(req.url);
const extension = path.extname(url.pathname);
const accept = req.headers.get('Accept');
if (extension === '.js' && accept.match(/text\/html/)) {
res.text('// Sorry no source code for you');
} else {
res.fetch(req);
}
});
This code will show the comment // Sorry no source code for you for every request to JavaScript
files from the browser (if open in a new tab). When you want to view the file the browser sends
Accept: text/html HTTP header.
File system middleware
import { Wayne, FileSystem } from 'https://cdn.jsdelivr.net/npm/@jcubic/wayne';
import FS from "https://cdn.skypack.dev/@isomorphic-git/lightning-fs";
import mime from "https://cdn.skypack.dev/mime";
import path from "https://cdn.skypack.dev/path-browserify";
const { promises: fs } = new FS("__wayne__");
const app = new Wayne();
app.use(FileSystem({ path, fs, mime, prefix: '__fs__' }));
When not using a module the code will be similar. When you access URLs with
the prefix __fs__ like ./__fs__/foo it will read files from the indexedDB file
system named __wayne__. See Lightning-FS repo for details about the library.
From version 0.12 you can use test callback option to check if the file should serve from the filesystem. Note that it will receive URLs from all domains.
From version 0.13.0 you can use dir callback function that allow to dynamically change directory of served files.
const test = url => {
const path = url.pathname;
// return true if pathname should go to filesystem
return path.match(/__fs__/);
};
const dir = () => '/';
app.use(wayne.FileSystem({ path, fs, mime, test, dir }));
From version 0.14.0 both functions dir and test can be async. So you can use data from IndexedDB
e.g. using idb-keyval by Jake Archibald.
A patch in 0.14.3 allow putting interceptors to inject something into output HTML from FileSystem
middleware. You do this by adding middleware before FileSystem and patch res.send method:
function fs_interecept(callback) {
return function(req, res, next) {
const send = res.send.bind(res);
res.send = function(data, ...rest) {
const url = new URL(req.url);
if (test(url)) {
data = callback(data);
}
return send(data, ...rest);
};
next();
};
}
app.use(fs_interecept(function(html) {
return html.replace(/<\/body>/, `<script>console.log('intercepted')</script></body>`);
}));
You should use the same test function to make sure that you patch only those requests that came
from FS.
Serving files from Cache
Since version 0.19.0 you can use Cache instead of indexedDB to serve the file from Service Worker. You still need file system on main thread to save the files, but then you can use:
wayne.make_cache({ fs, path, mime, dir: '/', prefix: '__fs__', cache: '__wayne__' });
This function will cache all requests from filesystem, so you can use in service worker:
const app = new Wayne();
app.use(FileSystem({ path, prefix: '__fs__', cache: '__wayne__' }));
Only path is required in service worker. Parameters cache and prefix needs to be the same on both calls.
RPC mechanism
In Service Worker, you create a generic route that sends data to the BroadcastChannel:
import { send } from 'https://cdn.jsdelivr.net/npm/@jcubic/wayne';
const channel = new BroadcastChannel('__rpc__');
app.get('/rpc/{name}/*', async (req, res) => {
const args = req.params[0].split('/');
const method = req.params.name;
try {
const data = await send(channel, method, args);
res.json(data);
} catch(e) {
res.text(e.message);
}
});
and in the main thread, you create the other side of the channel and the remote methods:
import { rpc } from 'https://cdn.jsdelivr.net/npm/@jcubic/wayne';
const channel = new BroadcastChannel('__rpc__');
rpc(channel, {
ping: function() {
return 'pong';
},
sin: function(x) {
return Math.sin(x);
},
random: function() {
return Math.random();
},
json: function() {
return fetch('https://api.npoint.io/8c7cc24b3fd405b775ce').then(res => res.json());
}
});
When you send a request /rpc/ping you will get a response from methods.ping function.
fetch('./rpc/ping')
.then(res => res.text())
.then(text => {
console.log({ text });
});
With this setup, you can create new functions/methods that will map to HTTP requests.
The demo below uses random requests:
let index = 0;
const requests = [
'./rpc/ping/',
'./rpc/json/',
'./rpc/random/',
'./rpc/sin/10'
];
rpc.addEventListener('click', () => {
get(random_request() );
});
function random_request() {
const next_
Related Skills
node-connect
347.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
