jcubic / wayne Goto Github PK
View Code? Open in Web Editor NEWService Worker Routing library for in browser HTTP requests
Home Page: https://jcubic.github.io/wayne
License: MIT License
Service Worker Routing library for in browser HTTP requests
Home Page: https://jcubic.github.io/wayne
License: MIT License
It was explained in the article: SSEGWSW: Server-Sent Events Gateway by Service Workers (that was helpful to create SSE implementation, link in README)
The code should use sysend.js to propagate the Events to other tabs in the same domain.
The task is to test if you can have React application (or a different library) that generates static HTML files.
The application should live in Service Worker and HTTP requests should return different part of the application.
This is just to test if something like this is possible.
With the following line:
const root_url = location.pathname.replace(/\/[^\/]+$/, '');
my root_url could be '/s', which lead to a failed route match for my request starts with '/s', but I know my root url should be '/', how can I fix this problem?
Not sure about the API.
Maybe based on Express.js Error Handling.
It should work with:
It should work similar to express, maybe something like:
import FS from 'https://cdn.jsdelivr.net/npm/@isomorphic-git/lightning-fs';
const fs = new FS("<name>");
app.use(Wayne.serve(fs, { prefix: '__fs__' })
It will require adding middleware maybe I will steal the API and architecture from express.js.
Properly document all the features that are in the base demo
Create Proof of concept of WebRTC chat (two people only) that uses GET request and SSE and the rest is in Service Worker.
This is a basic implementation of the body parser:
async function stream_to_string(stream) {
const result = [];
const decoder = new TextDecoder();
for await (const chunk of stream) {
result.push(decoder.decode(chunk, { stream: true }));
}
return result.join('');
}
async function stream_to_uint8Array(stream) {
return uint8_concat(read_chunks(stream));
}
function read_chunks(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return chunks;
}
function uint8_concat(chunks) {
const len = chunks_len(chunks);
const result = new Uint8Array();
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
function chunks_len(chunks) {
return chunks.reduce((acc, chunk) => acc + chunk.length, 0);
}
app.use(function(req, res, next) {
req.json = async function() {
return JSON.parse(await stream_to_string(req.body));
};
req.raw = function() {
return stream_to_uint8Array(req.body);
};
req.text = function() {
return stream_to_string(req.body);
};
next();
});
express.js allow to use this syntax:
app.get('/__fs__/*', function(req, res) {
res.send(req.params[0]);
});
Wayne should also support this syntax. Found why investigating #3
I'm building an app that includes a service worker (that I'm trying to manage with Wayne) and a dedicated worker (that runs a lightning-fs database). The dedicated worker must communicate with the main thread and with the service worker via broadcast channels. Among many other things, the application opens new tabs that get content fetched from the database by the dedicated worker and served by the service worker.
Here's an example of how a non-Wayne service worker would fetch data from the database through the dedicated worker in my app:
const channel = new BroadcastChannel('my-channel');
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('/yo')) {
event.respondWith(
makeResponse()
)
}
});
const makeResponse = async function() {
let x = await getMessage();
let y = await new Response(x, {
headers: {
'Content-Type': 'text/html'
}
});
return y
}
async function getMessage() {
return new Promise((resolve) => {
channel.addEventListener('message', (event) => {
channel.removeEventListener('message', event.currentTarget);
resolve(event.data);
});
channel.postMessage('/core/');
});
}
I have code on the dedicated worker that responds to that message with the content of /core/ :
const channel = new BroadcastChannel('my-channel');
channel.onmessage = async function(event) {
const ind = await fs.promises.readdir(event.data);
channel.postMessage(ind);
}
This example works fine (at least as long the main page stays open, which is how the app is intended to be used). But when I try to fit this inside a Wayne app.get, I incur into problems. I tried formulating the code in different ways, but I've incurred into weird behavior (the most common one is the "/yo" page not loading at the first attempt, loading on reload, and then alternating between not-loading&loading at every reload; I've noticed that if I wait a few seconds after a failed reload, the next reload is a failure too: a failed reload guarantees a successful reload at the next attempt only when the next attempt happens immediately after the failure).
I've tried a few things, including experimenting with code that should make the page load forever (something that's pretty easy to accoplish with a normal bootstrapped service worker), but it didn't work inside app.get, the page loads immediately and I get either a reload of "./" or a successful load of "./yo" (as I said, often alternating between the two). This made me wonder if the library abstracts away some of the control one would usually have over the http response?
I've looked inside the Wayne code, and while I get a general idea of it (besides including path manipulation with regex, you register routes in the Wayne object that associate a path with a function, and there's a "fetch" event listener that returns a blob with metadata based on the res method called), I deemed it more efficient to ask here instead of trying to reconstruct the entire code line by line (that could still be my last option, if you tell me that for some reason Wayne does not include ways to wait for the message from the dedicated worker, but I still decided to use it as a library, I'd might need to edit it). I suspect there's probably a fairly simple solution to this?
There should be a website and documentation.
This is time-consuming, but I think it will be a good first issue.
Curious; is supporting https://wintercg.org/ something you are interested in?
(CC @cyco130 author of HatTip which is also a modern cross-platform Express.js alternative.)
add overloaded use
API function similar to express.js:
app.use((req, res, next) => {
});
and
app.use((err, req, res, next) => {
});
This will help in #10
This came out in #20. There is a need for simple pass thru mechanism.
You can probably use something like this:
app.get('/foo/*', (req, res) => {
if (req.params[0].match(/.txt$/)) {
fs.promises.readFile(req.params[0], 'utf8').then(text => {
res.text(text);
});
} else {
fetch(req).then(text => res.text(text));
}
});
This should be tested if it works and maybe simplified.
I have a handler defined for my index page route as below:
app.get('/', function(req, res) {
res.html(`<h1>Index Page</h1>`);
});
Then I want to add handler to show "not found" page for all other routes
app.get('*', function(req, res) {
const accept = req.headers.get('Accept');
if (accept.match(/text\/html/)) {
res.html(render(`<h1>Page not found</h1>`));
} else {
res.fetch(req);
}
});
Unfortunately when added this wildcard handler following after index route, then index route is not working anymore as expected and instead it shows "not found" message for defined index route as well.
Add a way to see the source code of the demo. It should show the demo and the code side by side.
When using the FileSystem middleware and a binary resource, like an image file for example, is retrieved, the returned data is broken.
There is a need for me to not proxy all routes by fetch listener, if a route capture handler is provided outside event.respondWith
, that could be helpful.
self.addEventListener("fetch", (event) => {
if(routeCapture(event.request)) {
event.respondWith(promise.catch(() => {}));
}
});
routeCapture
could be like this:
({ request }) => isMatch(request)
In workbox, a capture
is like:
RegExp | string | RouteMatchCallback | Route
In current case, a normal route of request that not registered by app could resolve by fetch
as following:
fetch(event.request).then(resolve).catch(reject);
In my experience, a fetch proxy by service worker could lead to some unknown problems that's what concerns me. For example, the referer header of request could be modified to sw.js
if it's proxy by fetch.
Ciao,
I'm enjoying your little library but still cannot wrap my mind around an example of using a POST endpoint. You provided plenty of GET examples, and they helped, but POST and how to handle the body would really help.
I'm working on it, when (if) I'll succeed I'll provide a PR!
Best,
rosdec
In orinal article code there is:
resolve(Response.redirect(url + '/', 301));
The library should be able to do that.
Possible API:
app.get('/something', (req, res) => {
res.redirect(`${req.url}/`, 301);
});
TODO:
app.get('/message', function(req, res) {
res.text('Lorem Ipsum');
}, { ignoreCaseSensitive: true });
when ignoreCaseSensitive
param provided, we can match routes like /message
or /MESSAGE
that case not sensitive any more.
Sometime we want to match some requests that url case pattern not sure, but we know case not sensitive on service. So we don't
need to register paths for all different cases.
/restapi/v1/getUsers
/restapi/v1/getusers
Based on this https://github.com/shoom3301/ssegwsw/blob/master/sw.js
The API can be app.sse()
like in Javalin
While working on a demo to validate that the #26 fix works.
Found that the Unicode filenames don't work in the demo.
I am using wayne along with htmx and I would like elements to subscribe for specific events as in docs
<div hx-sse="connect:/sse">
<div hx-sse="swap:eventName1">
...
</div>
<div hx-sse="swap:eventName2">
...
</div>
</div>
then accordingly broadcast named events
app.get('/sse', function(req, res) {
//...
stream.send({ name: 'eventName1', data: '' } );
});
There is should be a way to have what's you can in express.js:
'/login(/:token)?'
with the Wayne syntax:
'/login(/{token})?'
Add support for:
There is a need for the API that will just use the same request but to the server. This similar to #22 that is not enough.
Consider code of syntax highlighting:
app.get('*', async function(req, res) {
const url = new URL(req.url);
const extension = path.extname(url.pathname).substring(1);
const language = language_map[extension];
const accept = req.headers.get('Accept');
if (language && Prism.languages[language] && accept.match(/text\/html/)) {
const code = await fetch(req.url).then(res => res.text());
const grammar = Prism.languages[language];
const tokens = Prism.tokenize(code, grammar);
const output = Prism.Token.stringify(tokens, language);
res.html(`${style}\n<pre><code class="language-${language}">${output}</code></pre>`);
} else {
const fetch_res = await fetch(req.url);
res._resolve(fetch_res);
}
});
from gh-pages branch: sw.js.
So there is a need to use hidden _resolve
method that accept response object.
It seems that ES Modules in Service Worker are not widely supported. So for this case, there is a need to generate a UMD file, while we are at it we can also transpile to a more widely supported ECMAScript.
Is there a proper way to call app.get without including a res method? Basically using the route just to execute arbitrary code. This seemed easier before the last version: now every fetch event seems to cause loading?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.