diff --git a/README.md b/README.md index e2c7f37..fa5e320 100644 --- a/README.md +++ b/README.md @@ -19,5 +19,3 @@ Running `pnpm dev` will build Scramjet and start a dev server on localhost:1337. - Only thing rewritten currently are imports and exports - Check imports/exports for values contained in the `importmap` array, don't rewrite the node value if present - Write client APIs -- The DAMN SCRIPTS not being executed -- esbuild bundling more than what it's supposed to (instead of using imports, it bundles the whole file which is not what we want) \ No newline at end of file diff --git a/esbuild.dev.js b/esbuild.dev.js index 9c5fee4..0a338d5 100644 --- a/esbuild.dev.js +++ b/esbuild.dev.js @@ -15,7 +15,7 @@ const bare = createBareServer("/bare/", { }); const fastify = Fastify({ - serverFactory: (handler) => { + serverFactory: (handler, opts) => { return createServer() .on("request", (req, res) => { if (bare.shouldRoute(req)) { @@ -53,14 +53,12 @@ const devServer = await context({ worker: "./src/worker/index.ts", codecs: "./src/codecs/index.ts", config: "./src/scramjet.config.ts", - html: "./src/html/index.ts" }, entryNames: "scramjet.[name]", outdir: "./dist", bundle: true, sourcemap: true, logLevel: "info", - format: "esm", plugins: [ copy({ resolveFrom: "cwd", diff --git a/esbuild.js b/esbuild.js index 0866ee8..a84d92b 100644 --- a/esbuild.js +++ b/esbuild.js @@ -9,7 +9,6 @@ const scramjetBuild = await build({ worker: "./src/worker/index.ts", codecs: "./src/codecs/index.ts", config: "./src/scramjet.config.ts", - html: "./src/html/index.ts" }, entryNames: "scramjet.[name]", outdir: "./dist", @@ -20,8 +19,7 @@ const scramjetBuild = await build({ logLevel: "info", metafile: true, treeShaking: true, - minify: true, - format: "esm" + minify: true }); writeFileSync("./meta.json", JSON.stringify(scramjetBuild.metafile)); diff --git a/src/bundle/index.ts b/src/bundle/index.ts index f780ba9..7be93f5 100644 --- a/src/bundle/index.ts +++ b/src/bundle/index.ts @@ -1,6 +1,6 @@ import { encodeUrl, decodeUrl } from "./rewriters/url"; import { rewriteCss } from "./rewriters/css"; -import { rewriteSrcset } from "./rewriters/srcset"; +import { rewriteHtml, rewriteSrcset } from "./rewriters/html"; import { rewriteJs } from "./rewriters/js"; import { rewriteHeaders } from "./rewriters/headers"; @@ -13,4 +13,24 @@ export function isScramjetFile(src: string) { return bool; } -export { encodeUrl, decodeUrl, rewriteCss, rewriteSrcset, rewriteJs, rewriteHeaders }; \ No newline at end of file +const bundle = { + rewriters: { + url: { + encodeUrl, decodeUrl + }, + rewriteCss, + rewriteHtml, + rewriteSrcset, + rewriteJs, + rewriteHeaders + }, + isScramjetFile +} + +declare global { + interface Window { + __scramjet$bundle: typeof bundle; + } +} + +self.__scramjet$bundle = bundle; \ No newline at end of file diff --git a/src/bundle/rewriters/html.ts b/src/bundle/rewriters/html.ts new file mode 100644 index 0000000..e0deb3b --- /dev/null +++ b/src/bundle/rewriters/html.ts @@ -0,0 +1,86 @@ +import { Parser } from "htmlparser2"; +import { DomHandler, Element } from "domhandler"; +import { hasAttrib } from "domutils"; +import render from "dom-serializer"; +import { encodeUrl } from "./url"; +import { rewriteCss } from "./css"; +import { rewriteJs } from "./js"; +import { isScramjetFile } from "../"; + +export function rewriteHtml(html: string, origin?: URL) { + const handler = new DomHandler((err, dom) => dom); + const parser = new Parser(handler); + + parser.write(html); + parser.end(); + + return render(traverseParsedHtml(handler.root, origin)); +} + +// i need to add the attributes in during rewriting + +function traverseParsedHtml(node, origin?: URL) { + /* csp attributes */ + if (hasAttrib(node, "nonce")) delete node.attribs.nonce; + if (hasAttrib(node, "integrity")) delete node.attribs.integrity; + if (hasAttrib(node, "csp")) delete node.attribs.csp; + + /* url attributes */ + if (hasAttrib(node, "src") && !isScramjetFile(node.attribs.src)) node.attribs.src = encodeUrl(node.attribs.src, origin); + if (hasAttrib(node, "href")) node.attribs.href = encodeUrl(node.attribs.href, origin); + if (hasAttrib(node, "data")) node.attribs.data = encodeUrl(node.attribs.data, origin); + if (hasAttrib(node, "action")) node.attribs.action = encodeUrl(node.attribs.action, origin); + if (hasAttrib(node, "formaction")) node.attribs.formaction = encodeUrl(node.attribs.formaction, origin); + + /* other */ + if (hasAttrib(node, "srcdoc")) node.attribs.srcdoc = rewriteHtml(node.attribs.srcdoc, origin); + if (hasAttrib(node, "srcset")) node.attribs.srcset = rewriteSrcset(node.attribs.srcset, origin); + if (hasAttrib(node, "imagesrcset")) node.attribs.imagesrcset = rewriteSrcset(node.attribs.imagesrcset, origin); + if (hasAttrib(node, "style")) node.attribs.style = rewriteCss(node.attribs.style, origin); + + if (node.name === "style" && node.children[0] !== undefined) node.children[0].data = rewriteCss(node.children[0].data, origin); + if (node.name === "script" && /(application|text)\/javascript|importmap|undefined/.test(node.attribs.type) && node.children[0] !== undefined) node.children[0].data = rewriteJs(node.children[0].data, origin); + if (node.name === "meta" && hasAttrib(node, "http-equiv")) { + if (node.attribs["http-equiv"] === "content-security-policy") { + node = {}; + } else if (node.attribs["http-equiv"] === "refresh" && node.attribs.content.includes("url")) { + const contentArray = node.attribs.content.split("url="); + contentArray[1] = encodeUrl(contentArray[1].trim(), origin); + node.attribs.content = contentArray.join("url="); + } + } + + if (node.name === "head") { + const scramjetScripts = []; + ["codecs", "config", "bundle", "client"].forEach((script) => { + scramjetScripts.push(new Element("script", { + src: self.__scramjet$config[script] + })); + }); + + node.children.unshift(...scramjetScripts); + } + + if (node.childNodes) { + for (const childNode in node.childNodes) { + node.childNodes[childNode] = traverseParsedHtml(node.childNodes[childNode], origin); + } + } + + return node; +} + +// stole from osana lmao +export function rewriteSrcset(srcset: string, origin?: URL) { + const urls = srcset.split(/ [0-9]+x,? ?/g); + if (!urls) return ""; + const sufixes = srcset.match(/ [0-9]+x,? ?/g); + if (!sufixes) return ""; + const rewrittenUrls = urls.map((url, i) => { + if (url && sufixes[i]) { + return encodeUrl(url, origin) + sufixes[i]; + } + }); + + return rewrittenUrls.join(""); +} diff --git a/src/bundle/rewriters/srcset.ts b/src/bundle/rewriters/srcset.ts deleted file mode 100644 index cc3db7b..0000000 --- a/src/bundle/rewriters/srcset.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { encodeUrl } from "./url"; - -export function rewriteSrcset(srcset: string, origin?: URL) { - const urls = srcset.split(/ [0-9]+x,? ?/g); - if (!urls) return ""; - const sufixes = srcset.match(/ [0-9]+x,? ?/g); - if (!sufixes) return ""; - const rewrittenUrls = urls.map((url, i) => { - if (url && sufixes[i]) { - return encodeUrl(url, origin) + sufixes[i]; - } - }); - - return rewrittenUrls.join(""); -} diff --git a/src/bundle/rewriters/url.ts b/src/bundle/rewriters/url.ts index 9e0444f..c7f8d11 100644 --- a/src/bundle/rewriters/url.ts +++ b/src/bundle/rewriters/url.ts @@ -1,6 +1,6 @@ import { rewriteJs } from "./js"; -function canParseUrl(url: string, origin: string | URL) { +function canParseUrl(url: string, origin?: URL) { try { new URL(url, origin); @@ -11,14 +11,14 @@ function canParseUrl(url: string, origin: string | URL) { } // something is broken with this but i didn't debug it -export function encodeUrl(url: string, origin?: string | URL) { +export function encodeUrl(url: string, origin?: URL) { if (!origin) { origin = new URL(self.__scramjet$config.codec.decode(location.href.slice((location.origin + self.__scramjet$config.prefix).length))); } if (url.startsWith("javascript:")) { return "javascript:" + rewriteJs(url.slice("javascript:".length)); - } else if (/^(#|mailto|about|data)/.test(url) || url.startsWith(location.origin + self.__scramjet$config.prefix)) { + } else if (/^(#|mailto|about|data)/.test(url)) { return url; } else if (canParseUrl(url, origin)) { return location.origin + self.__scramjet$config.prefix + self.__scramjet$config.codec.encode(new URL(url, origin).href); @@ -29,7 +29,9 @@ export function encodeUrl(url: string, origin?: string | URL) { export function decodeUrl(url: string) { if (/^(#|about|data|mailto|javascript)/.test(url)) { return url; - } else { + } else if (canParseUrl(url)) { return self.__scramjet$config.codec.decode(url.slice((location.origin + self.__scramjet$config.prefix).length)) + } else { + return url; } } \ No newline at end of file diff --git a/src/client/beacon.ts b/src/client/beacon.ts index bf377eb..b42e173 100644 --- a/src/client/beacon.ts +++ b/src/client/beacon.ts @@ -1,8 +1,6 @@ -import { encodeUrl } from "../bundle"; - navigator.sendBeacon = new Proxy(navigator.sendBeacon, { apply(target, thisArg, argArray) { - argArray[0] = encodeUrl(argArray[0]); + argArray[0] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[0]); return Reflect.apply(target, thisArg, argArray); }, diff --git a/src/client/css.ts b/src/client/css.ts index 2e5e61b..34ec502 100644 --- a/src/client/css.ts +++ b/src/client/css.ts @@ -1,23 +1,21 @@ -import { rewriteCss } from "../bundle"; - const cssProperties = ["background", "background-image", "mask", "mask-image", "list-style", "list-style-image", "border-image", "border-image-source", "cursor"]; const jsProperties = ["background", "backgroundImage", "mask", "maskImage", "listStyle", "listStyleImage", "borderImage", "borderImageSource", "cursor"]; - + CSSStyleDeclaration.prototype.setProperty = new Proxy(CSSStyleDeclaration.prototype.setProperty, { - apply(target, thisArg, argArray) { - if (cssProperties.includes(argArray[0])) argArray[1] = rewriteCss(argArray[1]); - - return Reflect.apply(target, thisArg, argArray); - }, + apply(target, thisArg, argArray) { + if (cssProperties.includes(argArray[0])) argArray[1] = self.__scramjet$bundle.rewriters.rewriteCss(argArray[1]); + + return Reflect.apply(target, thisArg, argArray); + }, }); jsProperties.forEach((prop) => { - const propDescriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, prop); + const propDescriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, prop); - Object.defineProperty(CSSStyleDeclaration.prototype, prop, { - set(v) { - propDescriptor.set.call(this, rewriteCss(v)); - }, - }) + Object.defineProperty(CSSStyleDeclaration.prototype, prop, { + set(v) { + propDescriptor.set.call(this, self.__scramjet$bundle.rewriters.rewriteCss(v)); + }, + }) }); diff --git a/src/client/element.ts b/src/client/element.ts index b696fab..616b7c1 100644 --- a/src/client/element.ts +++ b/src/client/element.ts @@ -1,6 +1,3 @@ -import { encodeUrl, rewriteCss, rewriteJs, rewriteSrcset } from "../bundle"; -import { rewriteHtml } from "../html"; - // object // iframe // embed @@ -40,34 +37,25 @@ Object.keys(attribs).forEach((attrib: string) => { const descriptor = Object.getOwnPropertyDescriptor(element.prototype, attrib); Object.defineProperty(element.prototype, attrib, { get() { - return this.dataset[`${attrib}`]; + return descriptor.get.call(this, [this.dataset[`_${attrib}`]]); }, set(value) { - if (this.dataset["scramjet"]) { - return; - } - console.log(value); - this.dataset[`${attrib}`] = value; + this.dataset[`_${attrib}`] = value; if (/nonce|integrity|csp/.test(attrib)) { this.removeAttribute(attrib); } else if (/src|href|data|action|formaction/.test(attrib)) { - // @ts-expect-error - // TrustedScriptURL does not exist as a type yet, but it is a real thing if (value instanceof TrustedScriptURL) { return; } - value = encodeUrl(value); + value = self.__scramjet$bundle.rewriters.url.encodeUrl(value); } else if (attrib === "srcdoc") { - // @ts-ignore - // This needs to be ignored because I'm bad at TypeScript - - value = rewriteHtml(value).documentElement.innerHTML; + value = self.__scramjet$bundle.rewriters.rewriteHtml(value); } else if (/(image)?srcset/.test(attrib)) { - value = rewriteSrcset(value); + value = self.__scramjet$bundle.rewriters.rewriteSrcset(value); } else if (attrib === "style") { - value = rewriteCss(value); + value = self.__scramjet$bundle.rewriters.rewriteCss(value); } descriptor.set.call(this, value); @@ -76,35 +64,38 @@ Object.keys(attribs).forEach((attrib: string) => { }) }); -Element.prototype.getAttribute = new Proxy(Element.prototype.getAttribute, { +HTMLElement.prototype.getAttribute = new Proxy(Element.prototype.getAttribute, { apply(target, thisArg, argArray) { - if (Object.keys(attribs).includes(argArray[0]) && thisArg.dataset[`${argArray[0]}`]) { - return thisArg.dataset[`${argArray[0]}`]; + console.log(thisArg); + if (Object.keys(attribs).includes(argArray[0])) { + argArray[0] = `_${argArray[0]}`; } return Reflect.apply(target, thisArg, argArray); }, }); -Element.prototype.setAttribute = new Proxy(Element.prototype.setAttribute, { +// setAttribute proxy is currently broken + +HTMLElement.prototype.setAttribute = new Proxy(Element.prototype.setAttribute, { apply(target, thisArg, argArray) { if (thisArg.dataset["scramjet"]) { return; } console.log(argArray[1]) if (Object.keys(attribs).includes(argArray[0])) { - thisArg.dataset[`${argArray[0]}`] = argArray[1]; + thisArg.dataset[`_${argArray[0]}`] = argArray[1]; if (/nonce|integrity|csp/.test(argArray[0])) { return; } else if (/src|href|data|action|formaction/.test(argArray[0])) { - argArray[1] = encodeUrl(argArray[1]); + console.log(thisArg); + argArray[1] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[1]); } else if (argArray[0] === "srcdoc") { - // @ts-ignore - argArray[1] = rewriteHtml(argArray[1]).documentElement.innerHTML; + argArray[1] = self.__scramjet$bundle.rewriters.rewriteHtml(argArray[1]); } else if (/(image)?srcset/.test(argArray[0])) { - argArray[1] = rewriteSrcset(argArray[1]); + argArray[1] = self.__scramjet$bundle.rewriters.rewriteSrcset(argArray[1]); } else if (argArray[1] === "style") { - argArray[1] = rewriteCss(argArray[1]); + argArray[1] = self.__scramjet$bundle.rewriters.rewriteCss(argArray[1]); } } @@ -117,22 +108,17 @@ const innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML" Object.defineProperty(HTMLElement.prototype, "innerHTML", { set(value) { if (this instanceof HTMLScriptElement) { - // @ts-expect-error - // TrustedScript does not exist as a type yet, but it is a real thing if (!(value instanceof TrustedScript)) { - value = rewriteJs(value); + value = self.__scramjet$bundle.rewriters.rewriteJs(value); } } else if (this instanceof HTMLStyleElement) { - value = rewriteCss(value); + value = self.__scramjet$bundle.rewriters.rewriteCss(value); } else { - // @ts-expect-error - // TrustedHTML does not exist as a type, but it is a real thing if (!(value instanceof TrustedHTML)) { - // @ts-ignore - value = rewriteHtml(value).documentElement.innerHTML; + value = self.__scramjet$bundle.rewriters.rewriteHtml(value); } } - innerHTML.set.call(this, value); + return innerHTML.set.call(this, value); }, -}); +}) \ No newline at end of file diff --git a/src/client/eval.ts b/src/client/eval.ts index c811304..27dd6b1 100644 --- a/src/client/eval.ts +++ b/src/client/eval.ts @@ -1,18 +1,16 @@ -import { rewriteJs } from "../bundle"; - const FunctionProxy = new Proxy(Function, { construct(target, argArray) { if (argArray.length === 1) { - return Reflect.construct(target, rewriteJs(argArray[0])); + return Reflect.construct(target, self.__scramjet$bundle.rewriters.rewriteJs(argArray[0])); } else { - return Reflect.construct(target, rewriteJs(argArray[argArray.length - 1])) + return Reflect.construct(target, self.__scramjet$bundle.rewriters.rewriteJs(argArray[argArray.length - 1])) } }, apply(target, thisArg, argArray) { if (argArray.length === 1) { - return Reflect.apply(target, undefined, rewriteJs(argArray[0])); + return Reflect.apply(target, undefined, self.__scramjet$bundle.rewriters.rewriteJs(argArray[0])); } else { - return Reflect.apply(target, undefined, [...argArray.map((x, index) => index === argArray.length - 1), rewriteJs(argArray[argArray.length - 1])]) + return Reflect.apply(target, undefined, [...argArray.map((x, index) => index === argArray.length - 1), self.__scramjet$bundle.rewriters.rewriteJs(argArray[argArray.length - 1])]) } }, }); diff --git a/src/client/fetch.ts b/src/client/fetch.ts index 172e425..ed40f1c 100644 --- a/src/client/fetch.ts +++ b/src/client/fetch.ts @@ -1,12 +1,8 @@ // ts throws an error if you dont do window.fetch -import { encodeUrl, rewriteHeaders } from "../bundle"; - window.fetch = new Proxy(window.fetch, { apply(target, thisArg, argArray) { - console.log(argArray); - if (!(argArray[0] instanceof Request)) argArray[0] = encodeUrl(argArray[0]); - console.log(argArray); + argArray[0] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[0]); return Reflect.apply(target, thisArg, argArray); }, @@ -14,7 +10,7 @@ window.fetch = new Proxy(window.fetch, { Headers = new Proxy(Headers, { construct(target, argArray, newTarget) { - argArray[0] = rewriteHeaders(argArray[0]); + argArray[0] = self.__scramjet$bundle.rewriters.rewriteHeaders(argArray[0]); return Reflect.construct(target, argArray, newTarget); }, @@ -22,7 +18,7 @@ Headers = new Proxy(Headers, { Request = new Proxy(Request, { construct(target, argArray, newTarget) { - if (typeof argArray[0] === "string") argArray[0] = encodeUrl(argArray[0]); + if (typeof argArray[0] === "string") argArray[0] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[0]); return Reflect.construct(target, argArray, newTarget); }, @@ -30,8 +26,8 @@ Request = new Proxy(Request, { Response.redirect = new Proxy(Response.redirect, { apply(target, thisArg, argArray) { - argArray[0] = encodeUrl(argArray[0]); + argArray[0] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[0]); return Reflect.apply(target, thisArg, argArray); }, -}); +}); \ No newline at end of file diff --git a/src/client/index.ts b/src/client/index.ts index c591b66..b2eaa43 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -11,4 +11,4 @@ declare global { interface Window { __location: Location; } -} \ No newline at end of file +} diff --git a/src/client/location.ts b/src/client/location.ts index 85554f6..67aac60 100644 --- a/src/client/location.ts +++ b/src/client/location.ts @@ -1,11 +1,10 @@ // @ts-nocheck -import { encodeUrl, decodeUrl } from "../bundle"; function urlLocation() { - const loc = new URL(decodeUrl(location.href)); - loc.assign = (url: string) => location.assign(encodeUrl(url)); + let loc = new URL(self.__scramjet$bundle.rewriters.url.decodeUrl(location.href)); + loc.assign = (url: string) => location.assign(self.__scramjet$bundle.rewriters.url.encodeUrl(url)); loc.reload = () => location.reload(); - loc.replace = (url: string) => location.replace(encodeUrl(url)); + loc.replace = (url: string) => location.replace(self.__scramjet$bundle.rewriters.url.encodeUrl(url)); loc.toString = () => loc.href; return loc; @@ -21,7 +20,7 @@ export function LocationProxy() { set(obj, prop, value) { if (prop === "href") { - location.href = encodeUrl(value); + location.href = self.__scramjet$bundle.rewriters.url.encodeUrl(value); } else { loc[prop] = value; } diff --git a/src/client/trustedTypes.ts b/src/client/trustedTypes.ts index 2894a71..3806a97 100644 --- a/src/client/trustedTypes.ts +++ b/src/client/trustedTypes.ts @@ -1,14 +1,9 @@ -import { encodeUrl, rewriteJs } from "../bundle"; -import { rewriteHtml } from "../html"; - -// @ts-expect-error -// trustedTypes isn't a type on the global scope yet 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)).documentElement.innerHTML; + return self.__scramjet$bundle.rewriters.rewriteHtml(target1(...argArray1)); }, }); } @@ -16,7 +11,7 @@ trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, { if (argArray[1].createScript) { argArray[1].createScript = new Proxy(argArray[1].createScript, { apply(target1, thisArg1, argArray1) { - return rewriteJs(target1(...argArray1)); + return self.__scramjet$bundle.rewriters.rewriteJs(target1(...argArray1)); }, }); } @@ -24,7 +19,7 @@ trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, { if (argArray[1].createScriptURL) { argArray[1].createScriptURL = new Proxy(argArray[1].createScriptURL, { apply(target1, thisArg1, argArray1) { - return encodeUrl(target1(...argArray1)) + return self.__scramjet$bundle.rewriters.url.encodeUrl(target1(...argArray1)) }, }) } diff --git a/src/client/xmlhttprequest.ts b/src/client/xmlhttprequest.ts index bee9567..8422a08 100644 --- a/src/client/xmlhttprequest.ts +++ b/src/client/xmlhttprequest.ts @@ -1,20 +1,18 @@ -import { encodeUrl, rewriteHeaders } from "../bundle"; - XMLHttpRequest.prototype.open = new Proxy(XMLHttpRequest.prototype.open, { - apply(target, thisArg, argArray) { - if (argArray[1]) argArray[1] = encodeUrl(argArray[1]); + apply(target, thisArg, argArray) { + if (argArray[1]) argArray[1] = self.__scramjet$bundle.rewriters.url.encodeUrl(argArray[1]); - return Reflect.apply(target, thisArg, argArray); - }, + return Reflect.apply(target, thisArg, argArray); + }, }); XMLHttpRequest.prototype.setRequestHeader = new Proxy(XMLHttpRequest.prototype.setRequestHeader, { - apply(target, thisArg, argArray) { - let headerObject = Object.fromEntries([argArray]); - headerObject = rewriteHeaders(headerObject); + apply(target, thisArg, argArray) { + let headerObject = Object.fromEntries([argArray]); + headerObject = self.__scramjet$bundle.rewriters.rewriteHeaders(headerObject); - argArray = Object.entries(headerObject)[0]; + argArray = Object.entries(headerObject)[0]; - return Reflect.apply(target, thisArg, argArray); - }, + return Reflect.apply(target, thisArg, argArray); + }, }); diff --git a/src/html/index.ts b/src/html/index.ts deleted file mode 100644 index d97ecdb..0000000 --- a/src/html/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -// this code needs to be bundled as a separate file due to when it is ran - -import { encodeUrl, rewriteCss, rewriteJs, rewriteSrcset } from "../bundle"; -import Clone from "./cloner.ts"; - -const parser = new DOMParser(); - -function parseHtml(html: string) { - return parser.parseFromString(html, "text/html"); -} - -function traverseParsedHtml(node: Element) { - for (const cspAttr of ["csp", "nonce", "integrity"]) { - if (node.hasAttribute(cspAttr)) { - node.setAttribute("data-" + cspAttr, node.getAttribute(cspAttr)); - node.removeAttribute(cspAttr); - } - } - - for (const urlAttr of ["src", "href", "data", "action"]) { - if (node.hasAttribute(urlAttr) && !node.hasAttribute("data-scramjet")) { - const url = node.getAttribute(urlAttr); - node.setAttribute("data-" + urlAttr, url) - node.setAttribute(urlAttr, encodeUrl(node.getAttribute(urlAttr))); - } - } - - for (const srcsetAttr of ["srcset", "imagesrcset"]) { - if (node.hasAttribute(srcsetAttr)) { - const srcset = node.getAttribute(srcsetAttr); - node.setAttribute("data-", srcset); - node.setAttribute(srcsetAttr, rewriteSrcset(srcset)); - } - } - - if (node.hasAttribute("srcdoc")) { - const srcdoc = node.getAttribute("srcdoc"); - node.setAttribute("data-srcdoc", srcdoc); - - const rewrittenSrcdoc = rewriteHtml(srcdoc); - node.setAttribute("srcdoc", rewrittenSrcdoc.documentElement.innerHTML); - } - - if (node instanceof HTMLScriptElement) { - if (node.hasAttribute("data-scramjet")) { - return; - } - node.innerHTML = rewriteJs(node.textContent); - } else if (node instanceof HTMLStyleElement) { - node.innerHTML = rewriteCss(node.textContent); - } else if (node instanceof HTMLHeadElement) { - // this array is reversed because it uses node.prepend() - for (const scramjetScript of ["client", "config", "codecs"]) { - const script = document.createElement("script"); - script.src = self.__scramjet$config[scramjetScript]; - script.setAttribute("data-scramjet", ""); - node.prepend(script); - } - } - - if (node.children) { - for (const child of node.children) { - traverseParsedHtml(child); - } - } -} - -export function rewriteHtml(html: string) { - const parsedHtml = parseHtml(html); - traverseParsedHtml(parsedHtml.documentElement); - - return parsedHtml; -} - -navigator.serviceWorker.ready.then(({ active }) => { - if (active) { - active.postMessage("rewriteHtml"); - } -}); - -navigator.serviceWorker.addEventListener("message", (message) => { - document.documentElement.replaceWith(rewriteHtml(message.data).documentElement); - - const scripts = document.querySelectorAll("script:not([data-scramjet])"); - - for (const script of scripts) { - const clone = new Clone(script); - clone.insertCopy(); - clone.removeElement(); - } -}); \ No newline at end of file diff --git a/src/scramjet.config.ts b/src/scramjet.config.ts index fd9d50a..ffc27fc 100644 --- a/src/scramjet.config.ts +++ b/src/scramjet.config.ts @@ -10,7 +10,6 @@ declare global { worker: string; client: string; codecs: string; - html: string; } } } @@ -22,6 +21,5 @@ self.__scramjet$config = { bundle: "/scramjet.bundle.js", worker: "/scramjet.worker.js", client: "/scramjet.client.js", - codecs: "/scramjet.codecs.js", - html: "/scramjet.html.js" + codecs: "/scramjet.codecs.js" } \ No newline at end of file diff --git a/src/worker/index.ts b/src/worker/index.ts index 0afbe28..e0590fc 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -1,6 +1,5 @@ import { BareClient } from "@mercuryworkshop/bare-mux"; -import { BareResponseFetch } from "@mercuryworkshop/bare-mux"; -import { encodeUrl, decodeUrl, rewriteCss, rewriteJs, rewriteHeaders } from "../bundle"; +import { BareResponseFetch } from "@mercuryworkshop/bare-mux" declare global { interface Window { @@ -8,11 +7,9 @@ declare global { } } -export class ScramjetServiceWorker { +self.ScramjetServiceWorker = class ScramjetServiceWorker { client: typeof BareClient.prototype; config: typeof self.__scramjet$config; - html: string; - constructor(config = self.__scramjet$config) { this.client = new BareClient(); if (!config.prefix) config.prefix = "/scramjet/"; @@ -28,11 +25,11 @@ export class ScramjetServiceWorker { const urlParam = new URLSearchParams(new URL(request.url).search); if (urlParam.has("url")) { - return Response.redirect(encodeUrl(urlParam.get("url"), new URL(urlParam.get("url")))) + return Response.redirect(self.__scramjet$bundle.rewriters.url.encodeUrl(urlParam.get("url"), new URL(urlParam.get("url")))) } try { - const url = new URL(decodeUrl(request.url)); + const url = new URL(self.__scramjet$bundle.rewriters.url.decodeUrl(request.url)); const response: BareResponseFetch = await this.client.fetch(url, { method: request.method, @@ -45,28 +42,18 @@ export class ScramjetServiceWorker { }); let responseBody; - const responseHeaders = rewriteHeaders(response.rawHeaders, url); + const responseHeaders = self.__scramjet$bundle.rewriters.rewriteHeaders(response.rawHeaders, url); if (response.body) { switch (request.destination) { case "iframe": case "document": - if (responseHeaders["content-type"].startsWith("text/html")) { - responseBody = - ` -
- ${["codecs", "config", "html"].map((script) => "").join("")} - - `; - this.html = await response.text(); - } else { - responseBody = response.body - } + responseBody = self.__scramjet$bundle.rewriters.rewriteHtml(await response.text(), url); break; case "script": - responseBody = rewriteJs(await response.text(), url); + responseBody = self.__scramjet$bundle.rewriters.rewriteJs(await response.text(), url); break; case "style": - responseBody = rewriteCss(await response.text(), url); + responseBody = self.__scramjet$bundle.rewriters.rewriteCss(await response.text(), url); break; case "sharedworker": break; @@ -117,13 +104,7 @@ export class ScramjetServiceWorker { console.error(err); - return renderError(err, decodeUrl(request.url)); - } - } - - async messageListener(message: MessageEvent) { - if (message.data === "rewriteHtml") { - message.source.postMessage(this.html); + return renderError(err, self.__scramjet$bundle.rewriters.url.decodeUrl(request.url)); } } } diff --git a/static/sw.js b/static/sw.js index af4db20..655b310 100644 --- a/static/sw.js +++ b/static/sw.js @@ -1,6 +1,7 @@ -import { ScramjetServiceWorker } from "./scramjet.worker.js"; -import "./scramjet.codecs.js"; -import "./scramjet.config.js"; +importScripts("scramjet.codecs.js"); +importScripts("scramjet.config.js"); +importScripts( __scramjet$config.bundle || "scramjet.bundle.js") +importScripts( __scramjet$config.worker || "scramjet.worker.js"); const scramjet = new ScramjetServiceWorker(); @@ -12,8 +13,4 @@ self.addEventListener("fetch", async (event) => { return await fetch(event.request); } })()) -}); - -self.addEventListener("message", async (message) => { - await scramjet.messageListener(message); -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/static/ui.js b/static/ui.js index 8cbc298..1e533c4 100644 --- a/static/ui.js +++ b/static/ui.js @@ -1,6 +1,5 @@ navigator.serviceWorker.register("./sw.js", { - scope: __scramjet$config.prefix, - type: "module" + scope: __scramjet$config.prefix }) const connection = new BareMux.BareMuxConnection("/bare-mux-worker.js") const flex = css`display: flex;`;