- manifest.webmanifest + apple/maskable PNG icons; relative paths so the app works both at dev root and under /weather - plain-JS service worker (sw.js) with notificationclick/push handlers and an SW-mediated showNotification() path for native Android notifications - src/lib/pwa.js bootstraps registration and routes alerts to the SW - Every alert kind (storm/precip/heat/cold/uv/wind) is now a per-type preference toggle in Settings; removed the old alertThresholds.thunderstorm boolean in favor of notificationTypes.storm - Store dedups native notifications per forecast day and fires best-effort native notifications for newly-appeared alerts - inline-assets keeps manifest/sw/icons in dist; deploy.yml uploads + verifies all PWA files via WebDAV
82 lines
1.9 KiB
JavaScript
82 lines
1.9 KiB
JavaScript
const VERSION = '1.0.0'
|
|
const APP_NAME = 'WeatherLens'
|
|
const APP_ICON = './icons/icon-192.png'
|
|
|
|
self.addEventListener('install', () => {
|
|
self.skipWaiting()
|
|
})
|
|
|
|
self.addEventListener('activate', () => {
|
|
self.clients?.claim?.()
|
|
})
|
|
|
|
function showNotification({ title = APP_NAME, body = '', icon, tag } = {}) {
|
|
try {
|
|
const opts = {
|
|
body,
|
|
icon: icon || APP_ICON,
|
|
tag,
|
|
requireInteraction: false,
|
|
silent: false,
|
|
}
|
|
const reg = self.registration
|
|
if (reg && typeof reg.showNotification === 'function') {
|
|
return reg.showNotification(title, opts)
|
|
}
|
|
if (typeof Notification === 'function' || typeof Notification === 'object') {
|
|
new Notification(title, opts).show()
|
|
return true
|
|
}
|
|
} catch (e) {
|
|
console.warn('[sw] notification failed:', e)
|
|
}
|
|
return false
|
|
}
|
|
|
|
self.addEventListener('message', (event) => {
|
|
const data = event?.data || {}
|
|
if (data.type === 'weather-notify') {
|
|
showNotification(data)
|
|
}
|
|
})
|
|
|
|
self.addEventListener('push', (event) => {
|
|
let payload = {}
|
|
try {
|
|
payload = event?.data?.json?.() ?? {}
|
|
} catch {
|
|
/* not JSON */
|
|
}
|
|
event?.waitUntil?.(
|
|
Promise.resolve(showNotification(payload)).then(async () => {
|
|
const clients = self.clients
|
|
if (clients) {
|
|
for (const client of await clients.matchAll({ includeUncontrolled: true })) {
|
|
client.postMessage({ type: 'weather-push-refresh' })
|
|
}
|
|
}
|
|
})
|
|
)
|
|
})
|
|
|
|
self.addEventListener('notificationclick', (event) => {
|
|
event?.waitUntil?.(
|
|
(async () => {
|
|
const url = event?.notification?.data?.url || './'
|
|
const clients = self.clients
|
|
if (clients) {
|
|
for (const client of await clients.matchAll({ includeUncontrolled: true })) {
|
|
await client.navigate?.(url)
|
|
}
|
|
}
|
|
await clients?.openWindow?.(url)
|
|
})()
|
|
)
|
|
})
|
|
|
|
self.addEventListener('fetch', () => {
|
|
/* network-first */
|
|
})
|
|
|
|
console.log(`[sw] ${APP_NAME} ${VERSION} active`)
|