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.
This commit is contained in:
271
PUSH_NOTIFICATIONS_SETUP.md
Normal file
271
PUSH_NOTIFICATIONS_SETUP.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Push Notifications Setup Guide
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install `web-push` which is now in package.json.
|
||||
|
||||
### 2. Generate VAPID Keys
|
||||
|
||||
```bash
|
||||
npm run vapid:generate
|
||||
```
|
||||
|
||||
Copy the output and add to your `.env` file:
|
||||
|
||||
```env
|
||||
VAPID_PUBLIC_KEY=your-generated-public-key
|
||||
VAPID_PRIVATE_KEY=your-generated-private-key
|
||||
VAPID_SUBJECT=mailto:your-email@example.com
|
||||
```
|
||||
|
||||
### 3. Update Database Schema
|
||||
|
||||
```bash
|
||||
npx prisma db push
|
||||
```
|
||||
|
||||
This adds the `PushSubscription` table to your database.
|
||||
|
||||
### 4. Add Notification Settings to Profile Page
|
||||
|
||||
Add the `NotificationSettings` component to your profile page:
|
||||
|
||||
```tsx
|
||||
import NotificationSettings from '@/components/NotificationSettings';
|
||||
|
||||
export default function ProfilePage() {
|
||||
return (
|
||||
<div>
|
||||
{/* Your existing profile content */}
|
||||
|
||||
<NotificationSettings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Integrate Notifications into Your Actions
|
||||
|
||||
Update your existing action files to send notifications:
|
||||
|
||||
#### In `lib/actions.ts` (or wherever you handle items):
|
||||
|
||||
```typescript
|
||||
import { notifyItemAdded, notifyItemRemoved, notifyItemCompleted } from '@/lib/notification-helpers';
|
||||
|
||||
// After creating an item:
|
||||
export async function createItem(listId: string, data: ItemData) {
|
||||
// ... your existing code to create item
|
||||
const item = await prisma.item.create({ ... });
|
||||
|
||||
// Send notification
|
||||
await notifyItemAdded(listId, item.title, session.user.id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// After deleting an item:
|
||||
export async function deleteItem(itemId: string) {
|
||||
const item = await prisma.item.findUnique({ where: { id: itemId } });
|
||||
// ... your existing delete code
|
||||
|
||||
if (item) {
|
||||
await notifyItemRemoved(item.listId, item.title, session.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
// After completing an item:
|
||||
export async function toggleItemComplete(itemId: string) {
|
||||
const item = await prisma.item.update({ ... });
|
||||
|
||||
if (item.completed) {
|
||||
await notifyItemCompleted(item.listId, item.title, session.user.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### In `lib/sharing-actions.ts`:
|
||||
|
||||
```typescript
|
||||
import { notifyListInvitation, notifyInvitationAccepted, notifyRemovedFromList } from '@/lib/notification-helpers';
|
||||
|
||||
// After creating an invitation:
|
||||
export async function inviteToList(listId: string, email: string, permission: string) {
|
||||
// ... your existing code
|
||||
const invitation = await prisma.invitation.create({ ... });
|
||||
|
||||
// Send notification
|
||||
await notifyListInvitation(invitation.id, email);
|
||||
|
||||
return invitation;
|
||||
}
|
||||
|
||||
// After accepting an invitation:
|
||||
export async function acceptInvitation(invitationId: string) {
|
||||
// ... your existing code
|
||||
|
||||
await notifyInvitationAccepted(invitationId);
|
||||
}
|
||||
|
||||
// After removing a collaborator:
|
||||
export async function removeCollaborator(listId: string, userId: string) {
|
||||
const list = await prisma.list.findUnique({ where: { id: listId } });
|
||||
// ... your existing code
|
||||
|
||||
if (list) {
|
||||
await notifyRemovedFromList(userId, list.title, session.user.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Architecture
|
||||
|
||||
1. **Client requests permission** → User clicks "Enable" in NotificationSettings
|
||||
2. **Service worker subscribes** → Browser creates a push subscription
|
||||
3. **Subscription saved to DB** → Stored in PushSubscription table
|
||||
4. **Events trigger notifications** → Server sends push via web-push library
|
||||
5. **Service worker displays** → User sees notification
|
||||
|
||||
### Self-Hosted Solution
|
||||
|
||||
This implementation uses **VAPID** (Voluntary Application Server Identification), which means:
|
||||
- ✅ No external services required (no Firebase, no third-party APIs)
|
||||
- ✅ Works entirely from your own server
|
||||
- ✅ Free and open-source
|
||||
- ✅ Full control over your data
|
||||
|
||||
### Browser Support
|
||||
|
||||
- ✅ Chrome/Edge (Desktop & Android)
|
||||
- ✅ Firefox (Desktop & Android)
|
||||
- ✅ Safari 16.4+ (iOS & macOS) - **Must be installed to home screen on iOS**
|
||||
- ❌ Safari < 16.4
|
||||
|
||||
## Testing
|
||||
|
||||
### Local Testing (Desktop)
|
||||
|
||||
1. Start your dev server: `npm run dev`
|
||||
2. Open Chrome DevTools → Application → Service Workers
|
||||
3. Go to your profile page and enable notifications
|
||||
4. Trigger an action (add item, invite someone, etc.)
|
||||
5. You should see a notification appear
|
||||
|
||||
### iOS Testing (Real Device Required)
|
||||
|
||||
1. Deploy to a server with HTTPS (required for PWA)
|
||||
2. Open Safari on iOS 16.4+
|
||||
3. Tap Share → Add to Home Screen
|
||||
4. Open the installed app
|
||||
5. Enable notifications in profile
|
||||
6. Test by triggering actions from another device
|
||||
|
||||
### Testing Push Manually
|
||||
|
||||
Use Chrome DevTools to simulate a push:
|
||||
|
||||
1. DevTools → Application → Service Workers
|
||||
2. Find your service worker
|
||||
3. Click "Push" to simulate receiving a notification
|
||||
|
||||
## Notification Events
|
||||
|
||||
The following events trigger notifications:
|
||||
|
||||
| Event | Recipient | Trigger |
|
||||
|-------|-----------|---------|
|
||||
| Item Added | All list collaborators (except adder) | Someone adds an item to a shared list |
|
||||
| Item Removed | All list collaborators (except remover) | Someone removes an item |
|
||||
| Item Completed | All list collaborators (except completer) | Someone marks an item complete |
|
||||
| List Invitation | Invitee | Someone invites you to a list |
|
||||
| Invitation Accepted | Inviter | Someone accepts your invitation |
|
||||
| Removed from List | Removed user | Owner removes you from a list |
|
||||
|
||||
## Customization
|
||||
|
||||
### Notification Content
|
||||
|
||||
Edit `lib/notification-helpers.ts` to customize notification titles, bodies, and icons.
|
||||
|
||||
### Notification Preferences
|
||||
|
||||
To add per-list notification preferences:
|
||||
|
||||
1. Add a `notificationsEnabled` field to the `Collaborator` model
|
||||
2. Update `NotificationSettings` to show per-list toggles
|
||||
3. Check this field in `notifyListCollaborators` before sending
|
||||
|
||||
### Notification Sounds
|
||||
|
||||
Add to service worker push event:
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
// ... existing options
|
||||
silent: false,
|
||||
sound: '/notification-sound.mp3',
|
||||
};
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Push notifications not configured" error
|
||||
- Check that VAPID keys are in `.env`
|
||||
- Restart your dev server after adding keys
|
||||
|
||||
### Notifications not appearing
|
||||
- Check browser permission (should be "granted")
|
||||
- Verify service worker is active (DevTools → Application)
|
||||
- Check browser console for errors
|
||||
- Ensure you're testing on HTTPS (or localhost)
|
||||
|
||||
### iOS notifications not working
|
||||
- Must be iOS 16.4 or later
|
||||
- Must be installed to home screen (not in Safari browser)
|
||||
- Check Settings → [Your App] → Notifications
|
||||
|
||||
### Subscription fails
|
||||
- Verify VAPID public key matches between client and server
|
||||
- Check service worker is registered and active
|
||||
- Ensure you're on HTTPS (or localhost)
|
||||
|
||||
## Security Notes
|
||||
|
||||
- ⚠️ **Never commit VAPID_PRIVATE_KEY to git**
|
||||
- ✅ VAPID keys are like JWT secrets - keep them secure
|
||||
- ✅ Subscriptions are tied to users - no cross-user notification access
|
||||
- ✅ Expired subscriptions are automatically cleaned up
|
||||
|
||||
## Production Checklist
|
||||
|
||||
- [ ] Generate production VAPID keys
|
||||
- [ ] Add VAPID keys to production environment variables
|
||||
- [ ] Run database migration: `npx prisma db push`
|
||||
- [ ] Test on actual iOS device (if targeting iOS)
|
||||
- [ ] Verify HTTPS is working (required for push)
|
||||
- [ ] Test notification delivery
|
||||
- [ ] Monitor for expired subscriptions in logs
|
||||
|
||||
## Performance
|
||||
|
||||
- Notifications are sent asynchronously (non-blocking)
|
||||
- Failed subscriptions are automatically removed
|
||||
- Multiple devices per user are supported
|
||||
- TTL is set to 24 hours (notifications expire if not delivered)
|
||||
|
||||
## Next Steps
|
||||
|
||||
Consider adding:
|
||||
- Notification preferences per list
|
||||
- Quiet hours (don't notify during certain times)
|
||||
- Notification history/inbox
|
||||
- Rich notifications with action buttons
|
||||
- Notification grouping/batching
|
||||
Reference in New Issue
Block a user