mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 00:59:22 -05:00
Add use_websocket + example
This commit is contained in:
parent
24c12cb1d1
commit
ae2eb61b6f
14 changed files with 896 additions and 0 deletions
|
@ -27,6 +27,7 @@ lazy_static = "1"
|
|||
version = "0.3"
|
||||
features = [
|
||||
"CssStyleDeclaration",
|
||||
"CloseEvent",
|
||||
"CustomEvent",
|
||||
"CustomEventInit",
|
||||
"DomRectReadOnly",
|
||||
|
@ -62,6 +63,7 @@ features = [
|
|||
docs = []
|
||||
math = ["num"]
|
||||
storage = ["serde", "serde_json", "web-sys/StorageEvent"]
|
||||
websocket = ["web-sys/BinaryType", "web-sys/WebSocket"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -28,6 +28,7 @@ members = [
|
|||
"use_scroll",
|
||||
"use_storage",
|
||||
"use_throttle_fn",
|
||||
"use_websocket",
|
||||
"watch_debounced",
|
||||
"watch_pausable",
|
||||
"watch_throttled",
|
||||
|
|
16
examples/use_websocket/Cargo.toml
Normal file
16
examples/use_websocket/Cargo.toml
Normal 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"
|
22
examples/use_websocket/README.md
Normal file
22
examples/use_websocket/README.md
Normal 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
|
||||
```
|
2
examples/use_websocket/Trunk.toml
Normal file
2
examples/use_websocket/Trunk.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
public_url = "/demo/"
|
7
examples/use_websocket/index.html
Normal file
7
examples/use_websocket/index.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="css" href="style/output.css">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
3
examples/use_websocket/input.css
Normal file
3
examples/use_websocket/input.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
2
examples/use_websocket/rust-toolchain.toml
Normal file
2
examples/use_websocket/rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
203
examples/use_websocket/src/main.rs
Normal file
203
examples/use_websocket/src/main.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use leptos::*;
|
||||
use leptos_use::docs::demo_or_body;
|
||||
use leptos_use::websocket::*;
|
||||
|
||||
#[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 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 {
|
||||
manual: true,
|
||||
onopen: Some(Box::new(move |e| {
|
||||
set_history2.update(|history: &mut Vec<_>| {
|
||||
history.push(format! {"[onopen]: event {:?}", e.type_()})
|
||||
});
|
||||
})),
|
||||
onclose: Some(Box::new(move |e| {
|
||||
set_history2.update(|history: &mut Vec<_>| {
|
||||
history.push(format! {"[onclose]: event {:?}", e.type_()})
|
||||
});
|
||||
})),
|
||||
onerror: Some(Box::new(move |e| {
|
||||
set_history2.update(|history: &mut Vec<_>| {
|
||||
history.push(format! {"[onerror]: event {:?}", e.type_()})
|
||||
});
|
||||
})),
|
||||
onmessage: Some(Box::new(move |m| {
|
||||
set_history2
|
||||
.update(|history: &mut Vec<_>| history.push(format! {"[onmessage]: {:?}", m}));
|
||||
})),
|
||||
onmessage_bytes: Some(Box::new(move |m| {
|
||||
set_history2.update(|history: &mut Vec<_>| {
|
||||
history.push(format! {"[onmessage_bytes]: {:?}", m})
|
||||
});
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
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 /> }
|
||||
})
|
||||
}
|
214
examples/use_websocket/style/output.css
Normal file
214
examples/use_websocket/style/output.css
Normal file
|
@ -0,0 +1,214 @@
|
|||
*, ::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
|
||||
}
|
||||
|
||||
.p-0 {
|
||||
padding: 0px
|
||||
}
|
||||
|
||||
.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
|
||||
}
|
||||
}
|
13
examples/use_websocket/tailwind.config.js
Normal file
13
examples/use_websocket/tailwind.config.js
Normal 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: [],
|
||||
}
|
|
@ -9,6 +9,8 @@ pub mod math;
|
|||
#[cfg(feature = "storage")]
|
||||
pub mod storage;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "websocket")]
|
||||
pub mod websocket;
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
mod use_element_size;
|
||||
|
|
3
src/websocket/mod.rs
Normal file
3
src/websocket/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod use_websocket;
|
||||
|
||||
pub use use_websocket::*;
|
406
src/websocket/use_websocket.rs
Normal file
406
src/websocket/use_websocket.rs
Normal file
|
@ -0,0 +1,406 @@
|
|||
use leptos::{leptos_dom::helpers::TimeoutHandle, *};
|
||||
|
||||
use core::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, time::Duration};
|
||||
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||
use web_sys::{BinaryType, Event, MessageEvent, WebSocket};
|
||||
|
||||
pub use web_sys::CloseEvent;
|
||||
|
||||
use crate::utils::CloneableFnMutWithArg;
|
||||
|
||||
/// The current state of the `WebSocket` connection.
|
||||
#[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 `WebSocket`.
|
||||
// #[derive(DefaultBuilder)]
|
||||
#[derive(Clone)]
|
||||
pub struct UseWebSocketOptions {
|
||||
/// `WebSocket` connect callback.
|
||||
pub onopen: Option<Box<dyn CloneableFnMutWithArg<Event>>>,
|
||||
/// `WebSocket` message callback for text.
|
||||
pub onmessage: Option<Box<dyn CloneableFnMutWithArg<String>>>,
|
||||
/// `WebSocket` message callback for binary.
|
||||
pub onmessage_bytes: Option<Box<dyn CloneableFnMutWithArg<Vec<u8>>>>,
|
||||
/// `WebSocket` error callback.
|
||||
pub onerror: Option<Box<dyn CloneableFnMutWithArg<Event>>>,
|
||||
/// `WebSocket` close callback.
|
||||
pub onclose: Option<Box<dyn CloneableFnMutWithArg<CloseEvent>>>,
|
||||
|
||||
/// Retry times.
|
||||
pub reconnect_limit: Option<u64>,
|
||||
/// Retry interval(ms).
|
||||
pub reconnect_interval: Option<u64>,
|
||||
/// Manually starts connection
|
||||
pub manual: bool,
|
||||
/// Sub protocols
|
||||
pub protocols: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for UseWebSocketOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
onopen: None,
|
||||
onmessage: None,
|
||||
onmessage_bytes: None,
|
||||
onerror: None,
|
||||
onclose: None,
|
||||
reconnect_limit: Some(3),
|
||||
reconnect_interval: Some(3 * 1000),
|
||||
manual: false,
|
||||
protocols: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return type of [`use_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: Rc<RefCell<Option<WebSocket>>>,
|
||||
|
||||
pub open: OpenFn,
|
||||
pub close: CloseFn,
|
||||
pub send: SendFn,
|
||||
pub send_bytes: SendBytesFn,
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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: Rc<RefCell<Option<WebSocket>>> = Rc::new(RefCell::new(None));
|
||||
|
||||
let onopen = Rc::new(RefCell::new(options.onopen));
|
||||
let onmessage = Rc::new(RefCell::new(options.onmessage));
|
||||
let onmessage_bytes = Rc::new(RefCell::new(options.onmessage_bytes));
|
||||
let onerror = Rc::new(RefCell::new(options.onerror));
|
||||
let onclose = Rc::new(RefCell::new(options.onclose));
|
||||
|
||||
let reconnect_limit = options.reconnect_limit.unwrap_or(3);
|
||||
let reconnect_interval = options.reconnect_interval.unwrap_or(3 * 1000);
|
||||
|
||||
let reconnect_timer: Rc<RefCell<Option<TimeoutHandle>>> = Rc::new(RefCell::new(None));
|
||||
let manual = options.manual;
|
||||
let protocols = options.protocols;
|
||||
|
||||
let reconnect_times: Rc<RefCell<u64>> = Rc::new(RefCell::new(0));
|
||||
let unmounted = Rc::new(RefCell::new(false));
|
||||
|
||||
let connect: Rc<RefCell<Option<Rc<dyn Fn()>>>> = Rc::new(RefCell::new(None));
|
||||
|
||||
let reconnect: Rc<RefCell<Option<Rc<dyn Fn()>>>> = Rc::new(RefCell::new(None));
|
||||
*reconnect.borrow_mut() = {
|
||||
let ws = Rc::clone(&ws);
|
||||
let reconnect_times = Rc::clone(&reconnect_times);
|
||||
let connect = connect.clone();
|
||||
Some(Rc::new(move || {
|
||||
if *reconnect_times.borrow() < reconnect_limit
|
||||
&& ws
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map_or(false, |ws: &WebSocket| ws.ready_state() != WebSocket::OPEN)
|
||||
{
|
||||
let reconnect_times = Rc::clone(&reconnect_times);
|
||||
let connect = Rc::clone(&connect);
|
||||
|
||||
*reconnect_timer.borrow_mut() = set_timeout_with_handle(
|
||||
move || {
|
||||
let connect = &mut *connect.borrow_mut();
|
||||
if let Some(connect) = connect {
|
||||
connect();
|
||||
*reconnect_times.borrow_mut() += 1;
|
||||
}
|
||||
},
|
||||
Duration::from_millis(reconnect_interval),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
}))
|
||||
};
|
||||
|
||||
*connect.borrow_mut() = {
|
||||
let ws = Rc::clone(&ws);
|
||||
let url = url.clone();
|
||||
let unmounted = Rc::clone(&unmounted);
|
||||
let onopen = Rc::clone(&onopen);
|
||||
let onmessage = Rc::clone(&onmessage);
|
||||
let onerror = Rc::clone(&onerror);
|
||||
let onclose = Rc::clone(&onclose);
|
||||
let reconnect = Rc::clone(&reconnect);
|
||||
|
||||
Some(Rc::new(move || {
|
||||
{
|
||||
let web_socket: &mut Option<WebSocket> = &mut ws.borrow_mut();
|
||||
if let Some(web_socket) = web_socket {
|
||||
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 unmounted = Rc::clone(&unmounted);
|
||||
let onopen = Rc::clone(&onopen);
|
||||
let onopen_closure = Closure::wrap(Box::new(move |e: Event| {
|
||||
if *unmounted.borrow() {
|
||||
return;
|
||||
}
|
||||
|
||||
let onopen = &mut *onopen.borrow_mut();
|
||||
if let Some(onopen) = onopen {
|
||||
onopen(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 unmounted = Rc::clone(&unmounted);
|
||||
let onmessage = Rc::clone(&onmessage);
|
||||
let onmessage_bytes = onmessage_bytes.clone();
|
||||
let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if *unmounted.borrow() {
|
||||
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 onmessage = &mut *onmessage.borrow_mut();
|
||||
if let Some(onmessage) = onmessage {
|
||||
let txt = txt.clone();
|
||||
onmessage(txt);
|
||||
}
|
||||
set_message.set(Some(txt.clone()));
|
||||
},
|
||||
);
|
||||
},
|
||||
|array_buffer| {
|
||||
let array = js_sys::Uint8Array::new(&array_buffer);
|
||||
let array = array.to_vec();
|
||||
let onmessage_bytes = &mut *onmessage_bytes.borrow_mut();
|
||||
if let Some(onmessage_bytes) = onmessage_bytes {
|
||||
let array = array.clone();
|
||||
onmessage_bytes(array);
|
||||
}
|
||||
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 unmounted = Rc::clone(&unmounted);
|
||||
let onerror = Rc::clone(&onerror);
|
||||
let reconnect = Rc::clone(&reconnect);
|
||||
let onerror_closure = Closure::wrap(Box::new(move |e: Event| {
|
||||
if *unmounted.borrow() {
|
||||
return;
|
||||
}
|
||||
|
||||
let reconnect: Rc<dyn Fn()> = { reconnect.borrow().as_ref().unwrap().clone() };
|
||||
reconnect();
|
||||
|
||||
let onerror = &mut *onerror.borrow_mut();
|
||||
if let Some(onerror) = onerror {
|
||||
onerror(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 unmounted = Rc::clone(&unmounted);
|
||||
let onclose = Rc::clone(&onclose);
|
||||
|
||||
let reconnect = Rc::clone(&reconnect);
|
||||
let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| {
|
||||
if *unmounted.borrow() {
|
||||
return;
|
||||
}
|
||||
|
||||
let reconnect: Rc<dyn Fn()> = { reconnect.borrow().as_ref().unwrap().clone() };
|
||||
reconnect();
|
||||
|
||||
let onclose = &mut *onclose.borrow_mut();
|
||||
if let Some(onclose) = onclose {
|
||||
onclose(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.borrow_mut() = Some(web_socket);
|
||||
}))
|
||||
};
|
||||
|
||||
// Send text (String)
|
||||
let send = {
|
||||
let ws = Rc::clone(&ws);
|
||||
Box::new(move |data: String| {
|
||||
if ready_state.get() == UseWebSocketReadyState::Open {
|
||||
if let Some(web_socket) = ws.borrow_mut().as_ref() {
|
||||
let _ = web_socket.send_with_str(&data);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// Send bytes
|
||||
let send_bytes = {
|
||||
let ws = Rc::clone(&ws);
|
||||
move |data: Vec<u8>| {
|
||||
if ready_state.get() == UseWebSocketReadyState::Open {
|
||||
let web_socket: &mut Option<WebSocket> = &mut ws.borrow_mut();
|
||||
if let Some(web_socket) = web_socket {
|
||||
let _ = web_socket.send_with_u8_array(&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Open connection
|
||||
let open = {
|
||||
let reconnect_times_ref = Rc::clone(&reconnect_times);
|
||||
// let connect = Rc::clone(&connect);
|
||||
move || {
|
||||
let connect = connect.clone();
|
||||
*reconnect_times_ref.borrow_mut() = 0;
|
||||
let connect: Rc<dyn Fn()> = { connect.borrow().as_ref().unwrap().clone() };
|
||||
connect();
|
||||
}
|
||||
};
|
||||
|
||||
// Close connection
|
||||
let close = {
|
||||
let ws = Rc::clone(&ws);
|
||||
let reconnect_times = Rc::clone(&reconnect_times);
|
||||
move || {
|
||||
*reconnect_times.as_ref().borrow_mut() = reconnect_limit;
|
||||
let web_socket: &mut Option<WebSocket> = &mut ws.borrow_mut();
|
||||
if let Some(web_socket) = web_socket {
|
||||
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.borrow_mut() = true;
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
UseWebsocketReturn {
|
||||
ready_state,
|
||||
message,
|
||||
message_bytes,
|
||||
ws,
|
||||
open,
|
||||
close,
|
||||
send,
|
||||
send_bytes,
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue