# TimeSafari Daily Notification Plugin Integration Guide

**Author**: Matthew Raymer  
**Version**: 2.2.0  
**Created**: 2025-01-27 12:00:00 UTC  
**Last Updated**: 2025-10-08 06:02:45 UTC

## Overview

This document provides comprehensive step-by-step instructions for integrating the TimeSafari Daily Notification Plugin into the TimeSafari application. The plugin features a **native-first architecture** with robust polling interface where the host app defines the inputs and response format, and the plugin provides a reliable polling routine optimized for **Android, iOS, and Electron platforms**.

### New Generic Polling Architecture

The plugin provides a **structured request/response polling system** where:

1. **Host App Defines**: Request schema, response schema, transformation logic, notification logic
2. **Plugin Provides**: Generic polling routine with retry logic, authentication, scheduling, storage pressure management
3. **Benefits**: Platform-agnostic, flexible, testable, maintainable

### TimeSafari Community Features

The Daily Notification Plugin supports TimeSafari's community-building goals by providing reliable daily notifications for:

**Offers**
- New offers directed to me
- Changed offers directed to me
- New offers to my projects
- Changed offers to my projects
- New offers to my favorited projects
- Changed offers to my favorited projects

**Projects**
- Local projects that are new
- Local projects that have changed
- Projects with content of interest that are new
- Favorited projects that have changed

**People**
- Local people who are new
- Local people who have changed
- People with content of interest who are new
- Favorited people who have changed
- People in my contacts who have changed

**Items**
- Local items that are new
- Local items that have changed
- Favorited items that have changed

All notifications are delivered through a single route that can be queried or bundled for efficient delivery while maintaining privacy-preserving communication.

This plugin provides enterprise-grade daily notification functionality with dual scheduling, callback support, TTL-at-fire logic, and comprehensive observability across Mobile (Capacitor) and Desktop (Electron) platforms with native-first architecture.

### Native-First Architecture

The plugin has been optimized for **native-first deployment** with the following key changes:

**Platform Support:**
- ✅ **Android**: WorkManager + AlarmManager + SQLite
- ✅ **iOS**: BGTaskScheduler + UNUserNotificationCenter + Core Data
- ✅ **Electron**: Desktop notifications + SQLite/LocalStorage
- ❌ **Web (PWA)**: Removed for native-first focus

**Key Benefits:**
- **Simplified Architecture**: Focused on mobile and desktop platforms
- **Better Performance**: Optimized for native platform capabilities
- **Reduced Complexity**: Fewer platform-specific code paths
- **Cleaner Codebase**: Removed unused web-specific code (~90 lines)

**Storage Strategy:**
- **Native Platforms**: SQLite integration with host-managed storage
- **Electron**: SQLite or LocalStorage fallback
- **No Browser Storage**: IndexedDB support removed

## Prerequisites

- Node.js 18+ and npm installed
- Android Studio (for Android development)
- Xcode 14+ (for iOS development)
- Git access to the TimeSafari daily-notification-plugin repository
- Understanding of Capacitor plugin architecture
- Basic knowledge of TypeScript and Vue.js (for TimeSafari integration)
- Understanding of TimeSafari's privacy-preserving claims architecture
- Familiarity with decentralized identifiers (DIDs) and cryptographic verification

## Plugin Repository Structure

The TimeSafari Daily Notification Plugin follows the standard Capacitor plugin structure:
```
daily-notification-plugin/
├── android/
│   ├── build.gradle                    # Plugin build configuration
│   ├── src/main/java/com/timesafari/dailynotification/
│   │   ├── DailyNotificationPlugin.java
│   │   ├── DailyNotificationWorker.java
│   │   ├── DailyNotificationDatabase.java
│   │   └── ... (other plugin classes)
│   └── src/main/AndroidManifest.xml
├── ios/
│   ├── DailyNotificationPlugin.swift
│   ├── NotificationManager.swift
│   ├── ContentFetcher.swift
│   ├── CallbackRegistry.swift
│   └── DailyNotificationPlugin.podspec
├── src/
│   ├── definitions.ts
│   ├── daily-notification.ts
│   ├── callback-registry.ts
│   ├── observability.ts
│   └── (web support removed - native-first architecture)
│       ├── service-worker-manager.ts
│       └── sw.ts
├── dist/
│   ├── plugin.js
│   ├── esm/
│   └── (web support removed - native-first architecture)
├── package.json
├── capacitor.config.ts
└── README.md
```

## Generic Polling Integration

### Quick Start with Generic Polling

The new generic polling interface allows TimeSafari to define exactly what data it needs and how to process it:

```typescript
import { 
  GenericPollingRequest, 
  PollingScheduleConfig,
  StarredProjectsRequest,
  StarredProjectsResponse
} from '@timesafari/polling-contracts';

// 1. Define your polling request
const starredProjectsRequest: GenericPollingRequest<StarredProjectsRequest, StarredProjectsResponse> = {
  endpoint: '/api/v2/report/plansLastUpdatedBetween',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'User-Agent': 'TimeSafari-DailyNotificationPlugin/1.0.0'
  },
  body: {
    planIds: [], // Will be populated from user settings
    afterId: undefined, // Will be populated from watermark
    limit: 100
  },
  responseSchema: {
    validate: (data: any): data is StarredProjectsResponse => {
      return data && 
             Array.isArray(data.data) && 
             typeof data.hitLimit === 'boolean' &&
             data.pagination && 
             typeof data.pagination.hasMore === 'boolean';
    },
    transformError: (error: any) => ({
      code: 'VALIDATION_ERROR',
      message: error.message || 'Validation failed',
      retryable: false
    })
  },
  retryConfig: {
    maxAttempts: 3,
    backoffStrategy: 'exponential',
    baseDelayMs: 1000
  },
  timeoutMs: 30000
};

// 2. Schedule the polling
const scheduleConfig: PollingScheduleConfig<StarredProjectsRequest, StarredProjectsResponse> = {
  request: starredProjectsRequest,
  schedule: {
    cronExpression: '0 10,16 * * *', // 10 AM and 4 PM daily
    timezone: 'UTC',
    maxConcurrentPolls: 1
  },
  notificationConfig: {
    enabled: true,
    templates: {
      singleUpdate: '{projectName} has been updated',
      multipleUpdates: 'You have {count} new updates in your starred projects'
    },
    groupingRules: {
      maxGroupSize: 5,
      timeWindowMinutes: 5
    }
  },
  stateConfig: {
    watermarkKey: 'lastAckedStarredPlanChangesJwtId',
    storageAdapter: new TimeSafariStorageAdapter()
  }
};

// 3. Execute the polling
const scheduleId = await DailyNotification.schedulePoll(scheduleConfig);
```

### Host App Integration Pattern

```typescript
// TimeSafari app integration
class TimeSafariPollingService {
  private pollingManager: GenericPollingManager;
  
  constructor() {
    this.pollingManager = new GenericPollingManager(jwtManager);
  }
  
  async setupStarredProjectsPolling(): Promise<string> {
    // Get user's starred projects
    const starredProjects = await this.getUserStarredProjects();
    
    // Update request body with user data
    starredProjectsRequest.body.planIds = starredProjects;
    
    // Get current watermark
    const watermark = await this.getCurrentWatermark();
    starredProjectsRequest.body.afterId = watermark;
    
    // Schedule the poll
    const scheduleId = await this.pollingManager.schedulePoll(scheduleConfig);
    
    return scheduleId;
  }
  
  async handlePollingResult(result: PollingResult<StarredProjectsResponse>): Promise<void> {
    if (result.success && result.data) {
      const changes = result.data.data;
      
      if (changes.length > 0) {
        // Generate notifications
        await this.generateNotifications(changes);
        
        // Update watermark with CAS
        const latestJwtId = changes[changes.length - 1].planSummary.jwtId;
        await this.updateWatermark(latestJwtId);
        
        // Acknowledge changes with server
        await this.acknowledgeChanges(changes.map(c => c.planSummary.jwtId));
      }
    } else if (result.error) {
      console.error('Polling failed:', result.error);
      // Handle error (retry, notify user, etc.)
    }
  }
}
```

## Integration Steps

### 1. Install Plugin and Contracts Package

Add the plugin and contracts package to your `package.json` dependencies:

```json
{
  "dependencies": {
    "@timesafari/daily-notification-plugin": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#main",
    "@timesafari/polling-contracts": "file:./packages/polling-contracts"
  }
}
```

Or install directly via npm:
```bash
npm install git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#main
npm install ./packages/polling-contracts
```

### 2. Configure Capacitor

Update `capacitor.config.ts` to include the plugin configuration:

```typescript
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'app.timesafari',
  appName: 'TimeSafari',
  webDir: 'dist', // For Capacitor web builds (not browser PWA)
  server: {
    cleartext: true
  },
  plugins: {
    // Existing TimeSafari plugins...
    App: {
      appUrlOpen: {
        handlers: [
          {
            url: 'timesafari://*',
            autoVerify: true
          }
        ]
      }
    },
    SplashScreen: {
      launchShowDuration: 3000,
      launchAutoHide: true,
      backgroundColor: '#ffffff',
      androidSplashResourceName: 'splash',
      androidScaleType: 'CENTER_CROP',
      showSpinner: false,
      androidSpinnerStyle: 'large',
      iosSpinnerStyle: 'small',
      spinnerColor: '#999999',
      splashFullScreen: true,
      splashImmersive: true
    },
    CapSQLite: {
      iosDatabaseLocation: 'Library/CapacitorDatabase',
      iosIsEncryption: false,
      iosBiometric: {
        biometricAuth: false,
        biometricTitle: 'Biometric login for TimeSafari'
      },
      androidIsEncryption: false,
      androidBiometric: {
        biometricAuth: false,
        biometricTitle: 'Biometric login for TimeSafari'
      },
      electronIsEncryption: false
    },
    // Add Daily Notification Plugin configuration with generic polling support
    DailyNotification: {
      // Plugin-specific configuration
      defaultChannel: 'timesafari_community',
      enableSound: true,
      enableVibration: true,
      enableLights: true,
      priority: 'high',
      
      // Generic Polling Support
      genericPolling: {
        enabled: true,
        schedules: [
          // Starred Projects Polling
          {
            request: {
              endpoint: '/api/v2/report/plansLastUpdatedBetween',
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'User-Agent': 'TimeSafari-DailyNotificationPlugin/1.0.0'
              },
              body: {
                planIds: [], // Populated from user settings
                afterId: undefined, // Populated from watermark
                limit: 100
              },
              responseSchema: {
                validate: (data: any) => data && Array.isArray(data.data),
                transformError: (error: any) => ({
                  code: 'VALIDATION_ERROR',
                  message: error.message,
                  retryable: false
                })
              },
              retryConfig: {
                maxAttempts: 3,
                backoffStrategy: 'exponential',
                baseDelayMs: 1000
              },
              timeoutMs: 30000
            },
            schedule: {
              cronExpression: '0 10,16 * * *', // 10 AM and 4 PM daily
              timezone: 'UTC',
              maxConcurrentPolls: 1
            },
            notificationConfig: {
              enabled: true,
              templates: {
                singleUpdate: '{projectName} has been updated',
                multipleUpdates: 'You have {count} new updates in your starred projects'
              },
              groupingRules: {
                maxGroupSize: 5,
                timeWindowMinutes: 5
              }
            },
            stateConfig: {
              watermarkKey: 'lastAckedStarredPlanChangesJwtId',
              storageAdapter: 'timesafari' // Use TimeSafari's storage
            }
          }
        ],
        maxConcurrentPolls: 3,
        globalRetryConfig: {
          maxAttempts: 3,
          backoffStrategy: 'exponential',
          baseDelayMs: 1000
        }
      },
      
      // Legacy dual scheduling configuration (for backward compatibility)
      contentFetch: {
        enabled: true,
        schedule: '0 8 * * *', // 8 AM daily - fetch community updates
        url: 'https://endorser.ch/api/v2/report/notifications/bundle',
        headers: {
          'Authorization': 'Bearer your-jwt-token',
          'Content-Type': 'application/json',
          'X-Privacy-Level': 'user-controlled'
        },
        ttlSeconds: 3600,
        timeout: 30000,
        retryAttempts: 3,
        retryDelay: 5000
      },
      userNotification: {
        enabled: true,
        schedule: '0 9 * * *',
        title: 'TimeSafari Community Update',
        body: 'New offers, projects, people, and items await your attention!',
        sound: true,
        vibration: true,
        priority: 'high'
      },
      
      // Observability configuration
      observability: {
        enableLogging: true,
        logLevel: 'info',
        enableMetrics: true,
        enableHealthChecks: true,
        telemetryConfig: {
          lowCardinalityMetrics: true,
          piiRedaction: true,
          retentionDays: 30
        }
      }
    }
  },
  // ... rest of your config
};

export default config;
```

### 3. Android Integration

#### 3.1 Update Android Settings

Modify `android/settings.gradle` to include the plugin:

```gradle
include ':app'
include ':capacitor-cordova-android-plugins'
include ':daily-notification-plugin'

project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
project(':daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android/')

apply from: 'capacitor.settings.gradle'
```

#### 3.2 Update Android App Build Configuration

Modify `android/app/build.gradle` to include the plugin dependency:

```gradle
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
    implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
    implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
    implementation project(':capacitor-android')
    implementation project(':capacitor-community-sqlite')
    implementation "androidx.biometric:biometric:1.2.0-alpha05"
    
    // Add Daily Notification Plugin
    implementation project(':daily-notification-plugin')
    
    // Required dependencies for the plugin
    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"
    
    testImplementation "junit:junit:$junitVersion"
    androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
    androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
    implementation project(':capacitor-cordova-android-plugins')
}
```

#### 3.3 Update Android Manifest

Add required permissions to `android/app/src/main/AndroidManifest.xml`:

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Existing permissions -->
    
    <!-- Daily Notification Plugin permissions -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
    <application>
        <!-- Existing application configuration -->
        
        <!-- Daily Notification Plugin receivers and services -->
        <receiver android:name="org.timesafari.dailynotification.NotifyReceiver"
                  android:enabled="true"
                  android:exported="false" />
        <receiver android:name="org.timesafari.dailynotification.BootReceiver"
                  android:enabled="true"
                  android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        
        <!-- WorkManager constraints -->
        <provider android:name="androidx.startup.InitializationProvider"
                  android:authorities="${applicationId}.androidx-startup"
                  android:exported="false"
                  tools:node="merge">
            <meta-data android:name="androidx.work.WorkManagerInitializer"
                       android:value="androidx.startup" />
        </provider>
    </application>
</manifest>
```

### 4. iOS Integration

#### 4.1 Update Podfile

Modify `ios/App/Podfile` to include the plugin:

```ruby
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'

platform :ios, '13.0'
use_frameworks!

# workaround to avoid Xcode caching of Pods that requires
# Product -> Clean Build Folder after new Cordova plugins installed
# Requires CocoaPods 1.6 or newer
install! 'cocoapods', :disable_input_output_paths => true

def capacitor_pods
  pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
  pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
  pod 'CapacitorCommunitySqlite', :path => '../../node_modules/@capacitor-community/sqlite'
  pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/@capacitor-mlkit/barcode-scanning'
  pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
  pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
  pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
  pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
  pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
  pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
  pod 'CapawesomeCapacitorFilePicker', :path => '../../node_modules/@capawesome/capacitor-file-picker'
  
  # Add Daily Notification Plugin
  pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'
end

target 'App' do
  capacitor_pods
  # Add your Pods here
end

post_install do |installer|
  assertDeploymentTarget(installer)
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
  end
end
```

#### 4.2 Update iOS Info.plist

Add required permissions to `ios/App/App/Info.plist`:

```xml
<dict>
    <!-- Existing configuration -->
    
    <!-- Daily Notification Plugin background modes -->
    <key>UIBackgroundModes</key>
    <array>
        <string>background-app-refresh</string>
        <string>background-processing</string>
        <string>background-fetch</string>
    </array>
    
    <!-- BGTaskScheduler identifiers -->
    <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>org.timesafari.dailynotification.content-fetch</string>
        <string>org.timesafari.dailynotification.notification-delivery</string>
    </array>
    
    <!-- Notification usage description -->
    <key>NSUserNotificationsUsageDescription</key>
    <string>TimeSafari needs permission to send you notifications about important updates and reminders.</string>
    
    <!-- Background processing usage description -->
    <key>NSBackgroundTasksUsageDescription</key>
    <string>TimeSafari uses background processing to fetch and deliver daily notifications.</string>
</dict>
```

#### 4.3 Enable iOS Capabilities

1. Open your project in Xcode
2. Select your app target
3. Go to "Signing & Capabilities"
4. Add the following capabilities:
   - **Background Modes**
     - Enable "Background App Refresh"
     - Enable "Background Processing"
   - **Push Notifications** (if using push notifications)

### 5. TypeScript Integration

#### 5.1 Create Plugin Service

Create `src/services/DailyNotificationService.ts`:

```typescript
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { 
  DualScheduleConfiguration, 
  ContentFetchConfig, 
  UserNotificationConfig,
  CallbackEvent 
} from '@timesafari/daily-notification-plugin';
import {
  GenericPollingRequest,
  PollingScheduleConfig,
  PollingResult,
  StarredProjectsRequest,
  StarredProjectsResponse,
  calculateBackoffDelay,
  createDefaultOutboxPressureManager
} from '@timesafari/polling-contracts';
import { logger } from '@/utils/logger';

/**
 * Service for managing daily notifications in TimeSafari
 * Supports community building through gifts, gratitude, and collaborative projects
 * Provides privacy-preserving notification delivery with user-controlled visibility
 * 
 * @author Matthew Raymer
 * @version 2.0.0
 * @since 2025
 */
export class DailyNotificationService {
  private static instance: DailyNotificationService;
  private isInitialized = false;
  private callbacks: Map<string, Function> = new Map();

  private constructor() {}

  /**
   * Get singleton instance
   */
  public static getInstance(): DailyNotificationService {
    if (!DailyNotificationService.instance) {
      DailyNotificationService.instance = new DailyNotificationService();
    }
    return DailyNotificationService.instance;
  }

  /**
   * Initialize the daily notification service
   * Must be called before using any notification features
   */
  public async initialize(): Promise<void> {
    if (this.isInitialized) {
      logger.debug('[DailyNotificationService] Already initialized');
      return;
    }

    try {
      // Request permissions
      const permissionResult = await DailyNotification.requestPermissions();
      logger.debug('[DailyNotificationService] Permission result:', permissionResult);

      if (!permissionResult.granted) {
        throw new Error('Notification permissions not granted');
      }

      // Configure the plugin for TimeSafari community features
      await DailyNotification.configure({
        dbPath: 'timesafari_community_notifications.db',
        storage: 'tiered',
        ttlSeconds: 3600,
        prefetchLeadMinutes: 30,
        maxNotificationsPerDay: 5,
        retentionDays: 30
      });

      // Register default callbacks
      await this.registerDefaultCallbacks();

      this.isInitialized = true;
      logger.debug('[DailyNotificationService] Successfully initialized');
    } catch (error) {
      logger.error('[DailyNotificationService] Initialization failed:', error);
      throw error;
    }
  }

  /**
   * Schedule a basic daily notification (backward compatible)
   * @param options Notification options
   */
  public async scheduleDailyNotification(options: {
    title: string;
    body: string;
    schedule: string; // Cron expression
    url?: string;
    actions?: Array<{ id: string; title: string }>;
  }): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.scheduleDailyNotification({
        title: options.title,
        body: options.body,
        time: this.cronToTime(options.schedule),
        url: options.url,
        sound: true,
        priority: 'high',
        retryCount: 3,
        retryInterval: 5000,
        offlineFallback: true
      });

      logger.debug('[DailyNotificationService] Daily notification scheduled:', options.title);
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to schedule daily notification:', error);
      throw error;
    }
  }

  /**
   * Schedule dual notification (content fetch + user notification)
   * @param config Dual scheduling configuration
   */
  public async scheduleDualNotification(config: DualScheduleConfiguration): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.scheduleDualNotification(config);
      logger.debug('[DailyNotificationService] Dual notification scheduled');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to schedule dual notification:', error);
      throw error;
    }
  }

  /**
   * Schedule content fetching separately
   * @param config Content fetch configuration
   */
  public async scheduleContentFetch(config: ContentFetchConfig): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.scheduleContentFetch(config);
      logger.debug('[DailyNotificationService] Content fetch scheduled');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to schedule content fetch:', error);
      throw error;
    }
  }

  /**
   * Schedule user notification separately
   * @param config User notification configuration
   */
  public async scheduleUserNotification(config: UserNotificationConfig): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.scheduleUserNotification(config);
      logger.debug('[DailyNotificationService] User notification scheduled');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to schedule user notification:', error);
      throw error;
    }
  }

  /**
   * Register a callback function
   * @param name Callback name
   * @param callback Callback function
   */
  public async registerCallback(name: string, callback: Function): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.registerCallback(name, callback);
      this.callbacks.set(name, callback);
      logger.debug('[DailyNotificationService] Callback registered:', name);
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to register callback:', error);
      throw error;
    }
  }

  /**
   * Unregister a callback function
   * @param name Callback name
   */
  public async unregisterCallback(name: string): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.unregisterCallback(name);
      this.callbacks.delete(name);
      logger.debug('[DailyNotificationService] Callback unregistered:', name);
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to unregister callback:', error);
      throw error;
    }
  }

  /**
   * Get dual schedule status
   */
  public async getDualScheduleStatus(): Promise<any> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      const status = await DailyNotification.getDualScheduleStatus();
      logger.debug('[DailyNotificationService] Status retrieved:', status);
      return status;
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to get status:', error);
      throw error;
    }
  }

  /**
   * Cancel all notifications
   */
  public async cancelAllNotifications(): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.cancelDualSchedule();
      logger.debug('[DailyNotificationService] All notifications cancelled');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to cancel notifications:', error);
      throw error;
    }
  }

  /**
   * Get battery status and optimization info
   */
  public async getBatteryStatus(): Promise<any> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      const batteryStatus = await DailyNotification.getBatteryStatus();
      logger.debug('[DailyNotificationService] Battery status:', batteryStatus);
      return batteryStatus;
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to get battery status:', error);
      throw error;
    }
  }

  /**
   * Request battery optimization exemption
   */
  public async requestBatteryOptimizationExemption(): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      await DailyNotification.requestBatteryOptimizationExemption();
      logger.debug('[DailyNotificationService] Battery optimization exemption requested');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to request battery exemption:', error);
      throw error;
    }
  }

  /**
   * Register default callbacks for TimeSafari notification types
   */
  private async registerDefaultCallbacks(): Promise<void> {
    // Offers notification callback
    await this.registerCallback('offers', async (event: CallbackEvent) => {
      try {
        await this.handleOffersNotification(event);
      } catch (error) {
        logger.error('[DailyNotificationService] Offers callback failed:', error);
      }
    });

    // Projects notification callback
    await this.registerCallback('projects', async (event: CallbackEvent) => {
      try {
        await this.handleProjectsNotification(event);
      } catch (error) {
        logger.error('[DailyNotificationService] Projects callback failed:', error);
      }
    });

    // People notification callback
    await this.registerCallback('people', async (event: CallbackEvent) => {
      try {
        await this.handlePeopleNotification(event);
      } catch (error) {
        logger.error('[DailyNotificationService] People callback failed:', error);
      }
    });

    // Items notification callback
    await this.registerCallback('items', async (event: CallbackEvent) => {
      try {
        await this.handleItemsNotification(event);
      } catch (error) {
        logger.error('[DailyNotificationService] Items callback failed:', error);
      }
    });

    // Community analytics callback
    await this.registerCallback('communityAnalytics', async (event: CallbackEvent) => {
      try {
        // Send community events to analytics service
        await fetch('https://analytics.timesafari.com/community-events', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer your-analytics-token'
          },
          body: JSON.stringify({
            event: 'community_notification',
            data: event,
            timestamp: new Date().toISOString(),
            privacyLevel: 'aggregated' // Respect privacy-preserving architecture
          })
        });
      } catch (error) {
        logger.error('[DailyNotificationService] Community analytics callback failed:', error);
      }
    });
  }

  /**
   * Process Endorser.ch notification bundle using parallel API requests
   * @param data Notification bundle data
   */
  private async processEndorserNotificationBundle(data: any): Promise<void> {
    try {
      const { userDid, starredPlanIds, lastKnownOfferId, lastKnownPlanId } = data;
      
      // Make parallel requests to Endorser.ch API endpoints
      const requests = [
        // Offers to person
        fetch(`https://endorser.ch/api/v2/report/offers?recipientId=${userDid}&afterId=${lastKnownOfferId}`, {
          headers: { 'Authorization': 'Bearer your-jwt-token' }
        }),
        
        // Offers to user's projects
        fetch(`https://endorser.ch/api/v2/report/offersToPlansOwnedByMe?afterId=${lastKnownOfferId}`, {
          headers: { 'Authorization': 'Bearer your-jwt-token' }
        }),
        
        // Changes to starred projects
        fetch('https://endorser.ch/api/v2/report/plansLastUpdatedBetween', {
          method: 'POST',
          headers: { 
            'Authorization': 'Bearer your-jwt-token',
            'Content-Type': 'application/json' 
          },
          body: JSON.stringify({
            planIds: starredPlanIds,
            afterId: lastKnownPlanId
          })
        })
      ];
      
      const [offersToPerson, offersToProjects, starredChanges] = await Promise.all(requests);
      
      const notificationData = {
        offersToPerson: await offersToPerson.json(),
        offersToProjects: await offersToProjects.json(),
        starredChanges: await starredChanges.json()
      };
      
      // Process each notification type
      await this.handleOffersNotification(notificationData.offersToPerson);
      await this.handleProjectsNotification(notificationData.starredChanges);
      
      logger.debug('[DailyNotificationService] Processed Endorser.ch notification bundle');
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to process Endorser.ch bundle:', error);
    }
  }

  /**
   * Handle offers notification events from Endorser.ch API
   * @param event Callback event
   */
  private async handleOffersNotification(event: CallbackEvent): Promise<void> {
    // Handle offers notifications: new/changed offers to me, my projects, favorited projects
    logger.debug('[DailyNotificationService] Handling offers notification:', event);
    
    if (event.data && event.data.length > 0) {
      // Process OfferSummaryArrayMaybeMoreBody format
      event.data.forEach((offer: any) => {
        logger.debug('[DailyNotificationService] Processing offer:', {
          jwtId: offer.jwtId,
          handleId: offer.handleId,
          offeredByDid: offer.offeredByDid,
          recipientDid: offer.recipientDid,
          objectDescription: offer.objectDescription
        });
      });
      
      // Check if there are more offers to fetch
      if (event.hitLimit) {
        const lastOffer = event.data[event.data.length - 1];
        logger.debug('[DailyNotificationService] More offers available, last JWT ID:', lastOffer.jwtId);
      }
    }
  }

  /**
   * Handle projects notification events from Endorser.ch API
   * @param event Callback event
   */
  private async handleProjectsNotification(event: CallbackEvent): Promise<void> {
    // Handle projects notifications: local new/changed, content of interest, favorited
    logger.debug('[DailyNotificationService] Handling projects notification:', event);
    
    if (event.data && event.data.length > 0) {
      // Process PlanSummaryAndPreviousClaimArrayMaybeMore format
      event.data.forEach((planData: any) => {
        const { plan, wrappedClaimBefore } = planData;
        logger.debug('[DailyNotificationService] Processing project change:', {
          jwtId: plan.jwtId,
          handleId: plan.handleId,
          name: plan.name,
          issuerDid: plan.issuerDid,
          hasPreviousClaim: !!wrappedClaimBefore
        });
      });
      
      // Check if there are more project changes to fetch
      if (event.hitLimit) {
        const lastPlan = event.data[event.data.length - 1];
        logger.debug('[DailyNotificationService] More project changes available, last JWT ID:', lastPlan.plan.jwtId);
      }
    }
  }

  /**
   * Handle people notification events
   * @param event Callback event
   */
  private async handlePeopleNotification(event: CallbackEvent): Promise<void> {
    // Handle people notifications: local new/changed, content of interest, favorited, contacts
    logger.debug('[DailyNotificationService] Handling people notification:', event);
    // Implementation would process people data and update local state
  }

  /**
   * Handle items notification events
   * @param event Callback event
   */
  private async handleItemsNotification(event: CallbackEvent): Promise<void> {
    // Handle items notifications: local new/changed, favorited
    logger.debug('[DailyNotificationService] Handling items notification:', event);
    // Implementation would process items data and update local state
  }

  /**
   * Update trust network with notification events
   * @param event Callback event
   */
  private async updateTrustNetwork(event: CallbackEvent): Promise<void> {
    // Implement trust network update logic here
    // This would integrate with TimeSafari's DID-based trust system
    logger.debug('[DailyNotificationService] Updating trust network:', event);
  }

  /**
   * Handle privacy-preserving notification delivery
   * @param event Callback event
   */
  private async handlePrivacyPreservingNotification(event: CallbackEvent): Promise<void> {
    // Implement privacy-preserving notification logic here
    // This would respect user-controlled visibility settings
    logger.debug('[DailyNotificationService] Handling privacy-preserving notification:', event);
  }

  /**
   * Save notification event to database
   * @param event Callback event
   */
  private async saveToDatabase(event: CallbackEvent): Promise<void> {
    // Implement your database save logic here
    logger.debug('[DailyNotificationService] Saving to database:', event);
  }

  /**
   * Convert cron expression to time string
   * @param cron Cron expression (e.g., "0 9 * * *")
   */
  private cronToTime(cron: string): string {
    const parts = cron.split(' ');
    if (parts.length >= 2) {
      const hour = parts[1].padStart(2, '0');
      const minute = parts[0].padStart(2, '0');
      return `${hour}:${minute}`;
    }
    return '09:00'; // Default to 9 AM
  }

  /**
   * Check if the service is initialized
   */
  public isServiceInitialized(): boolean {
    return this.isInitialized;
  }

  /**
   * Get service version
   */
  public getVersion(): string {
    return '2.1.0';
  }

  /**
   * Setup generic polling for starred projects
   * @param starredProjectIds Array of starred project IDs
   * @param currentWatermark Current watermark JWT ID
   */
  public async setupStarredProjectsPolling(
    starredProjectIds: string[], 
    currentWatermark?: string
  ): Promise<string> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      // Create the polling request
      const starredProjectsRequest: GenericPollingRequest<StarredProjectsRequest, StarredProjectsResponse> = {
        endpoint: '/api/v2/report/plansLastUpdatedBetween',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'User-Agent': 'TimeSafari-DailyNotificationPlugin/1.0.0',
          'Authorization': `Bearer ${await this.getJwtToken()}`
        },
        body: {
          planIds: starredProjectIds,
          afterId: currentWatermark,
          limit: 100
        },
        responseSchema: {
          validate: (data: any): data is StarredProjectsResponse => {
            return data && 
                   Array.isArray(data.data) && 
                   typeof data.hitLimit === 'boolean' &&
                   data.pagination && 
                   typeof data.pagination.hasMore === 'boolean';
          },
          transformError: (error: any) => ({
            code: 'VALIDATION_ERROR',
            message: error.message || 'Validation failed',
            retryable: false
          })
        },
        retryConfig: {
          maxAttempts: 3,
          backoffStrategy: 'exponential',
          baseDelayMs: 1000
        },
        timeoutMs: 30000
      };

      // Create the schedule configuration
      const scheduleConfig: PollingScheduleConfig<StarredProjectsRequest, StarredProjectsResponse> = {
        request: starredProjectsRequest,
        schedule: {
          cronExpression: '0 10,16 * * *', // 10 AM and 4 PM daily
          timezone: 'UTC',
          maxConcurrentPolls: 1
        },
        notificationConfig: {
          enabled: true,
          templates: {
            singleUpdate: '{projectName} has been updated',
            multipleUpdates: 'You have {count} new updates in your starred projects'
          },
          groupingRules: {
            maxGroupSize: 5,
            timeWindowMinutes: 5
          }
        },
        stateConfig: {
          watermarkKey: 'lastAckedStarredPlanChangesJwtId',
          storageAdapter: 'timesafari'
        }
      };

      // Schedule the polling
      const scheduleId = await DailyNotification.schedulePoll(scheduleConfig);
      
      logger.debug('[DailyNotificationService] Starred projects polling scheduled:', scheduleId);
      return scheduleId;
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to setup starred projects polling:', error);
      throw error;
    }
  }

  /**
   * Handle polling results
   * @param result Polling result
   */
  public async handlePollingResult(result: PollingResult<StarredProjectsResponse>): Promise<void> {
    if (!this.isInitialized) {
      throw new Error('DailyNotificationService not initialized');
    }

    try {
      if (result.success && result.data) {
        const changes = result.data.data;
        
        if (changes.length > 0) {
          // Generate notifications
          await this.generateNotifications(changes);
          
          // Update watermark with CAS
          const latestJwtId = changes[changes.length - 1].planSummary.jwtId;
          await this.updateWatermark(latestJwtId);
          
          // Acknowledge changes with server
          await this.acknowledgeChanges(changes.map(c => c.planSummary.jwtId));
          
          logger.debug('[DailyNotificationService] Processed polling result:', {
            changeCount: changes.length,
            latestJwtId
          });
        }
      } else if (result.error) {
        logger.error('[DailyNotificationService] Polling failed:', result.error);
        // Handle error (retry, notify user, etc.)
        await this.handlePollingError(result.error);
      }
    } catch (error) {
      logger.error('[DailyNotificationService] Failed to handle polling result:', error);
      throw error;
    }
  }

  /**
   * Get JWT token for authentication
   */
  private async getJwtToken(): Promise<string> {
    // Implementation would get JWT token from TimeSafari's auth system
    return 'your-jwt-token';
  }

  /**
   * Generate notifications from polling results
   */
  private async generateNotifications(changes: any[]): Promise<void> {
    // Implementation would generate notifications based on changes
    logger.debug('[DailyNotificationService] Generating notifications for changes:', changes.length);
  }

  /**
   * Update watermark with compare-and-swap
   */
  private async updateWatermark(jwtId: string): Promise<void> {
    // Implementation would update watermark using CAS
    logger.debug('[DailyNotificationService] Updating watermark:', jwtId);
  }

  /**
   * Acknowledge changes with server
   */
  private async acknowledgeChanges(jwtIds: string[]): Promise<void> {
    // Implementation would acknowledge changes with server
    logger.debug('[DailyNotificationService] Acknowledging changes:', jwtIds.length);
  }

  /**
   * Handle polling errors
   */
  private async handlePollingError(error: any): Promise<void> {
    // Implementation would handle polling errors
    logger.error('[DailyNotificationService] Handling polling error:', error);
  }
}
```

#### 5.2 Add to PlatformServiceMixin

Update `src/utils/PlatformServiceMixin.ts` to include notification methods:

```typescript
import { DailyNotificationService } from '@/services/DailyNotificationService';

// Add to the mixin object
export const PlatformServiceMixin = {
  // ... existing methods

  /**
   * Schedule a daily notification
   * @param options Notification options
   */
  async $scheduleDailyNotification(options: {
    title: string;
    body: string;
    schedule: string;
    url?: string;
    actions?: Array<{ id: string; title: string }>;
  }): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.scheduleDailyNotification(options);
  },

  /**
   * Schedule dual notification (content fetch + user notification)
   * @param config Dual scheduling configuration
   */
  async $scheduleDualNotification(config: any): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.scheduleDualNotification(config);
  },

  /**
   * Register a notification callback
   * @param name Callback name
   * @param callback Callback function
   */
  async $registerNotificationCallback(name: string, callback: Function): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.registerCallback(name, callback);
  },

  /**
   * Get notification status
   */
  async $getNotificationStatus(): Promise<any> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.getDualScheduleStatus();
  },

  /**
   * Cancel all notifications
   */
  async $cancelAllNotifications(): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.cancelAllNotifications();
  },

  /**
   * Get battery status
   */
  async $getBatteryStatus(): Promise<any> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.getBatteryStatus();
  },

  /**
   * Request battery optimization exemption
   */
  async $requestBatteryOptimizationExemption(): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.requestBatteryOptimizationExemption();
  },

  /**
   * Setup generic polling for starred projects
   * @param starredProjectIds Array of starred project IDs
   * @param currentWatermark Current watermark JWT ID
   */
  async $setupStarredProjectsPolling(starredProjectIds: string[], currentWatermark?: string): Promise<string> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.setupStarredProjectsPolling(starredProjectIds, currentWatermark);
  },

  /**
   * Handle polling results
   * @param result Polling result
   */
  async $handlePollingResult(result: any): Promise<void> {
    const notificationService = DailyNotificationService.getInstance();
    return await notificationService.handlePollingResult(result);
  },

  // ... rest of existing methods
};
```

#### 5.3 Update TypeScript Declarations

Add to the Vue module declaration in `src/utils/PlatformServiceMixin.ts`:

```typescript
declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    // ... existing methods
    
    // Daily Notification methods
    $scheduleDailyNotification(options: {
      title: string;
      body: string;
      schedule: string;
      url?: string;
      actions?: Array<{ id: string; title: string }>;
    }): Promise<void>;
    $scheduleDualNotification(config: any): Promise<void>;
    $registerNotificationCallback(name: string, callback: Function): Promise<void>;
    $getNotificationStatus(): Promise<any>;
    $cancelAllNotifications(): Promise<void>;
    $getBatteryStatus(): Promise<any>;
    $requestBatteryOptimizationExemption(): Promise<void>;
    $setupStarredProjectsPolling(starredProjectIds: string[], currentWatermark?: string): Promise<string>;
    $handlePollingResult(result: any): Promise<void>;
  }
}
```

### 6. Initialization in App

#### 6.1 Initialize in Main App Component

Update your main app component (e.g., `src/App.vue` or `src/main.ts`) to initialize the notification service:

```typescript
import { DailyNotificationService } from '@/services/DailyNotificationService';

// In your app initialization
async function initializeApp() {
  try {
    // Initialize other services first
    await initializeDatabase();
    await initializePlatformService();
    
    // Initialize daily notifications
    const notificationService = DailyNotificationService.getInstance();
    await notificationService.initialize();
    
    logger.debug('[App] All services initialized successfully');
  } catch (error) {
    logger.error('[App] Failed to initialize services:', error);
    // Handle initialization error
  }
}
```

#### 6.2 Initialize in Platform Service

Alternatively, initialize in your platform service startup:

```typescript
// In src/services/platforms/CapacitorPlatformService.ts
import { DailyNotificationService } from '@/services/DailyNotificationService';

export class CapacitorPlatformService implements PlatformService {
  // ... existing methods

  private async initializeDatabase(): Promise<void> {
    // ... existing database initialization

    // Initialize daily notifications after database is ready
    try {
      const notificationService = DailyNotificationService.getInstance();
      await notificationService.initialize();
      logger.debug('[CapacitorPlatformService] Daily notifications initialized');
    } catch (error) {
      logger.warn('[CapacitorPlatformService] Failed to initialize daily notifications:', error);
      // Don't fail the entire initialization for notification errors
    }
  }
}
```

### 7. Usage Examples

#### 7.1 Generic Polling for Starred Projects

```typescript
// In a Vue component
export default {
  data() {
    return {
      starredProjects: [],
      currentWatermark: null,
      pollingScheduleId: null
    };
  },
  
  async mounted() {
    await this.initializePolling();
  },
  
  methods: {
    async initializePolling() {
      try {
        // Get user's starred projects
        this.starredProjects = await this.getUserStarredProjects();
        
        // Get current watermark
        this.currentWatermark = await this.getCurrentWatermark();
        
        // Setup polling
        this.pollingScheduleId = await this.$setupStarredProjectsPolling(
          this.starredProjects,
          this.currentWatermark
        );
        
        this.$notify('Starred projects polling initialized successfully');
      } catch (error) {
        this.$notify('Failed to initialize polling: ' + error.message);
      }
    },
    
    async getUserStarredProjects() {
      // Implementation would get starred projects from TimeSafari's database
      return ['project-1', 'project-2', 'project-3'];
    },
    
    async getCurrentWatermark() {
      // Implementation would get current watermark from storage
      return '1704067200_abc123_12345678';
    },
    
    async handlePollingResult(result) {
      try {
        await this.$handlePollingResult(result);
        
        if (result.success && result.data && result.data.data.length > 0) {
          this.$notify(`Received ${result.data.data.length} project updates`);
        }
      } catch (error) {
        this.$notify('Failed to handle polling result: ' + error.message);
      }
    }
  }
};
```

#### 7.2 Community Update Notification

```typescript
// In a Vue component
export default {
  methods: {
    async scheduleCommunityUpdate() {
      try {
        await this.$scheduleDailyNotification({
          title: 'TimeSafari Community Update',
          body: 'New offers, projects, people, and items await your attention!',
          schedule: '0 9 * * *', // 9 AM daily
          url: 'https://timesafari.com/notifications/bundle',
          actions: [
            { id: 'view_offers', title: 'View Offers' },
            { id: 'view_projects', title: 'See Projects' },
            { id: 'view_people', title: 'Check People' },
            { id: 'view_items', title: 'Browse Items' },
            { id: 'dismiss', title: 'Dismiss' }
          ]
        });
        
        this.$notify('Community update notification scheduled successfully');
      } catch (error) {
        this.$notify('Failed to schedule community update: ' + error.message);
      }
    }
  }
};
```

#### 7.2 Community Content Fetch + Notification

```typescript
async scheduleCommunityContentFetch() {
  try {
    const config = {
      contentFetch: {
        enabled: true,
        schedule: '0 8 * * *', // Fetch community content at 8 AM
        url: 'https://endorser.ch/api/v2/report/notifications/bundle', // Single route for all notification types
        headers: {
          'Authorization': 'Bearer your-jwt-token',
          'Content-Type': 'application/json',
          'X-Privacy-Level': 'user-controlled'
        },
        ttlSeconds: 3600, // 1 hour TTL for community data
        timeout: 30000,
        retryAttempts: 3,
        retryDelay: 5000,
        callbacks: {
          onSuccess: async (data) => {
            console.log('Community notifications fetched successfully:', data);
            // Process bundled notifications using Endorser.ch API patterns
            await this.processEndorserNotificationBundle(data);
          },
          onError: async (error) => {
            console.error('Community content fetch failed:', error);
          }
        }
      },
      userNotification: {
        enabled: true,
        schedule: '0 9 * * *', // Notify at 9 AM
        title: 'TimeSafari Community Update Ready',
        body: 'New offers, projects, people, and items are available!',
        sound: true,
        vibration: true,
        priority: 'high',
        actions: [
          { id: 'view_offers', title: 'View Offers' },
          { id: 'view_projects', title: 'See Projects' },
          { id: 'view_people', title: 'Check People' },
          { id: 'view_items', title: 'Browse Items' },
          { id: 'dismiss', title: 'Dismiss' }
        ]
      },
      relationship: {
        autoLink: true,
        contentTimeout: 300000, // 5 minutes
        fallbackBehavior: 'show_default'
      }
    };

    await this.$scheduleDualNotification(config);
    this.$notify('Community content fetch scheduled successfully');
  } catch (error) {
    this.$notify('Failed to schedule community content fetch: ' + error.message);
  }
}
```

#### 7.3 Endorser.ch API Integration

```typescript
async integrateWithEndorserAPI() {
  try {
    // Register offers callback using Endorser.ch API endpoints
    await this.$registerNotificationCallback('offers', async (event) => {
      try {
        // Handle offers notifications using Endorser.ch API patterns
        const { userDid, lastKnownOfferId } = event;
        
        // Fetch offers to person
        const offersToPerson = await fetch(`https://endorser.ch/api/v2/report/offers?recipientId=${userDid}&afterId=${lastKnownOfferId}`, {
          headers: { 'Authorization': 'Bearer your-jwt-token' }
        });
        
        // Fetch offers to user's projects
        const offersToProjects = await fetch(`https://endorser.ch/api/v2/report/offersToPlansOwnedByMe?afterId=${lastKnownOfferId}`, {
          headers: { 'Authorization': 'Bearer your-jwt-token' }
        });
        
        const [offersToPersonData, offersToProjectsData] = await Promise.all([
          offersToPerson.json(),
          offersToProjects.json()
        ]);
        
        // Process OfferSummaryArrayMaybeMoreBody format
        const allOffers = [...offersToPersonData.data, ...offersToProjectsData.data];
        
        console.log('Processed offers:', allOffers.map(offer => ({
          jwtId: offer.jwtId,
          handleId: offer.handleId,
          offeredByDid: offer.offeredByDid,
          objectDescription: offer.objectDescription
        })));
        
      } catch (error) {
        console.error('Offers callback failed:', error);
      }
    });

    // Register projects callback using Endorser.ch API endpoints
    await this.$registerNotificationCallback('projects', async (event) => {
      try {
        // Handle projects notifications using Endorser.ch API patterns
        const { starredPlanIds, lastKnownPlanId } = event;
        
        // Fetch changes to starred projects
        const starredChanges = await fetch('https://endorser.ch/api/v2/report/plansLastUpdatedBetween', {
          method: 'POST',
          headers: { 
            'Authorization': 'Bearer your-jwt-token',
            'Content-Type': 'application/json' 
          },
          body: JSON.stringify({
            planIds: starredPlanIds,
            afterId: lastKnownPlanId
          })
        });
        
        const starredChangesData = await starredChanges.json();
        
        // Process PlanSummaryAndPreviousClaimArrayMaybeMore format
        console.log('Processed project changes:', starredChangesData.data.map(planData => ({
          jwtId: planData.plan.jwtId,
          handleId: planData.plan.handleId,
          name: planData.plan.name,
          issuerDid: planData.plan.issuerDid,
          hasPreviousClaim: !!planData.wrappedClaimBefore
        })));
        
      } catch (error) {
        console.error('Projects callback failed:', error);
      }
    });

    this.$notify('Endorser.ch API integration registered successfully');
  } catch (error) {
    this.$notify('Failed to register Endorser.ch API integration: ' + error.message);
  }
}
```

#### 7.4 Battery Optimization Management

```typescript
async checkBatteryOptimization() {
  try {
    const batteryStatus = await this.$getBatteryStatus();
    
    if (!batteryStatus.isOptimizationExempt) {
      // Request exemption from battery optimization
      await this.$requestBatteryOptimizationExemption();
      this.$notify('Battery optimization exemption requested');
    } else {
      this.$notify('App is already exempt from battery optimization');
    }
  } catch (error) {
    this.$notify('Failed to check battery optimization: ' + error.message);
  }
}
```

### 8. Endorser.ch API Integration Patterns

The TimeSafari Daily Notification Plugin integrates with the Endorser.ch API to fetch community activity using pagination-based filtering. The API provides several endpoints for retrieving "new" or recent activity using `afterId` and `beforeId` parameters.

#### 8.1 Core Pagination Pattern

All Endorser.ch "new" activity endpoints use the same pagination pattern:
- **`afterId`**: JWT ID of the entry after which to look (exclusive) - gets newer entries
- **`beforeId`**: JWT ID of the entry before which to look (exclusive) - gets older entries
- **Results**: Returned in reverse chronological order (newest first)
- **Response Format**: `{ data: [...], hitLimit: boolean }`

#### 8.2 Key Endpoints

**Offers to Person**
```typescript
GET /api/v2/report/offers?recipientId=${userDid}&afterId=${lastKnownOfferId}
```

**Offers to User's Projects**
```typescript
GET /api/v2/report/offersToPlansOwnedByMe?afterId=${lastKnownOfferId}
```

**Changes to Starred Projects**
```typescript
POST /api/v2/report/plansLastUpdatedBetween
{
  "planIds": ["plan-123", "plan-456"],
  "afterId": "01HSE3R9MAC0FT3P3KZ382TWV7"
}
```

#### 8.3 Parallel Requests Implementation

```typescript
async function getNewActivity(userDid, starredPlanIds, lastKnownOfferId, lastKnownPlanId) {
  const requests = [
    // Offers to person
    fetch(`https://endorser.ch/api/v2/report/offers?recipientId=${userDid}&afterId=${lastKnownOfferId}`, {
      headers: { 'Authorization': 'Bearer your-jwt-token' }
    }),
    
    // Offers to user's projects
    fetch(`https://endorser.ch/api/v2/report/offersToPlansOwnedByMe?afterId=${lastKnownOfferId}`, {
      headers: { 'Authorization': 'Bearer your-jwt-token' }
    }),
    
    // Changes to starred projects
    fetch('https://endorser.ch/api/v2/report/plansLastUpdatedBetween', {
      method: 'POST',
      headers: { 
        'Authorization': 'Bearer your-jwt-token',
        'Content-Type': 'application/json' 
      },
      body: JSON.stringify({
        planIds: starredPlanIds,
        afterId: lastKnownPlanId
      })
    })
  ];
  
  const [offersToPerson, offersToProjects, starredChanges] = await Promise.all(requests);
  
  return {
    offersToPerson: await offersToPerson.json(),
    offersToProjects: await offersToProjects.json(),
    starredChanges: await starredChanges.json()
  };
}
```

#### 8.4 Pagination Handling

```typescript
function handlePagination(response) {
  if (response.hitLimit) {
    // There may be more data - use the last item's jwtId as afterId for next request
    const lastItem = response.data[response.data.length - 1];
    return {
      hasMore: true,
      nextAfterId: lastItem.jwtId
    };
  }
  return { hasMore: false };
}
```

### 9. Build and Sync

After making all changes, run the following commands:

```bash
# Install dependencies
npm install

# Build the Capacitor app
npm run build:capacitor

# Sync with native platforms
npx cap sync

# For iOS, update pods
cd ios/App && pod install && cd ../..

# For Android, clean and rebuild
cd android && ./gradlew clean && cd ..
```

### 10. Testing

#### 10.1 Test on Android

```bash
# Build and run on Android
npm run build:android
npx cap run android
```

#### 10.2 Test on iOS

```bash
# Build and run on iOS
npm run build:ios
npx cap run ios
```

#### 10.3 Test on Electron

```bash
# Build and run on Electron
npm run build:electron
npm run electron:serve
```

### 11. Troubleshooting

#### 11.1 Common Issues

1. **Plugin not found**: Ensure the plugin is properly installed and the path is correct
2. **Permissions denied**: Check that all required permissions are added to manifests
3. **Build errors**: Clean and rebuild the project after adding the plugin
4. **TypeScript errors**: Ensure the plugin exports proper TypeScript definitions
5. **Background tasks not running**: Check battery optimization settings and background app refresh
6. **Endorser.ch API errors**: Verify JWT token authentication and endpoint availability

#### 11.2 Debug Steps

1. Check console logs for initialization errors
2. Verify plugin is loaded in `capacitor.plugins.json`
3. Test permissions manually in device settings
4. Use Electron dev tools for desktop platform testing
5. Check WorkManager logs on Android
6. Check BGTaskScheduler logs on iOS
7. Verify Endorser.ch API responses and pagination handling

#### 11.3 Platform-Specific Issues

**Android:**
- Ensure WorkManager is properly configured
- Check battery optimization settings
- Verify exact alarm permissions
- Check Room database initialization

**iOS:**
- Verify background modes are enabled
- Check BGTaskScheduler identifiers
- Ensure Core Data model is compatible
- Verify notification permissions

**Electron:**
- Ensure Electron main process is configured
- Check desktop notification permissions
- Verify SQLite/LocalStorage compatibility
- Check native notification setup

**Endorser.ch API:**
- Verify JWT token authentication
- Check pagination parameters (afterId, beforeId)
- Monitor rate limiting and hitLimit responses
- Ensure proper error handling for API failures

### 12. Security Considerations

- Ensure notification data doesn't contain sensitive personal information
- Validate all notification inputs and callback URLs
- Implement proper error handling and logging
- Respect user privacy preferences and visibility settings
- Follow platform-specific notification guidelines
- Use HTTPS for all network operations
- Implement proper authentication for callbacks
- Respect TimeSafari's privacy-preserving claims architecture
- Ensure user-controlled visibility for all notification data
- Use cryptographic verification for sensitive notification content

### 13. Performance Considerations

- Limit the number of scheduled notifications
- Clean up old notifications regularly
- Use efficient notification IDs
- Consider battery impact on mobile devices
- Implement proper caching strategies
- Use circuit breaker patterns for callbacks
- Monitor memory usage and database performance
- Implement efficient Endorser.ch API pagination handling
- Cache JWT tokens and API responses appropriately
- Monitor API rate limits and implement backoff strategies

### 14. Enterprise Integration Examples

#### 14.1 Community Analytics Integration

```typescript
// Register community analytics callback
await this.$registerNotificationCallback('communityAnalytics', async (event) => {
  try {
    // Send community events to analytics service
    await fetch('https://analytics.timesafari.com/community-events', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Privacy-Level': 'aggregated'
      },
      body: JSON.stringify({
        client_id: 'your-client-id',
        events: [{
          name: 'community_notification',
          params: {
            notification_id: event.id,
            action: event.action,
            timestamp: event.timestamp,
            community_type: event.communityType,
            privacy_level: 'aggregated'
          }
        }]
      })
    });
  } catch (error) {
    console.error('Community analytics callback failed:', error);
  }
});
```

#### 14.2 Trust Network Integration

```typescript
// Register trust network callback
await this.$registerNotificationCallback('trustNetwork', async (event) => {
  try {
    await fetch('https://api.timesafari.com/trust-network/events', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer your-trust-token',
        'Content-Type': 'application/json',
        'X-Privacy-Level': 'user-controlled'
      },
      body: JSON.stringify({
        Name: event.id,
        Action__c: event.action,
        Timestamp__c: new Date(event.timestamp).toISOString(),
        UserDid__c: event.userDid,
        TrustLevel__c: event.trustLevel,
        Data__c: JSON.stringify(event.data)
      })
    });
  } catch (error) {
    console.error('Trust network callback failed:', error);
  }
});
```

## Conclusion

This guide provides a comprehensive approach to integrating the TimeSafari Daily Notification Plugin into the TimeSafari application. The integration supports TimeSafari's core mission of fostering community building through gifts, gratitude, and collaborative projects.

The plugin offers advanced features specifically designed for community engagement:
- **Dual Scheduling**: Separate content fetch and user notification scheduling for community updates
- **TTL-at-Fire Logic**: Content validity checking at notification time for community data
- **Circuit Breaker Pattern**: Automatic failure detection and recovery for community services
- **Privacy-Preserving Architecture**: Respects TimeSafari's user-controlled visibility and DID-based identity system
- **Trust Network Integration**: Supports building and maintaining trust networks through notifications
- **Comprehensive Observability**: Structured logging and health monitoring for community features

The integration follows TimeSafari's development principles:
- **Platform Services**: Uses abstracted platform services via interfaces
- **Type Safety**: Implements strict TypeScript with type guards
- **Modern Architecture**: Follows current platform service patterns
- **Privacy-First**: Respects privacy-preserving claims architecture
- **Community-Focused**: Supports community building and trust network development

For questions or issues, refer to the plugin's documentation or contact the TimeSafari development team.

---

**Version**: 2.1.0  
**Last Updated**: 2025-10-07 04:32:12 UTC  
**Status**: Production Ready  
**Author**: Matthew Raymer
