Adds ability to list alarms with AlarmManager status in web interface.
Changes:
- Add getSchedulesWithStatus() method to DailyNotificationPlugin
- Add ScheduleWithStatus TypeScript interface with isActuallyScheduled flag
- Add alarm list UI to android-test-app with status indicators
Backend:
- getSchedulesWithStatus() returns schedules with AlarmManager verification
- Checks isAlarmScheduled() for each notify schedule with nextRunAt
- Returns isActuallyScheduled boolean flag for each schedule
TypeScript:
- New ScheduleWithStatus interface extending Schedule
- Method signature with JSDoc and usage examples
UI:
- New "📋 List Alarms" button in test app
- Color-coded alarm cards (green=scheduled, orange=not scheduled)
- Shows schedule ID, next run time, pattern, and AlarmManager status
- Useful for debugging recovery scenarios and verifying alarm state
Use case:
- Verify which alarms are in database vs actually scheduled
- Debug Phase 1/2/3 recovery scenarios
- Visual confirmation of alarm state after app launch/boot
Related:
- Enhances: android-test-app for Phase 1-3 testing
- Supports: Recovery verification and debugging
1539 lines
45 KiB
TypeScript
1539 lines
45 KiB
TypeScript
/**
|
|
* Daily Notification Plugin Definitions
|
|
*
|
|
* TypeScript definitions for the Daily Notification Plugin
|
|
* Aligned with Android implementation and test requirements
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 2.0.0
|
|
*/
|
|
|
|
// Import SPI types from content-fetcher.ts
|
|
import type {
|
|
SchedulingPolicy,
|
|
JsNotificationContentFetcher
|
|
} from './types/content-fetcher';
|
|
|
|
export interface NotificationResponse {
|
|
id: string;
|
|
title: string;
|
|
body: string;
|
|
timestamp: number;
|
|
url?: string;
|
|
}
|
|
|
|
export interface NotificationOptions {
|
|
url?: string;
|
|
time?: string;
|
|
title?: string;
|
|
body?: string;
|
|
sound?: boolean;
|
|
priority?: 'high' | 'default' | 'low' | 'min' | 'max' | 'normal';
|
|
timezone?: string;
|
|
retryCount?: number;
|
|
retryInterval?: number;
|
|
offlineFallback?: boolean;
|
|
headers?: Record<string, string>;
|
|
}
|
|
|
|
|
|
export interface NotificationSettings {
|
|
url?: string;
|
|
time?: string;
|
|
sound?: boolean;
|
|
priority?: string;
|
|
timezone?: string;
|
|
retryCount?: number;
|
|
retryInterval?: number;
|
|
offlineFallback?: boolean;
|
|
}
|
|
|
|
export interface NotificationStatus {
|
|
isEnabled?: boolean;
|
|
isScheduled?: boolean;
|
|
lastNotificationTime: number | Promise<number>;
|
|
nextNotificationTime: number | Promise<number>;
|
|
pending?: number;
|
|
settings: NotificationSettings;
|
|
error?: string;
|
|
}
|
|
|
|
export interface BatteryStatus {
|
|
level: number;
|
|
isCharging: boolean;
|
|
powerState: number;
|
|
isOptimizationExempt: boolean;
|
|
}
|
|
|
|
export interface PowerState {
|
|
powerState: number;
|
|
isOptimizationExempt: boolean;
|
|
}
|
|
|
|
export interface NotificationEvent extends Event {
|
|
detail: {
|
|
id: string;
|
|
action: string;
|
|
data?: Record<string, unknown>;
|
|
};
|
|
}
|
|
|
|
export interface PermissionStatus {
|
|
status?: string;
|
|
granted?: boolean;
|
|
notifications: PermissionState;
|
|
backgroundRefresh?: PermissionState; // iOS only
|
|
alert?: boolean;
|
|
badge?: boolean;
|
|
sound?: boolean;
|
|
lockScreen?: boolean;
|
|
carPlay?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Permission status result for checkPermissionStatus()
|
|
* Returns boolean flags for each permission type
|
|
*/
|
|
export interface PermissionStatusResult {
|
|
notificationsEnabled: boolean;
|
|
exactAlarmEnabled: boolean;
|
|
wakeLockEnabled: boolean;
|
|
allPermissionsGranted: boolean;
|
|
}
|
|
|
|
// Static Daily Reminder Interfaces
|
|
export interface DailyReminderOptions {
|
|
id: string;
|
|
title: string;
|
|
body: string;
|
|
time: string; // HH:mm format (e.g., "09:00")
|
|
sound?: boolean;
|
|
vibration?: boolean;
|
|
priority?: 'low' | 'normal' | 'high';
|
|
repeatDaily?: boolean;
|
|
timezone?: string;
|
|
}
|
|
|
|
export interface DailyReminderInfo {
|
|
id: string;
|
|
title: string;
|
|
body: string;
|
|
time: string;
|
|
sound: boolean;
|
|
vibration: boolean;
|
|
priority: 'low' | 'normal' | 'high';
|
|
repeatDaily: boolean;
|
|
timezone?: string;
|
|
isScheduled: boolean;
|
|
nextTriggerTime?: number;
|
|
createdAt: number;
|
|
lastTriggered?: number;
|
|
}
|
|
|
|
export type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' | 'provisional' | 'ephemeral' | 'unknown';
|
|
|
|
// Additional interfaces for enhanced functionality
|
|
export interface NotificationMetrics {
|
|
scheduledTime: number;
|
|
actualDeliveryTime?: number;
|
|
contentAge: number;
|
|
engagement?: 'TAPPED' | 'DISMISSED' | 'IGNORED';
|
|
failureReason?: string;
|
|
platformInfo: PlatformInfo;
|
|
}
|
|
|
|
export interface PlatformInfo {
|
|
oem?: string;
|
|
osVersion: string;
|
|
appState: string;
|
|
}
|
|
|
|
export interface FallbackContent {
|
|
title: string;
|
|
body: string;
|
|
isEmergency: boolean;
|
|
age?: string;
|
|
}
|
|
|
|
export interface CachePolicy {
|
|
maxSize: number;
|
|
evictionPolicy: 'LRU' | 'FIFO' | 'TTL';
|
|
ttl?: number;
|
|
cleanupInterval: number;
|
|
}
|
|
|
|
export interface NetworkConfig {
|
|
timeout: number;
|
|
retryAttempts: number;
|
|
retryDelay: number;
|
|
offlineFallback: boolean;
|
|
}
|
|
|
|
export interface SchedulingConfig {
|
|
exactAlarms: boolean;
|
|
adaptiveScheduling: boolean;
|
|
quietHours?: {
|
|
start: string;
|
|
end: string;
|
|
enabled: boolean;
|
|
};
|
|
timezone: string;
|
|
}
|
|
|
|
export interface ConfigureOptions {
|
|
dbPath?: string;
|
|
storage?: 'shared' | 'tiered';
|
|
ttlSeconds?: number;
|
|
prefetchLeadMinutes?: number;
|
|
maxNotificationsPerDay?: number;
|
|
retentionDays?: number;
|
|
// Phase 2: TimeSafari ActiveDid Integration Enhancement
|
|
activeDidIntegration?: {
|
|
platform: 'android' | 'ios' | 'electron';
|
|
storageType: 'plugin-managed' | 'host-managed';
|
|
jwtExpirationSeconds?: number;
|
|
apiServer?: string;
|
|
// Phase 2: Host-provided activeDid configuration
|
|
activeDid?: string; // Initial activeDid from host
|
|
hostCredentials?: {
|
|
platform?: string; // Platform identifier
|
|
accessToken?: string; // Optional access token
|
|
};
|
|
autoSync?: boolean; // Auto-sync activeDid changes
|
|
identityChangeGraceSeconds?: number; // Grace period for activeDid changes
|
|
};
|
|
}
|
|
|
|
// Dual Scheduling System Interfaces
|
|
export interface ContentFetchConfig {
|
|
enabled: boolean;
|
|
schedule: string; // Cron expression
|
|
url?: string;
|
|
headers?: Record<string, string>;
|
|
timeout?: number;
|
|
retryAttempts?: number;
|
|
retryDelay?: number;
|
|
callbacks: {
|
|
apiService?: string;
|
|
database?: string;
|
|
reporting?: string;
|
|
onSuccess?: (data: Record<string, unknown>) => Promise<void>;
|
|
onError?: (error: Error) => Promise<void>;
|
|
onComplete?: (result: ContentFetchResult) => Promise<void>;
|
|
};
|
|
cachePolicy?: CachePolicy;
|
|
networkConfig?: NetworkConfig;
|
|
// Phase 2: TimeSafari Endorser.ch API configuration
|
|
timesafariConfig?: {
|
|
activeDid: string; // Required activeDid for authentication
|
|
endpoints?: {
|
|
offersToPerson?: string;
|
|
offersToPlans?: string;
|
|
projectsLastUpdated?: string;
|
|
};
|
|
syncConfig?: {
|
|
enableParallel?: boolean; // Enable parallel API requests
|
|
maxConcurrent?: number; // Max concurrent requests
|
|
batchSize?: number; // Batch size for requests
|
|
};
|
|
credentialConfig?: {
|
|
jwtSecret?: string; // JWT secret for signing
|
|
tokenExpirationMinutes?: number; // Token expiration
|
|
refreshThresholdMinutes?: number; // Refresh threshold
|
|
};
|
|
errorPolicy?: {
|
|
maxRetries?: number;
|
|
backoffMultiplier?: number;
|
|
activeDidChangeRetries?: number; // Special retry for activeDid changes
|
|
};
|
|
};
|
|
}
|
|
|
|
export interface UserNotificationConfig {
|
|
enabled: boolean;
|
|
schedule: string; // Cron expression
|
|
title?: string;
|
|
body?: string;
|
|
sound?: boolean;
|
|
vibration?: boolean;
|
|
priority?: 'low' | 'normal' | 'high';
|
|
badge?: boolean;
|
|
actions?: NotificationAction[];
|
|
category?: string;
|
|
userInfo?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface NotificationAction {
|
|
id: string;
|
|
title: string;
|
|
icon?: string;
|
|
destructive?: boolean;
|
|
authenticationRequired?: boolean;
|
|
}
|
|
|
|
export interface DualScheduleConfiguration {
|
|
contentFetch: ContentFetchConfig;
|
|
userNotification: UserNotificationConfig;
|
|
relationship?: {
|
|
autoLink: boolean; // Automatically link content to notification
|
|
contentTimeout: number; // How long to wait for content before notification
|
|
fallbackBehavior: 'skip' | 'show_default' | 'retry';
|
|
};
|
|
}
|
|
|
|
export interface ContentFetchResult {
|
|
success: boolean;
|
|
data?: Record<string, unknown>;
|
|
timestamp: number;
|
|
contentAge: number;
|
|
error?: string;
|
|
retryCount: number;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
// ============================================================================
|
|
// DATABASE TYPE DEFINITIONS
|
|
// ============================================================================
|
|
// These types represent the plugin's internal SQLite database schema.
|
|
// The plugin owns its database, and these types are used for TypeScript
|
|
// access through Capacitor interfaces.
|
|
//
|
|
// See: docs/DATABASE_INTERFACES.md for complete documentation
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Recurring schedule pattern stored in database
|
|
* Used to restore schedules after device reboot
|
|
*/
|
|
export interface Schedule {
|
|
/** Unique schedule identifier */
|
|
id: string;
|
|
/** Schedule type: 'fetch' for content fetching, 'notify' for notifications */
|
|
kind: 'fetch' | 'notify';
|
|
/** Cron expression (e.g., "0 9 * * *" for daily at 9 AM) */
|
|
cron?: string;
|
|
/** Clock time in HH:mm format (e.g., "09:00") */
|
|
clockTime?: string;
|
|
/** Whether schedule is enabled */
|
|
enabled: boolean;
|
|
/** Timestamp of last execution (milliseconds since epoch) */
|
|
lastRunAt?: number;
|
|
/** Timestamp of next scheduled execution (milliseconds since epoch) */
|
|
nextRunAt?: number;
|
|
/** Random jitter in milliseconds for timing variation */
|
|
jitterMs: number;
|
|
/** Backoff policy ('exp' for exponential, etc.) */
|
|
backoffPolicy: string;
|
|
/** Optional JSON state for advanced scheduling */
|
|
stateJson?: string;
|
|
}
|
|
|
|
/**
|
|
* Schedule with AlarmManager status
|
|
* Extends Schedule with isActuallyScheduled flag indicating if alarm is registered in AlarmManager
|
|
*/
|
|
export interface ScheduleWithStatus extends Schedule {
|
|
/** Whether the alarm is actually scheduled in AlarmManager (Android only, for 'notify' schedules) */
|
|
isActuallyScheduled: boolean;
|
|
}
|
|
|
|
/**
|
|
* Input type for creating a new schedule
|
|
*/
|
|
export interface CreateScheduleInput {
|
|
kind: 'fetch' | 'notify';
|
|
cron?: string;
|
|
clockTime?: string;
|
|
enabled?: boolean;
|
|
jitterMs?: number;
|
|
backoffPolicy?: string;
|
|
stateJson?: string;
|
|
}
|
|
|
|
/**
|
|
* Content cache entry with TTL
|
|
* Stores prefetched content for offline-first display
|
|
*/
|
|
export interface ContentCache {
|
|
/** Unique cache identifier */
|
|
id: string;
|
|
/** Timestamp when content was fetched (milliseconds since epoch) */
|
|
fetchedAt: number;
|
|
/** Time-to-live in seconds */
|
|
ttlSeconds: number;
|
|
/** Content payload (JSON string or base64 encoded) */
|
|
payload: string;
|
|
/** Optional metadata */
|
|
meta?: string;
|
|
}
|
|
|
|
/**
|
|
* Input type for creating a content cache entry
|
|
*/
|
|
export interface CreateContentCacheInput {
|
|
id?: string; // Auto-generated if not provided
|
|
payload: string;
|
|
ttlSeconds: number;
|
|
meta?: string;
|
|
}
|
|
|
|
/**
|
|
* Plugin configuration entry
|
|
* Stores user preferences and plugin settings
|
|
*/
|
|
export interface Config {
|
|
/** Unique configuration identifier */
|
|
id: string;
|
|
/** Optional TimeSafari DID for user-specific configs */
|
|
timesafariDid?: string;
|
|
/** Configuration type (e.g., 'plugin_setting', 'user_preference') */
|
|
configType: string;
|
|
/** Configuration key */
|
|
configKey: string;
|
|
/** Configuration value (stored as string, parsed based on configDataType) */
|
|
configValue: string;
|
|
/** Data type: 'string' | 'boolean' | 'integer' | 'long' | 'float' | 'double' | 'json' */
|
|
configDataType: string;
|
|
/** Whether value is encrypted */
|
|
isEncrypted: boolean;
|
|
/** Timestamp when config was created (milliseconds since epoch) */
|
|
createdAt: number;
|
|
/** Timestamp when config was last updated (milliseconds since epoch) */
|
|
updatedAt: number;
|
|
}
|
|
|
|
/**
|
|
* Input type for creating a configuration entry
|
|
*/
|
|
export interface CreateConfigInput {
|
|
id?: string; // Auto-generated if not provided
|
|
timesafariDid?: string;
|
|
configType: string;
|
|
configKey: string;
|
|
configValue: string;
|
|
configDataType?: string; // Defaults to 'string' if not provided
|
|
isEncrypted?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Callback configuration
|
|
* Stores callback endpoint configurations for execution after events
|
|
*/
|
|
export interface Callback {
|
|
/** Unique callback identifier */
|
|
id: string;
|
|
/** Callback type: 'http' for HTTP requests, 'local' for local handlers, 'queue' for queue */
|
|
kind: 'http' | 'local' | 'queue';
|
|
/** Target URL or identifier */
|
|
target: string;
|
|
/** Optional JSON headers for HTTP callbacks */
|
|
headersJson?: string;
|
|
/** Whether callback is enabled */
|
|
enabled: boolean;
|
|
/** Timestamp when callback was created (milliseconds since epoch) */
|
|
createdAt: number;
|
|
}
|
|
|
|
/**
|
|
* Input type for creating a callback configuration
|
|
*/
|
|
export interface CreateCallbackInput {
|
|
id: string;
|
|
kind: 'http' | 'local' | 'queue';
|
|
target: string;
|
|
headersJson?: string;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Execution history entry
|
|
* Logs fetch/notify/callback execution for debugging and analytics
|
|
*/
|
|
export interface History {
|
|
/** Auto-incrementing history ID */
|
|
id: number;
|
|
/** Reference ID (content ID, schedule ID, etc.) */
|
|
refId: string;
|
|
/** Execution kind: 'fetch' | 'notify' | 'callback' | 'boot_recovery' */
|
|
kind: 'fetch' | 'notify' | 'callback' | 'boot_recovery';
|
|
/** Timestamp when execution occurred (milliseconds since epoch) */
|
|
occurredAt: number;
|
|
/** Execution duration in milliseconds */
|
|
durationMs?: number;
|
|
/** Outcome: 'success' | 'failure' | 'skipped_ttl' | 'circuit_open' */
|
|
outcome: string;
|
|
/** Optional JSON diagnostics */
|
|
diagJson?: string;
|
|
}
|
|
|
|
/**
|
|
* History statistics
|
|
*/
|
|
export interface HistoryStats {
|
|
/** Total number of history entries */
|
|
totalCount: number;
|
|
/** Count by outcome */
|
|
outcomes: Record<string, number>;
|
|
/** Count by kind */
|
|
kinds: Record<string, number>;
|
|
/** Most recent execution timestamp */
|
|
mostRecent?: number;
|
|
/** Oldest execution timestamp */
|
|
oldest?: number;
|
|
}
|
|
|
|
export interface DualScheduleStatus {
|
|
contentFetch: {
|
|
isEnabled: boolean;
|
|
isScheduled: boolean;
|
|
lastFetchTime?: number;
|
|
nextFetchTime?: number;
|
|
lastFetchResult?: ContentFetchResult;
|
|
pendingFetches: number;
|
|
};
|
|
userNotification: {
|
|
isEnabled: boolean;
|
|
isScheduled: boolean;
|
|
lastNotificationTime?: number;
|
|
nextNotificationTime?: number;
|
|
pendingNotifications: number;
|
|
};
|
|
relationship: {
|
|
isLinked: boolean;
|
|
contentAvailable: boolean;
|
|
lastLinkTime?: number;
|
|
};
|
|
overall: {
|
|
isActive: boolean;
|
|
lastActivity: number;
|
|
errorCount: number;
|
|
successRate: number;
|
|
};
|
|
}
|
|
|
|
// Enhanced DailyNotificationPlugin interface with dual scheduling
|
|
export interface DailyNotificationPlugin {
|
|
// Configuration methods
|
|
configure(options: ConfigureOptions): Promise<void>;
|
|
|
|
/**
|
|
* Configure native fetcher with API credentials (cross-platform)
|
|
*
|
|
* This method provides a cross-platform mechanism for passing API credentials
|
|
* from TypeScript/JavaScript code to native fetcher implementations. The
|
|
* configuration is passed directly without using platform-specific storage
|
|
* mechanisms, keeping the interface consistent across Android, iOS, and web.
|
|
*
|
|
* **Why this exists:**
|
|
* - Native fetchers run in background workers (WorkManager/BGTaskScheduler)
|
|
* - Background workers cannot access JavaScript variables or Capacitor bridge
|
|
* - This method provides direct injection without storage dependencies
|
|
*
|
|
* **When to call:**
|
|
* - After app startup, once API credentials are available
|
|
* - After user login/authentication, when activeDid changes
|
|
* - When API server URL changes (dev/staging/production)
|
|
*
|
|
* **Prerequisites:**
|
|
* - Native fetcher must be registered in `Application.onCreate()` (Android)
|
|
* or `AppDelegate.didFinishLaunching()` (iOS) BEFORE calling this method
|
|
* - Should be called before any background fetches occur
|
|
*
|
|
* **Thread Safety:**
|
|
* - Safe to call from any thread (main thread or background)
|
|
* - Configuration changes take effect immediately for subsequent fetches
|
|
* - Native implementations should use `volatile` fields for thread safety
|
|
เร็ *
|
|
* **Example:**
|
|
* ```typescript
|
|
* import { DailyNotification } from '@capacitor-community/daily-notification';
|
|
*
|
|
* await DailyNotification.configureNativeFetcher({
|
|
* apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
|
|
* activeDid: 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F',
|
|
* jwtSecret: 'test-jwt-secret-for-development'
|
|
* });
|
|
* ```
|
|
*
|
|
* **Error Handling:**
|
|
* - Rejects if required parameters are missing
|
|
* - Rejects if no native fetcher is registered
|
|
* - Rejects if native fetcher's `configure()` throws exception
|
|
*
|
|
* @param options Configuration options:
|
|
* - `apiBaseUrl` (required): Base URL for API server.
|
|
* - Android emulator: `"http://10.0.2.2:3000"` (maps to host localhost:3000)
|
|
* - iOS simulator: `"http://localhost:3000"`
|
|
* - Production: `"https://api.timesafari.com"`
|
|
* - `activeDid` (required): Active DID for authentication.
|
|
* Format: `"did:ethr:0x..."`. Used as JWT issuer/subject.
|
|
* - `jwtToken` (required): Pre-generated JWT token (ES256K signed).
|
|
* Generated in TypeScript using TimeSafari's `createEndorserJwtForKey()` function.
|
|
* **Note**: Token should be ES256K signed (DID-based), not HS256.
|
|
*
|
|
* **Architecture Note**: JWT tokens should be generated in TypeScript using TimeSafari's
|
|
* `createEndorserJwtForKey()` function (which uses DID-based ES256K signing), then passed
|
|
* to this method. This avoids the complexity of implementing DID-based JWT signing in Java.
|
|
*
|
|
* @throws {Error} If configuration fails (missing params, no fetcher registered, etc.)
|
|
*
|
|
* @see {@link https://github.com/timesafari/daily-notification-plugin/blob/main/docs/NATIVE_FETCHER_CONFIGURATION.md | Native Fetcher Configuration Guide}
|
|
* for complete documentation and examples
|
|
*/
|
|
configureNativeFetcher(options: {
|
|
apiBaseUrl: string;
|
|
activeDid: string;
|
|
jwtToken: string; // Pre-generated JWT token (ES256K signed) from TypeScript
|
|
}): Promise<void>;
|
|
|
|
// Rolling window management
|
|
maintainRollingWindow(): Promise<void>;
|
|
getRollingWindowStats(): Promise<{
|
|
stats: string;
|
|
maintenanceNeeded: boolean;
|
|
timeUntilNextMaintenance: number;
|
|
}>;
|
|
|
|
// Exact alarm management
|
|
getExactAlarmStatus(): Promise<{
|
|
supported: boolean;
|
|
enabled: boolean;
|
|
canSchedule: boolean;
|
|
fallbackWindow: string;
|
|
}>;
|
|
requestExactAlarmPermission(): Promise<void>;
|
|
openExactAlarmSettings(): Promise<void>;
|
|
|
|
// Reboot recovery management
|
|
getRebootRecoveryStatus(): Promise<{
|
|
inProgress: boolean;
|
|
lastRecoveryTime: number;
|
|
timeSinceLastRecovery: number;
|
|
recoveryNeeded: boolean;
|
|
}>;
|
|
|
|
// Existing methods
|
|
scheduleDailyNotification(options: NotificationOptions): Promise<void>;
|
|
|
|
/**
|
|
* Check if an alarm is scheduled for a given trigger time
|
|
* @param options Object containing triggerAtMillis (number)
|
|
* @returns Object with scheduled (boolean) and triggerAtMillis (number)
|
|
*/
|
|
isAlarmScheduled(options: { triggerAtMillis: number }): Promise<{ scheduled: boolean; triggerAtMillis: number }>;
|
|
|
|
/**
|
|
* Get the next scheduled alarm time from AlarmManager
|
|
* @returns Object with scheduled (boolean) and triggerAtMillis (number | null)
|
|
*/
|
|
getNextAlarmTime(): Promise<{ scheduled: boolean; triggerAtMillis?: number }>;
|
|
|
|
/**
|
|
* Test method: Schedule an alarm to fire in a few seconds
|
|
* Useful for verifying alarm delivery works correctly
|
|
* @param options Object containing secondsFromNow (number, default: 5)
|
|
* @returns Object with scheduled (boolean), secondsFromNow (number), and triggerAtMillis (number)
|
|
*/
|
|
testAlarm(options?: { secondsFromNow?: number }): Promise<{ scheduled: boolean; secondsFromNow: number; triggerAtMillis: number }>;
|
|
|
|
getLastNotification(): Promise<NotificationResponse | null>;
|
|
cancelAllNotifications(): Promise<void>;
|
|
getNotificationStatus(): Promise<NotificationStatus>;
|
|
updateSettings(settings: NotificationSettings): Promise<void>;
|
|
getBatteryStatus(): Promise<BatteryStatus>;
|
|
requestBatteryOptimizationExemption(): Promise<void>;
|
|
setAdaptiveScheduling(options: { enabled: boolean }): Promise<void>;
|
|
getPowerState(): Promise<PowerState>;
|
|
checkPermissions(): Promise<PermissionStatus>;
|
|
requestPermissions(): Promise<PermissionStatus>;
|
|
checkPermissionStatus(): Promise<PermissionStatusResult>;
|
|
requestNotificationPermissions(): Promise<PermissionStatus>;
|
|
isChannelEnabled(channelId?: string): Promise<{ enabled: boolean; channelId: string }>;
|
|
openChannelSettings(channelId?: string): Promise<void>;
|
|
checkStatus(): Promise<NotificationStatus>;
|
|
|
|
// New dual scheduling methods
|
|
scheduleContentFetch(config: ContentFetchConfig): Promise<void>;
|
|
scheduleUserNotification(config: UserNotificationConfig): Promise<void>;
|
|
scheduleDualNotification(config: DualScheduleConfiguration): Promise<void>;
|
|
getDualScheduleStatus(): Promise<DualScheduleStatus>;
|
|
updateDualScheduleConfig(config: DualScheduleConfiguration): Promise<void>;
|
|
cancelDualSchedule(): Promise<void>;
|
|
pauseDualSchedule(): Promise<void>;
|
|
resumeDualSchedule(): Promise<void>;
|
|
|
|
// Content management methods
|
|
getContentCache(): Promise<Record<string, unknown>>;
|
|
clearContentCache(): Promise<void>;
|
|
getContentHistory(): Promise<ContentFetchResult[]>;
|
|
|
|
// Callback management methods
|
|
registerCallback(name: string, callback: (...args: unknown[]) => void): Promise<void>;
|
|
unregisterCallback(name: string): Promise<void>;
|
|
getRegisteredCallbacks(): Promise<string[]>;
|
|
|
|
// ============================================================================
|
|
// DATABASE ACCESS METHODS
|
|
// ============================================================================
|
|
// These methods provide TypeScript/JavaScript access to the plugin's internal
|
|
// SQLite database. Since the plugin owns its database, the host app/webview
|
|
// accesses data through these Capacitor interfaces.
|
|
//
|
|
// Usage Pattern:
|
|
// import { DailyNotification } from '@capacitor-community/daily-notification';
|
|
// const schedules = await DailyNotification.getSchedules({ kind: 'notify' });
|
|
//
|
|
// See: docs/DATABASE_INTERFACES.md for complete documentation
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get all schedules matching optional filters
|
|
*
|
|
* @param options Optional filters:
|
|
* - kind: Filter by schedule type ('fetch' | 'notify')
|
|
* - enabled: Filter by enabled status (true = only enabled, false = only disabled, undefined = all)
|
|
* @returns Promise resolving to object with schedules array: { schedules: Schedule[] }
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Get all enabled notification schedules
|
|
* const result = await DailyNotification.getSchedules({
|
|
* kind: 'notify',
|
|
* enabled: true
|
|
* });
|
|
* const schedules = result.schedules;
|
|
* ```
|
|
*/
|
|
getSchedules(options?: { kind?: 'fetch' | 'notify'; enabled?: boolean }): Promise<{ schedules: Schedule[] }>;
|
|
|
|
/**
|
|
* Get all schedules with their AlarmManager status
|
|
* Returns schedules from database with isActuallyScheduled flag for each
|
|
*
|
|
* @param options Optional filters:
|
|
* - kind: Filter by schedule type ('fetch' | 'notify')
|
|
* - enabled: Filter by enabled status (true = only enabled, false = only disabled, undefined = all)
|
|
* @returns Promise resolving to object with schedules array: { schedules: ScheduleWithStatus[] }
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Get all notification schedules with AlarmManager status
|
|
* const result = await DailyNotification.getSchedulesWithStatus({
|
|
* kind: 'notify',
|
|
* enabled: true
|
|
* });
|
|
* result.schedules.forEach(schedule => {
|
|
* console.log(`${schedule.id}: ${schedule.isActuallyScheduled ? 'Scheduled' : 'Not scheduled'}`);
|
|
* });
|
|
* ```
|
|
*/
|
|
getSchedulesWithStatus(options?: { kind?: 'fetch' | 'notify'; enabled?: boolean }): Promise<{ schedules: ScheduleWithStatus[] }>;
|
|
|
|
/**
|
|
* Get a single schedule by ID
|
|
*
|
|
* @param id Schedule ID
|
|
* @returns Promise resolving to Schedule object or null if not found
|
|
*/
|
|
getSchedule(id: string): Promise<Schedule | null>;
|
|
|
|
/**
|
|
* Create a new recurring schedule
|
|
*
|
|
* @param schedule Schedule configuration
|
|
* @returns Promise resolving to created Schedule object
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const schedule = await DailyNotification.createSchedule({
|
|
* kind: 'notify',
|
|
* cron: '0 9 * * *', // Daily at 9 AM
|
|
* enabled: true
|
|
* });
|
|
* ```
|
|
*/
|
|
createSchedule(schedule: CreateScheduleInput): Promise<Schedule>;
|
|
|
|
/**
|
|
* Update an existing schedule
|
|
*
|
|
* @param id Schedule ID
|
|
* @param updates Partial schedule updates
|
|
* @returns Promise resolving to updated Schedule object
|
|
*/
|
|
updateSchedule(id: string, updates: Partial<Schedule>): Promise<Schedule>;
|
|
|
|
/**
|
|
* Delete a schedule
|
|
*
|
|
* @param id Schedule ID
|
|
* @returns Promise resolving when deletion completes
|
|
*/
|
|
deleteSchedule(id: string): Promise<void>;
|
|
|
|
/**
|
|
* Enable or disable a schedule
|
|
*
|
|
* @param id Schedule ID
|
|
* @param enabled Enable state
|
|
* @returns Promise resolving when update completes
|
|
*/
|
|
enableSchedule(id: string, enabled: boolean): Promise<void>;
|
|
|
|
/**
|
|
* Calculate next run time from a cron expression or clockTime
|
|
*
|
|
* @param schedule Cron expression (e.g., "0 9 * * *") or clockTime (e.g., "09:00")
|
|
* @returns Promise resolving to timestamp (milliseconds since epoch)
|
|
*/
|
|
calculateNextRunTime(schedule: string): Promise<number>;
|
|
|
|
/**
|
|
* Get content cache by ID or latest cache
|
|
*
|
|
* @param options Optional filters:
|
|
* - id: Specific cache ID (if not provided, returns latest)
|
|
* @returns Promise resolving to ContentCache object or null
|
|
*/
|
|
getContentCacheById(options?: { id?: string }): Promise<ContentCache | null>;
|
|
|
|
/**
|
|
* Get the latest content cache entry
|
|
*
|
|
* @returns Promise resolving to latest ContentCache object or null
|
|
*/
|
|
getLatestContentCache(): Promise<ContentCache | null>;
|
|
|
|
/**
|
|
* Get content cache history
|
|
*
|
|
* @param limit Maximum number of entries to return (default: 10)
|
|
* @returns Promise resolving to object with history array: { history: ContentCache[] }
|
|
*/
|
|
getContentCacheHistory(limit?: number): Promise<{ history: ContentCache[] }>;
|
|
|
|
/**
|
|
* Save content to cache
|
|
*
|
|
* @param content Content cache data
|
|
* @returns Promise resolving to saved ContentCache object
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await DailyNotification.saveContentCache({
|
|
* id: 'cache_123',
|
|
* payload: JSON.stringify({ title: 'Hello', body: 'World' }),
|
|
* ttlSeconds: 3600,
|
|
* meta: 'fetched_from_api'
|
|
* });
|
|
* ```
|
|
*/
|
|
saveContentCache(content: CreateContentCacheInput): Promise<ContentCache>;
|
|
|
|
/**
|
|
* Clear content cache entries
|
|
*
|
|
* @param options Optional filters:
|
|
* - olderThan: Only clear entries older than this timestamp (milliseconds)
|
|
* @returns Promise resolving when cleanup completes
|
|
*/
|
|
clearContentCacheEntries(options?: { olderThan?: number }): Promise<void>;
|
|
|
|
/**
|
|
* Get configuration value
|
|
*
|
|
* @param key Configuration key
|
|
* @param options Optional filters:
|
|
* - timesafariDid: Filter by TimeSafari DID
|
|
* @returns Promise resolving to Config object or null
|
|
*/
|
|
getConfig(key: string, options?: { timesafariDid?: string }): Promise<Config | null>;
|
|
|
|
/**
|
|
* Get all configurations matching filters
|
|
*
|
|
* @param options Optional filters:
|
|
* - timesafariDid: Filter by TimeSafari DID
|
|
* - configType: Filter by configuration type
|
|
* @returns Promise resolving to array of Config objects
|
|
*/
|
|
getAllConfigs(options?: { timesafariDid?: string; configType?: string }): Promise<{ configs: Config[] }>;
|
|
|
|
/**
|
|
* Set configuration value
|
|
*
|
|
* @param config Configuration data
|
|
* @returns Promise resolving to saved Config object
|
|
*/
|
|
setConfig(config: CreateConfigInput): Promise<Config>;
|
|
|
|
/**
|
|
* Update configuration value
|
|
*
|
|
* @param key Configuration key
|
|
* @param value New value (will be stringified based on dataType)
|
|
* @param options Optional filters:
|
|
* - timesafariDid: Filter by TimeSafari DID
|
|
* @returns Promise resolving to updated Config object
|
|
*/
|
|
updateConfig(key: string, value: string, options?: { timesafariDid?: string }): Promise<Config>;
|
|
|
|
/**
|
|
* Delete configuration
|
|
*
|
|
* @param key Configuration key
|
|
* @param options Optional filters:
|
|
* - timesafariDid: Filter by TimeSafari DID
|
|
* @returns Promise resolving when deletion completes
|
|
*/
|
|
deleteConfig(key: string, options?: { timesafariDid?: string }): Promise<void>;
|
|
|
|
/**
|
|
* Get all callbacks matching filters
|
|
*
|
|
* @param options Optional filters:
|
|
* - enabled: Filter by enabled status
|
|
* @returns Promise resolving to object with callbacks array: { callbacks: Callback[] }
|
|
*/
|
|
getCallbacks(options?: { enabled?: boolean }): Promise<{ callbacks: Callback[] }>;
|
|
|
|
/**
|
|
* Get a single callback by ID
|
|
*
|
|
* @param id Callback ID
|
|
* @returns Promise resolving to Callback object or null
|
|
*/
|
|
getCallback(id: string): Promise<Callback | null>;
|
|
|
|
/**
|
|
* Register a new callback
|
|
*
|
|
* @param callback Callback configuration
|
|
* @returns Promise resolving to created Callback object
|
|
*/
|
|
registerCallbackConfig(callback: CreateCallbackInput): Promise<Callback>;
|
|
|
|
/**
|
|
* Update an existing callback
|
|
*
|
|
* @param id Callback ID
|
|
* @param updates Partial callback updates
|
|
* @returns Promise resolving to updated Callback object
|
|
*/
|
|
updateCallback(id: string, updates: Partial<Callback>): Promise<Callback>;
|
|
|
|
/**
|
|
* Delete a callback
|
|
*
|
|
* @param id Callback ID
|
|
* @returns Promise resolving when deletion completes
|
|
*/
|
|
deleteCallback(id: string): Promise<void>;
|
|
|
|
/**
|
|
* Enable or disable a callback
|
|
*
|
|
* @param id Callback ID
|
|
* @param enabled Enable state
|
|
* @returns Promise resolving when update completes
|
|
*/
|
|
enableCallback(id: string, enabled: boolean): Promise<void>;
|
|
|
|
/**
|
|
* Get execution history
|
|
*
|
|
* @param options Optional filters:
|
|
* - since: Only return entries after this timestamp (milliseconds)
|
|
* - kind: Filter by execution kind ('fetch' | 'notify' | 'callback')
|
|
* - limit: Maximum number of entries to return (default: 50)
|
|
* @returns Promise resolving to object with history array: { history: History[] }
|
|
*/
|
|
getHistory(options?: {
|
|
since?: number;
|
|
kind?: 'fetch' | 'notify' | 'callback';
|
|
limit?: number;
|
|
}): Promise<{ history: History[] }>;
|
|
|
|
/**
|
|
* Get history statistics
|
|
*
|
|
* @returns Promise resolving to history statistics
|
|
*/
|
|
getHistoryStats(): Promise<HistoryStats>;
|
|
|
|
// Phase 1: ActiveDid Management Methods (Option A Implementation)
|
|
setActiveDidFromHost(activeDid: string): Promise<void>;
|
|
onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): void;
|
|
refreshAuthenticationForNewIdentity(activeDid: string): Promise<void>;
|
|
clearCacheForNewIdentity(): Promise<void>;
|
|
updateBackgroundTaskIdentity(activeDid: string): Promise<void>;
|
|
|
|
// Starred Plans Management Methods
|
|
/**
|
|
* Update starred plan IDs from host application
|
|
*
|
|
* This allows the TimeSafari app to dynamically update the list of starred
|
|
* project IDs when users star or unstar projects. The IDs are stored persistently
|
|
* and used for prefetch operations that query for starred project updates.
|
|
*
|
|
* @param options Contains:
|
|
* - planIds: string[] - Array of starred plan handle IDs
|
|
* @returns Promise with success status and plan count
|
|
*/
|
|
updateStarredPlans(options: { planIds: string[] }): Promise<{
|
|
success: boolean;
|
|
planIdsCount: number;
|
|
updatedAt: number;
|
|
}>;
|
|
|
|
/**
|
|
* Get current starred plan IDs
|
|
*
|
|
* Returns the currently stored starred plan IDs from SharedPreferences.
|
|
* This is useful for the host app to verify what IDs are stored.
|
|
*
|
|
* @returns Promise with current starred plan IDs
|
|
*/
|
|
getStarredPlans(): Promise<{
|
|
planIds: string[];
|
|
count: number;
|
|
updatedAt: number;
|
|
}>;
|
|
|
|
// Content Fetching Methods
|
|
/**
|
|
* Trigger an immediate standalone fetch for content updates
|
|
*
|
|
* This method allows manual triggering of content fetches independently of
|
|
* scheduled notifications. Useful for on-demand content refresh, cache warming,
|
|
* or background sync operations.
|
|
*
|
|
* @returns Promise with success status and message
|
|
*/
|
|
triggerImmediateFetch(): Promise<{
|
|
success: boolean;
|
|
message: string;
|
|
}>;
|
|
|
|
// Static Daily Reminder Methods
|
|
/**
|
|
* Schedule a simple daily reminder notification
|
|
* No network content required - just static text
|
|
*/
|
|
scheduleDailyReminder(options: DailyReminderOptions): Promise<void>;
|
|
|
|
/**
|
|
* Cancel a daily reminder notification
|
|
*/
|
|
cancelDailyReminder(reminderId: string): Promise<void>;
|
|
|
|
/**
|
|
* Get all scheduled daily reminders
|
|
*/
|
|
getScheduledReminders(): Promise<DailyReminderInfo[]>;
|
|
|
|
/**
|
|
* Update an existing daily reminder
|
|
*/
|
|
updateDailyReminder(reminderId: string, options: DailyReminderOptions): Promise<void>;
|
|
|
|
// Integration Point Refactor (PR1): SPI Registration Methods
|
|
|
|
/**
|
|
* Set JavaScript content fetcher for foreground operations
|
|
*
|
|
* NOTE: This is a stub in PR1. Full implementation coming in PR3.
|
|
* JS fetchers are ONLY used for foreground/manual refresh.
|
|
* Background workers must use native fetcher.
|
|
*
|
|
* @param fetcher JavaScript fetcher implementation
|
|
*/
|
|
setJsContentFetcher(fetcher: JsNotificationContentFetcher): void;
|
|
|
|
/**
|
|
* Enable or disable native fetcher
|
|
*
|
|
* Native fetcher is required for background workers. If disabled,
|
|
* background fetches will fail gracefully.
|
|
*
|
|
* @param enable Whether to enable native fetcher
|
|
* @returns Promise with enabled and registered status
|
|
*/
|
|
enableNativeFetcher(enable: boolean): Promise<{
|
|
enabled: boolean;
|
|
registered: boolean;
|
|
}>;
|
|
|
|
/**
|
|
* Set scheduling policy configuration
|
|
*
|
|
* Updates the scheduling policy used by the plugin for retry backoff,
|
|
* prefetch timing, deduplication, and cache TTL.
|
|
*
|
|
* @param policy Scheduling policy configuration
|
|
*/
|
|
setPolicy(policy: SchedulingPolicy): Promise<void>;
|
|
}
|
|
|
|
// Phase 1: TimeSafari Endorser.ch API Interfaces
|
|
export interface OffersResponse {
|
|
data: OfferSummaryRecord[];
|
|
hitLimit: boolean;
|
|
}
|
|
|
|
export interface OfferSummaryRecord {
|
|
jwtId: string;
|
|
handleId: string;
|
|
issuedAt: string;
|
|
offeredByDid: string;
|
|
recipientDid: string;
|
|
unit: string;
|
|
amount: number;
|
|
amountGiven: number;
|
|
amountGivenConfirmed: number;
|
|
objectDescription: string;
|
|
validThrough?: string;
|
|
fullClaim?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface OffersToPlansResponse {
|
|
data: OfferToPlanSummaryRecord[];
|
|
hitLimit: boolean;
|
|
}
|
|
|
|
export interface OfferToPlanSummaryRecord {
|
|
jwtId: string;
|
|
planId: string;
|
|
handleId: string;
|
|
issuedAt: string;
|
|
offeredByDid: string;
|
|
unit: string;
|
|
amount: number;
|
|
amountGiven: number;
|
|
objectDescription: string;
|
|
validThrough?: string;
|
|
}
|
|
|
|
export interface PlansLastUpdatedResponse {
|
|
data: PlanSummaryWithPreviousClaim[];
|
|
hitLimit: boolean;
|
|
}
|
|
|
|
export interface PlanSummaryWithPreviousClaim {
|
|
plan: PlanSummary;
|
|
wrappedClaimBefore?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface PlanSummary {
|
|
jwtId: string;
|
|
handleId: string;
|
|
name: string;
|
|
description: string;
|
|
issuerDid: string;
|
|
agentDid: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
locLat?: number;
|
|
locLon?: number;
|
|
url?: string;
|
|
}
|
|
|
|
// Phase 2: Detailed TimeSafari Notification Types
|
|
export interface TimeSafariNotificationBundle {
|
|
offersToPerson?: OffersResponse;
|
|
offersToProjects?: OffersToPlansResponse;
|
|
projectUpdates?: PlansLastUpdatedResponse;
|
|
fetchTimestamp: number;
|
|
success: boolean;
|
|
error?: string;
|
|
metadata?: {
|
|
activeDid: string;
|
|
fetchDurationMs: number;
|
|
cachedResponses: number;
|
|
networkResponses: number;
|
|
};
|
|
}
|
|
|
|
export interface TimeSafariUserConfig {
|
|
activeDid: string; // Required for all operations
|
|
lastKnownOfferId?: string;
|
|
lastKnownPlanId?: string;
|
|
starredPlanIds?: string[];
|
|
fetchOffersToPerson?: boolean;
|
|
fetchOffersToProjects?: boolean;
|
|
fetchProjectUpdates?: boolean;
|
|
notificationPreferences?: {
|
|
offers: boolean;
|
|
projects: boolean;
|
|
people: boolean;
|
|
items: boolean;
|
|
};
|
|
}
|
|
|
|
// Enhanced notification types per specification
|
|
export interface TimeSafariOfferNotification {
|
|
type: 'offer';
|
|
subtype: 'new_to_me' | 'changed_to_me' | 'new_to_projects' | 'changed_to_projects' | 'new_to_favorites' | 'changed_to_favorites';
|
|
offer: OfferSummaryRecord;
|
|
relevantProjects?: PlanSummary[];
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
export interface TimeSafariProjectNotification {
|
|
type: 'project';
|
|
subtype: 'local_and_new' | 'local_and_changed' | 'with_content_and_new' | 'favorite_and_changed';
|
|
project: PlanSummary;
|
|
changes?: {
|
|
fields: string[];
|
|
previousValues?: Record<string, unknown>;
|
|
};
|
|
relevantOffers?: OfferSummaryRecord[];
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
export interface TimeSafariPersonNotification {
|
|
type: 'person';
|
|
subtype: 'local_and_new' | 'local_and_changed' | 'with_content_and_new' | 'favorite_and_changed';
|
|
personDid: string;
|
|
changes?: {
|
|
fields: string[];
|
|
previousValues?: Record<string, unknown>;
|
|
};
|
|
relevantProjects?: PlanSummary[];
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
export interface TimeSafariItemNotification {
|
|
type: 'item';
|
|
subtype: 'local_and_new' | 'local_and_changed' | 'favorite_and_changed';
|
|
itemId: string;
|
|
changes?: {
|
|
fields: string[];
|
|
previousValues?: Record<string, unknown>;
|
|
};
|
|
relevantContext?: 'project' | 'offer' | 'person';
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
// Union type for TimeSafari notifications
|
|
export type TimeSafariNotification =
|
|
| TimeSafariOfferNotification
|
|
| TimeSafariProjectNotification
|
|
| TimeSafariPersonNotification
|
|
| TimeSafariItemNotification;
|
|
|
|
// Enhanced ActiveDid Management Events
|
|
export interface ActiveDidChangeEventEnhanced extends ActiveDidChangeEvent {
|
|
sourceComponent: string; // 'host' | 'plugin' | 'background' | 'sync'
|
|
changeReason: 'user_switch' | 'session_expired' | 'background_refresh' | 'setup';
|
|
transitionDurationMs?: number;
|
|
relatedNotifications?: TimeSafariNotification[];
|
|
}
|
|
|
|
// TimeSafari-specific Platform Configuration
|
|
export interface TimeSafariPlatformConfig {
|
|
platform: 'android' | 'ios' | 'electron';
|
|
storageType: 'plugin-managed' | 'host-managed';
|
|
syncStrategy: 'immediate' | 'batched' | 'scheduled';
|
|
permissions: {
|
|
notifications: boolean;
|
|
backgroundRefresh: boolean;
|
|
networkAccess: boolean;
|
|
};
|
|
capabilities: {
|
|
pushNotifications: boolean;
|
|
backgroundTasks: boolean;
|
|
identityManagement: boolean;
|
|
cryptoSigning: boolean;
|
|
};
|
|
}
|
|
|
|
export interface ActiveDidIntegrationConfig {
|
|
platform: 'android' | 'ios' | 'electron';
|
|
storageType: 'plugin-managed' | 'host-managed';
|
|
jwtExpirationSeconds?: number;
|
|
apiServer?: string;
|
|
}
|
|
|
|
export interface ActiveDidChangeEvent {
|
|
activeDid: string;
|
|
timestamp: number;
|
|
source: 'host' | 'plugin';
|
|
}
|
|
|
|
// MARK: - Phase 3: TimeSafari Background Coordination Interfaces
|
|
|
|
/**
|
|
* Phase 3: Extended DailyNotificationPlugin interface with TimeSafari coordination
|
|
*/
|
|
export interface EnhancedDailyNotificationPlugin extends DailyNotificationPlugin {
|
|
// Phase 1: ActiveDid Management (already extended in parent)
|
|
|
|
// Phase 3: TimeSafari Background Coordination
|
|
coordinateBackgroundTasks(): Promise<void>;
|
|
handleAppLifecycleEvent(event: AppLifecycleEvent): Promise<void>;
|
|
getCoordinationStatus(): Promise<CoordinationStatus>;
|
|
}
|
|
|
|
/**
|
|
* Phase 3: App lifecycle events for TimeSafari coordination
|
|
*/
|
|
export type AppLifecycleEvent =
|
|
| 'app_background'
|
|
| 'app_foreground'
|
|
| 'app_resumed'
|
|
| 'app_paused'
|
|
| 'app_visibility_change'
|
|
| 'app_hidden'
|
|
| 'app_visible'
|
|
| 'app_blur'
|
|
| 'app_focus';
|
|
|
|
/**
|
|
* Phase 3: Coordination status for debugging and monitoring
|
|
*/
|
|
export interface CoordinationStatus {
|
|
platform: 'android' | 'ios' | 'electron';
|
|
coordinationActive: boolean;
|
|
coordinationPaused: boolean;
|
|
autoSync?: boolean;
|
|
appBackgrounded?: boolean;
|
|
appHidden?: boolean;
|
|
visibilityState?: DocumentVisibilityState;
|
|
focused?: boolean;
|
|
lastActiveDidChange?: number;
|
|
lastCoordinationTimestamp?: number;
|
|
lastAppBackgrounded?: number;
|
|
lastAppForegrounded?: number;
|
|
lastCoordinationSuccess?: number;
|
|
lastCoordinationFailure?: number;
|
|
coordinationErrors?: string[];
|
|
activeDidTracking?: string;
|
|
}
|
|
|
|
/**
|
|
* Phase 3: PlatformServiceMixin coordination configuration
|
|
*/
|
|
export interface PlatformServiceMixinConfig {
|
|
enableAutoCoordination?: boolean;
|
|
coordinationTimeout?: number; // Max time for coordination attempts
|
|
enableLifecycleEvents?: boolean;
|
|
enableBackgroundSync?: boolean;
|
|
enableStatePersistence?: boolean;
|
|
coordinationGracePeriod?: number; // Grace period for coordination
|
|
eventHandlers?: {
|
|
[K in AppLifecycleEvent]?: () => Promise<void>;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Phase 3: WorkManager coordination data
|
|
*/
|
|
export interface WorkManagerCoordinationData {
|
|
timesafariCoordination: boolean;
|
|
coordinationTimestamp: number;
|
|
activeDidTracking: string;
|
|
platformCoordinationVersion?: number;
|
|
coordinationTimeouts?: {
|
|
maxCoordinationAge: number;
|
|
maxExecutionTime: number;
|
|
maxRetryAge: number;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Background execution constraints
|
|
*/
|
|
export interface BackgroundExecutionConstraints {
|
|
devicePowerMode?: 'normal' | 'low_power' | 'critical';
|
|
appForegroundState?: 'foreground' | 'background' | 'inactive';
|
|
activeDidStability?: 'stable' | 'changing' | 'unknown';
|
|
coordinationFreshness?: 'fresh' | 'stale' | 'expired';
|
|
networkAvailability?: 'cellular' | 'wifi' | 'offline';
|
|
batteryLevel?: 'high' | 'medium' | 'low' | 'critical';
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Coordination report
|
|
*/
|
|
export interface CoordinationReport {
|
|
success: boolean;
|
|
operation: string;
|
|
duration: number;
|
|
constraints: BackgroundExecutionConstraints;
|
|
errors?: string[];
|
|
timestamp: number;
|
|
activeDid?: string;
|
|
authUsed: boolean;
|
|
platformSpecific?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Phase 3: TimeSafari state synchronization data
|
|
*/
|
|
export interface TimeSafariSyncData {
|
|
authenticationState: {
|
|
activeDid: string;
|
|
jwtExpiration?: number;
|
|
tokenRefreshNeeded: boolean;
|
|
};
|
|
notificationState: {
|
|
lastDelivery: number;
|
|
lastDeliveryId?: string;
|
|
pendingDeliveries: string[];
|
|
};
|
|
backgroundTaskState: {
|
|
lastBackgroundExecution: number;
|
|
lastCoordinationSuccess: number;
|
|
pendingCoordinationTasks: string[];
|
|
};
|
|
activeDidHistory: {
|
|
changes: Array<{
|
|
did: string;
|
|
timestamp: number;
|
|
source: string;
|
|
}>;
|
|
pendingUpdates: string[];
|
|
};
|
|
}
|
|
|
|
// MARK: - Phase 4: TimeSafari Notification Types
|
|
|
|
/**
|
|
* Phase 4: TimeSafari-specific notification interfaces
|
|
*/
|
|
export interface TimeSafariNotificationBundle {
|
|
offersToPerson?: OffersResponse;
|
|
offersToProjects?: OffersToPlansResponse;
|
|
projectUpdates?: PlansLastUpdatedResponse;
|
|
fetchTimestamp: number;
|
|
success: boolean;
|
|
error?: string;
|
|
metadata?: {
|
|
activeDid: string;
|
|
fetchDurationMs: number;
|
|
cachedResponses: number;
|
|
networkResponses: number;
|
|
};
|
|
}
|
|
|
|
export interface TimeSafariUserConfig {
|
|
activeDid: string;
|
|
starredPlanIds?: string[];
|
|
lastKnownOfferId?: string;
|
|
lastKnownPlanId?: string;
|
|
fetchOffersToPerson?: boolean;
|
|
fetchOffersToProjects?: boolean;
|
|
fetchProjectUpdates?: boolean;
|
|
notificationPreferences?: {
|
|
offers: boolean;
|
|
projects: boolean;
|
|
people: boolean;
|
|
items: boolean;
|
|
};
|
|
}
|
|
|
|
// TimeSafari notification subtype types
|
|
export type TimeSafariOfferSubtype =
|
|
| 'new_to_me'
|
|
| 'changed_to_me'
|
|
| 'new_to_projects'
|
|
| 'changed_to_projects'
|
|
| 'new_to_favorites'
|
|
| 'changed_to_favorites';
|
|
|
|
export type TimeSafariProjectSubtype =
|
|
| 'local_and_new'
|
|
| 'local_and_changed'
|
|
| 'with_content_and_new'
|
|
| 'favorite_and_changed';
|
|
|
|
export type TimeSafariPersonSubtype =
|
|
| 'local_and_new'
|
|
| 'local_and_changed'
|
|
| 'with_content_and_new'
|
|
| 'favorite_and_changed';
|
|
|
|
export type TimeSafariItemSubtype =
|
|
| 'local_and_new'
|
|
| 'local_and_changed'
|
|
| 'favorite_and_changed';
|
|
|
|
// Individual notification interfaces
|
|
export interface TimeSafariOfferNotification {
|
|
type: 'offer';
|
|
subtype: TimeSafariOfferSubtype;
|
|
offer: OfferSummaryRecord; // Simplified to single type initially
|
|
relevantProjects?: PlanSummary[];
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface TimeSafariProjectNotification {
|
|
type: 'project';
|
|
subtype: TimeSafariProjectSubtype;
|
|
project: PlanSummary;
|
|
previousClaim?: Record<string, unknown>; // Previous claim data
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface TimeSafariPersonNotification {
|
|
type: 'person';
|
|
subtype: TimeSafariPersonSubtype;
|
|
person: {
|
|
did: string;
|
|
name?: string;
|
|
};
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
timestamp: number;
|
|
personDid: string; // Add missing property
|
|
}
|
|
|
|
export interface TimeSafariItemNotification {
|
|
type: 'item';
|
|
subtype: TimeSafariItemSubtype;
|
|
item: {
|
|
id: string;
|
|
name?: string;
|
|
type?: string;
|
|
};
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
timestamp: number;
|
|
itemId: string; // Add missing property
|
|
}
|
|
|
|
// Union type for all TimeSafari notification types
|
|
export type TimeSafariNotificationType =
|
|
| TimeSafariOfferNotification
|
|
| TimeSafariProjectNotification
|
|
| TimeSafariPersonNotification
|
|
| TimeSafariItemNotification;
|
|
|
|
// Enhanced notification interface for Phase 4
|
|
export interface EnhancedTimeSafariNotification {
|
|
type: 'offer' | 'project' | 'person' | 'item';
|
|
subtype: string;
|
|
notificationPriority: 'high' | 'medium' | 'low';
|
|
timestamp: number;
|
|
disabled: boolean;
|
|
sound: boolean;
|
|
vibration: boolean;
|
|
badge: boolean;
|
|
priority: 'low' | 'normal' | 'high';
|
|
metadata?: {
|
|
generatedAt: number;
|
|
platform: string;
|
|
userDid?: string;
|
|
preferencesVersion?: number;
|
|
fallback?: boolean;
|
|
message?: string;
|
|
};
|
|
// Type-specific properties (union approach)
|
|
offer?: OfferSummaryRecord;
|
|
project?: PlanSummary;
|
|
person?: { did: string; name?: string };
|
|
item?: { id: string; name?: string; type?: string };
|
|
relevantProjects?: PlanSummary[];
|
|
previousClaim?: Record<string, unknown>;
|
|
} |