mod js;
use std::{
cell::RefCell,
collections::VecDeque,
future::poll_fn,
rc::Rc,
task::{Poll, Waker},
};
use crate::client::web::clock;
use crate::client::web::socket::js::{Calls, connect, function, random, said};
use crate::live::{Event, FeedError, PING, Post, Session, Subscription, Tick};
use js_sys::Function;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{MessageEvent, WebSocket};
pub struct Feed {
driver: Rc<RefCell<Driver>>,
}
impl Feed {
#[must_use]
pub fn start(session: Session) -> Feed {
let url = session.url();
Feed::start_at(url, session)
}
#[must_use]
pub fn start_at(url: impl Into<String>, session: Session) -> Feed {
let global: JsValue = js_sys::global().into();
let timers = function(&global, "setTimeout").zip(function(&global, "clearTimeout"));
let driver = Rc::new_cyclic(|driver| {
RefCell::new(Driver {
url: url.into(),
session,
global,
timers,
socket: None,
timer: None,
calls: Calls::new(driver),
events: VecDeque::new(),
waker: None,
stopped: false,
})
});
if let Ok(mut driver) = driver.try_borrow_mut() {
if driver.timers.is_some() {
driver.dial();
} else {
driver.stop();
}
}
Feed { driver }
}
pub async fn subscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
self.with(|driver| {
if let Some(frame) = driver.session.subscribe(subscription)? {
driver.carry(&frame);
}
Ok(())
})
}
pub fn unsubscribe(&self, subscription: Subscription) -> Result<(), FeedError> {
self.with(|driver| {
if let Some(frame) = driver.session.unsubscribe(&subscription) {
driver.carry(&frame);
}
Ok(())
})
}
pub async fn post(&self, request: Post) -> Result<u64, FeedError> {
self.with(|driver| {
let (id, frame) = driver.session.post(&request)?;
driver.carry(&frame);
Ok(id)
})
}
pub async fn next(&mut self) -> Option<Event> {
poll_fn(|cx| {
let Ok(mut driver) = self.driver.try_borrow_mut() else {
cx.waker().wake_by_ref();
return Poll::Pending;
};
if let Some(event) = driver.events.pop_front() {
return Poll::Ready(Some(event));
}
if driver.stopped {
return Poll::Ready(None);
}
driver.waker = Some(cx.waker().clone());
Poll::Pending
})
.await
}
pub fn close(self) {
drop(self);
}
fn with<T>(&self, act: impl FnOnce(&mut Driver) -> Result<T, FeedError>) -> Result<T, FeedError> {
let mut driver = self.driver.try_borrow_mut().map_err(|_| FeedError::Stopped)?;
if driver.stopped {
return Err(FeedError::Stopped);
}
act(&mut driver)
}
}
impl Drop for Feed {
fn drop(&mut self) {
if let Ok(mut driver) = self.driver.try_borrow_mut() {
driver.stop();
}
}
}
struct Driver {
url: String,
session: Session,
global: JsValue,
timers: Option<(Function, Function)>,
socket: Option<WebSocket>,
timer: Option<JsValue>,
calls: Calls,
events: VecDeque<Event>,
waker: Option<Waker>,
stopped: bool,
}
impl Driver {
fn dial(&mut self) {
match connect(&self.global, &self.url) {
Ok(socket) => {
socket.set_onopen(Some(self.calls.open.as_ref().unchecked_ref()));
socket.set_onmessage(Some(self.calls.message.as_ref().unchecked_ref()));
socket.set_onclose(Some(self.calls.close.as_ref().unchecked_ref()));
socket.set_onerror(Some(self.calls.error.as_ref().unchecked_ref()));
self.socket = Some(socket);
}
Err(reason) => self.lost(reason),
}
}
fn opened(&mut self) {
for frame in self.session.opened(clock()) {
if let Err(reason) = self.send(&frame) {
return self.lost(reason);
}
}
self.events.push_back(Event::Opened);
self.arm_tick();
}
fn received(&mut self, message: &MessageEvent) {
let Some(text) = message.data().as_string() else { return };
let event = match self.session.received(clock(), &text) {
Ok(frame) => Event::Frame(Box::new(frame)),
Err(bad) => Event::Unreadable(bad),
};
self.events.push_back(event);
}
fn woke(&mut self) {
self.timer = None;
if self.session.is_open() {
match self.session.tick(clock()) {
Tick::Wait => {}
Tick::Ping => {
if let Err(reason) = self.send(PING) {
return self.lost(reason);
}
}
Tick::Reconnect => return self.lost("nothing arrived for too long".to_owned()),
}
self.arm_tick();
} else if self.socket.is_none() {
self.dial();
}
}
fn carry(&mut self, frame: &str) {
if let Err(reason) = self.send(frame) {
self.lost(reason);
}
}
fn send(&self, frame: &str) -> Result<(), String> {
let Some(socket) = &self.socket else { return Err("no socket to send on".to_owned()) };
socket.send_with_str(frame).map_err(|error| said(&error).unwrap_or_else(|| "the send failed".to_owned()))
}
fn lost(&mut self, reason: String) {
self.hang_up();
let retry = self.session.closed(clock(), random());
let after = retry.after_ms;
self.events.push_back(Event::Closed { reason, retry });
self.arm(after);
}
fn hang_up(&mut self) {
if let Some(socket) = self.socket.take() {
socket.set_onopen(None);
socket.set_onmessage(None);
socket.set_onclose(None);
socket.set_onerror(None);
let _ = socket.close();
}
}
fn stop(&mut self) {
self.hang_up();
self.disarm();
self.stopped = true;
}
fn arm_tick(&mut self) {
if let Some(at) = self.session.due() {
self.arm(at.saturating_sub(clock()));
}
}
fn arm(&mut self, ms: u64) {
self.disarm();
let Some((set_timeout, _)) = &self.timers else { return self.stop() };
let delay = JsValue::from(u32::try_from(ms).map_or(LONGEST_DELAY, |ms| ms.min(LONGEST_DELAY)));
match set_timeout.call2(&self.global, self.calls.wake.as_ref(), &delay) {
Ok(timer) => self.timer = Some(timer),
Err(_) => self.stop(),
}
}
fn disarm(&mut self) {
if let (Some(timer), Some((_, clear_timeout))) = (self.timer.take(), &self.timers) {
let _ = clear_timeout.call1(&self.global, &timer);
}
}
fn wakeable(&mut self) -> Option<Waker> {
if self.events.is_empty() && !self.stopped { None } else { self.waker.take() }
}
}
const LONGEST_DELAY: u32 = 0x7fff_ffff;
#[cfg(test)]
#[cfg(target_arch = "wasm32")]
#[path = "tests/feed.rs"]
mod tests;