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_timestamp/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$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book/book" />

View file

@ -1,6 +1,6 @@
use leptos::*;
use leptos_use::docs::demo_or_body;
use leptos_use::{use_service_worker, use_window};
use leptos_use::docs::{demo_or_body, BooleanDisplay};
use leptos_use::{use_document, use_service_worker, UseServiceWorkerReturn};
use web_sys::HtmlMetaElement;
#[component]
@ -9,21 +9,28 @@ fn Demo() -> impl IntoView {
.map(|meta| meta.content())
.expect("'version' meta element");
let sw = use_service_worker();
let UseServiceWorkerReturn {
registration,
installing,
waiting,
active,
skip_waiting,
..
} = use_service_worker();
view! {
<p>"Current build: "{build}</p>
<p>"Current build: " {build}</p>
<br/>
<p>"registration: "{move || format!("{:#?}", sw.registration.get())}</p>
<p>"installing: "{move || sw.installing.get()}</p>
<p>"waiting: "{move || sw.waiting.get()}</p>
<p>"active: "{move || sw.active.get()}</p>
<p>"registration: " {move || format!("{:#?}", registration())}</p>
<p>"installing: " <BooleanDisplay value=installing/></p>
<p>"waiting: " <BooleanDisplay value=waiting/></p>
<p>"active: " <BooleanDisplay value=active/></p>
<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_window()
.as_ref()
.ok_or_else(|| "No window instance!".to_owned())
.and_then(|window| {
window
.document()
.ok_or_else(|| "No document instance!".to_owned())
})
.and_then(|document| {
document
.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:?}'.")
})
})
if let Some(document) = &*use_document() {
document
.query_selector(format!("meta[name=\"{name}\"]").as_str())
.ok()
.flatten()
.ok_or_else(|| format!("Unable to find meta element with name '{name}'."))?
.dyn_into::<HtmlMetaElement>()
.map_err(|err| format!("Unable to cast element to HtmlMetaElement. Err: '{err:?}'."))
} else {
Err("Unable to find document.".into())
}
}

View file

@ -1,46 +1,52 @@
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::borrow::Cow;
use std::rc::Rc;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::ServiceWorkerRegistration;
use crate::use_window;
/// Reactive [ServiceWorker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API).
///
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_service_worker)
/// Please check the [working example](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_service_worker).
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_service_worker;
/// # use leptos_use::{use_service_worker_with_options, UseServiceWorkerOptions, UseServiceWorkerReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// # let sw = use_service_worker_with_options(UseServiceWorkerOptions {
/// # script_url: "service-worker.js".into(),
/// # skip_waiting_message: "skipWaiting".into(),
/// # ..UseServiceWorkerOptions::default()
/// # });
/// #
/// # view! { }
/// let UseServiceWorkerReturn {
/// registration,
/// installing,
/// waiting,
/// active,
/// skip_waiting,
/// 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())
}
/// 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)
// whenever a new ServiceWorker is installed.
if let Some(navigator) = use_window().navigator() {
let on_controller_change = options.on_controller_change.clone();
let js_closure = Closure::wrap(Box::new(move |_event: JsValue| {
on_controller_change.call(());
on_controller_change();
}) as Box<dyn FnMut(JsValue)>)
.into_js_value();
navigator
@ -95,7 +101,7 @@ pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseS
}
Err(err) => match err {
ServiceWorkerRegistrationError::Js(err) => {
tracing::warn!("ServiceWorker registration failed: {err:?}")
logging::warn!("ServiceWorker registration failed: {err:?}")
}
ServiceWorkerRegistrationError::NeverQueried => {}
},
@ -125,28 +131,28 @@ pub fn use_service_worker_with_options(options: UseServiceWorkerOptions) -> UseS
.unwrap_or_default()
})
}),
check_for_update: Callback::new(move |()| {
check_for_update: move || {
registration.with(|reg| {
if let Ok(reg) = reg {
update_sw.dispatch(reg.clone())
}
})
}),
skip_waiting: Callback::new(move |()| {
},
skip_waiting: move || {
registration.with_untracked(|reg| if let Ok(reg) = reg {
match reg.waiting() {
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)) {
tracing::warn!("Could not send message to active SW: Error: {err:?}");
logging::warn!("Could not send message to active SW: Error: {err:?}");
}
},
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 {
/// The name of your service-worker file. Must be deployed alongside your app.
/// 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 callback is part of the return type of [`use_service_worker`]!
/// 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?
/// The default implementation reloads the current page.
pub on_controller_change: Callback<()>,
on_controller_change: Rc<dyn Fn()>,
}
impl Default for UseServiceWorkerOptions {
@ -172,11 +180,11 @@ impl Default for UseServiceWorkerOptions {
Self {
script_url: "service-worker.js".into(),
skip_waiting_message: "skipWaiting".into(),
on_controller_change: Callback::new(move |()| {
on_controller_change: Rc::new(move || {
use std::ops::Deref;
if let Some(window) = use_window().deref() {
if let Err(err) = window.location().reload() {
tracing::warn!(
logging::warn!(
"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`].
pub struct UseServiceWorkerReturn {
pub struct UseServiceWorkerReturn<CheckFn, SkipFn>
where
CheckFn: Fn() + Clone,
SkipFn: Fn() + Clone,
{
/// The current registration state.
pub registration: Signal<Result<ServiceWorkerRegistration, ServiceWorkerRegistrationError>>,
@ -201,11 +213,11 @@ pub struct UseServiceWorkerReturn {
pub active: Signal<bool>,
/// 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.
/// 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);