-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ba52291
commit 66cbf45
Showing
9 changed files
with
409 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
[package] | ||
name = "leptos_sse" | ||
version = "0.4.0" | ||
edition = "2021" | ||
description = "Leptos server signals synced through server-sent-events (SSE)" | ||
repository = "https://github.com/messense/leptos_sse" | ||
license = "MIT" | ||
keywords = ["leptos", "server", "signal", "sse"] | ||
categories = [ | ||
"wasm", | ||
"web-programming", | ||
"web-programming::http-client", | ||
"web-programming::http-server", | ||
] | ||
|
||
[dependencies] | ||
cfg-if = "1" | ||
js-sys = "0.3.61" | ||
json-patch = "1.0.0" | ||
leptos.workspace = true | ||
serde = { version = "1.0.160", features = ["derive"] } | ||
serde_json = "1.0" | ||
wasm-bindgen = { version = "0.2.84", default-features = false } | ||
web-sys = { version = "0.3.61", features = ["EventSource", "MessageEvent"] } | ||
pin-project-lite = "0.2.12" | ||
tokio = { version = "1.36.0", optional = true } | ||
tokio-stream = { version = "0.1.14", optional = true } | ||
tracing.workspace = true | ||
|
||
axum = { version = "0.7", default-features = false, features = [ | ||
"tokio", | ||
"json", | ||
], optional = true } | ||
futures = { version = "0.3.28", default-features = false, optional = true } | ||
|
||
[features] | ||
default = [] | ||
ssr = ["dep:axum", "dep:futures", "dep:tokio", "dep:tokio-stream"] | ||
|
||
[package.metadata.docs.rs] | ||
features = ["ssr"] | ||
rustdoc-args = ["--cfg", "docsrs"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
use std::borrow::Cow; | ||
use std::pin::Pin; | ||
use std::task::Poll; | ||
|
||
use axum::response::sse::Event; | ||
use futures::stream::{Stream, StreamExt, TryStream}; | ||
use pin_project_lite::pin_project; | ||
use serde::Serialize; | ||
use serde_json::Value; | ||
use tokio::sync::mpsc; | ||
pub use tokio::sync::mpsc::error::{SendError, TrySendError}; | ||
use tokio_stream::wrappers::ReceiverStream; | ||
|
||
use crate::ServerSignalUpdate; | ||
|
||
pin_project! { | ||
/// A signal owned by the server which writes to the SSE when mutated. | ||
#[derive(Clone, Debug)] | ||
pub struct ServerSentEvents<S> { | ||
name: Cow<'static, str>, | ||
#[pin] | ||
stream: S, | ||
json_value: Value, | ||
} | ||
} | ||
|
||
impl<S> ServerSentEvents<S> { | ||
/// Create a new [`ServerSentEvents`] a stream, initializing `T` to default. | ||
/// | ||
/// This function can fail if serilization of `T` fails. | ||
pub fn new<T>(name: impl Into<Cow<'static, str>>, stream: S) -> Result<Self, serde_json::Error> | ||
where | ||
T: Default + Serialize, | ||
S: TryStream<Ok = T, Error = axum::BoxError>, | ||
{ | ||
Ok(ServerSentEvents { | ||
name: name.into(), | ||
stream, | ||
json_value: serde_json::to_value(T::default())?, | ||
}) | ||
} | ||
|
||
/// Create a server-sent-events (SSE) channel pair. | ||
/// | ||
/// The `buffer` argument controls how many unsent messages can be stored without waiting. | ||
/// | ||
/// The first item in the tuple is the MPSC channel sender half. | ||
pub fn channel<T>( | ||
name: impl Into<Cow<'static, str>>, | ||
buffer: usize, | ||
) -> Result< | ||
( | ||
Sender<T>, | ||
ServerSentEvents<impl TryStream<Ok = T, Error = axum::BoxError>>, | ||
), | ||
serde_json::Error, | ||
> | ||
where | ||
T: Default + Serialize, | ||
{ | ||
let (sender, receiver) = mpsc::channel::<T>(buffer); | ||
let stream = ReceiverStream::new(receiver).map(Ok); | ||
Ok((Sender(sender), ServerSentEvents::new(name, stream)?)) | ||
} | ||
} | ||
|
||
impl<S> Stream for ServerSentEvents<S> | ||
where | ||
S: TryStream<Error = axum::BoxError>, | ||
S::Ok: Serialize, | ||
{ | ||
type Item = Result<Event, axum::BoxError>; | ||
|
||
fn poll_next( | ||
self: Pin<&mut Self>, | ||
cx: &mut std::task::Context<'_>, | ||
) -> Poll<Option<Self::Item>> { | ||
let this = self.project(); | ||
match this.stream.try_poll_next(cx) { | ||
Poll::Ready(Some(Ok(value))) => { | ||
let new_json = serde_json::to_value(value)?; | ||
let update = ServerSignalUpdate::new_from_json::<S::Item>( | ||
this.name.clone(), | ||
this.json_value, | ||
&new_json, | ||
); | ||
*this.json_value = new_json; | ||
let event = Event::default().json_data(update)?; | ||
Poll::Ready(Some(Ok(event))) | ||
} | ||
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))), | ||
Poll::Ready(None) => Poll::Ready(None), | ||
Poll::Pending => Poll::Pending, | ||
} | ||
} | ||
} | ||
|
||
/// Sender half of a server-sent events stream. | ||
#[derive(Clone, Debug)] | ||
pub struct Sender<T>(mpsc::Sender<T>); | ||
|
||
impl<T> Sender<T> { | ||
/// Send an SSE message. | ||
pub async fn send(&self, value: T) -> Result<(), SendError<T>> | ||
where | ||
T: Serialize, | ||
{ | ||
self.0.send(value).await | ||
} | ||
|
||
/// Attempts to immediately send an SSE message. | ||
pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> | ||
where | ||
T: Serialize, | ||
{ | ||
self.0.try_send(value) | ||
} | ||
} |
Oops, something went wrong.