leptos-use/src/use_broadcast_channel.rs

219 lines
6.7 KiB
Rust
Raw Normal View History

2024-01-29 21:29:39 +00:00
use crate::{
js, use_event_listener, use_event_listener_with_options, use_supported, UseEventListenerOptions,
2024-01-29 21:29:39 +00:00
};
2024-07-08 17:10:29 +01:00
use codee::{CodecError, Decoder, Encoder};
2024-01-29 19:15:18 +00:00
use leptos::*;
2024-01-29 21:29:39 +00:00
use thiserror::Error;
use wasm_bindgen::JsValue;
2024-01-29 19:15:18 +00:00
2024-01-29 21:29:39 +00:00
/// Reactive [BroadcastChannel API](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel).
2024-01-29 19:15:18 +00:00
///
2024-01-29 21:29:39 +00:00
/// Closes a broadcast channel automatically when the component is cleaned up.
2024-01-29 19:15:18 +00:00
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_broadcast_channel)
///
/// ## Usage
///
2024-01-29 21:29:39 +00:00
/// The BroadcastChannel interface represents a named channel that any browsing context of a given origin can subscribe to. It allows communication between different documents (in different windows, tabs, frames, or iframes) of the same origin.
///
/// Messages are broadcasted via a message event fired at all BroadcastChannel objects listening to the channel.
///
2024-01-29 19:15:18 +00:00
/// ```
/// # use leptos::*;
2024-01-29 21:29:39 +00:00
/// # use leptos_use::{use_broadcast_channel, UseBroadcastChannelReturn};
2024-07-08 17:10:29 +01:00
/// # use codee::string::FromToStringCodec;
2024-01-29 19:15:18 +00:00
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
2024-01-29 21:29:39 +00:00
/// let UseBroadcastChannelReturn {
/// is_supported,
/// message,
/// post,
/// error,
/// close,
/// ..
/// } = use_broadcast_channel::<bool, FromToStringCodec>("some-channel-name");
///
/// post(&true);
///
/// close();
2024-01-29 19:15:18 +00:00
/// #
/// # view! { }
/// # }
/// ```
2024-01-29 21:29:39 +00:00
///
/// Values are (en)decoded via the given codec. You can use any of the string codecs or a
2024-07-27 18:10:38 +02:00
/// binary codec wrapped in `Base64`.
///
/// > Please check [the codec chapter](https://leptos-use.rs/codecs.html) to see what codecs are
/// available and what feature flags they require.
2024-01-29 21:29:39 +00:00
///
/// ```
/// # use leptos::*;
/// # use serde::{Deserialize, Serialize};
/// # use leptos_use::use_broadcast_channel;
2024-07-08 17:10:29 +01:00
/// # use codee::string::JsonSerdeCodec;
2024-01-29 21:29:39 +00:00
/// #
/// // Data sent in JSON must implement Serialize, Deserialize:
/// #[derive(Serialize, Deserialize, Clone, PartialEq)]
/// pub struct MyState {
/// pub playing_lego: bool,
/// pub everything_is_awesome: String,
/// }
///
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// use_broadcast_channel::<MyState, JsonSerdeCodec>("everyting-is-awesome");
2024-01-29 21:29:39 +00:00
/// # view! { }
/// # }
/// ```
pub fn use_broadcast_channel<T, C>(
name: &str,
) -> UseBroadcastChannelReturn<
T,
impl Fn(&T) + Clone,
impl Fn() + Clone,
<C as Encoder<T>>::Error,
<C as Decoder<T>>::Error,
>
2024-01-29 19:15:18 +00:00
where
C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
2024-01-29 19:15:18 +00:00
{
let is_supported = use_supported(|| js!("BroadcastChannel" in &window()));
2024-01-29 19:15:18 +00:00
let (is_closed, set_closed) = create_signal(false);
let (channel, set_channel) = create_signal(None::<web_sys::BroadcastChannel>);
let (message, set_message) = create_signal(None::<T>);
let (error, set_error) = create_signal(
None::<UseBroadcastChannelError<<C as Encoder<T>>::Error, <C as Decoder<T>>::Error>>,
);
2024-01-29 21:29:39 +00:00
let post = {
move |data: &T| {
if let Some(channel) = channel.get_untracked() {
match C::encode(data) {
2024-01-29 21:29:39 +00:00
Ok(msg) => {
channel
.post_message(&msg.into())
.map_err(|err| {
set_error.set(Some(UseBroadcastChannelError::PostMessage(err)))
})
.ok();
}
Err(err) => {
set_error.set(Some(UseBroadcastChannelError::Codec(CodecError::Encode(
err,
))));
2024-01-29 21:29:39 +00:00
}
}
}
}
};
let close = {
move || {
if let Some(channel) = channel.get_untracked() {
channel.close();
}
set_closed.set(true);
}
};
if is_supported.get_untracked() {
let channel_val = web_sys::BroadcastChannel::new(name).ok();
set_channel.set(channel_val.clone());
if let Some(channel) = channel_val {
let _ = use_event_listener_with_options(
channel.clone(),
ev::message,
move |event| {
if let Some(data) = event.data().as_string() {
match C::decode(&data) {
2024-01-29 21:29:39 +00:00
Ok(msg) => {
set_message.set(Some(msg));
}
Err(err) => set_error.set(Some(UseBroadcastChannelError::Codec(
CodecError::Decode(err),
))),
2024-01-29 21:29:39 +00:00
}
} else {
set_error.set(Some(UseBroadcastChannelError::ValueNotString));
}
},
UseEventListenerOptions::default().passive(true),
);
let _ = use_event_listener_with_options(
channel.clone(),
ev::messageerror,
move |event| {
set_error.set(Some(UseBroadcastChannelError::MessageEvent(event)));
},
UseEventListenerOptions::default().passive(true),
);
let _ = use_event_listener(channel, ev::close, move |_| set_closed.set(true));
2024-01-29 19:15:18 +00:00
}
2024-01-29 21:29:39 +00:00
}
on_cleanup(move || {
close();
});
2024-01-30 10:30:47 +01:00
UseBroadcastChannelReturn {
2024-01-29 21:29:39 +00:00
is_supported,
channel: channel.into(),
message: message.into(),
post,
close,
error: error.into(),
is_closed: is_closed.into(),
2024-01-30 10:30:47 +01:00
}
2024-01-29 19:15:18 +00:00
}
/// Return type of [`use_broadcast_channel`].
pub struct UseBroadcastChannelReturn<T, PFn, CFn, E, D>
2024-01-29 19:15:18 +00:00
where
2024-01-29 21:29:39 +00:00
T: 'static,
PFn: Fn(&T) + Clone,
CFn: Fn() + Clone,
E: 'static,
D: 'static,
2024-01-29 19:15:18 +00:00
{
/// `true` if this browser supports `BroadcastChannel`s.
2024-01-29 21:29:39 +00:00
pub is_supported: Signal<bool>,
2024-01-29 19:15:18 +00:00
/// The broadcast channel that is wrapped by this function
2024-01-29 21:29:39 +00:00
pub channel: Signal<Option<web_sys::BroadcastChannel>>,
2024-01-29 19:15:18 +00:00
/// Latest message received from the channel
2024-01-29 21:29:39 +00:00
pub message: Signal<Option<T>>,
2024-01-29 19:15:18 +00:00
/// Sends a message through the channel
2024-01-29 21:29:39 +00:00
pub post: PFn,
2024-01-29 19:15:18 +00:00
/// Closes the channel
2024-01-29 21:29:39 +00:00
pub close: CFn,
2024-01-29 19:15:18 +00:00
/// Latest error as reported by the `messageerror` event.
pub error: Signal<Option<UseBroadcastChannelError<E, D>>>,
2024-01-29 19:15:18 +00:00
/// Wether the channel is closed
2024-01-29 21:29:39 +00:00
pub is_closed: Signal<bool>,
}
#[derive(Debug, Error)]
pub enum UseBroadcastChannelError<E, D> {
2024-01-29 21:29:39 +00:00
#[error("failed to post message")]
PostMessage(JsValue),
#[error("channel message error")]
MessageEvent(web_sys::MessageEvent),
#[error("failed to (de)encode value")]
Codec(CodecError<E, D>),
2024-01-29 21:29:39 +00:00
#[error("received value is not a string")]
ValueNotString,
2024-01-29 19:15:18 +00:00
}