fix(hub): drop hub PWA install to unblock standalone PWAs (#41) #44
4 changed files with 47 additions and 188 deletions
126
public/sw.js
126
public/sw.js
|
|
@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
38
src/lib/decommission-hub-sw.ts
Normal file
38
src/lib/decommission-hub-sw.ts
Normal file
|
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
17
src/main.ts
17
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()
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue