ws_kit/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(docsrs, allow(unused_attributes))]
3#![forbid(unsafe_code)]
4#![deny(missing_docs)]
5#![deny(missing_docs)]
6
7//! # ws-kit
8//!
9//! Generic authenticated typed WebSocket toolkit.
10//!
11//! Provides:
12//! - [`config::WsConfig`] — heartbeat, broadcast capacity, connection limits, Origin allow-list with builder.
13//! - [`hub::BroadcastHub`] — typed `broadcast::Sender<T>` wrapper with connection counting, generic over `T: Clone + Serialize`.
14//! - [`room::RoomManager`] / [`room::Room`] — DashMap-backed room isolation with participant tracking, over the [`codec::Frame`] message model (text **and** binary).
15//! - [`room::RoomRegistry`] — the room-collection trait; swap the in-memory manager for a multi-node backend.
16//! - [`extractor::TokenExtractor`] — configurable token extraction (`Authorization: Bearer`, `?token=`, `?access_token=`, `Cookie`).
17//! - [`origin_allowed_in_parts`] — Origin validation for the upgrade handshake (CSWSH defense, REQ-WSKIT-200).
18//! - [`codec::Codec`] / [`codec::JsonCodec`] — JSON encode/decode via serde.
19//! - [`stats::StatsRecorder`] — backpressure/traffic counters (connections, rooms, bytes/messages in/out, drops), global + per-room, optional `metrics`-facade emission.
20//! - With `compression`: [`compression::FrameCompressor`] — application-layer DEFLATE for `Frame`s (not `permessage-deflate`; the module docs explain why).
21//! - With `redis`: [`redis_rooms::RedisRoomRegistry`] — multi-node room fan-out over Redis pub/sub (at-most-once; the module docs explain the topology).
22//!
23//! ## Quick start
24//!
25//! ```rust
26//! use ws_kit::{config::WsConfig, hub::BroadcastHub, room::RoomManager, codec::Codec};
27//! use serde::{Serialize, Deserialize};
28//!
29//! #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
30//! struct ChatMsg { user: String, body: String }
31//!
32//! # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
33//! let hub = BroadcastHub::<ChatMsg>::new(1024);
34//! let mut rx = hub.subscribe();
35//! hub.broadcast(ChatMsg { user: "alice".into(), body: "hi".into() }).unwrap();
36//! let msg = rx.recv().await.unwrap();
37//! assert_eq!(msg.user, "alice");
38//! # });
39//! ```
40//!
41//! ## Binary messages (0.4.0)
42//!
43//! ```rust
44//! use ws_kit::codec::Frame;
45//! use ws_kit::room::RoomManager;
46//!
47//! # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
48//! let m = RoomManager::new();
49//! let room = m.get_or_create("media");
50//! let mut rx = room.subscribe();
51//! room.broadcast(Frame::binary(vec![0xde_u8, 0xad, 0xbe, 0xef])).unwrap();
52//! assert!(rx.recv().await.unwrap().is_binary());
53//! # });
54//! ```
55//!
56//! Binary payloads are carried opaquely — ws-kit never parses them as
57//! text/JSON; [`codec::Frame::Text`] and [`codec::Frame::Binary`] are kept
58//! distinct end to end. Authentication (`TokenExtractor`) operates on the
59//! HTTP upgrade request and is frame-kind-agnostic.
60//!
61//! ## Features
62//!
63//! - `axum` (default): enables `axum` WebSocket and `tower` deps, and `TokenExtractor::extract_token(&Parts, _)`
64//! - `compression`: application-layer DEFLATE for `Frame` payloads (see [`compression`] for the layer assessment)
65//! - `redis`: multi-node room fan-out via [`redis_rooms::RedisRoomRegistry`] (at-most-once pub/sub)
66//! - `metrics`: emit [`stats`] counters through the `metrics` facade
67//! - `tracing`: deprecated no-op kept for 0.3.x compatibility
68
69pub mod codec;
70#[cfg(feature = "compression")]
71#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
72pub mod compression;
73pub mod config;
74mod counter;
75pub mod error;
76pub mod extractor;
77pub mod hub;
78pub mod origin;
79#[cfg(feature = "redis")]
80#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
81pub mod redis_rooms;
82pub mod room;
83pub mod stats;
84
85// Model-checking tests for `counter` — compiled only under `--cfg loom`.
86#[cfg(loom)]
87mod loom_tests;
88
89// Re-exports for ergonomic use
90pub use codec::{Codec, Frame, JsonCodec};
91pub use config::{WsConfig, WsConfigBuilder};
92pub use error::WsError;
93pub use extractor::{TokenExtractor, TokenSourceKind, WsAuthError};
94pub use hub::BroadcastHub;
95pub use origin::{normalize_origin, origin_allowed};
96pub use room::{Room, RoomManager, RoomRegistry};
97pub use stats::{RoomStats, Stats, StatsRecorder};
98
99#[cfg(feature = "axum")]
100#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
101pub use origin::origin_allowed_in_parts;
102
103#[cfg(feature = "compression")]
104#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
105pub use compression::{extension_accepted, CompressionConfig, FrameCompressor};
106
107#[cfg(feature = "redis")]
108#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
109pub use redis_rooms::RedisRoomRegistry;