Skip to main content

trillium_channels/
lib.rs

1//! # An implementation of phoenix channels for trillium.rs
2//!
3//! Channels are a means of distributing events in soft-realtime to
4//! connected websocket clients, including topic-subscription-based
5//! fanout.
6//!
7//! From the [phoenix docs](https://hexdocs.pm/phoenix/channels.html),
8//!
9//! Some possible use cases include:
10//!
11//! Chat rooms and APIs for messaging apps
12//! Breaking news, like "a goal was scored" or "an earthquake is coming"
13//! Tracking trains, trucks, or race participants on a map
14//! Events in multiplayer games
15//! Monitoring sensors and controlling lights
16//! Notifying a browser that a page's CSS or JavaScript has changed
17//! (this is handy in development)
18//! Conceptually, Channels are pretty simple.
19//!
20//! First, clients connect to the server using WebSockets. Once connected,
21//! they join one or more topics. For example, to interact with a public
22//! chat room clients may join a topic called public_chat, and to receive
23//! updates from a product with ID 7, they may need to join a topic called
24//! product_updates:7.
25//!
26//! Clients can push messages to the topics they've joined, and can also
27//! receive messages from them. The other way around, Channel servers
28//! receive messages from their connected clients, and can push messages
29//! to them too.
30//!
31//!
32//! ## Current known differences from phoenix channels:
33//!
34//! ### No long-polling support
35//!
36//! Phoenix channels support long polling transports as well as
37//! websockets. As most modern browsers and http clients support
38//! websockets, as of the current version, trillium channels exclusively
39//! are built on them. The design should be flexible to support long
40//! polling if it is eventually needed.
41//!
42//! ### No multi-server synchronization yet
43//!
44//! Phoenix channels support running several server nodes and distributing
45//! all broadcast messages between them. This will be straightforward to
46//! add to trillium channels in a future revision, but the current
47//! implementation does not synchronize messages across servers. However,
48//! in the mean time, you can use [`Channel::broadcaster`] to return a
49//! [`ChannelBroadcaster`] that can be used to publish and subscribe to
50//! messages between servers using whatever distribution mechanism is
51//! appropriate for your application and deployment. Open a discussion on
52//! the trillium repo for ideas on how this might work for you.
53//!
54//!
55//! ### Event routing is handled in user code
56//!
57//! Phoenix channels has a notion of registering channel handlers for
58//! different topics, so an implementation might involve registering a
59//! RoomChannel for `rooms:*`. Trillium channels does not currently
60//! provide this routing/matching behavior, but will likely do so
61//! eventually.
62//!
63//!
64//! ## Simple Example: Chat App
65//!
66//! ```
67//! use trillium_channels::{ChannelConn, ChannelEvent, ChannelHandler, channel};
68//!
69//! struct ChatChannel;
70//! impl ChannelHandler for ChatChannel {
71//!     async fn join_channel(&self, conn: ChannelConn<'_>, event: ChannelEvent) {
72//!         match event.topic() {
73//!             "rooms:lobby" => {
74//!                 conn.allow_join(&event, &()).await;
75//!                 conn.broadcast(("rooms:lobby", "user:entered"));
76//!             }
77//!
78//!             _ => {}
79//!         }
80//!     }
81//!
82//!     async fn incoming_message(&self, conn: ChannelConn<'_>, event: ChannelEvent) {
83//!         match (event.topic(), event.event()) {
84//!             ("rooms:lobby", "new:msg") => conn.broadcast(event),
85//!             _ => {}
86//!         }
87//!     }
88//! }
89//!
90//! // fn main() {
91//! //    trillium_smol::run(channel(ChatChannel));
92//! // }
93//! ```
94//!
95//! See channels/examples/channels.rs for a fully functional example that uses the front-end from [the phoenix chat example](https://github.com/chrismccord/phoenix_chat_example).
96
97#![forbid(unsafe_code)]
98#![deny(
99    missing_copy_implementations,
100    missing_debug_implementations,
101    nonstandard_style,
102    rustdoc::missing_crate_level_docs,
103    unused_qualifications
104)]
105#![warn(missing_docs)]
106
107#[cfg(test)]
108#[doc = include_str!("../README.md")]
109mod readme {}
110
111mod channel_central;
112pub(crate) use channel_central::ChannelCentral;
113
114mod channel_broadcaster;
115pub use channel_broadcaster::ChannelBroadcaster;
116
117mod channel_event;
118pub use channel_event::ChannelEvent;
119
120mod channel_client;
121pub use channel_client::ChannelClient;
122
123pub(crate) mod client_receiver;
124
125mod channel_handler;
126pub use channel_handler::ChannelHandler;
127
128mod channel;
129pub use channel::Channel;
130
131pub(crate) mod subscriptions;
132
133mod channel_conn;
134pub use channel_conn::ChannelConn;
135
136mod version;
137pub use version::Version;
138
139/// This macro provides a convenient constructor for a
140/// [`ChannelEvent`]. It is called with a topic, an event, and an optional
141/// inline json payload.
142///
143///
144/// ```
145/// let event = trillium_channels::event!("some:topic", "some:event");
146/// assert_eq!(event.topic(), "some:topic");
147/// assert_eq!(event.event(), "some:event");
148/// assert_eq!(serde_json::to_string(event.payload()).unwrap(), "{}");
149///
150///
151/// let event = trillium_channels::event!("some:topic", "some:event", { "payload": ["any", "json"] });
152/// assert_eq!(event.topic(), "some:topic");
153/// assert_eq!(event.event(), "some:event");
154/// assert_eq!(serde_json::to_string(event.payload()).unwrap(), r#"{"payload":["any","json"]}"#);
155/// ```
156#[macro_export]
157macro_rules! event {
158    ($topic:expr_2021, $event:expr_2021) => {
159        $crate::ChannelEvent::new($topic, $event, &())
160    };
161
162    ($topic:expr_2021, $event:expr_2021, $($json:tt)+) => {
163        $crate::ChannelEvent::new($topic, $event, &$crate::json!($($json)+))
164    };
165}
166
167pub use serde_json::json;
168
169/// Constructs a new [`Channel`] trillium handler from the provided
170/// [`ChannelHandler`]. This is an alias for [`Channel::new`]
171pub fn channel<CH: ChannelHandler>(channel_handler: CH) -> Channel<CH> {
172    Channel::new(channel_handler)
173}