Merge pull request #7 from sectore/use_websocket

Add `use_websocket`
This commit is contained in:
Marc-Stefan Cassola 2023-07-13 11:19:52 +01:00 committed by GitHub
commit ddbc1c1db5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 938 additions and 2 deletions

View file

@ -27,6 +27,7 @@ lazy_static = "1"
version = "0.3" version = "0.3"
features = [ features = [
"CssStyleDeclaration", "CssStyleDeclaration",
"CloseEvent",
"CustomEvent", "CustomEvent",
"CustomEventInit", "CustomEventInit",
"DomRectReadOnly", "DomRectReadOnly",
@ -63,6 +64,7 @@ features = [
docs = [] docs = []
math = ["num"] math = ["num"]
storage = ["serde", "serde_json", "web-sys/StorageEvent"] storage = ["serde", "serde_json", "web-sys/StorageEvent"]
websocket = ["web-sys/BinaryType", "web-sys/WebSocket"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -57,7 +57,7 @@ cargo test --all-features
First you need to install First you need to install
```shell ```shell
cargo install mdbook-cmdrun trunk cargo install mdbook mdbook-cmdrun trunk
``` ```
To build the book go in your terminal into the docs/book folder To build the book go in your terminal into the docs/book folder

View file

@ -11,6 +11,10 @@
- [use_session_storage](storage/use_session_storage.md) - [use_session_storage](storage/use_session_storage.md)
- [use_storage](storage/use_storage.md) - [use_storage](storage/use_storage.md)
# @WebSocket
- [use_websocket](websocket/use_websocket.md)
# Elements # Elements
- [use_active_element](elements/use_active_element.md) - [use_active_element](elements/use_active_element.md)
@ -68,4 +72,4 @@
- [use_floor](math/use_floor.md) - [use_floor](math/use_floor.md)
- [use_max](math/use_max.md) - [use_max](math/use_max.md)
- [use_min](math/use_min.md) - [use_min](math/use_min.md)
- [use_round](math/use_round.md) - [use_round](math/use_round.md)

View file

@ -0,0 +1,3 @@
# use_websocket
<!-- cmdrun python3 ../extract_doc_comment.py websocket/use_websocket websocket -->

View file

@ -29,6 +29,7 @@ members = [
"use_scroll", "use_scroll",
"use_storage", "use_storage",
"use_throttle_fn", "use_throttle_fn",
"use_websocket",
"use_window_focus", "use_window_focus",
"use_window_scroll", "use_window_scroll",
"watch_debounced", "watch_debounced",

View file

@ -0,0 +1,16 @@
[package]
name = "use_websocket"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { version = "0.4", features = ["nightly", "csr"] }
console_error_panic_hook = "0.1"
console_log = "1"
log = "0.4"
leptos-use = { path = "../..", features = ["docs", "websocket"] }
web-sys = "0.3"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"

View file

@ -0,0 +1,22 @@
A simple example for `use_websocket`.
If you don't have it installed already, install [Trunk](https://trunkrs.dev/) and [Tailwind](https://tailwindcss.com/docs/installation)
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
```bash
cargo install trunk
rustup toolchain install nightly
rustup target add wasm32-unknown-unknown
```
Then, open two terminals. In the first one, run:
```
npx tailwindcss -i ./input.css -o ./style/output.css --watch
```
In the second one, run:
```bash
trunk serve --open
```

View file

@ -0,0 +1,2 @@
[build]
public_url = "/demo/"

View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="css" href="style/output.css">
</head>
<body></body>
</html>

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -0,0 +1,211 @@
use leptos::*;
use leptos_use::docs::demo_or_body;
use leptos_use::websocket::*;
use web_sys::{CloseEvent, Event};
#[component]
fn Demo(cx: Scope) -> impl IntoView {
let (history, set_history) = create_signal(cx, vec![]);
fn update_history(&history: &WriteSignal<Vec<String>>, message: String) {
let _ = &history.update(|history: &mut Vec<_>| history.push(message));
}
// ----------------------------
// use_websocket
// ----------------------------
let UseWebsocketReturn {
ready_state,
message,
message_bytes,
send,
send_bytes,
open,
close,
..
} = use_websocket(cx, "wss://echo.websocket.events/".to_string());
let send_message = move |_| {
let m = "Hello, world!".to_string();
send(m.clone());
set_history.update(|history: &mut Vec<_>| history.push(format! {"[send]: {:?}", m}));
};
let send_byte_message = move |_| {
let m = b"Hello, world!\r\n".to_vec();
send_bytes(m.clone());
set_history.update(|history: &mut Vec<_>| history.push(format! {"[send_bytes]: {:?}", m}));
};
let status = move || ready_state().to_string();
let connected = move || ready_state.get() == UseWebSocketReadyState::Open;
let open_connection = move |_| {
open();
};
let close_connection = move |_| {
close();
};
create_effect(cx, move |_| {
if let Some(m) = message.get() {
update_history(&set_history, format! {"[message]: {:?}", m});
};
});
create_effect(cx, move |_| {
if let Some(m) = message_bytes.get() {
update_history(&set_history, format! {"[message_bytes]: {:?}", m});
};
});
// ----------------------------
// use_websocket_with_options
// ----------------------------
let (history2, set_history2) = create_signal(cx, vec![]);
let on_open_callback = move |e: Event| {
set_history2.update(|history: &mut Vec<_>| {
history.push(format! {"[onopen]: event {:?}", e.type_()})
});
};
let on_close_callback = move |e: CloseEvent| {
set_history2.update(|history: &mut Vec<_>| {
history.push(format! {"[onclose]: event {:?}", e.type_()})
});
};
let on_error_callback = move |e: Event| {
set_history2.update(|history: &mut Vec<_>| {
history.push(format! {"[onerror]: event {:?}", e.type_()})
});
};
let on_message_callback = move |m: String| {
set_history2.update(|history: &mut Vec<_>| history.push(format! {"[onmessage]: {:?}", m}));
};
let on_message_bytes_callback = move |m: Vec<u8>| {
set_history2
.update(|history: &mut Vec<_>| history.push(format! {"[onmessage_bytes]: {:?}", m}));
};
let UseWebsocketReturn {
ready_state: ready_state2,
send: send2,
send_bytes: send_bytes2,
open: open2,
close: close2,
message: message2,
message_bytes: message_bytes2,
..
} = use_websocket_with_options(
cx,
"wss://echo.websocket.events/".to_string(),
UseWebSocketOptions::default()
.manual(true)
.on_open(on_open_callback.clone())
.on_close(on_close_callback.clone())
.on_error(on_error_callback.clone())
.on_message(on_message_callback.clone())
.on_message_bytes(on_message_bytes_callback.clone()),
);
let open_connection2 = move |_| {
open2();
};
let close_connection2 = move |_| {
close2();
};
let send_message2 = move |_| {
let message = "Hello, use_leptos!".to_string();
send2(message.clone());
update_history(&set_history2, format! {"[send]: {:?}", message});
};
let send_byte_message2 = move |_| {
let m = b"Hello, world!\r\n".to_vec();
send_bytes2(m.clone());
update_history(&set_history2, format! {"[send_bytes]: {:?}", m});
};
let status2 = move || ready_state2.get().to_string();
create_effect(cx, move |_| {
if let Some(m) = message2.get() {
update_history(&set_history2, format! {"[message]: {:?}", m});
};
});
create_effect(cx, move |_| {
if let Some(m) = message_bytes2.get() {
update_history(&set_history2, format! {"[message_bytes]: {:?}", m});
};
});
let connected2 = move || ready_state2.get() == UseWebSocketReadyState::Open;
view! { cx,
<div class="container">
<div class="flex flex-col lg:flex-row gap-4">
<div class="w-full lg:w-1/2">
<h1 class="text-xl lg:text-4xl mb-2">"use_websocket"</h1>
<p>"status: " {status}</p>
<button on:click=send_message disabled=move || !connected()>"Send"</button>
<button on:click=send_byte_message disabled=move || !connected()>"Send bytes"</button>
<button on:click=open_connection disabled=connected>"Open"</button>
<button on:click=close_connection disabled=move || !connected()>"Close"</button>
<div class="flex items-center">
<h3 class="text-2xl mr-2">"History"</h3>
<button on:click=move |_| set_history(vec![]) disabled=move || history.get().len() <= 0>"Clear"</button>
</div>
<For
each=move || history.get().into_iter().enumerate()
key=|(index, _)| *index
view=move |cx, (_, message)| {
view! {cx, <div>{message}</div> }
}
/>
</div>
<div class="w-full lg:w-1/2">
<h1 class="text-xl lg:text-4xl mb-2">"use_websocket_with_options"</h1>
<p>"status: " {status2}</p>
<button on:click=open_connection2 disabled={connected2}>"Connect"</button>
<button on:click=close_connection2 disabled=move || !connected2()>"Close"</button>
<button on:click=send_message2 disabled=move || !connected2()>"Send"</button>
<button on:click=send_byte_message2 disabled=move || !connected2()>"Send Bytes"</button>
<div class="flex items-center">
<h3 class="text-2xl mr-2">"History"</h3>
<button on:click=move |_| set_history2(vec![]) disabled=move || history2.get().len() <= 0>"Clear"</button>
</div>
<ul>
<For
each=move || history2.get().into_iter().enumerate()
key=|(index, _)| *index
view=move |cx, (_, message)| {
view! {cx, <li>{message}</li> }
}
/>
</ul>
</div>
</div>
</div>
}
}
fn main() {
_ = console_log::init_with_level(log::Level::Info);
console_error_panic_hook::set_once();
mount_to(demo_or_body(), |cx| {
view! { cx, <Demo /> }
})
}

View file

@ -0,0 +1,210 @@
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia:
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia:
}
.container {
width: 100%
}
@media (min-width: 640px) {
.container {
max-width: 640px
}
}
@media (min-width: 768px) {
.container {
max-width: 768px
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px
}
}
.static {
position: static
}
.mb-2 {
margin-bottom: 0.5rem
}
.mr-2 {
margin-right: 0.5rem
}
.flex {
display: flex
}
.w-full {
width: 100%
}
.flex-col {
flex-direction: column
}
.items-center {
align-items: center
}
.gap-4 {
gap: 1rem
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem
}
.text-\[--brand-color\] {
color: var(--brand-color)
}
.text-green-600 {
--tw-text-opacity: 1;
color: rgb(22 163 74 / var(--tw-text-opacity))
}
.opacity-75 {
opacity: 0.75
}
@media (prefers-color-scheme: dark) {
.dark\:text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity))
}
}
@media (min-width: 1024px) {
.lg\:w-1\/2 {
width: 50%
}
.lg\:flex-row {
flex-direction: row
}
.lg\:text-4xl {
font-size: 2.25rem;
line-height: 2.5rem
}
}

View file

@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: {
files: ["*.html", "./src/**/*.rs", "../../src/docs/**/*.rs"],
},
theme: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View file

@ -9,6 +9,8 @@ pub mod math;
#[cfg(feature = "storage")] #[cfg(feature = "storage")]
pub mod storage; pub mod storage;
pub mod utils; pub mod utils;
#[cfg(feature = "websocket")]
pub mod websocket;
#[cfg(web_sys_unstable_apis)] #[cfg(web_sys_unstable_apis)]
mod use_element_size; mod use_element_size;

3
src/websocket/mod.rs Normal file
View file

@ -0,0 +1,3 @@
mod use_websocket;
pub use use_websocket::*;

View file

@ -0,0 +1,435 @@
use leptos::{leptos_dom::helpers::TimeoutHandle, *};
use core::fmt;
use std::rc::Rc;
use std::time::Duration;
use default_struct_builder::DefaultBuilder;
use js_sys::Array;
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
use crate::utils::CloneableFnWithArg;
/// Creating and managing a [Websocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) connection.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_websocket)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::websocket::*;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let UseWebsocketReturn {
/// ready_state,
/// message,
/// message_bytes,
/// send,
/// send_bytes,
/// open,
/// close,
/// ..
/// } = use_websocket(cx, "wss://echo.websocket.events/".to_string());
///
/// let send_message = move |_| {
/// let m = "Hello, world!".to_string();
/// send(m.clone());
/// };
///
/// let send_byte_message = move |_| {
/// let m = b"Hello, world!\r\n".to_vec();
/// send_bytes(m.clone());
/// };
///
/// let status = move || ready_state().to_string();
///
/// let connected = move || ready_state.get() == UseWebSocketReadyState::Open;
///
/// let open_connection = move |_| {
/// open();
/// };
///
/// let close_connection = move |_| {
/// close();
/// };
///
/// view! { cx,
/// <div>
/// <p>"status: " {status}</p>
/// button on:click=send_message disabled=move || !connected()>"Send"</button>
/// <button on:click=send_byte_message disabled=move || !connected()>"Send bytes"</button>
/// <button on:click=open_connection disabled=connected>"Open"</button>
/// <button on:click=close_connection disabled=move || !connected()>"Close"</button>
/// <p>"Receive message: " {format! {"{:?}", message}}</p>
/// <p>"Receive byte message: " {format! {"{:?}", message_bytes}}</p>
/// </div>
/// }
/// # }
/// ```
// #[doc(cfg(feature = "websocket"))]
pub fn use_websocket(
cx: Scope,
url: String,
) -> UseWebsocketReturn<
impl Fn() + Clone + 'static,
impl Fn() + Clone + 'static,
impl Fn(String) + Clone + 'static,
impl Fn(Vec<u8>) + Clone + 'static,
> {
use_websocket_with_options(cx, url, UseWebSocketOptions::default())
}
/// Version of [`use_websocket`] that takes `UseWebSocketOptions`. See [`use_websocket`] for how to use.
// #[doc(cfg(feature = "websocket"))]
pub fn use_websocket_with_options(
cx: Scope,
url: String,
options: UseWebSocketOptions,
) -> UseWebsocketReturn<
impl Fn() + Clone + 'static,
impl Fn() + Clone + 'static,
impl Fn(String) + Clone + 'static,
impl Fn(Vec<u8>) + Clone,
> {
let (ready_state, set_ready_state) = create_signal(cx, UseWebSocketReadyState::Closed);
let (message, set_message) = create_signal(cx, None);
let (message_bytes, set_message_bytes) = create_signal(cx, None);
let ws_ref: StoredValue<Option<WebSocket>> = store_value(cx, None);
let on_open_ref = store_value(cx, options.on_open);
let on_message_ref = store_value(cx, options.on_message);
let on_message_bytes_ref = store_value(cx, options.on_message_bytes);
let on_error_ref = store_value(cx, options.on_error);
let on_close_ref = store_value(cx, options.on_close);
let reconnect_limit = options.reconnect_limit.unwrap_or(3);
let reconnect_interval = options.reconnect_interval.unwrap_or(3 * 1000);
let reconnect_timer_ref: StoredValue<Option<TimeoutHandle>> = store_value(cx, None);
let manual = options.manual;
let protocols = options.protocols;
let reconnect_times_ref: StoredValue<u64> = store_value(cx, 0);
let unmounted_ref = store_value(cx, false);
let connect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(cx, None);
let reconnect_ref: StoredValue<Option<Rc<dyn Fn()>>> = store_value(cx, None);
reconnect_ref.set_value({
let ws = ws_ref.get_value();
Some(Rc::new(move || {
if reconnect_times_ref.get_value() < reconnect_limit
&& ws
.clone()
.map_or(false, |ws: WebSocket| ws.ready_state() != WebSocket::OPEN)
{
reconnect_timer_ref.set_value(
set_timeout_with_handle(
move || {
if let Some(connect) = connect_ref.get_value() {
connect();
reconnect_times_ref.update_value(|current| *current += 1);
}
},
Duration::from_millis(reconnect_interval),
)
.ok(),
);
}
}))
});
connect_ref.set_value({
let ws = ws_ref.get_value();
let url = url.clone();
Some(Rc::new(move || {
reconnect_timer_ref.set_value(None);
{
if let Some(web_socket) = &ws {
let _ = web_socket.close();
}
}
let web_socket = {
protocols.as_ref().map_or_else(
|| WebSocket::new(&url).unwrap_throw(),
|protocols| {
let array = protocols
.iter()
.map(|p| JsValue::from(p.clone()))
.collect::<Array>();
WebSocket::new_with_str_sequence(&url, &JsValue::from(&array))
.unwrap_throw()
},
)
};
web_socket.set_binary_type(BinaryType::Arraybuffer);
set_ready_state.set(UseWebSocketReadyState::Connecting);
// onopen handler
{
let onopen_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted_ref.get_value() {
return;
}
let callback = on_open_ref.get_value();
callback(e);
set_ready_state.set(UseWebSocketReadyState::Open);
}) as Box<dyn FnMut(Event)>);
web_socket.set_onopen(Some(onopen_closure.as_ref().unchecked_ref()));
// Forget the closure to keep it alive
onopen_closure.forget();
}
// onmessage handler
{
let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| {
if unmounted_ref.get_value() {
return;
}
e.data().dyn_into::<js_sys::ArrayBuffer>().map_or_else(
|_| {
e.data().dyn_into::<js_sys::JsString>().map_or_else(
|_| {
unreachable!("message event, received Unknown: {:?}", e.data());
},
|txt| {
let txt = String::from(&txt);
let callback = on_message_ref.get_value();
callback(txt.clone());
set_message.set(Some(txt.clone()));
},
);
},
|array_buffer| {
let array = js_sys::Uint8Array::new(&array_buffer);
let array = array.to_vec();
let callback = on_message_bytes_ref.get_value();
callback(array.clone());
set_message_bytes.set(Some(array));
},
);
})
as Box<dyn FnMut(MessageEvent)>);
web_socket.set_onmessage(Some(onmessage_closure.as_ref().unchecked_ref()));
onmessage_closure.forget();
}
// onerror handler
{
let onerror_closure = Closure::wrap(Box::new(move |e: Event| {
if unmounted_ref.get_value() {
return;
}
if let Some(reconnect) = &reconnect_ref.get_value() {
reconnect();
}
let callback = on_error_ref.get_value();
callback(e);
set_ready_state.set(UseWebSocketReadyState::Closed);
}) as Box<dyn FnMut(Event)>);
web_socket.set_onerror(Some(onerror_closure.as_ref().unchecked_ref()));
onerror_closure.forget();
}
// onclose handler
{
let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| {
if unmounted_ref.get_value() {
return;
}
if let Some(reconnect) = &reconnect_ref.get_value() {
reconnect();
}
let callback = on_close_ref.get_value();
callback(e);
set_ready_state.set(UseWebSocketReadyState::Closed);
})
as Box<dyn FnMut(CloseEvent)>);
web_socket.set_onclose(Some(onclose_closure.as_ref().unchecked_ref()));
onclose_closure.forget();
}
ws_ref.set_value(Some(web_socket));
}))
});
// Send text (String)
let send = {
Box::new(move |data: String| {
if ready_state.get() == UseWebSocketReadyState::Open {
if let Some(web_socket) = ws_ref.get_value() {
let _ = web_socket.send_with_str(&data);
}
}
})
};
// Send bytes
let send_bytes = {
move |data: Vec<u8>| {
if ready_state.get() == UseWebSocketReadyState::Open {
if let Some(web_socket) = ws_ref.get_value() {
let _ = web_socket.send_with_u8_array(&data);
}
}
}
};
// Open connection
let open = {
move || {
reconnect_times_ref.set_value(0);
if let Some(connect) = connect_ref.get_value() {
connect();
}
}
};
// Close connection
let close = {
reconnect_timer_ref.set_value(None);
move || {
reconnect_times_ref.set_value(reconnect_limit);
if let Some(web_socket) = ws_ref.get_value() {
let _ = web_socket.close();
}
}
};
// Open connection (not called if option `manual` is true)
{
let open = open.clone();
create_effect(cx, move |_| {
if !manual {
open();
}
|| ()
});
}
// clean up (unmount)
{
let close = close.clone();
on_cleanup(cx, move || {
unmounted_ref.set_value(true);
close();
});
}
UseWebsocketReturn {
ready_state,
message,
message_bytes,
ws: ws_ref.get_value(),
open,
close,
send,
send_bytes,
}
}
/// The current state of the `WebSocket` connection.
// #[doc(cfg(feature = "websocket"))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum UseWebSocketReadyState {
Connecting,
Open,
Closing,
Closed,
}
impl fmt::Display for UseWebSocketReadyState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
UseWebSocketReadyState::Connecting => write!(f, "Connecting"),
UseWebSocketReadyState::Open => write!(f, "Open"),
UseWebSocketReadyState::Closing => write!(f, "Closing"),
UseWebSocketReadyState::Closed => write!(f, "Closed"),
}
}
}
/// Options for [`use_websocket_with_options`].
// #[doc(cfg(feature = "websocket"))]
#[derive(DefaultBuilder)]
pub struct UseWebSocketOptions {
/// `WebSocket` connect callback.
on_open: Box<dyn CloneableFnWithArg<Event> + 'static>,
/// `WebSocket` message callback for text.
on_message: Box<dyn CloneableFnWithArg<String> + 'static>,
/// `WebSocket` message callback for binary.
on_message_bytes: Box<dyn CloneableFnWithArg<Vec<u8>> + 'static>,
/// `WebSocket` error callback.
on_error: Box<dyn CloneableFnWithArg<Event> + 'static>,
/// `WebSocket` close callback.
on_close: Box<dyn CloneableFnWithArg<CloseEvent> + 'static>,
/// Retry times.
reconnect_limit: Option<u64>,
/// Retry interval(ms).
reconnect_interval: Option<u64>,
/// Manually starts connection
manual: bool,
/// Sub protocols
protocols: Option<Vec<String>>,
}
impl Default for UseWebSocketOptions {
fn default() -> Self {
Self {
on_open: Box::new(|_| {}),
on_message: Box::new(|_| {}),
on_message_bytes: Box::new(|_| {}),
on_error: Box::new(|_| {}),
on_close: Box::new(|_| {}),
reconnect_limit: Some(3),
reconnect_interval: Some(3 * 1000),
manual: false,
protocols: Default::default(),
}
}
}
/// Return type of [`use_websocket`].
// #[doc(cfg(feature = "websocket"))]
#[derive(Clone)]
pub struct UseWebsocketReturn<OpenFn, CloseFn, SendFn, SendBytesFn>
where
OpenFn: Fn() + Clone + 'static,
CloseFn: Fn() + Clone + 'static,
SendFn: Fn(String) + Clone + 'static,
SendBytesFn: Fn(Vec<u8>) + Clone + 'static,
{
/// The current state of the `WebSocket` connection.
pub ready_state: ReadSignal<UseWebSocketReadyState>,
/// Latest text message received from `WebSocket`.
pub message: ReadSignal<Option<String>>,
/// Latest binary message received from `WebSocket`.
pub message_bytes: ReadSignal<Option<Vec<u8>>>,
/// The `WebSocket` instance.
pub ws: Option<WebSocket>,
/// Opens the `WebSocket` connection
pub open: OpenFn,
/// Closes the `WebSocket` connection
pub close: CloseFn,
/// Sends `text` (string) based data
pub send: SendFn,
/// Sends binary data
pub send_bytes: SendBytesFn,
}