diff --git a/README.md b/README.md index dcf5719..539909b 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,528 @@ # Daily Notification Plugin -A Native-First Capacitor plugin for reliable daily notifications across Android, iOS, and Web platforms. +**Author**: Matthew Raymer +**Version**: 2.0.0 +**Created**: 2025-09-22 09:22:32 UTC +**Last Updated**: 2025-09-22 09:22:32 UTC -## Key Features +## Overview -- **Native-First Architecture**: Optimized for mobile platforms with offline-first design -- **Shared SQLite Storage**: Single database file with WAL mode for concurrent access -- **TTL-at-Fire Enforcement**: Skip stale notifications before delivery -- **Rolling Window Safety**: Always keep today's notifications armed -- **Cross-Platform**: Unified API across Android, iOS, and Web -- **Production Ready**: Comprehensive error handling, performance optimization, and monitoring +The Daily Notification Plugin is a comprehensive Capacitor plugin that provides enterprise-grade daily notification functionality across Android, iOS, and Web platforms. It features dual scheduling, callback support, TTL-at-fire logic, and comprehensive observability. + +## Features + +### 🚀 **Core Features** + +- **Dual Scheduling**: Separate content fetch and user notification scheduling +- **TTL-at-Fire Logic**: Content validity checking at notification time +- **Callback System**: HTTP, local, and queue callback support +- **Circuit Breaker Pattern**: Automatic failure detection and recovery +- **Cross-Platform**: Android, iOS, and Web implementations + +### 📱 **Platform Support** + +- **Android**: WorkManager + AlarmManager + SQLite (Room) +- **iOS**: BGTaskScheduler + UNUserNotificationCenter + Core Data +- **Web**: Service Worker + IndexedDB + Push Notifications + +### 🔧 **Enterprise Features** + +- **Observability**: Structured logging with event codes +- **Health Monitoring**: Comprehensive status and performance metrics +- **Error Handling**: Exponential backoff and retry logic +- **Security**: Encrypted storage and secure callback handling + +## Installation + +```bash +npm install @timesafari/daily-notification-plugin +``` ## Quick Start +### Basic Usage + ```typescript import { DailyNotification } from '@timesafari/daily-notification-plugin'; -// Configure and schedule -await DailyNotification.configure({ - storage: 'shared', - ttlSeconds: 1800, - prefetchLeadMinutes: 15 +// Schedule a daily notification +await DailyNotification.scheduleDailyNotification({ + title: 'Daily Update', + body: 'Your daily content is ready', + schedule: '0 9 * * *' // 9 AM daily +}); +``` + +### Enhanced Usage (Recommended) + +```typescript +import { + DailyNotification, + DualScheduleConfiguration +} from '@timesafari/daily-notification-plugin'; + +// Configure dual scheduling +const config: DualScheduleConfiguration = { + contentFetch: { + schedule: '0 8 * * *', // Fetch at 8 AM + ttlSeconds: 3600, // 1 hour TTL + source: 'api', + url: 'https://api.example.com/daily-content' + }, + userNotification: { + schedule: '0 9 * * *', // Notify at 9 AM + title: 'Daily Update', + body: 'Your daily content is ready', + actions: [ + { id: 'view', title: 'View' }, + { id: 'dismiss', title: 'Dismiss' } + ] + } +}; + +await DailyNotification.scheduleDualNotification(config); +``` + +### Callback Integration + +```typescript +// Register analytics callback +await DailyNotification.registerCallback('analytics', { + kind: 'http', + target: 'https://analytics.example.com/events', + headers: { + 'Authorization': 'Bearer your-token', + 'Content-Type': 'application/json' + } }); +// Register local callback +await DailyNotification.registerCallback('database', { + kind: 'local', + target: 'saveToDatabase' +}); + +function saveToDatabase(event: CallbackEvent) { + console.log('Saving to database:', event); + // Your database save logic here +} +``` + +## API Reference + +### Core Methods + +#### `scheduleDailyNotification(options)` + +Schedule a basic daily notification (backward compatible). + +```typescript await DailyNotification.scheduleDailyNotification({ - url: 'https://api.example.com/daily-content', - time: '09:00', - title: 'Daily Update', - body: 'Your daily notification is ready' + title: string; + body: string; + schedule: string; // Cron expression + actions?: NotificationAction[]; }); ``` -## Installation +#### `scheduleContentFetch(config)` -```bash -npm install @timesafari/daily-notification-plugin +Schedule content fetching separately. + +```typescript +await DailyNotification.scheduleContentFetch({ + schedule: string; // Cron expression + ttlSeconds: number; // Time-to-live in seconds + source: string; // Content source identifier + url?: string; // API endpoint URL + headers?: Record; +}); ``` -## Documentation +#### `scheduleUserNotification(config)` -- **[Complete Usage Guide](USAGE.md)** - Comprehensive guide with examples and best practices -- **[API Reference](API.md)** - Complete method and type definitions -- **[Implementation Roadmap](doc/implementation-roadmap.md)** - Technical implementation details -- **[Notification System Spec](doc/notification-system.md)** - Architecture and design principles -- **[Glossary](doc/GLOSSARY.md)** - Key terminology and concepts +Schedule user notifications separately. -## Examples +```typescript +await DailyNotification.scheduleUserNotification({ + schedule: string; // Cron expression + title: string; // Notification title + body: string; // Notification body + actions?: NotificationAction[]; +}); +``` -- **Basic Usage**: `examples/usage.ts` -- **Phase-by-Phase Implementation**: - - Phase 1: `examples/phase1-*.ts` (Core Infrastructure) - - Phase 2: `examples/phase2-*.ts` (Platform Completion) - - Phase 3: `examples/phase3-*.ts` (Network Optimization) -- **Advanced Scenarios**: `examples/advanced-usage.ts` -- **Enterprise Features**: `examples/enterprise-usage.ts` +#### `scheduleDualNotification(config)` -## Configuration +Schedule both content fetch and user notification. + +```typescript +await DailyNotification.scheduleDualNotification({ + contentFetch: ContentFetchConfig; + userNotification: UserNotificationConfig; +}); +``` + +### Callback Methods + +#### `registerCallback(name, config)` + +Register a callback function. + +```typescript +await DailyNotification.registerCallback('callback-name', { + kind: 'http' | 'local' | 'queue'; + target: string; // URL or function name + headers?: Record; +}); +``` + +#### `unregisterCallback(name)` + +Remove a registered callback. + +```typescript +await DailyNotification.unregisterCallback('callback-name'); +``` + +#### `getRegisteredCallbacks()` + +Get list of registered callbacks. + +```typescript +const callbacks = await DailyNotification.getRegisteredCallbacks(); +// Returns: string[] +``` + +### Status Methods + +#### `getDualScheduleStatus()` + +Get comprehensive status information. + +```typescript +const status = await DailyNotification.getDualScheduleStatus(); +// Returns: { +// nextRuns: number[]; +// lastOutcomes: string[]; +// cacheAgeMs: number | null; +// staleArmed: boolean; +// queueDepth: number; +// circuitBreakers: CircuitBreakerStatus; +// performance: PerformanceMetrics; +// } +``` + +## Platform Requirements ### Android -Add the following permissions to your `AndroidManifest.xml`: +- **Minimum SDK**: API 21 (Android 5.0) +- **Target SDK**: API 34 (Android 14) +- **Permissions**: `POST_NOTIFICATIONS`, `SCHEDULE_EXACT_ALARM`, `USE_EXACT_ALARM` +- **Dependencies**: Room 2.6.1+, WorkManager 2.9.0+ + +### iOS + +- **Minimum Version**: iOS 13.0 +- **Background Modes**: Background App Refresh, Background Processing +- **Permissions**: Notification permissions required +- **Dependencies**: Core Data, BGTaskScheduler + +### Web + +- **Service Worker**: Required for background functionality +- **HTTPS**: Required for Service Worker and push notifications +- **Browser Support**: Chrome 40+, Firefox 44+, Safari 11.1+ + +## Configuration + +### Android Configuration + +#### AndroidManifest.xml ```xml + - - - - + + + + + + + ``` -## Development +#### build.gradle -### Prerequisites +```gradle +dependencies { + implementation "androidx.room:room-runtime:2.6.1" + implementation "androidx.work:work-runtime-ktx:2.9.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + annotationProcessor "androidx.room:room-compiler:2.6.1" +} +``` -- Node.js 14 or later -- Android Studio -- Android SDK -- Gradle +### iOS Configuration -### Building +#### Info.plist -```bash -# Install dependencies -npm install +```xml +UIBackgroundModes + + background-app-refresh + background-processing + + +BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.content-fetch + com.timesafari.dailynotification.notification-delivery + +``` -# Build the plugin -npm run build +#### Capabilities + +1. Enable "Background Modes" capability +2. Enable "Background App Refresh" +3. Enable "Background Processing" + +### Web Configuration + +#### Service Worker Registration + +```typescript +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + .then(registration => { + console.log('Service Worker registered:', registration); + }) + .catch(error => { + console.error('Service Worker registration failed:', error); + }); +} +``` + +#### Push Notification Setup + +```typescript +const permission = await Notification.requestPermission(); + +if (permission === 'granted') { + const subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: 'your-vapid-public-key' + }); + + await fetch('/api/push-subscription', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(subscription) + }); +} +``` -# Run tests +## Testing + +### Unit Tests + +```bash npm test ``` -### Project Structure +### Integration Tests + +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +describe('Integration Tests', () => { + test('dual scheduling workflow', async () => { + const config = { + contentFetch: { schedule: '0 8 * * *', ttlSeconds: 3600 }, + userNotification: { schedule: '0 9 * * *', title: 'Test' } + }; + + await DailyNotification.scheduleDualNotification(config); + const status = await DailyNotification.getDualScheduleStatus(); + expect(status.nextRuns.length).toBe(2); + }); +}); +``` + +## Enterprise Integration + +### Analytics Integration + +```typescript +// Google Analytics 4 +const ga4Callback = new GoogleAnalyticsCallback('G-XXXXXXXXXX', 'your-api-secret'); +await ga4Callback.register(); + +// Mixpanel +const mixpanelCallback = new MixpanelCallback('your-project-token'); +await mixpanelCallback.register(); +``` + +### CRM Integration + +```typescript +// Salesforce +const salesforceCallback = new SalesforceCallback('your-access-token', 'your-instance-url'); +await salesforceCallback.register(); + +// HubSpot +const hubspotCallback = new HubSpotCallback('your-api-key'); +await hubspotCallback.register(); +``` + +### Monitoring Integration + +```typescript +// Datadog +const datadogCallback = new DatadogCallback('your-api-key', 'your-app-key'); +await datadogCallback.register(); +// New Relic +const newrelicCallback = new NewRelicCallback('your-license-key'); +await newrelicCallback.register(); ``` -daily-notification-plugin/ -├── android/ # Android implementation -│ ├── app/ # Main application module -│ └── build.gradle # Root build configuration -├── src/ # TypeScript source -├── tests/ # Test files -├── package.json # Package configuration -└── README.md # This file + +## Troubleshooting + +### Common Issues + +#### Android + +- **Permission Denied**: Ensure all required permissions are declared +- **WorkManager Not Running**: Check battery optimization settings +- **Database Errors**: Verify Room database schema migration + +#### iOS + +- **Background Tasks Not Running**: Check Background App Refresh settings +- **Core Data Errors**: Verify Core Data model compatibility +- **Notification Permissions**: Request notification permissions + +#### Web + +- **Service Worker Not Registering**: Ensure HTTPS and proper file paths +- **Push Notifications Not Working**: Verify VAPID keys and server setup +- **IndexedDB Errors**: Check browser compatibility and storage quotas + +### Debug Commands + +```typescript +// Get comprehensive status +const status = await DailyNotification.getDualScheduleStatus(); +console.log('Status:', status); + +// Check registered callbacks +const callbacks = await DailyNotification.getRegisteredCallbacks(); +console.log('Callbacks:', callbacks); ``` +## Performance Considerations + +### Memory Usage + +- **Android**: Room database with connection pooling +- **iOS**: Core Data with lightweight contexts +- **Web**: IndexedDB with efficient indexing + +### Battery Optimization + +- **Android**: WorkManager with battery-aware constraints +- **iOS**: BGTaskScheduler with system-managed execution +- **Web**: Service Worker with efficient background sync + +### Network Usage + +- **Circuit Breaker**: Prevents excessive retry attempts +- **TTL-at-Fire**: Reduces unnecessary network calls +- **Exponential Backoff**: Intelligent retry scheduling + +## Security Considerations + +### Permissions + +- **Minimal Permissions**: Only request necessary permissions +- **Runtime Checks**: Verify permissions before operations +- **Graceful Degradation**: Handle permission denials gracefully + +### Data Protection + +- **Local Storage**: Encrypted local storage on all platforms +- **Network Security**: HTTPS-only for all network operations +- **Callback Security**: Validate callback URLs and headers + +### Privacy + +- **No Personal Data**: Plugin doesn't collect personal information +- **Local Processing**: All processing happens locally +- **User Control**: Users can disable notifications and callbacks + ## Contributing +### Development Setup + +```bash +git clone https://github.com/timesafari/daily-notification-plugin.git +cd daily-notification-plugin +npm install +npm run build +npm test +``` + +### Code Standards + +- **TypeScript**: Strict type checking enabled +- **ESLint**: Code quality and consistency +- **Prettier**: Code formatting +- **Jest**: Comprehensive testing + +### Pull Request Process + 1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add some amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Ensure all tests pass +6. Submit a pull request ## License -This project is licensed under the MIT License - see the LICENSE file for details. +MIT License - see [LICENSE](LICENSE) file for details. + +## Support -## Author +### Documentation -Matthew Raymer +- **API Reference**: Complete TypeScript definitions +- **Migration Guide**: [doc/migration-guide.md](doc/migration-guide.md) +- **Enterprise Examples**: [doc/enterprise-callback-examples.md](doc/enterprise-callback-examples.md) -## Security +### Community -This plugin follows security best practices: +- **GitHub Issues**: Report bugs and request features +- **Discussions**: Ask questions and share solutions +- **Contributing**: Submit pull requests and improvements -- Uses AndroidX for modern security features -- Implements proper permission handling -- Follows Android security guidelines -- Uses secure storage for sensitive data -- Implements proper error handling -- Logs security-relevant events -- Uses secure communication channels -- Implements proper access control -- Follows Android's security model -- Uses secure defaults +### Enterprise Support -## Changelog +- **Custom Implementations**: Tailored solutions for enterprise needs +- **Integration Support**: Help with complex integrations +- **Performance Optimization**: Custom performance tuning -### 1.0.0 +--- -- Initial release -- Basic notification scheduling -- System state handling -- Battery optimization support -- Background task management -- Rich logging system +**Version**: 2.0.0 +**Last Updated**: 2025-09-22 09:22:32 UTC +**Author**: Matthew Raymer diff --git a/doc/enterprise-callback-examples.md b/doc/enterprise-callback-examples.md new file mode 100644 index 0000000..a2a6e3f --- /dev/null +++ b/doc/enterprise-callback-examples.md @@ -0,0 +1,1083 @@ +# Enterprise Callback Examples + +**Author**: Matthew Raymer +**Version**: 2.0.0 +**Created**: 2025-09-22 09:22:32 UTC +**Last Updated**: 2025-09-22 09:22:32 UTC + +## Overview + +This document provides comprehensive examples of enterprise-grade callback implementations for the Daily Notification Plugin, covering analytics, CRM integration, database operations, and monitoring systems. + +## Table of Contents + +1. [Analytics Integration](#analytics-integration) +2. [CRM Integration](#crm-integration) +3. [Database Operations](#database-operations) +4. [Monitoring & Alerting](#monitoring--alerting) +5. [Multi-Service Orchestration](#multi-service-orchestration) +6. [Error Handling Patterns](#error-handling-patterns) +7. [Performance Optimization](#performance-optimization) +8. [Security Best Practices](#security-best-practices) + +## Analytics Integration + +### Google Analytics 4 + +```typescript +import { DailyNotification, CallbackEvent } from '@timesafari/daily-notification-plugin'; + +class GoogleAnalyticsCallback { + private measurementId: string; + private apiSecret: string; + + constructor(measurementId: string, apiSecret: string) { + this.measurementId = measurementId; + this.apiSecret = apiSecret; + } + + async register(): Promise { + await DailyNotification.registerCallback('ga4-analytics', { + kind: 'http', + target: `https://www.google-analytics.com/mp/collect?measurement_id=${this.measurementId}&api_secret=${this.apiSecret}`, + headers: { + 'Content-Type': 'application/json' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const payload = { + client_id: this.generateClientId(), + events: [{ + name: this.mapEventName(event.type), + params: { + event_category: 'daily_notification', + event_label: event.payload?.source || 'unknown', + value: this.calculateEventValue(event), + custom_parameter_1: event.id, + custom_parameter_2: event.at + } + }] + }; + + await this.sendToGA4(payload); + } + + private mapEventName(eventType: string): string { + const eventMap: Record = { + 'onFetchSuccess': 'content_fetch_success', + 'onFetchFailure': 'content_fetch_failure', + 'onNotifyDelivered': 'notification_delivered', + 'onNotifyClicked': 'notification_clicked', + 'onNotifyDismissed': 'notification_dismissed' + }; + return eventMap[eventType] || 'unknown_event'; + } + + private calculateEventValue(event: CallbackEvent): number { + // Calculate engagement value based on event type + const valueMap: Record = { + 'onFetchSuccess': 1, + 'onNotifyDelivered': 2, + 'onNotifyClicked': 5, + 'onNotifyDismissed': 0 + }; + return valueMap[event.type] || 0; + } + + private generateClientId(): string { + // Generate or retrieve client ID for GA4 + return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private async sendToGA4(payload: any): Promise { + // Implementation would send to GA4 Measurement Protocol + console.log('Sending to GA4:', payload); + } +} + +// Usage +const ga4Callback = new GoogleAnalyticsCallback('G-XXXXXXXXXX', 'your-api-secret'); +await ga4Callback.register(); +``` + +### Mixpanel Integration + +```typescript +class MixpanelCallback { + private projectToken: string; + private baseUrl: string; + + constructor(projectToken: string) { + this.projectToken = projectToken; + this.baseUrl = 'https://api.mixpanel.com'; + } + + async register(): Promise { + await DailyNotification.registerCallback('mixpanel-analytics', { + kind: 'http', + target: `${this.baseUrl}/track`, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.projectToken}` + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const eventData = { + event: this.mapEventName(event.type), + properties: { + distinct_id: this.getDistinctId(), + time: Math.floor(event.at / 1000), // Unix timestamp + $app_version: '2.0.0', + $os: this.getPlatform(), + notification_id: event.id, + content_source: event.payload?.source, + ttl_seconds: event.payload?.ttlSeconds, + fetch_duration: event.payload?.duration, + success: event.type.includes('Success') + } + }; + + await this.sendToMixpanel(eventData); + } + + private mapEventName(eventType: string): string { + return eventType.replace('on', '').toLowerCase(); + } + + private getDistinctId(): string { + // Generate or retrieve user ID + return `user_${Date.now()}`; + } + + private getPlatform(): string { + // Detect platform (Android, iOS, Web) + return 'web'; // Simplified for example + } + + private async sendToMixpanel(data: any): Promise { + // Implementation would send to Mixpanel API + console.log('Sending to Mixpanel:', data); + } +} +``` + +## CRM Integration + +### Salesforce Integration + +```typescript +class SalesforceCallback { + private accessToken: string; + private instanceUrl: string; + + constructor(accessToken: string, instanceUrl: string) { + this.accessToken = accessToken; + this.instanceUrl = instanceUrl; + } + + async register(): Promise { + await DailyNotification.registerCallback('salesforce-crm', { + kind: 'http', + target: `${this.instanceUrl}/services/data/v58.0/sobjects/Notification_Event__c/`, + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const salesforceRecord = { + Name: `Notification_${event.id}`, + Event_Type__c: event.type, + Event_Timestamp__c: new Date(event.at).toISOString(), + Notification_ID__c: event.id, + Content_Source__c: event.payload?.source, + Success__c: event.type.includes('Success'), + Error_Message__c: event.payload?.error || null, + User_Agent__c: this.getUserAgent(), + Platform__c: this.getPlatform() + }; + + await this.createSalesforceRecord(salesforceRecord); + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } + + private getPlatform(): string { + // Detect platform + return 'Web'; // Simplified for example + } + + private async createSalesforceRecord(record: any): Promise { + // Implementation would create Salesforce record + console.log('Creating Salesforce record:', record); + } +} +``` + +### HubSpot Integration + +```typescript +class HubSpotCallback { + private apiKey: string; + private baseUrl: string; + + constructor(apiKey: string) { + this.apiKey = apiKey; + this.baseUrl = 'https://api.hubapi.com'; + } + + async register(): Promise { + await DailyNotification.registerCallback('hubspot-crm', { + kind: 'http', + target: `${this.baseUrl}/crm/v3/objects/notifications`, + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const hubspotRecord = { + properties: { + notification_id: event.id, + event_type: event.type, + event_timestamp: event.at, + content_source: event.payload?.source, + success: event.type.includes('Success'), + error_message: event.payload?.error || null, + platform: this.getPlatform(), + user_agent: this.getUserAgent() + } + }; + + await this.createHubSpotRecord(hubspotRecord); + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } + + private async createHubSpotRecord(record: any): Promise { + // Implementation would create HubSpot record + console.log('Creating HubSpot record:', record); + } +} +``` + +## Database Operations + +### PostgreSQL Integration + +```typescript +class PostgreSQLCallback { + private connectionString: string; + + constructor(connectionString: string) { + this.connectionString = connectionString; + } + + async register(): Promise { + await DailyNotification.registerCallback('postgres-db', { + kind: 'http', + target: 'https://your-api.example.com/notifications', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer your-api-token' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const dbRecord = { + notification_id: event.id, + event_type: event.type, + event_timestamp: new Date(event.at), + content_source: event.payload?.source, + success: event.type.includes('Success'), + error_message: event.payload?.error || null, + platform: this.getPlatform(), + user_agent: this.getUserAgent(), + ttl_seconds: event.payload?.ttlSeconds, + fetch_duration: event.payload?.duration + }; + + await this.insertRecord(dbRecord); + } + + private async insertRecord(record: any): Promise { + // Implementation would insert into PostgreSQL + console.log('Inserting PostgreSQL record:', record); + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } +} +``` + +### MongoDB Integration + +```typescript +class MongoDBCallback { + private connectionString: string; + + constructor(connectionString: string) { + this.connectionString = connectionString; + } + + async register(): Promise { + await DailyNotification.registerCallback('mongodb-analytics', { + kind: 'http', + target: 'https://your-api.example.com/mongodb/notifications', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer your-api-token' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const mongoDocument = { + _id: event.id, + eventType: event.type, + timestamp: new Date(event.at), + payload: { + source: event.payload?.source, + success: event.type.includes('Success'), + error: event.payload?.error || null, + platform: this.getPlatform(), + userAgent: this.getUserAgent(), + ttlSeconds: event.payload?.ttlSeconds, + duration: event.payload?.duration + }, + metadata: { + createdAt: new Date(), + version: '2.0.0' + } + }; + + await this.insertDocument(mongoDocument); + } + + private async insertDocument(doc: any): Promise { + // Implementation would insert into MongoDB + console.log('Inserting MongoDB document:', doc); + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } +} +``` + +## Monitoring & Alerting + +### Datadog Integration + +```typescript +class DatadogCallback { + private apiKey: string; + private appKey: string; + + constructor(apiKey: string, appKey: string) { + this.apiKey = apiKey; + this.appKey = appKey; + } + + async register(): Promise { + await DailyNotification.registerCallback('datadog-monitoring', { + kind: 'http', + target: 'https://api.datadoghq.com/api/v1/events', + headers: { + 'Content-Type': 'application/json', + 'DD-API-KEY': this.apiKey, + 'DD-APPLICATION-KEY': this.appKey + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const datadogEvent = { + title: `Daily Notification ${event.type}`, + text: this.formatEventText(event), + priority: this.getPriority(event.type), + alert_type: this.getAlertType(event.type), + tags: [ + `platform:${this.getPlatform()}`, + `event_type:${event.type}`, + `source:${event.payload?.source || 'unknown'}`, + `success:${event.type.includes('Success')}` + ], + source_type_name: 'daily_notification_plugin' + }; + + await this.sendToDatadog(datadogEvent); + } + + private formatEventText(event: CallbackEvent): string { + return `Notification ${event.id} - ${event.type} at ${new Date(event.at).toISOString()}`; + } + + private getPriority(eventType: string): string { + if (eventType.includes('Failure')) return 'high'; + if (eventType.includes('Success')) return 'normal'; + return 'low'; + } + + private getAlertType(eventType: string): string { + if (eventType.includes('Failure')) return 'error'; + if (eventType.includes('Success')) return 'success'; + return 'info'; + } + + private getPlatform(): string { + return 'Web'; + } + + private async sendToDatadog(event: any): Promise { + // Implementation would send to Datadog + console.log('Sending to Datadog:', event); + } +} +``` + +### New Relic Integration + +```typescript +class NewRelicCallback { + private licenseKey: string; + + constructor(licenseKey: string) { + this.licenseKey = licenseKey; + } + + async register(): Promise { + await DailyNotification.registerCallback('newrelic-monitoring', { + kind: 'http', + target: 'https://metric-api.newrelic.com/metric/v1', + headers: { + 'Content-Type': 'application/json', + 'Api-Key': this.licenseKey + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const newRelicMetric = { + metrics: [{ + name: `daily_notification.${event.type}`, + type: 'count', + value: 1, + timestamp: Math.floor(event.at / 1000), + attributes: { + notification_id: event.id, + platform: this.getPlatform(), + source: event.payload?.source || 'unknown', + success: event.type.includes('Success'), + error: event.payload?.error || null + } + }] + }; + + await this.sendToNewRelic(newRelicMetric); + } + + private getPlatform(): string { + return 'Web'; + } + + private async sendToNewRelic(metric: any): Promise { + // Implementation would send to New Relic + console.log('Sending to New Relic:', metric); + } +} +``` + +## Multi-Service Orchestration + +### Event Bus Integration + +```typescript +class EventBusCallback { + private eventBusUrl: string; + private apiKey: string; + + constructor(eventBusUrl: string, apiKey: string) { + this.eventBusUrl = eventBusUrl; + this.apiKey = apiKey; + } + + async register(): Promise { + await DailyNotification.registerCallback('event-bus', { + kind: 'http', + target: `${this.eventBusUrl}/events`, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const eventBusMessage = { + id: event.id, + type: `daily_notification.${event.type}`, + timestamp: event.at, + source: 'daily_notification_plugin', + version: '2.0.0', + data: { + notification_id: event.id, + event_type: event.type, + payload: event.payload, + platform: this.getPlatform(), + user_agent: this.getUserAgent() + }, + metadata: { + correlation_id: this.generateCorrelationId(), + trace_id: this.generateTraceId() + } + }; + + await this.publishToEventBus(eventBusMessage); + } + + private generateCorrelationId(): string { + return `corr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private generateTraceId(): string { + return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } + + private async publishToEventBus(message: any): Promise { + // Implementation would publish to event bus + console.log('Publishing to event bus:', message); + } +} +``` + +### Apache Kafka Integration + +```typescript +class KafkaCallback { + private kafkaUrl: string; + private topic: string; + + constructor(kafkaUrl: string, topic: string) { + this.kafkaUrl = kafkaUrl; + this.topic = topic; + } + + async register(): Promise { + await DailyNotification.registerCallback('kafka-streams', { + kind: 'http', + target: `${this.kafkaUrl}/topics/${this.topic}`, + headers: { + 'Content-Type': 'application/vnd.kafka.json.v2+json' + } + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const kafkaMessage = { + records: [{ + key: event.id, + value: { + notification_id: event.id, + event_type: event.type, + timestamp: event.at, + payload: event.payload, + platform: this.getPlatform(), + user_agent: this.getUserAgent(), + metadata: { + version: '2.0.0', + source: 'daily_notification_plugin' + } + } + }] + }; + + await this.sendToKafka(kafkaMessage); + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } + + private async sendToKafka(message: any): Promise { + // Implementation would send to Kafka + console.log('Sending to Kafka:', message); + } +} +``` + +## Error Handling Patterns + +### Circuit Breaker Implementation + +```typescript +class CircuitBreakerCallback { + private failureThreshold: number = 5; + private timeout: number = 60000; // 1 minute + private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'; + private failureCount: number = 0; + private lastFailureTime: number = 0; + + async register(): Promise { + await DailyNotification.registerCallback('circuit-breaker', { + kind: 'local', + target: 'circuitBreakerHandler' + }); + } + + async handleCallback(event: CallbackEvent): Promise { + if (this.state === 'OPEN') { + if (Date.now() - this.lastFailureTime > this.timeout) { + this.state = 'HALF_OPEN'; + console.log('Circuit breaker transitioning to HALF_OPEN'); + } else { + console.log('Circuit breaker is OPEN, skipping callback'); + return; + } + } + + try { + await this.executeCallback(event); + this.onSuccess(); + } catch (error) { + this.onFailure(); + throw error; + } + } + + private async executeCallback(event: CallbackEvent): Promise { + // Your callback logic here + console.log('Executing callback:', event); + } + + private onSuccess(): void { + this.failureCount = 0; + this.state = 'CLOSED'; + } + + private onFailure(): void { + this.failureCount++; + this.lastFailureTime = Date.now(); + + if (this.failureCount >= this.failureThreshold) { + this.state = 'OPEN'; + console.log(`Circuit breaker opened after ${this.failureCount} failures`); + } + } +} +``` + +### Retry with Exponential Backoff + +```typescript +class RetryCallback { + private maxRetries: number = 5; + private baseDelay: number = 1000; // 1 second + private maxDelay: number = 60000; // 1 minute + + async register(): Promise { + await DailyNotification.registerCallback('retry-handler', { + kind: 'local', + target: 'retryHandler' + }); + } + + async handleCallback(event: CallbackEvent): Promise { + let attempt = 0; + let delay = this.baseDelay; + + while (attempt < this.maxRetries) { + try { + await this.executeCallback(event); + console.log(`Callback succeeded on attempt ${attempt + 1}`); + return; + } catch (error) { + attempt++; + console.log(`Callback failed on attempt ${attempt}:`, error); + + if (attempt >= this.maxRetries) { + console.log('Max retries exceeded, giving up'); + throw error; + } + + // Wait before retry + await this.sleep(delay); + delay = Math.min(delay * 2, this.maxDelay); + } + } + } + + private async executeCallback(event: CallbackEvent): Promise { + // Your callback logic here + console.log('Executing callback:', event); + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} +``` + +## Performance Optimization + +### Batch Processing + +```typescript +class BatchCallback { + private batchSize: number = 10; + private batchTimeout: number = 5000; // 5 seconds + private batch: CallbackEvent[] = []; + private batchTimer: NodeJS.Timeout | null = null; + + async register(): Promise { + await DailyNotification.registerCallback('batch-processor', { + kind: 'local', + target: 'batchHandler' + }); + } + + async handleCallback(event: CallbackEvent): Promise { + this.batch.push(event); + + if (this.batch.length >= this.batchSize) { + await this.processBatch(); + } else if (!this.batchTimer) { + this.batchTimer = setTimeout(() => { + this.processBatch(); + }, this.batchTimeout); + } + } + + private async processBatch(): Promise { + if (this.batch.length === 0) return; + + const currentBatch = [...this.batch]; + this.batch = []; + + if (this.batchTimer) { + clearTimeout(this.batchTimer); + this.batchTimer = null; + } + + try { + await this.sendBatch(currentBatch); + console.log(`Processed batch of ${currentBatch.length} events`); + } catch (error) { + console.error('Batch processing failed:', error); + // Re-queue failed events + this.batch.unshift(...currentBatch); + } + } + + private async sendBatch(events: CallbackEvent[]): Promise { + // Implementation would send batch to external service + console.log('Sending batch:', events); + } +} +``` + +### Rate Limiting + +```typescript +class RateLimitedCallback { + private requestsPerMinute: number = 60; + private requests: number[] = []; + + async register(): Promise { + await DailyNotification.registerCallback('rate-limited', { + kind: 'local', + target: 'rateLimitedHandler' + }); + } + + async handleCallback(event: CallbackEvent): Promise { + if (!this.isRateLimited()) { + await this.executeCallback(event); + this.recordRequest(); + } else { + console.log('Rate limit exceeded, skipping callback'); + } + } + + private isRateLimited(): boolean { + const now = Date.now(); + const oneMinuteAgo = now - 60000; + + // Remove old requests + this.requests = this.requests.filter(time => time > oneMinuteAgo); + + return this.requests.length >= this.requestsPerMinute; + } + + private recordRequest(): void { + this.requests.push(Date.now()); + } + + private async executeCallback(event: CallbackEvent): Promise { + // Your callback logic here + console.log('Executing rate-limited callback:', event); + } +} +``` + +## Security Best Practices + +### Authentication & Authorization + +```typescript +class SecureCallback { + private apiKey: string; + private secretKey: string; + + constructor(apiKey: string, secretKey: string) { + this.apiKey = apiKey; + this.secretKey = secretKey; + } + + async register(): Promise { + const signature = this.generateSignature(); + + await DailyNotification.registerCallback('secure-callback', { + kind: 'http', + target: 'https://your-api.example.com/secure-endpoint', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}`, + 'X-Signature': signature, + 'X-Timestamp': Date.now().toString() + } + }); + } + + private generateSignature(): string { + const timestamp = Date.now().toString(); + const message = `${this.apiKey}:${timestamp}`; + + // In a real implementation, you'd use HMAC-SHA256 + return btoa(message + this.secretKey); + } + + async handleCallback(event: CallbackEvent): Promise { + const securePayload = { + notification_id: event.id, + event_type: event.type, + timestamp: event.at, + payload: this.sanitizePayload(event.payload), + platform: this.getPlatform(), + user_agent: this.getUserAgent() + }; + + await this.sendSecureRequest(securePayload); + } + + private sanitizePayload(payload: any): any { + // Remove sensitive data + const sanitized = { ...payload }; + delete sanitized.password; + delete sanitized.token; + delete sanitized.secret; + return sanitized; + } + + private getPlatform(): string { + return 'Web'; + } + + private getUserAgent(): string { + return navigator.userAgent || 'Unknown'; + } + + private async sendSecureRequest(payload: any): Promise { + // Implementation would send secure request + console.log('Sending secure request:', payload); + } +} +``` + +### Data Encryption + +```typescript +class EncryptedCallback { + private encryptionKey: string; + + constructor(encryptionKey: string) { + this.encryptionKey = encryptionKey; + } + + async register(): Promise { + await DailyNotification.registerCallback('encrypted-callback', { + kind: 'local', + target: 'encryptedHandler' + }); + } + + async handleCallback(event: CallbackEvent): Promise { + const encryptedPayload = this.encryptPayload(event.payload); + + const secureEvent = { + ...event, + payload: encryptedPayload + }; + + await this.sendEncryptedEvent(secureEvent); + } + + private encryptPayload(payload: any): string { + // In a real implementation, you'd use proper encryption + const jsonString = JSON.stringify(payload); + return btoa(jsonString + this.encryptionKey); + } + + private async sendEncryptedEvent(event: any): Promise { + // Implementation would send encrypted event + console.log('Sending encrypted event:', event); + } +} +``` + +## Usage Examples + +### Complete Enterprise Setup + +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +class EnterpriseNotificationManager { + private callbacks: any[] = []; + + async initialize(): Promise { + // Register all enterprise callbacks + await this.registerAnalyticsCallbacks(); + await this.registerCRMCallbacks(); + await this.registerDatabaseCallbacks(); + await this.registerMonitoringCallbacks(); + + // Configure dual scheduling + await this.configureDualScheduling(); + } + + private async registerAnalyticsCallbacks(): Promise { + const ga4Callback = new GoogleAnalyticsCallback('G-XXXXXXXXXX', 'your-api-secret'); + await ga4Callback.register(); + this.callbacks.push(ga4Callback); + + const mixpanelCallback = new MixpanelCallback('your-project-token'); + await mixpanelCallback.register(); + this.callbacks.push(mixpanelCallback); + } + + private async registerCRMCallbacks(): Promise { + const salesforceCallback = new SalesforceCallback('your-access-token', 'your-instance-url'); + await salesforceCallback.register(); + this.callbacks.push(salesforceCallback); + + const hubspotCallback = new HubSpotCallback('your-api-key'); + await hubspotCallback.register(); + this.callbacks.push(hubspotCallback); + } + + private async registerDatabaseCallbacks(): Promise { + const postgresCallback = new PostgreSQLCallback('your-connection-string'); + await postgresCallback.register(); + this.callbacks.push(postgresCallback); + + const mongoCallback = new MongoDBCallback('your-connection-string'); + await mongoCallback.register(); + this.callbacks.push(mongoCallback); + } + + private async registerMonitoringCallbacks(): Promise { + const datadogCallback = new DatadogCallback('your-api-key', 'your-app-key'); + await datadogCallback.register(); + this.callbacks.push(datadogCallback); + + const newrelicCallback = new NewRelicCallback('your-license-key'); + await newrelicCallback.register(); + this.callbacks.push(newrelicCallback); + } + + private async configureDualScheduling(): Promise { + const config = { + contentFetch: { + schedule: '0 8 * * *', // 8 AM + ttlSeconds: 3600, // 1 hour TTL + source: 'api', + url: 'https://api.example.com/daily-content' + }, + userNotification: { + schedule: '0 9 * * *', // 9 AM + title: 'Daily Update', + body: 'Your daily content is ready', + actions: [ + { id: 'view', title: 'View' }, + { id: 'dismiss', title: 'Dismiss' } + ] + } + }; + + await DailyNotification.scheduleDualNotification(config); + } + + async getStatus(): Promise { + return await DailyNotification.getDualScheduleStatus(); + } + + async getCallbacks(): Promise { + return await DailyNotification.getRegisteredCallbacks(); + } +} + +// Usage +const enterpriseManager = new EnterpriseNotificationManager(); +await enterpriseManager.initialize(); + +// Monitor status +const status = await enterpriseManager.getStatus(); +console.log('Enterprise status:', status); + +// List callbacks +const callbacks = await enterpriseManager.getCallbacks(); +console.log('Registered callbacks:', callbacks); +``` + +--- + +**Next Steps**: After implementing enterprise callbacks, review the [Migration Guide](./migration-guide.md) for platform-specific setup instructions. diff --git a/doc/migration-guide.md b/doc/migration-guide.md new file mode 100644 index 0000000..2946db9 --- /dev/null +++ b/doc/migration-guide.md @@ -0,0 +1,436 @@ +# Daily Notification Plugin Migration Guide + +**Author**: Matthew Raymer +**Version**: 2.0.0 +**Created**: 2025-09-22 09:22:32 UTC +**Last Updated**: 2025-09-22 09:22:32 UTC + +## Overview + +This migration guide helps you transition from the basic daily notification plugin to the enhanced version with dual scheduling, callback support, and comprehensive observability. + +## Breaking Changes + +### API Changes + +#### New Methods Added + +- `scheduleContentFetch()` - Schedule content fetching separately +- `scheduleUserNotification()` - Schedule user notifications separately +- `scheduleDualNotification()` - Schedule both content fetch and notification +- `getDualScheduleStatus()` - Get comprehensive status information +- `registerCallback()` - Register callback functions +- `unregisterCallback()` - Remove callback functions +- `getRegisteredCallbacks()` - List registered callbacks + +#### Enhanced Configuration + +- New `DualScheduleConfiguration` interface +- Enhanced `NotificationOptions` with callback support +- New `ContentFetchConfig` and `UserNotificationConfig` interfaces + +### Platform Requirements + +#### Android + +- **Minimum SDK**: API 21 (Android 5.0) +- **Target SDK**: API 34 (Android 14) +- **Permissions**: `POST_NOTIFICATIONS`, `SCHEDULE_EXACT_ALARM`, `USE_EXACT_ALARM` +- **Dependencies**: Room 2.6.1+, WorkManager 2.9.0+ + +#### iOS + +- **Minimum Version**: iOS 13.0 +- **Background Modes**: Background App Refresh, Background Processing +- **Permissions**: Notification permissions required +- **Dependencies**: Core Data, BGTaskScheduler + +#### Web + +- **Service Worker**: Required for background functionality +- **HTTPS**: Required for Service Worker and push notifications +- **Browser Support**: Chrome 40+, Firefox 44+, Safari 11.1+ + +## Migration Steps + +### Step 1: Update Dependencies + +```bash +npm install @timesafari/daily-notification-plugin@^2.0.0 +``` + +### Step 2: Update Import Statements + +```typescript +// Before +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +// After +import { + DailyNotification, + DualScheduleConfiguration, + ContentFetchConfig, + UserNotificationConfig, + CallbackEvent +} from '@timesafari/daily-notification-plugin'; +``` + +### Step 3: Update Configuration + +#### Basic Migration (Minimal Changes) + +```typescript +// Before +await DailyNotification.scheduleDailyNotification({ + title: 'Daily Update', + body: 'Your daily content is ready', + schedule: '0 9 * * *' +}); + +// After (backward compatible) +await DailyNotification.scheduleDailyNotification({ + title: 'Daily Update', + body: 'Your daily content is ready', + schedule: '0 9 * * *' +}); +``` + +#### Enhanced Migration (Recommended) + +```typescript +// After (enhanced with dual scheduling) +const config: DualScheduleConfiguration = { + contentFetch: { + schedule: '0 8 * * *', // Fetch at 8 AM + ttlSeconds: 3600, // 1 hour TTL + source: 'api', + url: 'https://api.example.com/daily-content' + }, + userNotification: { + schedule: '0 9 * * *', // Notify at 9 AM + title: 'Daily Update', + body: 'Your daily content is ready', + actions: [ + { id: 'view', title: 'View' }, + { id: 'dismiss', title: 'Dismiss' } + ] + } +}; + +await DailyNotification.scheduleDualNotification(config); +``` + +### Step 4: Add Callback Support + +```typescript +// Register callbacks for external integrations +await DailyNotification.registerCallback('analytics', { + kind: 'http', + target: 'https://analytics.example.com/events', + headers: { + 'Authorization': 'Bearer your-token', + 'Content-Type': 'application/json' + } +}); + +await DailyNotification.registerCallback('database', { + kind: 'local', + target: 'saveToDatabase' +}); + +// Local callback function +function saveToDatabase(event: CallbackEvent) { + console.log('Saving to database:', event); + // Your database save logic here +} +``` + +### Step 5: Update Status Monitoring + +```typescript +// Before +const status = await DailyNotification.getNotificationStatus(); + +// After (enhanced status) +const status = await DailyNotification.getDualScheduleStatus(); +console.log('Next runs:', status.nextRuns); +console.log('Cache age:', status.cacheAgeMs); +console.log('Circuit breakers:', status.circuitBreakers); +console.log('Performance:', status.performance); +``` + +## Platform-Specific Migration + +### Android Migration + +#### Update AndroidManifest.xml + +```xml + + + + + + + + + + + + + + +``` + +#### Update build.gradle + +```gradle +dependencies { + implementation "androidx.room:room-runtime:2.6.1" + implementation "androidx.work:work-runtime-ktx:2.9.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + annotationProcessor "androidx.room:room-compiler:2.6.1" +} +``` + +### iOS Migration + +#### Update Info.plist + +```xml +UIBackgroundModes + + background-app-refresh + background-processing + + +BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.content-fetch + com.timesafari.dailynotification.notification-delivery + +``` + +#### Update Capabilities + +1. Enable "Background Modes" capability +2. Enable "Background App Refresh" +3. Enable "Background Processing" + +### Web Migration + +#### Service Worker Registration + +```typescript +// Register Service Worker +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + .then(registration => { + console.log('Service Worker registered:', registration); + }) + .catch(error => { + console.error('Service Worker registration failed:', error); + }); +} +``` + +#### Push Notification Setup + +```typescript +// Request notification permission +const permission = await Notification.requestPermission(); + +if (permission === 'granted') { + // Subscribe to push notifications + const subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: 'your-vapid-public-key' + }); + + // Send subscription to your server + await fetch('/api/push-subscription', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(subscription) + }); +} +``` + +## Testing Migration + +### Unit Tests + +```typescript +import { DailyNotification } from '@timesafari/daily-notification-plugin'; + +describe('Migration Tests', () => { + test('backward compatibility', async () => { + // Test that old API still works + await DailyNotification.scheduleDailyNotification({ + title: 'Test', + body: 'Test body', + schedule: '0 9 * * *' + }); + }); + + test('new dual scheduling', async () => { + const config = { + contentFetch: { schedule: '0 8 * * *', ttlSeconds: 3600 }, + userNotification: { schedule: '0 9 * * *', title: 'Test' } + }; + + await DailyNotification.scheduleDualNotification(config); + const status = await DailyNotification.getDualScheduleStatus(); + expect(status.nextRuns).toBeDefined(); + }); + + test('callback registration', async () => { + await DailyNotification.registerCallback('test', { + kind: 'local', + target: 'testCallback' + }); + + const callbacks = await DailyNotification.getRegisteredCallbacks(); + expect(callbacks).toContain('test'); + }); +}); +``` + +### Integration Tests + +```typescript +describe('Integration Tests', () => { + test('end-to-end dual scheduling', async () => { + // Schedule content fetch + await DailyNotification.scheduleContentFetch({ + schedule: '0 8 * * *', + ttlSeconds: 3600, + source: 'api', + url: 'https://api.example.com/content' + }); + + // Schedule notification + await DailyNotification.scheduleUserNotification({ + schedule: '0 9 * * *', + title: 'Daily Update', + body: 'Content ready' + }); + + // Verify status + const status = await DailyNotification.getDualScheduleStatus(); + expect(status.nextRuns.length).toBe(2); + }); +}); +``` + +## Troubleshooting + +### Common Issues + +#### Android + +- **Permission Denied**: Ensure all required permissions are declared +- **WorkManager Not Running**: Check battery optimization settings +- **Database Errors**: Verify Room database schema migration + +#### iOS + +- **Background Tasks Not Running**: Check Background App Refresh settings +- **Core Data Errors**: Verify Core Data model compatibility +- **Notification Permissions**: Request notification permissions + +#### Web + +- **Service Worker Not Registering**: Ensure HTTPS and proper file paths +- **Push Notifications Not Working**: Verify VAPID keys and server setup +- **IndexedDB Errors**: Check browser compatibility and storage quotas + +### Debug Commands + +```typescript +// Get comprehensive status +const status = await DailyNotification.getDualScheduleStatus(); +console.log('Status:', status); + +// Check registered callbacks +const callbacks = await DailyNotification.getRegisteredCallbacks(); +console.log('Callbacks:', callbacks); + +// Test callback firing +await DailyNotification.registerCallback('debug', { + kind: 'local', + target: 'debugCallback' +}); + +function debugCallback(event: CallbackEvent) { + console.log('Debug callback fired:', event); +} +``` + +## Performance Considerations + +### Memory Usage + +- **Android**: Room database with connection pooling +- **iOS**: Core Data with lightweight contexts +- **Web**: IndexedDB with efficient indexing + +### Battery Optimization + +- **Android**: WorkManager with battery-aware constraints +- **iOS**: BGTaskScheduler with system-managed execution +- **Web**: Service Worker with efficient background sync + +### Network Usage + +- **Circuit Breaker**: Prevents excessive retry attempts +- **TTL-at-Fire**: Reduces unnecessary network calls +- **Exponential Backoff**: Intelligent retry scheduling + +## Security Considerations + +### Permissions + +- **Minimal Permissions**: Only request necessary permissions +- **Runtime Checks**: Verify permissions before operations +- **Graceful Degradation**: Handle permission denials gracefully + +### Data Protection + +- **Local Storage**: Encrypted local storage on all platforms +- **Network Security**: HTTPS-only for all network operations +- **Callback Security**: Validate callback URLs and headers + +### Privacy + +- **No Personal Data**: Plugin doesn't collect personal information +- **Local Processing**: All processing happens locally +- **User Control**: Users can disable notifications and callbacks + +## Support + +### Documentation + +- **API Reference**: Complete TypeScript definitions +- **Examples**: Comprehensive usage examples +- **Troubleshooting**: Common issues and solutions + +### Community + +- **GitHub Issues**: Report bugs and request features +- **Discussions**: Ask questions and share solutions +- **Contributing**: Submit pull requests and improvements + +### Enterprise Support + +- **Custom Implementations**: Tailored solutions for enterprise needs +- **Integration Support**: Help with complex integrations +- **Performance Optimization**: Custom performance tuning + +--- + +**Next Steps**: After migration, explore the [Enterprise Callback Examples](./enterprise-callback-examples.md) for advanced integration patterns.