use std::cell::RefCell;
use std::rc::Rc;
use js_sys::{Array, Function, Reflect};
use wasm_bindgen::prelude::*;
use crate::error::LibfwError;
#[derive(Debug, Clone)]
pub struct Callbacks {
inner: Rc<RefCell<Option<JsValue>>>,
}
impl Default for Callbacks {
fn default() -> Self {
Callbacks::new()
}
}
impl Callbacks {
pub fn new() -> Self {
Callbacks {
inner: Rc::new(RefCell::new(None)),
}
}
pub fn set(&self, callbacks: JsValue) {
*self.inner.borrow_mut() = Some(callbacks);
}
pub fn is_set(&self) -> bool {
self.inner.borrow().is_some()
}
pub fn call(&self, method: &str, args: &[JsValue]) -> Result<JsValue, LibfwError> {
let obj = self
.inner
.borrow()
.clone()
.ok_or_else(|| LibfwError::Protocol("JS callbacks not installed".into()))?;
let f: Function = Reflect::get(&obj, &JsValue::from_str(method))
.map_err(|e| LibfwError::Js(format!("missing callback `{method}`: {e:?}")))?
.into();
let this = obj;
let arr = Array::new();
for a in args {
arr.push(a);
}
f.apply(&this, &arr)
.map_err(|e| LibfwError::Js(format!("callback `{method}` failed: {e:?}")))
}
pub fn on_file_start(&self, path: &str, size: u64) -> Result<(), LibfwError> {
self.call(
"onFileStart",
&[JsValue::from_str(path), JsValue::from_f64(size as f64)],
)
.map(|_| ())
}
pub fn on_write_chunk(&self, path: &str, offset: u64, data: &[u8]) -> Result<(), LibfwError> {
let arr = js_sys::Uint8Array::from(data);
self.call(
"onWriteChunk",
&[
JsValue::from_str(path),
JsValue::from_f64(offset as f64),
arr.into(),
],
)
.map(|_| ())
}
pub fn on_file_completed(&self, path: &str) -> Result<(), LibfwError> {
self.call("onFileCompleted", &[JsValue::from_str(path)])
.map(|_| ())
}
pub fn on_progress(&self, done: u64, total: u64) -> Result<(), LibfwError> {
self.call(
"onProgress",
&[JsValue::from_f64(done as f64), JsValue::from_f64(total as f64)],
)
.map(|_| ())
}
pub async fn load_state(&self, path: &str) -> Result<Option<JsValue>, LibfwError> {
let promise = self.call("loadState", &[JsValue::from_str(path)])?;
let value = await_promise(promise).await?;
if value.is_null() || value.is_undefined() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub async fn save_state(&self, path: &str, state: &JsValue) -> Result<(), LibfwError> {
let promise = self.call(
"saveState",
&[JsValue::from_str(path), state.clone()],
)?;
await_promise(promise).await.map(|_| ())
}
pub async fn file_list(&self) -> Result<Vec<crate::plan::FileEntry>, LibfwError> {
let promise = self.call("getFileList", &[])?;
let value = await_promise(promise).await?;
crate::plan::parse_file_entries(&value)
}
pub async fn read_file(&self, path: &str, offset: u64, length: u64) -> Result<Vec<u8>, LibfwError> {
let promise = self.call(
"readFile",
&[
JsValue::from_str(path),
JsValue::from_f64(offset as f64),
JsValue::from_f64(length as f64),
],
)?;
let value = await_promise(promise).await?;
u8_vec_from_js(&value)
}
pub fn log(&self, msg: &str) {
let _ = self.call("log", &[JsValue::from_str(msg)]);
}
}
pub async fn await_promise(value: JsValue) -> Result<JsValue, LibfwError> {
let promise = js_sys::Promise::from(value);
wasm_bindgen_futures::JsFuture::from(promise)
.await
.map_err(LibfwError::from)
}
pub fn u8_vec_from_js(value: &JsValue) -> Result<Vec<u8>, LibfwError> {
if value.is_instance_of::<js_sys::ArrayBuffer>() {
let buf: js_sys::ArrayBuffer = value.clone().unchecked_into();
let arr = js_sys::Uint8Array::new(&buf);
Ok(arr.to_vec())
} else if value.is_instance_of::<js_sys::Uint8Array>() {
let arr: js_sys::Uint8Array = value.clone().unchecked_into();
Ok(arr.to_vec())
} else {
Err(LibfwError::Js(
"expected ArrayBuffer or Uint8Array from JS callback".into(),
))
}
}