UNPKG

@ordojs/mobile

Version:

Mobile and PWA support for OrdoJS applications

1 lines 13.5 kB
{"version":3,"sources":["../src/pwa.ts"],"names":["PWAManager","config","swConfig","manifest","strategies","strategy","pattern","resolve","reject","promptEvent","e","newConfig"],"mappings":"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","file":"pwa.mjs","sourcesContent":["import type { CacheStrategy, PWAConfig, ServiceWorkerConfig } from './types';\n\nexport class PWAManager {\n private config: PWAConfig;\n private swConfig: ServiceWorkerConfig;\n\n constructor(config: PWAConfig, swConfig: ServiceWorkerConfig) {\n this.config = config;\n this.swConfig = swConfig;\n }\n\n /**\n * Generates the web app manifest\n */\n generateManifest(): string {\n const manifest = {\n name: this.config.name,\n short_name: this.config.shortName,\n description: this.config.description,\n start_url: this.config.startUrl,\n display: this.config.display,\n theme_color: this.config.themeColor,\n background_color: this.config.backgroundColor,\n icons: this.config.icons,\n categories: this.config.categories,\n lang: this.config.lang,\n dir: this.config.dir,\n orientation: this.config.orientation,\n scope: this.config.scope,\n prefer_related_applications: this.config.preferRelatedApplications,\n related_applications: this.config.relatedApplications\n };\n\n return JSON.stringify(manifest, null, 2);\n }\n\n /**\n * Generates the service worker script\n */\n generateServiceWorker(): string {\n const swScript = `\n// OrdoJS Service Worker\nconst CACHE_NAME = 'ordojs-cache-v1';\nconst STATIC_CACHE = 'static-cache-v1';\nconst DYNAMIC_CACHE = 'dynamic-cache-v1';\n\n// Install event\nself.addEventListener('install', (event) => {\n console.log('Service Worker installing...');\n event.waitUntil(\n caches.open(STATIC_CACHE).then((cache) => {\n return cache.addAll([\n '/',\n '/offline.html',\n '/static/css/main.css',\n '/static/js/main.js'\n ]);\n })\n );\n self.skipWaiting();\n});\n\n// Activate event\nself.addEventListener('activate', (event) => {\n console.log('Service Worker activating...');\n event.waitUntil(\n caches.keys().then((cacheNames) => {\n return Promise.all(\n cacheNames.map((cacheName) => {\n if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {\n return caches.delete(cacheName);\n }\n })\n );\n })\n );\n self.clients.claim();\n});\n\n// Fetch event\nself.addEventListener('fetch', (event) => {\n const { request } = event;\n const url = new URL(request.url);\n\n // Handle different cache strategies\n ${this.generateCacheStrategies()}\n\n // Background sync\n ${this.generateBackgroundSync()}\n\n // Push notifications\n ${this.generatePushNotifications()}\n});\n\n${this.generateCacheStrategies()}\n${this.generateBackgroundSync()}\n${this.generatePushNotifications()}\n `.trim();\n\n return swScript;\n }\n\n /**\n * Generates cache strategies for different resource types\n */\n private generateCacheStrategies(): string {\n let strategies = '';\n\n this.swConfig.cacheStrategies.forEach(strategy => {\n strategies += `\n // ${strategy.name} strategy\n if (${this.generatePatternMatcher(strategy.pattern)}) {\n event.respondWith(\n ${this.generateStrategyHandler(strategy)}\n );\n return;\n }\n `;\n });\n\n return strategies;\n }\n\n /**\n * Generates pattern matcher for cache strategies\n */\n private generatePatternMatcher(pattern: string): string {\n if (pattern.includes('*')) {\n const regex = pattern.replace(/\\*/g, '.*');\n return `url.pathname.match(/^${regex}$/)`;\n }\n return `url.pathname === '${pattern}'`;\n }\n\n /**\n * Generates strategy handler for different cache strategies\n */\n private generateStrategyHandler(strategy: CacheStrategy): string {\n switch (strategy.strategy) {\n case 'cache-first':\n return `\n caches.match(request).then((response) => {\n return response || fetch(request).then((fetchResponse) => {\n return caches.open(DYNAMIC_CACHE).then((cache) => {\n cache.put(request, fetchResponse.clone());\n return fetchResponse;\n });\n });\n })\n `;\n case 'network-first':\n return `\n fetch(request).then((response) => {\n return caches.open(DYNAMIC_CACHE).then((cache) => {\n cache.put(request, response.clone());\n return response;\n });\n }).catch(() => {\n return caches.match(request);\n })\n `;\n case 'stale-while-revalidate':\n return `\n caches.match(request).then((cachedResponse) => {\n const fetchPromise = fetch(request).then((networkResponse) => {\n return caches.open(DYNAMIC_CACHE).then((cache) => {\n cache.put(request, networkResponse.clone());\n return networkResponse;\n });\n });\n return cachedResponse || fetchPromise;\n })\n `;\n case 'network-only':\n return `fetch(request)`;\n case 'cache-only':\n return `caches.match(request)`;\n default:\n return `fetch(request)`;\n }\n }\n\n /**\n * Generates background sync functionality\n */\n private generateBackgroundSync(): string {\n if (!this.swConfig.backgroundSync.enabled) {\n return '';\n }\n\n return `\n// Background sync\nself.addEventListener('sync', (event) => {\n if (event.tag === '${this.swConfig.backgroundSync.syncName}') {\n event.waitUntil(\n // Perform background sync operations\n console.log('Background sync triggered');\n );\n }\n});\n `;\n }\n\n /**\n * Generates push notification functionality\n */\n private generatePushNotifications(): string {\n if (!this.swConfig.pushNotifications.enabled) {\n return '';\n }\n\n return `\n// Push notifications\nself.addEventListener('push', (event) => {\n const options = {\n body: event.data ? event.data.text() : 'New notification',\n icon: '/icon-192x192.png',\n badge: '/badge-72x72.png',\n vibrate: [100, 50, 100],\n data: {\n dateOfArrival: Date.now(),\n primaryKey: 1\n },\n actions: [\n {\n action: 'explore',\n title: 'View',\n icon: '/icon-192x192.png'\n },\n {\n action: 'close',\n title: 'Close',\n icon: '/icon-192x192.png'\n }\n ]\n };\n\n event.waitUntil(\n self.registration.showNotification('${this.config.name}', options)\n );\n});\n\nself.addEventListener('notificationclick', (event) => {\n event.notification.close();\n\n if (event.action === 'explore') {\n event.waitUntil(\n clients.openWindow('/')\n );\n }\n});\n `;\n }\n\n /**\n * Registers the service worker\n */\n registerServiceWorker(): Promise<ServiceWorkerRegistration | null> {\n if ('serviceWorker' in navigator) {\n return navigator.serviceWorker.register('/sw.js', {\n scope: this.swConfig.scope,\n updateViaCache: this.swConfig.updateViaCache\n });\n }\n return Promise.resolve(null);\n }\n\n /**\n * Checks if the app is installed\n */\n isInstalled(): boolean {\n return window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone === true;\n }\n\n /**\n * Shows install prompt\n */\n showInstallPrompt(): Promise<void> {\n return new Promise((resolve, reject) => {\n if ('BeforeInstallPromptEvent' in window) {\n const promptEvent = new Event('beforeinstallprompt');\n window.dispatchEvent(promptEvent);\n\n window.addEventListener('beforeinstallprompt', (e) => {\n e.preventDefault();\n (e as any).prompt();\n resolve();\n });\n } else {\n reject(new Error('Install prompt not supported'));\n }\n });\n }\n\n /**\n * Updates the PWA configuration\n */\n updateConfig(newConfig: Partial<PWAConfig>): void {\n this.config = { ...this.config, ...newConfig };\n }\n\n /**\n * Updates the service worker configuration\n */\n updateServiceWorkerConfig(newConfig: Partial<ServiceWorkerConfig>): void {\n this.swConfig = { ...this.swConfig, ...newConfig };\n }\n\n /**\n * Gets the current PWA configuration\n */\n getConfig(): PWAConfig {\n return { ...this.config };\n }\n\n /**\n * Gets the current service worker configuration\n */\n getServiceWorkerConfig(): ServiceWorkerConfig {\n return { ...this.swConfig };\n }\n}\n\n// Additional types for PWA\nexport interface ServiceWorkerRegistration {\n scope: string;\n updateViaCache: 'all' | 'none' | 'imports';\n installing: ServiceWorker | null;\n waiting: ServiceWorker | null;\n active: ServiceWorker | null;\n update(): Promise<void>;\n unregister(): Promise<boolean>;\n}\n\nexport interface ServiceWorker {\n scriptURL: string;\n state: 'installing' | 'installed' | 'activating' | 'activated' | 'redundant';\n postMessage(message: any): void;\n}\n\ndeclare global {\n interface Window {\n navigator: Navigator & {\n standalone?: boolean;\n };\n }\n\n interface Navigator {\n serviceWorker?: ServiceWorkerContainer;\n }\n\n interface ServiceWorkerContainer {\n register(scriptURL: string, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>;\n getRegistration(scope?: string): Promise<ServiceWorkerRegistration | undefined>;\n getRegistrations(): Promise<ServiceWorkerRegistration[]>;\n controller: ServiceWorker | null;\n ready: Promise<ServiceWorkerRegistration>;\n }\n\n interface RegistrationOptions {\n scope?: string;\n updateViaCache?: 'all' | 'none' | 'imports';\n }\n}\n"]}