diff --git a/src/client/beacon.ts b/src/client/beacon.ts deleted file mode 100644 index 7ed1d8a..0000000 --- a/src/client/beacon.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { client } from "."; - -// goodybye spyware~ -client.Proxy("navigator.sendBeacon", { - apply(ctx) { - ctx.return(null); - }, -}); diff --git a/src/client/client.ts b/src/client/client.ts new file mode 100644 index 0000000..1ad58d7 --- /dev/null +++ b/src/client/client.ts @@ -0,0 +1,229 @@ +import { createLocationProxy } from "./location"; +import { decodeUrl } from "./shared"; +import { createDocumentProxy, createWindowProxy } from "./window"; + +declare global { + interface Window { + $s: any; + $tryset: any; + $sImport: any; + } +} + +//eslint-disable-next-line +type AnyFunction = Function; + +type ProxyCtx = { + fn: AnyFunction; + this: any; + args: any[]; + newTarget: AnyFunction; + return: (r: any) => void; +}; +type Proxy = { + construct?(ctx: ProxyCtx): any; + apply?(ctx: ProxyCtx): any; +}; + +type TrapCtx = { + this: any; + get: () => T; + set: (v: T) => void; +}; +type Trap = { + writable?: boolean; + value?: any; + enumerable?: boolean; + configurable?: boolean; + get?: (ctx: TrapCtx) => T; + set?: (ctx: TrapCtx, v: T) => void; +}; + +export class ScramjetClient { + static SCRAMJET = Symbol.for("scramjet client global"); + + documentProxy: any; + windowProxy: any; + locationProxy: any; + + constructor(public global: typeof globalThis) { + if ("document" in self) { + this.documentProxy = createDocumentProxy(this, self); + } + + this.locationProxy = createLocationProxy(this, self); + this.windowProxy = createWindowProxy(this, self); + + self[ScramjetClient.SCRAMJET] = this; + } + + hook() { + dbg.log("hwat"); + + // @ts-ignore + const context = import.meta.webpackContext(".", { + recursive: true, + }); + + for (const key of context.keys()) { + if (!key.endsWith(".ts")) continue; + if ( + (key.startsWith("./dom/") && "window" in self) || + (key.startsWith("./worker/") && "WorkerGlobalScope" in self) || + key.startsWith("./shared/") + ) { + console.log("??", key); + const module = context(key); + module.default(this, this.global); + } + } + } + + Proxy(name: string | string[], handler: Proxy) { + if (Array.isArray(name)) { + for (const n of name) { + this.Proxy(n, handler); + } + + return; + } + + const split = name.split("."); + const prop = split.pop(); + const target = split.reduce((a, b) => a?.[b], this.global); + this.RawProxy(target, prop, handler); + } + RawProxy(target: any, prop: string, handler: Proxy) { + if (!target) return; + if (!prop) return; + if (!Reflect.has(target, prop)) return; + + const value = Reflect.get(target, prop); + delete target[prop]; + + const h: ProxyHandler = {}; + + if (handler.construct) { + h.construct = function ( + constructor: any, + argArray: any[], + newTarget: AnyFunction + ) { + let returnValue: any = null; + + const ctx: ProxyCtx = { + fn: constructor, + this: null, + args: argArray, + newTarget: newTarget, + return: (r: any) => { + returnValue = r; + }, + }; + + handler.construct(ctx); + + if (returnValue) { + return returnValue; + } + + return Reflect.construct(ctx.fn, ctx.args, ctx.newTarget); + }; + } + + if (handler.apply) { + h.apply = function (fn: any, thisArg: any, argArray: any[]) { + let returnValue: any = null; + + const ctx: ProxyCtx = { + fn, + this: thisArg, + args: argArray, + newTarget: null, + return: (r: any) => { + returnValue = r; + }, + }; + + handler.apply(ctx); + + if (returnValue) { + return returnValue; + } + + return Reflect.apply(ctx.fn, ctx.this, ctx.args); + }; + } + + target[prop] = new Proxy(value, h); + } + Trap(name: string | string[], descriptor: Trap) { + if (Array.isArray(name)) { + for (const n of name) { + this.Trap(n, descriptor); + } + + return; + } + + const split = name.split("."); + const prop = split.pop(); + const target = split.reduce((a, b) => a?.[b], this.global); + + this.RawTrap(target, prop, descriptor); + } + RawTrap(target: any, prop: string, descriptor: Trap) { + if (!target) return; + if (!prop) return; + if (!Reflect.has(target, prop)) return; + + const oldDescriptor = Object.getOwnPropertyDescriptor(target, prop); + + const ctx: TrapCtx = { + this: null, + get: function () { + return oldDescriptor && oldDescriptor.get.call(this.this); + }, + set: function (v: T) { + oldDescriptor && oldDescriptor.set.call(this.this, v); + }, + }; + + delete target[prop]; + + const desc: PropertyDescriptor = {}; + + if (descriptor.get) { + desc.get = function () { + ctx.this = this; + + return descriptor.get(ctx); + }; + } else if (oldDescriptor?.get) { + desc.get = oldDescriptor.get; + } + + if (descriptor.set) { + desc.set = function (v: T) { + ctx.this = this; + + descriptor.set(ctx, v); + }; + } else if (oldDescriptor?.set) { + desc.set = oldDescriptor.set; + } + + if (descriptor.enumerable) desc.enumerable = descriptor.enumerable; + else if (oldDescriptor?.enumerable) + desc.enumerable = oldDescriptor.enumerable; + if (descriptor.configurable) desc.configurable = descriptor.configurable; + else if (oldDescriptor?.configurable) + desc.configurable = oldDescriptor.configurable; + + Object.defineProperty(target, prop, desc); + } + + get url(): URL { + return new URL(decodeUrl(location.href)); + } +} diff --git a/src/client/cookie.ts b/src/client/cookie.ts deleted file mode 100644 index 719c2a7..0000000 --- a/src/client/cookie.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { parse } from "set-cookie-parser"; -import { client } from "."; - -client.Trap("Document.prototype.cookie", { - get(ctx) { - const cookiestring = ctx.get(); - dbg.log("original cookiestring", cookiestring); - // - // if (!cookiestring) return ""; - // - // - // let string = ""; - // - // for (const cookiestr of cookiestring.split(";")) { - // const cookie = parse(cookiestr.trim())[0]; - // if (cookie.name.startsWith(client.url.hostname + "@")) { - // let name = cookie.name.substring(client.url.hostname.length + 1); - // string += `${name}=${cookie.value}; `; - // } - // } - // - // return string.trimEnd(); - - return "sp_t=00246e00653d39d1341bbe9d10f138c4; OptanonConsent=isGpcEnabled=0&datestamp=Sat+Jul+20+2024+16%3A11%3A26+GMT-0400+(Eastern+Daylight+Time)&version=202405.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&landingPath=https%3A%2F%2Fopen.spotify.com%2F&groups=BG169%3A1%2Ct00%3A1%2Ci00%3A1%2CBG170%3A1%2Cs00%3A1%2Cf00%3A1%2Cm00%3A1%2Cf11%3A1"; - }, - set(ctx, value: string) { - dbg.debug("setting cookie", value); - const cookie = parse(value)[0]; - - let date = new Date(); - let expires = cookie.expires; - - dbg.error("expires", expires); - // if (expires instanceof Date) { - // if (isNaN(expires.getTime())) return; - // if (expires.getTime() < date.getTime()) return; - // } - - // set.call(document, `${cookie.name}=${cookie.value}`); - }, -}); - -// @ts-ignore -delete self.cookieStore; - -// sp_t=00e49dc8-59d0-4b5f-9beb-ec7b67368498; path=/; expires=Invalid Date -// OTZ=7653361_72_76_104100_72_446760;path=/;expires=Mon, 19 Aug 2024 20:01:06 GMT;secure diff --git a/src/client/dom/cookie.ts b/src/client/dom/cookie.ts new file mode 100644 index 0000000..6959a89 --- /dev/null +++ b/src/client/dom/cookie.ts @@ -0,0 +1,49 @@ +import { parse } from "set-cookie-parser"; +import { ScramjetClient } from "../client"; + +export default function (client: ScramjetClient, self: typeof window) { + client.Trap("Document.prototype.cookie", { + get(ctx) { + const cookiestring = ctx.get(); + dbg.log("original cookiestring", cookiestring); + // + // if (!cookiestring) return ""; + // + // + // let string = ""; + // + // for (const cookiestr of cookiestring.split(";")) { + // const cookie = parse(cookiestr.trim())[0]; + // if (cookie.name.startsWith(client.url.hostname + "@")) { + // let name = cookie.name.substring(client.url.hostname.length + 1); + // string += `${name}=${cookie.value}; `; + // } + // } + // + // return string.trimEnd(); + + return "sp_t=00246e00653d39d1341bbe9d10f138c4; OptanonConsent=isGpcEnabled=0&datestamp=Sat+Jul+20+2024+16%3A11%3A26+GMT-0400+(Eastern+Daylight+Time)&version=202405.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&landingPath=https%3A%2F%2Fopen.spotify.com%2F&groups=BG169%3A1%2Ct00%3A1%2Ci00%3A1%2CBG170%3A1%2Cs00%3A1%2Cf00%3A1%2Cm00%3A1%2Cf11%3A1"; + }, + set(ctx, value: string) { + dbg.debug("setting cookie", value); + const cookie = parse(value)[0]; + + let date = new Date(); + let expires = cookie.expires; + + dbg.error("expires", expires); + // if (expires instanceof Date) { + // if (isNaN(expires.getTime())) return; + // if (expires.getTime() < date.getTime()) return; + // } + + // set.call(document, `${cookie.name}=${cookie.value}`); + }, + }); + + // @ts-ignore + delete self.cookieStore; + + // sp_t=00e49dc8-59d0-4b5f-9beb-ec7b67368498; path=/; expires=Invalid Date + // OTZ=7653361_72_76_104100_72_446760;path=/;expires=Mon, 19 Aug 2024 20:01:06 GMT;secure +} diff --git a/src/client/css.ts b/src/client/dom/css.ts similarity index 51% rename from src/client/css.ts rename to src/client/dom/css.ts index e1da852..0701036 100644 --- a/src/client/css.ts +++ b/src/client/dom/css.ts @@ -1,5 +1,5 @@ -import { client } from "."; -import { rewriteCss } from "./shared"; +import { ScramjetClient } from "../client"; +import { rewriteCss } from "../shared"; const cssProperties = [ "background", @@ -14,9 +14,11 @@ const cssProperties = [ ]; // const jsProperties = ["background", "backgroundImage", "mask", "maskImage", "listStyle", "listStyleImage", "borderImage", "borderImageSource", "cursor"]; -client.Proxy("CSSStyleDeclaration.prototype.setProperty", { - apply(ctx) { - if (cssProperties.includes(ctx.args[0])) - ctx.args[1] = rewriteCss(ctx.args[1]); - }, -}); +export default function (client: ScramjetClient) { + client.Proxy("CSSStyleDeclaration.prototype.setProperty", { + apply(ctx) { + if (cssProperties.includes(ctx.args[0])) + ctx.args[1] = rewriteCss(ctx.args[1]); + }, + }); +} diff --git a/src/client/dom/element.ts b/src/client/dom/element.ts new file mode 100644 index 0000000..bfd30ae --- /dev/null +++ b/src/client/dom/element.ts @@ -0,0 +1,163 @@ +import { ScramjetClient } from "../client"; +import { decodeUrl } from "../shared"; +import { + encodeUrl, + rewriteCss, + rewriteHtml, + rewriteJs, + rewriteSrcset, +} from "../shared"; + +declare global { + interface Element { + $origattrs: Record; + } +} +export default function (client: ScramjetClient, self: typeof window) { + dbg.log("RAN"); + const attrObject = { + nonce: [self.HTMLElement], + integrity: [self.HTMLScriptElement, self.HTMLLinkElement], + csp: [self.HTMLIFrameElement], + src: [ + self.HTMLImageElement, + self.HTMLMediaElement, + self.HTMLIFrameElement, + self.HTMLEmbedElement, + self.HTMLScriptElement, + ], + href: [self.HTMLAnchorElement, self.HTMLLinkElement], + data: [self.HTMLObjectElement], + action: [self.HTMLFormElement], + formaction: [self.HTMLButtonElement, self.HTMLInputElement], + srcdoc: [self.HTMLIFrameElement], + srcset: [self.HTMLImageElement, self.HTMLSourceElement], + imagesrcset: [self.HTMLLinkElement], + }; + + const attrs = Object.keys(attrObject); + + for (const attr of attrs) { + for (const element of attrObject[attr]) { + const descriptor = Object.getOwnPropertyDescriptor( + element.prototype, + attr + ); + Object.defineProperty(element.prototype, attr, { + get() { + if (["src", "data", "href", "action", "formaction"].includes(attr)) { + return decodeUrl(descriptor.get.call(this)); + } + + if (this.$origattrs[attr]) { + return this.$origattrs[attr]; + } + + return descriptor.get.call(this); + }, + + set(value) { + this.$origattrs[attr] = value; + + if (["nonce", "integrity", "csp"].includes(attr)) { + return; + } else if ( + ["src", "data", "href", "action", "formaction"].includes(attr) + ) { + value = encodeUrl(value); + } else if (attr === "srcdoc") { + value = rewriteHtml(value); + } else if (["srcset", "imagesrcset"].includes(attr)) { + value = rewriteSrcset(value); + } + + descriptor.set.call(this, value); + }, + }); + } + } + + self.Element.prototype.$origattrs = {}; + + self.Element.prototype.getAttribute = new Proxy( + self.Element.prototype.getAttribute, + { + apply(target, thisArg, argArray) { + if (attrs.includes(argArray[0]) && thisArg.$origattrs[argArray[0]]) { + return thisArg.$origattrs[argArray[0]]; + } + + return Reflect.apply(target, thisArg, argArray); + }, + } + ); + + self.Element.prototype.setAttribute = new Proxy( + self.Element.prototype.setAttribute, + { + apply(target, thisArg, argArray) { + if (attrs.includes(argArray[0])) { + thisArg.$origattrs[argArray[0]] = argArray[1]; + if (["nonce", "integrity", "csp"].includes(argArray[0])) { + return; + } else if ( + ["src", "data", "href", "action", "formaction"].includes( + argArray[0] + ) + ) { + argArray[1] = encodeUrl(argArray[1]); + } else if (argArray[0] === "srcdoc") { + // TODO: this will rewrite with the wrong url in mind for iframes!! + argArray[1] = rewriteHtml(argArray[1]); + } else if (["srcset", "imagesrcset"].includes(argArray[0])) { + argArray[1] = rewriteSrcset(argArray[1]); + } else if (argArray[1] === "style") { + argArray[1] = rewriteCss(argArray[1]); + } + } + + return Reflect.apply(target, thisArg, argArray); + }, + } + ); + + const innerHTML = Object.getOwnPropertyDescriptor( + self.Element.prototype, + "innerHTML" + ); + + Object.defineProperty(self.Element.prototype, "innerHTML", { + set(value) { + if (this instanceof self.HTMLScriptElement) { + value = rewriteJs(value); + } else if (this instanceof self.HTMLStyleElement) { + value = rewriteCss(value); + } else { + value = rewriteHtml(value); + } + + return innerHTML.set.call(this, value); + }, + }); + + for (const target of [ + self.Node.prototype, + self.MutationObserver.prototype, + self.document, + ]) { + for (const prop in target) { + try { + if (typeof target[prop] === "function") { + client.RawProxy(target, prop, { + apply(ctx) { + for (const i in ctx.args) { + if (ctx.args[i] === client.documentProxy) + ctx.args[i] = self.document; + } + }, + }); + } + } catch (e) {} + } + } +} diff --git a/src/client/dom/history.ts b/src/client/dom/history.ts new file mode 100644 index 0000000..4ca4d7f --- /dev/null +++ b/src/client/dom/history.ts @@ -0,0 +1,16 @@ +import { ScramjetClient } from "../client"; +import { encodeUrl } from "../shared"; + +export default function (client: ScramjetClient, self: typeof globalThis) { + client.Proxy("history.pushState", { + apply(ctx) { + ctx.args[2] = encodeUrl(ctx.args[2]); + }, + }); + + client.Proxy("history.replaceState", { + apply(ctx) { + ctx.args[2] = encodeUrl(ctx.args[2]); + }, + }); +} diff --git a/src/client/dom/origin.ts b/src/client/dom/origin.ts new file mode 100644 index 0000000..f2e23ff --- /dev/null +++ b/src/client/dom/origin.ts @@ -0,0 +1,40 @@ +import { ScramjetClient } from "../client"; +import { decodeUrl } from "../shared"; + +export default function (client: ScramjetClient, self: typeof window) { + client.Trap("origin", { + get() { + return client.url.origin; + }, + set() { + return false; + }, + }); + + client.Trap("document.URL", { + get() { + return client.url; + }, + set() { + return false; + }, + }); + + client.Trap("document.baseURI", { + get() { + return decodeUrl(self.location.href); + }, + set() { + return false; + }, + }); + + client.Trap("document.domain", { + get() { + return client.url.hostname; + }, + set() { + return false; + }, + }); +} diff --git a/src/client/dom/postmessage.ts b/src/client/dom/postmessage.ts new file mode 100644 index 0000000..645dbd9 --- /dev/null +++ b/src/client/dom/postmessage.ts @@ -0,0 +1,9 @@ +import { ScramjetClient } from "../client"; + +export default function (client: ScramjetClient, self: typeof window) { + client.Proxy("window.postMessage", { + apply(ctx) { + if (typeof ctx.args[1] === "string") ctx.args[1] = "*"; + }, + }); +} diff --git a/src/client/storage.ts b/src/client/dom/storage.ts similarity index 80% rename from src/client/storage.ts rename to src/client/dom/storage.ts index 39d90fb..ccd55ba 100644 --- a/src/client/storage.ts +++ b/src/client/dom/storage.ts @@ -1,6 +1,6 @@ -import { client } from "."; +import { ScramjetClient } from "../client"; -if ("window" in self) { +export default function (client: ScramjetClient, self: typeof window) { const handler: ProxyHandler = { get(target, prop) { switch (prop) { @@ -85,15 +85,15 @@ if ("window" in self) { }, }; - const realLocalStorage = window.localStorage; - const realSessionStorage = window.sessionStorage; + const realLocalStorage = self.localStorage; + const realSessionStorage = self.sessionStorage; - const localStorageProxy = new Proxy(window.localStorage, handler); - const sessionStorageProxy = new Proxy(window.sessionStorage, handler); + const localStorageProxy = new Proxy(self.localStorage, handler); + const sessionStorageProxy = new Proxy(self.sessionStorage, handler); - delete window.localStorage; - delete window.sessionStorage; + delete self.localStorage; + delete self.sessionStorage; - window.localStorage = localStorageProxy; - window.sessionStorage = sessionStorageProxy; + self.localStorage = localStorageProxy; + self.sessionStorage = sessionStorageProxy; } diff --git a/src/client/dom/trustedTypes.ts b/src/client/dom/trustedTypes.ts new file mode 100644 index 0000000..eb0195e --- /dev/null +++ b/src/client/dom/trustedTypes.ts @@ -0,0 +1,8 @@ +export default function (client, self) { + delete self.TrustedHTML; + delete self.TrustedScript; + delete self.TrustedScriptURL; + delete self.TrustedTypePolicy; + delete self.TrustedTypePolicyFactory; + delete self.trustedTypes; +} diff --git a/src/client/element.ts b/src/client/element.ts deleted file mode 100644 index ff760a2..0000000 --- a/src/client/element.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { client } from "."; -import { decodeUrl } from "../shared/rewriters/url"; -import { - encodeUrl, - rewriteCss, - rewriteHtml, - rewriteJs, - rewriteSrcset, -} from "./shared"; -import { documentProxy } from "./window"; - -if ("window" in self) { - const attrObject = { - nonce: [HTMLElement], - integrity: [HTMLScriptElement, HTMLLinkElement], - csp: [HTMLIFrameElement], - src: [ - HTMLImageElement, - HTMLMediaElement, - HTMLIFrameElement, - HTMLEmbedElement, - HTMLScriptElement, - ], - href: [HTMLAnchorElement, HTMLLinkElement], - data: [HTMLObjectElement], - action: [HTMLFormElement], - formaction: [HTMLButtonElement, HTMLInputElement], - srcdoc: [HTMLIFrameElement], - srcset: [HTMLImageElement, HTMLSourceElement], - imagesrcset: [HTMLLinkElement], - }; - - const attrs = Object.keys(attrObject); - - for (const attr of attrs) { - for (const element of attrObject[attr]) { - const descriptor = Object.getOwnPropertyDescriptor( - element.prototype, - attr - ); - Object.defineProperty(element.prototype, attr, { - get() { - if (["src", "data", "href", "action", "formaction"].includes(attr)) { - return decodeUrl(descriptor.get.call(this)); - } - - if (this.$origattrs[attr]) { - return this.$origattrs[attr]; - } - - return descriptor.get.call(this); - }, - - set(value) { - this.$origattrs[attr] = value; - - if (["nonce", "integrity", "csp"].includes(attr)) { - return; - } else if ( - ["src", "data", "href", "action", "formaction"].includes(attr) - ) { - value = encodeUrl(value); - } else if (attr === "srcdoc") { - value = rewriteHtml(value); - } else if (["srcset", "imagesrcset"].includes(attr)) { - value = rewriteSrcset(value); - } - - descriptor.set.call(this, value); - }, - }); - } - } - - declare global { - interface Element { - $origattrs: Record; - } - } - - Element.prototype.$origattrs = {}; - - Element.prototype.getAttribute = new Proxy(Element.prototype.getAttribute, { - apply(target, thisArg, argArray) { - if (attrs.includes(argArray[0]) && thisArg.$origattrs[argArray[0]]) { - return thisArg.$origattrs[argArray[0]]; - } - - return Reflect.apply(target, thisArg, argArray); - }, - }); - - Element.prototype.setAttribute = new Proxy(Element.prototype.setAttribute, { - apply(target, thisArg, argArray) { - if (attrs.includes(argArray[0])) { - thisArg.$origattrs[argArray[0]] = argArray[1]; - if (["nonce", "integrity", "csp"].includes(argArray[0])) { - return; - } else if ( - ["src", "data", "href", "action", "formaction"].includes(argArray[0]) - ) { - argArray[1] = encodeUrl(argArray[1]); - } else if (argArray[0] === "srcdoc") { - argArray[1] = rewriteHtml(argArray[1]); - } else if (["srcset", "imagesrcset"].includes(argArray[0])) { - argArray[1] = rewriteSrcset(argArray[1]); - } else if (argArray[1] === "style") { - argArray[1] = rewriteCss(argArray[1]); - } - } - - return Reflect.apply(target, thisArg, argArray); - }, - }); - - const innerHTML = Object.getOwnPropertyDescriptor( - Element.prototype, - "innerHTML" - ); - - Object.defineProperty(Element.prototype, "innerHTML", { - set(value) { - if (this instanceof HTMLScriptElement) { - value = rewriteJs(value); - } else if (this instanceof HTMLStyleElement) { - value = rewriteCss(value); - } else { - value = rewriteHtml(value); - } - - return innerHTML.set.call(this, value); - }, - }); - - for (const target of [Node.prototype, MutationObserver.prototype, document]) { - for (const prop in target) { - try { - if (typeof target[prop] === "function") { - client.RawProxy(target, prop, { - apply(ctx) { - for (const i in ctx.args) { - if (ctx.args[i] === documentProxy) ctx.args[i] = document; - } - }, - }); - } - } catch (e) {} - } - } -} diff --git a/src/client/event.ts b/src/client/event.ts deleted file mode 100644 index 887b113..0000000 --- a/src/client/event.ts +++ /dev/null @@ -1,15 +0,0 @@ -// idk what shit has to be done on here but it has to be done -// i'm going to temporarily disable rewriting if a MemberExpression detects addEventListener - -// window.addEventListener = new Proxy(window.addEventListener, { -// apply (target, thisArg, argArray) { -// // - -// return Reflect.apply(target, thisArg, argArray); -// } -// }) -// window.addEventListener = new Proxy(window.addEventListener, { -// apply(target1, thisArg, argArray) { -// window.addEventListener(argArray[0], argArray[1]); -// }, -// }); diff --git a/src/client/history.ts b/src/client/history.ts deleted file mode 100644 index 27272e6..0000000 --- a/src/client/history.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { client } from "."; -import { encodeUrl } from "./shared"; - -client.Proxy("history.pushState", { - apply(ctx) { - ctx.args[2] = encodeUrl(ctx.args[2]); - }, -}); - -client.Proxy("history.replaceState", { - apply(ctx) { - ctx.args[2] = encodeUrl(ctx.args[2]); - }, -}); diff --git a/src/client/import.ts b/src/client/import.ts deleted file mode 100644 index eed1436..0000000 --- a/src/client/import.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { encodeUrl } from "../shared/rewriters/url"; - -self.$sImport = function (base) { - return function (url) { - const resolved = new URL(url, base).href; - - return function () {}.constructor( - `return import("${encodeUrl(resolved)}")` - )(); - }; -}; diff --git a/src/client/index.ts b/src/client/index.ts index 04ce73b..1814df2 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,202 +1,10 @@ -import { decodeUrl } from "./shared"; +// entrypoint for scramjet.client.js -declare global { - interface Window { - $s: any; - $tryset: any; - $sImport: any; - } -} - -//eslint-disable-next-line -type AnyFunction = Function; - -type ProxyCtx = { - fn: AnyFunction; - this: any; - args: any[]; - newTarget: AnyFunction; - return: (r: any) => void; -}; -type Proxy = { - construct?(ctx: ProxyCtx): any; - apply?(ctx: ProxyCtx): any; -}; - -type TrapCtx = { - this: any; - get: () => T; - set: (v: T) => void; -}; -type Trap = { - writable?: boolean; - value?: any; - enumerable?: boolean; - configurable?: boolean; - get?: (ctx: TrapCtx) => T; - set?: (ctx: TrapCtx, v: T) => void; -}; - -class ScramjetClient { - Proxy(name: string | string[], handler: Proxy) { - if (Array.isArray(name)) { - for (const n of name) { - this.Proxy(n, handler); - } - - return; - } - - const split = name.split("."); - const prop = split.pop(); - const target = split.reduce((a, b) => a?.[b], self); - this.RawProxy(target, prop, handler); - } - RawProxy(target: any, prop: string, handler: Proxy) { - if (!target) return; - if (!prop) return; - if (!Reflect.has(target, prop)) return; - - const value = Reflect.get(target, prop); - delete target[prop]; - - const h: ProxyHandler = {}; - - if (handler.construct) { - h.construct = function ( - constructor: any, - argArray: any[], - newTarget: AnyFunction - ) { - let returnValue: any = null; - - const ctx: ProxyCtx = { - fn: constructor, - this: null, - args: argArray, - newTarget: newTarget, - return: (r: any) => { - returnValue = r; - }, - }; - - handler.construct(ctx); - - if (returnValue) { - return returnValue; - } - - return Reflect.construct(ctx.fn, ctx.args, ctx.newTarget); - }; - } - - if (handler.apply) { - h.apply = function (fn: any, thisArg: any, argArray: any[]) { - let returnValue: any = null; - - const ctx: ProxyCtx = { - fn, - this: thisArg, - args: argArray, - newTarget: null, - return: (r: any) => { - returnValue = r; - }, - }; - - handler.apply(ctx); - - if (returnValue) { - return returnValue; - } - - return Reflect.apply(ctx.fn, ctx.this, ctx.args); - }; - } - - target[prop] = new Proxy(value, h); - } - Trap(name: string | string[], descriptor: Trap) { - if (Array.isArray(name)) { - for (const n of name) { - this.Trap(n, descriptor); - } - - return; - } - - const split = name.split("."); - const prop = split.pop(); - const target = split.reduce((a, b) => a?.[b], self); - - this.RawTrap(target, prop, descriptor); - } - RawTrap(target: any, prop: string, descriptor: Trap) { - if (!target) return; - if (!prop) return; - if (!Reflect.has(target, prop)) return; - - const oldDescriptor = Object.getOwnPropertyDescriptor(target, prop); - - const ctx: TrapCtx = { - this: null, - get: function () { - return oldDescriptor && oldDescriptor.get.call(this.this); - }, - set: function (v: T) { - oldDescriptor && oldDescriptor.set.call(this.this, v); - }, - }; - - delete target[prop]; - - const desc: PropertyDescriptor = {}; - - if (descriptor.get) { - desc.get = function () { - ctx.this = this; - - return descriptor.get(ctx); - }; - } else if (oldDescriptor?.get) { - desc.get = oldDescriptor.get; - } - - if (descriptor.set) { - desc.set = function (v: T) { - ctx.this = this; - - descriptor.set(ctx, v); - }; - } else if (oldDescriptor?.set) { - desc.set = oldDescriptor.set; - } - - if (descriptor.enumerable) desc.enumerable = descriptor.enumerable; - else if (oldDescriptor?.enumerable) - desc.enumerable = oldDescriptor.enumerable; - if (descriptor.configurable) desc.configurable = descriptor.configurable; - else if (oldDescriptor?.configurable) - desc.configurable = oldDescriptor.configurable; - - Object.defineProperty(target, prop, desc); - } - - get url(): URL { - return new URL(decodeUrl(location.href)); - } - - async init() {} -} - -export const client = new ScramjetClient(); -client.init(); - -// @ts-ignore -const context = import.meta.webpackContext("./", { - recursive: true, -}); - -for (const key of context.keys()) { - context(key); +import { ScramjetClient } from "./client"; + +dbg.log("scrammin"); +// if it already exists, that means the handlers have probably already been setup by the parent document +if (!(ScramjetClient.SCRAMJET in self)) { + const client = new ScramjetClient(self); + client.hook(); } diff --git a/src/client/location.ts b/src/client/location.ts index 509c847..fb73aeb 100644 --- a/src/client/location.ts +++ b/src/client/location.ts @@ -1,37 +1,44 @@ // @ts-nocheck +import { ScramjetClient } from "./client"; import { encodeUrl, decodeUrl } from "./shared"; -function createLocation() { - const loc = new URL(decodeUrl(location.href)); - loc.assign = (url: string) => location.assign(encodeUrl(url)); - loc.reload = () => location.reload(); - loc.replace = (url: string) => location.replace(encodeUrl(url)); - loc.toString = () => loc.href; +export function createLocationProxy( + client: ScramjetClient, + self: typeof globalThis +) { + function createLocation() { + const loc = new URL(client.url.href); - return loc; -} + loc.assign = (url: string) => self.location.assign(encodeUrl(url)); + loc.reload = () => self.location.reload(); + loc.replace = (url: string) => self.location.replace(encodeUrl(url)); + loc.toString = () => loc.href; -export const locationProxy = new Proxy( - { - host: "", - }, - { - get(target, prop) { - const loc = createLocation(); - - return loc[prop]; - }, - - set(obj, prop, value) { - const loc = createLocation(); - - if (prop === "href") { - location.href = encodeUrl(value); - } else { - loc[prop] = value; - } - - return true; - }, + return loc; } -); + + return new Proxy( + { + host: "", + }, + { + get(target, prop) { + const loc = createLocation(); + + return loc[prop]; + }, + + set(obj, prop, value) { + const loc = createLocation(); + + if (prop === "href") { + self.location.href = encodeUrl(value); + } else { + loc[prop] = value; + } + + return true; + }, + } + ); +} diff --git a/src/client/native/eval.ts b/src/client/native/eval.ts deleted file mode 100644 index 03112fc..0000000 --- a/src/client/native/eval.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* -import { rewriteJs } from "../shared"; - -const FunctionProxy = new Proxy(Function, { - construct(target, argArray) { - if (argArray.length === 1) { - return Reflect.construct(target, rewriteJs(argArray[0])); - } else { - return Reflect.construct( - target, - rewriteJs(argArray[argArray.length - 1]) - ); - } - }, - apply(target, thisArg, argArray) { - if (argArray.length === 1) { - return Reflect.apply(target, undefined, [rewriteJs(argArray[0])]); - } else { - return Reflect.apply(target, undefined, [ - ...argArray.map((x, index) => index === argArray.length - 1), - rewriteJs(argArray[argArray.length - 1]), - ]); - } - }, -}); - -delete window.Function; - -window.Function = FunctionProxy; - -window.eval = new Proxy(window.eval, { - apply(target, thisArg, argArray) { - return Reflect.apply(target, thisArg, [rewriteJs(argArray[0])]); - }, -}); -*/ diff --git a/src/client/origin.ts b/src/client/origin.ts deleted file mode 100644 index a061fa7..0000000 --- a/src/client/origin.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { client } from "."; -import { decodeUrl } from "../shared/rewriters/url"; - -client.Trap("origin", { - get() { - return client.url.origin; - }, - set() { - return false; - }, -}); - -client.Trap("document.URL", { - get() { - return client.url; - }, - set() { - return false; - }, -}); - -client.Trap("document.baseURI", { - get() { - return decodeUrl(location.href); - }, - set() { - return false; - }, -}); - -client.Trap("document.domain", { - get() { - return client.url.hostname; - }, - set() { - return false; - }, -}); diff --git a/src/client/postmessage.ts b/src/client/postmessage.ts deleted file mode 100644 index 6ea981d..0000000 --- a/src/client/postmessage.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { client } from "."; - -client.Proxy("window.postMessage", { - apply(ctx) { - if (typeof ctx.args[1] === "string") ctx.args[1] = "*"; - }, -}); diff --git a/src/client/requests/fetch.ts b/src/client/requests/fetch.ts deleted file mode 100644 index 951b7fc..0000000 --- a/src/client/requests/fetch.ts +++ /dev/null @@ -1,28 +0,0 @@ -// ts throws an error if you dont do window.fetch - -import { client } from ".."; -import { encodeUrl, rewriteHeaders } from "../shared"; - -client.Proxy("fetch", { - apply(ctx) { - ctx.args[0] = encodeUrl(ctx.args[0]); - }, -}); - -client.Proxy("Headers", { - construct(ctx) { - ctx.args[0] = rewriteHeaders(ctx.args[0]); - }, -}); - -client.Proxy("Request", { - construct(ctx) { - if (typeof ctx.args[0] === "string") ctx.args[0] = encodeUrl(ctx.args[0]); - }, -}); - -client.Proxy("Response.redirect", { - apply(ctx) { - ctx.args[0] = encodeUrl(ctx.args[0]); - }, -}); diff --git a/src/client/requests/xmlhttprequest.ts b/src/client/requests/xmlhttprequest.ts deleted file mode 100644 index 83f588f..0000000 --- a/src/client/requests/xmlhttprequest.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { client } from ".."; -import { encodeUrl, rewriteHeaders } from "../shared"; - -client.Proxy("XMLHttpRequest.prototype.open", { - apply(ctx) { - if (ctx.args[1]) ctx.args[1] = encodeUrl(ctx.args[1]); - }, -}); - -client.Proxy("XMLHttpRequest.prototype.setRequestHeader", { - apply(ctx) { - let headerObject = Object.fromEntries([ctx.args]); - headerObject = rewriteHeaders(headerObject); - - ctx.args = Object.entries(headerObject)[0]; - }, -}); diff --git a/src/client/scope.ts b/src/client/scope.ts deleted file mode 100644 index 618c7e3..0000000 --- a/src/client/scope.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { locationProxy } from "./location"; -import { documentProxy, windowProxy } from "./window"; - -export const iswindow = "window" in self; -export const isworker = "WorkerGlobalScope" in self; -export const issw = "ServiceWorkerGlobalScope" in self; -export const isdedicated = "DedicatedWorkerGlobalScope" in self; -export const isshared = "SharedWorkerGlobalScope" in self; - -function scope(identifier: any) { - // this will break iframe postmessage! - if ( - iswindow && - (identifier instanceof Window || - identifier instanceof top.window.Window || - identifier instanceof parent.window.Window) - ) { - return windowProxy; - } else if ( - (iswindow && identifier instanceof Location) || - (isworker && identifier instanceof WorkerLocation) - ) { - return locationProxy; - } else if (iswindow && identifier instanceof Document) { - return documentProxy; - } else if (isworker && identifier instanceof WorkerGlobalScope) { - return windowProxy; - } - - return identifier; -} - -// shorthand because this can get out of hand reall quickly -self.$s = scope; - -self.$tryset = function (lhs: any, op: string, rhs: any) { - if (lhs instanceof Location) { - // @ts-ignore - locationProxy.href = rhs; - - return true; - } -}; diff --git a/src/client/shared/apply.ts b/src/client/shared/apply.ts new file mode 100644 index 0000000..b7ff4c8 --- /dev/null +++ b/src/client/shared/apply.ts @@ -0,0 +1,18 @@ +import { ScramjetClient } from "../client"; + +export default function (client: ScramjetClient, self: typeof globalThis) { + // do we need this? + client.Proxy( + [ + "Function.prototype.call", + "Function.prototype.bind", + "Function.prototype.apply", + ], + { + apply(ctx) { + if (ctx.args[0] === client.windowProxy) ctx.args[0] = self; + if (ctx.args[0] === client.documentProxy) ctx.args[0] = self.document; + }, + } + ); +} diff --git a/src/client/shared/eval.ts b/src/client/shared/eval.ts new file mode 100644 index 0000000..556e71c --- /dev/null +++ b/src/client/shared/eval.ts @@ -0,0 +1,38 @@ +export default function (client, self) {} + +/* +import { rewriteJs } from "../shared"; + +const FunctionProxy = new Proxy(Function, { + construct(target, argArray) { + if (argArray.length === 1) { + return Reflect.construct(target, rewriteJs(argArray[0])); + } else { + return Reflect.construct( + target, + rewriteJs(argArray[argArray.length - 1]) + ); + } + }, + apply(target, thisArg, argArray) { + if (argArray.length === 1) { + return Reflect.apply(target, undefined, [rewriteJs(argArray[0])]); + } else { + return Reflect.apply(target, undefined, [ + ...argArray.map((x, index) => index === argArray.length - 1), + rewriteJs(argArray[argArray.length - 1]), + ]); + } + }, +}); + +delete window.Function; + +window.Function = FunctionProxy; + +window.eval = new Proxy(window.eval, { + apply(target, thisArg, argArray) { + return Reflect.apply(target, thisArg, [rewriteJs(argArray[0])]); + }, +}); +*/ diff --git a/src/client/shared/import.ts b/src/client/shared/import.ts new file mode 100644 index 0000000..75c4483 --- /dev/null +++ b/src/client/shared/import.ts @@ -0,0 +1,13 @@ +import { encodeUrl } from "../shared"; + +export default function (client, self) { + self.$sImport = function (base) { + return function (url) { + const resolved = new URL(url, base).href; + + return function () {}.constructor( + `return import("${encodeUrl(resolved)}")` + )(); + }; + }; +} diff --git a/src/client/shared/requests/beacon.ts b/src/client/shared/requests/beacon.ts new file mode 100644 index 0000000..495cbc3 --- /dev/null +++ b/src/client/shared/requests/beacon.ts @@ -0,0 +1,8 @@ +export default function (client, self) { + // goodybye spyware~ + client.Proxy("navigator.sendBeacon", { + apply(ctx) { + ctx.return(null); + }, + }); +} diff --git a/src/client/shared/requests/fetch.ts b/src/client/shared/requests/fetch.ts new file mode 100644 index 0000000..384e38e --- /dev/null +++ b/src/client/shared/requests/fetch.ts @@ -0,0 +1,29 @@ +// ts throws an error if you dont do window.fetch + +import { encodeUrl, rewriteHeaders } from "../../shared"; + +export default function (client: ScramjetClient, self: typeof globalThis) { + client.Proxy("fetch", { + apply(ctx) { + ctx.args[0] = encodeUrl(ctx.args[0]); + }, + }); + + client.Proxy("Headers", { + construct(ctx) { + ctx.args[0] = rewriteHeaders(ctx.args[0]); + }, + }); + + client.Proxy("Request", { + construct(ctx) { + if (typeof ctx.args[0] === "string") ctx.args[0] = encodeUrl(ctx.args[0]); + }, + }); + + client.Proxy("Response.redirect", { + apply(ctx) { + ctx.args[0] = encodeUrl(ctx.args[0]); + }, + }); +} diff --git a/src/client/requests/websocket.ts b/src/client/shared/requests/websocket.ts similarity index 57% rename from src/client/requests/websocket.ts rename to src/client/shared/requests/websocket.ts index 7885427..956ed15 100644 --- a/src/client/requests/websocket.ts +++ b/src/client/shared/requests/websocket.ts @@ -1,7 +1,7 @@ -import { client } from ".."; -import { BareClient } from "../shared"; +import { ScramjetClient } from "../../client"; +import { BareClient } from "../../shared"; -if ("window" in self) { +export default function (client: ScramjetClient, self: typeof globalThis) { const bare = new BareClient(); client.Proxy("WebSocket", { @@ -12,7 +12,7 @@ if ("window" in self) { ctx.args[1], ctx.fn as typeof WebSocket, { - "User-Agent": navigator.userAgent, + "User-Agent": self.navigator.userAgent, Origin: client.url.origin, }, ArrayBuffer.prototype diff --git a/src/client/shared/requests/xmlhttprequest.ts b/src/client/shared/requests/xmlhttprequest.ts new file mode 100644 index 0000000..43242d6 --- /dev/null +++ b/src/client/shared/requests/xmlhttprequest.ts @@ -0,0 +1,18 @@ +import { encodeUrl, rewriteHeaders } from "../../shared"; + +export default function (client, self) { + client.Proxy("XMLHttpRequest.prototype.open", { + apply(ctx) { + if (ctx.args[1]) ctx.args[1] = encodeUrl(ctx.args[1]); + }, + }); + + client.Proxy("XMLHttpRequest.prototype.setRequestHeader", { + apply(ctx) { + let headerObject = Object.fromEntries([ctx.args]); + headerObject = rewriteHeaders(headerObject); + + ctx.args = Object.entries(headerObject)[0]; + }, + }); +} diff --git a/src/client/shared/scope.ts b/src/client/shared/scope.ts new file mode 100644 index 0000000..b704779 --- /dev/null +++ b/src/client/shared/scope.ts @@ -0,0 +1,44 @@ +import { ScramjetClient } from "../client"; + +export const iswindow = "window" in self; +export const isworker = "WorkerGlobalScope" in self; +export const issw = "ServiceWorkerGlobalScope" in self; +export const isdedicated = "DedicatedWorkerGlobalScope" in self; +export const isshared = "SharedWorkerGlobalScope" in self; + +export default function (client: ScramjetClient, self: typeof globalThis) { + function scope(identifier: any) { + // this will break iframe postmessage! + if ( + iswindow && + (identifier instanceof self.Window || + identifier instanceof self.top.window.Window || + identifier instanceof self.parent.window.Window) + ) { + return client.windowProxy; + } else if ( + (iswindow && identifier instanceof Location) || + (isworker && identifier instanceof WorkerLocation) + ) { + return client.locationProxy; + } else if (iswindow && identifier instanceof Document) { + return client.documentProxy; + } else if (isworker && identifier instanceof WorkerGlobalScope) { + return client.windowProxy; + } + + return identifier; + } + + // shorthand because this can get out of hand reall quickly + self.$s = scope; + + self.$tryset = function (lhs: any, op: string, rhs: any) { + if (lhs instanceof Location) { + // @ts-ignore + locationProxy.href = rhs; + + return true; + } + }; +} diff --git a/src/client/shared/worker.ts b/src/client/shared/worker.ts new file mode 100644 index 0000000..0920a2f --- /dev/null +++ b/src/client/shared/worker.ts @@ -0,0 +1,20 @@ +import { encodeUrl } from "../../shared/rewriters/url"; +import { ScramjetClient } from "../client"; + +export default function (client: ScramjetClient, self: typeof globalThis) { + client.Proxy("Worker", { + construct({ args }) { + if (args[0] instanceof URL) args[0] = args[0].href; + if (args[0].startsWith("blob:") || args[0].startsWith("data:")) { + // TODO + return; + } + + args[0] = encodeUrl(args[0]) + "?dest=worker"; + + if (args[1] && args[1].type === "module") { + args[0] += "&type=module"; + } + }, + }); +} diff --git a/src/client/trustedTypes.ts b/src/client/trustedTypes.ts deleted file mode 100644 index cab0e96..0000000 --- a/src/client/trustedTypes.ts +++ /dev/null @@ -1,41 +0,0 @@ -// import { rewriteHtml, rewriteJs, encodeUrl } from "./shared"; - -// trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, { -// apply(target, thisArg, argArray) { -// if (argArray[1].createHTML) { -// argArray[1].createHTML = new Proxy(argArray[1].createHTML, { -// apply(target1, thisArg1, argArray1) { -// return rewriteHtml(target1(...argArray1)); -// }, -// }); -// } -// -// if (argArray[1].createScript) { -// argArray[1].createScript = new Proxy(argArray[1].createScript, { -// apply(target1, thisArg1, argArray1) { -// return rewriteJs(target1(...argArray1)); -// }, -// }); -// } -// -// if (argArray[1].createScriptURL) { -// argArray[1].createScriptURL = new Proxy(argArray[1].createScriptURL, { -// apply(target1, thisArg1, argArray1) { -// return encodeUrl(target1(...argArray1)); -// }, -// }); -// } -// -// return Reflect.apply(target, thisArg, argArray); -// }, -// }); - -if ("window" in self) { - //@ts-nocheck - delete window.TrustedHTML; - delete window.TrustedScript; - delete window.TrustedScriptURL; - delete window.TrustedTypePolicy; - delete window.TrustedTypePolicyFactory; - delete window.trustedTypes; -} diff --git a/src/client/window.ts b/src/client/window.ts index 92ea9e9..1ada212 100644 --- a/src/client/window.ts +++ b/src/client/window.ts @@ -1,102 +1,97 @@ -import { client } from "."; import { encodeUrl } from "../shared/rewriters/url"; -import { locationProxy } from "./location"; +import { ScramjetClient } from "./client"; -export const windowProxy = new Proxy(self, { - get(target, prop) { - const propIsString = typeof prop === "string"; - if (propIsString && prop === "location") { - return locationProxy; - } else if ( - propIsString && - ["window", "top", "self", "globalThis"].includes(prop) - ) { - return windowProxy; - } else if (propIsString && prop == "parent") { - return self.parent; - } else if (propIsString && prop === "$scramjet") { - return; - } +export function createWindowProxy( + client: ScramjetClient, + self: typeof globalThis +): typeof globalThis { + return new Proxy(self, { + get(target, prop) { + const propIsString = typeof prop === "string"; + if (propIsString && prop === "location") { + return client.locationProxy; + } else if ( + propIsString && + ["window", "top", "self", "globalThis"].includes(prop) + ) { + return client.windowProxy; + } else if (propIsString && prop == "parent") { + return self.parent; + } else if (propIsString && prop === "$scramjet") { + return; + } - const value = Reflect.get(target, prop); + const value = Reflect.get(target, prop); - // this is bad! i don't know what the right thing to do is - if (typeof value === "function") { - return new Proxy(value, { - apply(_target, thisArg, argArray) { - return value.apply(self, argArray); - }, - }); - } + // this is bad! i don't know what the right thing to do is + if (typeof value === "function") { + return new Proxy(value, { + apply(_target, thisArg, argArray) { + return value.apply(self, argArray); + }, + }); + } - return value; - }, - - set(target, prop, newValue) { - // ensures that no apis are overwritten - if ( - typeof prop === "string" && - ["window", "top", "parent", "self", "globalThis", "location"].includes( - prop - ) - ) { - return false; - } - - return Reflect.set(target, prop, newValue); - }, - defineProperty(target, property, attributes) { - if (!attributes.get && !attributes.set) { - attributes.writable = true; - } - attributes.configurable = true; - - return Reflect.defineProperty(target, property, attributes); - }, -}); - -export const documentProxy = new Proxy(self.document || {}, { - get(target, prop) { - const propIsString = typeof prop === "string"; - - if (propIsString && prop === "location") { - return locationProxy; - } - - const value = Reflect.get(target, prop); - - if (typeof value === "function") { - return new Proxy(value, { - apply(_target, thisArg, argArray) { - return value.apply(self.document, argArray); - }, - }); - } - - return value; - }, - set(target, prop, newValue) { - if (typeof prop === "string" && prop === "location") { - //@ts-ignore - location = new URL(encodeUrl(newValue)); - - return; - } - - return Reflect.set(target, prop, newValue); - }, -}); - -client.Proxy( - [ - "Function.prototype.call", - "Function.prototype.bind", - "Function.prototype.apply", - ], - { - apply(ctx) { - if (ctx.args[0] === windowProxy) ctx.args[0] = window; - if (ctx.args[0] === documentProxy) ctx.args[0] = document; + return value; }, - } -); + + set(target, prop, newValue) { + // ensures that no apis are overwritten + if ( + typeof prop === "string" && + ["window", "top", "parent", "self", "globalThis", "location"].includes( + prop + ) + ) { + return false; + } + + return Reflect.set(target, prop, newValue); + }, + defineProperty(target, property, attributes) { + if (!attributes.get && !attributes.set) { + attributes.writable = true; + } + attributes.configurable = true; + + return Reflect.defineProperty(target, property, attributes); + }, + }); +} + +export function createDocumentProxy( + client: ScramjetClient, + self: typeof globalThis +) { + return new Proxy(self.document || {}, { + get(target, prop) { + const propIsString = typeof prop === "string"; + + if (propIsString && prop === "location") { + return client.locationProxy; + } + + const value = Reflect.get(target, prop); + + if (typeof value === "function") { + return new Proxy(value, { + apply(_target, thisArg, argArray) { + return value.apply(self.document, argArray); + }, + }); + } + + return value; + }, + set(target, prop, newValue) { + if (typeof prop === "string" && prop === "location") { + //@ts-ignore + location = new URL(encodeUrl(newValue)); + + return; + } + + return Reflect.set(target, prop, newValue); + }, + }); +} diff --git a/src/client/worker.ts b/src/client/worker.ts deleted file mode 100644 index 78d9c08..0000000 --- a/src/client/worker.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { client } from "."; -import { encodeUrl } from "./shared"; - -client.Proxy("Worker", { - construct({ args }) { - if (args[0] instanceof URL) args[0] = args[0].href; - if (args[0].startsWith("blob:") || args[0].startsWith("data:")) { - // TODO - return; - } - - args[0] = encodeUrl(args[0]) + "?dest=worker"; - - if (args[1] && args[1].type === "module") { - args[0] += "&type=module"; - } - }, -}); - -if ("window" in self) { - Worklet.prototype.addModule = new Proxy(Worklet.prototype.addModule, { - apply(target, thisArg, argArray) { - argArray[0] = encodeUrl(argArray[0]); - - return Reflect.apply(target, thisArg, argArray); - }, - }); - - client.Proxy("navigator.serviceWorker.register", { - apply(ctx) { - if (ctx.args[0] instanceof URL) ctx.args[0] = ctx.args[0].href; - let url = encodeUrl(ctx.args[0]) + "?dest=serviceworker"; - if (ctx.args[1] && ctx.args[1].type === "module") { - url += "&type=module"; - } - let worker = new SharedWorker(url); - - let handle = worker.port; - - navigator.serviceWorker.controller.postMessage({ - scramjet$type: "registerServiceWorker", - port: handle, - }); - - const fakeRegistration = new Proxy( - { - __proto__: ServiceWorkerRegistration.prototype, - }, - { - get(target, prop) { - if (prop === "installing") { - return null; - } - if (prop === "waiting") { - return null; - } - if (prop === "active") { - return handle; - } - if (prop === "scope") { - return ctx.args[0]; - } - - return Reflect.get(target, prop); - }, - } - ); - - ctx.return(new Promise((resolve) => resolve(fakeRegistration))); - }, - }); -} - -client.Proxy("importScripts", { - apply(ctx) { - for (const i in ctx.args) { - ctx.args[i] = encodeUrl(ctx.args[i]); - } - }, -}); diff --git a/src/client/worker/worker.ts b/src/client/worker/worker.ts new file mode 100644 index 0000000..8e375cc --- /dev/null +++ b/src/client/worker/worker.ts @@ -0,0 +1,61 @@ +// if ("window" in self) { +// Worklet.prototype.addModule = new Proxy(Worklet.prototype.addModule, { +// apply(target, thisArg, argArray) { +// argArray[0] = encodeUrl(argArray[0]); +// +// return Reflect.apply(target, thisArg, argArray); +// }, +// }); +// +// client.Proxy("navigator.serviceWorker.register", { +// apply(ctx) { +// if (ctx.args[0] instanceof URL) ctx.args[0] = ctx.args[0].href; +// let url = encodeUrl(ctx.args[0]) + "?dest=serviceworker"; +// if (ctx.args[1] && ctx.args[1].type === "module") { +// url += "&type=module"; +// } +// let worker = new SharedWorker(url); +// +// let handle = worker.port; +// +// navigator.serviceWorker.controller.postMessage({ +// scramjet$type: "registerServiceWorker", +// port: handle, +// }); +// +// const fakeRegistration = new Proxy( +// { +// __proto__: ServiceWorkerRegistration.prototype, +// }, +// { +// get(target, prop) { +// if (prop === "installing") { +// return null; +// } +// if (prop === "waiting") { +// return null; +// } +// if (prop === "active") { +// return handle; +// } +// if (prop === "scope") { +// return ctx.args[0]; +// } +// +// return Reflect.get(target, prop); +// }, +// } +// ); +// +// ctx.return(new Promise((resolve) => resolve(fakeRegistration))); +// }, +// }); +// } +// +// client.Proxy("importScripts", { +// apply(ctx) { +// for (const i in ctx.args) { +// ctx.args[i] = encodeUrl(ctx.args[i]); +// } +// }, +// }); diff --git a/src/scramjet.config.ts b/src/scramjet.config.ts index 468fb34..736622b 100644 --- a/src/scramjet.config.ts +++ b/src/scramjet.config.ts @@ -4,7 +4,7 @@ if (!self.$scramjet) { } self.$scramjet.config = { prefix: "/scramjet/", - codec: self.$scramjet.codecs.base64, + codec: self.$scramjet.codecs.plain, config: "/scram/scramjet.config.js", shared: "/scram/scramjet.shared.js", worker: "/scram/scramjet.worker.js",