scramjet/src/controller/index.ts
2024-12-15 23:27:46 -06:00

139 lines
3.4 KiB
TypeScript

import { ScramjetConfig } from "../types";
import { ScramjetFrame } from "./frame";
import { $scramjet, loadCodecs } from "../scramjet";
export class ScramjetController {
private db: IDBDatabase;
constructor(config: Partial<ScramjetConfig>) {
// sane ish defaults
const defaultConfig: ScramjetConfig = {
prefix: "/scramjet/",
globals: {
wrapfn: "$scramjet$wrap",
wrapthisfn: "$scramjet$wrapthis",
trysetfn: "$scramjet$tryset",
importfn: "$scramjet$import",
rewritefn: "$scramjet$rewrite",
metafn: "$scramjet$meta",
setrealmfn: "$scramjet$setrealm",
pushsourcemapfn: "$scramjet$pushsourcemap",
},
files: {
wasm: "/scramjet.wasm.wasm",
shared: "/scramjet.shared.js",
worker: "/scramjet.worker.js",
client: "/scramjet.client.js",
sync: "/scramjet.sync.js",
},
flags: {
serviceworkers: false,
syncxhr: false,
strictRewrites: true,
naiiveRewriter: false,
rewriterLogs: true,
captureErrors: true,
cleanErrors: false,
scramitize: false,
sourcemaps: false,
},
siteFlags: {},
codec: {
encode: `if (!url) return url;
return encodeURIComponent(url);`,
decode: `if (!url) return url;
return decodeURIComponent(url);`,
},
};
const deepMerge = (target: any, source: any): any => {
for (const key in source) {
if (source[key] instanceof Object && key in target) {
Object.assign(source[key], deepMerge(target[key], source[key]));
}
}
return Object.assign(target || {}, source);
};
$scramjet.config = deepMerge(defaultConfig, config);
}
async init(serviceWorkerPath: string): Promise<ServiceWorkerRegistration> {
loadCodecs();
await this.openIDB();
const reg = await navigator.serviceWorker.register(serviceWorkerPath);
dbg.log("service worker registered");
return reg;
}
createFrame(frame?: HTMLIFrameElement): ScramjetFrame {
if (!frame) {
frame = document.createElement("iframe");
}
return new ScramjetFrame(this, frame);
}
encodeUrl(url: string | URL): string {
if (url instanceof URL) url = url.toString();
return $scramjet.config.prefix + $scramjet.codec.encode(url);
}
decodeUrl(url: string | URL) {
if (url instanceof URL) url = url.toString();
return $scramjet.codec.decode(url);
}
async openIDB(): Promise<IDBDatabase> {
const db = indexedDB.open("$scramjet", 1);
return new Promise<IDBDatabase>((resolve, reject) => {
db.onsuccess = async () => {
this.db = db.result;
await this.#saveConfig();
resolve(db.result);
};
db.onupgradeneeded = () => {
const res = db.result;
if (!res.objectStoreNames.contains("config")) {
res.createObjectStore("config");
}
if (!res.objectStoreNames.contains("cookies")) {
res.createObjectStore("cookies");
}
};
db.onerror = () => reject(db.error);
});
}
async #saveConfig() {
if (!this.db) {
console.error("Store not ready!");
return;
}
const tx = this.db.transaction("config", "readwrite");
const store = tx.objectStore("config");
const req = store.put($scramjet.config, "config");
return new Promise((resolve, reject) => {
req.onsuccess = resolve;
req.onerror = reject;
});
}
async modifyConfig(config: ScramjetConfig) {
$scramjet.config = Object.assign({}, $scramjet.config, config);
loadCodecs();
await this.#saveConfig();
}
}
window.ScramjetController = ScramjetController;