mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-27 01:02:45 +01:00
Merge pull request #356 from euzu/feature/websocket_reconnect
Reconnect on WebSocket disconnect
This commit is contained in:
+2
-1
@@ -6,7 +6,8 @@
|
||||
- Using tmdbid for VOD from get_vod_streams if available, which makes using resolve_vod for STRM generation not necessary.
|
||||
- File length problem with strm generation fixed.
|
||||
- Series name empty paren problem fixed.
|
||||
- Default sort removed
|
||||
- Default sort removed
|
||||
- Reconnect on WebSocket disconnect, WebSocket connection status icon in WEB-UI
|
||||
|
||||
# 3.1.6 (2025-09-01)
|
||||
- EPG Config View added
|
||||
|
||||
@@ -491,5 +491,9 @@
|
||||
{
|
||||
"keys": ["Moon"],
|
||||
"path": "M9.37 5.51c-.18.64-.27 1.31-.27 1.99 0 4.08 3.32 7.4 7.4 7.4.68 0 1.35-.09 1.99-.27C17.45 17.19 14.93 19 12 19c-3.86 0-7-3.14-7-7 0-2.93 1.81-5.45 4.37-6.49M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1"
|
||||
},
|
||||
{
|
||||
"keys": ["WsDisconnected", "Offline"],
|
||||
"path": "M12 2.02c-5.51 0-9.98 4.47-9.98 9.98s4.47 9.98 9.98 9.98 9.98-4.47 9.98-9.98S17.51 2.02 12 2.02m0 17.96c-4.4 0-7.98-3.58-7.98-7.98S7.6 4.02 12 4.02 19.98 7.6 19.98 12 16.4 19.98 12 19.98M12.75 5l-4.5 8.5h3.14V19l4.36-8.5h-3z"
|
||||
}
|
||||
]
|
||||
@@ -71,3 +71,4 @@
|
||||
@forward "components/api_user/api_user_view";
|
||||
@forward "components/keyvalue_editor";
|
||||
@forward "components/loading_screen";
|
||||
@forward "components/websocket_status";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.tp__websocket-status {
|
||||
fill: var(--warn-color);
|
||||
.svg-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use yew::{function_component, html, Callback, Html};
|
||||
use crate::app::components::loading_indicator::BusyIndicator;
|
||||
use crate::app::components::{AppIcon, IconButton, ToastrView};
|
||||
use crate::app::components::{AppIcon, IconButton, ToastrView, WebsocketStatus};
|
||||
use crate::app::components::theme::Theme;
|
||||
use crate::hooks::use_service_context;
|
||||
use crate::provider::DialogProvider;
|
||||
@@ -44,6 +44,7 @@ pub fn ApiUserView() -> Html {
|
||||
}
|
||||
</div>
|
||||
<div class={"tp__app-header-toolbar"}>
|
||||
<WebsocketStatus/>
|
||||
<IconButton name="Theme" icon={if *theme == Theme::Bright {"Moon"} else {"Sun"}} onclick={handle_theme_switch} />
|
||||
<IconButton name="Logout" icon="Logout" onclick={handle_logout} />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::app::components::{AppIcon, DashboardView, IconButton, InputRow, Panel, PlaylistEditorView,
|
||||
PlaylistExplorerView, PlaylistUpdateView, Sidebar, StatsView, ToastrView, UserlistView};
|
||||
use crate::app::components::{AppIcon, DashboardView, IconButton, InputRow, Panel, PlaylistEditorView, PlaylistExplorerView, PlaylistUpdateView, Sidebar, StatsView, ToastrView, UserlistView, WebsocketStatus};
|
||||
use crate::app::context::{ConfigContext, PlaylistContext, StatusContext};
|
||||
use crate::hooks::{use_server_status, use_service_context};
|
||||
use crate::model::{EventMessage, ViewType};
|
||||
@@ -160,6 +159,7 @@ pub fn Home() -> Html {
|
||||
}
|
||||
</div>
|
||||
<div class={"tp__app-header-toolbar"}>
|
||||
<WebsocketStatus/>
|
||||
<IconButton name="Theme" icon={if *theme == Theme::Bright {"Moon"} else {"Sun"}} onclick={handle_theme_switch} />
|
||||
<IconButton name="Logout" icon="Logout" onclick={handle_logout} />
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,7 @@ mod cell_value;
|
||||
mod key_value_editor;
|
||||
mod loading_screen;
|
||||
mod theme;
|
||||
mod websocket_status;
|
||||
// pub use self::input::*;
|
||||
// pub use self::menu_item::*;
|
||||
// pub use self::popup_menu::*;
|
||||
@@ -84,5 +85,6 @@ pub use self::role_based_content::*;
|
||||
pub use self::cell_value::*;
|
||||
pub use self::key_value_editor::*;
|
||||
pub use self::loading_screen::*;
|
||||
pub use self::websocket_status::*;
|
||||
//pub use self::number_input::*;
|
||||
//pub use self::date_input::*;
|
||||
//pub use self::date_input::*;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use yew::prelude::*;
|
||||
use crate::app::components::AppIcon;
|
||||
use crate::hooks::use_service_context;
|
||||
use crate::model::EventMessage;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct WebsocketStatusProps {
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn WebsocketStatus(props: &WebsocketStatusProps) -> Html {
|
||||
let status = use_state(|| true);
|
||||
let services = use_service_context();
|
||||
|
||||
{
|
||||
let status_clone = status.clone();
|
||||
use_effect_with((), move |_| {
|
||||
let services_ctx = services.clone();
|
||||
let status_clone = status_clone.clone();
|
||||
let subid = services_ctx.event.subscribe(move |msg| {
|
||||
if let EventMessage::WebSocketStatus(active) = msg {
|
||||
status_clone.set(active);
|
||||
}
|
||||
});
|
||||
move || services_ctx.event.unsubscribe(subid)
|
||||
});
|
||||
}
|
||||
|
||||
if *status {
|
||||
return html! { <></> }
|
||||
}
|
||||
|
||||
html! {
|
||||
<div class="tp__websocket-status">
|
||||
<AppIcon name="WsDisconnected" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,5 @@ pub enum EventMessage {
|
||||
Busy(BusyStatus),
|
||||
PlaylistUpdate(PlaylistUpdateState),
|
||||
PlaylistUpdateProgress(String, String),
|
||||
WebSocketStatus(bool),
|
||||
}
|
||||
@@ -11,6 +11,8 @@ use shared::utils::{concat_path_leading_slash};
|
||||
use crate::model::EventMessage;
|
||||
use crate::services::{get_base_href, get_token, EventService, StatusService};
|
||||
|
||||
const WS_RECONNECT_MS:i32 = 2000;
|
||||
|
||||
pub struct WebSocketService {
|
||||
connected: Rc<AtomicBool>,
|
||||
ws: Rc<RefCell<Option<WebSocket>>>,
|
||||
@@ -30,6 +32,16 @@ impl WebSocketService {
|
||||
ws_path: concat_path_leading_slash(&base_href, "ws"),
|
||||
}
|
||||
}
|
||||
/// Helper function to allow cloning the service into JS closures for reconnect
|
||||
fn clone_for_reconnect(&self) -> Self {
|
||||
Self {
|
||||
connected: self.connected.clone(),
|
||||
ws: self.ws.clone(),
|
||||
status_service: self.status_service.clone(),
|
||||
event_service: self.event_service.clone(),
|
||||
ws_path: self.ws_path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect_ws(&self) {
|
||||
if self.connected.load(Ordering::SeqCst) {
|
||||
@@ -102,31 +114,67 @@ impl WebSocketService {
|
||||
|
||||
let ws_open_clone = Rc::clone(&ws_clone);
|
||||
let connected_clone = self.connected.clone();
|
||||
let event_service_clone = Rc::clone(&self.event_service);
|
||||
// onopen
|
||||
let onopen_callback = Closure::<dyn FnMut(_)>::wrap(Box::new(move |_event: Event| {
|
||||
trace!("WebSocket connection opened.");
|
||||
connected_clone.store(true, Ordering::SeqCst);
|
||||
Self::try_send_message(ws_open_clone.borrow().as_ref(), ProtocolMessage::Version(PROTOCOL_VERSION));
|
||||
event_service_clone.broadcast(EventMessage::WebSocketStatus(true));
|
||||
}));
|
||||
socket.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
||||
onopen_callback.forget();
|
||||
|
||||
let ws_close_rc = self.ws.clone();
|
||||
let connected_clone = self.connected.clone();
|
||||
let ws_service_reconnect = Rc::new(self.clone_for_reconnect());
|
||||
let event_service_clone = Rc::clone(&self.event_service);
|
||||
let onclose_callback = Closure::<dyn FnMut(_)>::wrap(Box::new(move |e: CloseEvent| {
|
||||
debug!("Websocket closed, Code: {}, Reason: {}, WasClean: {}", e.code(), e.reason(), e.was_clean());
|
||||
*ws_close_rc.borrow_mut() = None;
|
||||
connected_clone.store(false, Ordering::SeqCst);
|
||||
event_service_clone.broadcast(EventMessage::WebSocketStatus(false));
|
||||
|
||||
// schedule reconnect after 3 seconds
|
||||
let ws_service_inner = ws_service_reconnect.clone();
|
||||
let timeout_cb = Closure::once_into_js(Box::new(move || {
|
||||
ws_service_inner.connect_ws();
|
||||
}) as Box<dyn FnOnce()>);
|
||||
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
timeout_cb.unchecked_ref(),
|
||||
WS_RECONNECT_MS,
|
||||
)
|
||||
.unwrap();
|
||||
}));
|
||||
socket.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||
onclose_callback.forget();
|
||||
|
||||
let connected_clone = self.connected.clone();
|
||||
let ws_service_reconnect = Rc::new(self.clone_for_reconnect());
|
||||
let event_service_clone = Rc::clone(&self.event_service);
|
||||
// onerror
|
||||
let onerror_callback = Closure::<dyn FnMut(_)>::wrap(Box::new(move |e: ErrorEvent| {
|
||||
error!("WebSocket error");
|
||||
connected_clone.store(false, Ordering::SeqCst);
|
||||
event_service_clone.broadcast(EventMessage::WebSocketStatus(false));
|
||||
web_sys::console::error_1(&e);
|
||||
|
||||
// schedule reconnect after 3 seconds
|
||||
let ws_service_inner = ws_service_reconnect.clone();
|
||||
let timeout_cb = Closure::once_into_js(Box::new(move || {
|
||||
ws_service_inner.connect_ws();
|
||||
}) as Box<dyn FnOnce()>);
|
||||
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
timeout_cb.unchecked_ref(),
|
||||
WS_RECONNECT_MS,
|
||||
)
|
||||
.unwrap();
|
||||
}));
|
||||
socket.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
||||
onerror_callback.forget();
|
||||
|
||||
Reference in New Issue
Block a user