Fix closure panics
This commit is contained in:
parent
a509ff08b4
commit
beda106f6a
7 changed files with 219 additions and 152 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1051,12 +1051,14 @@ dependencies = [
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"chrono",
|
"chrono",
|
||||||
"headers",
|
"headers",
|
||||||
|
"http",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -17,3 +17,9 @@ serde = { version = "1", features = ["derive"] }
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
url = "2"
|
url = "2"
|
||||||
|
|
||||||
|
web-sys = { version = "0.3", features = ["Headers"], optional = true }
|
||||||
|
http = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
wasm = ["web-sys", "http"]
|
|
@ -293,6 +293,21 @@ impl From<Expiration> for HeaderValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
impl TryFrom<web_sys::Headers> for Expiration {
|
||||||
|
type Error = ParseHeaderValueError;
|
||||||
|
|
||||||
|
fn try_from(headers: web_sys::Headers) -> Result<Self, Self::Error> {
|
||||||
|
headers
|
||||||
|
.get(http::header::EXPIRES.as_str())
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|v| Expiration::try_from(v).ok())
|
||||||
|
.ok_or(ParseHeaderValueError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ParseHeaderValueError;
|
pub struct ParseHeaderValueError;
|
||||||
|
|
||||||
impl TryFrom<&HeaderValue> for Expiration {
|
impl TryFrom<&HeaderValue> for Expiration {
|
||||||
|
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
omegaupload-common = { path = "../common" }
|
omegaupload-common = { path = "../common", features = ["wasm"] }
|
||||||
# Enables wasm support
|
# Enables wasm support
|
||||||
getrandom = { version = "*", features = ["js"] }
|
getrandom = { version = "*", features = ["js"] }
|
||||||
|
|
||||||
|
|
89
web/src/idb_object.rs
Normal file
89
web/src/idb_object.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use std::{hint::unreachable_unchecked, marker::PhantomData};
|
||||||
|
|
||||||
|
use js_sys::{Array, JsString, Object};
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
|
pub 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 From<IdbObject<Ready>> for Object {
|
||||||
|
fn from(db_object: IdbObject<Ready>) -> Self {
|
||||||
|
match Object::from_entries(db_object.as_ref()) {
|
||||||
|
Ok(o) => o,
|
||||||
|
// SAFETY: IdbObject maintains the invariant that it can eventually
|
||||||
|
// be constructed into a JS object.
|
||||||
|
_ => unsafe { panic!() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdbObject<NeedsType> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Array::new(), PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn video(self) -> IdbObject<NeedsExpiration> {
|
||||||
|
self.add_tuple("type", &JsString::from("video"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn audio(self) -> IdbObject<NeedsExpiration> {
|
||||||
|
self.add_tuple("type", &JsString::from("audio"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image(self) -> IdbObject<NeedsExpiration> {
|
||||||
|
self.add_tuple("type", &JsString::from("image"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blob(self) -> IdbObject<NeedsExpiration> {
|
||||||
|
self.add_tuple("type", &JsString::from("blob"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(self) -> IdbObject<NeedsExpiration> {
|
||||||
|
self.add_tuple("type", &JsString::from("string"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdbObject<NeedsExpiration> {
|
||||||
|
pub fn expiration_text(self, expires: &str) -> IdbObject<NeedsData> {
|
||||||
|
self.add_tuple("expiration", &JsString::from(expires))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdbObject<NeedsData> {
|
||||||
|
pub fn data(self, value: &JsValue) -> IdbObject<Ready> {
|
||||||
|
self.add_tuple("data", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdbObject<Ready> {
|
||||||
|
pub 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_idb_object_state {
|
||||||
|
($($ident:ident),*) => {
|
||||||
|
pub trait IdbObjectState {}
|
||||||
|
$(
|
||||||
|
pub enum $ident {}
|
||||||
|
impl IdbObjectState for $ident {}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_idb_object_state!(NeedsType, NeedsExpiration, NeedsData, Ready);
|
244
web/src/main.rs
244
web/src/main.rs
|
@ -1,36 +1,56 @@
|
||||||
#![warn(clippy::nursery, clippy::pedantic)]
|
#![warn(clippy::nursery, clippy::pedantic)]
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use byte_unit::Byte;
|
use byte_unit::Byte;
|
||||||
use decrypt::DecryptedData;
|
use decrypt::DecryptedData;
|
||||||
use gloo_console::log;
|
use gloo_console::log;
|
||||||
use http::header::EXPIRES;
|
|
||||||
use http::uri::PathAndQuery;
|
use http::uri::PathAndQuery;
|
||||||
use http::{StatusCode, Uri};
|
use http::{StatusCode, Uri};
|
||||||
use js_sys::{Array, JsString, Object, Uint8Array};
|
use js_sys::{JsString, Object, Uint8Array};
|
||||||
use omegaupload_common::{Expiration, PartialParsedUrl};
|
use omegaupload_common::{Expiration, PartialParsedUrl};
|
||||||
use reqwasm::http::Request;
|
use reqwasm::http::Request;
|
||||||
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||||
use web_sys::{window, Event, IdbDatabase, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode};
|
use web_sys::{Event, IdbObjectStore, IdbOpenDbRequest, IdbTransactionMode, Location, Window};
|
||||||
|
|
||||||
use crate::decrypt::decrypt;
|
use crate::decrypt::decrypt;
|
||||||
|
use crate::idb_object::IdbObject;
|
||||||
|
use crate::util::as_idb_db;
|
||||||
|
|
||||||
mod decrypt;
|
mod decrypt;
|
||||||
|
mod idb_object;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn loadFromDb();
|
#[wasm_bindgen(js_name = loadFromDb)]
|
||||||
fn createNotFoundUi();
|
fn load_from_db();
|
||||||
|
#[wasm_bindgen(js_name = createNotFoundUi)]
|
||||||
|
fn create_not_found_ui();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window() -> Window {
|
||||||
|
web_sys::window().expect("Failed to get a reference of the window")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn location() -> Location {
|
||||||
|
window().location()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_idb() -> Result<IdbOpenDbRequest> {
|
||||||
|
window()
|
||||||
|
.indexed_db()
|
||||||
|
.unwrap()
|
||||||
|
.context("Missing browser idb impl")?
|
||||||
|
.open("omegaupload")
|
||||||
|
.map_err(|_| anyhow!("Failed to open idb"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let window = window().unwrap();
|
let url = String::from(location().to_string());
|
||||||
let url = String::from(window.location().to_string());
|
|
||||||
let request_uri = {
|
let request_uri = {
|
||||||
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
|
let mut uri_parts = url.parse::<Uri>().unwrap().into_parts();
|
||||||
if let Some(parts) = uri_parts.path_and_query.as_mut() {
|
if let Some(parts) = uri_parts.path_and_query.as_mut() {
|
||||||
|
@ -39,30 +59,27 @@ fn main() {
|
||||||
Uri::from_parts(uri_parts).unwrap()
|
Uri::from_parts(uri_parts).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
if window.location().pathname().unwrap() == "/" {
|
log!(&url);
|
||||||
|
log!(&request_uri.to_string());
|
||||||
|
log!(&location().pathname().unwrap());
|
||||||
|
if location().pathname().unwrap() == "/" {
|
||||||
} else {
|
} else {
|
||||||
spawn_local(async {
|
spawn_local(async {
|
||||||
a(request_uri, url).await;
|
if let Err(e) = fetch_resources(request_uri, url).await {
|
||||||
|
log!(e.to_string());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::future_not_send)]
|
#[allow(clippy::future_not_send)]
|
||||||
async fn a(request_uri: Uri, url: String) -> Result<()> {
|
async fn fetch_resources(request_uri: Uri, url: String) -> Result<()> {
|
||||||
match Request::get(&request_uri.to_string()).send().await {
|
match Request::get(&request_uri.to_string()).send().await {
|
||||||
Ok(resp) if resp.status() == StatusCode::OK => {
|
Ok(resp) if resp.status() == StatusCode::OK => {
|
||||||
let expires = resp
|
let expires = Expiration::try_from(resp.headers()).map_or_else(
|
||||||
.headers()
|
|_| "This item does not expire.".to_string(),
|
||||||
.get(EXPIRES.as_str())
|
|expires| expires.to_string(),
|
||||||
.ok()
|
);
|
||||||
.flatten()
|
|
||||||
.as_deref()
|
|
||||||
.and_then(|v| Expiration::try_from(v).ok())
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(
|
|
||||||
|| "This item does not expire.".to_string(),
|
|
||||||
Expiration::to_string,
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
let data_fut = resp
|
let data_fut = resp
|
||||||
|
@ -75,44 +92,33 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
|
||||||
Uint8Array::new(&data).to_vec()
|
Uint8Array::new(&data).to_vec()
|
||||||
};
|
};
|
||||||
|
|
||||||
let info = url
|
let (key, nonce) = {
|
||||||
.split_once('#')
|
let partial_parsed_url = url
|
||||||
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
|
.split_once('#')
|
||||||
.unwrap_or_default();
|
.map(|(_, fragment)| PartialParsedUrl::from(fragment))
|
||||||
let key = info
|
.unwrap_or_default();
|
||||||
.decryption_key
|
let key = partial_parsed_url
|
||||||
.expect("missing key should be handled in the future");
|
.decryption_key
|
||||||
let nonce = info.nonce.expect("missing nonce be handled in the future");
|
.context("missing key should be handled in the future")?;
|
||||||
|
let nonce = partial_parsed_url
|
||||||
let result = decrypt(data, key, nonce, None);
|
.nonce
|
||||||
|
.context("missing nonce be handled in the future")?;
|
||||||
let decrypted = match result {
|
(key, nonce)
|
||||||
Ok(decrypted) => decrypted,
|
|
||||||
Err(err) => {
|
|
||||||
// log!("decryption error: {}", err);
|
|
||||||
// return Box::new(PasteError(err));
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_open_req = window()
|
let decrypted = decrypt(data, key, nonce, None)?;
|
||||||
.unwrap()
|
let db_open_req = open_idb()?;
|
||||||
.indexed_db()
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.open("omegaupload")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// On success callback
|
// On success callback
|
||||||
let on_success = Closure::once(Box::new(move |event: Event| {
|
let on_success = Closure::once(Box::new(move |event: Event| {
|
||||||
let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap();
|
let transaction: IdbObjectStore = as_idb_db(&event)
|
||||||
let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap();
|
|
||||||
let transaction: IdbObjectStore = db
|
|
||||||
.transaction_with_str_and_mode("decrypted data", IdbTransactionMode::Readwrite)
|
.transaction_with_str_and_mode("decrypted data", IdbTransactionMode::Readwrite)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.object_store("decrypted data")
|
.object_store("decrypted data")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
log!(line!());
|
||||||
|
|
||||||
let decrypted_object = match &decrypted {
|
let decrypted_object = match &decrypted {
|
||||||
DecryptedData::String(s) => IdbObject::new()
|
DecryptedData::String(s) => IdbObject::new()
|
||||||
.string()
|
.string()
|
||||||
|
@ -146,37 +152,49 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
|
||||||
.data(blob),
|
.data(blob),
|
||||||
};
|
};
|
||||||
|
|
||||||
let db_entry = Object::from_entries(decrypted_object.as_ref()).unwrap();
|
log!(line!());
|
||||||
transaction
|
let put_action = transaction
|
||||||
.put_with_key(
|
.put_with_key(
|
||||||
&db_entry,
|
&Object::from(decrypted_object),
|
||||||
&JsString::from(window().unwrap().location().pathname().unwrap()),
|
&JsString::from(location().pathname().unwrap()),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.set_onsuccess(Some(
|
put_action.set_onsuccess(Some(
|
||||||
Closure::once(Box::new(|| {
|
Closure::wrap(Box::new(|| {
|
||||||
log!("success");
|
log!("success");
|
||||||
loadFromDb();
|
load_from_db();
|
||||||
}) as Box<dyn FnOnce()>)
|
}) as Box<dyn Fn()>)
|
||||||
.into_js_value()
|
.into_js_value()
|
||||||
.dyn_ref()
|
.unchecked_ref(),
|
||||||
.unwrap(),
|
));
|
||||||
));
|
put_action.set_onerror(Some(
|
||||||
|
Closure::wrap(Box::new(|e| {
|
||||||
|
log!(line!());
|
||||||
|
log!(e);
|
||||||
|
}) as Box<dyn Fn(Event)>)
|
||||||
|
.into_js_value()
|
||||||
|
.unchecked_ref(),
|
||||||
|
));
|
||||||
}) as Box<dyn FnOnce(Event)>);
|
}) as Box<dyn FnOnce(Event)>);
|
||||||
|
|
||||||
db_open_req.set_onsuccess(Some(on_success.into_js_value().dyn_ref().unwrap()));
|
db_open_req.set_onsuccess(Some(on_success.into_js_value().unchecked_ref()));
|
||||||
|
db_open_req.set_onerror(Some(
|
||||||
// On upgrade callback
|
Closure::wrap(Box::new(|e| {
|
||||||
|
log!(line!());
|
||||||
|
log!(e);
|
||||||
|
}) as Box<dyn Fn(Event)>)
|
||||||
|
.into_js_value()
|
||||||
|
.unchecked_ref(),
|
||||||
|
));
|
||||||
let on_upgrade = Closure::wrap(Box::new(move |event: Event| {
|
let on_upgrade = Closure::wrap(Box::new(move |event: Event| {
|
||||||
let target: IdbOpenDbRequest = event.target().unwrap().dyn_into().unwrap();
|
let db = as_idb_db(&event);
|
||||||
let db: IdbDatabase = target.result().unwrap().dyn_into().unwrap();
|
let _ = db.create_object_store("decrypted data").unwrap();
|
||||||
let _obj_store = db.create_object_store("decrypted data").unwrap();
|
|
||||||
}) as Box<dyn FnMut(Event)>);
|
}) as Box<dyn FnMut(Event)>);
|
||||||
|
db_open_req.set_onupgradeneeded(Some(on_upgrade.into_js_value().unchecked_ref()));
|
||||||
db_open_req.set_onupgradeneeded(Some(on_upgrade.into_js_value().dyn_ref().unwrap()));
|
log!(&db_open_req);
|
||||||
}
|
}
|
||||||
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
|
Ok(resp) if resp.status() == StatusCode::NOT_FOUND => {
|
||||||
createNotFoundUi();
|
create_not_found_ui();
|
||||||
}
|
}
|
||||||
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {}
|
Ok(resp) if resp.status() == StatusCode::BAD_REQUEST => {}
|
||||||
Ok(err) => {}
|
Ok(err) => {}
|
||||||
|
@ -185,79 +203,3 @@ async fn a(request_uri: Uri, url: String) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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 {}
|
|
||||||
|
|
13
web/src/util.rs
Normal file
13
web/src/util.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use js_sys::Function;
|
||||||
|
use wasm_bindgen::closure::WasmClosure;
|
||||||
|
use wasm_bindgen::prelude::Closure;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{Event, IdbDatabase, IdbOpenDbRequest};
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This will panic if event is not an event from the IDB API.
|
||||||
|
pub fn as_idb_db(event: &Event) -> IdbDatabase {
|
||||||
|
let target: IdbOpenDbRequest = event.target().map(JsCast::unchecked_into).unwrap();
|
||||||
|
target.result().map(JsCast::unchecked_into).unwrap()
|
||||||
|
}
|
Loading…
Reference in a new issue