Push Notifications

Push Notifications Package (@repo/push-notifications)

A comprehensive push notification service supporting multiple providers (Expo, Firebase, Web Push) for sending and managing push notifications with user activity tracking.

Why You Need This

Engaging users across devices requires reliable push notifications. This package unifies provider APIs so you can deliver messages without writing custom code for each service. Check the Getting Started guide to enable provider credentials.

Features

  • Multi-provider support (Expo, Firebase Cloud Messaging, Web Push)
  • Database integration (@repo/database) for notification tracking and user preferences
  • User activity tracking via lastActiveAt timestamps
  • Support for identifying inactive users
  • Pre-built notification templates (src/templates)
  • Support for device token management (registration, removal)
  • Message status tracking (pending, sent, delivered, read)
  • Respects user preferences and "Do Not Disturb" settings
  • TypeScript support

Database Schema

This package relies on schemas defined in @repo/database/src/schema/notifications.ts, which includes tables like:

  • pushNotifications: Stores individual sent notifications, their content, status, and associated user.
  • notificationPreferences: Stores user-level preferences for receiving different types of notifications.
  • deviceTokens: Stores registered device tokens (from Expo, FCM, Web Push) linked to users, along with device metadata and last activity timestamp.

Setup

  1. Install Dependencies: Ensure necessary provider SDKs are installed (e.g., expo-server-sdk-node, firebase-admin, web-push).
  2. Configure Provider: Set up environment variables for your chosen provider(s).

Expo Setup

EXPO_ACCESS_TOKEN=your_expo_access_token

Firebase Setup

# Path to your Firebase service account JSON file FIREBASE_SERVICE_ACCOUNT_PATH=path/to/service-account.json # Your Firebase project's Realtime Database URL (often optional) FIREBASE_DATABASE_URL=https://your-project.firebaseio.com

Web Push Setup

# VAPID Public Key WEB_PUSH_PUBLIC_KEY=your_vapid_public_key # VAPID Private Key WEB_PUSH_PRIVATE_KEY=your_vapid_private_key # Contact email for VAPID WEB_PUSH_SUBJECT=mailto:your-email@example.com

Info: You can generate VAPID keys using the WebPushProvider.generateVAPIDKeys() static method from this package. Remember to store these keys securely.

Core Service: PushNotificationService

The main entry point is the PushNotificationService class.

Initialization

Create an instance, specifying the provider and its configuration.

import { PushNotificationService } from '@repo/push-notifications'; // Example: Initialize for Expo const pushService = PushNotificationService.create({ provider: 'expo', expo: { accessToken: process.env.EXPO_ACCESS_TOKEN, }, // Optionally configure other providers if needed // firebase: { ... }, // web: { ... }, });

Sending Notifications

// Send a notification to a specific user ID // The service looks up the user's registered & active device tokens await pushService.sendToUser({ userId: 'user123', title: 'New Message', body: 'You have received a new message.', data: { // Optional data payload messageId: 'msg_abc', actionUrl: '/messages/msg_abc' } });

Using Templates

The package provides pre-built notification templates (e.g., newLoginNotification) which return structured title, body, and data.

import { PushNotificationService, newLoginNotification } from '@repo/push-notifications'; const pushService = PushNotificationService.create({ /* ... */ }); const deviceInfo = { deviceType: 'Mobile', os: 'Android' }; const location = { city: 'London', country: 'UK', ip: '...' }; // Generate notification content from the template const notificationContent = newLoginNotification(deviceInfo, location); await pushService.sendToUser({ userId: 'user456', title: notificationContent.title, body: notificationContent.body, data: notificationContent.data, });

Available Templates (check src/templates/index.ts):

  • newLoginNotification
  • systemUpdateNotification
  • messageReceivedNotification

Managing Notifications

// Get notifications for a user (with pagination and filtering) const userNotifications = await pushService.getUserNotifications('user123', { limit: 10, offset: 0, status: 'delivered' // Filter by status (optional) }); // Mark a specific notification as read by the user // This also updates the user's last activity timestamp await pushService.markAsRead('notification_xyz', 'user123');

Device Token Management

Register and remove device tokens as users log in/out or grant/revoke permissions.

import { registerDeviceToken, removeDeviceToken } from '@repo/push-notifications'; // Register a token (e.g., on login or app startup) await registerDeviceToken( 'user123', // The user ID 'ExponentPushToken[xxxxxxxxxxxxxx]', // The actual token 'expo', // The provider ('expo', 'fcm', 'web') { // Optional device metadata deviceName: 'Pixel 6', osName: 'Android', osVersion: '13' } ); // Remove a token (e.g., on logout) await removeDeviceToken('ExponentPushToken[xxxxxxxxxxxxxx]');

User Activity Tracking

Keep track of when users were last active to identify inactive users or potentially suppress notifications.

import { updateUserActivity, updateTokenActivity } from '@repo/push-notifications'; // Update activity for a specific token (e.g., app opened) await updateTokenActivity('ExponentPushToken[yyyyyyyyyyyyyy]'); // Update activity for ALL of a user's tokens (e.g., user performed an action) await updateUserActivity('user123'); // Example usage in an API route (update activity in the background) app.get('/api/some-action', async (c) => { const user = c.get('user'); // Assuming user is available in context if (user) { updateUserActivity(user.id).catch(console.error); } // ... rest of the route handler ... return c.json({ success: true }); }); // Find users inactive since a specific date const cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days ago const inactiveUsers = await pushService.getInactiveUsers(cutoffDate); // Returns array of user IDs and their last active timestamp

Warning: Regularly calling updateUserActivity or updateTokenActivity is crucial for the accuracy of inactive user detection.

Providers

  • Expo: Uses expo-server-sdk-node.
  • Firebase (FCM): Uses firebase-admin.
  • Web Push: Uses web-push library and requires client-side Service Worker implementation for subscription management.