diff --git a/packages/scramjet/packages/controller/src/index.ts b/packages/scramjet/packages/controller/src/index.ts index 7cc7dabb..1c6ac505 100644 --- a/packages/scramjet/packages/controller/src/index.ts +++ b/packages/scramjet/packages/controller/src/index.ts @@ -1,6 +1,5 @@ import { type MethodsDefinition, RpcHelper } from "@mercuryworkshop/rpc"; import type * as ScramjetGlobal from "@mercuryworkshop/scramjet"; - declare const $scramjet: typeof ScramjetGlobal; export let Plugin = $scramjet.Plugin; @@ -13,30 +12,40 @@ import { type WebSocketMessage, } from "./types"; import { - BareCompatibleClient, BareResponse, type ProxyTransport, } from "@mercuryworkshop/proxy-transports"; -const cookieJar = new $scramjet.CookieJar(); - type Config = { + prefix: string; + scramjetPath: string; wasmPath: string; injectPath: string; - scramjetPath: string; virtualWasmPath: string; - prefix: string; + codec: Record<"encode" | "decode", (input: string) => string>; }; export const config: Config = { prefix: "/~/sj/", - virtualWasmPath: "scramjet.wasm.js", - injectPath: "/controller/controller.inject.js", scramjetPath: "/scramjet/scramjet.js", wasmPath: "/scramjet/scramjet.wasm", + injectPath: "/controller/controller.inject.js", + virtualWasmPath: "scramjet.wasm.js", + codec: { + encode: (url: string) => { + if (!url) return url; + + return encodeURIComponent(url); + }, + decode: (url: string) => { + if (!url) return url; + + return decodeURIComponent(url); + }, + }, }; -const defaultCfg = { +const scramjetConfig: Partial = { flags: { ...$scramjet.defaultConfig.flags, allowFailedIntercepts: true, @@ -44,31 +53,21 @@ const defaultCfg = { maskedfiles: ["inject.js", "scramjet.wasm.js"], }; -const frames: Record = {}; - -let wasmPayload: string | null = null; - function makeId(): string { return Math.random().toString(36).substring(2, 10); } -const codecEncode = (url: string) => { - if (!url) return url; - - return encodeURIComponent(url); -}; - -const codecDecode = (url: string) => { - if (!url) return url; - - return decodeURIComponent(url); -}; +function deepMerge(target: any, source: any): any { + for (const key in source) { + if (key in target) { + Object.assign(source[key], deepMerge(target[key], source[key])); + } + } -type ControllerInit = { - serviceworker: ServiceWorker; - transport: ProxyTransport; -}; + return Object.assign(target || {}, source); +} +let wasmPayload: string | null = null; let wasmAlreadyFetched = false; async function loadScramjetWasm() { @@ -76,17 +75,25 @@ async function loadScramjetWasm() { return; } - let resp = await fetch(config.wasmPath); + const resp = await fetch(config.wasmPath); $scramjet.setWasm(await resp.arrayBuffer()); wasmAlreadyFetched = true; } +type ControllerInit = { + serviceworker: ServiceWorker; + transport: ProxyTransport; + config?: Partial; + scramjetConfig?: Partial; +}; + export class Controller { + config: Config; + scramjetConfig: ScramjetGlobal.ScramjetConfig; id: string; prefix: string; frames: Frame[] = []; cookieJar = new $scramjet.CookieJar(); - flags: typeof defaultCfg.flags = { ...defaultCfg.flags }; rpc: RpcHelper; private ready: Promise<[void, void]>; @@ -101,7 +108,7 @@ export class Controller { }, request: async (data) => { try { - let path = new URL(data.rawUrl).pathname; + const path = new URL(data.rawUrl).pathname; const frame = this.frames.find((f) => path.startsWith(f.prefix)); if (!frame) throw new Error("No frame found for request"); @@ -136,7 +143,7 @@ export class Controller { ]; } - let sjheaders = $scramjet.ScramjetHeaders.fromRawHeaders( + const sjheaders = $scramjet.ScramjetHeaders.fromRawHeaders( data.initialHeaders ); @@ -175,7 +182,7 @@ export class Controller { const rpc = new RpcHelper( { request: async ({ remote, method, body, headers }) => { - let response = await this.transport.request( + const response = await this.transport.request( new URL(remote), method, body, @@ -186,7 +193,7 @@ export class Controller { }, connect: async ({ url, protocols, requestHeaders, port }) => { let resolve: (arg: TransportToController["connect"][1]) => void; - let promise = new Promise( + const promise = new Promise( (res) => (resolve = res) ); const [send, close] = this.transport.connect( @@ -258,9 +265,12 @@ export class Controller { }; constructor(public init: ControllerInit) { + this.config = deepMerge(config, init.config); + this.scramjetConfig = deepMerge($scramjet.defaultConfig, scramjetConfig); + this.scramjetConfig = deepMerge(this.scramjetConfig, init.scramjetConfig); this.transport = init.transport; this.id = makeId(); - this.prefix = config.prefix + this.id + "/"; + this.prefix = this.config.prefix + this.id + "/"; this.ready = Promise.all([ new Promise((resolve) => { @@ -269,7 +279,7 @@ export class Controller { loadScramjetWasm(), ]); - let channel = new MessageChannel(); + const channel = new MessageChannel(); this.rpc = new RpcHelper( this.methods, "tabchannel-" + this.id, @@ -285,7 +295,7 @@ export class Controller { init.serviceworker.postMessage( { $controller$init: { - prefix: config.prefix + this.id, + prefix: this.config.prefix + this.id, id: this.id, }, }, @@ -323,50 +333,47 @@ function base64Encode(text: string) { } function yieldGetInjectScripts( - cookieJar: ScramjetGlobal.CookieJar, config: Config, sjconfig: ScramjetGlobal.ScramjetConfig, prefix: URL, + cookieJar: ScramjetGlobal.CookieJar, codecEncode: (input: string) => string, codecDecode: (input: string) => string ) { - let getInjectScripts: ScramjetGlobal.ScramjetInterface["getInjectScripts"] = ( - meta, - handler, - script - ) => { - function base64Encode(text: string) { - return btoa( - new TextEncoder() - .encode(text) - .reduce( - (data, byte) => (data.push(String.fromCharCode(byte)), data), - [] as any - ) - .join("") - ); - } - return [ - script(config.scramjetPath), - script(config.injectPath), - script(prefix.href + config.virtualWasmPath), - script( - "data:text/javascript;charset=utf-8;base64," + - base64Encode(` + const getInjectScripts: ScramjetGlobal.ScramjetInterface["getInjectScripts"] = + (meta, handler, script) => { + function base64Encode(text: string) { + return btoa( + new TextEncoder() + .encode(text) + .reduce( + (data, byte) => (data.push(String.fromCharCode(byte)), data), + [] as any + ) + .join("") + ); + } + return [ + script(config.scramjetPath), + script(config.injectPath), + script(prefix.href + config.virtualWasmPath), + script( + "data:text/javascript;charset=utf-8;base64," + + base64Encode(` document.currentScript.remove(); $scramjetController.load({ config: ${JSON.stringify(config)}, sjconfig: ${JSON.stringify(sjconfig)}, - cookies: ${cookieJar.dump()}, prefix: new URL("${prefix.href}"), + cookies: ${cookieJar.dump()}, yieldGetInjectScripts: ${yieldGetInjectScripts.toString()}, codecEncode: ${codecEncode.toString()}, codecDecode: ${codecDecode.toString()}, }) `) - ), - ]; - }; + ), + ]; + }; return getInjectScripts; } @@ -380,30 +387,24 @@ export class Frame { }; get context(): ScramjetGlobal.ScramjetContext { - let sjcfg = { - ...$scramjet.defaultConfig, - flags: this.controller.flags, - maskedfiles: defaultCfg.maskedfiles, - }; - return { - cookieJar, + config: this.controller.scramjetConfig, prefix: new URL(this.prefix, location.href), - config: sjcfg, + cookieJar: this.controller.cookieJar, interface: { getInjectScripts: yieldGetInjectScripts( - this.controller.cookieJar, - config, - sjcfg, + this.controller.config, + this.controller.scramjetConfig, new URL(this.prefix, location.href), - codecEncode, - codecDecode + this.controller.cookieJar, + this.controller.config.codec.encode, + this.controller.config.codec.decode ), getWorkerInjectScripts: (meta, type, script) => { let str = ""; - str += script(config.scramjetPath); - str += script(this.prefix + config.virtualWasmPath); + str += script(this.controller.config.scramjetPath); + str += script(this.prefix + this.controller.config.virtualWasmPath); str += script( "data:text/javascript;charset=utf-8;base64," + base64Encode(` @@ -413,13 +414,13 @@ export class Frame { setWasm(Uint8Array.from(atob(self.WASM), (c) => c.charCodeAt(0))); delete self.WASM; - const sjconfig = ${JSON.stringify(sjcfg)}; + const sjconfig = ${JSON.stringify(this.controller.scramjetConfig)}; const prefix = new URL("${this.prefix}", location.href); const context = { interface: { - codecEncode: ${codecEncode.toString()}, - codecDecode: ${codecDecode.toString()}, + codecEncode: ${this.controller.config.codec.encode.toString()}, + codecDecode: ${this.controller.config.codec.decode.toString()}, }, prefix, config: sjconfig @@ -429,7 +430,7 @@ export class Frame { context, transport: null, shouldPassthroughWebsocket: (url) => { - return url === "wss://anura.pro/"; + return false; } }); @@ -440,8 +441,8 @@ export class Frame { return str; }, - codecEncode, - codecDecode, + codecEncode: this.controller.config.codec.encode, + codecDecode: this.controller.config.codec.decode, }, }; } diff --git a/packages/scramjet/packages/controller/src/inject.ts b/packages/scramjet/packages/controller/src/inject.ts index 50c07096..68ae20a8 100644 --- a/packages/scramjet/packages/controller/src/inject.ts +++ b/packages/scramjet/packages/controller/src/inject.ts @@ -139,10 +139,6 @@ class RemoteTransport implements ProxyTransport { headers, }); } - - meta() { - return {}; - } } const sw = navigator.serviceWorker.controller; @@ -152,13 +148,13 @@ type Config = any; type Init = { config: Config; sjconfig: ScramjetConfig; - cookies: string; prefix: URL; + cookies: string; yieldGetInjectScripts: ( - cookieJar: CookieJar, config: Config, sjconfig: ScramjetConfig, prefix: URL, + cookieJar: CookieJar, codecEncode: (input: string) => string, codecDecode: (input: string) => string ) => any; @@ -177,7 +173,7 @@ export function load(init: Init) { delete (self as any).WASM; setWasm(wasm); - let context = new ExecutionContextWrapper(globalThis, init); + const context = new ExecutionContextWrapper(globalThis, init); } function createFrameId() { @@ -221,21 +217,21 @@ class ExecutionContextWrapper { } const context: ScramjetGlobal.ScramjetContext = { + config: this.init.sjconfig, + prefix: this.init.prefix, + cookieJar: this.cookieJar, interface: { getInjectScripts: this.init.yieldGetInjectScripts( - this.cookieJar, this.init.config, this.init.sjconfig, this.init.prefix, + this.cookieJar, this.init.codecEncode, this.init.codecDecode ), codecEncode: this.init.codecEncode, codecDecode: this.init.codecDecode, }, - config: this.init.sjconfig, - prefix: this.init.prefix, - cookieJar: this.cookieJar, }; this.client = new ScramjetClient(this.global, { @@ -249,13 +245,13 @@ class ExecutionContextWrapper { // } // }); }, - shouldPassthroughWebsocket: (url) => { - return url === "wss://anura.pro/"; + shouldPassthroughWebsocket: () => { + return false; }, - shouldBlockMessageEvent(i) { + shouldBlockMessageEvent: () => { return false; }, - hookSubcontext: (frameself, frame) => { + hookSubcontext: (frameself) => { const context = new ExecutionContextWrapper(frameself, this.init); return context.client; }, diff --git a/packages/scramjet/packages/demo/src/App.tsx b/packages/scramjet/packages/demo/src/App.tsx index 6d9c89ce..f7dbbd4a 100644 --- a/packages/scramjet/packages/demo/src/App.tsx +++ b/packages/scramjet/packages/demo/src/App.tsx @@ -246,7 +246,7 @@ export const App: Component< inline={true} onFlagsChange={(flags) => { console.log("flags changed", flags); - Object.assign(controller.flags, flags); + Object.assign(controller.scramjetConfig.flags, flags); }} /> diff --git a/packages/scramjet/packages/demo/src/index.tsx b/packages/scramjet/packages/demo/src/index.tsx index e307a86d..0e87337b 100644 --- a/packages/scramjet/packages/demo/src/index.tsx +++ b/packages/scramjet/packages/demo/src/index.tsx @@ -142,7 +142,7 @@ async function waitForControllerOrReady(timeoutMs = 10000): Promise { async function mount() { try { - const root = ; + const root = ; app.replaceWith(root); } catch (e) { let err = e as any; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 282da548..3b68c102 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4196,20 +4196,22 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -6045,10 +6047,12 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.1: resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-dir@3.0.0: resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==}