1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! WebSocket support for both native and WASM targets.
//!
//! **Platform Support:**
//! - ✅ Native (tokio): Full WebSocket support via async-tungstenite
//! - ✅ WASM (WinterCG): WebSocket support via `WebSocketPair` API
//!
//! **Platform Differences:**
//! - WASM: outbound message size is enforced from `WebSocketConfig::max_message_size`; the host
//! runtime may also impose its own cap (e.g. Cloudflare Workers limits messages to 1 MiB)
//! - WASM: No custom ping/pong frame control
//! - WASM: Event-driven model vs native stream model
//!
//! # Quick Start
//!
//! ## JSON Messages
//!
//! ```no_run
//! use futures_util::StreamExt;
//! use skyzen::websocket::{WebSocketUpgrade, WebSocketMessage};
//! use skyzen::Responder;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize)]
//! struct ChatMessage {
//! user: String,
//! text: String,
//! }
//!
//! async fn chat_handler(ws: WebSocketUpgrade) -> impl Responder {
//! ws.on_upgrade(|mut socket| async move {
//! // Receive JSON messages using the convenient recv_json method
//! while let Some(message) = socket.recv_json::<ChatMessage>().await {
//! // Echo back with automatic JSON serialization
//! socket.send(&message?).await?;
//! }
//! Ok::<_, skyzen::Error>(())
//! })
//! }
//! ```
//!
//! ## Text Messages
//!
//! ```no_run
//! # use futures_util::StreamExt;
//! # use skyzen::websocket::{WebSocketUpgrade, WebSocketMessage};
//! # use skyzen::Responder;
//! async fn text_echo(ws: WebSocketUpgrade) -> impl Responder {
//! ws.on_upgrade(|mut socket| async move {
//! while let Some(message) = socket.next().await {
//! if let Some(text) = message?.into_text() {
//! socket.send_text(text).await?;
//! }
//! }
//! Ok::<_, skyzen::Error>(())
//! })
//! }
//! ```
//!
//! ## Binary Messages
//!
//! ```no_run
//! # use futures_util::StreamExt;
//! # use skyzen::websocket::{WebSocketUpgrade, WebSocketMessage};
//! # use skyzen::Responder;
//! async fn binary_echo(ws: WebSocketUpgrade) -> impl Responder {
//! ws.on_upgrade(|mut socket| async move {
//! while let Some(message) = socket.next().await {
//! if let Some(data) = message?.into_bytes() {
//! socket.send_binary(data).await?;
//! }
//! }
//! Ok::<_, skyzen::Error>(())
//! })
//! }
//! ```
//!
//! # Reporting a failed session
//!
//! A session handler may return `()` or a `Result<(), E>` for any `E` that converts into
//! [`skyzen::Error`](crate::Error) — [`WebSocketError`] included, so `?` works on every socket
//! operation. An error ends the session, is logged with its whole `source()` chain, and closes the
//! connection with [`INTERNAL_ERROR`] (`1011`) so the peer can tell a server-side failure from a
//! clean goodbye. Returning `()` keeps the older behaviour: whatever the handler swallows stays
//! swallowed.
//!
//! # Convenience Methods
//!
//! The `WebSocket` type provides several convenience methods for common operations:
//!
//! - **JSON**: `send(&value)` for serialization, `recv_json::<T>()` for deserialization
//! - **Text**: `send_text(string)` for plain text messages
//! - **Binary**: `send_binary(bytes)` for binary data
//! - **Ping/Pong**: `send_ping(data)` and `send_pong(data)` (native only)
//!
//! # Protocol Negotiation
//!
//! ```no_run
//! # use skyzen::websocket::WebSocketUpgrade;
//! # use skyzen::Responder;
//! async fn with_protocols(ws: WebSocketUpgrade) -> impl Responder {
//! ws.protocols(["chat", "superchat"])
//! .on_upgrade(|socket| async move {
//! // Handle connection
//! })
//! }
//! ```
//!
//! The answer is echoed in the `101`'s `Sec-WebSocket-Protocol`, and that echo is mandatory:
//! RFC 6455 §4.1 has a client that offered a subprotocol **fail the connection** when the
//! handshake comes back without one.
//!
//! ## Authenticating a browser socket
//!
//! The browser `WebSocket` constructor sends no custom headers — no `Authorization`, no cookie you
//! control — so the subprotocol list is the only in-band channel a page has for a credential:
//!
//! ```js
//! new WebSocket(url, [`app.bearer.${token}`])
//! ```
//!
//! A fixed list of supported names cannot match a value that carries a token, so read the offer
//! and answer it verbatim. [`RequestedSubprotocols`] does both halves, and works the same in a
//! Durable Object, where the upgrade is constructed rather than extracted:
//!
//! ```no_run
//! # use skyzen::websocket::{RequestedSubprotocols, WebSocketError, WebSocketUpgrade};
//! # use skyzen::Responder;
//! const PREFIX: &str = "app.bearer.";
//!
//! async fn authenticated(
//! ws: WebSocketUpgrade,
//! offered: RequestedSubprotocols,
//! ) -> Result<impl Responder, WebSocketError> {
//! let token = offered
//! .iter()
//! .find_map(|protocol| protocol.strip_prefix(PREFIX))
//! .ok_or_else(|| WebSocketError::Protocol("no bearer subprotocol".to_owned()))?;
//! // ... verify `token` ...
//!
//! let answer = offered
//! .answer(|protocol| protocol.starts_with(PREFIX))
//! .ok_or_else(|| WebSocketError::Protocol("unanswerable subprotocol".to_owned()))?;
//! Ok(ws.protocol(answer).on_upgrade(|socket| async move {
//! // Handle connection
//! }))
//! }
//! ```
//!
//! # Configuration
//!
//! ```no_run
//! # use skyzen::websocket::{WebSocketConfig, WebSocketUpgrade};
//! # use skyzen::Responder;
//! async fn with_config(ws: WebSocketUpgrade) -> impl Responder {
//! let config = WebSocketConfig::default()
//! .with_max_message_size(Some(1024 * 1024)) // 1 MB limit
//! .with_max_frame_size(Some(64 * 1024)); // 64 KB frame limit
//!
//! ws.config(config)
//! .on_upgrade(|socket| async move {
//! // Handle connection
//! })
//! }
//! ```
pub
pub use *;
pub use upgrade_from_request;
pub use *;
pub use session_handler;
pub use ;
pub use select_offered_protocol;
pub use *;
pub use *;