Use session storage instead of location.hash
This commit is contained in:
parent
7cf9537f3e
commit
69c66e7d99
|
@ -1,38 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a `Uint8Array` to a URL-safe base64 string.
|
|
||||||
* @param {Uint8Array} bytes
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export const stringify = (bytes) => {
|
|
||||||
const string = String.fromCharCode(...bytes);
|
|
||||||
const base64 = btoa(string);
|
|
||||||
return base64
|
|
||||||
.replaceAll("+", "-")
|
|
||||||
.replaceAll("/", "_")
|
|
||||||
.replaceAll("=", "");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a URL-safe base64 string to a `Uint8Array`.
|
|
||||||
* Returns `null` if the string is invalid.
|
|
||||||
* @param {string} text
|
|
||||||
* @returns {null | Uint8Array}
|
|
||||||
*/
|
|
||||||
export const parse = (text) => {
|
|
||||||
const normalizedText = text
|
|
||||||
.replaceAll("-", "+")
|
|
||||||
.replaceAll("_", "/")
|
|
||||||
.replaceAll(" ", "+");
|
|
||||||
try {
|
|
||||||
const string = atob(normalizedText);
|
|
||||||
const bytes = new Uint8Array(string.length);
|
|
||||||
for (let i = 0; i < string.length; i++) {
|
|
||||||
bytes[i] = string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
} catch (_err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,41 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
import maybeJsonParse from "../common/maybeJsonParse.js";
|
|
||||||
import * as base64url from "../common/base64url.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a location hash into `name` and `bytes`.
|
|
||||||
* @param {string} hash
|
|
||||||
* @returns {null | { name: string, bytes: Uint8Array }} The parsed data, or `null` if the hash can't be parsed.
|
|
||||||
*/
|
|
||||||
export default (hash) => {
|
|
||||||
const normalizedHash = decodeURI(hash.replace(/^#/, ""));
|
|
||||||
|
|
||||||
const parsed = maybeJsonParse(normalizedHash);
|
|
||||||
if (!parsed || typeof parsed !== "object") {
|
|
||||||
console.warn("Couldn't parse hash as JSON object");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!("name" in parsed) || (typeof parsed.name !== "string") ||
|
|
||||||
!("bytes" in parsed) || (typeof parsed.bytes !== "string")
|
|
||||||
) {
|
|
||||||
console.warn("Hash fields missing or invalid type");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name } = parsed;
|
|
||||||
if (!name) {
|
|
||||||
console.warn("Name is empty");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes = base64url.parse(parsed.bytes);
|
|
||||||
if (!bytes || !bytes.byteLength) {
|
|
||||||
console.warn("Bytes is empty");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name, bytes };
|
|
||||||
};
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*! base64-js
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Jameson Little
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// deno-lint-ignore-file
|
||||||
|
// deno-fmt-ignore-file
|
||||||
|
|
||||||
|
var B=Object.create;var l=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var H=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),U=(r,e)=>{for(var t in e)l(r,t,{get:e[t],enumerable:!0})},A=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of k(e))!j.call(r,o)&&o!==t&&l(r,o,{get:()=>e[o],enumerable:!(a=_(e,o))||a.enumerable});return r},u=(r,e,t)=>(A(r,e,"default"),t&&A(t,e,"default")),C=(r,e,t)=>(t=r!=null?B(w(r)):{},A(e||!r||!r.__esModule?l(t,"default",{value:r,enumerable:!0}):t,r));var p=H(y=>{"use strict";y.byteLength=I;y.toByteArray=T;y.fromByteArray=D;var h=[],d=[],E=typeof Uint8Array<"u"?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(F=0,L=s.length;F<L;++F)h[F]=s[F],d[s.charCodeAt(F)]=F;var F,L;d["-".charCodeAt(0)]=62;d["_".charCodeAt(0)]=63;function g(r){var e=r.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var t=r.indexOf("=");t===-1&&(t=e);var a=t===e?0:4-t%4;return[t,a]}function I(r){var e=g(r),t=e[0],a=e[1];return(t+a)*3/4-a}function O(r,e,t){return(e+t)*3/4-t}function T(r){var e,t=g(r),a=t[0],o=t[1],n=new E(O(r,a,o)),v=0,x=o>0?a-4:a,f;for(f=0;f<x;f+=4)e=d[r.charCodeAt(f)]<<18|d[r.charCodeAt(f+1)]<<12|d[r.charCodeAt(f+2)]<<6|d[r.charCodeAt(f+3)],n[v++]=e>>16&255,n[v++]=e>>8&255,n[v++]=e&255;return o===2&&(e=d[r.charCodeAt(f)]<<2|d[r.charCodeAt(f+1)]>>4,n[v++]=e&255),o===1&&(e=d[r.charCodeAt(f)]<<10|d[r.charCodeAt(f+1)]<<4|d[r.charCodeAt(f+2)]>>2,n[v++]=e>>8&255,n[v++]=e&255),n}function q(r){return h[r>>18&63]+h[r>>12&63]+h[r>>6&63]+h[r&63]}function z(r,e,t){for(var a,o=[],n=e;n<t;n+=3)a=(r[n]<<16&16711680)+(r[n+1]<<8&65280)+(r[n+2]&255),o.push(q(a));return o.join("")}function D(r){for(var e,t=r.length,a=t%3,o=[],n=16383,v=0,x=t-a;v<x;v+=n)o.push(z(r,v,v+n>x?x:v+n));return a===1?(e=r[t-1],o.push(h[e>>2]+h[e<<4&63]+"==")):a===2&&(e=(r[t-2]<<8)+r[t-1],o.push(h[e>>10]+h[e>>4&63]+h[e<<2&63]+"=")),o.join("")}});var c={};U(c,{byteLength:()=>G,default:()=>N,fromByteArray:()=>K,toByteArray:()=>J});var i=C(p());u(c,C(p()));var{byteLength:G,toByteArray:J,fromByteArray:K}=i,{default:m,...M}=i,N=m!==void 0?m:M;export{G as byteLength,N as default,K as fromByteArray,J as toByteArray};
|
|
@ -1,17 +1,16 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import parseHash from "../common/parseHash.js";
|
import * as base64 from "../common/vendor/base64-js.js";
|
||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
// TODO: We may want a better UI here.
|
// TODO: We may want a better UI here.
|
||||||
// TODO: Handle hash changes.
|
// TODO: Handle errors.
|
||||||
const parsedHash = parseHash(location.hash);
|
const fileDataBase64 = window.sessionStorage.getItem("fileData");
|
||||||
if (!parsedHash) {
|
if (!fileDataBase64) {
|
||||||
location.href = "..";
|
location.href = "..";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bytes = base64.toByteArray(fileDataBase64);
|
||||||
const { bytes } = parsedHash;
|
|
||||||
|
|
||||||
console.log(bytes);
|
console.log(bytes);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
import * as base64 from "../common/vendor/base64-js.js";
|
||||||
import { SUPPORTED_FILE_TYPES } from "./constants.js";
|
import { SUPPORTED_FILE_TYPES } from "./constants.js";
|
||||||
import crel from "../common/crel.js";
|
import crel from "../common/crel.js";
|
||||||
import * as base64url from "../common/base64url.js";
|
|
||||||
import routeFile from "./routeFile.js";
|
import routeFile from "./routeFile.js";
|
||||||
|
|
||||||
const accept = SUPPORTED_FILE_TYPES
|
const accept = SUPPORTED_FILE_TYPES
|
||||||
|
@ -51,16 +51,6 @@ const disclaimerParagraphEl = crel(
|
||||||
crel("small", {}, "Your files do not leave your computer."),
|
crel("small", {}, "Your files do not leave your computer."),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Blob} blob
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
const formatBytes = async (blob) => {
|
|
||||||
const arrayBuffer = await blob.arrayBuffer();
|
|
||||||
const bytes = new Uint8Array(arrayBuffer);
|
|
||||||
return base64url.stringify(bytes);
|
|
||||||
};
|
|
||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
const appEl = document.getElementById("app");
|
const appEl = document.getElementById("app");
|
||||||
if (!appEl) throw new Error("HTML is not set up correctly");
|
if (!appEl) throw new Error("HTML is not set up correctly");
|
||||||
|
@ -81,10 +71,10 @@ const main = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
location.href = route + "#" + JSON.stringify({
|
const fileData = new Uint8Array(await file.arrayBuffer());
|
||||||
name: file.name,
|
window.sessionStorage.setItem("fileData", base64.fromByteArray(fileData));
|
||||||
bytes: await formatBytes(file),
|
|
||||||
});
|
location.href = route;
|
||||||
});
|
});
|
||||||
|
|
||||||
appEl.append(labelParagraphEl, inputContainerEl, disclaimerParagraphEl);
|
appEl.append(labelParagraphEl, inputContainerEl, disclaimerParagraphEl);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import parseHash from "../common/parseHash.js";
|
import * as base64 from "../common/vendor/base64-js.js";
|
||||||
import parsePng from "./parsePng.js";
|
import parsePng from "./parsePng.js";
|
||||||
import getNodeUi from "./getNodeUi.js";
|
import getNodeUi from "./getNodeUi.js";
|
||||||
import explorer from "./explorer.js";
|
import explorer from "./explorer.js";
|
||||||
|
@ -11,14 +11,13 @@ if (!errorEl || !explorerEl) throw new Error("HTML is not set up correctly");
|
||||||
|
|
||||||
const main = () => {
|
const main = () => {
|
||||||
// TODO: We may want a better UI here.
|
// TODO: We may want a better UI here.
|
||||||
// TODO: Handle hash changes.
|
// TODO: Handle errors.
|
||||||
const parsedHash = parseHash(location.hash);
|
const fileDataBase64 = window.sessionStorage.getItem("fileData");
|
||||||
if (!parsedHash) {
|
if (!fileDataBase64) {
|
||||||
location.href = "..";
|
location.href = "..";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bytes = base64.toByteArray(fileDataBase64);
|
||||||
const { bytes } = parsedHash;
|
|
||||||
|
|
||||||
const rootNode = parsePng(bytes);
|
const rootNode = parsePng(bytes);
|
||||||
if (!rootNode) {
|
if (!rootNode) {
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { assertEquals } from "assert";
|
|
||||||
import { b } from "../helpers.ts";
|
|
||||||
import * as base64url from "../../public/common/base64url.js";
|
|
||||||
|
|
||||||
Deno.test('encodes no bytes as ""', () => {
|
|
||||||
assertEquals(base64url.stringify(b()), "");
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("encodes in a URL-safe way", () => {
|
|
||||||
const input = b(105, 183, 62, 249, 215, 191, 254);
|
|
||||||
assertEquals(
|
|
||||||
base64url.stringify(input),
|
|
||||||
"abc--de__g",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("decodes the empty string", () => {
|
|
||||||
assertEquals(base64url.parse(""), b());
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("decodes URL-safe strings", () => {
|
|
||||||
assertEquals(base64url.parse("_-o"), b(255, 234));
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("decodes URL-unsafe strings (as a bonus)", () => {
|
|
||||||
assertEquals(base64url.parse("/+o="), b(255, 234));
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("round-trips", () => {
|
|
||||||
const testCases = [
|
|
||||||
b(),
|
|
||||||
b(1),
|
|
||||||
b(1, 2, 3),
|
|
||||||
b(255, 234),
|
|
||||||
b(105, 183, 62, 249, 215, 191, 254),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const original of testCases) {
|
|
||||||
const string = base64url.stringify(original);
|
|
||||||
const parsed = base64url.parse(string);
|
|
||||||
assertEquals(parsed, original, `Round-trip failed for ${original}`);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { assertEquals } from "assert";
|
|
||||||
import { stubbingWarn } from "../helpers.ts";
|
|
||||||
import parseHash from "../../public/common/parseHash.js";
|
|
||||||
|
|
||||||
Deno.test(
|
|
||||||
"returns null if hash cannot be parsed",
|
|
||||||
stubbingWarn(() => {
|
|
||||||
const testCases = [
|
|
||||||
// Missing fields
|
|
||||||
"#null",
|
|
||||||
"#{}",
|
|
||||||
"#{%22name%22:%22image.png%22}",
|
|
||||||
"#{%22bytes%22:%22AQID%22}",
|
|
||||||
// Invalid JSON
|
|
||||||
"",
|
|
||||||
"#{%22name%22:%22small.png%22,%22bytes%22:%22iAQID",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
assertEquals(
|
|
||||||
parseHash(testCase),
|
|
||||||
null,
|
|
||||||
`Parsing ${testCase} should fail`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Deno.test("parses hashes", () => {
|
|
||||||
const hash = "#{%22name%22:%22small.png%22,%22bytes%22:%22AQID%22}";
|
|
||||||
assertEquals(parseHash(hash), {
|
|
||||||
name: "small.png",
|
|
||||||
bytes: new Uint8Array([1, 2, 3]),
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue