1use std::fmt;
3
4use gloo_events::EventListener;
5use std::borrow::Cow;
6use wasm_bindgen::JsCast;
7use web_sys::{Event, EventSource, MessageEvent};
8use yew::callback::Callback;
9use yew::format::{FormatError, Text};
10use yew::services::Task;
11
12#[derive(PartialEq, Debug)]
14pub enum EventSourceStatus {
15 Open,
17 Error,
19}
20
21#[derive(PartialEq, Debug)]
25pub enum ReadyState {
26 Connecting,
28 Open,
30 Closed,
32}
33
34pub struct EventSourceTask {
36 event_source: EventSource,
37 _notification: Callback<EventSourceStatus>,
39 listeners: Vec<EventListener>,
40}
41
42impl EventSourceTask {
43 #![allow(clippy::unnecessary_wraps)]
44 fn new(
45 event_source: EventSource,
46 notification: Callback<EventSourceStatus>,
47 ) -> Result<EventSourceTask, &'static str> {
48 Ok(EventSourceTask {
49 event_source,
50 _notification: notification,
51 listeners: vec![],
52 })
53 }
54
55 fn add_unwrapped_event_listener<S, F>(&mut self, event_type: S, callback: F)
56 where
57 S: Into<Cow<'static, str>>,
58 F: FnMut(&Event) + 'static,
59 {
60 self.listeners
61 .push(EventListener::new(&self.event_source, event_type, callback));
62 }
63
64 pub fn add_event_listener<S, OUT: 'static>(&mut self, event_type: S, callback: Callback<OUT>)
69 where
70 S: Into<Cow<'static, str>>,
71 OUT: From<Text>,
72 {
73 let wrapped_callback = move |event: &Event| {
76 let event = event.dyn_ref::<MessageEvent>().unwrap();
77 let text = event.data().as_string();
78
79 let data = if let Some(text) = text {
80 Ok(text)
81 } else {
82 Err(FormatError::ReceivedBinaryForText.into())
83 };
84
85 let out = OUT::from(data);
86 callback.emit(out);
87 };
88 self.add_unwrapped_event_listener(event_type, wrapped_callback);
89 }
90
91 pub fn ready_state(&self) -> ReadyState {
93 match self.event_source.ready_state() {
94 web_sys::EventSource::CONNECTING => ReadyState::Connecting,
95 web_sys::EventSource::OPEN => ReadyState::Open,
96 web_sys::EventSource::CLOSED => ReadyState::Closed,
97 _ => panic!("unexpected ready state"),
98 }
99 }
100}
101
102impl fmt::Debug for EventSourceTask {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str("EventSourceTask")
105 }
106}
107
108#[derive(Default, Debug)]
110pub struct EventSourceService {}
111
112impl EventSourceService {
113 pub fn new() -> Self {
115 Self {}
116 }
117
118 pub fn connect(
123 &mut self,
124 url: &str,
125 notification: Callback<EventSourceStatus>,
126 ) -> Result<EventSourceTask, &str> {
127 let event_source = EventSource::new(url);
128 if event_source.is_err() {
129 return Err("Failed to created event source with given URL");
130 }
131
132 let event_source = event_source.map_err(|_| "failed to build event source")?;
133
134 let notify = notification.clone();
135 let listener_open = move |_: &Event| {
136 notify.emit(EventSourceStatus::Open);
137 };
138 let notify = notification.clone();
139 let listener_error = move |_: &Event| {
140 notify.emit(EventSourceStatus::Error);
141 };
142
143 let mut result = EventSourceTask::new(event_source, notification)?;
144 result.add_unwrapped_event_listener("open", listener_open);
145 result.add_unwrapped_event_listener("error", listener_error);
146 Ok(result)
147 }
148}
149
150impl Task for EventSourceTask {
151 fn is_active(&self) -> bool {
152 self.ready_state() == ReadyState::Open
153 }
154}
155
156impl Drop for EventSourceTask {
157 fn drop(&mut self) {
158 if self.is_active() {
159 self.event_source.close()
160 }
161 }
162}