use wallet_adapter_common::WalletCommonUtils;
use web_sys::{
js_sys::{self, Array, Function, Object, Reflect},
wasm_bindgen::{JsCast, JsValue},
};
use crate::{WalletError, WalletResult};
pub struct InnerUtils;
impl InnerUtils {
pub fn jsvalue_to_error<T: core::fmt::Debug>(
value: Result<T, JsValue>,
) -> Result<(), WalletError> {
if let Err(error) = value {
Err(error.into())
} else {
Ok(())
}
}
pub fn jsvalue_to_signature(
value: JsValue,
namespace: &str,
) -> WalletResult<ed25519_dalek::Signature> {
let in_case_of_error = Err(WalletError::InternalError(format!(
"{namespace}: `{value:?}` cannot be cast to a Uint8Array, only a JsValue of bytes can be cast."
)));
let signature_bytes: [u8; 64] = value
.dyn_into::<js_sys::Uint8Array>()
.or(in_case_of_error)?
.to_vec()
.try_into()
.or(Err(WalletError::InvalidEd25519PublicKeyBytes))?;
Ok(WalletCommonUtils::signature(&signature_bytes))
}
}
#[derive(Debug)]
pub struct Reflection(JsValue);
impl Reflection {
pub fn new(value: JsValue) -> WalletResult<Self> {
Reflection::check_is_undefined(&value)?;
Ok(Self(value))
}
pub fn new_from_str(value: &JsValue, key: &str) -> WalletResult<Self> {
let inner = Reflect::get(value, &key.into())?;
Reflection::new(inner)
}
pub fn new_object() -> Self {
Self(Object::new().into())
}
pub fn take(self) -> JsValue {
self.0
}
pub fn set_object_str(&mut self, key: &str, value: &str) -> WalletResult<&Self> {
self.set_object(&key.into(), &value.into())
}
pub fn set_object_string_optional(
&mut self,
key: &str,
value: Option<&String>,
) -> WalletResult<&Self> {
if let Some(inner_value) = value {
self.set_object(&key.into(), &inner_value.into())
} else {
Ok(self)
}
}
pub fn set_object(&mut self, key: &JsValue, value: &JsValue) -> WalletResult<&Self> {
if !self.0.is_object() {
return Err(WalletError::InternalError(format!(
"Attempted to set the key `{key:?} in type `{value:?} which is not a JS object"
)));
}
let target = self.0.dyn_ref::<Object>().unwrap();
Reflect::set(target, key, value)?;
self.0 = target.into();
Ok(self)
}
pub fn reflect_inner(&self, key: &str) -> WalletResult<JsValue> {
let inner = Reflect::get(&self.0, &key.into())?;
Reflection::check_is_undefined(&inner)?;
Ok(inner)
}
pub fn string(&self, key: &str) -> WalletResult<String> {
let name = Reflect::get(&self.0, &key.into())?;
let parsed = name.as_string().ok_or(WalletError::InternalError(format!(
"Reflecting {key:?} did not yield a JsString"
)))?;
Ok(parsed)
}
pub fn string_optional(&self, key: &str) -> WalletResult<Option<String>> {
let name = Reflect::get(&self.0, &key.into())?;
Ok(name.as_string())
}
pub fn get_bytes_from_vec(&self, key: &str) -> WalletResult<Vec<Vec<u8>>> {
let js_array = self.get_array()?;
js_array
.iter()
.map(|value| Reflection::new(value)?.reflect_bytes(key))
.collect::<WalletResult<Vec<Vec<u8>>>>()
}
pub fn into_bytes(self) -> WalletResult<Vec<u8>> {
let js_typeof = Self::js_typeof(&self.0);
Ok(self
.0
.dyn_into::<js_sys::Uint8Array>()
.or(Err(Self::concat_error("Uint8Array", &js_typeof)))?
.to_vec())
}
pub fn reflect_bytes(&self, key: &str) -> WalletResult<Vec<u8>> {
let js_value = Reflect::get(&self.0, &key.into())?;
let incase_of_error = Err(WalletError::InternalError(format!(
"`{js_value:?}` reflected from key `{key}` of JsValue `{:?}` cannot be cast to a Uint8Array, only a JsValue of bytes can be cast.", self.0
)));
let to_uint8array = js_value
.dyn_into::<js_sys::Uint8Array>()
.or(incase_of_error)?;
Ok(to_uint8array.to_vec())
}
pub fn byte32array(&self, key: &str) -> WalletResult<[u8; 32]> {
let js_value = Reflect::get(&self.0, &key.into())?;
let to_js_array: js_sys::Uint8Array = js_value.unchecked_into();
let byte32array: [u8; 32] = to_js_array
.to_vec()
.try_into()
.or(Err(WalletError::Expected32ByteLength))?;
Ok(byte32array)
}
pub fn get_array(&self) -> WalletResult<Array> {
Ok(self.0.clone().dyn_into::<js_sys::Array>()?)
}
pub fn get_string(value: &JsValue) -> WalletResult<String> {
value.as_string().ok_or(WalletError::InternalError(format!(
"{value:?} is not a JsString"
)))
}
pub fn vec_string_accept_undefined(&self, key: &str) -> WalletResult<Vec<String>> {
let to_js_array = self.reflect_js_array(key)?;
Ok(to_js_array
.iter()
.filter_map(|value| Self::get_string(&value).ok())
.collect::<Vec<String>>())
}
pub fn reflect_js_array(&self, key: &str) -> WalletResult<Array> {
let js_value = self.reflect_inner(key)?;
Self::new(js_value)?.into_array()
}
pub(crate) fn vec_string_and_filter(
&self,
key: &str,
filter: &str,
) -> WalletResult<Vec<String>> {
let js_value = self.reflect_inner(key)?;
let to_js_array = Reflection::new(js_value)?.into_array()?;
to_js_array
.iter()
.map(|value| {
value.as_string().ok_or(WalletError::InternalError(format!(
"{value:?} is not a JsString"
)))
})
.map(|value| {
let value = value?;
if value.starts_with(filter) {
Ok(value)
} else {
Err(WalletError::UnsupportedChain(value.to_string()))
}
})
.collect::<WalletResult<Vec<String>>>()
}
pub(crate) fn object_to_vec_string(&self, key: &str) -> WalletResult<Vec<String>> {
let features_value = self.reflect_inner(key)?;
let js_typeof = Self::js_typeof(&self.0);
let features_object = features_value
.dyn_ref::<Object>()
.ok_or(Self::concat_error("JS Object", &js_typeof))?;
Object::keys(features_object)
.iter()
.map(|value| {
value.as_string().ok_or(WalletError::InternalError(format!(
"{value:?} is not a JsString"
)))
})
.collect::<WalletResult<Vec<String>>>()
}
pub fn check_is_undefined(value: &JsValue) -> WalletResult<()> {
if value.is_undefined() || value.is_null() {
Err(WalletError::ValueNotFound)
} else {
Ok(())
}
}
pub fn get_function(&self, key: &str) -> WalletResult<Function> {
let js_value = Reflect::get(&self.0, &key.into())?;
let incase_of_error = Err(WalletError::InternalError(format!(
"`{js_value:?}` reflected from key `{key}` of JsValue `{:?}` cannot be cast to a js_sys::Function, only a JsValue of bytes can be cast.", self.0
)));
js_value.dyn_into::<Function>().or(incase_of_error)
}
pub fn get_inner(&self) -> &JsValue {
&self.0
}
pub fn js_typeof(value: &JsValue) -> String {
value.js_typeof().as_string().unwrap()
}
pub fn into_function(self) -> WalletResult<Function> {
let js_typeof = Self::js_typeof(&self.0);
self.0
.dyn_into::<Function>()
.or(Err(Self::concat_error("Function", &js_typeof)))
}
pub fn into_array(self) -> WalletResult<Array> {
let js_typeof = Self::js_typeof(&self.0);
self.0
.dyn_into::<Array>()
.or(Err(Self::concat_error("Array", &js_typeof)))
}
fn concat_error(expected: &str, encountered: &str) -> WalletError {
WalletError::InternalError(
String::new()
+ "Expected a typeof JS "
+ expected
+ "but encountered a typeof Js `"
+ encountered
+ "`.",
)
}
}
impl Default for Reflection {
fn default() -> Self {
Reflection(JsValue::undefined())
}
}
impl Clone for Reflection {
fn clone(&self) -> Self {
Reflection(self.0.clone())
}
}