diff --git a/CHANGELOG.md b/CHANGELOG.md index 30bc2db..97a94ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `use_scroll` now uses `try_get_untracked` in the debounced callback to avoid panics if the context has been destroyed while the callback was waiting to be called. - `use_idle` works properly now (no more idles too early). +- `use_web_notification` doesn't panic on the server anymore. ## [0.8.2] - 2023-11-09 diff --git a/src/use_display_media.rs b/src/use_display_media.rs index 2dbc5f4..fb0ff8c 100644 --- a/src/use_display_media.rs +++ b/src/use_display_media.rs @@ -1,10 +1,8 @@ use crate::core::MaybeRwSignal; -use crate::use_window::use_window; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::*; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{DisplayMediaStreamConstraints, MediaStream}; /// Reactive [`mediaDevices.getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) streaming. /// @@ -56,7 +54,7 @@ pub fn use_display_media_with_options( let (enabled, set_enabled) = enabled.into_signal(); - let (stream, set_stream) = create_signal(None::>); + let (stream, set_stream) = create_signal(None::>); let _start = move || async move { cfg_if! { if #[cfg(not(feature = "ssr"))] { @@ -67,6 +65,8 @@ pub fn use_display_media_with_options( let stream = create_media(audio).await; set_stream.update(|s| *s = Some(stream)); + } else { + let _ = audio; }} }; @@ -122,13 +122,15 @@ pub fn use_display_media_with_options( } #[cfg(not(feature = "ssr"))] -async fn create_media(audio: bool) -> Result { +async fn create_media(audio: bool) -> Result { + use crate::use_window::use_window; + let media = use_window() .navigator() .ok_or_else(|| JsValue::from_str("Failed to access window.navigator")) .and_then(|n| n.media_devices())?; - let mut constraints = DisplayMediaStreamConstraints::new(); + let mut constraints = web_sys::DisplayMediaStreamConstraints::new(); if audio { constraints.audio(&JsValue::from(true)); } @@ -136,7 +138,7 @@ async fn create_media(audio: bool) -> Result { let promise = media.get_display_media_with_constraints(&constraints)?; let res = wasm_bindgen_futures::JsFuture::from(promise).await?; - Ok::<_, JsValue>(MediaStream::unchecked_from_js(res)) + Ok::<_, JsValue>(web_sys::MediaStream::unchecked_from_js(res)) } // NOTE: there's no video value because it has to be `true`. Otherwise the stream would always resolve to an Error. @@ -172,7 +174,7 @@ where /// Initially this is `None` until `start` resolved successfully. /// In case the stream couldn't be started, for example because the user didn't grant permission, /// this has the value `Some(Err(...))`. - pub stream: Signal>>, + pub stream: Signal>>, /// Starts the screen streaming. Triggers the ask for permission if not already granted. pub start: StartFn, diff --git a/src/use_service_worker.rs b/src/use_service_worker.rs index ec2d262..b9dda2d 100644 --- a/src/use_service_worker.rs +++ b/src/use_service_worker.rs @@ -33,6 +33,10 @@ use crate::use_window; /// # view! { } /// # } /// ``` +/// +/// ## Server-Side Rendering +/// +/// This function does **not** support SSR. Call it inside a `create_effect`. pub fn use_service_worker() -> UseServiceWorkerReturn { use_service_worker_with_options(UseServiceWorkerOptions::default()) } diff --git a/src/use_web_notification.rs b/src/use_web_notification.rs index 98c66ef..1b8ca89 100644 --- a/src/use_web_notification.rs +++ b/src/use_web_notification.rs @@ -1,10 +1,8 @@ -use crate::{use_event_listener, use_supported, use_window}; +use crate::{use_supported, use_window}; +use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; -use leptos::ev::visibilitychange; use leptos::*; use std::rc::Rc; -use wasm_bindgen::closure::Closure; -use wasm_bindgen::JsCast; /// Reactive [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notification). /// @@ -53,108 +51,122 @@ pub fn use_web_notification_with_options( let (permission, set_permission) = create_signal(NotificationPermission::default()); - let on_click_closure = Closure::::new({ - let on_click = Rc::clone(&options.on_click); - move |e: web_sys::Event| { - on_click(e); - } - }) - .into_js_value(); + cfg_if! { if #[cfg(feature = "ssr")] { + let _ = options; + let _ = set_notification; + let _ = set_permission; - let on_close_closure = Closure::::new({ - let on_close = Rc::clone(&options.on_close); - move |e: web_sys::Event| { - on_close(e); - } - }) - .into_js_value(); + let show = move |_: ShowOptions| (); + let close = move || (); + } else { + use crate::use_event_listener; + use leptos::ev::visibilitychange; + use wasm_bindgen::closure::Closure; + use wasm_bindgen::JsCast; - let on_error_closure = Closure::::new({ - let on_error = Rc::clone(&options.on_error); - move |e: web_sys::Event| { - on_error(e); - } - }) - .into_js_value(); - - let on_show_closure = Closure::::new({ - let on_show = Rc::clone(&options.on_show); - move |e: web_sys::Event| { - on_show(e); - } - }) - .into_js_value(); - - let show = { - let options = options.clone(); - let on_click_closure = on_click_closure.clone(); - let on_close_closure = on_close_closure.clone(); - let on_error_closure = on_error_closure.clone(); - let on_show_closure = on_show_closure.clone(); - - move |options_override: ShowOptions| { - if !is_supported.get_untracked() { - return; + let on_click_closure = Closure::::new({ + let on_click = Rc::clone(&options.on_click); + move |e: web_sys::Event| { + on_click(e); } + }) + .into_js_value(); + let on_close_closure = Closure::::new({ + let on_close = Rc::clone(&options.on_close); + move |e: web_sys::Event| { + on_close(e); + } + }) + .into_js_value(); + + let on_error_closure = Closure::::new({ + let on_error = Rc::clone(&options.on_error); + move |e: web_sys::Event| { + on_error(e); + } + }) + .into_js_value(); + + let on_show_closure = Closure::::new({ + let on_show = Rc::clone(&options.on_show); + move |e: web_sys::Event| { + on_show(e); + } + }) + .into_js_value(); + + let show = { let options = options.clone(); let on_click_closure = on_click_closure.clone(); let on_close_closure = on_close_closure.clone(); let on_error_closure = on_error_closure.clone(); let on_show_closure = on_show_closure.clone(); - spawn_local(async move { - set_permission.set(request_web_notification_permission().await); + move |options_override: ShowOptions| { + if !is_supported.get_untracked() { + return; + } - let mut notification_options = web_sys::NotificationOptions::from(&options); - options_override.override_notification_options(&mut notification_options); + let options = options.clone(); + let on_click_closure = on_click_closure.clone(); + let on_close_closure = on_close_closure.clone(); + let on_error_closure = on_error_closure.clone(); + let on_show_closure = on_show_closure.clone(); - let notification_value = web_sys::Notification::new_with_options( - &options_override.title.unwrap_or(options.title), - ¬ification_options, - ) - .expect("Notification should be created"); + spawn_local(async move { + set_permission.set(request_web_notification_permission().await); - notification_value.set_onclick(Some(on_click_closure.unchecked_ref())); - notification_value.set_onclose(Some(on_close_closure.unchecked_ref())); - notification_value.set_onerror(Some(on_error_closure.unchecked_ref())); - notification_value.set_onshow(Some(on_show_closure.unchecked_ref())); + let mut notification_options = web_sys::NotificationOptions::from(&options); + options_override.override_notification_options(&mut notification_options); - set_notification.set(Some(notification_value)); - }); - } - }; + let notification_value = web_sys::Notification::new_with_options( + &options_override.title.unwrap_or(options.title), + ¬ification_options, + ) + .expect("Notification should be created"); - let close = { - move || { - notification.with_untracked(|notification| { - if let Some(notification) = notification { - notification.close(); + notification_value.set_onclick(Some(on_click_closure.unchecked_ref())); + notification_value.set_onclose(Some(on_close_closure.unchecked_ref())); + notification_value.set_onerror(Some(on_error_closure.unchecked_ref())); + notification_value.set_onshow(Some(on_show_closure.unchecked_ref())); + + set_notification.set(Some(notification_value)); + }); + } + }; + + let close = { + move || { + notification.with_untracked(|notification| { + if let Some(notification) = notification { + notification.close(); + } + }); + set_notification.set(None); + } + }; + + spawn_local(async move { + set_permission.set(request_web_notification_permission().await); + }); + + on_cleanup(close); + + // Use close() to remove a notification that is no longer relevant to to + // the user (e.g.the user already read the notification on the webpage). + // Most modern browsers dismiss notifications automatically after a few + // moments(around four seconds). + if is_supported.get_untracked() { + let _ = use_event_listener(document(), visibilitychange, move |e: web_sys::Event| { + e.prevent_default(); + if document().visibility_state() == web_sys::VisibilityState::Visible { + // The tab has become visible so clear the now-stale Notification: + close() } }); - set_notification.set(None); } - }; - - spawn_local(async move { - set_permission.set(request_web_notification_permission().await); - }); - - on_cleanup(close); - - // Use close() to remove a notification that is no longer relevant to to - // the user (e.g.the user already read the notification on the webpage). - // Most modern browsers dismiss notifications automatically after a few - // moments(around four seconds). - if is_supported.get_untracked() { - let _ = use_event_listener(document(), visibilitychange, move |e: web_sys::Event| { - e.prevent_default(); - if document().visibility_state() == web_sys::VisibilityState::Visible { - // The tab has become visible so clear the now-stale Notification: - close() - } - }); - } + }} UseWebNotificationReturn { is_supported, @@ -192,6 +204,7 @@ impl From for web_sys::NotificationDirection { /// - `silent` /// - `image` #[derive(DefaultBuilder, Clone)] +#[cfg_attr(feature = "ssr", allow(dead_code))] pub struct UseWebNotificationOptions { /// The title property of the Notification interface indicates /// the title of the notification @@ -302,6 +315,7 @@ impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions { /// - `silent` /// - `image` #[derive(DefaultBuilder, Default)] +#[cfg_attr(feature = "ssr", allow(dead_code))] pub struct ShowOptions { /// The title property of the Notification interface indicates /// the title of the notification @@ -344,6 +358,7 @@ pub struct ShowOptions { // renotify: Option, } +#[cfg(not(feature = "ssr"))] impl ShowOptions { fn override_notification_options(&self, options: &mut web_sys::NotificationOptions) { if let Some(direction) = self.direction { @@ -413,6 +428,7 @@ impl From for NotificationPermission { /// Use `window.Notification.requestPosition()`. Returns a future that should be awaited /// at least once before using [`use_web_notification`] to make sure /// you have the permission to send notifications. +#[cfg(not(feature = "ssr"))] async fn request_web_notification_permission() -> NotificationPermission { if let Ok(notification_permission) = web_sys::Notification::request_permission() { let _ = wasm_bindgen_futures::JsFuture::from(notification_permission).await; diff --git a/src/use_websocket.rs b/src/use_websocket.rs index 5b81a9e..9f40d4f 100644 --- a/src/use_websocket.rs +++ b/src/use_websocket.rs @@ -485,7 +485,7 @@ pub struct UseWebSocketOptions { /// If `false` you have to manually call the `open` function. /// Defaults to `true`. immediate: bool, - /// Sub protocols + /// Sub protocols. See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#protocols). protocols: Option>, }