Skip to content

Commit

Permalink
feat: configuration through url + context abstraction through rpc (#8)
Browse files Browse the repository at this point in the history
This PR removes direct dependency on browser's `MessageChannel` in favor
of an RPC implementation: https://github.com/microsoft/postmessage-rpc/

PR also allows configuration of the service worker through initial query
string parameters.
  • Loading branch information
3p3r committed Jun 24, 2024
1 parent 9851d71 commit 19564b3
Show file tree
Hide file tree
Showing 22 changed files with 2,651 additions and 336 deletions.
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "native tests",
"request": "launch",
"runtimeArgs": ["run-script", "test:native"],
"runtimeExecutable": "npm",
"skipFiles": ["<node_internals>/**"],
"type": "node"
}
]
}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fakettp

sandbox node core http module in a service worker.
sandbox node core http module in a service worker.

- [disclaimer](#disclaimer)
- [motivation](#motivation)
Expand Down Expand Up @@ -146,8 +146,10 @@ and if you attempt to send a request after that, you will get an error:
working samples:

- `ext/samples/express.ts`: express app with a dynamic route.
- `ext/samples/express-post.ts`: express app with a post route.
- `ext/samples/express-static.ts`: express app with static files.
- `ext/samples/socket-io.ts`: express app with socket io.
- `ext/samples/remote-context`: shows the usage of `fakettp.html`.

this is what is known to work good enough for most use cases:

Expand Down
5 changes: 3 additions & 2 deletions ext/bib.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// BIB: Browser In Browser.
export async function createBib(initialUrl: string) {
export async function createBib(initialUrl: string): Promise<HTMLIFrameElement> {
const html = `\
<div id="bib">
<iframe id="bib-frame" sandbox="allow-same-origin" seamless></iframe>
<iframe id="bib-frame" sandbox="allow-scripts allow-same-origin allow-modals" seamless></iframe>
<input id="bib-url" />
</div>`;
const parser = new DOMParser();
Expand Down Expand Up @@ -54,4 +54,5 @@ export async function createBib(initialUrl: string) {
frame.addEventListener("error", (err) => {
frame.srcdoc = err.message;
});
return frame;
}
30 changes: 30 additions & 0 deletions ext/samples/express-post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import cors from "cors";
import express from "express";
import { createServer } from "http";
import type { AddressInfo } from "net";

const app = express();
const server = createServer(app);

app.use(cors());

app.get("/", (req, res) => {
console.log("Request received.", req.url);
res.send("Hello From Express.");
});

app.post("/", (req, res) => {
console.log("POST request received.", req.url);
req.pipe(res);
});

server.listen(async () => {
console.log(`Server listening on ${server.address()}.`);
const address = server.address() as AddressInfo;
const scheme = address.port === 443 ? "https" : "http";
const url = `${scheme}://${address.address}:${address.port}/`;
const response = await fetch(url, { method: "POST", body: "Hello From Fetch." });
console.log("Response received.", response.status, response.statusText);
console.log("Response body:", await response.text());
alert("Check console for logs!");
});
40 changes: 40 additions & 0 deletions ext/samples/remote-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import express from "express";
import { createServer } from "http";
import type { AddressInfo } from "net";

import { createBib } from "../bib";
import { IFrameContext, setContext } from "../../src/context";

const params = new URLSearchParams(location.search);

params.set("d", ""); // enables debugging
params.set("i", new RegExp("localhost").source);
params.append("e", new RegExp("fakettp.html").source);
params.append("e", new RegExp("fakettp.js").source);
params.append("e", new RegExp("nosw.js").source);
params.append("e", new RegExp("sample-.*").source);

const serviceUrl = new URL(`fakettp.html?${params.toString()}`, location.href);

createBib(serviceUrl.href).then((frame) => {
frame.onload = () => {
const context = new IFrameContext(frame);
setContext(context);

const app = express();
const server = createServer(app);

app.get("/", (req, res) => {
console.log("Request received.", req.url);
res.send("Hello From Remote Context!");
});

server.listen(() => {
console.log(`Server listening on ${server.address()}.`);
const address = server.address() as AddressInfo;
const scheme = address.port === 443 ? "https" : "http";
const url = `${scheme}://${address.address}:${address.port}/`;
context.browse(url);
});
};
});
54 changes: 54 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// <reference lib="dom" />
declare module "fakettp" {
type CleanupReceiver = () => void | Promise<void>;
type MessageReceiver = (message: any) => void | Promise<void>;
export interface Context {
readonly postMessage: MessageReceiver;
readonly readMessages: (callback: MessageReceiver) => CleanupReceiver;
readonly reloadWorker?: () => void | Promise<void>;
readonly unloadWorker?: () => void | Promise<void>;
}
export function setContext(context: Context): void;
export function getContext(): Context;
export class WindowContext implements Context {
constructor(config?: { include?: RegExp[]; exclude?: RegExp[] });
postMessage(message: any): void;
readMessages(callback: MessageReceiver): CleanupReceiver;
reloadWorker(): Promise<void>;
unloadWorker(): Promise<void>;
}
export interface MessageEvent {
data: any;
}
export interface Postable {
postMessage(data: any): void;
}
export interface Receivable {
readMessages(callback: (ev: MessageEvent) => void): () => void;
}
export interface RPCOptions {
target: Postable;
serviceId: string;
receiver?: Receivable;
}
export class RPC {
readonly isReady: Promise<void>;
constructor(options: RPCOptions);
expose<T>(method: string, handler: (params: T) => Promise<any> | any): this;
call<T>(method: string, params?: object, waitForReply?: true): Promise<T>;
call(method: string, params?: object, waitForReply?: false): void;
destroy(): void;
}
export class RemoteContext implements Context {
protected readonly rpc: RPC;
constructor(rpc: RPC);
postMessage(message: any): void;
readMessages(callback: MessageReceiver): CleanupReceiver;
reloadWorker(): Promise<void>;
unloadWorker(): Promise<void>;
browse(url: string): Promise<void>;
}
export class IFrameContext extends RemoteContext {
constructor(frame: HTMLIFrameElement);
}
}
Loading

0 comments on commit 19564b3

Please sign in to comment.