emscripten_functions/
websocket.rs

1//! Select methods (with rust-native parameter and return value types) from the emscripten [`websocket.h`] header file, and helper types for them.
2//!
3//! For this module's functions to work, the `-lwebsocket` flag needs to be passed to the linker by your application.
4//! This can be accomplished e.g. by adding `println!("cargo:rustc-link-arg=-lwebsocket");` to the `main` function in `build.rs`.
5//!
6//! [`websocket.h`]: https://github.com/emscripten-core/emscripten/blob/main/system/include/emscripten/websocket.h
7
8use emscripten_functions_sys::websocket::{
9    pthread_t, EmscriptenWebSocketCloseEvent, EmscriptenWebSocketErrorEvent,
10    EmscriptenWebSocketMessageEvent, EmscriptenWebSocketOpenEvent, __pthread,
11    emscripten_websocket_close, emscripten_websocket_delete,
12    emscripten_websocket_get_buffered_amount, emscripten_websocket_get_protocol,
13    emscripten_websocket_get_protocol_length, emscripten_websocket_get_url,
14    emscripten_websocket_get_url_length, emscripten_websocket_is_supported,
15    emscripten_websocket_new, emscripten_websocket_send_binary,
16    emscripten_websocket_send_utf8_text, emscripten_websocket_set_onclose_callback_on_thread,
17    emscripten_websocket_set_onerror_callback_on_thread,
18    emscripten_websocket_set_onmessage_callback_on_thread,
19    emscripten_websocket_set_onopen_callback_on_thread, EmscriptenWebSocketCreateAttributes,
20    EMSCRIPTEN_RESULT_SUCCESS,
21};
22use std::ffi::CString;
23
24pub const EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD: pthread_t = 0x2 as *mut __pthread;
25
26#[derive(Clone, Copy, PartialEq, Eq)]
27pub enum WebSocketState {
28    Connecting,
29    Opened,
30    Closing,
31    Closed,
32    Error,
33}
34
35pub enum WebSocketData {
36    Text(String),
37    RawBuffer(Vec<u8>),
38}
39
40pub struct WebSocket {
41    id: i32,
42    state: WebSocketState,
43
44    open_cb: Option<fn(&mut Self)>,
45    error_cb: Option<fn(&mut Self)>,
46    close_cb: Option<fn(&mut Self)>,
47    message_cb: Option<fn(&mut Self, WebSocketData)>,
48}
49
50unsafe extern "C" fn on_open_callback(
51    _event_type: ::std::os::raw::c_int,
52    _websocket_event: *const EmscriptenWebSocketOpenEvent,
53    user_data: *mut ::std::os::raw::c_void,
54) -> bool {
55    let ws: &mut WebSocket = &mut *(user_data as *mut WebSocket);
56    ws.state = WebSocketState::Opened;
57
58    if let Some(fn_cb) = ws.open_cb {
59        (fn_cb)(ws);
60    }
61
62    true
63}
64
65unsafe extern "C" fn on_error_callback(
66    _event_type: ::std::os::raw::c_int,
67    _websocket_event: *const EmscriptenWebSocketErrorEvent,
68    user_data: *mut ::std::os::raw::c_void,
69) -> bool {
70    let ws: &mut WebSocket = &mut *(user_data as *mut WebSocket);
71    ws.state = WebSocketState::Error;
72    ws.clear_internal_callback();
73
74    if let Some(fn_cb) = ws.error_cb {
75        (fn_cb)(ws);
76    }
77
78    true
79}
80
81unsafe extern "C" fn on_close_callback(
82    _event_type: ::std::os::raw::c_int,
83    _websocket_event: *const EmscriptenWebSocketCloseEvent,
84    user_data: *mut ::std::os::raw::c_void,
85) -> bool {
86    let ws: &mut WebSocket = &mut *(user_data as *mut WebSocket);
87    ws.state = WebSocketState::Closed;
88    ws.clear_internal_callback();
89
90    if let Some(fn_cb) = ws.close_cb {
91        (fn_cb)(ws);
92    }
93
94    true
95}
96
97unsafe extern "C" fn on_message_callback(
98    _event_type: ::std::os::raw::c_int,
99    websocket_event: *const EmscriptenWebSocketMessageEvent,
100    user_data: *mut ::std::os::raw::c_void,
101) -> bool {
102    let ws: &mut WebSocket = &mut *(user_data as *mut WebSocket);
103    if ws.message_cb.is_none() {
104        return true;
105    }
106
107    let fn_cb = ws.message_cb.unwrap();
108    if (*websocket_event).isText {
109        let tmp_vec = Vec::from_raw_parts(
110            (*websocket_event).data,
111            (*websocket_event).numBytes as usize,
112            (*websocket_event).numBytes as usize,
113        );
114        (fn_cb)(ws, WebSocketData::Text(String::from_utf8(tmp_vec).unwrap()));
115    } else {
116        let tmp_vec = Vec::from_raw_parts(
117            (*websocket_event).data,
118            (*websocket_event).numBytes as usize,
119            (*websocket_event).numBytes as usize,
120        );
121        (fn_cb)(ws, WebSocketData::RawBuffer(tmp_vec));
122    }
123
124    true
125}
126
127impl WebSocket {
128    /// Create a new WebSocket handler object. One object should be created by socket.
129    ///
130    /// # Examples
131    /// ```rust
132    /// let mut ws = WebSocket::new().unwrap();
133    /// ws.connect("wss://echo.websocket.org/");
134    /// ```
135    pub fn new() -> Option<WebSocket> {
136        if unsafe { emscripten_websocket_is_supported() } {
137            Some(WebSocket {
138                id: 0,
139                state: WebSocketState::Closed,
140                open_cb: None,
141                error_cb: None,
142                close_cb: None,
143                message_cb: None,
144            })
145        } else {
146            None
147        }
148    }
149
150    /// The connect method open a socket connection to the provided URL. This method should only be
151    /// call if the current state of the handler is Closed, else the function will do nothing and
152    /// return false.
153    ///
154    /// # Examples
155    /// ```rust
156    /// let mut ws = WebSocket::new().unwrap();
157    /// ws.connect("wss://echo.websocket.org/");
158    /// ```
159    pub fn connect<T>(&mut self, url: T) -> bool
160    where
161        T: AsRef<str>,
162    {
163        if self.state != WebSocketState::Closed {
164            return false;
165        }
166
167        let url_cstr = CString::new(url.as_ref()).unwrap();
168        let mut create_attr = EmscriptenWebSocketCreateAttributes {
169            url: url_cstr.as_ptr(),
170            protocols: std::ptr::null(),
171            createOnMainThread: true,
172        };
173
174        let socket: i32 = unsafe { emscripten_websocket_new(&mut create_attr) };
175        if socket > 0 {
176            self.id = socket;
177            self.state = WebSocketState::Connecting;
178            self.init_internal_callback();
179
180            true
181        } else {
182            self.id = 0;
183            self.state = WebSocketState::Closed;
184
185            false
186        }
187    }
188
189    /// Get id field of websocket.
190    pub fn get_id(&self) -> i32 {
191        self.id
192    }
193    /// Get current websocket state.
194    pub fn get_state(&self) -> WebSocketState {
195        self.state
196    }
197    /// Get the WebSocket.bufferedAmount field into bufferedAmount.
198    pub fn get_buffered_amount(&self) -> usize {
199        let mut size: usize = 0;
200        unsafe {
201            emscripten_websocket_get_buffered_amount(self.id, &mut size);
202        }
203
204        size
205    }
206    /// Get the websocket URL field.
207    pub fn get_url(&self) -> String {
208        let mut size: i32 = 0;
209        unsafe {
210            emscripten_websocket_get_url_length(self.id, &mut size);
211            let mut url_raw = vec![0u8; size as usize];
212            let url_raw_ptr = url_raw.as_mut_ptr() as *mut i8;
213            emscripten_websocket_get_url(self.id, url_raw_ptr, 26);
214            String::from_utf8(url_raw).unwrap()
215        }
216    }
217    /// Get the websocket protocol field.
218    pub fn get_protocol(&self) -> String {
219        let mut size: i32 = 0;
220        unsafe {
221            emscripten_websocket_get_protocol_length(self.id, &mut size);
222            let mut protocol_raw = vec![0u8; size as usize];
223            let protocol_raw_ptr = protocol_raw.as_mut_ptr() as *mut i8;
224            emscripten_websocket_get_protocol(self.id, protocol_raw_ptr, 26);
225            String::from_utf8(protocol_raw).unwrap()
226        }
227    }
228
229    /// Set all internal callbacks for current object.
230    fn init_internal_callback(&mut self) {
231        unsafe {
232            emscripten_websocket_set_onopen_callback_on_thread(
233                self.id,
234                self as *mut _ as *mut std::os::raw::c_void,
235                Some(on_open_callback),
236                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
237            );
238            emscripten_websocket_set_onerror_callback_on_thread(
239                self.id,
240                self as *mut _ as *mut std::os::raw::c_void,
241                Some(on_error_callback),
242                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
243            );
244            emscripten_websocket_set_onclose_callback_on_thread(
245                self.id,
246                self as *mut _ as *mut std::os::raw::c_void,
247                Some(on_close_callback),
248                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
249            );
250            emscripten_websocket_set_onmessage_callback_on_thread(
251                self.id,
252                self as *mut _ as *mut std::os::raw::c_void,
253                Some(on_message_callback),
254                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
255            );
256        }
257    }
258    /// Clear all internal callbacks for current object.
259    pub fn clear_internal_callback(&mut self) {
260        unsafe {
261            emscripten_websocket_set_onopen_callback_on_thread(
262                self.id,
263                std::ptr::null_mut::<std::os::raw::c_void>(),
264                None,
265                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
266            );
267            emscripten_websocket_set_onerror_callback_on_thread(
268                self.id,
269                std::ptr::null_mut::<std::os::raw::c_void>(),
270                None,
271                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
272            );
273            emscripten_websocket_set_onclose_callback_on_thread(
274                self.id,
275                std::ptr::null_mut::<std::os::raw::c_void>(),
276                None,
277                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
278            );
279            emscripten_websocket_set_onmessage_callback_on_thread(
280                self.id,
281                std::ptr::null_mut::<std::os::raw::c_void>(),
282                None,
283                EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD,
284            );
285        }
286    }
287
288    /// Set on open callback for current object.
289    pub fn set_open_callback(&mut self, cb: Option<fn(&mut Self)>) {
290        self.open_cb = cb;
291    }
292    /// Set on error callback for current object.
293    pub fn set_error_callback(&mut self, cb: Option<fn(&mut Self)>) {
294        self.error_cb = cb;
295    }
296    /// Set on close callback for current object.
297    pub fn set_close_callback(&mut self, cb: Option<fn(&mut Self)>) {
298        self.close_cb = cb;
299    }
300    /// Set on message callback for current object.
301    pub fn set_message_callback(&mut self, cb: Option<fn(&mut Self, WebSocketData)>) {
302        self.message_cb = cb;
303    }
304
305    /// Send UTF-8 formatted string through websocket. The state of current socket should be
306    /// opened else the function will return false.
307    ///
308    /// # Examples
309    /// ```rust
310    /// let mut ws = WebSocket::new().unwrap();
311    /// ws.connect("wss://echo.websocket.org/");
312    /// ws.send_utf8_text("foo");
313    /// ```
314    pub fn send_utf8_text<T>(&mut self, string: T) -> bool
315    where
316        T: AsRef<str>,
317    {
318        let text_cstr = CString::new(string.as_ref()).unwrap();
319        unsafe {
320            let result = emscripten_websocket_send_utf8_text(self.id, text_cstr.as_ptr());
321            (result as u32) == EMSCRIPTEN_RESULT_SUCCESS
322        }
323    }
324
325    /// Send UTF-8 raw data through websocket. The state of current socket should be
326    /// opened else the function will return false.
327    ///
328    /// # Examples
329    /// ```rust
330    /// let mut ws = WebSocket::new().unwrap();
331    /// ws.connect("wss://echo.websocket.org/");
332    /// ws.send_binary([42, 69]);
333    /// ```
334    pub fn send_binary(&mut self, data: &mut [u8]) -> bool {
335        unsafe {
336            let result = emscripten_websocket_send_binary(
337                self.id,
338                data.as_mut_ptr() as *mut std::os::raw::c_void,
339                data.len() as u32,
340            );
341
342            (result as u32) == EMSCRIPTEN_RESULT_SUCCESS
343        }
344    }
345
346    /// Close the current websocket. This function will clear all
347    /// callbacks and trigger on close callback.
348    ///
349    /// # Examples
350    /// ```rust
351    /// let mut ws = WebSocket::new().unwrap();
352    /// ws.connect("wss://echo.websocket.org/");
353    /// ws.close(1000, "over");
354    /// ```
355    pub fn close<T>(&mut self, code: u16, reason: T)
356    where
357        T: AsRef<str>,
358    {
359        self.state = WebSocketState::Closing;
360        let reason_cstr = CString::new(reason.as_ref()).unwrap();
361        unsafe {
362            emscripten_websocket_close(self.id, code, reason_cstr.as_ptr());
363        }
364    }
365}
366
367impl Drop for WebSocket {
368    fn drop(&mut self) {
369        unsafe {
370            emscripten_websocket_delete(self.id);
371        }
372    }
373}