leptos_use_web_lock/use_web_lock.rs
1use crate::UseWebLockOptions;
2use std::future::Future;
3use thiserror::Error;
4use wasm_bindgen::JsValue;
5pub use web_sys::LockMode;
6
7/// Rustified [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API).
8///
9/// The **Web Locks API** allows scripts running in one tab or worker to asynchronously acquire a
10/// lock, hold it while work is performed, then release it. While held, no other script executing
11/// in the same origin can acquire the same lock, which allows a web app running in multiple tabs or
12/// workers to coordinate work and the use of resources.
13///
14/// > This function requires `--cfg=web_sys_unstable_apis` to be activated as
15/// > [described in the wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html).
16///
17/// ## Demo
18///
19/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_web_lock)
20///
21/// ## Usage
22///
23/// ```
24/// # use leptos::prelude::*;
25/// # use leptos_use_web_lock::use_web_lock;
26/// #
27/// async fn my_process(_lock: web_sys::Lock) -> i32 {
28/// // do sth
29/// 42
30/// }
31///
32/// # #[component]
33/// # fn Demo() -> impl IntoView {
34/// leptos::task::spawn_local(async {
35/// let res = use_web_lock("my_lock", my_process).await;
36/// assert!(matches!(res, Ok(42)));
37/// });
38/// #
39/// # view! { }
40/// # }
41/// ```
42///
43/// ## Server-Side Rendering
44///
45/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
46///
47/// On the server this returns `Err(UseWebLockError::Server)` and the task is not executed.
48pub async fn use_web_lock<C, F, R>(name: &str, callback: C) -> Result<R, UseWebLockError>
49where
50 C: FnOnce(web_sys::Lock) -> F + 'static,
51 F: Future<Output = R>,
52 R: 'static,
53{
54 use_web_lock_with_options(name, callback, UseWebLockOptions::default()).await
55}
56
57/// Version of [`fn@crate::use_web_lock`] that takes a `UseWebLockOptions`. See [`fn@crate::use_web_lock`] for how to use.
58pub async fn use_web_lock_with_options<C, F, R>(
59 name: &str,
60 callback: C,
61 options: UseWebLockOptions,
62) -> Result<R, UseWebLockError>
63where
64 C: FnOnce(web_sys::Lock) -> F + 'static,
65 F: Future<Output = R>,
66 R: 'static,
67{
68 #[cfg(feature = "ssr")]
69 {
70 let _ = name;
71 let _ = callback;
72 let _ = options;
73
74 Err(UseWebLockError::Server)
75 }
76
77 #[cfg(not(feature = "ssr"))]
78 {
79 use crate::js_fut;
80 use leptos::prelude::window;
81 use std::sync::{Arc, Mutex};
82 use wasm_bindgen::JsCast;
83 use wasm_bindgen::closure::Closure;
84 use wasm_bindgen_futures::future_to_promise;
85
86 let ret_value = Arc::new(Mutex::new(None));
87
88 let handler = Closure::once(Box::new({
89 let ret_value = Arc::clone(&ret_value);
90
91 move |lock| {
92 future_to_promise(async move {
93 let ret = callback(lock).await;
94 ret_value.lock().expect("Lock failed").replace(ret);
95 Ok(JsValue::null())
96 })
97 }
98 }) as Box<dyn FnOnce(web_sys::Lock) -> _>)
99 .into_js_value();
100
101 let lock_promise = window().navigator().locks().request_with_options(
102 name,
103 &options.to_web_sys(),
104 handler.unchecked_ref(),
105 );
106
107 js_fut!(lock_promise)
108 .await
109 .map(move |_| {
110 Arc::into_inner(ret_value)
111 .expect("Arc has more than one reference still")
112 .into_inner()
113 .expect("Lock failed")
114 .expect("Return value was None")
115 })
116 .map_err(UseWebLockError::Failed)
117 }
118}
119
120#[derive(Error, Debug)]
121pub enum UseWebLockError {
122 #[error("Lock cannot be acquired on the server")]
123 Server,
124
125 #[error("Lock failed")]
126 Failed(JsValue),
127}