start client api

This commit is contained in:
Avad3 2024-05-27 17:11:45 -04:00
parent 9f2ad08edf
commit 7433e0a7ff
7 changed files with 131 additions and 10 deletions

View file

@ -37,6 +37,7 @@
"esbuild-server": "^0.3.0", "esbuild-server": "^0.3.0",
"fastify": "^4.26.2", "fastify": "^4.26.2",
"htmlparser2": "^9.1.0", "htmlparser2": "^9.1.0",
"idb-keyval": "^6.2.1",
"meriyah": "^4.4.2" "meriyah": "^4.4.2"
} }
} }

8
pnpm-lock.yaml generated
View file

@ -35,6 +35,9 @@ importers:
htmlparser2: htmlparser2:
specifier: ^9.1.0 specifier: ^9.1.0
version: 9.1.0 version: 9.1.0
idb-keyval:
specifier: ^6.2.1
version: 6.2.1
meriyah: meriyah:
specifier: ^4.4.2 specifier: ^4.4.2
version: 4.4.2 version: 4.4.2
@ -809,6 +812,9 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
idb-keyval@6.2.1:
resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==}
ieee754@1.2.1: ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@ -2076,6 +2082,8 @@ snapshots:
statuses: 2.0.1 statuses: 2.0.1
toidentifier: 1.0.1 toidentifier: 1.0.1
idb-keyval@6.2.1: {}
ieee754@1.2.1: {} ieee754@1.2.1: {}
ignore@5.3.1: {} ignore@5.3.1: {}

View file

@ -3,6 +3,16 @@ import { rewriteCss } from "./rewriters/css";
import { rewriteHtml, rewriteSrcset } from "./rewriters/html"; import { rewriteHtml, rewriteSrcset } from "./rewriters/html";
import { rewriteJs } from "./rewriters/js"; import { rewriteJs } from "./rewriters/js";
import { rewriteHeaders } from "./rewriters/headers"; import { rewriteHeaders } from "./rewriters/headers";
import * as idb from "idb-keyval";
export function isScramjetFile(src: string) {
let bool = false;
["codecs", "client", "bundle", "worker", "config"].forEach((file) => {
if (src === self.__scramjet$config[file]) bool = true;
});
return bool;
}
const bundle = { const bundle = {
rewriters: { rewriters: {
@ -14,7 +24,9 @@ const bundle = {
rewriteSrcset, rewriteSrcset,
rewriteJs, rewriteJs,
rewriteHeaders rewriteHeaders
} },
idb,
isScramjetFile
} }
declare global { declare global {

View file

@ -1,13 +1,11 @@
import { Parser } from "htmlparser2"; import { Parser } from "htmlparser2";
import { DomHandler } from "domhandler"; import { DomHandler, Element } from "domhandler";
import { hasAttrib } from "domutils"; import { hasAttrib, prependChild } from "domutils";
import render from "dom-serializer"; import render from "dom-serializer";
import { encodeUrl } from "./url"; import { encodeUrl } from "./url";
import { rewriteCss } from "./css"; import { rewriteCss } from "./css";
import { rewriteJs } from "./js"; import { rewriteJs } from "./js";
import { isScramjetFile } from "../";
// html nodes to rewrite
// meta
export function rewriteHtml(html: string, origin?: URL) { export function rewriteHtml(html: string, origin?: URL) {
const handler = new DomHandler((err, dom) => dom); const handler = new DomHandler((err, dom) => dom);
@ -26,9 +24,10 @@ function traverseParsedHtml(node, origin?: URL) {
if (hasAttrib(node, "csp")) delete node.attribs.csp; if (hasAttrib(node, "csp")) delete node.attribs.csp;
/* url attributes */ /* url attributes */
if (hasAttrib(node, "src")) node.attribs.src = encodeUrl(node.attribs.src, origin); 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, "href")) node.attribs.href = encodeUrl(node.attribs.href, origin);
if (hasAttrib(node, "data")) node.attribs.data = encodeUrl(node.attribs.data, 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); if (hasAttrib(node, "formaction")) node.attribs.formaction = encodeUrl(node.attribs.formaction, origin);
if (hasAttrib(node, "form")) node.attribs.action = encodeUrl(node.attribs.action, origin); if (hasAttrib(node, "form")) node.attribs.action = encodeUrl(node.attribs.action, origin);
@ -41,11 +40,21 @@ function traverseParsedHtml(node, origin?: URL) {
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 === "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.name === "meta" && hasAttrib(node, "http-equiv")) {
if (node.attribs["http-equiv"] === "content-security-policy") { if (node.attribs["http-equiv"] === "content-security-policy") {
return; node = {};
} else if (node.attribs["http-equiv"] === "refresh") { } else if (node.attribs["http-equiv"] === "refresh") {
node.attribs.content = node.attribs.content.split(";url=").map((elem, index) => index === 1 ? encodeUrl(elem) : elem).join(";url="); const contentArray = node.attribs.content.split(";url=");
contentArray[1] = encodeUrl(contentArray[1], origin);
node.attribs.content = contentArray.join(";url=");
} }
} }
if (node.name === "head") {
["codecs", "config", "bundle", "client"].forEach((script) => {
prependChild(node, new Element("script", {
src: self.__scramjet$config[script]
}));
});
}
if (node.childNodes) { if (node.childNodes) {
for (const childNode in node.childNodes) { for (const childNode in node.childNodes) {

View file

@ -0,0 +1,8 @@
import "./location";
import "./storage";
declare global {
interface Window {
__location: Location;
}
}

View file

@ -0,0 +1,30 @@
function urlLocation() {
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(self.__scramjet$bundle.rewriters.url.encodeUrl(url));
loc.toString = () => loc.href;
return loc;
}
export function locationProxy() {
const loc = urlLocation();
return new Proxy(Location, {
get(target, prop) {
return loc[prop];
},
set(obj, prop, value) {
if (prop === "href") {
location.href = self.__scramjet$bundle.rewriters.url.encodeUrl(value);
} else {
loc[prop] = value;
}
return true;
}
})
}
window.__location = locationProxy();

53
src/client/storage.ts Normal file
View file

@ -0,0 +1,53 @@
function storageProxy(scope: Storage): Storage {
// sessionStorage isn't properly implemented currently, since everything is being stored in IDB
const { set, get, keys, del, createStore } = self.__scramjet$bundle.idb;
const store = createStore(window.__location.host, "store");
return new Proxy(scope, {
get(target, prop) {
switch (prop) {
case "getItem":
return async function getItem(key: string) {
return await get(key, store);
}
case "setItem":
return async function setItem(key: string, value: string) {
await set(key, value, store);
}
case "removeItem":
return async function removeItem(key: string) {
await del(key, store);
}
case "clear":
return async function clear() {
// clear can't be used because the names are the exact same
await self.__scramjet$bundle.idb.clear(store);
}
case "key":
return async function key(index: number) {
// supposed to be key but key is the name of the function
return (await keys(store))[index];
}
case "length":
return (async ()=>{
return (await keys(store)).length;
})();
}
},
});
}
const localStorageProxy = storageProxy(window.localStorage);
const sessionStorageProxy = storageProxy(window.sessionStorage);
delete window.localStorage;
delete window.sessionStorage;
window.localStorage = localStorageProxy;
window.sessionStorage = sessionStorageProxy;