refactor: delete codecs/ & self.$scramjet

This commit is contained in:
velzie 2024-10-13 10:20:19 -04:00
parent f8d33a207e
commit 337134bcdc
No known key found for this signature in database
GPG key ID: AA51AEFB0A1F3820
22 changed files with 148 additions and 206 deletions

View file

@ -49,17 +49,18 @@ fn get_config(scramjet: &Object) -> Config {
let codec = &get_obj(scramjet, "codec");
let config = &get_obj(scramjet, "config");
let flags = &get_obj(config, "flags");
let globals = &get_obj(config, "globals");
Config {
prefix: get_str(config, "prefix"),
encode: create_encode_function(get_obj(codec, "encode")),
wrapfn: get_str(config, "wrapfn"),
importfn: get_str(config, "importfn"),
rewritefn: get_str(config, "rewritefn"),
metafn: get_str(config, "metafn"),
setrealmfn: get_str(config, "setrealmfn"),
pushsourcemapfn: get_str(config, "pushsourcemapfn"),
wrapfn: get_str(globals, "wrapfn"),
importfn: get_str(globals, "importfn"),
rewritefn: get_str(globals, "rewritefn"),
metafn: get_str(globals, "metafn"),
setrealmfn: get_str(globals, "setrealmfn"),
pushsourcemapfn: get_str(globals, "pushsourcemapfn"),
do_sourcemaps: get_bool(flags, "sourcemaps"),
capture_errors: get_bool(flags, "captureErrors"),

View file

@ -419,7 +419,7 @@ fn expression_span(e: &Expression) -> Span {
}
// js MUST not be able to get a reference to any of these because sbx
const UNSAFE_GLOBALS: [&str; 9] = [
const UNSAFE_GLOBALS: [&str; 10] = [
"window",
"self",
"globalThis",

View file

@ -15,9 +15,7 @@ export default defineConfig({
entry: {
shared: join(__dirname, "src/shared/index.ts"),
worker: join(__dirname, "src/worker/index.ts"),
thread: join(__dirname, "src/thread/thread.ts"),
client: join(__dirname, "src/client/index.ts"),
codecs: join(__dirname, "src/codecs/index.ts"),
controller: join(__dirname, "src/controller/index.ts"),
sync: join(__dirname, "src/sync.ts"),
},

View file

@ -1,5 +1,6 @@
// entrypoint for scramjet.client.js
import { loadCodecs } from "../scramjet";
import { SCRAMJETCLIENT } from "../symbols";
import { ScramjetClient } from "./client";
import { ScramjetServiceWorkerRuntime } from "./swruntime";
@ -19,6 +20,8 @@ export class ScramjetContextInit extends Event {
dbg.log("scrammin");
// if it already exists, that means the handlers have probably already been setup by the parent document
if (!(SCRAMJETCLIENT in <Partial<typeof self>>self)) {
loadCodecs();
const client = new ScramjetClient(self);
if (self.COOKIE) client.loadcookies(self.COOKIE);

View file

@ -4,7 +4,7 @@ import { config, rewriteJs } from "../../shared";
export default function (client: ScramjetClient, self: Self) {
// used for proxying *direct eval*
// eval("...") -> eval($scramjet$rewrite("..."))
Object.defineProperty(self, config.rewritefn, {
Object.defineProperty(self, config.globals.rewritefn, {
value: function (js: any) {
if (typeof js !== "string") return js;

View file

@ -5,7 +5,7 @@ import { rewriteUrl } from "../../shared/rewriters/url";
export default function (client: ScramjetClient, self: Self) {
const Function = client.natives.Function;
self[config.importfn] = function (base: string) {
self[config.globals.importfn] = function (base: string) {
return function (url: string) {
const resolved = new URL(url, base).href;
@ -15,7 +15,7 @@ export default function (client: ScramjetClient, self: Self) {
};
};
self[config.metafn] = function (base: string) {
self[config.globals.metafn] = function (base: string) {
return {
url: base,
resolve: function (url: string) {

View file

@ -60,7 +60,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) {
// the main magic of the proxy. all attempts to access any "banned objects" will be redirected here, and instead served a proxy object
// this contrasts from how other proxies will leave the root object alone and instead attempt to catch every member access
// this presents some issues (see element.ts), but makes us a good bit faster at runtime!
Object.defineProperty(self, config.wrapfn, {
Object.defineProperty(self, config.globals.wrapfn, {
value: client.wrapfn,
writable: false,
configurable: false,
@ -81,7 +81,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) {
// ((t)=>$scramjet$tryset(location,"+=",t)||location+=t)(...);
// it has to be a discrete function because there's always the possibility that "location" is a local variable
// we have to use an IIFE to avoid duplicating side-effects in the getter
Object.defineProperty(self, config.trysetfn, {
Object.defineProperty(self, config.globals.trysetfn, {
value: function (lhs: any, op: string, rhs: any) {
if (lhs instanceof Location) {
// @ts-ignore

View file

@ -1,79 +0,0 @@
// for some reason eslint was parsing the type inside of the function params as a variable
export interface Codec {
// eslint-disable-next-line
encode: (str: string | undefined) => string;
// eslint-disable-next-line
decode: (str: string | undefined) => string;
}
const none = {
encode: (str: string | undefined) => str,
decode: (str: string | undefined) => str,
};
const plain = {
encode: (str: string | undefined) => {
if (!str) return str;
return encodeURIComponent(str);
},
decode: (str: string | undefined) => {
if (!str) return str;
return decodeURIComponent(str);
},
};
const xor = {
encode: (str: string | undefined, key: number = 2) => {
if (!str) return str;
let result = "";
for (let i = 0; i < str.length; i++) {
result += i % key ? String.fromCharCode(str.charCodeAt(i) ^ key) : str[i];
}
return encodeURIComponent(result);
},
decode: (str: string | undefined, key: number = 2) => {
if (!str) return str;
const [input, ...search] = str.split("?");
let result = "";
const decoded = decodeURIComponent(input);
for (let i = 0; i < decoded.length; i++) {
result +=
i % key ? String.fromCharCode(decoded.charCodeAt(i) ^ key) : decoded[i];
}
return result + (search.length ? "?" + search.join("?") : "");
},
};
const base64 = {
encode: (str: string | undefined) => {
if (!str) return str;
return decodeURIComponent(btoa(str));
},
decode: (str: string | undefined) => {
if (!str) return str;
return atob(str);
},
};
if (typeof self.$scramjet === "undefined") {
//@ts-expect-error really dumb workaround
self.$scramjet = {};
}
self.$scramjet.codecs = {
none,
plain,
xor,
base64,
};
if ("document" in self && document.currentScript) {
document.currentScript.remove();
}

View file

@ -1,16 +1,17 @@
import { ScramjetConfig } from "../types";
import { Codec } from "../codecs";
import { ScramjetFrame } from "./frame";
import { $scramjet, loadCodecs } from "../scramjet";
export class ScramjetController {
config: ScramjetConfig;
private store: IDBDatabase;
codec: Codec;
constructor(config: ScramjetConfig) {
constructor(config: Partial<ScramjetConfig>) {
// sane ish defaults
const defaultConfig: Partial<ScramjetConfig> = {
prefix: "/scramjet/",
codec: "plain",
globals: {
wrapfn: "$scramjet$wrap",
trysetfn: "$scramjet$tryset",
importfn: "$scramjet$import",
@ -18,13 +19,14 @@ export class ScramjetController {
metafn: "$scramjet$meta",
setrealmfn: "$scramjet$setrealm",
pushsourcemapfn: "$scramjet$pushsourcemap",
},
files: {
wasm: "/scramjet.wasm.js",
shared: "/scramjet.shared.js",
worker: "/scramjet.worker.js",
thread: "/scramjet.thread.js",
client: "/scramjet.client.js",
codecs: "/scramjet.codecs.js",
sync: "/scramjet.sync.js",
},
flags: {
serviceworkers: false,
naiiveRewriter: false,
@ -34,6 +36,12 @@ export class ScramjetController {
scramitize: false,
sourcemaps: false,
},
codec: {
encode: `if (!url) return url;
return encodeURIComponent(url);`,
decode: `if (!url) return url;
return decodeURIComponent(url);`,
},
};
const deepMerge = (target: any, source: any): any => {
@ -46,12 +54,11 @@ export class ScramjetController {
return Object.assign(target || {}, source);
};
this.config = deepMerge(defaultConfig, config);
$scramjet.config = deepMerge(defaultConfig, config);
}
async init(serviceWorkerPath: string): Promise<ServiceWorkerRegistration> {
await import(/* webpackIgnore: true */ this.config.codecs);
this.codec = self.$scramjet.codecs[this.config.codec];
loadCodecs();
await this.openIDB();
@ -72,7 +79,7 @@ export class ScramjetController {
encodeUrl(url: string | URL): string {
if (url instanceof URL) url = url.toString();
return this.config.prefix + this.codec.encode(url);
return $scramjet.config.prefix + $scramjet.codec.encode(url);
}
async openIDB(): Promise<IDBDatabase> {
@ -102,7 +109,7 @@ export class ScramjetController {
}
const tx = this.store.transaction("config", "readwrite");
const store = tx.objectStore("config");
const req = store.put(this.config, "config");
const req = store.put($scramjet.config, "config");
return new Promise((resolve, reject) => {
req.onsuccess = resolve;
@ -111,8 +118,8 @@ export class ScramjetController {
}
async modifyConfig(config: ScramjetConfig) {
this.config = Object.assign({}, this.config, config);
this.codec = self.$scramjet.codecs[this.config.codec];
$scramjet.config = Object.assign({}, $scramjet.config, config);
loadCodecs();
await this.#saveConfig();
}

3
src/global.d.ts vendored
View file

@ -5,4 +5,7 @@ declare const dbg: {
debug: (message: string, ...args: any[]) => void;
};
declare const COMMITHASH: string;
declare const VERSION: string;
declare type Self = Window & typeof globalThis;

26
src/scramjet.ts Normal file
View file

@ -0,0 +1,26 @@
import { ScramjetConfig } from "./types";
if (!("$scramjet" in self)) {
// @ts-expect-error ts stuff
self.$scramjet = {
version: {
build: COMMITHASH,
version: VERSION,
},
codec: {},
};
}
export const $scramjet = self.$scramjet;
const nativeFunction = Function;
export function loadCodecs() {
$scramjet.codec.encode = nativeFunction(
"url",
$scramjet.config.codec.encode
) as any;
$scramjet.codec.decode = nativeFunction(
"url",
$scramjet.config.codec.decode
) as any;
}

View file

@ -1,3 +1,5 @@
import { $scramjet } from "./scramjet";
export const {
util: { BareClient, ScramjetHeaders, BareMuxConnection },
url: { rewriteUrl, unrewriteUrl, rewriteBlob, unrewriteBlob },
@ -13,6 +15,6 @@ export const {
htmlRules,
},
CookieStore,
} = self.$scramjet.shared;
} = $scramjet.shared;
export const config = self.$scramjet.config;
export const config = $scramjet.config;

View file

@ -14,8 +14,9 @@ import { parseDomain } from "parse-domain";
import { ScramjetHeaders } from "./headers";
import { CookieStore } from "./cookie";
import { htmlRules, unrewriteHtml } from "./rewriters/html";
import { $scramjet } from "../scramjet";
self.$scramjet.shared = {
$scramjet.shared = {
util: {
parseDomain,
BareClient,

View file

@ -6,6 +6,7 @@ import { rewriteCss } from "./css";
import { rewriteJs } from "./js";
import { CookieStore } from "../cookie";
import { unrewriteBlob } from "../../shared/rewriters/url";
import { $scramjet } from "../../scramjet";
export function rewriteHtml(
html: string,
@ -43,8 +44,7 @@ export function rewriteHtml(
const dump = JSON.stringify(cookieStore.dump());
const injected = `
self.COOKIE = ${dump};
self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)};
self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];
self.$scramjet.config = ${JSON.stringify($scramjet.config)};
if ("document" in self && document.currentScript) {
document.currentScript.remove();
}
@ -53,11 +53,10 @@ export function rewriteHtml(
const script = (src) => new Element("script", { src });
head.children.unshift(
script(self.$scramjet.config["wasm"]),
script(self.$scramjet.config["codecs"]),
script($scramjet.config.files.wasm),
script($scramjet.config.files.shared),
script("data:application/javascript;base64," + btoa(injected)),
script(self.$scramjet.config["shared"]),
script(self.$scramjet.config["client"])
script($scramjet.config.files.client)
);
}

View file

@ -7,6 +7,7 @@ import {
rewrite_js,
rewrite_js_from_arraybuffer,
} from "../../../rewriter/out/rewriter.js";
import { $scramjet } from "../../scramjet";
initSync({
module: new WebAssembly.Module(
@ -19,7 +20,7 @@ init();
Error.stackTraceLimit = 50;
export function rewriteJs(js: string | ArrayBuffer, meta: URLMeta) {
if (self.$scramjet.config.flags.naiiveRewriter) {
if ($scramjet.config.flags.naiiveRewriter) {
const text = typeof js === "string" ? js : new TextDecoder().decode(js);
return rewriteJsNaiive(text);
@ -27,14 +28,12 @@ export function rewriteJs(js: string | ArrayBuffer, meta: URLMeta) {
const before = performance.now();
if (typeof js === "string") {
js = new TextDecoder().decode(
rewrite_js(js, meta.base.href, self.$scramjet)
);
js = new TextDecoder().decode(rewrite_js(js, meta.base.href, $scramjet));
} else {
js = rewrite_js_from_arraybuffer(
new Uint8Array(js),
meta.base.href,
self.$scramjet
$scramjet
);
}
const after = performance.now();
@ -56,7 +55,7 @@ export function rewriteJsNaiive(js: string | ArrayBuffer) {
}
return `
with (${self.$scramjet.config.wrapfn}(globalThis)) {
with (${$scramjet.config.globals.wrapfn}(globalThis)) {
${js}

View file

@ -1,3 +1,4 @@
import { $scramjet } from "../../scramjet";
import { rewriteJs } from "./js";
export type URLMeta = {
@ -31,9 +32,9 @@ export function rewriteUrl(url: string | URL, meta: URLMeta) {
if (url.startsWith("javascript:")) {
return "javascript:" + rewriteJs(url.slice("javascript:".length), meta);
} else if (url.startsWith("blob:")) {
return location.origin + self.$scramjet.config.prefix + url;
return location.origin + $scramjet.config.prefix + url;
} else if (url.startsWith("data:")) {
return location.origin + self.$scramjet.config.prefix + url;
return location.origin + $scramjet.config.prefix + url;
} else if (url.startsWith("mailto:") || url.startsWith("about:")) {
return url;
} else {
@ -43,8 +44,8 @@ export function rewriteUrl(url: string | URL, meta: URLMeta) {
return (
location.origin +
self.$scramjet.config.prefix +
self.$scramjet.codec.encode(new URL(url, base).href)
$scramjet.config.prefix +
$scramjet.codec.encode(new URL(url, base).href)
);
}
}
@ -54,7 +55,7 @@ export function unrewriteUrl(url: string | URL) {
url = url.href;
}
const prefixed = location.origin + self.$scramjet.config.prefix;
const prefixed = location.origin + $scramjet.config.prefix;
if (url.startsWith("javascript:")) {
//TODO
@ -69,8 +70,8 @@ export function unrewriteUrl(url: string | URL) {
} else if (url.startsWith("mailto:") || url.startsWith("about:")) {
return url;
} else if (tryCanParseURL(url)) {
return self.$scramjet.codec.decode(
url.slice((location.origin + self.$scramjet.config.prefix).length)
return $scramjet.codec.decode(
url.slice((location.origin + $scramjet.config.prefix).length)
);
} else {
return url;

View file

@ -1,3 +1,4 @@
import { $scramjet } from "../../scramjet";
import { rewriteJs } from "./js";
import { URLMeta } from "./url";
@ -9,22 +10,11 @@ export function rewriteWorkers(
) {
let str = "";
str += `self.$scramjet = {}; self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)};
`;
str += "";
for (const script of clientscripts) {
if (type === "module") {
str += `import "${self.$scramjet.config["codecs"]}"
self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];
`;
for (const script of clientscripts) {
str += `import "${self.$scramjet.config[script]}"\n`;
}
str += `import "${$scramjet.config.files[script]}"\n`;
} else {
str += `importScripts("${self.$scramjet.config["codecs"]}");
self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];
`;
for (const script of clientscripts) {
str += `importScripts("${self.$scramjet.config[script]}");\n`;
str += `importScripts("${$scramjet.config.files[script]}");\n`;
}
}

22
src/types.d.ts vendored
View file

@ -36,7 +36,7 @@ type ScramjetFlags = {
interface ScramjetConfig {
prefix: string;
codec: string;
globals: {
wrapfn: string;
trysetfn: string;
importfn: string;
@ -44,15 +44,20 @@ interface ScramjetConfig {
metafn: string;
setrealmfn: string;
pushsourcemapfn: string;
};
files: {
wasm: string;
shared: string;
worker: string;
thread: string;
client: string;
codecs: string;
sync: string;
};
flags: ScramjetFlags;
siteflags?: Record<string, ScramjetFlags>;
siteflags: Record<string, ScramjetFlags>;
codec: {
encode: string;
decode: string;
};
}
declare global {
@ -85,13 +90,10 @@ declare global {
CookieStore: typeof CookieStore;
};
config: ScramjetConfig;
codecs: {
none: Codec;
plain: Codec;
base64: Codec;
xor: Codec;
codec: {
encode: (url: string) => string;
decode: (url: string) => string;
};
codec: Codec;
};
COOKIE: string;
WASM: string;

View file

@ -16,6 +16,7 @@ import {
} from "../shared";
import type { URLMeta } from "../shared/rewriters/url";
import { $scramjet } from "../scramjet";
function newmeta(url: URL): URLMeta {
return {
@ -115,7 +116,7 @@ export async function swfetch(
if (
client &&
new URL(client.url).pathname.startsWith(self.$scramjet.config.prefix)
new URL(client.url).pathname.startsWith($scramjet.config.prefix)
) {
// TODO: i was against cors emulation but we might actually break stuff if we send full origin/referrer always
const clientURL = new URL(unrewriteUrl(client.url));

View file

@ -3,22 +3,23 @@ import { swfetch } from "./fetch";
import { ScramjetThreadpool } from "./threadpool";
import type BareClient from "@mercuryworkshop/bare-mux";
import { ScramjetConfig } from "../types";
import { $scramjet, loadCodecs } from "../scramjet";
export class ScramjetServiceWorker extends EventTarget {
client: BareClient;
config: typeof self.$scramjet.config;
config: ScramjetConfig;
threadpool: ScramjetThreadpool;
syncPool: Record<number, (val?: any) => void> = {};
synctoken = 0;
cookieStore = new self.$scramjet.shared.CookieStore();
cookieStore = new $scramjet.shared.CookieStore();
serviceWorkers: FakeServiceWorker[] = [];
constructor() {
super();
this.client = new self.$scramjet.shared.util.BareClient();
this.client = new $scramjet.shared.util.BareClient();
this.threadpool = new ScramjetThreadpool();
@ -40,19 +41,6 @@ export class ScramjetServiceWorker extends EventTarget {
async loadConfig() {
if (this.config) return;
// const store = new IDBMap("config", {
// prefix: "scramjet",
// });
// if (store.has("config")) {
// const config = await store.get("config");
// this.config = config;
// self.$scramjet.config = config;
// self.$scramjet.codec = self.$scramjet.codecs[config.codec];
// }
// Recreate the above code using the stock IDB API
const request = indexedDB.open("$scramjet", 1);
return new Promise<void>((resolve, reject) => {
@ -64,8 +52,9 @@ export class ScramjetServiceWorker extends EventTarget {
config.onsuccess = () => {
this.config = config.result;
self.$scramjet.config = config.result;
self.$scramjet.codec = self.$scramjet.codecs[config.result.codec];
$scramjet.config = config.result;
loadCodecs();
resolve();
};

View file

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

View file

@ -1,11 +1,11 @@
const scramjet = new ScramjetController({
files: {
wasm: "/scram/scramjet.wasm.js",
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",
sync: "/scram/scramjet.sync.js",
},
});
scramjet.init("./sw.js");
@ -148,7 +148,7 @@ function App() {
return html`
<div>
<h1>Percury Unblocker</h1>
<h1>scramjet</h1>
<p>surf the unblocked and mostly buggy web</p>
<div class=${[flex, "cfg"]}>