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! {
-
-
-
+ <>
+
+
+ >
},
}
}
-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! {
-
- };
- }
-
- 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! {
-
-
- { "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! {
-
- }
- } 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"