Files
crowd-funder-for-time-pwa/doc/daily-notification-plugin-architecture.md
Jose Olarte III 14ffcb5434 feat: integrate daily notification plugin for native iOS/Android
Add native notification support via @timesafari/daily-notification-plugin
while maintaining existing Web Push for web/PWA builds. Platform detection
automatically selects the appropriate notification system at runtime.

Key Changes:
- Created NotificationService abstraction layer with unified API
- Implemented NativeNotificationService for iOS/Android
- Stubbed WebPushNotificationService for future web integration
- Registered DailyNotificationPlugin in Capacitor plugin system

Android Configuration:
- Added notification permissions (POST_NOTIFICATIONS, SCHEDULE_EXACT_ALARM, etc.)
- Registered DailyNotificationReceiver for alarm-based notifications
- Registered BootReceiver to restore schedules after device restart
- Added Room, WorkManager, and Coroutines dependencies
- Registered plugin in MainActivity.java

iOS Configuration:
- Added UIBackgroundModes (fetch, processing) to Info.plist
- Configured BGTaskSchedulerPermittedIdentifiers
- Added NSUserNotificationAlertStyle

Documentation:
- Created comprehensive integration guide
- Added architecture overview with diagrams
- Created implementation checklist
- Documented platform-specific behavior

Manual Steps Required:
- iOS: Enable Background Modes capability in Xcode
- iOS: Run `pod install` to install CapacitorDailyNotification pod
- Run `npx cap sync` to sync native projects

Platform Support:
- iOS: Native UNUserNotificationCenter (requires Xcode setup)
- Android: Native NotificationManager with AlarmManager
- Web/PWA: Existing Web Push (coexists, not yet wired to service)
- Electron: Ready (uses native implementation)

Status: Phase 1 complete - infrastructure ready for UI integration
Next: Update PushNotificationPermission.vue to use NotificationService
2026-01-21 22:22:48 +08:00

9.8 KiB

Daily Notification Plugin - Architecture Overview

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Vue Components                            │
│  (PushNotificationPermission.vue, AccountViewView.vue, etc.)    │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│              NotificationService (Factory)                       │
│           - Platform detection via Capacitor API                 │
│           - Singleton pattern                                    │
│           - Returns appropriate implementation                   │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                ┌───────────┴────────────┐
                ▼                        ▼
┌───────────────────────────┐  ┌────────────────────────────┐
│ NativeNotificationService │  │ WebPushNotificationService │
│                           │  │                            │
│ iOS/Android               │  │ Web/PWA                    │
│ - UNUserNotificationCenter│  │ - Web Push API             │
│ - NotificationManager     │  │ - Service Workers          │
│ - AlarmManager            │  │ - VAPID keys               │
│ - Background tasks        │  │ - Push server              │
└─────────────┬─────────────┘  └────────────┬───────────────┘
              │                              │
              ▼                              ▼
┌─────────────────────────┐  ┌──────────────────────────────┐
│  DailyNotificationPlugin│  │  Existing Web Push Logic     │
│  (Capacitor Plugin)     │  │  (PushNotificationPermission)│
│                         │  │                              │
│  - Native iOS code      │  │  - Service worker            │
│  - Native Android code  │  │  - VAPID subscription        │
│  - SQLite storage       │  │  - Push server integration   │
└─────────────────────────┘  └──────────────────────────────┘

Platform Decision Flow

User Action: Schedule Notification
         │
         ▼
   NotificationService.getInstance()
         │
         ├──> Check: Capacitor.isNativePlatform()
         │
    ┌────┴─────┐
    │          │
   YES        NO
    │          │
    ▼          ▼
Native      Web/PWA
Service     Service
    │          │
    ▼          ▼
Plugin      Web Push

Data Flow Example: Scheduling a Notification

Native Platform (iOS/Android)

1. User clicks "Enable Notifications"
   │
2. PushNotificationPermission.vue
   │
   └─> NotificationService.getInstance()
       │
       └─> Returns NativeNotificationService (detected iOS/Android)
           │
           └─> nativeService.requestPermissions()
               │
               └─> DailyNotification.requestPermissions() [Capacitor Plugin]
                   │
                   └─> Native code requests OS permissions
                       │
                       └─> Returns: { granted: true/false }

3. User sets time & message
   │
4. nativeService.scheduleDailyNotification({ time: '09:00', ... })
   │
   └─> DailyNotification.scheduleDailyReminder({ ... })
       │
       └─> Native code:
           - Stores in SQLite
           - Schedules AlarmManager (Android) or UNNotificationRequest (iOS)
           - Returns: success/failure

5. At 9:00 AM:
   - Android: AlarmManager triggers → DailyNotificationReceiver
   - iOS: UNUserNotificationCenter triggers notification
   - Notification appears even if app is closed

Web Platform

1. User clicks "Enable Notifications"
   │
2. PushNotificationPermission.vue
   │
   └─> NotificationService.getInstance()
       │
       └─> Returns WebPushNotificationService (detected web)
           │
           └─> webService.requestPermissions()
               │
               └─> Notification.requestPermission() [Browser API]
                   │
                   └─> Returns: 'granted'/'denied'/'default'

3. User sets time & message
   │
4. webService.scheduleDailyNotification({ ... })
   │
   └─> [TODO] Subscribe to push service with VAPID
       │
       └─> Send subscription to server with schedule time
           │
           └─> Server sends push at scheduled time
               │
               └─> Service worker receives → shows notification

File Organization

src/
├── plugins/
│   └── DailyNotificationPlugin.ts          [Plugin registration]
│
├── services/
│   └── notifications/
│       ├── index.ts                        [Barrel export]
│       ├── NotificationService.ts          [Factory + Interface]
│       ├── NativeNotificationService.ts    [iOS/Android impl]
│       └── WebPushNotificationService.ts   [Web impl stub]
│
├── components/
│   └── PushNotificationPermission.vue      [UI - to be updated]
│
└── views/
    └── AccountViewView.vue                 [Settings UI]

Key Design Decisions

1. Unified Interface

  • Single NotificationServiceInterface for all platforms
  • Consistent API regardless of underlying implementation
  • Type-safe across TypeScript codebase

2. Runtime Platform Detection

  • No build-time configuration needed
  • Same code bundle for all platforms
  • Factory pattern selects implementation automatically

3. Coexistence Strategy

  • Web Push and Native run on different platforms
  • No conflicts - mutually exclusive at runtime
  • Allows gradual migration and testing

4. Singleton Pattern

  • One service instance per app lifecycle
  • Efficient resource usage
  • Consistent state management

Permission Flow

Android

App Launch
  ↓
Check if POST_NOTIFICATIONS granted (API 33+)
  │
  ├─> YES: Ready to schedule
  │
  └─> NO: Request runtime permission
          ↓
      Show system dialog
          ↓
      User grants/denies
          ↓
      Schedule notifications (if granted)

iOS

App Launch
  ↓
Check notification authorization status
  │
  ├─> authorized: Ready to schedule
  │
  ├─> notDetermined: Request permission
  │       ↓
  │   Show system dialog
  │       ↓
  │   User grants/denies
  │
  └─> denied: Guide user to Settings

Web

App Load
  ↓
Check Notification.permission
  │
  ├─> "granted": Ready to subscribe
  │
  ├─> "default": Request permission
  │       ↓
  │   Show browser prompt
  │       ↓
  │   User grants/denies
  │
  └─> "denied": Cannot show notifications

Error Handling Strategy

// All methods return promises with success/failure
try {
  const granted = await service.requestPermissions();
  if (granted) {
    const success = await service.scheduleDailyNotification({...});
    if (success) {
      // Show success message
    } else {
      // Show scheduling error
    }
  } else {
    // Show permission denied message
  }
} catch (error) {
  // Log error and show generic error message
  logger.error('Notification error:', error);
}

Background Execution

Native (iOS/Android)

  • Full background support
  • Survives app termination
  • Survives device reboot (with BootReceiver)
  • Exact alarm scheduling
  • Works offline

Web/PWA

  • ⚠️ Limited background support
  • ⚠️ Requires active service worker
  • ⚠️ Browser/OS dependent
  • Needs network for delivery
  • ⚠️ iOS: Only on Home Screen PWAs (16.4+)

Storage

Native

DailyNotificationPlugin
    ↓
SQLite Database (Room/Core Data)
    ↓
Stores:
- Schedule configurations
- Content cache
- Delivery history
- Callback registrations

Web

Web Push
    ↓
IndexedDB (via Dexie)
    ↓
Stores:
- Settings (notifyingNewActivityTime, etc.)
- Push subscription info
- VAPID keys

Testing Strategy

Unit Testing

  • Mock Capacitor.isNativePlatform() to test both paths
  • Test factory returns correct implementation
  • Test each service implementation independently

Integration Testing

  • Test on actual devices (iOS/Android)
  • Test in browsers (Chrome, Safari, Firefox)
  • Verify notification delivery
  • Test permission flows

E2E Testing

  • Schedule notification → Wait → Verify delivery
  • Test app restart scenarios
  • Test device reboot scenarios
  • Test permission denial recovery

Key Takeaway: The architecture provides a clean separation between platforms while maintaining a unified API for Vue components. Platform detection happens automatically at runtime, and the appropriate notification system is used transparently.