use typestate

webpack
Edward Shen 2021-10-24 13:12:14 -07:00
parent 23b90ebfe4
commit a509ff08b4
Signed by: edward
GPG Key ID: 19182661E818369F
3 changed files with 130 additions and 113 deletions

1
Cargo.lock generated
View File

@ -1082,6 +1082,7 @@ dependencies = [
name = "omegaupload-web"
version = "0.1.0"
dependencies = [
"anyhow",
"byte-unit",
"bytes",
"getrandom",

View File

@ -10,6 +10,7 @@ omegaupload-common = { path = "../common" }
# Enables wasm support
getrandom = { version = "*", features = ["js"] }
anyhow = "1"
bytes = "1"
byte-unit = "4"
gloo-console = "0.1"

View File

@ -1,7 +1,9 @@
#![warn(clippy::nursery, clippy::pedantic)]
use std::marker::PhantomData;
use std::str::FromStr;
use anyhow::{anyhow, Result};
use byte_unit::Byte;
use decrypt::DecryptedData;
use gloo_console::log;
@ -39,12 +41,14 @@ fn main() {
if window.location().pathname().unwrap() == "/" {
} else {
spawn_local(a(request_uri, url));
spawn_local(async {
a(request_uri, url).await;
});
}
}
#[allow(clippy::future_not_send)]
async fn a(request_uri: Uri, url: String) {
async fn a(request_uri: Uri, url: String) -> Result<()> {
match Request::get(&request_uri.to_string()).send().await {
Ok(resp) if resp.status() == StatusCode::OK => {
let expires = resp
@ -61,20 +65,24 @@ async fn a(request_uri: Uri, url: String) {
);
let data = {
Uint8Array::new(
&JsFuture::from(resp.as_raw().array_buffer().unwrap())
.await
.unwrap(),
)
.to_vec()
let data_fut = resp
.as_raw()
.array_buffer()
.expect("Failed to get raw bytes from response");
let data = JsFuture::from(data_fut)
.await
.expect("Failed to result array buffer future");
Uint8Array::new(&data).to_vec()
};
let info = url
.split_once('#')
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
.unwrap_or_default();
let key = info.decryption_key.unwrap();
let nonce = info.nonce.unwrap();
let key = info
.decryption_key
.expect("missing key should be handled in the future");
let nonce = info.nonce.expect("missing nonce be handled in the future");
let result = decrypt(data, key, nonce, None);
@ -105,111 +113,40 @@ async fn a(request_uri: Uri, url: String) {
.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);
}
let decrypted_object = match &decrypted {
DecryptedData::String(s) => IdbObject::new()
.string()
.expiration_text(&expires)
.data(&JsValue::from_str(s)),
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);
IdbObject::new().blob().expiration_text(&expires).data(blob)
}
DecryptedData::Image(blob, (width, height), size) => {
let entry = Array::new();
entry.push(&JsString::from("data"));
entry.push(blob);
decrypted_object.push(&entry);
DecryptedData::Image(blob, (width, height), size) => IdbObject::new()
.image()
.expiration_text(&expires)
.data(blob)
.extra("width", *width)
.extra("height", *height)
.extra(
"button",
&format!(
"Download {} \u{2014} {} by {}",
Byte::from_bytes(*size as u128).get_appropriate_unit(true),
width,
height,
),
),
DecryptedData::Audio(blob) => IdbObject::new()
.audio()
.expiration_text(&expires)
.data(blob),
DecryptedData::Video(blob) => IdbObject::new()
.video()
.expiration_text(&expires)
.data(blob),
};
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);
}
DecryptedData::Audio(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("audio"));
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::Video(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("video"));
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();
let db_entry = Object::from_entries(decrypted_object.as_ref()).unwrap();
transaction
.put_with_key(
&db_entry,
@ -244,5 +181,83 @@ async fn a(request_uri: Uri, url: String) {
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {}
Ok(err) => {}
Err(err) => {}
};
}
Ok(())
}
struct IdbObject<State>(Array, PhantomData<State>);
impl<State: IdbObjectState> IdbObject<State> {
fn add_tuple<NextState>(self, key: &str, value: &JsValue) -> IdbObject<NextState> {
let array = Array::new();
array.push(&JsString::from(key));
array.push(value);
self.0.push(&array);
IdbObject(self.0, PhantomData)
}
}
impl IdbObject<NeedsType> {
fn new() -> Self {
Self(Array::new(), PhantomData)
}
fn video(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("video"))
}
fn audio(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("audio"))
}
fn image(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("image"))
}
fn blob(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("blob"))
}
fn string(self) -> IdbObject<NeedsExpiration> {
self.add_tuple("type", &JsString::from("string"))
}
}
impl IdbObject<NeedsExpiration> {
fn expiration_text(self, expires: &str) -> IdbObject<NeedsData> {
self.add_tuple("expiration", &JsString::from(expires))
}
}
impl IdbObject<NeedsData> {
fn data(self, value: &JsValue) -> IdbObject<Ready> {
self.add_tuple("data", value)
}
}
impl IdbObject<Ready> {
fn extra(self, key: &str, value: impl Into<JsValue>) -> Self {
self.add_tuple(key, &value.into())
}
}
impl AsRef<JsValue> for IdbObject<Ready> {
fn as_ref(&self) -> &JsValue {
self.0.as_ref()
}
}
trait IdbObjectState {}
enum NeedsType {}
impl IdbObjectState for NeedsType {}
enum NeedsExpiration {}
impl IdbObjectState for NeedsExpiration {}
enum NeedsData {}
impl IdbObjectState for NeedsData {}
enum Ready {}
impl IdbObjectState for Ready {}