This document describes the refactoring of the Notification System implemented as part of issue #505. The refactoring consolidates multiple notification implementations into a unified, maintainable architecture with clear separation of concerns.
-
Multiple Conflicting Implementations: The codebase had three separate notification systems:
notificationStore.ts- Zustand-based local state managementNotificationprovider.tsx- React Context with WebSocket integrationuse-notification.ts- Simple toast-based utility hook
-
Inconsistent Data Structures: Different notification types and interfaces across implementations:
AppNotificationtype vsNotificationtype- Different field naming conventions
- Inconsistent timestamp handling (ISO string vs Date object)
-
Scattered Responsibilities: Business logic spread across multiple files without clear organization:
- Validation logic mixed with UI components
- Duplicate utility functions
- No clear service layer
-
Poor Test Coverage: Limited test coverage and no integration tests
src/lib/notifications/
├── types.ts # Unified type definitions
├── service.ts # Business logic layer
├── index.ts # Public API exports
└── __tests__/
├── service.test.ts # Unit tests for service layer
└── integration.test.ts # Integration tests
- Unified Type System: Single source of truth for all notification types
- Service Layer: Clear separation of business logic from UI components
- Better Organization: Logical grouping of related functionality
- Enhanced Testing: Comprehensive unit and integration tests
- Maintainability: Easier to extend and modify notification functionality
Centralized type definitions for the entire notification system:
- NotificationType: 'info' | 'success' | 'warning' | 'error' | 'message' | 'course' | 'system'
- NotificationChannel: 'push' | 'email' | 'sms' | 'in-app'
- NotificationPriority: 'low' | 'medium' | 'high' | 'urgent'
- NotificationCategory: 'course_update' | 'message' | 'achievement' | 'reminder' | 'system' | 'social' | 'payment'
- AppNotification: Main notification interface
- UserNotificationPreferences: User preference structure
- NotificationAnalytics: Analytics data structureBusiness logic layer that handles:
- Notification Creation:
createNotification()- Creates validated notifications - Delivery Logic:
deliverToChannels()- Handles multi-channel delivery - Preference Management:
validatePreferences(),createDefaultPreferences() - Delivery Checking:
shouldDeliver()- Checks if notification should be sent based on preferences
Example usage:
import { NotificationService } from '@/lib/notifications';
// Create a notification
const notification = NotificationService.createNotification({
message: 'Course updated!',
type: 'success',
category: 'course_update',
priority: 'high',
channels: ['in-app', 'email'],
});
// Validate preferences
const validation = NotificationService.validatePreferences(preferences);
if (!validation.valid) {
console.error(validation.errors);
}
// Deliver to channels
const results = await NotificationService.deliverToChannels(notification, ['email']);Refactored to use the new service layer:
- Uses
NotificationService.createNotification()for consistency - Maintains backward compatibility with existing API
- Improved type safety with unified types
Enhanced React hook with:
- Service layer integration for notification creation
- Improved preference validation
- Better multi-channel delivery support
- Enhanced analytics integration
- Notification preference heartbeat state for liveness monitoring
The notification preferences heartbeat runs from useNotifications after preferences load. It
writes notification_preferences_heartbeat_v1 to localStorage with the current userId,
lastBeatAt, intervalMs, and staleAfterMs. Consumers can read
preferencesHeartbeat.status (online, stale, or offline) and call
refreshPreferencesHeartbeat() to re-check the stored heartbeat without waiting for the next
interval. The preferences UI surfaces this as a compact sync status so users are not left guessing
when preference persistence is delayed or unavailable.
Most existing code will continue to work without changes due to backward compatibility. However, consider these updates:
const { addNotification } = useNotificationStore();
addNotification({
type: 'info',
message: 'Hello',
meta: { custom: 'data' },
});import { NotificationService } from '@/lib/notifications';
const notification = NotificationService.createNotification({
message: 'Hello',
type: 'info',
meta: { custom: 'data' },
});
// Then use with store or hook
const { addNotification } = useNotificationStore();
addNotification(notification);Update imports to use the unified types:
// Old
import { AppNotification } from '@/app/store/notificationStore';
// New
import { AppNotification } from '@/lib/notifications/types';
// or
import { AppNotification } from '@/lib/notifications';Service layer has comprehensive unit tests covering:
- Notification creation with various parameters
- Preference validation
- Multi-channel delivery
- Default preference generation
Run unit tests:
pnpm test src/lib/notifications/__tests__/service.test.tsIntegration tests verify:
- End-to-end notification flows
- Store and hook synchronization
- Service layer integration
- Persistence operations
- Analytics calculation
Run integration tests:
pnpm test src/lib/notifications/__tests__/integration.test.tsUpdated existing tests to use new type imports:
src/app/store/__tests__/notificationStore.test.tssrc/app/hooks/__tests__/useNotifications.test.ts
- Clearer API: Single entry point via
@/lib/notifications - Better TypeScript Support: Unified types improve autocomplete and type checking
- Easier Testing: Service layer is easily testable in isolation
- Consistent Behavior: All notification paths use same business logic
- Reduced Duplication: Single implementation of common operations
- Better Separation: UI components focus on presentation, service layer handles logic
- Easier Maintenance: Changes to notification logic in one place
- Improved Testability: Comprehensive test coverage
- Consistent Experience: All notifications follow same rules
- Better Reliability: Improved validation and error handling
- Enhanced Features: Better multi-channel support and preference handling
The refactoring maintains performance characteristics:
- No additional overhead for existing functionality
- Service layer methods are lightweight and fast
- LocalStorage operations remain unchanged
- WebSocket integration unaffected
The real-time notification provider now uses a dedicated NotificationSocketService
(src/lib/notifications/socket.ts) with production-ready reconnection behavior:
- Exponential backoff with configurable jitter to avoid thundering herds
- Automatic reconnect on unexpected disconnects and connection errors
- Outbound message queue so read/clear actions are sent after reconnect
- Browser lifecycle hooks (
online,visibilitychange) for faster recovery - Connection state API exposed via
NotificationProvidercontext (connectionState) - Graceful shutdown that clears timers, listeners, and pending reconnect attempts
Example:
const { connectionState } = useNotifications();
if (connectionState.status === 'reconnecting') {
// show subtle reconnecting indicator in the notification UI
}Potential areas for future improvement:
- Real Delivery Integration: Replace simulated delivery with actual channel implementations
- Notification Templates: Add template system for common notification types
- Batch Operations: Support for batch notification creation and delivery
- Scheduled Notifications: Add support for delayed/scheduled notifications
- Analytics Dashboard: UI for viewing notification analytics
- A/B Testing: Framework for testing notification effectiveness
If issues arise, the refactoring can be rolled back by:
- Reverting commits to this branch
- Restoring previous implementations
- No database changes required (all changes are code-only)
This refactoring significantly improves the notification system by:
- Consolidating multiple implementations into a unified architecture
- Adding comprehensive test coverage
- Improving code organization and maintainability
- Maintaining backward compatibility
- Providing clear migration path for future enhancements
The system is now better positioned to support future notification features and improvements.