leptos-use/src/use_broadcast_channel.rs

210 lines
6.4 KiB
Rust
Raw Normal View History

2024-01-29 21:29:39 +00:00
use crate::utils::StringCodec;
use crate::{
use_event_listener, use_event_listener_with_options, use_supported, UseEventListenerOptions,
};
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};
/// # use leptos_use::utils::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
///
/// Just like with [`use_storage`] you can use different codecs for encoding and decoding.
///
/// ```
/// # use leptos::*;
/// # use serde::{Deserialize, Serialize};
/// # use leptos_use::use_broadcast_channel;
/// # use leptos_use::utils::JsonCodec;
/// #
/// // 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, JsonCodec>("everyting-is-awesome");
/// # view! { }
/// # }
/// ```
pub fn use_broadcast_channel<T, C>(
name: &str,
) -> UseBroadcastChannelReturn<T, impl Fn(&T) + Clone, impl Fn() + Clone, C::Error>
2024-01-29 19:15:18 +00:00
where
2024-01-29 21:29:39 +00:00
C: StringCodec<T> + Default + Clone,
2024-01-29 19:15:18 +00:00
{
let is_supported = use_supported(|| JsValue::from("BroadcastChannel").js_in(&window()));
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>);
2024-01-29 21:29:39 +00:00
let (error, set_error) = create_signal(None::<UseBroadcastChannelError<C::Error>>);
2024-01-29 19:15:18 +00:00
2024-01-29 21:29:39 +00:00
let codec = C::default();
let post = {
let codec = codec.clone();
move |data: &T| {
if let Some(channel) = channel.get_untracked() {
match codec.encode(data) {
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::Encode(err)));
}
}
}
}
};
let close = {
let channel = channel.clone();
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 codec.decode(data) {
Ok(msg) => {
set_message.set(Some(msg));
}
Err(err) => set_error.set(Some(UseBroadcastChannelError::Decode(err))),
}
} 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();
});
return UseBroadcastChannelReturn {
is_supported,
channel: channel.into(),
message: message.into(),
post,
close,
error: error.into(),
is_closed: is_closed.into(),
2024-01-29 19:15:18 +00:00
};
}
/// Return type of [`use_broadcast_channel`].
2024-01-29 21:29:39 +00:00
pub struct UseBroadcastChannelReturn<T, PFn, CFn, Err>
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,
Err: '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.
2024-01-29 21:29:39 +00:00
pub error: Signal<Option<UseBroadcastChannelError<Err>>>,
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, Clone)]
pub enum UseBroadcastChannelError<Err> {
#[error("failed to post message")]
PostMessage(JsValue),
#[error("channel message error")]
MessageEvent(web_sys::MessageEvent),
#[error("failed to encode value")]
Encode(Err),
#[error("failed to decode value")]
Decode(Err),
#[error("received value is not a string")]
ValueNotString,
2024-01-29 19:15:18 +00:00
}