use_web_notification is now SSR-safe.

relates to #46
This commit is contained in:
Maccesch 2023-12-06 00:10:33 +00:00
parent a3dc18064f
commit 5f5779ed10
5 changed files with 119 additions and 96 deletions

View file

@ -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

View file

@ -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::<Result<MediaStream, JsValue>>);
let (stream, set_stream) = create_signal(None::<Result<web_sys::MediaStream, JsValue>>);
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<MediaStream, JsValue> {
async fn create_media(audio: bool) -> Result<web_sys::MediaStream, JsValue> {
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<MediaStream, JsValue> {
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<Option<Result<MediaStream, JsValue>>>,
pub stream: Signal<Option<Result<web_sys::MediaStream, JsValue>>>,
/// Starts the screen streaming. Triggers the ask for permission if not already granted.
pub start: StartFn,

View file

@ -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<impl Fn() + Clone, impl Fn() + Clone> {
use_service_worker_with_options(UseServiceWorkerOptions::default())
}

View file

@ -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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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::<dyn Fn(web_sys::Event)>::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),
&notification_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),
&notification_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<NotificationDirection> 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<bool>,
}
#[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<web_sys::NotificationPermission> 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;

View file

@ -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<Vec<String>>,
}