fix(hub): drop hub PWA install to unblock standalone PWAs (closes #41)

Pre-#41 the hub shipped a Workbox SW with manifest scope `/`, which
claimed the entire app.ariege.io origin and made Chrome treat the
path-mounted standalones at /libra/, /market/, etc. as sub-areas of the
already-installed hub PWA — suppressing the install affordance for each
standalone.

Drop the VitePWA plugin from the hub entirely. The hub is a tile-grid
launcher; users install the standalones they actually use. Add a
decommission helper that runs on every hub boot and unregisters any
legacy hub SW, so users who installed the old hub PWA get cleaned up
automatically. Standalone SWs at deeper scopes are left alone.
This commit is contained in:
Padreug 2026-05-06 07:48:37 +02:00
commit 0a0769115b
4 changed files with 47 additions and 188 deletions

View file

@ -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
})
}
})