leptos_use/
use_broadcast_channel.rs1use crate::sendwrap_fn;
2use crate::{
3    js, use_event_listener, use_event_listener_with_options, use_supported, UseEventListenerOptions,
4};
5use codee::{CodecError, Decoder, Encoder};
6use leptos::ev::messageerror;
7use leptos::prelude::*;
8use thiserror::Error;
9use wasm_bindgen::JsValue;
10
11pub fn use_broadcast_channel<T, C>(
80    name: &str,
81) -> UseBroadcastChannelReturn<
82    T,
83    impl Fn(&T) + Clone + Send + Sync,
84    impl Fn() + Clone + Send + Sync,
85    C,
86>
87where
88    T: Send + Sync,
89    C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str> + Send + Sync,
90    <C as Encoder<T>>::Error: Send + Sync,
91    <C as Decoder<T>>::Error: Send + Sync,
92{
93    let is_supported = use_supported(|| js!("BroadcastChannel" in &window()));
94
95    let (is_closed, set_closed) = signal(false);
96    let (channel, set_channel) = signal_local(None::<web_sys::BroadcastChannel>);
97    let (message, set_message) = signal(None::<T>);
98    let (error, set_error) = signal_local(
99        None::<UseBroadcastChannelError<<C as Encoder<T>>::Error, <C as Decoder<T>>::Error>>,
100    );
101
102    let post = {
103        sendwrap_fn!(move |data: &T| {
104            if let Some(channel) = channel.get_untracked() {
105                match C::encode(data) {
106                    Ok(msg) => {
107                        channel
108                            .post_message(&msg.into())
109                            .map_err(|err| {
110                                set_error.set(Some(UseBroadcastChannelError::PostMessage(err)))
111                            })
112                            .ok();
113                    }
114                    Err(err) => {
115                        set_error.set(Some(UseBroadcastChannelError::Codec(CodecError::Encode(
116                            err,
117                        ))));
118                    }
119                }
120            }
121        })
122    };
123
124    let close = {
125        sendwrap_fn!(move || {
126            if let Some(channel) = channel.get_untracked() {
127                channel.close();
128            }
129            set_closed.set(true);
130        })
131    };
132
133    if is_supported.get_untracked() {
134        let channel_val = web_sys::BroadcastChannel::new(name).ok();
135        set_channel.set(channel_val.clone());
136
137        if let Some(channel) = channel_val {
138            let _ = use_event_listener_with_options(
139                channel.clone(),
140                leptos::ev::message,
141                move |event| {
142                    if let Some(data) = event.data().as_string() {
143                        match C::decode(&data) {
144                            Ok(msg) => {
145                                set_message.set(Some(msg));
146                            }
147                            Err(err) => set_error.set(Some(UseBroadcastChannelError::Codec(
148                                CodecError::Decode(err),
149                            ))),
150                        }
151                    } else {
152                        set_error.set(Some(UseBroadcastChannelError::ValueNotString));
153                    }
154                },
155                UseEventListenerOptions::default().passive(true),
156            );
157
158            let _ = use_event_listener_with_options(
159                channel.clone(),
160                messageerror,
161                move |event| {
162                    set_error.set(Some(UseBroadcastChannelError::MessageEvent(event)));
163                },
164                UseEventListenerOptions::default().passive(true),
165            );
166
167            let _ = use_event_listener(channel, leptos::ev::close, move |_| set_closed.set(true));
168        }
169    }
170
171    on_cleanup({
172        let close = close.clone();
173
174        move || {
175            close();
176        }
177    });
178
179    UseBroadcastChannelReturn {
180        is_supported,
181        channel: channel.into(),
182        message: message.into(),
183        post,
184        close,
185        error: error.into(),
186        is_closed: is_closed.into(),
187    }
188}
189
190pub struct UseBroadcastChannelReturn<T, PFn, CFn, C>
192where
193    T: Send + Sync + 'static,
194    PFn: Fn(&T) + Clone,
195    CFn: Fn() + Clone,
196    C: Encoder<T> + Decoder<T> + Send + Sync,
197{
198    pub is_supported: Signal<bool>,
200
201    pub channel: Signal<Option<web_sys::BroadcastChannel>, LocalStorage>,
203
204    pub message: Signal<Option<T>>,
206
207    pub post: PFn,
209
210    pub close: CFn,
212
213    pub error: Signal<Option<ErrorType<T, C>>, LocalStorage>,
215
216    pub is_closed: Signal<bool>,
218}
219
220type ErrorType<T, C> = UseBroadcastChannelError<<C as Encoder<T>>::Error, <C as Decoder<T>>::Error>;
221
222#[derive(Debug, Error)]
223pub enum UseBroadcastChannelError<E, D> {
224    #[error("failed to post message")]
225    PostMessage(JsValue),
226    #[error("channel message error")]
227    MessageEvent(web_sys::MessageEvent),
228    #[error("failed to (de)encode value")]
229    Codec(CodecError<E, D>),
230    #[error("received value is not a string")]
231    ValueNotString,
232}