1#![warn(missing_docs)]
105#![deny(unsafe_code)]
106
107pub mod cache;
108pub mod error;
109pub mod lifecycle;
110pub mod manifest;
111pub mod notifications;
112pub mod service_worker;
113pub mod sync;
114
115pub use error::{PwaError, Result};
117
118pub use cache::{
119 CacheManager, CacheStorageManager,
120 geospatial::{BoundingBox, GeospatialCache, TileCoord},
121 strategies::{CacheStrategy, StrategyType},
122};
123
124pub use lifecycle::{
125 DisplayModeDetection, InstallPrompt, InstallState, PwaLifecycle, UpdateManager,
126};
127
128pub use manifest::{
129 AppIcon, DisplayMode, ManifestBuilder, Orientation, Screenshot, WebAppManifest,
130};
131
132pub use notifications::{
133 NotificationAction, NotificationConfig, NotificationManager, Permission,
134 PushNotificationManager,
135};
136
137pub use service_worker::{
138 ServiceWorkerEvents, ServiceWorkerMessaging, ServiceWorkerRegistry, ServiceWorkerScope,
139 get_registration, get_service_worker_container, is_service_worker_supported,
140 register_service_worker,
141};
142
143pub use sync::{BackgroundSync, QueuedOperation, SyncCoordinator, SyncOptions, SyncQueue};
144
145pub fn initialize() {
147 #[cfg(feature = "console_error_panic_hook")]
148 console_error_panic_hook::set_once();
149}
150
151pub struct PwaConfig {
153 pub service_worker_url: String,
155
156 pub scope: Option<String>,
158
159 pub enable_cache_management: bool,
161
162 pub enable_background_sync: bool,
164
165 pub enable_notifications: bool,
167
168 pub enable_geospatial_cache: bool,
170}
171
172impl Default for PwaConfig {
173 fn default() -> Self {
174 Self {
175 service_worker_url: "/sw.js".to_string(),
176 scope: None,
177 enable_cache_management: true,
178 enable_background_sync: false,
179 enable_notifications: false,
180 enable_geospatial_cache: true,
181 }
182 }
183}
184
185impl PwaConfig {
186 pub fn new() -> Self {
188 Self::default()
189 }
190
191 pub fn with_service_worker_url(mut self, url: impl Into<String>) -> Self {
193 self.service_worker_url = url.into();
194 self
195 }
196
197 pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
199 self.scope = Some(scope.into());
200 self
201 }
202
203 pub fn with_cache_management(mut self, enable: bool) -> Self {
205 self.enable_cache_management = enable;
206 self
207 }
208
209 pub fn with_background_sync(mut self, enable: bool) -> Self {
211 self.enable_background_sync = enable;
212 self
213 }
214
215 pub fn with_notifications(mut self, enable: bool) -> Self {
217 self.enable_notifications = enable;
218 self
219 }
220
221 pub fn with_geospatial_cache(mut self, enable: bool) -> Self {
223 self.enable_geospatial_cache = enable;
224 self
225 }
226}
227
228pub struct PwaApp {
230 config: PwaConfig,
231 lifecycle: PwaLifecycle,
232 cache_manager: Option<CacheManager>,
233 geospatial_cache: Option<GeospatialCache>,
234 notification_manager: Option<NotificationManager>,
235 sync_coordinator: Option<SyncCoordinator>,
236}
237
238impl PwaApp {
239 pub fn new(config: PwaConfig) -> Self {
241 Self {
242 config,
243 lifecycle: PwaLifecycle::new(),
244 cache_manager: None,
245 geospatial_cache: None,
246 notification_manager: None,
247 sync_coordinator: None,
248 }
249 }
250
251 pub fn with_defaults() -> Self {
253 Self::new(PwaConfig::default())
254 }
255
256 pub async fn initialize(&mut self) -> Result<()> {
258 initialize();
260
261 self.lifecycle.initialize()?;
263
264 let registration = register_service_worker(
266 &self.config.service_worker_url,
267 self.config.scope.as_deref(),
268 )
269 .await?;
270
271 self.lifecycle.set_registration(registration.clone());
272
273 if self.config.enable_cache_management {
275 self.cache_manager = Some(CacheManager::new("oxigdal-pwa-cache"));
276 }
277
278 if self.config.enable_geospatial_cache {
280 self.geospatial_cache = Some(GeospatialCache::with_defaults());
281 }
282
283 if self.config.enable_notifications {
285 self.notification_manager = Some(NotificationManager::new());
286 }
287
288 if self.config.enable_background_sync {
290 self.sync_coordinator = Some(SyncCoordinator::new(registration));
291 }
292
293 Ok(())
294 }
295
296 pub fn lifecycle(&self) -> &PwaLifecycle {
298 &self.lifecycle
299 }
300
301 pub fn lifecycle_mut(&mut self) -> &mut PwaLifecycle {
303 &mut self.lifecycle
304 }
305
306 pub fn cache_manager(&self) -> Option<&CacheManager> {
308 self.cache_manager.as_ref()
309 }
310
311 pub fn geospatial_cache(&self) -> Option<&GeospatialCache> {
313 self.geospatial_cache.as_ref()
314 }
315
316 pub fn notification_manager(&self) -> Option<&NotificationManager> {
318 self.notification_manager.as_ref()
319 }
320
321 pub fn sync_coordinator(&self) -> Option<&SyncCoordinator> {
323 self.sync_coordinator.as_ref()
324 }
325
326 pub fn sync_coordinator_mut(&mut self) -> Option<&mut SyncCoordinator> {
328 self.sync_coordinator.as_mut()
329 }
330
331 pub fn is_pwa(&self) -> bool {
333 self.lifecycle.is_pwa()
334 }
335
336 pub fn can_install(&self) -> bool {
338 self.lifecycle.can_install()
339 }
340
341 pub async fn prompt_install(&mut self) -> Result<bool> {
343 self.lifecycle.prompt_install().await
344 }
345
346 pub fn config(&self) -> &PwaConfig {
348 &self.config
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_pwa_config() {
358 let config = PwaConfig::new()
359 .with_service_worker_url("/custom-sw.js")
360 .with_scope("/app")
361 .with_cache_management(true)
362 .with_geospatial_cache(true);
363
364 assert_eq!(config.service_worker_url, "/custom-sw.js");
365 assert_eq!(config.scope, Some("/app".to_string()));
366 assert!(config.enable_cache_management);
367 assert!(config.enable_geospatial_cache);
368 }
369
370 #[test]
371 #[cfg(target_arch = "wasm32")]
372 fn test_pwa_app_creation() {
373 let app = PwaApp::with_defaults();
374 assert_eq!(app.config.service_worker_url, "/sw.js");
375 assert!(app.cache_manager.is_none()); }
377
378 #[test]
379 fn test_pwa_config_builder() {
380 let config = PwaConfig::default();
381 assert_eq!(config.service_worker_url, "/sw.js");
382 assert!(config.enable_cache_management);
383 assert!(config.enable_geospatial_cache);
384 }
385}