hermes-explorigin 897f49ae22
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 1m1s
Test, Build & Deploy / deploy (push) Successful in 22s
Add installable PWA (manifest, service worker, icons) + per-type notification toggles
- 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
2026-09-08 00:59:19 +00:00

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`)