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