#[cfg(target_arch = "wasm32")]
use std::cell::RefCell;
#[cfg(target_arch = "wasm32")]
use std::rc::Rc;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use crate::storage::BlockStorage;
#[cfg(target_arch = "wasm32")]
pub struct WasmAutoSyncManager {
db_name: String,
is_active: bool,
idle_callback_handle: Option<u32>,
visibility_listener: Option<Closure<dyn FnMut()>>,
beforeunload_listener: Option<Closure<dyn FnMut()>>,
idle_closure: Option<Closure<dyn FnMut()>>,
}
#[cfg(target_arch = "wasm32")]
impl WasmAutoSyncManager {
pub fn new(db_name: String) -> Self {
log::info!(
"Creating event-driven WASM auto-sync manager for database: {}",
db_name
);
Self {
db_name,
is_active: false,
idle_callback_handle: None,
visibility_listener: None,
beforeunload_listener: None,
idle_closure: None,
}
}
pub fn start(&mut self) {
if self.is_active {
log::warn!("WASM auto-sync already active, stopping previous instance");
self.stop();
}
self.is_active = true;
log::info!("Starting event-driven WASM auto-sync");
self.setup_idle_callback();
self.setup_visibility_listener();
self.setup_beforeunload_listener();
}
pub fn stop(&mut self) {
if !self.is_active {
return;
}
log::info!("Stopping WASM auto-sync and cleaning up event listeners");
if let Some(handle) = self.idle_callback_handle.take() {
if let Some(window) = web_sys::window() {
window.cancel_idle_callback(handle);
}
}
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
if let Some(listener) = self.visibility_listener.take() {
let _ = document.remove_event_listener_with_callback(
"visibilitychange",
listener.as_ref().unchecked_ref(),
);
}
}
if let Some(listener) = self.beforeunload_listener.take() {
let _ = window.remove_event_listener_with_callback(
"beforeunload",
listener.as_ref().unchecked_ref(),
);
}
}
self.idle_closure = None;
self.is_active = false;
}
fn setup_idle_callback(&mut self) {
let window = match web_sys::window() {
Some(w) => w,
None => {
log::error!("Failed to get window object for idle callback");
return;
}
};
let db_name_clone = self.db_name.clone();
let closure = Closure::wrap(Box::new(move || {
let db_name = db_name_clone.clone();
log::debug!("Idle callback triggered for database: {}", db_name);
wasm_bindgen_futures::spawn_local(async move {
if let Ok(storage) = BlockStorage::new(&db_name).await {
let dirty_count = storage.get_dirty_count();
if dirty_count > 0 {
log::info!("Idle sync: syncing {} dirty blocks", dirty_count);
if let Err(e) = storage.sync().await {
log::error!("Idle sync failed: {}", e.message);
}
}
}
});
}) as Box<dyn FnMut()>);
match window.request_idle_callback(closure.as_ref().unchecked_ref()) {
Ok(handle) => {
self.idle_callback_handle = Some(handle);
self.idle_closure = Some(closure);
log::debug!("Idle callback registered");
}
Err(e) => {
log::warn!("Failed to register idle callback: {:?}", e);
}
}
}
fn setup_visibility_listener(&mut self) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
let document = match window.document() {
Some(d) => d,
None => return,
};
let db_name_clone = self.db_name.clone();
let closure = Closure::wrap(Box::new(move || {
let db_name = db_name_clone.clone();
log::debug!("Visibility change detected for database: {}", db_name);
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
if document.hidden() {
log::info!("Tab hidden, triggering sync");
wasm_bindgen_futures::spawn_local(async move {
if let Ok(storage) = BlockStorage::new(&db_name).await {
let _ = storage.sync().await;
}
});
}
}
}
}) as Box<dyn FnMut()>);
let _ = document
.add_event_listener_with_callback("visibilitychange", closure.as_ref().unchecked_ref());
self.visibility_listener = Some(closure);
}
fn setup_beforeunload_listener(&mut self) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
let db_name_clone = self.db_name.clone();
let closure = Closure::wrap(Box::new(move || {
let db_name = db_name_clone.clone();
log::info!("Page unloading, triggering final sync for {}", db_name);
wasm_bindgen_futures::spawn_local(async move {
if let Ok(storage) = BlockStorage::new(&db_name).await {
let _ = storage.sync().await;
}
});
}) as Box<dyn FnMut()>);
let _ = window
.add_event_listener_with_callback("beforeunload", closure.as_ref().unchecked_ref());
self.beforeunload_listener = Some(closure);
}
pub fn is_active(&self) -> bool {
self.is_active
}
}
#[cfg(target_arch = "wasm32")]
impl Drop for WasmAutoSyncManager {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(target_arch = "wasm32")]
thread_local! {
static WASM_AUTO_SYNC_REGISTRY: RefCell<std::collections::HashMap<String, Rc<RefCell<WasmAutoSyncManager>>>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(target_arch = "wasm32")]
pub fn register_wasm_auto_sync(db_name: &str) {
WASM_AUTO_SYNC_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
let manager = reg.entry(db_name.to_string()).or_insert_with(|| {
Rc::new(RefCell::new(WasmAutoSyncManager::new(db_name.to_string())))
});
manager.borrow_mut().start();
});
}
#[cfg(target_arch = "wasm32")]
pub fn unregister_wasm_auto_sync(db_name: &str) {
WASM_AUTO_SYNC_REGISTRY.with(|registry| {
let mut reg = registry.borrow_mut();
if let Some(manager) = reg.remove(db_name) {
manager.borrow_mut().stop();
}
});
}
#[cfg(target_arch = "wasm32")]
pub fn is_wasm_auto_sync_active(db_name: &str) -> bool {
WASM_AUTO_SYNC_REGISTRY.with(|registry| {
let reg = registry.borrow();
reg.get(db_name).map_or(false, |m| m.borrow().is_active())
})
}