mirror of
https://github.com/xtr-dev/payload-notifications.git
synced 2025-12-10 10:53:23 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| baa6af990c | |||
| 3425ec92dc | |||
| f5cf5dfea9 | |||
| 55fe0418f9 |
357
README.md
357
README.md
@@ -14,9 +14,7 @@ A PayloadCMS plugin that adds a configurable notifications collection for sendin
|
||||
- 🎯 Recipient targeting support
|
||||
- ⚙️ Flexible plugin configuration
|
||||
- 📅 Automatic timestamp tracking
|
||||
- 🔔 **Web Push Notifications** for mobile PWA support
|
||||
- 📲 Service Worker integration for offline notifications
|
||||
- 🔐 VAPID keys support for secure push messaging
|
||||
- 🔔 Optional web push notifications support (see [WEBPUSH.md](./WEBPUSH.md))
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -54,7 +52,7 @@ notificationsPlugin({
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced Configuration with Relationships and Web Push
|
||||
### Advanced Configuration with Relationships
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
@@ -89,37 +87,14 @@ notificationsPlugin({
|
||||
update: ({ req }) => Boolean(req.user?.role === 'admin'),
|
||||
delete: ({ req }) => Boolean(req.user?.role === 'admin'),
|
||||
},
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true, // Automatically send push notifications when notifications are created
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
// Optional: Custom notification transformer
|
||||
transformNotification: (notification) => ({
|
||||
title: `🔔 ${notification.title}`,
|
||||
body: extractTextFromRichText(notification.message).substring(0, 120) + '...',
|
||||
icon: '/icons/notification-icon.png',
|
||||
badge: '/icons/notification-badge.png',
|
||||
data: {
|
||||
notificationId: notification.id,
|
||||
url: `/admin/collections/notifications/${notification.id}`
|
||||
},
|
||||
actions: [
|
||||
{ action: 'view', title: 'View', icon: '/icons/view.png' },
|
||||
{ action: 'dismiss', title: 'Dismiss' }
|
||||
fields: [
|
||||
// Add custom fields to the notifications collection
|
||||
]
|
||||
}),
|
||||
// Optional: Custom hook for finding push subscriptions (for anonymous notifications)
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
// Custom logic to find subscriptions based on notification data
|
||||
// Return array of push subscription documents
|
||||
return []
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> For web push notifications setup, see [WEBPUSH.md](./WEBPUSH.md)
|
||||
|
||||
## Collection Schema
|
||||
|
||||
The plugin creates a notifications collection with the following fields:
|
||||
@@ -233,282 +208,110 @@ notificationsPlugin({
|
||||
})
|
||||
```
|
||||
|
||||
## Web Push Notifications
|
||||
## Email Notifications
|
||||
|
||||
The plugin supports web push notifications for PWA and mobile browser users.
|
||||
You can add email functionality to notifications using the `collectionOverrides` option. This allows you to add custom hooks to the notifications collection without modifying the plugin code.
|
||||
|
||||
### Anonymous Notifications Support
|
||||
### Using Collection Overrides
|
||||
|
||||
For scenarios where you need to send notifications to anonymous users or have custom recipient logic (e.g., notifications based on email addresses, phone numbers, or custom identifiers), you can use the `findSubscriptions` hook combined with custom fields.
|
||||
|
||||
**Example: Email-based notifications**
|
||||
The key is to preserve existing hooks (like web push) while adding your own:
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
// Add custom email field to notifications collection
|
||||
fields: [
|
||||
{
|
||||
name: 'recipientEmail',
|
||||
type: 'email',
|
||||
label: 'Recipient Email',
|
||||
admin: {
|
||||
description: 'Email address of the notification recipient',
|
||||
},
|
||||
}
|
||||
],
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true,
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
// Custom hook to find subscriptions based on email
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
if (!notification.recipientEmail) return []
|
||||
import { notificationsPlugin } from '@xtr-dev/payload-notifications'
|
||||
|
||||
// Find push subscriptions associated with this email
|
||||
const subscriptions = await payload.find({
|
||||
collection: 'push-subscriptions',
|
||||
where: {
|
||||
and: [
|
||||
{ recipientEmail: { equals: notification.recipientEmail } },
|
||||
{ isActive: { equals: true } },
|
||||
// Channel filtering (if specified)
|
||||
...(notification.channel ? [{
|
||||
or: [
|
||||
{ channels: { contains: notification.channel } },
|
||||
{ channels: { contains: 'all' } },
|
||||
{ channels: { exists: false } },
|
||||
]
|
||||
}] : [])
|
||||
notificationsPlugin({
|
||||
channels: [{ id: 'default', name: 'Default' }],
|
||||
collectionOverrides: {
|
||||
notifications: (config) => ({
|
||||
...config,
|
||||
hooks: {
|
||||
...config.hooks, // Preserve existing hooks (web push, etc.)
|
||||
afterChange: [
|
||||
...(config.hooks?.afterChange || []), // Preserve existing afterChange hooks
|
||||
// Add your custom email hook
|
||||
async ({ doc, operation, req }) => {
|
||||
if (operation === 'create') {
|
||||
// Your email logic here
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
return subscriptions.docs
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Example: Phone number-based notifications**
|
||||
### Example: Custom Email Service
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
fields: [
|
||||
{
|
||||
name: 'recipientPhone',
|
||||
type: 'text',
|
||||
label: 'Recipient Phone',
|
||||
admin: {
|
||||
description: 'Phone number of the notification recipient',
|
||||
},
|
||||
}
|
||||
],
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true,
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
if (!notification.recipientPhone) return []
|
||||
import { notificationsPlugin } from '@xtr-dev/payload-notifications'
|
||||
import { sendEmail } from './your-email-service'
|
||||
import { renderNotificationEmail } from './email-templates'
|
||||
|
||||
// Custom logic to find subscriptions by phone number
|
||||
// You might have a separate mapping table or user lookup
|
||||
const user = await payload.find({
|
||||
notificationsPlugin({
|
||||
channels: [{ id: 'default', name: 'Default' }],
|
||||
collectionOverrides: {
|
||||
notifications: (config) => ({
|
||||
...config,
|
||||
hooks: {
|
||||
...config.hooks,
|
||||
afterChange: [
|
||||
...(config.hooks?.afterChange || []),
|
||||
async ({ doc, operation, req }) => {
|
||||
// Send email when notification is created
|
||||
if (operation === 'create') {
|
||||
try {
|
||||
// Get recipient user details
|
||||
let recipientId = doc.recipient
|
||||
if (typeof recipientId === 'object' && recipientId?.id) {
|
||||
recipientId = recipientId.id
|
||||
}
|
||||
|
||||
if (!recipientId) {
|
||||
console.log('No recipient for email notification')
|
||||
return
|
||||
}
|
||||
|
||||
const recipient = await req.payload.findByID({
|
||||
collection: 'users',
|
||||
where: { phone: { equals: notification.recipientPhone } },
|
||||
limit: 1
|
||||
id: recipientId
|
||||
})
|
||||
|
||||
if (!user.docs[0]) return []
|
||||
if (!recipient?.email) {
|
||||
console.log('Recipient has no email address')
|
||||
return
|
||||
}
|
||||
|
||||
const subscriptions = await payload.find({
|
||||
collection: 'push-subscriptions',
|
||||
where: {
|
||||
and: [
|
||||
{ user: { equals: user.docs[0].id } },
|
||||
{ isActive: { equals: true } }
|
||||
// Send email
|
||||
await sendEmail({
|
||||
to: recipient.email,
|
||||
subject: doc.title,
|
||||
html: renderNotificationEmail(doc)
|
||||
})
|
||||
|
||||
console.log(`Email sent to ${recipient.email}`)
|
||||
} catch (error) {
|
||||
console.error('Failed to send notification email:', error)
|
||||
// Don't throw - we don't want to prevent notification creation
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
return subscriptions.docs
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- The default `recipient` field remains a user relationship for standard notifications
|
||||
- Add custom recipient fields via the `fields` option for your specific use case
|
||||
- Use the `findSubscriptions` hook to implement custom subscription lookup logic
|
||||
- The hook receives the full notification document and payload instance
|
||||
- Return an array of push subscription documents that should receive the notification
|
||||
- The plugin will handle the actual push notification sending and error handling
|
||||
|
||||
### Setup VAPID Keys
|
||||
|
||||
**Step 1:** Generate VAPID keys for secure push messaging:
|
||||
|
||||
```bash
|
||||
npx web-push generate-vapid-keys
|
||||
```
|
||||
|
||||
This will output something like:
|
||||
```
|
||||
=======================================
|
||||
Public Key:
|
||||
BNde-uFUkQB5BweFbOt_40Tn3xZahMop2JKT8kqRn4UqMMinieguHmVCTxwN_qfM-jZ0YFpVpIk3CWehlXcTl8A
|
||||
|
||||
Private Key:
|
||||
RVtnLcW8qlSkuhNskz8lwBwYcam78x-zO0Ssm_P2bmE
|
||||
=======================================
|
||||
```
|
||||
|
||||
**Step 2:** Add the keys to your environment variables:
|
||||
|
||||
```env
|
||||
VAPID_PUBLIC_KEY=BNde-uFUkQB5BweFbOt_40Tn3xZahMop2JKT8kqRn4UqMMinieguHmVCTxwN_qfM-jZ0YFpVpIk3CWehlXcTl8A
|
||||
VAPID_PRIVATE_KEY=RVtnLcW8qlSkuhNskz8lwBwYcam78x-zO0Ssm_P2bmE
|
||||
```
|
||||
|
||||
**Step 3:** Restart your application to load the new environment variables.
|
||||
|
||||
⚠️ **Important:** Keep your private key secure and never commit it to version control!
|
||||
|
||||
### Client-Side Integration
|
||||
|
||||
⚠️ **Authentication Required:** Users must be signed in to subscribe to push notifications. Push subscriptions are associated with user accounts.
|
||||
|
||||
```typescript
|
||||
import { ClientPushManager, usePushNotifications } from '@xtr-dev/payload-notifications/client'
|
||||
|
||||
// React Hook (if using React)
|
||||
function NotificationSettings() {
|
||||
const {
|
||||
isSupported,
|
||||
isSubscribed,
|
||||
permission,
|
||||
subscribe,
|
||||
unsubscribe
|
||||
} = usePushNotifications(process.env.NEXT_PUBLIC_VAPID_KEY)
|
||||
|
||||
if (!isSupported) return <div>Push notifications not supported</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Status: {isSubscribed ? 'Subscribed' : 'Not subscribed'}</p>
|
||||
<p>Permission: {permission}</p>
|
||||
|
||||
{!isSubscribed ? (
|
||||
<button onClick={subscribe}>Enable Notifications</button>
|
||||
) : (
|
||||
<button onClick={unsubscribe}>Disable Notifications</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Vanilla JavaScript
|
||||
const pushManager = new ClientPushManager('your-vapid-public-key')
|
||||
|
||||
// Subscribe to notifications
|
||||
await pushManager.subscribe()
|
||||
|
||||
// Check subscription status
|
||||
const isSubscribed = await pushManager.isSubscribed()
|
||||
```
|
||||
|
||||
### Service Worker Setup
|
||||
|
||||
Generate a service worker file automatically:
|
||||
|
||||
```bash
|
||||
npx @xtr-dev/payload-notifications generate-sw
|
||||
```
|
||||
|
||||
This will create a `/public/sw.js` file with the complete service worker template that handles:
|
||||
|
||||
- Push notification events
|
||||
- Notification click handling
|
||||
- Service worker lifecycle management
|
||||
- Error handling and fallbacks
|
||||
- Notification tracking and analytics
|
||||
|
||||
**Important Notes:**
|
||||
- The service worker file **must** be placed at `/public/sw.js` in Next.js projects
|
||||
- This makes it accessible at `https://yourdomain.com/sw.js`
|
||||
- Service workers must be served from the root domain for security
|
||||
- After creating the file, restart your Next.js development server
|
||||
- Always spread existing hooks (`...config.hooks`) to preserve plugin functionality
|
||||
- Use the spread operator for hook arrays (`...(config.hooks?.afterChange || [])`)
|
||||
- Don't throw errors in hooks if you want to allow notification creation to succeed even if email fails
|
||||
- Email sending happens asynchronously after the notification is created
|
||||
|
||||
### Server-Side Push Notifications
|
||||
## Web Push Notifications
|
||||
|
||||
```typescript
|
||||
import { WebPushManager } from '@xtr-dev/payload-notifications/rsc'
|
||||
|
||||
// Send push notification to a user
|
||||
const pushManager = new WebPushManager(webPushConfig, payload)
|
||||
|
||||
await pushManager.sendToUser(
|
||||
userId,
|
||||
'Order Shipped!',
|
||||
'Your order #12345 has been shipped',
|
||||
{
|
||||
icon: '/icons/order-shipped.png',
|
||||
badge: '/icons/badge.png',
|
||||
data: { orderId: '12345', url: '/orders/12345' },
|
||||
actions: [
|
||||
{ action: 'view', title: 'View Order', icon: '/icons/view.png' },
|
||||
{ action: 'dismiss', title: 'Dismiss' }
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
The plugin automatically creates these endpoints when web push is enabled:
|
||||
|
||||
- `POST /api/push-notifications/subscribe` - Subscribe to push notifications ⚠️ **Requires authentication**
|
||||
- `POST /api/push-notifications/unsubscribe` - Unsubscribe from push notifications
|
||||
- `GET /api/push-notifications/vapid-public-key` - Get VAPID public key
|
||||
- `POST /api/push-notifications/send` - Send notification to user ⚠️ **Requires authentication**
|
||||
- `POST /api/push-notifications/test` - Send test notification ⚠️ **Admin only**
|
||||
- `POST /api/push-notifications/track` - Track notification events
|
||||
|
||||
### Integration with Notifications Collection
|
||||
|
||||
When creating notifications, you can automatically send push notifications:
|
||||
|
||||
```typescript
|
||||
// Create notification and send push notification
|
||||
const notification = await payload.create({
|
||||
collection: 'notifications',
|
||||
data: {
|
||||
title: 'New Message',
|
||||
message: [{ children: [{ text: 'You have a new message!' }] }],
|
||||
recipient: userId,
|
||||
attachments: { message: messageId }
|
||||
}
|
||||
})
|
||||
|
||||
// Send push notification
|
||||
if (webPushEnabled) {
|
||||
await pushManager.sendToUser(
|
||||
userId,
|
||||
notification.title,
|
||||
'You have a new notification',
|
||||
{
|
||||
data: {
|
||||
notificationId: notification.id,
|
||||
url: `/notifications/${notification.id}`
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
The plugin includes optional web push notifications support for PWA and mobile browser users. For complete setup instructions, configuration options, and usage examples, see [WEBPUSH.md](./WEBPUSH.md).
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
|
||||
348
WEBPUSH.md
Normal file
348
WEBPUSH.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# Web Push Notifications
|
||||
|
||||
The `@xtr-dev/payload-notifications` plugin includes built-in support for web push notifications for PWA and mobile browser users.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔔 Web Push Notifications for mobile PWA support
|
||||
- 📲 Service Worker integration for offline notifications
|
||||
- 🔐 VAPID keys support for secure push messaging
|
||||
- 🎯 Auto-push on notification creation
|
||||
- 🔄 Custom notification transformers
|
||||
- 👤 Anonymous user notification support
|
||||
|
||||
## Setup VAPID Keys
|
||||
|
||||
**Step 1:** Generate VAPID keys for secure push messaging:
|
||||
|
||||
```bash
|
||||
npx web-push generate-vapid-keys
|
||||
```
|
||||
|
||||
This will output something like:
|
||||
```
|
||||
=======================================
|
||||
Public Key:
|
||||
BNde-uFUkQB5BweFbOt_40Tn3xZahMop2JKT8kqRn4UqMMinieguHmVCTxwN_qfM-jZ0YFpVpIk3CWehlXcTl8A
|
||||
|
||||
Private Key:
|
||||
RVtnLcW8qlSkuhNskz8lwBwYcam78x-zO0Ssm_P2bmE
|
||||
=======================================
|
||||
```
|
||||
|
||||
**Step 2:** Add the keys to your environment variables:
|
||||
|
||||
```env
|
||||
VAPID_PUBLIC_KEY=BNde-uFUkQB5BweFbOt_40Tn3xZahMop2JKT8kqRn4UqMMinieguHmVCTxwN_qfM-jZ0YFpVpIk3CWehlXcTl8A
|
||||
VAPID_PRIVATE_KEY=RVtnLcW8qlSkuhNskz8lwBwYcam78x-zO0Ssm_P2bmE
|
||||
```
|
||||
|
||||
**Step 3:** Restart your application to load the new environment variables.
|
||||
|
||||
⚠️ **Important:** Keep your private key secure and never commit it to version control!
|
||||
|
||||
## Plugin Configuration
|
||||
|
||||
### Basic Web Push Setup
|
||||
|
||||
```typescript
|
||||
import { notificationsPlugin } from '@xtr-dev/payload-notifications'
|
||||
|
||||
notificationsPlugin({
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true, // Automatically send push notifications when notifications are created
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true,
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
// Optional: Custom notification transformer
|
||||
transformNotification: (notification) => ({
|
||||
title: `🔔 ${notification.title}`,
|
||||
body: extractTextFromRichText(notification.message).substring(0, 120) + '...',
|
||||
icon: '/icons/notification-icon.png',
|
||||
badge: '/icons/notification-badge.png',
|
||||
data: {
|
||||
notificationId: notification.id,
|
||||
url: `/admin/collections/notifications/${notification.id}`
|
||||
},
|
||||
actions: [
|
||||
{ action: 'view', title: 'View', icon: '/icons/view.png' },
|
||||
{ action: 'dismiss', title: 'Dismiss' }
|
||||
]
|
||||
}),
|
||||
// Optional: Custom hook for finding push subscriptions (for anonymous notifications)
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
// Custom logic to find subscriptions based on notification data
|
||||
// Return array of push subscription documents
|
||||
return []
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Anonymous Notifications Support
|
||||
|
||||
For scenarios where you need to send notifications to anonymous users or have custom recipient logic (e.g., notifications based on email addresses, phone numbers, or custom identifiers), you can use the `findSubscriptions` hook combined with custom fields.
|
||||
|
||||
### Example: Email-based notifications
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
// Add custom email field to notifications collection
|
||||
fields: [
|
||||
{
|
||||
name: 'recipientEmail',
|
||||
type: 'email',
|
||||
label: 'Recipient Email',
|
||||
admin: {
|
||||
description: 'Email address of the notification recipient',
|
||||
},
|
||||
}
|
||||
],
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true,
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
// Custom hook to find subscriptions based on email
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
if (!notification.recipientEmail) return []
|
||||
|
||||
// Find push subscriptions associated with this email
|
||||
const subscriptions = await payload.find({
|
||||
collection: 'push-subscriptions',
|
||||
where: {
|
||||
and: [
|
||||
{ recipientEmail: { equals: notification.recipientEmail } },
|
||||
{ isActive: { equals: true } },
|
||||
// Channel filtering (if specified)
|
||||
...(notification.channel ? [{
|
||||
or: [
|
||||
{ channels: { contains: notification.channel } },
|
||||
{ channels: { contains: 'all' } },
|
||||
{ channels: { exists: false } },
|
||||
]
|
||||
}] : [])
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
return subscriptions.docs
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Example: Phone number-based notifications
|
||||
|
||||
```typescript
|
||||
notificationsPlugin({
|
||||
fields: [
|
||||
{
|
||||
name: 'recipientPhone',
|
||||
type: 'text',
|
||||
label: 'Recipient Phone',
|
||||
admin: {
|
||||
description: 'Phone number of the notification recipient',
|
||||
},
|
||||
}
|
||||
],
|
||||
webPush: {
|
||||
enabled: true,
|
||||
autoPush: true,
|
||||
vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
|
||||
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
|
||||
vapidSubject: 'mailto:your-email@example.com',
|
||||
findSubscriptions: async (notification, payload) => {
|
||||
if (!notification.recipientPhone) return []
|
||||
|
||||
// Custom logic to find subscriptions by phone number
|
||||
// You might have a separate mapping table or user lookup
|
||||
const user = await payload.find({
|
||||
collection: 'users',
|
||||
where: { phone: { equals: notification.recipientPhone } },
|
||||
limit: 1
|
||||
})
|
||||
|
||||
if (!user.docs[0]) return []
|
||||
|
||||
const subscriptions = await payload.find({
|
||||
collection: 'push-subscriptions',
|
||||
where: {
|
||||
and: [
|
||||
{ user: { equals: user.docs[0].id } },
|
||||
{ isActive: { equals: true } }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
return subscriptions.docs
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- The default `recipient` field remains a user relationship for standard notifications
|
||||
- Add custom recipient fields via the `fields` option for your specific use case
|
||||
- Use the `findSubscriptions` hook to implement custom subscription lookup logic
|
||||
- The hook receives the full notification document and payload instance
|
||||
- Return an array of push subscription documents that should receive the notification
|
||||
- The plugin will handle the actual push notification sending and error handling
|
||||
|
||||
## Client-Side Integration
|
||||
|
||||
⚠️ **Authentication Required:** Users must be signed in to subscribe to push notifications. Push subscriptions are associated with user accounts.
|
||||
|
||||
### React Hook
|
||||
|
||||
```typescript
|
||||
import { usePushNotifications } from '@xtr-dev/payload-notifications/client'
|
||||
|
||||
function NotificationSettings() {
|
||||
const {
|
||||
isSupported,
|
||||
isSubscribed,
|
||||
permission,
|
||||
subscribe,
|
||||
unsubscribe
|
||||
} = usePushNotifications(process.env.NEXT_PUBLIC_VAPID_KEY)
|
||||
|
||||
if (!isSupported) return <div>Push notifications not supported</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Status: {isSubscribed ? 'Subscribed' : 'Not subscribed'}</p>
|
||||
<p>Permission: {permission}</p>
|
||||
|
||||
{!isSubscribed ? (
|
||||
<button onClick={subscribe}>Enable Notifications</button>
|
||||
) : (
|
||||
<button onClick={unsubscribe}>Disable Notifications</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Vanilla JavaScript
|
||||
|
||||
```typescript
|
||||
import { ClientPushManager } from '@xtr-dev/payload-notifications/client'
|
||||
|
||||
const pushManager = new ClientPushManager('your-vapid-public-key')
|
||||
|
||||
// Subscribe to notifications
|
||||
await pushManager.subscribe()
|
||||
|
||||
// Check subscription status
|
||||
const isSubscribed = await pushManager.isSubscribed()
|
||||
|
||||
// Unsubscribe
|
||||
await pushManager.unsubscribe()
|
||||
```
|
||||
|
||||
## Service Worker Setup
|
||||
|
||||
Generate a service worker file automatically:
|
||||
|
||||
```bash
|
||||
npx @xtr-dev/payload-notifications generate-sw
|
||||
```
|
||||
|
||||
This will create a `/public/sw.js` file with the complete service worker template that handles:
|
||||
|
||||
- Push notification events
|
||||
- Notification click handling
|
||||
- Service worker lifecycle management
|
||||
- Error handling and fallbacks
|
||||
- Notification tracking and analytics
|
||||
|
||||
**Important Notes:**
|
||||
- The service worker file **must** be placed at `/public/sw.js` in Next.js projects
|
||||
- This makes it accessible at `https://yourdomain.com/sw.js`
|
||||
- Service workers must be served from the root domain for security
|
||||
- After creating the file, restart your Next.js development server
|
||||
|
||||
## Server-Side Push Notifications
|
||||
|
||||
```typescript
|
||||
import { WebPushManager } from '@xtr-dev/payload-notifications/rsc'
|
||||
|
||||
// Send push notification to a user
|
||||
const pushManager = new WebPushManager(webPushConfig, payload)
|
||||
|
||||
await pushManager.sendToUser(
|
||||
userId,
|
||||
'Order Shipped!',
|
||||
'Your order #12345 has been shipped',
|
||||
{
|
||||
icon: '/icons/order-shipped.png',
|
||||
badge: '/icons/badge.png',
|
||||
data: { orderId: '12345', url: '/orders/12345' },
|
||||
actions: [
|
||||
{ action: 'view', title: 'View Order', icon: '/icons/view.png' },
|
||||
{ action: 'dismiss', title: 'Dismiss' }
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The plugin automatically creates these endpoints when web push is enabled:
|
||||
|
||||
- `POST /api/push-notifications/subscribe` - Subscribe to push notifications ⚠️ **Requires authentication**
|
||||
- `POST /api/push-notifications/unsubscribe` - Unsubscribe from push notifications
|
||||
- `GET /api/push-notifications/vapid-public-key` - Get VAPID public key
|
||||
- `POST /api/push-notifications/send` - Send notification to user ⚠️ **Requires authentication**
|
||||
- `POST /api/push-notifications/test` - Send test notification ⚠️ **Admin only**
|
||||
- `POST /api/push-notifications/track` - Track notification events
|
||||
|
||||
## Integration with Notifications Collection
|
||||
|
||||
When creating notifications, you can automatically send push notifications:
|
||||
|
||||
```typescript
|
||||
// Create notification and send push notification
|
||||
const notification = await payload.create({
|
||||
collection: 'notifications',
|
||||
data: {
|
||||
title: 'New Message',
|
||||
message: [{ children: [{ text: 'You have a new message!' }] }],
|
||||
recipient: userId,
|
||||
attachments: { message: messageId }
|
||||
}
|
||||
})
|
||||
|
||||
// With autoPush enabled, push notifications are sent automatically
|
||||
// Or manually send push notification
|
||||
if (webPushEnabled) {
|
||||
await pushManager.sendToUser(
|
||||
userId,
|
||||
notification.title,
|
||||
'You have a new notification',
|
||||
{
|
||||
data: {
|
||||
notificationId: notification.id,
|
||||
url: `/notifications/${notification.id}`
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xtr-dev/payload-notifications",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "A PayloadCMS plugin that adds a configurable notifications collection for sending messages with titles, content, and attachable relationship items",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -66,8 +66,8 @@ export interface WebPushConfig {
|
||||
export interface NotificationsPluginOptions {
|
||||
/** Collection configuration */
|
||||
collectionOverrides?: {
|
||||
notifications: (config: CollectionConfig) => CollectionConfig
|
||||
pushSubscriptions: (config: CollectionConfig) => CollectionConfig
|
||||
notifications?: (config: CollectionConfig) => CollectionConfig
|
||||
pushSubscriptions?: (config: CollectionConfig) => CollectionConfig
|
||||
}
|
||||
/** Web push notification configuration */
|
||||
webPush?: WebPushConfig
|
||||
|
||||
Reference in New Issue
Block a user