← Back to Notes

Web Notifications API

2026-05-24

By @boooshir, @Deepseek v4

web Notification

Overview

The app sends browser notifications at a user-configured daily time (e.g. 20:00) listing incomplete habits. Notifications rely on a Dedicated Web Worker that polls every 10s, plus the PWA Service Worker as the notification delivery channel.

Architecture

[ReminderSettings]              [Protected Route]
  sets reminderTime               reads reminderTime
        |                               |
        v                               v
  Server (D1 DB, users.reminder_time)
        |
        v
  React Query (key: "reminder-settings")
        |
        v
  useReminder(reminderTime)
        |
        ├── Creates Dedicated Worker (reminder.worker.ts)
        │     polls every 10s: now >= reminderTime?
        │     yes → postMessage("reminder-tick")
        │
        ├── Worker message handler:
        │     checks incomplete habits via queryClient cache
        │     → showNotification(body)
        │
        └── VisibilityChange listener:
              when tab becomes visible: is reminderTime passed?
              → showNotification(body)

Notification Delivery

1async function showNotification(body: string) { 2 // 1. Try Service Worker registration (system-level on Android) 3 try { 4 const registration = await navigator.serviceWorker.ready 5 await registration.showNotification('Habito', { 6 body, 7 icon, 8 badge, 9 vibrate, 10 }) 11 } catch { 12 // 2. Fallback to page-context Notification API 13 if (Notification.permission === 'granted') { 14 new Notification('Habito', { body }) 15 } 16 } 17 // 3. Play sound regardless 18 new Audio('/sound/notification.wav').play() 19}

Issues

1. Dedicated Worker throttling on Android

When the PWA is backgrounded (screen off, other app open), Chrome aggressively throttles in dedicated workers. The 10s interval may not fire for minutes or at all.

Impact: Notifications may not appear at the scheduled time if the user isn't actively using the app.

2. unreliable on mobile time inputs

The native time picker on Android/iOS does not reliably fire when the user dismisses it. The original code relied on to save the time; a Save button was added as a secondary path.

3. not awaited

The original code called without . If the user denied the prompt, the reminder was still enabled but notifications silently never fired.

4. vs

in page context may not appear as a system notification on Android Chrome when the PWA is backgrounded. Using the Service Worker's is more reliable on mobile.

Possible Solutions

A. Service Worker time check (injectManifest mode)

Switch from to . Write a custom that:

  • Listens for "config" messages from the page with the reminder time
  • Runs its own (more background time than a dedicated worker on some browsers)
  • Shows directly

Pros: The SW has a longer lifetime than a dedicated worker; notifications work even after the page is closed for a short period. Cons: SW lifecycle is still limited on mobile; Chrome kills SWs after ~30s without a client.

B. Server push (Web Push API)

Add a scheduled server function (cron trigger on Cloudflare Workers) that:

  • Queries D1 for users whose matches the current minute
  • Sends a Web Push notification via the Push API
  • Client handles event in the service worker

Pros: Truly reliable — works even when the app is completely closed. Cons: Requires server infrastructure (push subscription storage, VAPID keys, cron); significant complexity for a habit tracker.

C. Keep current approach + improve reliability

The current approach is already a decent balance:

  • Dedicated worker for foreground detection (10s polling)
  • Service Worker for delivery (better than )
  • listener catches missed timers when the user returns to the app

Limitations: Still won't fire if the phone is locked and the user doesn't open the app for hours.

D. Service Worker wake-up via periodic background sync

Use the Periodic Background Sync API:

  • Register a periodic sync event in the SW when the user sets a reminder
  • The browser wakes the SW periodically (min interval is browser-controlled, typically ~12h+ on Android)
  • Check the time and show notification

Pros: Works without the app being open at all. Cons: Very limited browser support (Chrome ≥80 on Android only); minimum interval is large; requires user engagement signals.

Recommendation

For a habit tracker, Option C (current approach) is pragmatic. The notification fires reliably when the app is in the foreground, and the handler catches it shortly after returning. For truly time-critical reminders, Option B (server push) would be needed, but the complexity is rarely justified for a personal habit tracker.