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
/*!
# An implementation of phoenix channels for trillium.rs
Channels are a means of distributing events in soft-realtime to
connected websocket clients, including topic-subscription-based
fanout.
From the [phoenix docs](https://hexdocs.pm/phoenix/channels.html),
Some possible use cases include:
* Chat rooms and APIs for messaging apps
* Breaking news, like "a goal was scored" or "an earthquake is coming"
* Tracking trains, trucks, or race participants on a map
* Events in multiplayer games
* Monitoring sensors and controlling lights
* Notifying a browser that a page's CSS or JavaScript has changed
(this is handy in development)
* Conceptually, Channels are pretty simple.
First, clients connect to the server using WebSockets. Once connected,
they join one or more topics. For example, to interact with a public
chat room clients may join a topic called public_chat, and to receive
updates from a product with ID 7, they may need to join a topic called
product_updates:7.
Clients can push messages to the topics they've joined, and can also
receive messages from them. The other way around, Channel servers
receive messages from their connected clients, and can push messages
to them too.
## Current known differences from phoenix channels:
### No long-polling support
Phoenix channels support long polling transports as well as
websockets. As most modern browsers and http clients support
websockets, as of the current version, trillium channels exclusively
are built on them. The design should be flexible to support long
polling if it is eventually needed.
### No multi-server synchronization yet
Phoenix channels support running several server nodes and distributing
all broadcast messages between them. This will be straightforward to
add to trillium channels in a future revision, but the current
implementation does not synchronize messages across servers. However,
in the mean time, you can use [`Channel::broadcaster`] to return a
[`ChannelBroadcaster`] that can be used to publish and subscribe to
messages between servers using whatever distribution mechanism is
appropriate for your application and deployment. Open a discussion on
the trillium repo for ideas on how this might work for you.
### Event routing is handled in user code
Phoenix channels has a notion of registering channel handlers for
different topics, so an implementation might involve registering a
RoomChannel for `rooms:*`. Trillium channels does not currently
provide this routing/matching behavior, but will likely do so
eventually.
## Simple Example: Chat App
```
use trillium_channels::{channel, ChannelConn, ChannelEvent, ChannelHandler};
struct ChatChannel;
#[trillium::async_trait]
impl ChannelHandler for ChatChannel {
async fn join_channel(&self, conn: ChannelConn<'_>, event: ChannelEvent) {
match event.topic() {
"rooms:lobby" => {
conn.allow_join(&event, &()).await;
conn.broadcast(("rooms:lobby", "user:entered"));
}
_ => {}
}
}
async fn incoming_message(&self, conn: ChannelConn<'_>, event: ChannelEvent) {
match (event.topic(), event.event()) {
("rooms:lobby", "new:msg") => conn.broadcast(event),
_ => {}
}
}
}
// fn main() {
// trillium_smol::run(channel(ChatChannel));
// }
```
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).
*/
#![forbid(unsafe_code)]
#![deny(
missing_copy_implementations,
missing_debug_implementations,
nonstandard_style,
rustdoc::missing_crate_level_docs,
unused_qualifications
)]
#![warn(missing_docs)]
mod channel_central;
pub(crate) use channel_central::ChannelCentral;
mod channel_broadcaster;
pub use channel_broadcaster::ChannelBroadcaster;
mod channel_event;
pub use channel_event::ChannelEvent;
mod channel_client;
pub use channel_client::ChannelClient;
pub(crate) mod client_receiver;
mod channel_handler;
pub use channel_handler::ChannelHandler;
mod channel;
pub use channel::Channel;
pub(crate) mod subscriptions;
mod channel_conn;
pub use channel_conn::ChannelConn;
mod version;
pub use version::Version;
/**
This macro provides a convenient constructor for a
[`ChannelEvent`]. It is called with a topic, an event, and an optional
inline json payload.
```
let event = trillium_channels::event!("some:topic", "some:event");
assert_eq!(event.topic(), "some:topic");
assert_eq!(event.event(), "some:event");
assert_eq!(serde_json::to_string(event.payload()).unwrap(), "{}");
let event = trillium_channels::event!("some:topic", "some:event", { "payload": ["any", "json"] });
assert_eq!(event.topic(), "some:topic");
assert_eq!(event.event(), "some:event");
assert_eq!(serde_json::to_string(event.payload()).unwrap(), r#"{"payload":["any","json"]}"#);
```
*/
#[macro_export]
macro_rules! event {
($topic:expr, $event:expr) => {
$crate::ChannelEvent::new($topic, $event, &())
};
($topic:expr, $event:expr, $($json:tt)+) => {
$crate::ChannelEvent::new($topic, $event, &$crate::json!($($json)+))
};
}
pub use serde_json::json;
/**
Constructs a new [`Channel`] trillium handler from the provided
[`ChannelHandler`]. This is an alias for [`Channel::new`]
*/
pub fn channel<CH: ChannelHandler>(channel_handler: CH) -> Channel<CH> {
Channel::new(channel_handler)
}