#![cfg(target_arch = "wasm32")]
#![deny(missing_docs)]
use std::cell::RefCell;
use gloo_events::EventListener;
use swr_core::{Instant, Runtime, RuntimeFuture, SwrClient, SwrEvent};
#[derive(Clone, Copy, Debug, Default)]
pub struct WebRuntime;
impl WebRuntime {
pub fn new() -> Self {
Self
}
}
impl Runtime for WebRuntime {
fn now(&self) -> Instant {
Instant::now()
}
fn spawn(&self, fut: RuntimeFuture) {
wasm_bindgen_futures::spawn_local(fut);
}
fn sleep_until(&self, at: Instant) -> RuntimeFuture {
Box::pin(async move {
let delay = at.saturating_duration_since(Instant::now());
gloo_timers::future::sleep(delay).await;
})
}
}
#[must_use = "dropping the WebEventSource detaches the client from browser events"]
pub struct WebEventSource {
id: u64,
}
impl WebEventSource {
pub fn attach(client: &SwrClient) -> Self {
REGISTRY.with(|registry| registry.borrow_mut().attach(client.clone()))
}
}
impl Drop for WebEventSource {
fn drop(&mut self) {
REGISTRY.with(|registry| registry.borrow_mut().detach(self.id));
}
}
thread_local! {
static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
}
#[derive(Default)]
struct Registry {
next_id: u64,
clients: Vec<(u64, SwrClient)>,
listeners: Option<Vec<EventListener>>,
}
impl Registry {
fn attach(&mut self, client: SwrClient) -> WebEventSource {
let id = self.next_id;
self.next_id += 1;
self.clients.push((id, client));
if self.listeners.is_none() {
self.listeners = Some(register_dom_listeners());
}
WebEventSource { id }
}
fn detach(&mut self, id: u64) {
self.clients.retain(|(client_id, _)| *client_id != id);
if self.clients.is_empty() {
self.listeners = None;
}
}
}
fn register_dom_listeners() -> Vec<EventListener> {
let window = web_sys::window().expect("browser environment: window missing");
let document = window
.document()
.expect("browser environment: document missing");
vec![
EventListener::new(&window, "focus", |_event| broadcast_all(SwrEvent::Focus)),
EventListener::new(&window, "online", |_event| broadcast_all(SwrEvent::Online)),
EventListener::new(&document, "visibilitychange", |_event| {
if document_visible() {
broadcast_all(SwrEvent::Focus);
}
}),
]
}
fn document_visible() -> bool {
web_sys::window()
.and_then(|window| window.document())
.is_some_and(|document| document.visibility_state() == web_sys::VisibilityState::Visible)
}
fn broadcast_all(ev: SwrEvent) {
let clients: Vec<SwrClient> = REGISTRY.with(|registry| {
registry
.borrow()
.clients
.iter()
.map(|(_, client)| client.clone())
.collect()
});
for client in clients {
client.broadcast(ev);
}
}