Merge branch 'master' into ios-implementation
This commit is contained in:
619
docs/DATABASE_INTERFACES.md
Normal file
619
docs/DATABASE_INTERFACES.md
Normal file
@@ -0,0 +1,619 @@
|
||||
# Database Interfaces Documentation
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: 2025-01-21
|
||||
|
||||
## Overview
|
||||
|
||||
The Daily Notification Plugin owns its own SQLite database for storing schedules, cached content, configuration, and execution history. Since the plugin's database is isolated from the host app, the webview accesses this data through TypeScript/Capacitor interfaces.
|
||||
|
||||
This document explains how to use these interfaces from TypeScript/JavaScript code in your Capacitor app.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Host App (TypeScript) │
|
||||
│ import { DailyNotification } from '@capacitor-community/...'│
|
||||
│ │
|
||||
│ const schedules = await DailyNotification.getSchedules() │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│ Capacitor Bridge
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Plugin (Native Android/Kotlin) │
|
||||
│ │
|
||||
│ @PluginMethod │
|
||||
│ getSchedules() → Room Database → SQLite │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { DailyNotification } from '@capacitor-community/daily-notification';
|
||||
|
||||
// Get all enabled notification schedules
|
||||
const schedules = await DailyNotification.getSchedules({
|
||||
kind: 'notify',
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// Get latest cached content
|
||||
const content = await DailyNotification.getLatestContentCache();
|
||||
|
||||
// Create a new schedule
|
||||
const newSchedule = await DailyNotification.createSchedule({
|
||||
kind: 'notify',
|
||||
cron: '0 9 * * *', // Daily at 9 AM
|
||||
enabled: true
|
||||
});
|
||||
```
|
||||
|
||||
## Interface Categories
|
||||
|
||||
### 1. Schedules Management
|
||||
|
||||
Schedules represent recurring patterns for fetching content or displaying notifications. These are critical for reboot recovery - Android doesn't persist AlarmManager/WorkManager schedules, so they must be restored from the database.
|
||||
|
||||
#### Get All Schedules
|
||||
|
||||
```typescript
|
||||
// Get all schedules
|
||||
const result = await DailyNotification.getSchedules();
|
||||
const allSchedules = result.schedules;
|
||||
|
||||
// Get only enabled notification schedules
|
||||
const notifyResult = await DailyNotification.getSchedules({
|
||||
kind: 'notify',
|
||||
enabled: true
|
||||
});
|
||||
const enabledNotify = notifyResult.schedules;
|
||||
|
||||
// Get only fetch schedules
|
||||
const fetchResult = await DailyNotification.getSchedules({
|
||||
kind: 'fetch'
|
||||
});
|
||||
const fetchSchedules = fetchResult.schedules;
|
||||
```
|
||||
|
||||
**Returns**: `Promise<{ schedules: Schedule[] }>` - Note: Array is wrapped in object due to Capacitor serialization
|
||||
|
||||
#### Get Single Schedule
|
||||
|
||||
```typescript
|
||||
const schedule = await DailyNotification.getSchedule('notify_1234567890');
|
||||
if (schedule) {
|
||||
console.log(`Next run: ${new Date(schedule.nextRunAt)}`);
|
||||
}
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Schedule | null>`
|
||||
|
||||
#### Create Schedule
|
||||
|
||||
```typescript
|
||||
const schedule = await DailyNotification.createSchedule({
|
||||
kind: 'notify',
|
||||
cron: '0 9 * * *', // Daily at 9 AM (cron format)
|
||||
// OR
|
||||
clockTime: '09:00', // Simple HH:mm format
|
||||
enabled: true,
|
||||
jitterMs: 60000, // 1 minute jitter
|
||||
backoffPolicy: 'exp'
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Schedule>`
|
||||
|
||||
#### Update Schedule
|
||||
|
||||
```typescript
|
||||
// Update schedule enable state
|
||||
await DailyNotification.updateSchedule('notify_1234567890', {
|
||||
enabled: false
|
||||
});
|
||||
|
||||
// Update next run time
|
||||
await DailyNotification.updateSchedule('notify_1234567890', {
|
||||
nextRunAt: Date.now() + 86400000 // Tomorrow
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Schedule>`
|
||||
|
||||
#### Delete Schedule
|
||||
|
||||
```typescript
|
||||
await DailyNotification.deleteSchedule('notify_1234567890');
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
#### Enable/Disable Schedule
|
||||
|
||||
```typescript
|
||||
// Disable schedule
|
||||
await DailyNotification.enableSchedule('notify_1234567890', false);
|
||||
|
||||
// Enable schedule
|
||||
await DailyNotification.enableSchedule('notify_1234567890', true);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
#### Calculate Next Run Time
|
||||
|
||||
```typescript
|
||||
// Calculate next run from cron expression
|
||||
const nextRun = await DailyNotification.calculateNextRunTime('0 9 * * *');
|
||||
|
||||
// Calculate next run from clockTime
|
||||
const nextRun2 = await DailyNotification.calculateNextRunTime('09:00');
|
||||
|
||||
console.log(`Next run: ${new Date(nextRun)}`);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<number>` (timestamp in milliseconds)
|
||||
|
||||
### 2. Content Cache Management
|
||||
|
||||
Content cache stores prefetched content for offline-first display. Each entry has a TTL (time-to-live) for freshness validation.
|
||||
|
||||
#### Get Latest Content Cache
|
||||
|
||||
```typescript
|
||||
const latest = await DailyNotification.getLatestContentCache();
|
||||
if (latest) {
|
||||
const content = JSON.parse(latest.payload);
|
||||
const age = Date.now() - latest.fetchedAt;
|
||||
const isFresh = age < (latest.ttlSeconds * 1000);
|
||||
|
||||
console.log(`Content age: ${age}ms, Fresh: ${isFresh}`);
|
||||
}
|
||||
```
|
||||
|
||||
**Returns**: `Promise<ContentCache | null>`
|
||||
|
||||
#### Get Content Cache by ID
|
||||
|
||||
```typescript
|
||||
const cache = await DailyNotification.getContentCacheById({
|
||||
id: 'cache_1234567890'
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<ContentCache | null>`
|
||||
|
||||
#### Get Content Cache History
|
||||
|
||||
```typescript
|
||||
// Get last 10 cache entries
|
||||
const result = await DailyNotification.getContentCacheHistory(10);
|
||||
const history = result.history;
|
||||
|
||||
history.forEach(cache => {
|
||||
console.log(`Cache ${cache.id}: ${new Date(cache.fetchedAt)}`);
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<{ history: ContentCache[] }>`
|
||||
|
||||
#### Save Content Cache
|
||||
|
||||
```typescript
|
||||
const cached = await DailyNotification.saveContentCache({
|
||||
payload: JSON.stringify({
|
||||
title: 'Daily Update',
|
||||
body: 'Your daily content is ready!',
|
||||
data: { /* ... */ }
|
||||
}),
|
||||
ttlSeconds: 3600, // 1 hour TTL
|
||||
meta: 'fetched_from_api'
|
||||
});
|
||||
|
||||
console.log(`Cached content with ID: ${cached.id}`);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<ContentCache>`
|
||||
|
||||
#### Clear Content Cache
|
||||
|
||||
```typescript
|
||||
// Clear all cache entries
|
||||
await DailyNotification.clearContentCacheEntries();
|
||||
|
||||
// Clear entries older than 24 hours
|
||||
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
|
||||
await DailyNotification.clearContentCacheEntries({
|
||||
olderThan: oneDayAgo
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### 3. Configuration Management
|
||||
|
||||
**Note**: Configuration management methods (`getConfig`, `setConfig`, etc.) are currently not implemented in the Kotlin database schema. These will be available once the database consolidation is complete (see `android/DATABASE_CONSOLIDATION_PLAN.md`). For now, use the Java-based `DailyNotificationStorageRoom` for configuration storage if needed.
|
||||
|
||||
When implemented, these methods will store plugin settings and user preferences with optional TimeSafari DID scoping.
|
||||
|
||||
#### Get Configuration
|
||||
|
||||
```typescript
|
||||
// Get config by key
|
||||
const config = await DailyNotification.getConfig('notification_sound_enabled');
|
||||
|
||||
if (config) {
|
||||
const value = config.configDataType === 'boolean'
|
||||
? config.configValue === 'true'
|
||||
: config.configValue;
|
||||
console.log(`Sound enabled: ${value}`);
|
||||
}
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Config | null>`
|
||||
|
||||
#### Get All Configurations
|
||||
|
||||
```typescript
|
||||
// Get all configs
|
||||
const allConfigs = await DailyNotification.getAllConfigs();
|
||||
|
||||
// Get configs for specific user
|
||||
const userConfigs = await DailyNotification.getAllConfigs({
|
||||
timesafariDid: 'did:ethr:0x...'
|
||||
});
|
||||
|
||||
// Get configs by type
|
||||
const pluginConfigs = await DailyNotification.getAllConfigs({
|
||||
configType: 'plugin_setting'
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Config[]>`
|
||||
|
||||
#### Set Configuration
|
||||
|
||||
```typescript
|
||||
await DailyNotification.setConfig({
|
||||
configType: 'user_preference',
|
||||
configKey: 'notification_sound_enabled',
|
||||
configValue: 'true',
|
||||
configDataType: 'boolean',
|
||||
timesafariDid: 'did:ethr:0x...' // Optional: user-specific
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Config>`
|
||||
|
||||
#### Update Configuration
|
||||
|
||||
```typescript
|
||||
await DailyNotification.updateConfig(
|
||||
'notification_sound_enabled',
|
||||
'false',
|
||||
{ timesafariDid: 'did:ethr:0x...' }
|
||||
);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Config>`
|
||||
|
||||
#### Delete Configuration
|
||||
|
||||
```typescript
|
||||
await DailyNotification.deleteConfig('notification_sound_enabled', {
|
||||
timesafariDid: 'did:ethr:0x...'
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### 4. Callbacks Management
|
||||
|
||||
Callbacks are executed after fetch/notify events. They can be HTTP endpoints, local handlers, or queue destinations.
|
||||
|
||||
#### Get All Callbacks
|
||||
|
||||
```typescript
|
||||
// Get all callbacks
|
||||
const result = await DailyNotification.getCallbacks();
|
||||
const allCallbacks = result.callbacks;
|
||||
|
||||
// Get only enabled callbacks
|
||||
const enabledResult = await DailyNotification.getCallbacks({
|
||||
enabled: true
|
||||
});
|
||||
const enabledCallbacks = enabledResult.callbacks;
|
||||
```
|
||||
|
||||
**Returns**: `Promise<{ callbacks: Callback[] }>`
|
||||
|
||||
#### Get Single Callback
|
||||
|
||||
```typescript
|
||||
const callback = await DailyNotification.getCallback('on_notify_delivered');
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Callback | null>`
|
||||
|
||||
#### Register Callback
|
||||
|
||||
```typescript
|
||||
await DailyNotification.registerCallbackConfig({
|
||||
id: 'on_notify_delivered',
|
||||
kind: 'http',
|
||||
target: 'https://api.example.com/webhooks/notify',
|
||||
headersJson: JSON.stringify({
|
||||
'Authorization': 'Bearer token123',
|
||||
'Content-Type': 'application/json'
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Callback>`
|
||||
|
||||
#### Update Callback
|
||||
|
||||
```typescript
|
||||
await DailyNotification.updateCallback('on_notify_delivered', {
|
||||
enabled: false,
|
||||
headersJson: JSON.stringify({ 'Authorization': 'Bearer newtoken' })
|
||||
});
|
||||
```
|
||||
|
||||
**Returns**: `Promise<Callback>`
|
||||
|
||||
#### Delete Callback
|
||||
|
||||
```typescript
|
||||
await DailyNotification.deleteCallback('on_notify_delivered');
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
#### Enable/Disable Callback
|
||||
|
||||
```typescript
|
||||
await DailyNotification.enableCallback('on_notify_delivered', false);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<void>`
|
||||
|
||||
### 5. History/Analytics
|
||||
|
||||
History provides execution logs for debugging and analytics.
|
||||
|
||||
#### Get History
|
||||
|
||||
```typescript
|
||||
// Get last 50 entries
|
||||
const result = await DailyNotification.getHistory();
|
||||
const history = result.history;
|
||||
|
||||
// Get entries since yesterday
|
||||
const yesterday = Date.now() - (24 * 60 * 60 * 1000);
|
||||
const recentResult = await DailyNotification.getHistory({
|
||||
since: yesterday,
|
||||
limit: 100
|
||||
});
|
||||
const recentHistory = recentResult.history;
|
||||
|
||||
// Get only fetch executions
|
||||
const fetchResult = await DailyNotification.getHistory({
|
||||
kind: 'fetch',
|
||||
limit: 20
|
||||
});
|
||||
const fetchHistory = fetchResult.history;
|
||||
```
|
||||
|
||||
**Returns**: `Promise<{ history: History[] }>`
|
||||
|
||||
#### Get History Statistics
|
||||
|
||||
```typescript
|
||||
const stats = await DailyNotification.getHistoryStats();
|
||||
|
||||
console.log(`Total executions: ${stats.totalCount}`);
|
||||
console.log(`Success rate: ${stats.outcomes.success / stats.totalCount * 100}%`);
|
||||
console.log(`Fetch executions: ${stats.kinds.fetch}`);
|
||||
console.log(`Most recent: ${new Date(stats.mostRecent)}`);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<HistoryStats>`
|
||||
|
||||
## Type Definitions
|
||||
|
||||
### Schedule
|
||||
|
||||
```typescript
|
||||
interface Schedule {
|
||||
id: string;
|
||||
kind: 'fetch' | 'notify';
|
||||
cron?: string; // Cron expression (e.g., "0 9 * * *")
|
||||
clockTime?: string; // HH:mm format (e.g., "09:00")
|
||||
enabled: boolean;
|
||||
lastRunAt?: number; // Timestamp (ms)
|
||||
nextRunAt?: number; // Timestamp (ms)
|
||||
jitterMs: number;
|
||||
backoffPolicy: string; // 'exp', etc.
|
||||
stateJson?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### ContentCache
|
||||
|
||||
```typescript
|
||||
interface ContentCache {
|
||||
id: string;
|
||||
fetchedAt: number; // Timestamp (ms)
|
||||
ttlSeconds: number;
|
||||
payload: string; // JSON string or base64
|
||||
meta?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
```typescript
|
||||
interface Config {
|
||||
id: string;
|
||||
timesafariDid?: string;
|
||||
configType: string;
|
||||
configKey: string;
|
||||
configValue: string;
|
||||
configDataType: string; // 'string' | 'boolean' | 'integer' | etc.
|
||||
isEncrypted: boolean;
|
||||
createdAt: number; // Timestamp (ms)
|
||||
updatedAt: number; // Timestamp (ms)
|
||||
}
|
||||
```
|
||||
|
||||
### Callback
|
||||
|
||||
```typescript
|
||||
interface Callback {
|
||||
id: string;
|
||||
kind: 'http' | 'local' | 'queue';
|
||||
target: string;
|
||||
headersJson?: string;
|
||||
enabled: boolean;
|
||||
createdAt: number; // Timestamp (ms)
|
||||
}
|
||||
```
|
||||
|
||||
### History
|
||||
|
||||
```typescript
|
||||
interface History {
|
||||
id: number;
|
||||
refId: string;
|
||||
kind: 'fetch' | 'notify' | 'callback' | 'boot_recovery';
|
||||
occurredAt: number; // Timestamp (ms)
|
||||
durationMs?: number;
|
||||
outcome: string; // 'success' | 'failure' | etc.
|
||||
diagJson?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Check Schedule Status
|
||||
|
||||
```typescript
|
||||
async function checkScheduleStatus() {
|
||||
const result = await DailyNotification.getSchedules({ enabled: true });
|
||||
const schedules = result.schedules;
|
||||
|
||||
for (const schedule of schedules) {
|
||||
if (schedule.nextRunAt) {
|
||||
const nextRun = new Date(schedule.nextRunAt);
|
||||
const now = new Date();
|
||||
const timeUntil = nextRun.getTime() - now.getTime();
|
||||
|
||||
console.log(`${schedule.kind} schedule ${schedule.id}:`);
|
||||
console.log(` Next run: ${nextRun}`);
|
||||
console.log(` Time until: ${Math.round(timeUntil / 1000 / 60)} minutes`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Verify Content Freshness
|
||||
|
||||
```typescript
|
||||
async function isContentFresh(): Promise<boolean> {
|
||||
const cache = await DailyNotification.getLatestContentCache();
|
||||
|
||||
if (!cache) {
|
||||
return false; // No content available
|
||||
}
|
||||
|
||||
const age = Date.now() - cache.fetchedAt;
|
||||
const ttlMs = cache.ttlSeconds * 1000;
|
||||
|
||||
return age < ttlMs;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Update User Preferences
|
||||
|
||||
```typescript
|
||||
async function updateUserPreferences(did: string, preferences: Record<string, any>) {
|
||||
for (const [key, value] of Object.entries(preferences)) {
|
||||
await DailyNotification.setConfig({
|
||||
timesafariDid: did,
|
||||
configType: 'user_preference',
|
||||
configKey: key,
|
||||
configValue: String(value),
|
||||
configDataType: typeof value === 'boolean' ? 'boolean' : 'string'
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Monitor Execution Health
|
||||
|
||||
```typescript
|
||||
async function checkExecutionHealth() {
|
||||
const stats = await DailyNotification.getHistoryStats();
|
||||
const recentResult = await DailyNotification.getHistory({
|
||||
since: Date.now() - (24 * 60 * 60 * 1000) // Last 24 hours
|
||||
});
|
||||
const recent = recentResult.history;
|
||||
|
||||
const successCount = recent.filter(h => h.outcome === 'success').length;
|
||||
const failureCount = recent.filter(h => h.outcome === 'failure').length;
|
||||
const successRate = successCount / recent.length;
|
||||
|
||||
console.log(`24h Success Rate: ${(successRate * 100).toFixed(1)}%`);
|
||||
console.log(`Successes: ${successCount}, Failures: ${failureCount}`);
|
||||
|
||||
return successRate > 0.9; // Healthy if > 90% success rate
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All methods return Promises and can reject with errors:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const schedule = await DailyNotification.getSchedule('invalid_id');
|
||||
if (!schedule) {
|
||||
console.log('Schedule not found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error accessing database:', error);
|
||||
// Handle error - database might be unavailable, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
All database operations are executed on background threads (Kotlin `Dispatchers.IO`). Methods are safe to call from any thread in your TypeScript code.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ Implemented
|
||||
- Schedule management (CRUD operations)
|
||||
- Content cache management (CRUD operations)
|
||||
- Callback management (CRUD operations)
|
||||
- History/analytics (read operations)
|
||||
|
||||
### ⚠️ Pending Database Consolidation
|
||||
- Configuration management (Config table exists in Java DB, needs to be added to Kotlin schema)
|
||||
- See `android/DATABASE_CONSOLIDATION_PLAN.md` for full consolidation plan
|
||||
|
||||
## Return Format Notes
|
||||
|
||||
**Important**: Capacitor serializes arrays wrapped in JSObject. Methods that return arrays will return them in this format:
|
||||
- `getSchedules()` → `{ schedules: Schedule[] }`
|
||||
- `getCallbacks()` → `{ callbacks: Callback[] }`
|
||||
- `getHistory()` → `{ history: History[] }`
|
||||
- `getContentCacheHistory()` → `{ history: ContentCache[] }`
|
||||
|
||||
This is due to Capacitor's serialization mechanism. Always access the array property from the returned object.
|
||||
|
||||
157
docs/DATABASE_INTERFACES_IMPLEMENTATION.md
Normal file
157
docs/DATABASE_INTERFACES_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Database Interfaces Implementation Summary
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-01-21
|
||||
**Status**: ✅ **COMPLETE** - TypeScript interfaces and Android implementations ready
|
||||
|
||||
## Overview
|
||||
|
||||
The Daily Notification Plugin now exposes comprehensive TypeScript interfaces for accessing its internal SQLite database. Since the plugin owns its database (isolated from host apps), the webview accesses data through Capacitor bridge methods.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### ✅ TypeScript Interface Definitions (`src/definitions.ts`)
|
||||
|
||||
Added 30+ database access methods with full type definitions:
|
||||
|
||||
- **Schedule Management**: `getSchedules()`, `createSchedule()`, `updateSchedule()`, `deleteSchedule()`, `enableSchedule()`, `calculateNextRunTime()`
|
||||
- **Content Cache Management**: `getContentCacheById()`, `getLatestContentCache()`, `getContentCacheHistory()`, `saveContentCache()`, `clearContentCacheEntries()`
|
||||
- **Callback Management**: `getCallbacks()`, `getCallback()`, `registerCallbackConfig()`, `updateCallback()`, `deleteCallback()`, `enableCallback()`
|
||||
- **History/Analytics**: `getHistory()`, `getHistoryStats()`
|
||||
- **Configuration Management**: Stubs for `getConfig()`, `setConfig()`, `updateConfig()`, `deleteConfig()`, `getAllConfigs()` (pending database consolidation)
|
||||
|
||||
### ✅ Android PluginMethods (`DailyNotificationPlugin.kt`)
|
||||
|
||||
Implemented all database access methods:
|
||||
- All operations run on background threads (`Dispatchers.IO`) for thread safety
|
||||
- Proper error handling with descriptive error messages
|
||||
- JSON serialization helpers for entity-to-JSObject conversion
|
||||
- Filter support (by kind, enabled status, time ranges, etc.)
|
||||
|
||||
### ✅ Database Schema Extensions (`DatabaseSchema.kt`)
|
||||
|
||||
Extended DAOs with additional queries:
|
||||
- `ScheduleDao`: Added `getAll()`, `getByKind()`, `getByKindAndEnabled()`, `deleteById()`, `update()`
|
||||
- `ContentCacheDao`: Added `getHistory()`, `deleteAll()`
|
||||
- `CallbackDao`: Added `getAll()`, `getByEnabled()`, `getById()`, `update()`
|
||||
- `HistoryDao`: Added `getSinceByKind()`, `getRecent()`
|
||||
|
||||
### ✅ Comprehensive Documentation (`docs/DATABASE_INTERFACES.md`)
|
||||
|
||||
Created 600+ line documentation guide:
|
||||
- Complete API reference with examples
|
||||
- Common usage patterns
|
||||
- Type definitions
|
||||
- Error handling guidance
|
||||
- Return format notes (Capacitor serialization)
|
||||
- Implementation status
|
||||
|
||||
## Key Features
|
||||
|
||||
### For Developers
|
||||
|
||||
- **Type-Safe**: Full TypeScript type definitions
|
||||
- **Well-Documented**: Comprehensive JSDoc comments and examples
|
||||
- **Error Handling**: Clear error messages for debugging
|
||||
- **Thread-Safe**: All operations on background threads
|
||||
|
||||
### For AI Assistants
|
||||
|
||||
- **Clear Structure**: Methods organized by category
|
||||
- **Comprehensive Examples**: Real-world usage patterns
|
||||
- **Type Information**: Complete type definitions with JSDoc
|
||||
- **Architecture Documentation**: Clear explanation of plugin database ownership
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
import { DailyNotification } from '@capacitor-community/daily-notification';
|
||||
|
||||
// Get all enabled notification schedules
|
||||
const result = await DailyNotification.getSchedules({
|
||||
kind: 'notify',
|
||||
enabled: true
|
||||
});
|
||||
const schedules = result.schedules;
|
||||
|
||||
// Get latest cached content
|
||||
const cache = await DailyNotification.getLatestContentCache();
|
||||
if (cache) {
|
||||
const content = JSON.parse(cache.payload);
|
||||
console.log('Content:', content);
|
||||
}
|
||||
|
||||
// Create a new schedule
|
||||
const newSchedule = await DailyNotification.createSchedule({
|
||||
kind: 'notify',
|
||||
cron: '0 9 * * *', // Daily at 9 AM
|
||||
enabled: true
|
||||
});
|
||||
```
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ Fully Implemented
|
||||
- Schedule management (CRUD)
|
||||
- Content cache management (CRUD)
|
||||
- Callback management (CRUD)
|
||||
- History/analytics (read operations)
|
||||
|
||||
### ⚠️ Pending Database Consolidation
|
||||
- Configuration management (Config table exists in Java DB, needs to be added to Kotlin schema)
|
||||
- See `android/DATABASE_CONSOLIDATION_PLAN.md` for details
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### Why Plugin Owns Database
|
||||
|
||||
1. **Isolation**: Plugin data is separate from host app data
|
||||
2. **Reboot Recovery**: Schedules must persist across reboots (Android doesn't persist AlarmManager schedules)
|
||||
3. **Offline-First**: Cached content available without network
|
||||
4. **Self-Contained**: Plugin manages its own lifecycle
|
||||
|
||||
### How Webview Accesses Database
|
||||
|
||||
```
|
||||
TypeScript/Webview
|
||||
↓ Capacitor Bridge
|
||||
Android PluginMethod (@PluginMethod)
|
||||
↓ Kotlin Coroutines (Dispatchers.IO)
|
||||
Room Database (Kotlin)
|
||||
↓ SQLite
|
||||
daily_notification_plugin.db
|
||||
```
|
||||
|
||||
## Files Modified/Created
|
||||
|
||||
1. **`src/definitions.ts`** - Added database interface methods and types
|
||||
2. **`android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt`** - Implemented PluginMethods
|
||||
3. **`android/src/main/java/com/timesafari/dailynotification/DatabaseSchema.kt`** - Extended DAOs
|
||||
4. **`docs/DATABASE_INTERFACES.md`** - Complete documentation
|
||||
5. **`android/DATABASE_CONSOLIDATION_PLAN.md`** - Updated with interface requirements
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Complete Database Consolidation**: Merge Java and Kotlin databases into single unified schema
|
||||
2. **Add Config Table**: Implement Config management methods once consolidated
|
||||
3. **Testing**: Test all database methods end-to-end
|
||||
4. **iOS Implementation**: Adapt to iOS when ready
|
||||
|
||||
## Documentation References
|
||||
|
||||
- **Complete API Reference**: `docs/DATABASE_INTERFACES.md`
|
||||
- **Consolidation Plan**: `android/DATABASE_CONSOLIDATION_PLAN.md`
|
||||
- **TypeScript Definitions**: `src/definitions.ts`
|
||||
- **Database Schema**: `android/src/main/java/com/timesafari/dailynotification/DatabaseSchema.kt`
|
||||
|
||||
## For AI Assistants
|
||||
|
||||
This implementation provides:
|
||||
- **Clear Interface Contracts**: TypeScript interfaces define exact method signatures
|
||||
- **Comprehensive Examples**: Every method has usage examples
|
||||
- **Architecture Context**: Clear explanation of why database is plugin-owned
|
||||
- **Implementation Details**: Android code shows how methods work internally
|
||||
- **Error Patterns**: Consistent error handling across all methods
|
||||
|
||||
All interfaces are type-safe, well-documented, and ready for use in projects that integrate this plugin.
|
||||
|
||||
@@ -435,8 +435,36 @@ adb logcat -c
|
||||
- Check exact alarm permissions: `adb shell "dumpsys alarm | grep SCHEDULE_EXACT_ALARM"`
|
||||
- Verify alarm is scheduled: `adb shell "dumpsys alarm | grep timesafari"`
|
||||
- Check battery optimization settings
|
||||
- Use diagnostic methods to verify alarm status:
|
||||
```typescript
|
||||
// Check if alarm is scheduled
|
||||
const status = await DailyNotification.isAlarmScheduled({
|
||||
triggerAtMillis: scheduledTime
|
||||
});
|
||||
|
||||
// Get next alarm time
|
||||
const nextAlarm = await DailyNotification.getNextAlarmTime();
|
||||
|
||||
// Test alarm delivery
|
||||
await DailyNotification.testAlarm({ secondsFromNow: 10 });
|
||||
```
|
||||
|
||||
#### 4. App Crashes on Force Stop
|
||||
#### 4. BroadcastReceiver Not Invoked
|
||||
**Symptoms**: Alarm fires but notification doesn't appear, no logs from `NotifyReceiver`
|
||||
**Solutions**:
|
||||
- **CRITICAL**: Verify `NotifyReceiver` is registered in `AndroidManifest.xml`:
|
||||
```xml
|
||||
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</receiver>
|
||||
```
|
||||
- Check logs for `NotifyReceiver` registration: `adb logcat -d | grep -i "NotifyReceiver"`
|
||||
- Verify the receiver is in your app's manifest, not just the plugin's manifest
|
||||
- Check if app process is killed: `adb shell "ps | grep timesafari"`
|
||||
- Review alarm scheduling logs: `adb logcat -d | grep -E "DNP-NOTIFY|Alarm clock"`
|
||||
|
||||
#### 5. App Crashes on Force Stop
|
||||
**Symptoms**: App crashes when force-stopped
|
||||
**Solutions**:
|
||||
- This is expected behavior - force-stop kills the app
|
||||
|
||||
Reference in New Issue
Block a user