Compare commits

...

3 commits

Author SHA1 Message Date
0d5f9f3288
Fix clap 3.0.0-beta.5 renaming 2021-10-26 22:30:37 -07:00
9bb265670e
Remove commented out dep 2021-10-26 22:25:28 -07:00
William Tan
6ef8d48db0
Add support for previewing zip files 2021-10-26 22:23:31 -07:00
7 changed files with 200 additions and 44 deletions

137
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aead" name = "aead"
version = "0.4.3" version = "0.4.3"
@ -154,9 +160,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.7.1" version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]] [[package]]
name = "byte-unit" name = "byte-unit"
@ -173,6 +179,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.1.0"
@ -267,9 +279,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.0.0-beta.4" version = "3.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406" checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@ -280,14 +292,14 @@ dependencies = [
"strsim", "strsim",
"termcolor", "termcolor",
"textwrap", "textwrap",
"vec_map", "unicase",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "3.0.0-beta.4" version = "3.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac" checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@ -315,6 +327,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@ -326,9 +347,9 @@ dependencies = [
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.28" version = "0.8.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -339,6 +360,18 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e"
[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -498,9 +531,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -523,18 +556,18 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]] [[package]]
name = "headers" name = "headers"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855" checksum = "a4c4eb0471fcb85846d8b0690695ef354f9afb11cb03cac2e1d7c9253351afb0"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags", "bitflags",
"bytes", "bytes",
"headers-core", "headers-core",
"http", "http",
"httpdate",
"mime", "mime",
"sha-1", "sha-1",
"time",
] ]
[[package]] [[package]]
@ -577,9 +610,9 @@ dependencies = [
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@ -600,9 +633,9 @@ checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.13" version = "0.14.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -702,9 +735,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.103" version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -771,10 +804,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677" checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
[[package]] [[package]]
name = "mio" name = "miniz_oxide"
version = "0.7.13" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
@ -917,10 +960,12 @@ dependencies = [
"js-sys", "js-sys",
"omegaupload-common", "omegaupload-common",
"reqwasm", "reqwasm",
"serde",
"tree_magic_mini", "tree_magic_mini",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"zip",
] ]
[[package]] [[package]]
@ -937,9 +982,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "3.1.0" version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d" checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
@ -1008,9 +1056,9 @@ dependencies = [
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
@ -1157,9 +1205,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.5" version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51c732d463dd300362ffb44b7b125f299c23d2990411a4253824630ebc7467fb" checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@ -1535,9 +1583,9 @@ dependencies = [
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d15a6b60cdff0cb039d81d3b37f8bc3d7e53dca09069aae3ef2502ca4834fe30" checksum = "c00e500fff5fa1131c866b246041a6bf96da9c965f8fe4128cb1421f23e93c00"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
@ -1687,6 +1735,15 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.7" version = "0.3.7"
@ -1754,12 +1811,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.3"
@ -1930,3 +1981,15 @@ name = "zeroize"
version = "1.4.2" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
[[package]]
name = "zip"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
dependencies = [
"byteorder",
"crc32fast",
"flate2",
"thiserror",
]

View file

@ -5,7 +5,7 @@ use std::io::{Read, Write};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use atty::Stream; use atty::Stream;
use clap::Clap; use clap::Parser;
use omegaupload_common::crypto::{gen_key_nonce, open_in_place, seal_in_place, Key}; use omegaupload_common::crypto::{gen_key_nonce, open_in_place, seal_in_place, Key};
use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url}; use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url};
use reqwest::blocking::Client; use reqwest::blocking::Client;
@ -13,13 +13,13 @@ use reqwest::header::EXPIRES;
use reqwest::StatusCode; use reqwest::StatusCode;
use secrecy::{ExposeSecret, SecretString}; use secrecy::{ExposeSecret, SecretString};
#[derive(Clap)] #[derive(Parser)]
struct Opts { struct Opts {
#[clap(subcommand)] #[clap(subcommand)]
action: Action, action: Action,
} }
#[derive(Clap)] #[derive(Parser)]
enum Action { enum Action {
Upload { Upload {
url: Url, url: Url,

View file

@ -19,8 +19,10 @@ http = "0.2"
js-sys = "0.3" js-sys = "0.3"
reqwasm = "0.2" reqwasm = "0.2"
tree_magic_mini = { version = "3", features = ["with-gpl-data"] } tree_magic_mini = { version = "3", features = ["with-gpl-data"] }
wasm-bindgen = "0.2" serde = { version = "1.0", featurse = ["derive"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"]}
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
zip = { version = "0.5", default-features = false, features = ["deflate"] }
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3" version = "0.3"
@ -40,4 +42,4 @@ features = [
"Window", "Window",
"Performance", "Performance",
"Location", "Location",
] ]

View file

@ -1,4 +1,5 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::io::Cursor;
use std::sync::Arc; use std::sync::Arc;
use gloo_console::log; use gloo_console::log;
@ -6,6 +7,13 @@ use js_sys::{Array, Uint8Array};
use omegaupload_common::crypto::{open_in_place, Key, Nonce}; use omegaupload_common::crypto::{open_in_place, Key, Nonce};
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::Blob; use web_sys::Blob;
use serde::Serialize;
#[derive(Clone, Serialize)]
pub struct ArchiveMeta {
name: String,
file_size: usize,
}
#[derive(Clone)] #[derive(Clone)]
pub enum DecryptedData { pub enum DecryptedData {
@ -14,6 +22,7 @@ pub enum DecryptedData {
Image(Arc<Blob>, usize), Image(Arc<Blob>, usize),
Audio(Arc<Blob>), Audio(Arc<Blob>),
Video(Arc<Blob>), Video(Arc<Blob>),
Archive(Arc<Blob>, Vec<ArchiveMeta>),
} }
fn now() -> f64 { fn now() -> f64 {
@ -70,6 +79,7 @@ pub fn decrypt(
log!(format!("Blob conversion completed in {}ms", now() - start)); log!(format!("Blob conversion completed in {}ms", now() - start));
let mime_type = tree_magic_mini::from_u8(container); let mime_type = tree_magic_mini::from_u8(container);
log!("Mimetype: ", mime_type);
if mime_type.starts_with("image/") || mime_type == "application/x-riff" { if mime_type.starts_with("image/") || mime_type == "application/x-riff" {
Ok(DecryptedData::Image(blob, container.len())) Ok(DecryptedData::Image(blob, container.len()))
@ -77,6 +87,28 @@ pub fn decrypt(
Ok(DecryptedData::Audio(blob)) Ok(DecryptedData::Audio(blob))
} else if mime_type.starts_with("video/") || mime_type == "application/x-matroska" { } else if mime_type.starts_with("video/") || mime_type == "application/x-matroska" {
Ok(DecryptedData::Video(blob)) Ok(DecryptedData::Video(blob))
} else if mime_type == "application/zip" {
let mut entries = vec![];
let cursor = Cursor::new(container);
if let Ok(mut zip) = zip::ZipArchive::new(cursor) {
for i in 0..zip.len() {
match zip.by_index(i) {
Ok(file) => {
entries.push(ArchiveMeta{name: file.name().to_string(), file_size: file.size() as usize})
},
Err(err) => {
match err {
zip::result::ZipError::UnsupportedArchive(s) => { log!("Unsupported: ", s.to_string()); }
_ => { log!(format!("Error: {}", err)); }
}
}
}
}
}
Ok(DecryptedData::Archive(blob, entries))
} else if mime_type == "application/gzip" {
let entries = vec![];
Ok(DecryptedData::Archive(blob, entries))
} else { } else {
Ok(DecryptedData::Blob(blob)) Ok(DecryptedData::Blob(blob))
} }

View file

@ -31,6 +31,10 @@ impl IdbObject<NeedsType> {
Self(Array::new(), PhantomData) Self(Array::new(), PhantomData)
} }
pub fn archive(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("archive"))
}
pub fn video(self) -> IdbObject<NeedsExpiration> { pub fn video(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("video")) self.add_tuple("type", &JsString::from("video"))
} }

View file

@ -8,7 +8,7 @@ use decrypt::DecryptedData;
use gloo_console::{error, log}; use gloo_console::{error, log};
use http::uri::PathAndQuery; use http::uri::PathAndQuery;
use http::{StatusCode, Uri}; use http::{StatusCode, Uri};
use js_sys::{JsString, Object, Uint8Array}; use js_sys::{JsString, Object, Uint8Array, Array};
use omegaupload_common::crypto::{Key, Nonce}; use omegaupload_common::crypto::{Key, Nonce};
use omegaupload_common::{hash, Expiration, PartialParsedUrl}; use omegaupload_common::{hash, Expiration, PartialParsedUrl};
use reqwasm::http::Request; use reqwasm::http::Request;
@ -191,6 +191,16 @@ async fn fetch_resources(
.video() .video()
.expiration_text(&expires) .expiration_text(&expires)
.data(blob), .data(blob),
DecryptedData::Archive(blob, entries) => IdbObject::new()
.archive()
.expiration_text(&expires)
.data(blob)
.extra(
"entries",
JsValue::from(entries.into_iter()
.filter_map(|x| JsValue::from_serde(x).ok())
.collect::<Array>())
),
}; };
let put_action = transaction let put_action = transaction

View file

@ -25,6 +25,9 @@ function loadFromDb() {
case "video": case "video":
createVideoPasteUi(data); createVideoPasteUi(data);
break; break;
case "archive":
createArchivePasteUi(data);
break;
default: default:
renderMessage("Something went wrong. Try clearing local data."); renderMessage("Something went wrong. Try clearing local data.");
break; break;
@ -133,6 +136,48 @@ function createVideoPasteUi({ expiration, data }) {
createMultiMediaPasteUi("video", expiration, data, "Download"); createMultiMediaPasteUi("video", expiration, data, "Download");
} }
function createArchivePasteUi({ expiration, data, entries }) {
let bodyEle = document.getElementsByTagName("body")[0];
bodyEle.textContent = '';
let mainEle = document.createElement("main");
mainEle.classList.add("hljs");
mainEle.classList.add("centered");
mainEle.classList.add("fullscreen");
const downloadLink = URL.createObjectURL(data);
let expirationEle = document.createElement("p");
expirationEle.textContent = expiration;
mainEle.appendChild(expirationEle);
let mediaEle = document.createElement("table");
mediaEle.style.width = "50%";
const tr = mediaEle.insertRow();
const tdName = tr.insertCell();
tdName.appendChild(document.createTextNode("Name"));
const tdSize = tr.insertCell();
tdSize.appendChild(document.createTextNode("File Size"));
for (const entry of entries) {
const tr = mediaEle.insertRow();
const tdName = tr.insertCell();
tdName.appendChild(document.createTextNode(entry.name));
const tdSize = tr.insertCell();
tdSize.appendChild(document.createTextNode(entry.file_size));
}
mainEle.appendChild(mediaEle);
let downloadEle = document.createElement("a");
downloadEle.href = downloadLink;
downloadEle.download = window.location.pathname;
downloadEle.classList.add("hljs-meta");
mainEle.appendChild(downloadEle);
bodyEle.appendChild(mainEle);
downloadEle.textContent = "Download";
}
function createMultiMediaPasteUi(tag, expiration, data, on_create?) { function createMultiMediaPasteUi(tag, expiration, data, on_create?) {
let bodyEle = document.getElementsByTagName("body")[0]; let bodyEle = document.getElementsByTagName("body")[0];
bodyEle.textContent = ''; bodyEle.textContent = '';
@ -179,4 +224,4 @@ function renderMessage(message) {
body.appendChild(mainEle); body.appendChild(mainEle);
} }
window.addEventListener("hashchange", () => location.reload()); window.addEventListener("hashchange", () => location.reload());