Files
Todo/PUSH_NOTIFICATIONS_COMPLETE.md
Paul Bailey 7cda58440f feat: implement complete web push notification system
- Add self-hosted push notifications using VAPID (no external services)
- Create PushSubscription model in database
- Implement server-side push logic with web-push library
- Add notification helpers for list events (items, invitations, collaborators)
- Create NotificationSettings UI component for profile page
- Add usePushNotifications React hook for subscription management
- Create API routes for subscription management and VAPID key
- Update service worker with push event handlers
- Integrate notifications into item actions (add, remove, complete)
- Integrate notifications into sharing actions (invite, accept, remove)
- Add VAPID key generation script
- Add comprehensive documentation and setup guides

Notifications are sent when:
- Items are added/removed/completed in shared lists
- Users are invited to lists
- Invitations are accepted
- Collaborators are removed from lists

Works on iOS 16.4+ (when installed to home screen), Android, and desktop browsers.
2025-12-07 01:22:16 +00:00

7.4 KiB

Web Push Notifications - Complete Implementation ✅

What Was Implemented

A complete, self-hosted Web Push notification system for your PWA shopping list app.

📦 Files Created

Core Implementation

  • lib/push-notifications.ts - Server-side push notification logic with VAPID
  • lib/notification-helpers.ts - Helper functions for specific notification events
  • hooks/usePushNotifications.ts - React hook for managing subscriptions
  • components/NotificationSettings.tsx - UI component for enabling/disabling notifications

API Routes

  • app/api/push/vapid-key/route.ts - Returns public VAPID key
  • app/api/push/subscribe/route.ts - Saves push subscriptions
  • app/api/push/unsubscribe/route.ts - Removes push subscriptions

Database

  • Updated prisma/schema.prisma - Added PushSubscription model

Service Worker

  • Updated public/sw.js - Added push event handlers

Scripts & Config

  • scripts/generate-vapid-keys.js - Generate VAPID keys
  • Updated package.json - Added web-push dependency and npm script
  • Updated .env.example - Added VAPID key placeholders

Documentation

  • PUSH_NOTIFICATIONS_QUICK_START.md - 5-minute setup guide
  • PUSH_NOTIFICATIONS_SETUP.md - Complete setup and configuration
  • PUSH_NOTIFICATIONS_INTEGRATION_EXAMPLE.md - Code integration examples
  • PUSH_NOTIFICATIONS_GUIDE.md - Architecture and testing guide
  • PUSH_NOTIFICATIONS_COMPLETE.md - This file

🎯 How It Works

The Push Notification Server

You don't need an external service! This implementation uses VAPID (Voluntary Application Server Identification), which is a standard protocol that allows your own server to send push notifications directly to browsers.

Here's the flow:

  1. Generate VAPID Keys (one-time setup)

    • Run npm run vapid:generate
    • These are like JWT secrets - a public/private key pair
    • Public key: shared with browsers
    • Private key: kept secret on your server
  2. User Subscribes

    • User clicks "Enable" in NotificationSettings
    • Browser creates a unique push subscription (includes endpoint URL)
    • Subscription saved to your database
  3. Event Occurs

    • Someone adds an item to a shared list
    • Your server action calls notifyItemAdded()
    • Function looks up all subscriptions for list members
    • Sends push notification using web-push library
  4. Browser Receives

    • Browser's push service receives the notification
    • Service worker wakes up and displays it
    • User sees notification even if app is closed

The "Server" is Just Your App

The push notification "server" is simply:

  • The web-push npm package (installed in your app)
  • Your VAPID keys (in your .env file)
  • Your existing Next.js API routes

No external service, no additional infrastructure needed!

🚀 Setup Instructions

Step 1: Install Dependencies

npm install

Step 2: Generate VAPID Keys

npm run vapid:generate

Copy the output to your .env file:

VAPID_PUBLIC_KEY=BNxxx...
VAPID_PRIVATE_KEY=xxx...
VAPID_SUBJECT=mailto:your-email@example.com

Step 3: Update Database

npx prisma db push

Step 4: Add UI Component

In app/profile/page.tsx:

import NotificationSettings from '@/components/NotificationSettings';

export default function ProfilePage() {
  return (
    <div>
      {/* Your existing content */}
      <NotificationSettings />
    </div>
  );
}

Step 5: Integrate Notifications

In your action files (see PUSH_NOTIFICATIONS_INTEGRATION_EXAMPLE.md for details):

import { notifyItemAdded } from '@/lib/notification-helpers';

// After creating an item:
await notifyItemAdded(listId, item.title, session.user.id);

📱 Notification Events

Event Function When
Item added notifyItemAdded() Someone adds item to shared list
Item removed notifyItemRemoved() Someone removes item
Item completed notifyItemCompleted() Someone marks item complete
List invitation notifyListInvitation() Someone invites you
Invitation accepted notifyInvitationAccepted() Someone accepts your invite
Removed from list notifyRemovedFromList() Owner removes you

🌐 Browser Support

Platform Support Notes
Chrome Desktop ✅ Full Works perfectly
Chrome Android ✅ Full Works perfectly
Firefox Desktop ✅ Full Works perfectly
Firefox Android ✅ Full Works perfectly
Safari macOS ✅ 16.4+ Works when installed
Safari iOS ✅ 16.4+ Must be installed to home screen
Edge ✅ Full Chromium-based

iOS Requirements

  • iOS 16.4 or later
  • Must use "Add to Home Screen" (won't work in Safari browser)
  • Requires HTTPS in production

🔒 Security

  • VAPID private key must be kept secret (like JWT secret)
  • Subscriptions are tied to authenticated users
  • Expired subscriptions automatically cleaned up
  • No cross-user notification access

🧪 Testing

Desktop (Chrome)

  1. Start dev server: npm run dev
  2. Open DevTools → Application → Service Workers
  3. Enable notifications in profile
  4. Trigger an action (add item, etc.)
  5. See notification appear

iOS (Real Device)

  1. Deploy with HTTPS
  2. Open in Safari
  3. Share → Add to Home Screen
  4. Open installed app
  5. Enable notifications
  6. Test from another device

📊 Database Schema

New table added:

model PushSubscription {
  id         String   @id @default(cuid())
  userId     String
  endpoint   String   @db.Text
  p256dh     String   @db.Text
  auth       String   @db.Text
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
  user       User     @relation(...)
}

🎨 Customization

Change Notification Content

Edit lib/notification-helpers.ts to customize titles, bodies, icons.

Add Per-List Preferences

  1. Add notificationsEnabled to Collaborator model
  2. Check this field before sending notifications
  3. Add UI toggle in list settings

Add Notification Sounds

Update service worker push event:

const options = {
  ...existing,
  silent: false,
  sound: '/notification-sound.mp3',
};

🐛 Troubleshooting

"Push notifications not configured"

  • Verify VAPID keys are in .env
  • Restart dev server

Notifications not appearing

  • Check browser permission (must be "granted")
  • Verify service worker is active
  • Check console for errors
  • Ensure HTTPS (or localhost)

iOS not working

  • Check iOS version (16.4+)
  • Must be installed to home screen
  • Check Settings → [App] → Notifications

📈 Performance

  • Notifications sent asynchronously (non-blocking)
  • Multiple devices per user supported
  • Automatic cleanup of expired subscriptions
  • TTL: 24 hours

✅ Production Checklist

  • Generate production VAPID keys
  • Add to production environment variables
  • Run npx prisma db push on production DB
  • Verify HTTPS is working
  • Test on actual iOS device
  • Monitor logs for errors

🎉 You're Done!

Your app now has:

  • ✅ Self-hosted push notifications
  • ✅ No external dependencies
  • ✅ iOS and Android support
  • ✅ Multi-device support
  • ✅ Automatic subscription management

Users will receive notifications when:

  • Items are added/removed/completed in shared lists
  • They're invited to lists
  • Their invitations are accepted
  • They're removed from lists

All without any external services or additional infrastructure!