diff --git a/.gitignore b/.gitignore index b14351f..1cdef6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/database /web/dist/ +**/node_modules \ No newline at end of file diff --git a/.swcrc b/.swcrc new file mode 100644 index 0000000..857062a --- /dev/null +++ b/.swcrc @@ -0,0 +1,8 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript" + }, + "target": "es2021" + } +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c00fffc..0553ed9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,12 +435,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - [[package]] name = "either" version = "1.6.1" @@ -1135,7 +1129,6 @@ dependencies = [ name = "omegaupload-common" version = "0.1.0" dependencies = [ - "anyhow", "base64", "bytes", "chacha20poly1305", @@ -1172,10 +1165,8 @@ dependencies = [ name = "omegaupload-web" version = "0.1.0" dependencies = [ - "anyhow", "byte-unit", "bytes", - "downcast-rs", "getrandom", "gloo-console", "http", diff --git a/Trunk.toml b/Trunk.toml index 28a1ae7..2268626 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -6,3 +6,8 @@ release = true [[proxy]] backend = "http://localhost:8081" rewrite = "/api/" + +[[hooks]] +stage="post_build" +command="npx" +command_arguments=["swc", "$TRUNK_SOURCE_DIR/src/main.ts", "-o", "$TRUNK_STAGING_DIR/main.js"] \ No newline at end of file diff --git a/common/Cargo.toml b/common/Cargo.toml index 046fff4..11ef53a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1" base64 = "0.13" bytes = { version = "*", features = ["serde"] } chacha20poly1305 = "0.9" diff --git a/common/src/lib.rs b/common/src/lib.rs index a18c67e..aef345b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -153,13 +153,13 @@ impl From<&str> for PartialParsedUrl { for (key, value) in args { match (key, value) { ("key", Some(value)) => { - decryption_key = base64::decode(value).map(|k| *Key::from_slice(&k)).ok(); + decryption_key = dbg!(base64::decode(value).map(|k| *Key::from_slice(&k)).ok()); } ("pw", _) => { needs_password = true; } ("nonce", Some(value)) => { - nonce = base64::decode(value).as_deref().map(Nonce::from_slice).ok(); + nonce = dbg!(base64::decode(value).as_deref().map(Nonce::from_slice).ok()); } _ => (), } @@ -229,12 +229,12 @@ impl Display for Expiration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Expiration::BurnAfterReading => { - write!(f, "This paste has been burned. You now have the only copy.") + write!(f, "This item has been burned. You now have the only copy.") } Expiration::UnixTime(time) => write!( f, "{}", - time.format("This paste will expire on %A, %B %-d, %Y at %T %Z.") + time.format("This item will expire on %A, %B %-d, %Y at %T %Z.") ), } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd36b53 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@swc/cli": "^0.1.51", + "@swc/core": "^1.2.102" + } +} diff --git a/web/Cargo.toml b/web/Cargo.toml index e54f1b9..41b7ca5 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -10,19 +10,32 @@ omegaupload-common = { path = "../common" } # Enables wasm support getrandom = { version = "*", features = ["js"] } -anyhow = "1" bytes = "1" byte-unit = "4" -downcast-rs = "1" gloo-console = "0.1" http = "0.2" image = "0.23" js-sys = "0.3" -# reqwest = { version = "0.11", default_features = false, features = ["tokio-rustls"] } reqwasm = "0.2" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" -web-sys = { version = "0.3", features = ["TextDecoder"] } yew = { version = "0.18", features = ["wasm-bindgen-futures"] } yew-router = "0.15" -yewtil = "0.4" \ No newline at end of file +yewtil = "0.4" + +[dependencies.web-sys] +version = "0.3" +features = [ + "TextDecoder", + "IdbFactory", + "IdbOpenDbRequest", + "IdbRequest", + "IdbDatabase", + "IdbObjectStore", + "IdbTransaction", + "IdbTransactionMode", + "IdbIndex", + "IdbIndexParameters", + "Event", + "EventTarget" +] \ No newline at end of file diff --git a/web/index.html b/web/index.html index 7d80d50..7c85220 100644 --- a/web/index.html +++ b/web/index.html @@ -5,15 +5,13 @@ Omegaupload - - - + diff --git a/web/src/decrypt.rs b/web/src/decrypt.rs index a4335d8..179abdb 100644 --- a/web/src/decrypt.rs +++ b/web/src/decrypt.rs @@ -1,99 +1,16 @@ -use std::{collections::HashSet, sync::Arc}; +use std::fmt::{Display, Formatter}; +use std::sync::Arc; use gloo_console::log; use image::GenericImageView; use js_sys::{Array, Uint8Array}; -use omegaupload_common::{ - crypto::{open_in_place, Key, Nonce}, - Expiration, -}; +use omegaupload_common::crypto::{open_in_place, Key, Nonce}; use wasm_bindgen::JsCast; use web_sys::Blob; -use yew::worker::{Agent, AgentLink, Context, HandlerId}; -use yew::{html::Scope, worker::Public}; -use crate::{DecryptedData, Paste, PasteCompleteConstructionError}; +use crate::DecryptedData; -#[derive(Clone)] -pub struct DecryptionAgent { - link: AgentLink, -} - -impl Agent for DecryptionAgent { - type Reach = Public; - - type Message = (); - - type Input = DecryptionAgentMessage; - - type Output = Result<(DecryptedData, PasteContext), PasteCompleteConstructionError>; - - fn create(link: AgentLink) -> Self { - Self { link } - } - - fn update(&mut self, _: Self::Message) {} - - fn handle_input( - &mut self, - DecryptionAgentMessage { context, params }: Self::Input, - id: HandlerId, - ) { - let DecryptionParams { - data, - key, - nonce, - maybe_password, - } = params; - - self.link.respond( - id, - decrypt(data, key, nonce, maybe_password).map(|res| (res, context)), - ) - } -} - -pub struct DecryptionAgentMessage { - context: PasteContext, - params: DecryptionParams, -} - -impl DecryptionAgentMessage { - pub fn new(context: PasteContext, params: DecryptionParams) -> Self { - Self { context, params } - } -} - -pub struct PasteContext { - pub link: Scope, - pub expires: Option, -} - -impl PasteContext { - pub fn new(link: Scope, expires: Option) -> Self { - Self { link, expires } - } -} - -pub struct DecryptionParams { - data: Vec, - key: Key, - nonce: Nonce, - maybe_password: Option, -} - -impl DecryptionParams { - pub fn new(data: Vec, key: Key, nonce: Nonce, maybe_password: Option) -> Self { - Self { - data, - key, - nonce, - maybe_password, - } - } -} - -fn decrypt( +pub fn decrypt( mut container: Vec, key: Key, nonce: Nonce, @@ -136,3 +53,24 @@ fn decrypt( } } } + +#[derive(Debug)] +pub enum PasteCompleteConstructionError { + StageOneFailure, + StageTwoFailure, +} + +impl std::error::Error for PasteCompleteConstructionError {} + +impl Display for PasteCompleteConstructionError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PasteCompleteConstructionError::StageOneFailure => { + write!(f, "Failed to decrypt stage one.") + } + PasteCompleteConstructionError::StageTwoFailure => { + write!(f, "Failed to decrypt stage two.") + } + } + } +} diff --git a/web/src/main.rs b/web/src/main.rs index a60de24..5b7516a 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -1,37 +1,28 @@ #![warn(clippy::nursery, clippy::pedantic)] -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; use std::str::FromStr; use std::sync::Arc; -use anyhow::{anyhow, bail, Context}; use byte_unit::Byte; -use bytes::Bytes; -use decrypt::DecryptionAgent; -use downcast_rs::{impl_downcast, Downcast}; use gloo_console::log; use http::header::EXPIRES; use http::uri::PathAndQuery; use http::{StatusCode, Uri}; -use image::GenericImageView; -use js_sys::{Array, ArrayBuffer, Uint8Array}; -use omegaupload_common::crypto::{open, open_in_place, Key, Nonce}; +use js_sys::{Array, JsString, Object, Uint8Array}; use omegaupload_common::{Expiration, PartialParsedUrl}; use reqwasm::http::Request; -use wasm_bindgen::JsCast; +use wasm_bindgen::prelude::{wasm_bindgen, Closure}; +use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; -use web_sys::TextDecoder; -use web_sys::{Blob, Url}; -use yew::agent::Dispatcher; +use web_sys::{Blob, Event, IdbDatabase, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode}; use yew::utils::window; -use yew::worker::Agent; -use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, ShouldRender}; -use yew::{Dispatched, Properties}; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; use yew_router::router::Router; use yew_router::Switch; use yewtil::future::LinkFuture; -use crate::decrypt::{DecryptionAgentMessage, DecryptionParams, PasteContext}; +use crate::decrypt::decrypt; mod decrypt; @@ -39,6 +30,12 @@ fn main() { yew::start_app::(); } +#[wasm_bindgen] +extern "C" { + fn loadFromDb(); + fn createNotFoundUi(); +} + struct App; impl Component for App { type Message = (); @@ -80,21 +77,20 @@ fn render_route(route: Route) -> Html { }, Route::Path(_) => html! { -
- -
+ <> + +
+

{"Loading"}

+
+ }, } } -pub struct Paste { - state: Box, - _listener: Box>, -} +pub struct Paste; impl Component for Paste { - type Message = Box; - + type Message = (); type Properties = (); fn create(_: Self::Properties, link: ComponentLink) -> Self { @@ -107,20 +103,6 @@ impl Component for Paste { Uri::from_parts(uri_parts).unwrap() }; - let handle_decryption_result = |res: ::Output| { - log!("Got decryption result back!"); - match res { - Ok((decrypted, context)) => { - Box::new(PasteComplete::new(context.link, decrypted, context.expires)) - as Box - } - Err(e) => Box::new(PasteError(anyhow!("wtf"))) as Box, - } - }; - - let listener = DecryptionAgent::bridge(link.callback(handle_decryption_result)); - - let link_clone = link.clone(); link.send_future(async move { match Request::get(&request_uri.to_string()).send().await { Ok(resp) if resp.status() == StatusCode::OK => { @@ -130,7 +112,10 @@ impl Component for Paste { .ok() .flatten() .as_deref() - .and_then(|v| Expiration::try_from(v).ok()); + .and_then(|v| Expiration::try_from(v).ok()) + .as_ref() + .map(Expiration::to_string) + .unwrap_or_else(|| "This item does not expire.".to_string()); let data = { Uint8Array::new( @@ -148,344 +133,169 @@ impl Component for Paste { let key = info.decryption_key.unwrap(); let nonce = info.nonce.unwrap(); - let mut decryption_agent = DecryptionAgent::dispatcher(); + let result = decrypt(data, key, nonce, None); - let params = DecryptionParams::new(data, key, nonce, None); - let ctx = PasteContext::new(link_clone, expires); - decryption_agent.send(DecryptionAgentMessage::new(ctx, params)); - Box::new(PasteDecrypting(decryption_agent)) as Box + let decrypted = match result { + Ok(decrypted) => decrypted, + Err(err) => { + // log!("decryption error: {}", err); + // return Box::new(PasteError(err)); + unimplemented!() + } + }; + + let db_open_req = window() + .indexed_db() + .unwrap() + .unwrap() + .open("omegaupload") + .unwrap(); + + // On success callback + let on_success = Closure::once(Box::new(move |event: Event| { + let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap(); + let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap(); + let transaction: IdbObjectStore = db + .transaction_with_str_and_mode( + "decrypted data", + IdbTransactionMode::Readwrite, + ) + .unwrap() + .object_store("decrypted data") + .unwrap(); + + let decrypted_object = Array::new(); + match &decrypted { + DecryptedData::String(s) => { + let entry = Array::new(); + entry.push(&JsString::from("data")); + entry.push(&JsValue::from_str(&s)); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("type")); + entry.push(&JsString::from("string")); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("expiration")); + entry.push(&JsString::from(expires.to_string())); + decrypted_object.push(&entry); + } + DecryptedData::Blob(blob) => { + let entry = Array::new(); + entry.push(&JsString::from("data")); + entry.push(blob); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("type")); + entry.push(&JsString::from("blob")); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("expiration")); + entry.push(&JsString::from(expires.to_string())); + decrypted_object.push(&entry); + } + DecryptedData::Image(blob, (width, height), size) => { + let entry = Array::new(); + entry.push(&JsString::from("data")); + entry.push(blob); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("type")); + entry.push(&JsString::from("image")); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("width")); + entry.push(&JsValue::from(*width)); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("height")); + entry.push(&JsValue::from(*height)); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("button")); + entry.push(&JsString::from(format!( + "Download {} \u{2014} {} by {}", + Byte::from_bytes(*size as u128).get_appropriate_unit(true), + width, + height, + ))); + decrypted_object.push(&entry); + + let entry = Array::new(); + entry.push(&JsString::from("expiration")); + entry.push(&JsString::from(expires.to_string())); + decrypted_object.push(&entry); + } + } + + let db_entry = Object::from_entries(&decrypted_object).unwrap(); + transaction + .put_with_key( + &db_entry, + &JsString::from(window().location().pathname().unwrap()), + ) + .unwrap() + .set_onsuccess(Some( + Closure::once(Box::new(|| { + log!("success"); + loadFromDb(); + }) + as Box) + .into_js_value() + .dyn_ref() + .unwrap(), + )); + }) + as Box); + + db_open_req.set_onsuccess(Some(on_success.into_js_value().dyn_ref().unwrap())); + + // On upgrade callback + let on_upgrade = Closure::wrap(Box::new(move |event: Event| { + let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap(); + let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap(); + let _obj_store = db.create_object_store("decrypted data").unwrap(); + }) as Box); + + db_open_req + .set_onupgradeneeded(Some(on_upgrade.into_js_value().dyn_ref().unwrap())); } Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { - Box::new(PasteNotFound) as Box + createNotFoundUi(); } - Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => { - Box::new(PasteBadRequest) as Box - } - Ok(err) => { - Box::new(PasteError(anyhow!("Got {}.", err.status()))) as Box - } - Err(err) => Box::new(PasteError(anyhow!("Got {}.", err))) as Box, - } + Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {} + Ok(err) => {} + Err(err) => {} + }; }); - Self { - state: Box::new(PasteLoading), - _listener: listener, - } + Self } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - self.state = msg; - true + fn update(&mut self, _: Self::Message) -> ShouldRender { + false } - fn change(&mut self, _props: Self::Properties) -> ShouldRender { + fn change(&mut self, _: Self::Properties) -> ShouldRender { false } fn view(&self) -> Html { - if self.state.is::() { - return html! { -

{ "loading" }

- }; - } - - if self.state.is::() { - return html! { - "decrypting" - }; - } - - if self.state.is::() { - return html! { -
-

{ "Either the paste has been burned or one never existed." }

-
- }; - } - - if self.state.is::() { - return html! { -
-

{ "Bad Request. Is this a valid paste URL?" }

-
- }; - } - - if let Some(error) = self.state.downcast_ref::() { - return html! { -

{ error.0.to_string() }

- }; - } - - if let Some(partial_paste) = self.state.downcast_ref::() { - return partial_paste.view(); - } - - if let Some(paste) = self.state.downcast_ref::() { - return paste.view(); - } - - html! { - "An internal error occurred: client is in unknown state!" - } + html! {} } } -struct PasteError(anyhow::Error); - -#[derive(Debug)] -struct PastePartial { - parent: ComponentLink, - dispatcher: Dispatcher, - data: Bytes, - expires: Option, - key: Option, - nonce: Option, - password: Option, - needs_pw: bool, -} - -#[derive(Properties, Clone)] -struct PasteComplete { - parent: ComponentLink, - decrypted: DecryptedData, - expires: Option, -} - #[derive(Clone)] pub enum DecryptedData { String(Arc), Blob(Arc), Image(Arc, (u32, u32), usize), } - -pub trait PasteState: Downcast {} -impl_downcast!(PasteState); - -impl PasteState for PasteError {} -impl PasteState for PastePartial {} -impl PasteState for PasteComplete {} - -macro_rules! impl_paste_type_state { - ( - $($state:ident),* $(,)? - ) => { - $( - struct $state; - impl PasteState for $state {} - )* - }; -} - -impl_paste_type_state!(PasteLoading, PasteNotFound, PasteBadRequest); - -struct PasteDecrypting(Dispatcher); - -impl PasteState for PasteDecrypting {} - -impl PastePartial { - fn new( - data: Bytes, - expires: Option, - partial_parsed_url: &PartialParsedUrl, - parent: ComponentLink, - ) -> Self { - Self { - parent, - dispatcher: DecryptionAgent::dispatcher(), - data, - expires, - key: partial_parsed_url.decryption_key, - nonce: partial_parsed_url.nonce, - password: None, - needs_pw: partial_parsed_url.needs_password, - } - } -} - -enum PartialPasteMessage { - DecryptionKey(Key), - Nonce(Nonce), - Password(Key), -} - -impl Component for PastePartial { - type Message = PartialPasteMessage; - - type Properties = (); - - fn create(_: Self::Properties, _: ComponentLink) -> Self { - unimplemented!() - } - - fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - PartialPasteMessage::DecryptionKey(key) => self.key = Some(key), - PartialPasteMessage::Nonce(nonce) => self.nonce = Some(nonce), - PartialPasteMessage::Password(password) => self.password = Some(password), - } - - match (self.key, self.nonce, self.password) { - (Some(key), Some(nonce), maybe_password) - if (self.needs_pw && maybe_password.is_some()) - || (!self.needs_pw && maybe_password.is_none()) => - { - let parent = self.parent.clone(); - let mut data = self.data.to_vec(); - let expires = self.expires; - - // self.dispatcher.send((data, key, nonce, maybe_password)); - todo!() - } - _ => (), - } - - // parent should re-render so this element should be dropped; no point - // in saying this needs to be re-rendered. - false - } - - fn change(&mut self, _props: Self::Properties) -> ShouldRender { - false - } - - fn view(&self) -> Html { - html! { - "got partial data" - } - } -} - -#[derive(Debug)] -pub enum PasteCompleteConstructionError { - StageOneFailure, - StageTwoFailure, -} - -impl std::error::Error for PasteCompleteConstructionError {} - -impl Display for PasteCompleteConstructionError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - PasteCompleteConstructionError::StageOneFailure => { - write!(f, "Failed to decrypt stage one.") - } - PasteCompleteConstructionError::StageTwoFailure => { - write!(f, "Failed to decrypt stage two.") - } - } - } -} - -impl PasteComplete { - fn new( - parent: ComponentLink, - decrypted: DecryptedData, - expires: Option, - ) -> Self { - Self { - parent, - decrypted, - expires, - } - } - - fn view(&self) -> Html { - match &self.decrypted { - DecryptedData::String(decrypted) => html! { - html! { - <> -
-                                
- { - self.expires.as_ref().map(ToString::to_string).unwrap_or_else(|| - "This paste will not expire.".to_string() - ) - } -
-
- {decrypted} -
- - - - } - }, - DecryptedData::Blob(decrypted) => { - let object_url = Url::create_object_url_with_blob(decrypted); - if let Ok(object_url) = object_url { - let file_name = window().location().pathname().unwrap_or("file".to_string()); - let mut cloned = self.clone(); - let decrypted_ref = Arc::clone(&decrypted); - let display_anyways_callback = - self.parent.callback_future_once(|_| async move { - let array_buffer: ArrayBuffer = - JsFuture::from(decrypted_ref.array_buffer()) - .await - .unwrap() - .dyn_into() - .unwrap(); - let decoder = TextDecoder::new().unwrap(); - cloned.decrypted = decoder - .decode_with_buffer_source(&array_buffer) - .map(Arc::new) - .map(DecryptedData::String) - .unwrap(); - Box::new(cloned) as Box - }); - html! { -
-
-

{ "Found a binary file." }

- {"Download"} -
-

{ "Display anyways?" }

-
- } - } else { - // This branch really shouldn't happen, but might as well - // try and give a user-friendly error message. - html! { -
-

{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }

-
- } - } - } - DecryptedData::Image(decrypted, (width, height), size) => { - let object_url = Url::create_object_url_with_blob(decrypted); - if let Ok(object_url) = object_url { - let file_name = window().location().pathname().unwrap_or("file".to_string()); - html! { -
- - - { - format!( - "Download {} \u{2014} {} by {}", - Byte::from_bytes(*size as u128).get_appropriate_unit(true), - width, height, - ) - } - -
- } - } else { - // This branch really shouldn't happen, but might as well - // try and give a user-friendly error message. - html! { -
-

{ "Failed to create an object URL for the decrypted file. Try reloading the page?" }

-
- } - } - } - } - } -} diff --git a/web/src/main.scss b/web/src/main.scss index 1e35126..d1dacf7 100644 --- a/web/src/main.scss +++ b/web/src/main.scss @@ -59,8 +59,9 @@ main { } .display-anyways { - margin-bottom: 4em; + margin-top: 4em; text-decoration: underline; + cursor: pointer; } img { @@ -72,4 +73,4 @@ img { .primary { @extend .hljs; -} +} \ No newline at end of file diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..e50dd21 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,154 @@ +// Exported to main.rs +function loadFromDb() { + const dbReq = window.indexedDB.open("omegaupload", 1); + dbReq.onsuccess = (evt) => { + const db = (evt.target as IDBRequest).result; + const obj_store = db + .transaction("decrypted data", "readonly") + .objectStore("decrypted data") + .get(window.location.pathname); + obj_store.onsuccess = (evt) => { + const data = (evt.target as IDBRequest).result; + switch (data.type) { + case "string": + createStringPasteUi(data); + break; + case "blob": + createBlobPasteUi(data); + break; + case "image": + createImagePasteUi(data); + break; + default: + createBrokenStateUi(); + break; + } + }; + + obj_store.onerror = (evt) => { + console.log("err"); + console.log(evt); + }; + }; +} + +function createStringPasteUi(data) { + let bodyEle = document.getElementsByTagName("body")[0]; + bodyEle.textContent = ''; + + let mainEle = document.createElement("main"); + let preEle = document.createElement("pre"); + preEle.classList.add("paste"); + + let headerEle = document.createElement("header"); + headerEle.classList.add("unselectable"); + headerEle.textContent = data.expiration; + preEle.appendChild(headerEle); + + preEle.appendChild(document.createElement("hr")); + + let codeEle = document.createElement("code"); + codeEle.textContent = data.data; + preEle.appendChild(codeEle); + + mainEle.appendChild(preEle); + bodyEle.appendChild(mainEle); + + hljs.highlightAll(); + hljs.initLineNumbersOnLoad(); +} + +function createImagePasteUi(data) { + 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.data); + + let expirationEle = document.createElement("p"); + expirationEle.textContent = data.expiration; + mainEle.appendChild(expirationEle); + + let imgEle = document.createElement("img"); + imgEle.src = downloadLink; + mainEle.appendChild(imgEle); + + + let downloadEle = document.createElement("a"); + downloadEle.href = downloadLink; + downloadEle.download = window.location.pathname; + downloadEle.classList.add("hljs-meta"); + downloadEle.textContent = data.button; + mainEle.appendChild(downloadEle); + + + bodyEle.appendChild(mainEle); +} + +function createBlobPasteUi(data) { + 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"); + + let divEle = document.createElement("div"); + divEle.classList.add("centered"); + + let expirationEle = document.createElement("p"); + expirationEle.textContent = data.expiration; + divEle.appendChild(expirationEle); + + let downloadEle = document.createElement("a"); + downloadEle.href = URL.createObjectURL(data.data); + downloadEle.download = window.location.pathname; + downloadEle.classList.add("hljs-meta"); + downloadEle.textContent = "Download binary file."; + divEle.appendChild(downloadEle); + + + mainEle.appendChild(divEle); + + let displayAnywayEle = document.createElement("p"); + displayAnywayEle.classList.add("display-anyways"); + displayAnywayEle.classList.add("hljs-comment"); + displayAnywayEle.textContent = "Display anyways?"; + displayAnywayEle.onclick = () => { + data.data.text().then(text => { + data.data = text; + createStringPasteUi(data); + }) + }; + mainEle.appendChild(displayAnywayEle); + bodyEle.appendChild(mainEle); +} + +// Exported to main.rs +function createNotFoundUi() { + let body = document.getElementsByTagName("body")[0]; + body.textContent = ''; + body.appendChild(createGenericError("Either the paste has been burned or one never existed.")); +} + +function createBrokenStateUi() { + let body = document.getElementsByTagName("body")[0]; + body.textContent = ''; + body.appendChild(createGenericError("Something went wrong. Try clearing local data.")); +} + +function createGenericError(message) { + let mainEle = document.createElement("main"); + mainEle.classList.add("hljs"); + mainEle.classList.add("centered"); + mainEle.classList.add("fullscreen"); + mainEle.textContent = message; + return mainEle; +} + +window.addEventListener("hashchange", () => location.reload()); \ No newline at end of file diff --git a/web/src/reload_on_hash_change.js b/web/src/reload_on_hash_change.js deleted file mode 100644 index 489dc5d..0000000 --- a/web/src/reload_on_hash_change.js +++ /dev/null @@ -1 +0,0 @@ -window.addEventListener("hashchange", () => location.reload()); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..5e3b184 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,233 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@napi-rs/triples@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" + integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA== + +"@node-rs/helper@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@node-rs/helper/-/helper-1.2.1.tgz#e079b05f21ff4329d82c4e1f71c0290e4ecdc70c" + integrity sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg== + dependencies: + "@napi-rs/triples" "^1.0.3" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@swc/cli@^0.1.51": + version "0.1.51" + resolved "https://registry.yarnpkg.com/@swc/cli/-/cli-0.1.51.tgz#720f2d966446558011085c2a40a65fd9079d06a1" + integrity sha512-7eqZGpkI4QOYfF+9FV4xpT/V/LSRDs5OMJcm4Z46JnPMvv+sxumAFdCe1hHRzHgnzwis9OtjI8Tt3Srf9JudQw== + dependencies: + commander "^7.1.0" + fast-glob "^3.2.5" + slash "3.0.0" + source-map "^0.7.3" + +"@swc/core-android-arm64@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.102.tgz#972a47217053cc759f0bc0308a508279de1247ba" + integrity sha512-03wXXSyzm3I/7E3HihYRwvR/v5Xq8Z6j+oXYAouNoQo0/ODTMH9ATFv30csrK3mRtVEcJUk8VpVvfyh1N4hqkw== + +"@swc/core-darwin-arm64@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.102.tgz#1795a4080d1060ca8ac52f218cc4c64b73a7ef06" + integrity sha512-DlC9+qt6gq6gGbmr9MCuMZmdHD/RyfZlf7YfkbQOlRlxaanUWz0lq0TZDWGI6MIofVOgaTle0FImPXby6dI/RA== + +"@swc/core-darwin-x64@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.102.tgz#f48ab5b8b56798d36acd38838c62eb0be4af3c4b" + integrity sha512-JQhxbDnb8RYZ4m7B1f5J05HlUfmjiniQDnpSrvo5rDhlAZWXxuAKjEJQw8Qas/vqLdmgqrZ9POZmFaMBwIgKwg== + +"@swc/core-linux-arm-gnueabihf@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.102.tgz#a617cea4f5a2ff1aac9f668da12dd7a6a0a6700d" + integrity sha512-XWr6Cm3lBOcSGjTjPDLWHBh+lOSkKFMS2gCpLmIC3StAvtcN0oQY59T4cqDfe7VcBgJcdeo/H4dEnrXvnJyCaw== + +"@swc/core-linux-arm64-gnu@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.102.tgz#0abccb26af51783be141dea8b7af0baa0dcc55b4" + integrity sha512-HVtLVBpyqpSIkXmonW75nDzpdrRtZXwEYLYG+y6Sw/8AQFQ9WntwnR+xoJ8q9o3Bby2DGpWetjR0V8rr1m+lmg== + +"@swc/core-linux-arm64-musl@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.102.tgz#ab079233626a355995b8e25e3666a759edffe1c5" + integrity sha512-XDgnkd90alnkBB+JcXaYIG5lXrv/ppLb9Z5fZ4BIsi8uNsVZgo+H/eAj/BTcYff4mpyGdCdqd7P1lC/WRR8uEg== + +"@swc/core-linux-x64-gnu@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.102.tgz#6336698ffae22a491e782cb9ab7478130ceb2240" + integrity sha512-O3XZpJ0GMghNcO5uxfhAvDTJ4FgDOcq8DBPpa4f4Mz7hU6fcGY4Koy4rUeff4BuOKlIzI/O+REszxk7Fiivh2w== + +"@swc/core-linux-x64-musl@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.102.tgz#b9f557dd53647318ac6dd832f174efe8e7386e1a" + integrity sha512-bGqatsVX3yc56YoOLGcHMUG23I2PKMe638vCBfuKVWN6UKcGJGMzqZV/efyPiHJDFcWzN/1jYW3GccGQq97G3Q== + +"@swc/core-win32-arm64-msvc@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.102.tgz#e7c7958f0c2adb1e83a375e0d8d6af673e53150d" + integrity sha512-A15tUAEDS72a2ixNQl3mKCgMD6RVzntMdWl9pDG71/xRd/U/NVIKx0x12FT5fUQH8PTJ7cgcp2Y0VqKeeEuF5w== + +"@swc/core-win32-ia32-msvc@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.102.tgz#f14c93ac0c8b2ec03a8d5438b86551d962da4b0c" + integrity sha512-uePvr9+C1Z0KVElU/Y6ZyXw7vqzRIxl+KSYfn9mCFpgYy/1BRSmxpxqTzR0rkfClXMBi2W9a0JosmWbUvRC8ZQ== + +"@swc/core-win32-x64-msvc@1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.102.tgz#e3cbbb58d99174b7b7d9c1c58467491d31dca269" + integrity sha512-VsUducGCqKm0ucFrZiKQZ95Y4EcCSivg/zYBdTXM20eu/7mG9ynBXHCoKW0B+69D6J3IZsrc9Hvcu7gKkT9QfQ== + +"@swc/core@^1.2.102": + version "1.2.102" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.102.tgz#c71fd22941fa9b7b3c52c778d494ab57beef2c17" + integrity sha512-KQEsDWb8HTSQ/I8N6qVHNSl6al/qcXxsRAjpEP8ZPnnpgThbnP9MeKpT2KeU+Qd4VD0qXM72dMm2QOPzdSlxHQ== + dependencies: + "@node-rs/helper" "^1.0.0" + optionalDependencies: + "@swc/core-android-arm64" "1.2.102" + "@swc/core-darwin-arm64" "1.2.102" + "@swc/core-darwin-x64" "1.2.102" + "@swc/core-linux-arm-gnueabihf" "1.2.102" + "@swc/core-linux-arm64-gnu" "1.2.102" + "@swc/core-linux-arm64-musl" "1.2.102" + "@swc/core-linux-x64-gnu" "1.2.102" + "@swc/core-linux-x64-musl" "1.2.102" + "@swc/core-win32-arm64-msvc" "1.2.102" + "@swc/core-win32-ia32-msvc" "1.2.102" + "@swc/core-win32-x64-msvc" "1.2.102" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +commander@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +fast-glob@^3.2.5: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +slash@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0"