Skip to main content

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