made use_service_worker more in line with the other functions

This commit is contained in:
Maccesch 2023-10-24 00:36:32 -05:00
parent 2b405b3504
commit 96cc7f7399
3 changed files with 73 additions and 62 deletions

1
.idea/leptos-use.iml generated
View file

@ -57,6 +57,7 @@
<sourceFolder url="file://$MODULE_DIR$/examples/use_idle/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/use_idle/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_timestamp/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/use_timestamp/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_sorted/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/examples/use_sorted/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_service_worker/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" /> <excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book/book" /> <excludeFolder url="file://$MODULE_DIR$/docs/book/book" />

View file

@ -1,6 +1,6 @@
use leptos::*; use leptos::*;
use leptos_use::docs::demo_or_body; use leptos_use::docs::{demo_or_body, BooleanDisplay};
use leptos_use::{use_service_worker, use_window}; use leptos_use::{use_document, use_service_worker, UseServiceWorkerReturn};
use web_sys::HtmlMetaElement; use web_sys::HtmlMetaElement;
#[component] #[component]
@ -9,21 +9,28 @@ fn Demo() -> impl IntoView {
.map(|meta| meta.content()) .map(|meta| meta.content())
.expect("'version' meta element"); .expect("'version' meta element");
let sw = use_service_worker(); let UseServiceWorkerReturn {
registration,
installing,
waiting,
active,
skip_waiting,
..
} = use_service_worker();
view! { view! {
<p>"Current build: "{build}</p> <p>"Current build: " {build}</p>
<br/> <br/>
<p>"registration: "{move || format!("{:#?}", sw.registration.get())}</p> <p>"registration: " {move || format!("{:#?}", registration())}</p>
<p>"installing: "{move || sw.installing.get()}</p> <p>"installing: " <BooleanDisplay value=installing/></p>
<p>"waiting: "{move || sw.waiting.get()}</p> <p>"waiting: " <BooleanDisplay value=waiting/></p>
<p>"active: "{move || sw.active.get()}</p> <p>"active: " <BooleanDisplay value=active/></p>
<br/> <br/>
<button on:click=move |_| {sw.skip_waiting.call(())}>"Send skipWaiting event"</button> <button on:click=move |_| { skip_waiting() }>"Send skip_waiting event"</button>
} }
} }
@ -36,26 +43,17 @@ fn main() {
}) })
} }
fn load_meta_element<S: AsRef<str>>(name: S) -> Result<web_sys::HtmlMetaElement, String> { fn load_meta_element(name: &str) -> Result<web_sys::HtmlMetaElement, String> {
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use_window() if let Some(document) = &*use_document() {
.as_ref() document
.ok_or_else(|| "No window instance!".to_owned()) .query_selector(format!("meta[name=\"{name}\"]").as_str())
.and_then(|window| { .ok()
window .flatten()
.document() .ok_or_else(|| format!("Unable to find meta element with name '{name}'."))?
.ok_or_else(|| "No document instance!".to_owned()) .dyn_into::<HtmlMetaElement>()
}) .map_err(|err| format!("Unable to cast element to HtmlMetaElement. Err: '{err:?}'."))
.and_then(|document| { } else {
document Err("Unable to find document.".into())
.query_selector(format!("meta[name=\"{}\"]", name.as_ref()).as_str()) }
.ok()
.flatten()
.ok_or_else(|| format!("Unable to find meta element with name 'version'."))
})
.and_then(|element| {
element.dyn_into::<HtmlMetaElement>().map_err(|err| {
format!("Unable to cast element to HtmlMetaElement. Err: '{err:?}'.")
})
})
} }

View file

@ -1,46 +1,52 @@
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::*; use leptos::*;
use std::borrow::Cow; use std::rc::Rc;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::ServiceWorkerRegistration; use web_sys::ServiceWorkerRegistration;
use crate::use_window; use crate::use_window;
/// Reactive [ServiceWorker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API).
/// ///
/// /// Please check the [working example](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_service_worker).
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_service_worker)
/// ///
/// ## Usage /// ## Usage
/// ///
/// ``` /// ```
/// # use leptos::*; /// # use leptos::*;
/// # use leptos_use::use_service_worker; /// # use leptos_use::{use_service_worker_with_options, UseServiceWorkerOptions, UseServiceWorkerReturn};
/// # /// #
/// # #[component] /// # #[component]
/// # fn Demo() -> impl IntoView { /// # fn Demo() -> impl IntoView {
/// # let sw = use_service_worker_with_options(UseServiceWorkerOptions { /// let UseServiceWorkerReturn {
/// # script_url: "service-worker.js".into(), /// registration,
/// # skip_waiting_message: "skipWaiting".into(), /// installing,
/// # ..UseServiceWorkerOptions::default() /// waiting,
/// # }); /// active,
/// # /// skip_waiting,
/// # view! { } /// check_for_update,
/// } = use_service_worker_with_options(UseServiceWorkerOptions::default()
/// .script_url("service-worker.js")
/// .skip_waiting_message("skipWaiting"),
/// );
///
/// # view! { }
/// # } /// # }
/// ``` /// ```
pub fn use_service_worker() -> UseServiceWorkerReturn { pub fn use_service_worker() -> UseServiceWorkerReturn<impl Fn() + Clone, impl Fn() + Clone> {
use_service_worker_with_options(UseServiceWorkerOptions::default()) use_service_worker_with_options(UseServiceWorkerOptions::default())
} }
/// Version of [`use_service_worker`] that takes a `UseServiceWorkerOptions`. See [`use_service_worker`] for how to use. /// Version of [`use_service_worker`] that takes a `UseServiceWorkerOptions`. See [`use_service_worker`] for how to use.
pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseServiceWorkerReturn { pub fn use_service_worker_with_options(
options: UseServiceWorkerOptions,
) -> UseServiceWorkerReturn<impl Fn() + Clone, impl Fn() + Clone> {
// Trigger the user-defined action (page-reload by default) // Trigger the user-defined action (page-reload by default)
// whenever a new ServiceWorker is installed. // whenever a new ServiceWorker is installed.
if let Some(navigator) = use_window().navigator() { if let Some(navigator) = use_window().navigator() {
let on_controller_change = options.on_controller_change.clone(); let on_controller_change = options.on_controller_change.clone();
let js_closure = Closure::wrap(Box::new(move |_event: JsValue| { let js_closure = Closure::wrap(Box::new(move |_event: JsValue| {
on_controller_change.call(()); on_controller_change();
}) as Box<dyn FnMut(JsValue)>) }) as Box<dyn FnMut(JsValue)>)
.into_js_value(); .into_js_value();
navigator navigator
@ -95,7 +101,7 @@ pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseS
} }
Err(err) => match err { Err(err) => match err {
ServiceWorkerRegistrationError::Js(err) => { ServiceWorkerRegistrationError::Js(err) => {
tracing::warn!("ServiceWorker registration failed: {err:?}") logging::warn!("ServiceWorker registration failed: {err:?}")
} }
ServiceWorkerRegistrationError::NeverQueried => {} ServiceWorkerRegistrationError::NeverQueried => {}
}, },
@ -125,28 +131,28 @@ pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseS
.unwrap_or_default() .unwrap_or_default()
}) })
}), }),
check_for_update: Callback::new(move |()| { check_for_update: move || {
registration.with(|reg| { registration.with(|reg| {
if let Ok(reg) = reg { if let Ok(reg) = reg {
update_sw.dispatch(reg.clone()) update_sw.dispatch(reg.clone())
} }
}) })
}), },
skip_waiting: Callback::new(move |()| { skip_waiting: move || {
registration.with_untracked(|reg| if let Ok(reg) = reg { registration.with_untracked(|reg| if let Ok(reg) = reg {
match reg.waiting() { match reg.waiting() {
Some(sw) => { Some(sw) => {
tracing::info!("Updating to newly installed SW..."); logging::debug_warn!("Updating to newly installed SW...");
if let Err(err) = sw.post_message(&JsValue::from_str(&options.skip_waiting_message)) { if let Err(err) = sw.post_message(&JsValue::from_str(&options.skip_waiting_message)) {
tracing::warn!("Could not send message to active SW: Error: {err:?}"); logging::warn!("Could not send message to active SW: Error: {err:?}");
} }
}, },
None => { None => {
tracing::warn!("You tried to update the SW while no new SW was waiting. This is probably a bug."); logging::warn!("You tried to update the SW while no new SW was waiting. This is probably a bug.");
}, },
} }
}); });
}), },
} }
} }
@ -155,16 +161,18 @@ pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseS
pub struct UseServiceWorkerOptions { pub struct UseServiceWorkerOptions {
/// The name of your service-worker file. Must be deployed alongside your app. /// The name of your service-worker file. Must be deployed alongside your app.
/// The default name is 'service-worker.js'. /// The default name is 'service-worker.js'.
pub script_url: Cow<'static, str>, #[builder(into)]
script_url: String,
/// The message sent to a waiting ServiceWorker when you call the `skip_waiting` callback. /// The message sent to a waiting ServiceWorker when you call the `skip_waiting` callback.
/// The callback is part of the return type of [`use_service_worker`]! /// The callback is part of the return type of [`use_service_worker`]!
/// The default message is 'skipWaiting'. /// The default message is 'skipWaiting'.
pub skip_waiting_message: Cow<'static, str>, #[builder(into)]
skip_waiting_message: String,
/// What should happen when a new service worker was activated? /// What should happen when a new service worker was activated?
/// The default implementation reloads the current page. /// The default implementation reloads the current page.
pub on_controller_change: Callback<()>, on_controller_change: Rc<dyn Fn()>,
} }
impl Default for UseServiceWorkerOptions { impl Default for UseServiceWorkerOptions {
@ -172,11 +180,11 @@ impl Default for UseServiceWorkerOptions {
Self { Self {
script_url: "service-worker.js".into(), script_url: "service-worker.js".into(),
skip_waiting_message: "skipWaiting".into(), skip_waiting_message: "skipWaiting".into(),
on_controller_change: Callback::new(move |()| { on_controller_change: Rc::new(move || {
use std::ops::Deref; use std::ops::Deref;
if let Some(window) = use_window().deref() { if let Some(window) = use_window().deref() {
if let Err(err) = window.location().reload() { if let Err(err) = window.location().reload() {
tracing::warn!( logging::warn!(
"Detected a ServiceWorkerController change but the page reload failed! Error: {err:?}" "Detected a ServiceWorkerController change but the page reload failed! Error: {err:?}"
); );
} }
@ -187,7 +195,11 @@ impl Default for UseServiceWorkerOptions {
} }
/// Return type of [`use_service_worker`]. /// Return type of [`use_service_worker`].
pub struct UseServiceWorkerReturn { pub struct UseServiceWorkerReturn<CheckFn, SkipFn>
where
CheckFn: Fn() + Clone,
SkipFn: Fn() + Clone,
{
/// The current registration state. /// The current registration state.
pub registration: Signal<Result<ServiceWorkerRegistration, ServiceWorkerRegistrationError>>, pub registration: Signal<Result<ServiceWorkerRegistration, ServiceWorkerRegistrationError>>,
@ -201,11 +213,11 @@ pub struct UseServiceWorkerReturn {
pub active: Signal<bool>, pub active: Signal<bool>,
/// Check for a ServiceWorker update. /// Check for a ServiceWorker update.
pub check_for_update: Callback<()>, pub check_for_update: CheckFn,
/// Call this to activate a new ("waiting") SW if one is available. /// Call this to activate a new ("waiting") SW if one is available.
/// Calling this while the [`UseServiceWorkerReturn::waiting`] signal resolves to false has no effect. /// Calling this while the [`UseServiceWorkerReturn::waiting`] signal resolves to false has no effect.
pub skip_waiting: Callback<()>, pub skip_waiting: SkipFn,
} }
struct ServiceWorkerScriptUrl(pub String); struct ServiceWorkerScriptUrl(pub String);