# Case Study: PWA Push Notifications on iOS (Cloudflare Workers) Implementing push notifications for iOS PWAs on Cloudflare Workers requires addressing several unique runtime and OS-specific constraints. ## 🛠 Tech Stack - **Framework**: Next.js (OpenNext) - **Runtime**: Cloudflare Workers (Edge) - **Database**: Cloudflare D1 - **Library**: `web-push` ## 🚀 Key Implementation Details ### 1. Edge Runtime Compatibility (Crucial) The standard `webPush.sendNotification()` relies on Node.js's `https` module, which is **not supported** in Cloudflare Workers. **Solution**: Use `webPush.generateRequestDetails()` to create the encrypted payload, and then send it using the Edge-native `fetch`. ```typescript const requestDetails = await webPush.generateRequestDetails( { endpoint, keys: { p256dh, auth } }, JSON.stringify(payload) ); const response = await fetch(requestDetails.endpoint, { method: 'POST', headers: requestDetails.headers, body: requestDetails.body // Note: This is a Buffer/ArrayBuffer }); ``` ### 2. VAPID Key Generation VAPID keys must be generated using the native `crypto` module to avoid dependency issues in restricted environments. ### 3. iOS PWA Manifest Requirements For "Add to Home Screen" to trigger the correct push capabilities: - `display: standalone` or `fullscreen` is required. - The user **must** trigger the permission request via a direct interaction (e.g., clicking an "Enable" button). ### 4. Background Cron Trigger Since Next.js API routes on Cloudflare don't support long-running processes, a separate **Cron Worker** is needed to ping the API every minute. - **Workflow**: `Cron Worker (triggers every minute)` -> `Ping /api/cron/reminders` -> `App logic checks D1` -> `App sends push via fetch`. ### 5. Local Timezone Management iOS background alerts are only useful if they fire at the user's local time. - **The fix**: Explicitly capture the user's timezone on the client (`Intl.DateTimeFormat().resolvedOptions().timeZone`) and save it to the database. - **Verification**: Ensure the backend API updates the `timezone` column alongside reminder settings to avoid defaulting to UTC. ## 💡 Lessons Learned - **Minute-Precision**: For hourly reminders, sync the *minute* value (e.g., :18 past the hour) between the UI and the backend checker. - **Visual Feedback**: iOS users are often skeptical of web-based push. Providing a "Test & Sync" button that sends an immediate notification builds trust and verifies the connection. - **Authentication**: Protect the cron endpoint with a `CRON_SECRET` header to prevent unauthorized trigger pings.