firebase_rs_sdk/messaging/
sw_manager.rs1#[cfg(all(
2 feature = "wasm-web",
3 target_arch = "wasm32",
4 feature = "experimental-indexed-db"
5))]
6mod wasm {
7 use js_sys::Reflect;
8 use wasm_bindgen::JsCast;
9 use wasm_bindgen::JsValue;
10 use wasm_bindgen_futures::JsFuture;
11
12 use crate::messaging::constants::{
13 DEFAULT_REGISTRATION_TIMEOUT_MS, DEFAULT_SW_PATH, DEFAULT_SW_SCOPE,
14 REGISTRATION_POLL_INTERVAL_MS,
15 };
16 use crate::messaging::error::{
17 available_in_window, failed_default_registration, unsupported_browser, MessagingResult,
18 };
19 use crate::platform::runtime;
20
21 #[derive(Clone)]
23 pub struct ServiceWorkerRegistrationHandle {
24 inner: web_sys::ServiceWorkerRegistration,
25 }
26
27 impl ServiceWorkerRegistrationHandle {
28 fn new(inner: web_sys::ServiceWorkerRegistration) -> Self {
29 Self { inner }
30 }
31
32 pub fn as_web_sys(&self) -> &web_sys::ServiceWorkerRegistration {
34 &self.inner
35 }
36 }
37
38 #[derive(Default)]
42 pub struct ServiceWorkerManager {
43 registration: Option<ServiceWorkerRegistrationHandle>,
44 }
45
46 impl ServiceWorkerManager {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn registration(&self) -> Option<ServiceWorkerRegistrationHandle> {
53 self.registration.clone()
54 }
55
56 pub fn use_registration(
58 &mut self,
59 registration: web_sys::ServiceWorkerRegistration,
60 ) -> ServiceWorkerRegistrationHandle {
61 let handle = ServiceWorkerRegistrationHandle::new(registration);
62 self.registration = Some(handle.clone());
63 handle
64 }
65
66 pub async fn register_default(
68 &mut self,
69 ) -> MessagingResult<ServiceWorkerRegistrationHandle> {
70 if let Some(handle) = &self.registration {
71 return Ok(handle.clone());
72 }
73
74 let window = web_sys::window().ok_or_else(|| {
75 available_in_window("Service worker registration requires a Window context")
76 })?;
77 let navigator = window.navigator();
78 let navigator_js = JsValue::from(navigator.clone());
79 let container_value = Reflect::get(&navigator_js, &JsValue::from_str("serviceWorker"))
80 .map_err(|_| {
81 unsupported_browser(
82 "Service workers are not available in this browser environment.",
83 )
84 })?;
85 if container_value.is_undefined() || container_value.is_null() {
86 return Err(unsupported_browser(
87 "Service workers are not available in this browser environment.",
88 ));
89 }
90 let container: web_sys::ServiceWorkerContainer =
91 container_value.dyn_into().map_err(|_| {
92 unsupported_browser(
93 "Service workers are not available in this browser environment.",
94 )
95 })?;
96
97 let options = web_sys::RegistrationOptions::new();
98 options.set_scope(DEFAULT_SW_SCOPE);
99
100 let promise = container.register_with_options(DEFAULT_SW_PATH, &options);
101 let registration_js = JsFuture::from(promise).await.map_err(|err| {
102 failed_default_registration(format_js_error("serviceWorker.register", err))
103 })?;
104 let registration: web_sys::ServiceWorkerRegistration =
105 registration_js.dyn_into().map_err(|_| {
106 failed_default_registration(
107 "Unexpected return value from serviceWorker.register",
108 )
109 })?;
110
111 if let Ok(update_promise) = registration.update() {
112 let _ = JsFuture::from(update_promise).await;
113 }
114
115 wait_for_registration_active(®istration).await?;
116
117 let handle = ServiceWorkerRegistrationHandle::new(registration);
118 self.registration = Some(handle.clone());
119 Ok(handle)
120 }
121 }
122
123 async fn wait_for_registration_active(
124 registration: &web_sys::ServiceWorkerRegistration,
125 ) -> MessagingResult<()> {
126 if registration.active().is_some() {
127 return Ok(());
128 }
129
130 let mut elapsed = 0;
131 while elapsed < DEFAULT_REGISTRATION_TIMEOUT_MS {
132 if registration.active().is_some() {
133 return Ok(());
134 }
135
136 if registration.installing().is_none() && registration.waiting().is_none() {
137 return Err(failed_default_registration(
138 "No incoming service worker found during registration.",
139 ));
140 }
141
142 sleep_ms(REGISTRATION_POLL_INTERVAL_MS).await?;
143 elapsed += REGISTRATION_POLL_INTERVAL_MS;
144 }
145
146 Err(failed_default_registration(format!(
147 "Service worker not registered after {} ms",
148 DEFAULT_REGISTRATION_TIMEOUT_MS
149 )))
150 }
151
152 async fn sleep_ms(ms: i32) -> MessagingResult<()> {
153 if ms <= 0 {
154 return Ok(());
155 }
156 let duration = std::time::Duration::from_millis(ms as u64);
157 runtime::sleep(duration).await;
158 Ok(())
159 }
160
161 fn format_js_error(operation: &str, err: JsValue) -> String {
162 let detail = err.as_string().unwrap_or_else(|| format!("{:?}", err));
163 format!("{operation} failed: {detail}")
164 }
165
166 pub use ServiceWorkerManager as Manager;
167 pub use ServiceWorkerRegistrationHandle as Handle;
168}
169
170#[cfg(all(
171 feature = "wasm-web",
172 target_arch = "wasm32",
173 feature = "experimental-indexed-db"
174))]
175pub use wasm::{Handle as ServiceWorkerRegistrationHandle, Manager as ServiceWorkerManager};
176
177#[cfg(any(
178 not(all(feature = "wasm-web", target_arch = "wasm32")),
179 all(
180 feature = "wasm-web",
181 target_arch = "wasm32",
182 not(feature = "experimental-indexed-db")
183 )
184))]
185#[derive(Default)]
186pub struct ServiceWorkerManager;
187
188#[cfg(any(
189 not(all(feature = "wasm-web", target_arch = "wasm32")),
190 all(
191 feature = "wasm-web",
192 target_arch = "wasm32",
193 not(feature = "experimental-indexed-db")
194 )
195))]
196impl ServiceWorkerManager {
197 pub fn new() -> Self {
198 Self
199 }
200
201 pub fn registration(&self) -> Option<ServiceWorkerRegistrationHandle> {
202 None
203 }
204
205 pub async fn register_default(
206 &mut self,
207 ) -> crate::messaging::error::MessagingResult<ServiceWorkerRegistrationHandle> {
208 Err(crate::messaging::error::unsupported_browser(
209 "Service worker registration is only available when the `wasm-web` feature is enabled.",
210 ))
211 }
212}
213
214#[cfg(any(
215 not(all(feature = "wasm-web", target_arch = "wasm32")),
216 all(
217 feature = "wasm-web",
218 target_arch = "wasm32",
219 not(feature = "experimental-indexed-db")
220 )
221))]
222#[derive(Clone, Debug)]
223pub struct ServiceWorkerRegistrationHandle;
224
225#[cfg(all(
226 test,
227 any(
228 not(all(feature = "wasm-web", target_arch = "wasm32")),
229 all(
230 feature = "wasm-web",
231 target_arch = "wasm32",
232 not(feature = "experimental-indexed-db")
233 )
234 )
235))]
236mod tests {
237 use super::*;
238
239 #[tokio::test(flavor = "current_thread")]
240 async fn native_manager_reports_unsupported() {
241 let mut manager = ServiceWorkerManager::new();
242 assert!(manager.registration().is_none());
243 let err = manager.register_default().await.unwrap_err();
244 assert_eq!(err.code_str(), "messaging/unsupported-browser");
245 }
246}