use crate::page; use common::{ClientMessage, ServerMessage}; use seed::app::orders::OrdersContainer; use seed::prelude::*; use seed::{log, window}; use seed_router::Router; use std::collections::VecDeque; pub type AppOrders = OrdersContainer>>; /// Delays between successive attempts to reconnect in case the socket breaks. In seconds. const TIMEOUT_CONNECT_DELAYS: &[u32] = &[2, 5, 10, 10, 10, 20, 30, 60, 120, 300]; pub struct Model { page: Pages, send_queue: VecDeque, socket: WebSocket, ws_url: String, timeout_count: usize, } #[derive(Router)] pub enum Pages { #[page("404", NotFound)] NotFound(page::not_found::Model), #[page("info", Info)] Info(page::info::Model), #[page("lights", Lights)] Lights(page::lights::Model), } #[derive(Debug)] pub enum PageMsg { NotFound(page::not_found::Msg), Info(page::info::Msg), Lights(page::lights::Msg), } #[derive(Debug)] pub enum Msg { Page(PageMsg), SendMessage(ClientMessage), FlushMessageQueue, // Global Connect, SocketOpened(), SocketClosed(CloseEvent), SocketError(), SocketMessage(WebSocketMessage), } pub fn init(url: Url, orders: &mut impl Orders) -> Model { orders.subscribe(Msg::SendMessage); let location = window().location(); let host = location.host().expect("Failed to get hostname"); let ws_protocol = match location.protocol().ok().as_deref() { Some("http:") => "ws", _ => "wss", }; let ws_url = format!("{ws_protocol}://{host}/api/ws"); Model { page: Pages::from_url(url, &mut orders.proxy(Msg::Page)) .unwrap_or(Pages::NotFound(Default::default())), send_queue: Default::default(), socket: open_socket(&ws_url, orders), ws_url, timeout_count: 0, } } fn open_socket(url: &str, orders: &mut impl Orders) -> WebSocket { WebSocket::builder(url, orders) .on_open(Msg::SocketOpened) .on_close(Msg::SocketClosed) .on_error(Msg::SocketError) .on_message(Msg::SocketMessage) .build_and_open() .expect("failed to open websocket") } pub fn update(msg: Msg, model: &mut Model, orders: &mut AppOrders) { #[cfg(debug_assertions)] log!(format!("{msg:?}")); match msg { Msg::Page(msg) => model.page.update(msg, &mut orders.proxy(Msg::Page)), Msg::FlushMessageQueue => { while let Some(message) = model.send_queue.pop_front() { let serialized = ron::to_string(&message).unwrap(); if let Err(e) = model.socket.send_text(serialized) { model.send_queue.push_front(message); log!(e); return; } } } Msg::Connect => { model.socket = open_socket(&model.ws_url, orders); } Msg::SendMessage(message) => { model.send_queue.push_back(message); orders.send_msg(Msg::FlushMessageQueue); } Msg::SocketOpened() => { model.timeout_count = 0; orders.send_msg(Msg::FlushMessageQueue); } Msg::SocketClosed(_event) => { let timeout_sec = TIMEOUT_CONNECT_DELAYS[model.timeout_count]; let timeout_ms = timeout_sec * 1000; orders.perform_cmd(cmds::timeout(timeout_ms, || Msg::Connect)); log!(format!( "Socket closed, trying to reconnect in {timeout_sec} seconds" )); model.timeout_count = TIMEOUT_CONNECT_DELAYS.len().min(model.timeout_count + 1); } Msg::SocketError() => {} Msg::SocketMessage(message) => { if let Err(e) = handle_ws_msg(message, orders) { log!(e); } } } } fn handle_ws_msg(message: WebSocketMessage, orders: &mut impl Orders) -> anyhow::Result<()> { let message = message.text().map_err(|e| anyhow::format_err!("{e:?}"))?; let message: ServerMessage = ron::from_str(&message)?; orders.notify(message); Ok(()) } pub fn view(model: &Model) -> Vec> { vec![model.page.view().map_msg(Msg::Page)] //match &model.page { // Pages::NotFound => vec![h1!["Not Found"]], // Pages::InfoScreen => vec![div![C![C.info_box], raw![&model.info_page]]], // Pages::Lights(page) => vec![], //} }