Skip to content

Commit 4a20d3f

Browse files
committed
feat: WebSocket
1 parent 4587920 commit 4a20d3f

File tree

15 files changed

+681
-151
lines changed

15 files changed

+681
-151
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- [BREAKING] Renamed `to_kbevent` to `to_keyboard_event`.
5050
- [BREAKING] `after_next_render` returns `RenderInfo`.
5151
- `web_sys` and `wasm_bindgen` available in `seed::web_sys` and `seed::wasm_bindgen`.
52+
- Added `WebSocket`.
5253

5354
## v0.6.0
5455
- Implemented `UpdateEl` for `Filter` and `FilterMap`.

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ console_error_panic_hook = "0.1.6"
2525
cookie = { version = "0.13.3", features = ["percent-encode"] }
2626
enclose = "1.1.8"
2727
gloo-timers = { version = "0.2.1", features = ["futures"] }
28+
gloo-file = { version = "0.1.0", features = ["futures"] }
2829
indexmap = "1.3.2"
2930
js-sys = "0.3.37"
3031
pulldown-cmark = "0.7.0"
@@ -43,7 +44,10 @@ version = "0.3.37"
4344
features = [
4445
"AbortController",
4546
"AbortSignal",
47+
"Blob",
48+
"BinaryType",
4649
"CanvasRenderingContext2d",
50+
"CloseEvent",
4751
"CustomEvent",
4852
"CustomEventInit",
4953
"DataTransfer",
@@ -73,6 +77,7 @@ features = [
7377
"HtmlSelectElement",
7478
"HtmlButtonElement",
7579
"Location",
80+
"MessageEvent",
7681
"MouseEvent",
7782
"Node",
7883
"NodeList",
@@ -89,11 +94,13 @@ features = [
8994
"Response",
9095
"Selection",
9196
"Storage",
97+
"TcpReadyState",
9298
"Text",
9399
"Touch",
94100
"TouchEvent",
95101
"TouchList",
96102
"console",
103+
"WebSocket",
97104
"Window",
98105
"KeyboardEvent",
99106
"InputEvent",

examples/websocket/Cargo.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@ path = "src/server.rs"
1414

1515
[dependencies]
1616
# common
17-
serde = { version = "^1.0.103", features = ["derive"] }
18-
serde_json = "^1.0.44"
17+
serde = { version = "^1.0.106", features = ["derive"] }
18+
serde_json = "^1.0.51"
1919

2020
# server
2121
ws = { version = "0.9.1", optional = true }
2222

2323
#client
2424
seed = { path = "../../", optional = true }
25-
wasm-bindgen = { version = "^0.2.55", optional = true }
26-
js-sys = "0.3.32"
25+
wasm-bindgen = { version = "0.2.60", optional = true }
2726
[dependencies.web-sys]
28-
version = "^0.3.32"
29-
features = ["WebSocket", "MessageEvent"]
27+
version = "^0.3.37"
3028
optional = true
3129

3230
[features]

examples/websocket/src/client.rs

Lines changed: 62 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use js_sys::Function;
21
use seed::{prelude::*, *};
3-
use wasm_bindgen::JsCast;
4-
use web_sys::{MessageEvent, WebSocket};
52

6-
mod json;
3+
mod shared;
74

85
const WS_URL: &str = "ws://127.0.0.1:9000/ws";
96

@@ -12,105 +9,74 @@ const WS_URL: &str = "ws://127.0.0.1:9000/ws";
129
// ------ ------
1310

1411
struct Model {
15-
data: Data,
16-
services: Services,
17-
}
18-
19-
#[derive(Default)]
20-
struct Data {
21-
connected: bool,
22-
msg_rx_cnt: usize,
23-
msg_tx_cnt: usize,
24-
input_text: String,
12+
sent_messages_count: usize,
2513
messages: Vec<String>,
26-
}
27-
28-
struct Services {
29-
ws: WebSocket,
14+
input_text: String,
15+
web_socket: WebSocket,
3016
}
3117

3218
// ------ ------
33-
// After Mount
19+
// Init
3420
// ------ ------
3521

36-
fn after_mount(_: Url, orders: &mut impl Orders<Msg>) -> AfterMount<Model> {
37-
let ws = WebSocket::new(WS_URL).unwrap();
38-
39-
register_ws_handler(WebSocket::set_onopen, Msg::Connected, &ws, orders);
40-
register_ws_handler(WebSocket::set_onclose, Msg::Closed, &ws, orders);
41-
register_ws_handler(WebSocket::set_onmessage, Msg::ServerMessage, &ws, orders);
42-
register_ws_handler(WebSocket::set_onerror, Msg::Error, &ws, orders);
43-
44-
AfterMount::new(Model {
45-
data: Data::default(),
46-
services: Services { ws },
47-
})
48-
}
49-
50-
fn register_ws_handler<T, F>(
51-
ws_cb_setter: fn(&WebSocket, Option<&Function>),
52-
msg: F,
53-
ws: &WebSocket,
54-
orders: &mut impl Orders<Msg>,
55-
) where
56-
T: wasm_bindgen::convert::FromWasmAbi + 'static,
57-
F: Fn(T) -> Msg + 'static,
58-
{
59-
let (app, msg_mapper) = (orders.clone_app(), orders.msg_mapper());
60-
61-
let closure = Closure::new(move |data| {
62-
app.update(msg_mapper(msg(data)));
63-
});
64-
65-
ws_cb_setter(ws, Some(closure.as_ref().unchecked_ref()));
66-
closure.forget();
22+
fn init(_: Url, orders: &mut impl Orders<Msg>) -> Model {
23+
let web_socket = WebSocket::builder(WS_URL, orders)
24+
.on_open(|| log!("WebSocket connection is open now"))
25+
.on_message(Msg::MessageReceived)
26+
.on_close(Msg::WebSocketClosed)
27+
.on_error(|| log!("Error"))
28+
.build_and_open()
29+
.unwrap();
30+
31+
Model {
32+
sent_messages_count: 0,
33+
messages: Vec::new(),
34+
input_text: String::new(),
35+
web_socket,
36+
}
6737
}
6838

6939
// ------ ------
7040
// Update
7141
// ------ ------
7242

7343
enum Msg {
74-
Connected(JsValue),
75-
ServerMessage(MessageEvent),
76-
Send(json::ClientMessage),
77-
Sent,
78-
EditChange(String),
79-
Closed(JsValue),
80-
Error(JsValue),
44+
MessageReceived(WebSocketMessage),
45+
CloseWebSocket,
46+
WebSocketClosed(CloseEvent),
47+
InputTextChanged(String),
48+
SendMessage(shared::ClientMessage),
8149
}
8250

83-
fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
51+
fn update(msg: Msg, mut model: &mut Model, _: &mut impl Orders<Msg>) {
8452
match msg {
85-
Msg::Connected(_) => {
86-
log!("WebSocket connection is open now");
87-
model.data.connected = true;
88-
}
89-
Msg::ServerMessage(msg_event) => {
53+
Msg::MessageReceived(message) => {
9054
log!("Client received a message");
91-
let txt = msg_event.data().as_string().unwrap();
92-
let json: json::ServerMessage = serde_json::from_str(&txt).unwrap();
93-
94-
model.data.msg_rx_cnt += 1;
95-
model.data.messages.push(json.text);
96-
}
97-
Msg::EditChange(input_text) => {
98-
model.data.input_text = input_text;
55+
model
56+
.messages
57+
.push(message.json::<shared::ServerMessage>().unwrap().text);
9958
}
100-
Msg::Send(msg) => {
101-
let s = serde_json::to_string(&msg).unwrap();
102-
model.services.ws.send_with_str(&s).unwrap();
103-
orders.send_msg(Msg::Sent);
59+
Msg::CloseWebSocket => {
60+
model
61+
.web_socket
62+
.close(None, Some("user clicked Close button"))
63+
.unwrap();
10464
}
105-
Msg::Sent => {
106-
model.data.input_text = "".into();
107-
model.data.msg_tx_cnt += 1;
65+
Msg::WebSocketClosed(close_event) => {
66+
log!("==================");
67+
log!("WebSocket connection was closed:");
68+
log!("Clean:", close_event.was_clean());
69+
log!("Code:", close_event.code());
70+
log!("Reason:", close_event.reason());
71+
log!("==================");
10872
}
109-
Msg::Closed(_) => {
110-
log!("WebSocket connection was closed");
73+
Msg::InputTextChanged(input_text) => {
74+
model.input_text = input_text;
11175
}
112-
Msg::Error(_) => {
113-
log!("Error");
76+
Msg::SendMessage(msg) => {
77+
model.web_socket.send_json(&msg).unwrap();
78+
model.input_text.clear();
79+
model.sent_messages_count += 1;
11480
}
11581
}
11682
}
@@ -119,43 +85,35 @@ fn update(msg: Msg, mut model: &mut Model, orders: &mut impl Orders<Msg>) {
11985
// View
12086
// ------ ------
12187

122-
fn view(model: &Model) -> impl IntoNodes<Msg> {
123-
let data = &model.data;
124-
88+
fn view(model: &Model) -> Vec<Node<Msg>> {
12589
vec![
126-
h1!["seed websocket example"],
127-
if data.connected {
90+
h1!["WebSocket example"],
91+
div![model.messages.iter().map(|message| p![message])],
92+
if model.web_socket.state() == web_socket::State::Open {
12893
div![
12994
input![
13095
id!("text"),
13196
attrs! {
13297
At::Type => "text",
133-
At::Value => data.input_text;
98+
At::Value => model.input_text;
13499
},
135-
input_ev(Ev::Input, Msg::EditChange)
100+
input_ev(Ev::Input, Msg::InputTextChanged)
136101
],
137102
button![
138-
id!("send"),
139-
attrs! { At::Type => "button" },
140103
ev(Ev::Click, {
141-
let message_text = data.input_text.to_owned();
142-
move |_| Msg::Send(json::ClientMessage { text: message_text })
104+
let message_text = model.input_text.to_owned();
105+
move |_| Msg::SendMessage(shared::ClientMessage { text: message_text })
143106
}),
144107
"Send"
145-
]
108+
],
109+
button![ev(Ev::Click, |_| Msg::CloseWebSocket), "Close"],
146110
]
147111
} else {
148-
div![p![em!["Connecting..."]]]
112+
div![p![em!["Connecting or closed"]]]
149113
},
150-
div![data.messages.iter().map(|message| p![message])],
151114
footer![
152-
if data.connected {
153-
p!["Connected"]
154-
} else {
155-
p!["Disconnected"]
156-
},
157-
p![format!("{} messages received", data.msg_rx_cnt)],
158-
p![format!("{} messages sent", data.msg_tx_cnt)]
115+
p![format!("{} messages", model.messages.len())],
116+
p![format!("{} messages sent", model.sent_messages_count)]
159117
],
160118
]
161119
}
@@ -166,7 +124,5 @@ fn view(model: &Model) -> impl IntoNodes<Msg> {
166124

167125
#[wasm_bindgen(start)]
168126
pub fn start() {
169-
App::builder(update, view)
170-
.after_mount(after_mount)
171-
.build_and_start();
127+
App::start("app", init, update, view);
172128
}

examples/websocket/src/server.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,52 @@
1-
use ws::{listen, Handler, Message, Request, Response, Result, Sender};
1+
use ws::{listen, CloseCode, Handler, Message, Request, Response, Result, Sender};
22

3-
mod json;
3+
mod shared;
44

5-
// Server web application handler
65
struct Server {
76
out: Sender,
87
}
98

109
impl Handler for Server {
11-
// Handle messages recieved in the websocket (in this case, only on /ws)
10+
fn on_request(&mut self, req: &Request) -> Result<Response> {
11+
match req.resource() {
12+
"/ws" => Response::from_request(req),
13+
_ => Ok(Response::new(
14+
200,
15+
"OK",
16+
b"Websocket server is running".to_vec(),
17+
)),
18+
}
19+
}
20+
21+
// Handle messages recieved in the websocket (in this case, only on `/ws`).
1222
fn on_message(&mut self, msg: Message) -> Result<()> {
1323
let client_id: usize = self.out.token().into();
1424

15-
let client_msg: json::ClientMessage =
25+
let client_msg: shared::ClientMessage =
1626
serde_json::from_str(&msg.into_text().unwrap()).unwrap();
1727

1828
println!(
1929
"Server received text: '{}'\nfrom client '{}'\n",
2030
client_msg.text, client_id
2131
);
2232

23-
let server_msg: Message = serde_json::to_string(&json::ServerMessage {
33+
let server_msg: Message = serde_json::to_string(&shared::ServerMessage {
2434
id: client_id,
2535
text: client_msg.text,
2636
})
2737
.unwrap()
2838
.into();
29-
// Broadcast to all connections
39+
// Broadcast to all connections.
3040
self.out.broadcast(server_msg)
3141
}
3242

33-
fn on_request(&mut self, req: &Request) -> Result<Response> {
34-
match req.resource() {
35-
"/ws" => Response::from_request(req),
36-
_ => Ok(Response::new(
37-
200,
38-
"OK",
39-
b"Websocket server is running".to_vec(),
40-
)),
41-
}
43+
fn on_close(&mut self, code: CloseCode, reason: &str) {
44+
let client_id: usize = self.out.token().into();
45+
let code_number: u16 = code.into();
46+
println!(
47+
"WebSocket closing - client: {}, code: {} {:?}, reason: {}",
48+
client_id, code_number, code, reason
49+
);
4250
}
4351
}
4452

File renamed without changes.

src/app/orders/container.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ use crate::app::{
66
use crate::virtual_dom::IntoNodes;
77
use futures::future::FutureExt;
88
use futures::stream::{Stream, StreamExt};
9-
use std::{
10-
any::{Any, TypeId},
11-
collections::VecDeque,
12-
convert::identity,
13-
future::Future,
14-
rc::Rc,
15-
};
9+
use std::{any::Any, collections::VecDeque, convert::identity, future::Future, rc::Rc};
1610

1711
#[allow(clippy::module_name_repetitions)]
1812
pub struct OrdersContainer<Ms: 'static, Mdl: 'static, INodes: IntoNodes<Ms>, GMs = UndefinedGMsg> {

src/app/orders/proxy.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ use super::{
88
use crate::virtual_dom::IntoNodes;
99
use futures::future::{Future, FutureExt};
1010
use futures::stream::{Stream, StreamExt};
11-
use std::{
12-
any::{Any, TypeId},
13-
convert::identity,
14-
rc::Rc,
15-
};
11+
use std::{any::Any, convert::identity, rc::Rc};
1612

1713
#[allow(clippy::module_name_repetitions)]
1814
pub struct OrdersProxy<

0 commit comments

Comments
 (0)