docs: add comprehensive integration guides and diagnostic method documentation

Add integration guides and update API documentation with new Android
diagnostic methods. Emphasize critical NotifyReceiver registration
requirement that was causing notification delivery failures.

Documentation Updates:
- API.md: Document isAlarmScheduled(), getNextAlarmTime(), testAlarm()
- README.md: Add Quick Integration section and Android diagnostic methods
- notification-testing-procedures.md: Add BroadcastReceiver troubleshooting

New Integration Guides:
- QUICK_INTEGRATION.md: Step-by-step guide for human developers
- AI_INTEGRATION_GUIDE.md: Machine-readable guide with verification steps
- TODO.md: Task tracking for pending improvements

Key Improvements:
- Explicit NotifyReceiver registration requirement highlighted
- Complete troubleshooting flow for BroadcastReceiver issues
- Diagnostic method examples for debugging alarm scheduling
- AI-friendly integration instructions with verification commands

Fixes notification delivery issues caused by missing NotifyReceiver
registration in host app AndroidManifest.xml files.
This commit is contained in:
Matthew Raymer
2025-11-06 10:08:18 +00:00
parent a19cb2ba61
commit 37753bb051
7 changed files with 1427 additions and 3 deletions

529
AI_INTEGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,529 @@
# Daily Notification Plugin - AI Integration Guide
**Author**: Matthew Raymer
**Version**: 2.2.0
**Last Updated**: 2025-11-06
**Purpose**: Step-by-step guide optimized for AI agents to integrate this plugin
## Overview
This guide provides **explicit, unambiguous instructions** for integrating the Daily Notification Plugin into a Capacitor application. Each step includes:
- Exact file paths
- Before/after code examples
- Verification commands
- Expected outputs
- Error handling
## Integration Checklist
```yaml
steps:
- name: "Install plugin"
file: "package.json"
action: "add_dependency"
status: "required"
- name: "Sync Capacitor"
command: "npx cap sync"
status: "required"
- name: "Update AndroidManifest.xml"
file: "android/app/src/main/AndroidManifest.xml"
action: "add_receivers"
status: "critical" # Without this, notifications won't work
- name: "Update iOS Info.plist"
file: "ios/App/App/Info.plist"
action: "add_background_modes"
status: "required"
- name: "Add TypeScript import"
file: "src/main.ts" # or equivalent entry point
action: "import_plugin"
status: "required"
```
## Step 1: Install Plugin
### Action
Add dependency to `package.json`:
```json
{
"dependencies": {
"@timesafari/daily-notification-plugin": "^1.0.1"
}
}
```
### Command
```bash
npm install @timesafari/daily-notification-plugin
```
### Verification
```bash
# Check if package is installed
npm list @timesafari/daily-notification-plugin
# Expected output:
# └── @timesafari/daily-notification-plugin@1.0.1
```
### Error Handling
- **Error**: "Package not found"
- **Solution**: Check npm registry access or use Git URL: `npm install git+https://github.com/timesafari/daily-notification-plugin.git`
## Step 2: Sync Capacitor
### Command
```bash
npx cap sync android
npx cap sync ios
```
### Verification
```bash
# Check if plugin is in capacitor.plugins.json
cat android/app/src/main/assets/capacitor.plugins.json | grep DailyNotification
# Expected output should include:
# "DailyNotification": { "class": "com.timesafari.dailynotification.DailyNotificationPlugin" }
```
### Error Handling
- **Error**: "Plugin not found in capacitor.plugins.json"
- **Solution**: Run `npx cap sync` again, ensure plugin is in `node_modules`
## Step 3: Android Configuration
### File Path
`android/app/src/main/AndroidManifest.xml`
### Action: Add Permissions
**Location**: Inside `<manifest>` tag, before `<application>` tag
**Before**:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- existing content -->
</application>
</manifest>
```
**After**:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required permissions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<!-- existing content -->
</application>
</manifest>
```
### Action: Add Receivers (CRITICAL)
**Location**: Inside `<application>` tag
**Before**:
```xml
<application>
<activity android:name=".MainActivity">
<!-- existing activity config -->
</activity>
</application>
```
**After**:
```xml
<application>
<activity android:name=".MainActivity">
<!-- existing activity config -->
</activity>
<!-- Daily Notification Plugin Receivers -->
<!-- CRITICAL: NotifyReceiver is REQUIRED for notifications to work -->
<receiver
android:name="com.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- BootReceiver for reboot recovery (optional but recommended) -->
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
```
### Verification
```bash
# Check if receivers are in manifest
grep -A 3 "NotifyReceiver" android/app/src/main/AndroidManifest.xml
# Expected output:
# <receiver
# android:name="com.timesafari.dailynotification.NotifyReceiver"
# android:enabled="true"
```
### Error Handling
- **Error**: "Notifications scheduled but not appearing"
- **Check**: Verify `NotifyReceiver` is in manifest (see verification above)
- **Solution**: Add the receiver if missing, rebuild app
- **Error**: "Permission denied"
- **Check**: Verify permissions are in manifest
- **Solution**: Add missing permissions, rebuild app
## Step 4: iOS Configuration
### File Path
`ios/App/App/Info.plist`
### Action: Add Background Modes
**Location**: Inside root `<dict>` tag
**Before**:
```xml
<dict>
<key>CFBundleName</key>
<string>App</string>
<!-- other keys -->
</dict>
```
**After**:
```xml
<dict>
<key>CFBundleName</key>
<string>App</string>
<!-- other keys -->
<key>UIBackgroundModes</key>
<array>
<string>background-app-refresh</string>
<string>background-processing</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
</array>
</dict>
```
### Action: Enable Capabilities (Manual Step)
**Note**: This requires Xcode UI interaction, cannot be automated
1. Open `ios/App/App.xcworkspace` in Xcode
2. Select app target
3. Go to "Signing & Capabilities" tab
4. Click "+ Capability"
5. Add "Background Modes"
6. Check "Background App Refresh" and "Background Processing"
### Verification
```bash
# Check if background modes are in Info.plist
grep -A 3 "UIBackgroundModes" ios/App/App/Info.plist
# Expected output:
# <key>UIBackgroundModes</key>
# <array>
# <string>background-app-refresh</string>
```
## Step 5: TypeScript Integration
### File Path
`src/main.ts` (or your app's entry point)
### Action: Import Plugin
**Before**:
```typescript
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
```
**After**:
```typescript
import { createApp } from 'vue'
import App from './App.vue'
import '@capacitor/core'
import '@timesafari/daily-notification-plugin'
const app = createApp(App)
app.mount('#app')
```
### Action: Use Plugin
**File**: Any component or service file
```typescript
import { DailyNotification } from '@timesafari/daily-notification-plugin';
// Configure plugin
await DailyNotification.configure({
storage: 'tiered',
ttlSeconds: 1800,
enableETagSupport: true
});
// Request permissions
const status = await DailyNotification.checkPermissions();
if (status.notifications !== 'granted') {
await DailyNotification.requestPermissions();
}
// Schedule notification
await DailyNotification.scheduleDailyReminder({
id: 'test',
title: 'Test Notification',
body: 'This is a test',
time: '09:00',
sound: true,
vibration: true,
priority: 'normal'
});
```
### Verification
```typescript
// Check if plugin is available
if (window.Capacitor?.Plugins?.DailyNotification) {
console.log('✅ Plugin registered');
} else {
console.error('❌ Plugin not found');
}
```
## Step 6: Build and Test
### Build Commands
```bash
# Android
cd android
./gradlew assembleDebug
# iOS
cd ios
pod install
# Then build in Xcode
```
### Test Commands
```bash
# Install on Android device
adb install app/build/outputs/apk/debug/app-debug.apk
# Check logs
adb logcat | grep -E "DNP-|NotifyReceiver|DailyNotification"
```
### Expected Log Output (Success)
```
DNP-PLUGIN: DailyNotification plugin initialized
DNP-NOTIFY: Alarm clock scheduled (setAlarmClock): triggerAt=...
DNP-NOTIFY: Notification receiver triggered: triggerTime=...
```
### Error Log Patterns
```
# Missing NotifyReceiver
# No logs from "Notification receiver triggered"
# Missing permissions
# Error: "Permission denied" or "SCHEDULE_EXACT_ALARM not granted"
# Plugin not registered
# Error: "Cannot read property 'DailyNotification' of undefined"
```
## Complete Integration Example
### File Structure
```
my-capacitor-app/
├── package.json # Step 1: Add dependency
├── src/
│ └── main.ts # Step 5: Import plugin
├── android/
│ └── app/
│ └── src/
│ └── main/
│ └── AndroidManifest.xml # Step 3: Add receivers
└── ios/
└── App/
└── App/
└── Info.plist # Step 4: Add background modes
```
### Complete Code Example
**`src/services/notification-service.ts`**:
```typescript
import { DailyNotification } from '@timesafari/daily-notification-plugin';
export class NotificationService {
async initialize() {
// Configure plugin
await DailyNotification.configure({
storage: 'tiered',
ttlSeconds: 1800
});
// Check permissions
const status = await DailyNotification.checkPermissions();
if (status.notifications !== 'granted') {
await DailyNotification.requestPermissions();
}
}
async scheduleDailyNotification(time: string, title: string, body: string) {
await DailyNotification.scheduleDailyReminder({
id: `daily_${Date.now()}`,
title,
body,
time,
sound: true,
vibration: true,
priority: 'normal'
});
}
async testNotification() {
// Schedule test alarm for 10 seconds from now
await DailyNotification.testAlarm({ secondsFromNow: 10 });
}
}
```
## Verification Checklist
Run these checks to verify integration:
```bash
# 1. Plugin installed
npm list @timesafari/daily-notification-plugin
# 2. Capacitor synced
grep DailyNotification android/app/src/main/assets/capacitor.plugins.json
# 3. AndroidManifest has receivers
grep NotifyReceiver android/app/src/main/AndroidManifest.xml
# 4. iOS Info.plist has background modes
grep UIBackgroundModes ios/App/App/Info.plist
# 5. TypeScript can import
# (Check for no TypeScript errors in your IDE)
```
## Troubleshooting Decision Tree
```
Notifications not appearing?
├─ Is NotifyReceiver in AndroidManifest.xml?
│ ├─ No → Add receiver, rebuild
│ └─ Yes → Continue
├─ Are permissions granted?
│ ├─ No → Request permissions
│ └─ Yes → Continue
├─ Is alarm scheduled?
│ ├─ Check: await DailyNotification.isAlarmScheduled({...})
│ ├─ No → Schedule alarm
│ └─ Yes → Continue
└─ Check logs: adb logcat | grep DNP-
├─ No logs → Plugin not initialized
└─ Logs present → Check for errors
```
## AI Agent Instructions
For AI agents integrating this plugin:
1. **Parse this guide sequentially** - Each step builds on the previous
2. **Verify each step** - Use verification commands before proceeding
3. **Handle errors explicitly** - Check error handling section for each step
4. **Test after integration** - Use test commands to verify functionality
5. **Check logs** - Use ADB logs to diagnose issues
### Key Files to Modify
```yaml
files:
- path: "package.json"
action: "add_dependency"
key: "dependencies.@timesafari/daily-notification-plugin"
- path: "android/app/src/main/AndroidManifest.xml"
action: "add_xml_elements"
elements:
- type: "uses-permission"
name: "android.permission.POST_NOTIFICATIONS"
- type: "receiver"
name: "com.timesafari.dailynotification.NotifyReceiver"
attributes:
android:enabled: "true"
android:exported: "false"
- path: "ios/App/App/Info.plist"
action: "add_dict_keys"
keys:
- "UIBackgroundModes"
- "BGTaskSchedulerPermittedIdentifiers"
- path: "src/main.ts" # or entry point
action: "add_import"
import: "@timesafari/daily-notification-plugin"
```
## Success Criteria
Integration is successful when:
1. ✅ Plugin installs without errors
2.`capacitor.plugins.json` contains DailyNotification entry
3. ✅ AndroidManifest.xml contains NotifyReceiver
4. ✅ iOS Info.plist contains background modes
5. ✅ TypeScript imports work without errors
6.`window.Capacitor.Plugins.DailyNotification` is available
7. ✅ Test alarm fires successfully (use `testAlarm()`)
## Next Steps
After successful integration:
- Read [API.md](./API.md) for complete API reference
- Check [README.md](./README.md) for advanced usage
- Review [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for testing

56
API.md
View File

@@ -2,7 +2,7 @@
**Author**: Matthew Raymer
**Version**: 2.2.0
**Last Updated**: 2025-10-08 06:02:45 UTC
**Last Updated**: 2025-11-06 09:51:00 UTC
## Overview
@@ -74,6 +74,60 @@ Open exact alarm settings in system preferences.
Get reboot recovery status and statistics.
##### `isAlarmScheduled(options: { triggerAtMillis: number }): Promise<{ scheduled: boolean; triggerAtMillis: number }>`
Check if an alarm is scheduled for a specific trigger time. Useful for debugging and verification.
**Parameters:**
- `options.triggerAtMillis`: `number` - The trigger time in milliseconds (Unix timestamp)
**Returns:**
- `scheduled`: `boolean` - Whether the alarm is currently scheduled
- `triggerAtMillis`: `number` - The trigger time that was checked
**Example:**
```typescript
const result = await DailyNotification.isAlarmScheduled({
triggerAtMillis: 1762421400000
});
console.log(`Alarm scheduled: ${result.scheduled}`);
```
##### `getNextAlarmTime(): Promise<{ scheduled: boolean; triggerAtMillis?: number }>`
Get the next scheduled alarm time from AlarmManager. Requires Android 5.0+ (API 21+).
**Returns:**
- `scheduled`: `boolean` - Whether any alarm is scheduled
- `triggerAtMillis`: `number | undefined` - The next alarm trigger time (if scheduled)
**Example:**
```typescript
const result = await DailyNotification.getNextAlarmTime();
if (result.scheduled) {
const nextAlarm = new Date(result.triggerAtMillis);
console.log(`Next alarm: ${nextAlarm.toLocaleString()}`);
}
```
##### `testAlarm(options?: { secondsFromNow?: number }): Promise<{ scheduled: boolean; secondsFromNow: number; triggerAtMillis: number }>`
Schedule a test alarm that fires in a few seconds. Useful for verifying alarm delivery works correctly.
**Parameters:**
- `options.secondsFromNow`: `number` (optional) - Seconds from now to fire the alarm (default: 5)
**Returns:**
- `scheduled`: `boolean` - Whether the alarm was scheduled successfully
- `secondsFromNow`: `number` - The delay used
- `triggerAtMillis`: `number` - The trigger time in milliseconds
**Example:**
```typescript
const result = await DailyNotification.testAlarm({ secondsFromNow: 10 });
console.log(`Test alarm scheduled for ${result.secondsFromNow} seconds`);
```
### Management Methods
#### `maintainRollingWindow(): Promise<void>`

260
QUICK_INTEGRATION.md Normal file
View File

@@ -0,0 +1,260 @@
# Daily Notification Plugin - Quick Integration Guide
**Author**: Matthew Raymer
**Version**: 2.2.0
**Last Updated**: 2025-11-06
## Overview
This guide provides a **quick, step-by-step** process for integrating the Daily Notification Plugin into any Capacitor application. For detailed documentation, see [README.md](./README.md) and [API.md](./API.md).
**For AI Agents**: See [AI_INTEGRATION_GUIDE.md](./AI_INTEGRATION_GUIDE.md) for explicit, machine-readable integration instructions with verification steps and error handling.
## Prerequisites
- Capacitor 6.0+ project
- Android Studio (for Android development)
- Xcode 14+ (for iOS development)
- Node.js 18+
## Step 1: Install the Plugin
```bash
npm install @timesafari/daily-notification-plugin
```
Or install from Git:
```bash
npm install git+https://github.com/timesafari/daily-notification-plugin.git
```
## Step 2: Sync Capacitor
```bash
npx cap sync android
npx cap sync ios
```
## Step 3: Android Configuration
### 3.1 Update AndroidManifest.xml
**⚠️ CRITICAL**: You **must** add the `NotifyReceiver` registration to your app's `AndroidManifest.xml`. Without it, alarms will fire but notifications won't be displayed.
Add to `android/app/src/main/AndroidManifest.xml`:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required permissions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<!-- ... your existing application components ... -->
<!-- Daily Notification Plugin Receivers -->
<!-- REQUIRED: NotifyReceiver for AlarmManager-based notifications -->
<receiver
android:name="com.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- BootReceiver for reboot recovery (optional but recommended) -->
<receiver
android:name="com.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
```
### 3.2 Update build.gradle (if needed)
The plugin should work with standard Capacitor setup. If you encounter dependency issues, ensure these are in `android/app/build.gradle`:
```gradle
dependencies {
// ... your existing dependencies ...
// Plugin dependencies (usually auto-added by Capacitor sync)
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"
}
```
## Step 4: iOS Configuration
### 4.1 Update Info.plist
Add to `ios/App/App/Info.plist`:
```xml
<key>UIBackgroundModes</key>
<array>
<string>background-app-refresh</string>
<string>background-processing</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.content-fetch</string>
<string>com.timesafari.dailynotification.notification-delivery</string>
</array>
```
### 4.2 Enable Capabilities
In Xcode:
1. Select your app target
2. Go to "Signing & Capabilities"
3. Enable "Background Modes"
4. Check "Background App Refresh" and "Background Processing"
## Step 5: Use the Plugin
### Basic Usage
```typescript
import { DailyNotification } from '@timesafari/daily-notification-plugin';
// Configure the plugin
await DailyNotification.configure({
storage: 'tiered',
ttlSeconds: 1800,
enableETagSupport: true
});
// Schedule a daily notification
await DailyNotification.scheduleDailyNotification({
title: 'Daily Update',
body: 'Your daily content is ready',
schedule: '0 9 * * *' // 9 AM daily (cron format)
});
```
### Request Permissions
```typescript
// Check permissions
const status = await DailyNotification.checkPermissions();
console.log('Notification permission:', status.notifications);
// Request permissions
if (status.notifications !== 'granted') {
await DailyNotification.requestPermissions();
}
```
### Schedule a Simple Reminder
```typescript
// Schedule a static daily reminder (no network required)
await DailyNotification.scheduleDailyReminder({
id: 'morning_checkin',
title: 'Good Morning!',
body: 'Time to check your updates',
time: '09:00', // HH:mm format
sound: true,
vibration: true,
priority: 'normal'
});
```
### Diagnostic Methods (Android)
```typescript
// Check if an alarm is scheduled
const result = await DailyNotification.isAlarmScheduled({
triggerAtMillis: scheduledTime
});
console.log('Alarm scheduled:', result.scheduled);
// Get next alarm time
const nextAlarm = await DailyNotification.getNextAlarmTime();
if (nextAlarm.scheduled) {
console.log('Next alarm:', new Date(nextAlarm.triggerAtMillis));
}
// Test alarm delivery (schedules alarm for 10 seconds from now)
await DailyNotification.testAlarm({ secondsFromNow: 10 });
```
## Step 6: Verify Installation
### Check Plugin Registration
```typescript
// Verify plugin is available
if (window.Capacitor?.Plugins?.DailyNotification) {
console.log('✅ Plugin is registered');
} else {
console.error('❌ Plugin not found');
}
```
### Test Notification
```typescript
// Schedule a test notification for 10 seconds from now
await DailyNotification.testAlarm({ secondsFromNow: 10 });
// Or schedule a regular notification
await DailyNotification.scheduleDailyReminder({
id: 'test',
title: 'Test Notification',
body: 'This is a test',
time: new Date(Date.now() + 60000).toTimeString().slice(0, 5) // 1 minute from now
});
```
## Troubleshooting
### Notifications Not Appearing
1. **Check NotifyReceiver Registration**: Verify `NotifyReceiver` is in your `AndroidManifest.xml` (see Step 3.1)
2. **Check Permissions**: Ensure notification permissions are granted
3. **Check Logs**: Use ADB to check logs:
```bash
adb logcat | grep -E "DNP-|NotifyReceiver|Notification"
```
4. **Use Diagnostic Methods**: Use `isAlarmScheduled()` and `getNextAlarmTime()` to verify alarms
### Common Issues
#### Android: "Alarm fires but notification doesn't appear"
- **Solution**: Ensure `NotifyReceiver` is registered in your app's `AndroidManifest.xml` (not just the plugin's manifest)
#### Android: "Permission denied" errors
- **Solution**: Request `POST_NOTIFICATIONS` and `SCHEDULE_EXACT_ALARM` permissions
#### iOS: Background tasks not running
- **Solution**: Ensure Background Modes are enabled in Xcode capabilities
#### Plugin not found
- **Solution**: Run `npx cap sync` and rebuild the app
## Next Steps
- Read the [API Reference](./API.md) for complete method documentation
- Check [README.md](./README.md) for advanced usage examples
- Review [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for testing guidance
## Support
For issues or questions:
- Check the troubleshooting section above
- Review the [API documentation](./API.md)
- Check [docs/notification-testing-procedures.md](./docs/notification-testing-procedures.md) for debugging steps

View File

@@ -98,6 +98,19 @@ npm install git+https://github.com/timesafari/daily-notification-plugin.git
The plugin follows the standard Capacitor Android structure - no additional path configuration needed!
## Quick Integration
**New to the plugin?** Start with the [Quick Integration Guide](./QUICK_INTEGRATION.md) for step-by-step setup instructions.
The quick guide covers:
- Installation and setup
- AndroidManifest.xml configuration (⚠️ **Critical**: NotifyReceiver registration)
- iOS configuration
- Basic usage examples
- Troubleshooting common issues
**For AI Agents**: See [AI Integration Guide](./AI_INTEGRATION_GUIDE.md) for explicit, machine-readable instructions with verification steps, error handling, and decision trees.
## Quick Start
### Basic Usage
@@ -312,6 +325,42 @@ const status = await DailyNotification.getDualScheduleStatus();
// }
```
### Android Diagnostic Methods
#### `isAlarmScheduled(options)`
Check if an alarm is scheduled for a specific trigger time. Useful for debugging and verification.
```typescript
const result = await DailyNotification.isAlarmScheduled({
triggerAtMillis: 1762421400000 // Unix timestamp in milliseconds
});
console.log(`Alarm scheduled: ${result.scheduled}`);
```
#### `getNextAlarmTime()`
Get the next scheduled alarm time from AlarmManager. Requires Android 5.0+ (API 21+).
```typescript
const result = await DailyNotification.getNextAlarmTime();
if (result.scheduled) {
const nextAlarm = new Date(result.triggerAtMillis);
console.log(`Next alarm: ${nextAlarm.toLocaleString()}`);
}
```
#### `testAlarm(options?)`
Schedule a test alarm that fires in a few seconds. Useful for verifying alarm delivery works correctly.
```typescript
// Schedule test alarm for 10 seconds from now
const result = await DailyNotification.testAlarm({ secondsFromNow: 10 });
console.log(`Test alarm scheduled for ${result.secondsFromNow} seconds`);
console.log(`Will fire at: ${new Date(result.triggerAtMillis).toLocaleString()}`);
```
## Capacitor Compatibility Matrix
| Plugin Version | Capacitor Version | Status | Notes |
@@ -480,6 +529,8 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
#### AndroidManifest.xml
**⚠️ CRITICAL**: The `NotifyReceiver` registration is **required** for alarm-based notifications to work. Without it, alarms will fire but notifications won't be displayed.
```xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
@@ -487,9 +538,13 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- NotifyReceiver for AlarmManager-based notifications -->
<!-- REQUIRED: Without this, alarms fire but notifications won't display -->
<receiver android:name="com.timesafari.dailynotification.NotifyReceiver"
android:enabled="true"
android:exported="false" />
android:exported="false">
</receiver>
<receiver android:name="com.timesafari.dailynotification.BootReceiver"
android:enabled="true"
android:exported="false">
@@ -499,6 +554,8 @@ await DailyNotification.updateDailyReminder('morning_checkin', {
</receiver>
```
**Note**: The `NotifyReceiver` must be registered in your app's `AndroidManifest.xml`, not just in the plugin's manifest. If notifications aren't appearing even though alarms are scheduled, check that `NotifyReceiver` is properly registered.
#### build.gradle
```gradle

210
TODO.md Normal file
View File

@@ -0,0 +1,210 @@
# Daily Notification Plugin - TODO Items
**Last Updated**: 2025-11-06
**Status**: Active tracking of pending improvements and features
---
## 🔴 High Priority
### 1. Add Instrumentation Tests
**Status**: In Progress
**Priority**: High
**Context**: Expand beyond basic `ExampleInstrumentedTest.java`
**Tasks**:
- [x] Create comprehensive instrumentation test suite
- [x] Test alarm scheduling and delivery
- [x] Test BroadcastReceiver registration
- [x] Test alarm status checking
- [x] Test alarm cancellation
- [x] Test unique request codes
- [ ] Test notification display (requires UI testing)
- [ ] Test prefetch mechanism (requires WorkManager testing)
- [ ] Test permission handling edge cases
- [ ] Test offline scenarios
**Location**: `test-apps/daily-notification-test/android/app/src/androidTest/java/com/timesafari/dailynotification/NotificationInstrumentationTest.java`
**Reference**: `docs/android-app-improvement-plan.md` - Phase 2: Testing & Reliability
**Completed**: Created `NotificationInstrumentationTest.java` with tests for:
- NotifyReceiver registration verification
- Alarm scheduling with setAlarmClock()
- Unique request code generation
- Alarm status checking (isAlarmScheduled)
- Next alarm time retrieval
- Alarm cancellation
- PendingIntent uniqueness
---
### 2. Update Documentation
**Status**: ✅ Completed
**Priority**: High
**Context**: Documentation needs updates for recent changes
**Tasks**:
- [x] Update API reference with new methods (`isAlarmScheduled`, `getNextAlarmTime`, `testAlarm`)
- [x] Document NotifyReceiver registration requirements
- [x] Update AndroidManifest.xml examples
- [x] Document alarm scheduling improvements (`setAlarmClock()`)
- [x] Add troubleshooting guide for BroadcastReceiver issues
- [ ] Update integration guide with Vue test app setup
**Completed**: Updated documentation in:
- `API.md`: Added new diagnostic methods with examples
- `README.md`: Added Android diagnostic methods section, emphasized NotifyReceiver requirement
- `docs/notification-testing-procedures.md`: Added troubleshooting for BroadcastReceiver issues, diagnostic method usage
**Reference**: `docs/android-app-improvement-plan.md` - Phase 3: Security & Performance
---
## 🟡 Medium Priority
### 3. Phase 2 Platform Implementation
**Status**: Pending
**Priority**: Medium
**Context**: Complete platform-specific implementations per specification
**Android Tasks**:
- [ ] WorkManager integration improvements
- [ ] SQLite storage implementation (shared database)
- [ ] TTL enforcement at notification fire time
- [ ] Rolling window safety mechanisms
- [ ] ETag support for content fetching
**iOS Tasks**:
- [ ] BGTaskScheduler implementation
- [ ] UNUserNotificationCenter integration
- [ ] Background task execution
- [ ] Tlead prefetch logic
**Storage System**:
- [ ] SQLite schema design with TTL rules
- [ ] WAL (Write-Ahead Logging) mode
- [ ] Shared database access pattern
- [ ] Hot-read verification for UI
**Callback Registry**:
- [ ] Full implementation with retries
- [ ] Redaction support for sensitive data
- [ ] Webhook delivery mechanism
- [ ] Error handling and recovery
**Reference**: `doc/implementation-roadmap.md` - Phase 2 details
---
### 4. Performance Optimization
**Status**: Pending
**Priority**: Medium
**Context**: Optimize battery usage and system resources
**Tasks**:
- [ ] Battery optimization recommendations
- [ ] Network request optimization
- [ ] Background execution efficiency
- [ ] Memory usage optimization
- [ ] CPU usage profiling
**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist
---
### 5. Security Audit
**Status**: Pending
**Priority**: Medium
**Context**: Security hardening review
**Tasks**:
- [ ] Permission validation review
- [ ] Input sanitization audit
- [ ] Network security review
- [ ] Storage encryption review
- [ ] JWT token handling security
**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist
---
## 🟢 Low Priority / Nice-to-Have
### 6. iOS Implementation Completion
**Status**: Pending
**Priority**: Low
**Context**: Complete iOS platform implementation
**Tasks**:
- [ ] BGTaskScheduler registration
- [ ] Background task handlers
- [ ] UNUserNotificationCenter integration
- [ ] UserDefaults storage improvements
- [ ] Background App Refresh handling
**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist
---
### 7. Monitoring and Analytics
**Status**: Pending
**Priority**: Low
**Context**: Add observability and metrics
**Tasks**:
- [ ] Structured logging improvements
- [ ] Health monitoring endpoints
- [ ] Success rate tracking
- [ ] Latency metrics
- [ ] Error distribution tracking
**Reference**: `doc/directives/0001-Daily-Notification-Plugin-Implementation-Directive.md`
---
### 8. User Documentation
**Status**: Pending
**Priority**: Low
**Context**: End-user documentation
**Tasks**:
- [ ] User guide for notification setup
- [ ] Troubleshooting guide for users
- [ ] Battery optimization instructions
- [ ] Permission setup guide
**Reference**: `code-summary-for-chatgpt.md` - Production Readiness Checklist
---
### 9. Production Deployment Guide
**Status**: Pending
**Priority**: Low
**Context**: Deployment procedures
**Tasks**:
- [ ] Production build configuration
- [ ] Release checklist
- [ ] Rollback procedures
- [ ] Monitoring setup guide
**Reference**: `DEPLOYMENT_CHECKLIST.md`
---
## 📝 Notes
- **CI/CD**: Excluded from this list per project requirements
- **Current Focus**: High priority items (#1 and #2)
- **Recent Completion**: NotifyReceiver registration fix (2025-11-06)
- **Verification**: Notification system working in both test apps
---
**Related Documents**:
- `docs/android-app-improvement-plan.md` - Detailed improvement plan
- `doc/implementation-roadmap.md` - Implementation phases
- `DEPLOYMENT_CHECKLIST.md` - Deployment procedures
- `test-apps/daily-notification-test/TODO_NATIVE_FETCHER.md` - Native fetcher TODOs

View File

@@ -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
});
#### 4. App Crashes on Force Stop
// Get next alarm time
const nextAlarm = await DailyNotification.getNextAlarmTime();
// Test alarm delivery
await DailyNotification.testAlarm({ secondsFromNow: 10 });
```
#### 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

View File

@@ -0,0 +1,286 @@
package com.timesafari.dailynotification;
import static org.junit.Assert.*;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Calendar;
/**
* Instrumentation tests for Daily Notification Plugin
*
* Tests critical notification scheduling and delivery paths
*/
@RunWith(AndroidJUnit4.class)
public class NotificationInstrumentationTest {
private Context appContext;
private AlarmManager alarmManager;
@Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(
android.Manifest.permission.POST_NOTIFICATIONS,
android.Manifest.permission.SCHEDULE_EXACT_ALARM
);
@Before
public void setUp() {
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
alarmManager = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE);
}
@Test
public void testNotifyReceiverRegistration() {
// Verify NotifyReceiver is registered in AndroidManifest
Intent intent = new Intent(appContext, NotifyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
appContext,
0,
intent,
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
);
// If NotifyReceiver is registered, we can create a PendingIntent for it
assertNotNull("NotifyReceiver should be registered in AndroidManifest", pendingIntent);
}
@Test
public void testAlarmScheduling() {
// Test that alarms can be scheduled
long triggerTime = System.currentTimeMillis() + 60000; // 1 minute from now
Intent intent = new Intent(appContext, NotifyReceiver.class);
intent.putExtra("title", "Test Notification");
intent.putExtra("body", "Test body");
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
appContext,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
try {
// Use setAlarmClock for Android 5.0+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
triggerTime,
null
);
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
}
// Verify alarm is scheduled
boolean isScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
assertTrue("Alarm should be scheduled", isScheduled);
// Clean up
alarmManager.cancel(pendingIntent);
} catch (SecurityException e) {
fail("Should have permission to schedule exact alarms: " + e.getMessage());
}
}
@Test
public void testUniqueRequestCodes() {
// Test that different trigger times generate different request codes
long triggerTime1 = System.currentTimeMillis() + 60000;
long triggerTime2 = System.currentTimeMillis() + 120000;
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
// Request codes should be different for different trigger times
assertNotEquals("Different trigger times should generate different request codes",
requestCode1, requestCode2);
}
@Test
public void testAlarmStatusCheck() {
// Test isAlarmScheduled method
long triggerTime = System.currentTimeMillis() + 60000;
// Initially should not be scheduled
boolean initiallyScheduled = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
assertFalse("Alarm should not be scheduled initially", initiallyScheduled);
// Schedule alarm
Intent intent = new Intent(appContext, NotifyReceiver.class);
intent.putExtra("title", "Test");
intent.putExtra("body", "Test");
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
appContext,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
triggerTime,
null
);
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
}
// Now should be scheduled
boolean afterScheduling = NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime);
assertTrue("Alarm should be scheduled after calling setAlarmClock", afterScheduling);
// Clean up
alarmManager.cancel(pendingIntent);
} catch (SecurityException e) {
fail("Should have permission to schedule exact alarms: " + e.getMessage());
}
}
@Test
public void testNextAlarmTime() {
// Test getNextAlarmTime method (requires Android 5.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Long nextAlarmTime = NotifyReceiver.Companion.getNextAlarmTime(appContext);
// May be null if no alarm scheduled, which is valid
if (nextAlarmTime != null) {
assertTrue("Next alarm time should be in the future",
nextAlarmTime > System.currentTimeMillis());
}
}
}
@Test
public void testAlarmCancellation() {
// Test that alarms can be cancelled
long triggerTime = System.currentTimeMillis() + 60000;
Intent intent = new Intent(appContext, NotifyReceiver.class);
intent.putExtra("title", "Test");
intent.putExtra("body", "Test");
int requestCode = NotifyReceiver.Companion.getRequestCode(triggerTime);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
appContext,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
try {
// Schedule alarm
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
triggerTime,
null
);
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
);
}
// Verify scheduled
assertTrue("Alarm should be scheduled",
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
// Cancel alarm
NotifyReceiver.Companion.cancelNotification(appContext, triggerTime);
// Verify cancelled
assertFalse("Alarm should be cancelled",
NotifyReceiver.Companion.isAlarmScheduled(appContext, triggerTime));
} catch (SecurityException e) {
fail("Should have permission to schedule exact alarms: " + e.getMessage());
}
}
@Test
public void testPendingIntentUniqueness() {
// Test that PendingIntents with different request codes don't conflict
long triggerTime1 = System.currentTimeMillis() + 60000;
long triggerTime2 = System.currentTimeMillis() + 120000;
Intent intent1 = new Intent(appContext, NotifyReceiver.class);
intent1.putExtra("title", "Test 1");
intent1.putExtra("body", "Test 1");
Intent intent2 = new Intent(appContext, NotifyReceiver.class);
intent2.putExtra("title", "Test 2");
intent2.putExtra("body", "Test 2");
int requestCode1 = NotifyReceiver.Companion.getRequestCode(triggerTime1);
int requestCode2 = NotifyReceiver.Companion.getRequestCode(triggerTime2);
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(
appContext,
requestCode1,
intent1,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(
appContext,
requestCode2,
intent2,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
// Both PendingIntents should be created successfully
assertNotNull("First PendingIntent should be created", pendingIntent1);
assertNotNull("Second PendingIntent should be created", pendingIntent2);
// They should be different objects
assertNotSame("PendingIntents should be different objects",
pendingIntent1, pendingIntent2);
}
}