diff --git a/docs/bitty.js b/docs/bitty.js
index 11e81fb..c79349c 100644
--- a/docs/bitty.js
+++ b/docs/bitty.js
@@ -16,7 +16,146 @@ const dataUrlRE =
// Fragment characters: A-Z a-z 0-9 + / =
// ? : @ - . _ ~ ! $ & ' ( ) * , ; and kinda (#)
+class DataURL {
+ constructor(url) {
+ let match = url.match(dataUrlRE);
+ let info = match.groups;
+ Object.assign(this, info);
+ this.params = info.params ? JSON.parse('{"' + decodeURI(info.params?.substring(1)).replace(/"/g, '\\"').replace(/;/g, '","').replace(/=/g,'":"') + '"}') : {};
+ if (this.encoding) this.data = this.data.replace(/=/g,"");
+ }
+
+ get href() {
+ let urlString = "data:";
+ if (this.mediatype) urlString += this.mediatype
+ if (this.params) Object.entries(this.params).forEach( e => urlString += `;${e[0]}=${e[1]}`)
+ if (this.encoding) urlString += ";" + this.encoding
+ urlString += "," + (this.dataPrefix || '') + this.data;
+ return urlString;
+ }
+
+ get format() {
+ return this.params.format || this.encoding;
+ }
+
+ decompress = async () => {
+
+ // Decrypt if needed
+ if (this.params.cipher) {
+ console.log(this.params.cipher, decryptData)
+ this.data = await decryptData(this.params.cipher, this.data);
+ }
+
+ // Decompress if needed
+ if (this.format != "base64") {
+ let bytes = base64ToByteArray(this.data);
+ this.rawData = await decompressData(bytes, this.format)
+ this.data = await dataToBase64(this.rawData);
+ delete this.params.format
+ this.encoding = BASE64_MARKER
+ }
+
+ return this;
+ }
+
+ compress = async (format = LZMA_MARKER) => {
+ console.log("data", this.data.length, {data:this.data})
+ let rawData = await base64ToByteArray(this.data);
+
+ let compressedData = await compressData(rawData, format);
+ var base64String = dataToBase64(compressedData);
+ this.data = base64String;
+ this.params.format = format;
+ return this;
+ }
+
+}
+
+async function testCompression(rawData) {
+ let gz = await compressData(rawData, GZIP_MARKER);
+ console.log(gz.length, typeof gz, dataToBase64(gz).length);
+
+ let xz = await compressData(rawData, LZMA_MARKER);
+ console.log(xz.length, typeof xz, dataToBase64(xz).length);
+
+ let ungz = await decompressData(gz, GZIP_MARKER);
+ let unxz = await decompressData(xz, LZMA_MARKER);
+
+ console.log("unzip", ungz == rawData, unxz==rawData,{ungz, unxz,rawData,
+ raw: byteArrayToString(rawData).substring(684),
+ ungzs: byteArrayToString(ungz).substring(684),
+ unxzs: byteArrayToString(unxz).substring(684)
+ }, (byteArrayToString(ungz)) == byteArrayToString(unxz))
+}
+
+async function compressData(data, encoding = GZIP_MARKER, callback) {
+ console.log("compressing with", encoding)
+ if (encoding == GZIP_MARKER) {
+ return import("/js/gzip/pako.js").then((module) => {
+ console.log({gzdata:data})
+ return pako.deflate(data, {level:"9"});
+ });
+ } else if (encoding == BROT_MARKER) {
+
+ } else if (encoding == LZMA_MARKER) {
+ return new Promise(function(resolve, reject) {
+ console.log({xz:data})
+
+ LZMA.compress(data, 9, function(result, error) {
+ if (error) reject(error);
+ resolve(result);
+ });
+ });
+ }
+}
+
+function stringToByteArray(string) {
+ return new TextEncoder().encode(string);
+ return Uint8Array.from(string, c => c.charCodeAt(0));
+}
+
+function byteArrayToString(bytes) {
+ return new TextDecoder().decode(bytes);
+ return String.fromCharCode.apply(null, new Uint8Array(bytes));
+}
+
+async function decompressData(data, encoding, callback) {
+ if (encoding == GZIP_MARKER) {
+ return import("/js/gzip/pako.js").then((module) => {
+ let byteArray = pako.inflate(data);
+ return byteArray;
+ });
+ } else if (encoding == BROT_MARKER) {
+ return import("/js/brotli/decode.js").then((module) => {
+ return module.BrotliDecode(data);
+ });
+ } else if (encoding == LZMA_MARKER || encoding == LZMA64_MARKER) {
+ return new Promise(function(resolve, reject) {
+ LZMA.decompress(data, (result, error) => {
+ if (error) reject(error);
+ resolve(stringToByteArray(result));
+ });
+ });
+ }
+}
+
+
+async function decryptData(cipher, base64) {
+ return new Promise((resolve, reject) => {
+ loadScript("/js/crypto-js.min.js", () => {
+ console.log("decrypting", cipher)
+ let pass = prompt("This page is encrypted. What's the passcode?");
+ if (!pass) resolve(base64);
+
+ let decrypted = CryptoJS[cipher.toUpperCase()].decrypt(base64, pass);
+ return resolve(CryptoJS.enc.Base64.stringify(decrypted));
+ })
+ })
+}
+
+
function infoForDataURL(url) {
+ return new DataURL(url);
let match = url.match(dataUrlRE);
let info = match.groups;
// info.params = new URLSearchParams(info?.groups.attrs?.substring(1).replace(/;/g, "&"));
@@ -29,6 +168,11 @@ var LZMA64_MARKER = 'bxze64';
var GZIP64_MARKER = 'gzip64';
var BROT64_MARKER = 'brot64';
+var BASE_MARKER = 'bs';
+var LZMA_MARKER = 'xz';
+var GZIP_MARKER = 'gz';
+var BROT_MARKER = 'br';
+
function compressDataURL(dataURL, callback) {
var base64Index = dataURL.indexOf(BASE64_MARKER);
var base64 = dataURL.substring(base64Index + BASE64_MARKER.length + 1);
@@ -38,15 +182,19 @@ function compressDataURL(dataURL, callback) {
}
function base64ToByteArray(base64) {
- var raw = window.atob(base64);
- var rawLength = raw.length;
- var array = new Uint8Array(new ArrayBuffer(rawLength));
- for(let i = 0; i < rawLength; i++) {
- array[i] = raw.charCodeAt(i);
- }
- return array;
+ return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
}
+// function base64ToByteArray(base64) {
+// var raw = window.atob(base64);
+// var rawLength = raw.length;
+// var array = new Uint8Array(new ArrayBuffer(rawLength));
+// for(let i = 0; i < rawLength; i++) {
+// array[i] = raw.charCodeAt(i);
+// }
+// return array;
+// }
+
function loadScript(src, callback) {
let script = el("script", { src });
script.addEventListener('load', function(e) {
@@ -63,8 +211,6 @@ function escapeStringForIMessage(str) {
return str;
}
-// import * as CryptoJS from "";
-
function decryptBase64(cipher, base64, callback) {
if (!cipher) return callback(base64);
@@ -104,11 +250,13 @@ function decompressDataURL(dataURL, preamble, callback) {
function compressString(string, encoding = LZMA64_MARKER, callback) {
if (encoding == LZMA64_MARKER) {
- LZMA.compress(string, 9, function(result, error) {
- if (error) console.error(error);
- var base64String = window.btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
- return callback(base64String);
- });
+ loadScript("/js/lzma/lzma_worker-min.js", () => {
+ LZMA.compress(string, 9, function(result, error) {
+ if (error) console.error(error);
+ var base64String = window.btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
+ return callback(base64String);
+ });
+ })
} else if (encoding == BROT64_MARKER) {
// import("/js/brotli/decode.js").then((module) => {
// console.log("module", module)
@@ -143,6 +291,47 @@ function decompressString(data, encoding, callback) {
}
}
+
+// echo -n 'hello world' | brotli | base64
+// DwWAaGVsbG8gd29ybGQD
+
+// echo -n 'hello world' | gzip -9 | base64
+// H4sIAPfMmGICA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA==
+
+// echo -n 'hello world' | lzma -9 | base64
+// XQAAAAT//////////wA0GUnujekXiTozYAX3z2T/+3ggAA==
+
+
+
+function dataToBase64(data) {
+ return btoa(String.fromCharCode.apply(null, new Uint8Array(data)));
+}
+
+function dataToBase64FR(data) {
+ return new Promise((resolve, reject) => {
+ if (!data || !data.length) return resolve("");
+
+ var fr = new FileReader();
+ fr.onload = () => resolve(fr.result.split(',')[1]);
+ fr.onerror = reject;
+ fr.readAsDataURL(new Blob([data], {encoding:"UTF-8",type:"text/html;charset=UTF-8"}));
+ })
+}
+
+function dataURLToString(durl) {
+ return fetch(durl)
+ .then(r => r.blob())
+ .then(blob => {
+ return new Promise((resolve, reject) => {
+ var fr = new FileReader();
+ fr.onload = () => resolve(fr.result)
+ fr.onerror = reject;
+ fr.readAsText(blob);
+ })
+ })
+}
+
+
function stringToData(string, callback) {
if (!string.length) return callback("");
var a = new FileReader();
@@ -151,7 +340,7 @@ function stringToData(string, callback) {
}
function dataToString(data, callback) {
- newDataURLtoBlob(data).then(blob => {
+ return newDataURLtoBlob(data).then(blob => {
var reader = new FileReader();
reader.onload = function(e) { callback(reader.result) }
reader.readAsText(blob);
@@ -178,6 +367,7 @@ function dataURLtoBlob(dataURL) {
export {
+ DataURL,
infoForDataURL,
stringToData,
dataToString,
@@ -189,4 +379,8 @@ export {
LZMA64_MARKER,
GZIP64_MARKER,
BROT64_MARKER,
+ BASE_MARKER,
+ LZMA_MARKER,
+ GZIP_MARKER,
+ BROT_MARKER,
};
diff --git a/docs/index.html b/docs/index.html
index ce6ed81..8134a78 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -7,6 +7,8 @@
+
+
diff --git a/docs/index.js b/docs/index.js
index f87203a..62cb91d 100644
--- a/docs/index.js
+++ b/docs/index.js
@@ -1,8 +1,9 @@
import * as bitty from './bitty.js';
+ window.bitty = bitty;
+
const HEAD_TAGS = () => btoa('\n');
const HEAD_TAGS_EXTENDED = () => btoa(` `);
-
// if ('serviceWorker' in navigator) {
// navigator.serviceWorker
@@ -40,7 +41,7 @@
"text/rawhtml": {script:"parse"},
"javascript": {script:"bookmarklet"},
"web3": {script:"web3", mode:"frame"},
-}
+ }
window.addEventListener("message", function(e) {
console.debug("Message:", e.origin, e.data)
@@ -53,15 +54,27 @@
if (e.data.image) path.push("i/" + encodeURIComponent(btoa(e.data.image)));
window.location.pathname = path.join('/');
}
+
if (e.data.replaceURL) {
- window.history.replaceState(null, null, e.data.replaceURL);
- renderContent();
+ if (e.data.compressURL) {
+ let durl = new bitty.DataURL(e.data.replaceURL);
+
+ durl.compress().then(arg => {
+ window.history.replaceState(null, null, "/#/" + arg.href);
+ renderContent();
+ })
+
+ } else {
+ window.history.replaceState(null, null, e.data.replaceURL);
+ renderContent();
+ }
}
+
if (e.data.setStorage) document.localStorage.setItem(contentHash, e.data.set);
if (e.data.getStorage) document.getElementById("iframe").postMessage(document.localStorage.getItem(contentHash), e.origin)
}, false);
- function renderContent() {
+ async function renderContent() {
var fragment = window.location.hash.substring(1);
if (window.location.search) { // Redirect search to path (coming out of server opengraph forwarding)
@@ -138,14 +151,6 @@
renderMode = "script";
}
- // if (info?.encoding == "base64" && !info.params?.encode) {
- // bitty.compressDataURL(fragment, function(compressedFragment) {
- // console.log("Compressing long url", fragment.length, compressedFragment.length)
- // window.location.hash = window.location.hash.replace(fragment, compressedFragment);
- // window.location.reload();
- // console.log("Reloading")
- // })
- // }
} else {
var colon = fragment.indexOf(":");
if ( colon > 0 && colon < 15) {
@@ -155,15 +160,15 @@
let renderer = renderers[scheme];
if (renderer) {
- return renderContentWithScript(renderer.script, title, info, fragment, fragment);
+ return renderContentWithScript(false, renderer.script, title, info, fragment, fragment);
}
return window.location.replace(fragment);
}
var compressed = true;
dataPrefix = HEAD_TAGS_EXTENDED();
- let encoding = !compressed ? "base64," : (fragment.startsWith("XQA") ? bitty.LZMA64_MARKER : bitty.GZIP64_MARKER);
- fragment = "data:text/html;charset=utf-8;" + encoding + "," + fragment;
+ let encoding = !compressed ? "base64," : (fragment.startsWith("XQA") ? bitty.LZMA_MARKER : bitty.GZIP_MARKER);
+ fragment = "data:text/html;charset=utf-8;format=" + encoding + ";base64," + fragment;
}
@@ -173,16 +178,25 @@
'Edge only supports shorter URLs (maximum 2083 bytes).
Larger sites may require a different browser.
Learn more';
}
- bitty.decompressDataURL(fragment, dataPrefix, function(dataURL, dataContent) {
+ let durl = new bitty.DataURL(fragment);
+
+ await durl.decompress();
+
+ durl.dataPrefix = dataPrefix;
+ let dataURL = durl.href;
+ let dataContent = durl.rawData;
+ // bitty.decompressDataURL(fragment, dataPrefix, function(dataURL, dataContent)
+ {
if (!dataURL) return;
iframe.sandbox = "allow-downloads allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox";
if (isIE && renderMode == "data") renderMode = "frame";
let contentTarget// = iframe.contentWindow.document;
if (isWatch) {
+ console.log("Rendering for watch")
contentTarget = document;
}
- console.log("Rendering mode: " + "\x1B[1m" + renderMode)
+ console.log("Rendering mode: " + "\x1B[1m" + renderMode, durl)
// dataURL = dataURL.replace("application/ld+json", "text/plain");
if (renderMode == "download") {
try {
@@ -208,11 +222,11 @@
if (renderMode == "frame") {
writeDocContent(contentTarget, content)
} else if (renderMode == "script") {
- renderContentWithScript(script, title, info, content, dataURL);
+ renderContentWithScript(contentTarget == document, script, title, info, content, dataURL);
}
});
}
- });
+ }
let recordHistory = false
if (recordHistory) recordToHistory(title, type, description, window.location);
@@ -222,16 +236,29 @@
window.addEventListener('load',renderContent);
// window.addEventListener('hashchange',renderContent);
+
const SCRIPT_LOADER = ``
- function renderContentWithScript(script, title, info, body, url) {
+ function renderContentWithScript(overwrite, script, title, info, body, url) {
if (script.indexOf("/") == -1) script = location.origin + '/render/' + script + '.js'
- iframe.onload = (() => {
- iframe.contentWindow.postMessage({script, url, title, info, body}, "*");
- delete iframe.onload
- });
- // writeDocContent(iframe.contentWindow.document, SCRIPT_LOADER)
- // iframe.srcdoc = SCRIPT_LOADER;
- iframe.src = "data:text/html," + SCRIPT_LOADER;
+
+ if (overwrite) {
+ let scriptEl = document.createElement("script")
+ scriptEl.src = "/render.js"
+ scriptEl.addEventListener('load', function(e) {
+ console.log("Loaded script", scriptEl.src);
+ renderScriptContent({script, url, title, info, body}, "*");
+ });
+ document.head.appendChild(scriptEl);
+
+ } else {
+ iframe.onload = (() => {
+ iframe.contentWindow.postMessage({script, url, title, info, body}, "*");
+ delete iframe.onload
+ });
+ // writeDocContent(iframe.contentWindow.document, SCRIPT_LOADER)
+ // iframe.srcdoc = SCRIPT_LOADER;
+ iframe.src = "data:text/html," + SCRIPT_LOADER;
+ }
}
function writeDocContent(doc, content) {
diff --git a/docs/render.js b/docs/render.js
index 16d5bf5..18b5af1 100644
--- a/docs/render.js
+++ b/docs/render.js
@@ -27,12 +27,15 @@ function loadSyle(href) {
document.head.appendChild(el("link", { type: "text/css", rel: "stylesheet", href}));
}
-window.addEventListener("message", function(e) {
- var base = el('base', {href: e.data.script});
+function renderScriptContent(data, origin) {
+ var base = el('base', {href: data});
document.head.appendChild(base);
+ window.params = data;
+ window.params.origin = origin;
+ console.log("Rendering with", data.script, data)
+ loadScript(data.script);
+}
- window.params = e.data;
- window.params.origin = e.origin;
- console.log("Rendering with", e.data.script, e.data)
- loadScript(e.data.script);
+window.addEventListener("message", function(e) {
+ renderScriptContent(e.data, e.origin);
}, false);
diff --git a/docs/render/parse.js b/docs/render/parse.js
index b13a633..48edd1f 100644
--- a/docs/render/parse.js
+++ b/docs/render/parse.js
@@ -2,21 +2,41 @@
parent.postMessage({title:"Parsing Content..."}, "*");
-document.write("PARSING CONTENT…")
+document.body.appendChild(document.createTextNode("• • •"))
const parser = new DOMParser();
const doc = parser.parseFromString(params.body, "text/html");
+
let ldjson = doc.querySelector('script[type="application/ld+json"]')
if (ldjson) {
- ldjson = ldjson.innerText.trim();
- let f = new FileReader();
- f.onload = function(e) {
- //top.location.href = ( '/#/' + e.target.result);
- parent.postMessage({replaceURL:'/#/' + e.target.result}, "*");
- };
- f.readAsDataURL(new Blob([ldjson],{type : 'application/ld+json;charset=utf-8'}));
+ cleanRecipe(ldjson)
} else {
alert("No recipe found")
-}
\ No newline at end of file
+}
+
+
+
+function cleanRecipe(ldjson) {
+
+ try {
+ var json = JSON.parse(ldjson.innerText); //JSON.parse(data);
+ if (json["@type"] != "Recipe") {
+ json = (json["@graph"] ?? json).find((item) => item["@type"] == "Recipe")
+ }
+
+ delete json.review;
+ delete json.video;
+
+ let f = new FileReader();
+ f.onload = function(e) {
+ parent.postMessage({replaceURL:e.target.result, compressURL:"true"}, "*");
+ };
+ f.readAsDataURL(new Blob([JSON.stringify(json)],{type : 'application/ld+json;charset=utf-8'}));
+
+ } catch (e) {
+ console.debug("Data", e, {ldjson});
+ }
+}
+