This commit is contained in:
velzie 2024-07-31 11:59:38 -04:00
commit 30c95934ed
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
17 changed files with 200 additions and 143 deletions

View file

@ -1,27 +0,0 @@
import { defineConfig } from "@farmfe/core";
// import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
import { join } from "path";
import sj from "scramjet-farm-plugin";
export default defineConfig({
plugins: [sj()],
compilation: {
presetEnv: false,
mode: "development",
sourcemap: false,
input: {
worker: "src/worker/index.ts",
thread: "src/thread/thread.ts",
client: "src/client/index.ts",
config: "src/scramjet.config.ts",
},
output: {
path: "dist",
format: "cjs",
targetEnv: "browser-esnext",
entryFilename: "scramjet.[entryName].js",
filename: "scramjet.split.[name].js",
},
},
});

View file

@ -33,36 +33,39 @@ fn create_encode_function(encode: JsValue) -> EncodeFn {
}) })
} }
fn get_obj(config: &JsValue, k: &str) -> JsValue { fn get_obj(obj: &JsValue, k: &str) -> JsValue {
Reflect::get(config, &k.into()).unwrap() Reflect::get(obj, &k.into()).unwrap()
} }
fn get_str(config: &JsValue, k: &str) -> String { fn get_str(obj: &JsValue, k: &str) -> String {
Reflect::get(config, &k.into()) Reflect::get(obj, &k.into())
.unwrap() .unwrap()
.as_string() .as_string()
.unwrap() .unwrap()
} }
fn get_config(config: Object) -> Config { fn get_config(scramjet: &Object) -> Config {
let codec = &get_obj(scramjet, "codec");
let config = &get_obj(scramjet, "config");
Config { Config {
prefix: get_str(&config, "prefix"), prefix: get_str(config, "prefix"),
encode: create_encode_function(get_obj(&get_obj(&config, "codec"), "encode")), encode: create_encode_function(get_obj(codec, "encode")),
wrapfn: get_str(&config, "wrapfn"), wrapfn: get_str(config, "wrapfn"),
importfn: get_str(&config, "importfn"), importfn: get_str(config, "importfn"),
rewritefn: get_str(&config, "rewritefn"), rewritefn: get_str(config, "rewritefn"),
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn rewrite_js(js: &str, url: &str, config: Object) -> Vec<u8> { pub fn rewrite_js(js: &str, url: &str, scramjet: &Object) -> Vec<u8> {
rewrite(js, Url::from_str(url).unwrap(), get_config(config)) rewrite(js, Url::from_str(url).unwrap(), get_config(scramjet))
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn rewrite_js_from_arraybuffer(js: &[u8], url: &str, config: Object) -> Vec<u8> { pub fn rewrite_js_from_arraybuffer(js: &[u8], url: &str, scramjet: &Object) -> Vec<u8> {
// we know that this is a valid utf-8 string // we know that this is a valid utf-8 string
let js = unsafe { std::str::from_utf8_unchecked(js) }; let js = unsafe { std::str::from_utf8_unchecked(js) };
rewrite(js, Url::from_str(url).unwrap(), get_config(config)) rewrite(js, Url::from_str(url).unwrap(), get_config(scramjet))
} }

View file

@ -1,39 +0,0 @@
import typescript from "rollup-plugin-typescript2";
import { join } from "node:path";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import { nodeResolve } from "@rollup/plugin-node-resolve";
// check if its
const production = !process.env.ROLLUP_WATCH;
console.log(production);
fs.rmSync(join(fileURLToPath(new URL(".", import.meta.url)), "./dist"), {
recursive: true,
force: true,
});
const commonPlugins = () => [
typescript({
tsconfig: "tsconfig.json",
}),
nodeResolve(),
];
export default {
plugins: commonPlugins(),
input: {
client: "./src/client/index.ts",
worker: "./src/worker/index.ts",
config: "./src/scramjet.config.ts",
},
output: {
entryFileNames: "scramjet.[name].js",
dir: "./dist",
format: "system",
bundle: true,
minify: production,
sourcemap: true,
treeshake: "recommended",
},
};

View file

@ -16,6 +16,7 @@ export default defineConfig({
client: join(__dirname, "src/client/index.ts"), client: join(__dirname, "src/client/index.ts"),
config: join(__dirname, "src/scramjet.config.ts"), config: join(__dirname, "src/scramjet.config.ts"),
codecs: join(__dirname, "src/codecs/index.ts"), codecs: join(__dirname, "src/codecs/index.ts"),
bootstrapper: join(__dirname, "src/bootsrapper/index.ts"),
}, },
resolve: { resolve: {
extensions: [".ts", ".js"], extensions: [".ts", ".js"],
@ -44,6 +45,11 @@ export default defineConfig({
type: "javascript/auto", type: "javascript/auto",
}, },
], ],
parser: {
javascript: {
dynamicImportMode: "eager",
},
},
}, },
output: { output: {
filename: "scramjet.[name].js", filename: "scramjet.[name].js",

View file

@ -76,7 +76,7 @@ fastify.register(fastifyStatic, {
}); });
fastify.listen({ fastify.listen({
port: process.env.PORT || 1337, port: process.env.PORT || 1337,
host: "0.0.0.0", // host: "0.0.0.0",
}); });
writeFileSync( writeFileSync(

73
src/bootsrapper/index.ts Normal file
View file

@ -0,0 +1,73 @@
import IDBMap from "idb-map-entries";
import { ScramjetConfig } from "../types";
import { Codec } from "../codecs";
export class ScramjetBootstrapper {
config: ScramjetConfig;
private store: IDBMap;
codec: Codec;
constructor(config: ScramjetConfig) {
const defaultConfig = {
prefix: "/scramjet/",
codec: "plain",
wrapfn: "$scramjet$wrap",
trysetfn: "$scramjet$tryset",
importfn: "$scramjet$import",
rewritefn: "$scramjet$rewrite",
shared: "/scramjet.shared.js",
worker: "/scramjet.worker.js",
thread: "/scramjet.thread.js",
client: "/scramjet.client.js",
codecs: "/scramjet.codecs.js",
};
this.config = Object.assign({}, defaultConfig, config);
// rspack won't let me use a dynamic import
fetch(config.codecs).then(async (response) => {
eval(await response.text());
self.$scramjet.codec = self.$scramjet.codecs[this.config.codec];
self.$scramjet.config = this.config;
});
console.log(this.config);
this.store = new IDBMap("config", {
prefix: "scramjet",
});
this.saveConfig();
}
registerSw(serviceWorkerPath: string) {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(serviceWorkerPath, {
scope: this.config.prefix,
})
.then((registration) => {
console.log(
"ServiceWorker registration successful with scope: ",
registration.scope
);
})
.catch((err) => {
console.log("ServiceWorker registration failed: ", err);
});
}
}
saveConfig() {
this.store.set("config", this.config).then(() => {
console.log("scramjet config saved");
});
}
modifyConfig(config: ScramjetConfig) {
this.config = Object.assign({}, this.config, config);
this.saveConfig();
}
}
window.ScramjetBootstrapper = ScramjetBootstrapper;

View file

@ -1,18 +1,18 @@
if (!self.$scramjet) { // if (!self.$scramjet) {
//@ts-expect-error really dumb workaround // //@ts-expect-error really dumb workaround
self.$scramjet = {}; // self.$scramjet = {};
} // }
self.$scramjet.config = { // self.$scramjet.config = {
prefix: "/scramjet/", // prefix: "/scramjet/",
codec: self.$scramjet.codecs.plain, // codec: self.$scramjet.codecs.plain,
wrapfn: "$scramjet$wrap", // wrapfn: "$scramjet$wrap",
trysetfn: "$scramjet$tryset", // trysetfn: "$scramjet$tryset",
importfn: "$scramjet$import", // importfn: "$scramjet$import",
rewritefn: "$scramjet$rewrite", // rewritefn: "$scramjet$rewrite",
config: "/scram/scramjet.config.js", // config: "/scram/scramjet.config.js",
shared: "/scram/scramjet.shared.js", // shared: "/scram/scramjet.shared.js",
worker: "/scram/scramjet.worker.js", // worker: "/scram/scramjet.worker.js",
thread: "/scram/scramjet.thread.js", // thread: "/scram/scramjet.thread.js",
client: "/scram/scramjet.client.js", // client: "/scram/scramjet.client.js",
codecs: "/scram/scramjet.codecs.js", // codecs: "/scram/scramjet.codecs.js",
}; // };

View file

@ -1,5 +1,5 @@
import { Parser } from "htmlparser2"; import { ElementType, Parser } from "htmlparser2";
import { DomHandler, Element } from "domhandler"; import { DomHandler, Element, Text } from "domhandler";
import { hasAttrib } from "domutils"; import { hasAttrib } from "domutils";
import render from "dom-serializer"; import render from "dom-serializer";
import { encodeUrl } from "./url"; import { encodeUrl } from "./url";
@ -8,7 +8,7 @@ import { rewriteJs } from "./js";
export function isScramjetFile(src: string) { export function isScramjetFile(src: string) {
let bool = false; let bool = false;
["codecs", "client", "shared", "worker", "config"].forEach((file) => { ["codecs", "client", "shared", "worker"].forEach((file) => {
if (src === self.$scramjet.config[file]) bool = true; if (src === self.$scramjet.config[file]) bool = true;
}); });
@ -95,12 +95,14 @@ function traverseParsedHtml(node, origin?: URL) {
/(application|text)\/javascript|module|importmap|undefined/.test( /(application|text)\/javascript|module|importmap|undefined/.test(
node.attribs.type node.attribs.type
) && ) &&
node.children[0] !== undefined node.children[0] !== undefined &&
!node.attribs["data-scramjet"]
) { ) {
let js = node.children[0].data; let js = node.children[0].data;
const htmlcomment = /<!--[\s\S]*?-->/g; const htmlcomment = /<!--[\s\S]*?-->/g;
js = js.replace(htmlcomment, ""); js = js.replace(htmlcomment, "");
node.children[0].data = rewriteJs(js, origin); node.children[0].data = rewriteJs(js, origin);
console.log(node.children);
} }
if (node.name === "meta" && hasAttrib(node, "http-equiv")) { if (node.name === "meta" && hasAttrib(node, "http-equiv")) {
if (node.attribs["http-equiv"] === "content-security-policy") { if (node.attribs["http-equiv"] === "content-security-policy") {
@ -117,17 +119,32 @@ function traverseParsedHtml(node, origin?: URL) {
} }
if (node.name === "head") { if (node.name === "head") {
const scramjetScripts = []; const scripts = [];
["codecs", "config", "shared", "client"].forEach((script) => {
scramjetScripts.push( const codecs = new Element("script", {
new Element("script", { src: self.$scramjet.config["codecs"],
src: self.$scramjet.config[script], "data-scramjet": "true",
"data-scramjet": "", });
}) const config = new Element("script", {
); src:
"data:application/javascript;base64," +
btoa(
`self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)}; self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];`
),
"data-scramjet": "true",
});
const shared = new Element("script", {
src: self.$scramjet.config["shared"],
"data-scramjet": "true",
});
const client = new Element("script", {
src: self.$scramjet.config["client"],
"data-scramjet": "true",
}); });
node.children.unshift(...scramjetScripts); scripts.push(codecs, config, shared, client);
node.children.unshift(...scripts);
} }
if (node.childNodes) { if (node.childNodes) {

View file

@ -26,13 +26,13 @@ export function rewriteJs(js: string | ArrayBuffer, origin?: URL) {
const before = performance.now(); const before = performance.now();
if (typeof js === "string") { if (typeof js === "string") {
js = new TextDecoder().decode( js = new TextDecoder().decode(
rewrite_js(js, origin.toString(), self.$scramjet.config) rewrite_js(js, origin.toString(), self.$scramjet)
); );
} else { } else {
js = rewrite_js_from_arraybuffer( js = rewrite_js_from_arraybuffer(
new Uint8Array(js), new Uint8Array(js),
origin.toString(), origin.toString(),
self.$scramjet.config self.$scramjet
); );
} }
const after = performance.now(); const after = performance.now();

View file

@ -21,7 +21,7 @@ export function encodeUrl(url: string | URL, origin?: URL) {
origin = new URL(new URL(location.href).searchParams.get("origin")); origin = new URL(new URL(location.href).searchParams.get("origin"));
} else } else
origin = new URL( origin = new URL(
self.$scramjet.config.codec.decode( self.$scramjet.codec.decode(
location.href.slice( location.href.slice(
(location.origin + self.$scramjet.config.prefix).length (location.origin + self.$scramjet.config.prefix).length
) )
@ -40,7 +40,7 @@ export function encodeUrl(url: string | URL, origin?: URL) {
return ( return (
location.origin + location.origin +
self.$scramjet.config.prefix + self.$scramjet.config.prefix +
self.$scramjet.config.codec.encode(new URL(url, origin).href) self.$scramjet.codec.encode(new URL(url, origin).href)
); );
} }
} }
@ -55,13 +55,13 @@ export function decodeUrl(url: string | URL) {
URL.canParse(url) && URL.canParse(url) &&
new URL(url).pathname.startsWith(self.$scramjet.config.prefix + "worker") new URL(url).pathname.startsWith(self.$scramjet.config.prefix + "worker")
) { ) {
return new URL(new URL(url).searchParams.get("origin")); return new URL(new URL(url).searchParams.get("origin")).href;
} }
if (/^(#|about|data|mailto|javascript)/.test(url)) { if (/^(#|about|data|mailto|javascript)/.test(url)) {
return url; return url;
} else if (canParseUrl(url)) { } else if (canParseUrl(url)) {
return self.$scramjet.config.codec.decode( return self.$scramjet.codec.decode(
url.slice((location.origin + self.$scramjet.config.prefix).length) url.slice((location.origin + self.$scramjet.config.prefix).length)
); );
} else { } else {

View file

@ -1,6 +1,6 @@
import { rewriteJs } from "./js"; import { rewriteJs } from "./js";
const clientscripts = ["codecs", "config", "shared", "client"]; const clientscripts = ["codecs", "shared", "client"];
export function rewriteWorkers(js: string | ArrayBuffer, origin?: URL) { export function rewriteWorkers(js: string | ArrayBuffer, origin?: URL) {
let dest = origin.searchParams.get("dest"); let dest = origin.searchParams.get("dest");
let type = origin.searchParams.get("type"); let type = origin.searchParams.get("type");
@ -24,6 +24,8 @@ export function rewriteWorkers(js: string | ArrayBuffer, origin?: URL) {
if (rewritten instanceof Uint8Array) { if (rewritten instanceof Uint8Array) {
rewritten = new TextDecoder().decode(rewritten); rewritten = new TextDecoder().decode(rewritten);
} }
str += `self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)};
self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];\n`;
str += "\n" + rewritten; str += "\n" + rewritten;
dbg.log("Rewrite", type, dest, str); dbg.log("Rewrite", type, dest, str);

32
src/types.d.ts vendored
View file

@ -1,3 +1,4 @@
import { ScramjetBootstrapper } from "./bootsrapper/index";
import { encodeUrl, decodeUrl } from "./shared/rewriters/url"; import { encodeUrl, decodeUrl } from "./shared/rewriters/url";
import { rewriteCss } from "./shared/rewriters/css"; import { rewriteCss } from "./shared/rewriters/css";
import { rewriteHtml, rewriteSrcset } from "./shared/rewriters/html"; import { rewriteHtml, rewriteSrcset } from "./shared/rewriters/html";
@ -9,6 +10,20 @@ import type { Codec } from "./codecs";
import { BareClient } from "@mercuryworkshop/bare-mux"; import { BareClient } from "@mercuryworkshop/bare-mux";
import { parseDomain } from "parse-domain"; import { parseDomain } from "parse-domain";
interface ScramjetConfig {
prefix: string;
codec: string;
wrapfn: string;
trysetfn: string;
importfn: string;
rewritefn: string;
shared: string;
worker: string;
thread: string;
client: string;
codecs: string;
}
declare global { declare global {
interface Window { interface Window {
$scramjet: { $scramjet: {
@ -31,27 +46,16 @@ declare global {
parseDomain: typeof parseDomain; parseDomain: typeof parseDomain;
}; };
}; };
config: { config: ScramjetConfig;
prefix: string;
codec: Codec;
wrapfn: string;
trysetfn: string;
importfn: string;
rewritefn: string;
config: string;
shared: string;
worker: string;
thread: string;
client: string;
codecs: string;
};
codecs: { codecs: {
none: Codec; none: Codec;
plain: Codec; plain: Codec;
base64: Codec; base64: Codec;
xor: Codec; xor: Codec;
}; };
codec: Codec;
}; };
WASM: string; WASM: string;
ScramjetBootstrapper: typeof ScramjetBootstrapper;
} }
} }

View file

@ -46,7 +46,7 @@ export async function swfetch(
try { try {
const url = new URL(decodeUrl(request.url)); const url = new URL(decodeUrl(request.url));
let headers = new Headers(); const headers = new Headers();
for (const [key, value] of request.headers.entries()) { for (const [key, value] of request.headers.entries()) {
headers.set(key, value); headers.set(key, value);
} }

View file

@ -1,3 +1,4 @@
import IDBMap from "idb-map-entries";
import { FakeServiceWorker } from "./fakesw"; import { FakeServiceWorker } from "./fakesw";
import { swfetch } from "./fetch"; import { swfetch } from "./fetch";
import { ScramjetThreadpool } from "./threadpool"; import { ScramjetThreadpool } from "./threadpool";
@ -18,10 +19,9 @@ export class ScramjetServiceWorker {
serviceWorkers: FakeServiceWorker[] = []; serviceWorkers: FakeServiceWorker[] = [];
constructor(config = self.$scramjet.config) { constructor() {
this.loadConfig();
this.client = new self.$scramjet.shared.util.BareClient(); this.client = new self.$scramjet.shared.util.BareClient();
if (!config.prefix) config.prefix = "/scramjet/";
this.config = config;
this.threadpool = new ScramjetThreadpool(); this.threadpool = new ScramjetThreadpool();
@ -46,6 +46,20 @@ export class ScramjetServiceWorker {
}); });
} }
loadConfig() {
const store = new IDBMap("config", {
prefix: "scramjet",
});
if (store.has("config")) {
store.get("config").then((config) => {
this.config = config;
self.$scramjet.config = config;
self.$scramjet.codec = self.$scramjet.codecs[config.codec];
});
}
}
async getLocalStorage(): Promise<Record<string, string>> { async getLocalStorage(): Promise<Record<string, string>> {
let clients = await self.clients.matchAll(); let clients = await self.clients.matchAll();
clients = clients.filter( clients = clients.filter(

View file

@ -32,8 +32,7 @@
<body> <body>
<script src="https://unpkg.com/dreamland"></script> <script src="https://unpkg.com/dreamland"></script>
<script src="/baremux/index.js" defer></script> <script src="/baremux/index.js" defer></script>
<script src="/scram/scramjet.codecs.js" defer></script> <script src="/scram/scramjet.bootstrapper.js"></script>
<script src="/scram/scramjet.config.js" defer></script>
<script src="ui.js" defer></script> <script src="ui.js" defer></script>
</body> </body>
</html> </html>

View file

@ -1,6 +1,5 @@
importScripts( importScripts(
"/scram/scramjet.codecs.js", "/scram/scramjet.codecs.js",
"/scram/scramjet.config.js",
"/scram/scramjet.shared.js", "/scram/scramjet.shared.js",
"/scram/scramjet.worker.js" "/scram/scramjet.worker.js"
); );

View file

@ -1,7 +1,13 @@
navigator.serviceWorker.register("./sw.js").then((reg) => { const bootstrapper = new ScramjetBootstrapper({
reg.update(); codecs: "/scram/scramjet.codecs.js",
worker: "/scram/scramjet.worker.js",
thread: "/scram/scramjet.thread.js",
client: "/scram/scramjet.client.js",
shared: "/scram/scramjet.shared.js",
}); });
bootstrapper.registerSw("./sw.js");
navigator.serviceWorker.ready.then((reg) => { navigator.serviceWorker.ready.then((reg) => {
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
const thread = new SharedWorker($scramjet.config.thread, { const thread = new SharedWorker($scramjet.config.thread, {
@ -159,7 +165,7 @@ function App() {
<button on:click=${() => window.open(this.urlencoded)}>open in fullscreen</button> <button on:click=${() => window.open(this.urlencoded)}>open in fullscreen</button>
</div> </div>
</div> </div>
<input class="bar" bind:value=${use(store.url)} on:input=${(e) => (store.url = e.target.value)} on:keyup=${(e) => e.keyCode == 13 && console.log((this.urlencoded = $scramjet.config.prefix + $scramjet.config.codec.encode(e.target.value)))}></input> <input class="bar" bind:value=${use(store.url)} on:input=${(e) => (store.url = e.target.value)} on:keyup=${(e) => e.keyCode == 13 && console.log((this.urlencoded = $scramjet.config.prefix + $scramjet.codec.encode(e.target.value)))}></input>
<iframe src=${use(this.urlencoded)}></iframe> <iframe src=${use(this.urlencoded)}></iframe>
</div> </div>
`; `;