#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use std::io;
use js_sys::{Function, Promise, Reflect};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BrowserStorageEstimate {
pub usage_bytes: Option<u64>,
pub quota_bytes: Option<u64>,
}
pub async fn browser_storage_estimate() -> Result<BrowserStorageEstimate> {
let storage = browser_storage_manager()?;
let estimate = storage_function(&storage, "estimate")?;
let promise = estimate
.call0(&storage)
.map_err(|error| map_js_value_error(&error, "request browser storage estimate"))?
.dyn_into::<Promise>()
.map_err(|_| Error::unsupported_backend("browser storage estimate promise"))?;
let value = JsFuture::from(promise)
.await
.map_err(|error| map_js_value_error(&error, "await browser storage estimate"))?;
Ok(BrowserStorageEstimate {
usage_bytes: reflect_optional_u64(&value, "usage")?,
quota_bytes: reflect_optional_u64(&value, "quota")?,
})
}
pub async fn browser_persistent_storage_granted() -> Result<bool> {
call_storage_bool_method("persisted").await
}
pub async fn request_browser_persistent_storage() -> Result<bool> {
call_storage_bool_method("persist").await
}
async fn call_storage_bool_method(method: &'static str) -> Result<bool> {
let storage = browser_storage_manager()?;
let function = storage_function(&storage, method)?;
let promise = function
.call0(&storage)
.map_err(|error| map_js_value_error(&error, "request browser storage status"))?
.dyn_into::<Promise>()
.map_err(|_| Error::unsupported_backend("browser storage status promise"))?;
let value = JsFuture::from(promise)
.await
.map_err(|error| map_js_value_error(&error, "await browser storage status"))?;
value
.as_bool()
.ok_or_else(|| Error::Io(io::Error::other("browser storage status was not a boolean")))
}
fn browser_storage_manager() -> Result<JsValue> {
let navigator = Reflect::get(&js_sys::global(), &JsValue::from_str("navigator"))
.map_err(|error| map_js_value_error(&error, "read browser navigator"))?;
if navigator.is_null() || navigator.is_undefined() {
return Err(Error::unsupported_backend("browser navigator"));
}
let storage = Reflect::get(&navigator, &JsValue::from_str("storage"))
.map_err(|error| map_js_value_error(&error, "read browser storage manager"))?;
if storage.is_null() || storage.is_undefined() {
return Err(Error::unsupported_backend("browser storage manager"));
}
Ok(storage)
}
fn storage_function(storage: &JsValue, method: &'static str) -> Result<Function> {
Reflect::get(storage, &JsValue::from_str(method))
.map_err(|error| map_js_value_error(&error, "read browser storage function"))?
.dyn_into::<Function>()
.map_err(|_| Error::unsupported_backend("browser storage manager function"))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
fn reflect_optional_u64(value: &JsValue, property: &'static str) -> Result<Option<u64>> {
let value = Reflect::get(value, &JsValue::from_str(property))
.map_err(|error| map_js_value_error(&error, "read browser storage estimate field"))?;
if value.is_null() || value.is_undefined() {
return Ok(None);
}
let Some(number) = value.as_f64() else {
return Err(Error::Io(io::Error::other(format!(
"browser storage estimate field {property} was not a number"
))));
};
if !number.is_finite() || number < 0.0 || number > u64::MAX as f64 {
return Err(Error::Io(io::Error::other(format!(
"browser storage estimate field {property} was out of range"
))));
}
Ok(Some(number as u64))
}
fn map_js_value_error(error: &JsValue, action: &'static str) -> Error {
let message = js_value_property(error, "message")
.or_else(|| js_value_property(error, "name"))
.or_else(|| error.as_string())
.unwrap_or_else(|| format!("{error:?}"));
Error::Io(io::Error::other(format!(
"browser storage manager failed to {action}: {message}"
)))
}
fn js_value_property(value: &JsValue, property: &str) -> Option<String> {
Reflect::get(value, &JsValue::from_str(property))
.ok()
.and_then(|value| value.as_string())
}