diff --git a/public/sw.js b/public/sw.js deleted file mode 100644 index 4daad6b..0000000 --- a/public/sw.js +++ /dev/null @@ -1,126 +0,0 @@ -// Custom service worker for push notifications -// This will be merged with Workbox generated SW - -import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching' -import { clientsClaim, skipWaiting } from 'workbox-core' - -// Precache and route static assets -precacheAndRoute(self.__WB_MANIFEST) -cleanupOutdatedCaches() - -// Take control of all pages immediately -skipWaiting() -clientsClaim() - -// Push notification event handler -self.addEventListener('push', (event) => { - console.log('Push event received:', event) - - let notificationData = { - title: 'New Announcement', - body: 'You have a new admin announcement', - icon: '/pwa-192x192.png', - badge: '/pwa-192x192.png', - data: { - url: '/', - timestamp: Date.now() - }, - tag: 'admin-announcement', - requireInteraction: true, - actions: [ - { - action: 'view', - title: 'View', - icon: '/pwa-192x192.png' - }, - { - action: 'dismiss', - title: 'Dismiss' - } - ] - } - - // Parse push data if available - if (event.data) { - try { - const pushData = event.data.json() - notificationData = { - ...notificationData, - ...pushData - } - } catch (error) { - console.warn('Failed to parse push data:', error) - // Use default notification data - } - } - - event.waitUntil( - self.registration.showNotification(notificationData.title, notificationData) - ) -}) - -// Notification click handler -self.addEventListener('notificationclick', (event) => { - console.log('Notification clicked:', event) - - event.notification.close() - - const action = event.action - const notificationData = event.notification.data || {} - - if (action === 'dismiss') { - return // Just close the notification - } - - // Default action or 'view' action - open the app - const urlToOpen = notificationData.url || '/' - - event.waitUntil( - clients.matchAll({ type: 'window', includeUncontrolled: true }) - .then((clientList) => { - // Try to find an existing window with the app - for (const client of clientList) { - if (client.url.includes(self.location.origin) && 'focus' in client) { - client.focus() - // Navigate to the notification URL if different - if (client.url !== urlToOpen) { - client.navigate(urlToOpen) - } - return - } - } - // If no existing window, open a new one - if (clients.openWindow) { - return clients.openWindow(urlToOpen) - } - }) - ) -}) - -// Background sync for offline notification queue (future enhancement) -self.addEventListener('sync', (event) => { - if (event.tag === 'notification-queue') { - event.waitUntil( - // Process any queued notifications when back online - console.log('Background sync: notification-queue') - ) - } -}) - -// Message handler for communication with main app -self.addEventListener('message', (event) => { - console.log('Service worker received message:', event.data) - - if (event.data && event.data.type === 'SHOW_NOTIFICATION') { - const { title, body, data } = event.data.payload - - self.registration.showNotification(title, { - body, - icon: '/pwa-192x192.png', - badge: '/pwa-192x192.png', - data, - tag: 'manual-notification', - requireInteraction: false - }) - } -}) \ No newline at end of file diff --git a/src/lib/decommission-hub-sw.ts b/src/lib/decommission-hub-sw.ts new file mode 100644 index 0000000..62fd4aa --- /dev/null +++ b/src/lib/decommission-hub-sw.ts @@ -0,0 +1,38 @@ +/** + * Unregister the legacy hub service worker (closes #41). + * + * Pre-#41 the hub shipped a Workbox SW at `${origin}/sw.js` with scope `/`, + * which claimed the whole origin and blocked Chrome from offering installs + * for the path-mounted standalones at /libra/, /market/, etc. The hub is no + * longer a PWA; this helper proactively cleans up that stale registration + * for users who installed the hub before this change shipped. + * + * Only touches SWs whose scope is the origin root. Standalone SWs at + * /libra/, /market/, etc. live at deeper scopes and are left alone — they + * are still legitimate PWAs. + * + * Idempotent: once the legacy registration is gone, future calls are + * cheap no-ops. Safe to leave in place permanently. + */ +export async function decommissionHubServiceWorker(): Promise { + if (!('serviceWorker' in navigator)) return + + try { + const regs = await navigator.serviceWorker.getRegistrations() + const originRoot = `${location.origin}/` + const legacy = regs.filter(r => r.scope === originRoot) + if (legacy.length === 0) return + + console.warn( + `[decommission-hub-sw] Unregistering ${legacy.length} legacy hub service worker(s).` + ) + await Promise.all(legacy.map(r => r.unregister())) + + if ('caches' in window) { + const keys = await caches.keys() + await Promise.all(keys.map(k => caches.delete(k))) + } + } catch (err) { + console.warn('[decommission-hub-sw] failed to unregister:', err) + } +} diff --git a/src/main.ts b/src/main.ts index a12e2a8..f00fd9c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,24 +1,15 @@ // New modular application entry point import { startApp } from './app' -import { registerSW } from 'virtual:pwa-register' import { cleanupStaleDevServiceWorkers } from '@/lib/dev-sw-cleanup' +import { decommissionHubServiceWorker } from '@/lib/decommission-hub-sw' import 'vue-sonner/style.css' // Clean up any leftover dev-mode service workers from a previous session cleanupStaleDevServiceWorkers() -// Simple periodic service worker updates -const intervalMS = 60 * 60 * 1000 // 1 hour -registerSW({ - onRegistered(r) { - r && setInterval(() => { - r.update() - }, intervalMS) - }, - onOfflineReady() { - console.log('App ready to work offline') - } -}) +// Hub is no longer a PWA (#41) — unregister any legacy hub SW left behind +// on users who installed the old hub PWA before this change shipped. +void decommissionHubServiceWorker() // Start the modular application startApp() diff --git a/vite.config.ts b/vite.config.ts index b1e4ff5..b3de398 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,12 +2,16 @@ import { fileURLToPath, URL } from 'node:url' import vue from '@vitejs/plugin-vue' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' -import { VitePWA } from 'vite-plugin-pwa' import Inspect from 'vite-plugin-inspect' import { ViteImageOptimizer } from 'vite-plugin-image-optimizer' import { visualizer } from 'rollup-plugin-visualizer' // https://vite.dev/config/ +// +// The hub is intentionally NOT a PWA (closes #41). Its scope `/` claimed +// the entire origin and blocked Chrome from offering installs for the +// path-mounted standalones at /libra/, /market/, etc. The hub is a +// launcher page; users install the standalones they actually use. export default defineConfig(({ mode }) => ({ // Per-app dep cache so concurrent dev servers don't race on .vite/deps cacheDir: 'node_modules/.vite-hub', @@ -18,54 +22,6 @@ export default defineConfig(({ mode }) => ({ plugins: [ vue(), tailwindcss(), - VitePWA({ - registerType: 'autoUpdate', - devOptions: { - // SW disabled in dev — was caching stale bundles across restarts. - // Run `npm run preview` to test PWA behaviour against a real build. - enabled: false - }, - // strategies: 'injectManifest', - srcDir: 'public', - filename: 'sw.js', - workbox: { - globPatterns: [ - '**/*.{js,css,html,ico,png,svg}' - ], - // Don't intercept standalone app paths — they have their own service workers - navigateFallbackDenylist: [/^\/sortir\//, /^\/libra\//, /^\/wallet\//, /^\/chat\//, /^\/market\//, /^\/cart\//, /^\/checkout\//, /^\/tasks\//, /^\/forum\//, /^\/submit\//, /^\/submission\//], - }, - includeAssets: [ - 'favicon.ico', - 'apple-touch-icon.png', - 'mask-icon.svg', - // optional: include the icon PNGs explicitly if you also reference them directly - 'icon-192.png', - 'icon-512.png', - 'icon-maskable-192.png', - 'icon-maskable-512.png', - ], - manifest: { - name: 'AIO - Community Hub', - short_name: 'AIO', - description: 'Nostr-based community platform with Lightning Network integration for events, market and announcements', - theme_color: '#1f2937', - background_color: '#ffffff', - display: 'standalone', - orientation: 'portrait-primary', - start_url: '/', - scope: '/', - id: 'aio-community-hub', - categories: ['social', 'utilities'], - lang: 'en', - "icons": [ - { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, - { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, - { "src": "/icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, - { "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } - ], - } - }), Inspect(), ViteImageOptimizer({ jpg: {