fix(ios): configure method parameter parsing and improve build process

Fix configure() method to read parameters directly from CAPPluginCall
instead of expecting nested options object, matching Android implementation.

Improve build process to ensure canonical UI is always copied:
- iOS build script: Copy www/index.html to test app before build
- Android build.gradle: Add copyCanonicalUI task to run before build
- Ensures test apps always use latest UI from www/index.html

This fixes the issue where configure() was returning 'Configuration
options required' error because it expected a nested options object
when Capacitor passes parameters directly on the call object.
This commit is contained in:
Matthew
2025-11-19 20:09:01 -08:00
parent 3d9254e26d
commit 92bb566631
5 changed files with 822 additions and 1149 deletions

View File

@@ -80,24 +80,21 @@ public class DailyNotificationPlugin: CAPPlugin {
* @param call Plugin call containing configuration parameters * @param call Plugin call containing configuration parameters
*/ */
@objc func configure(_ call: CAPPluginCall) { @objc func configure(_ call: CAPPluginCall) {
guard let options = call.getObject("options") else { // Capacitor passes the options object directly as call data
call.reject("Configuration options required") // Read parameters directly from call (matching Android implementation)
return
}
print("DNP-PLUGIN: Configuring plugin with new options") print("DNP-PLUGIN: Configuring plugin with new options")
do { do {
// Get configuration options // Get configuration options directly from call (matching Android)
let dbPath = options["dbPath"] as? String let dbPath = call.getString("dbPath")
let storageMode = options["storage"] as? String ?? "tiered" let storageMode = call.getString("storage") ?? "tiered"
let ttlSeconds = options["ttlSeconds"] as? Int let ttlSeconds = call.getInt("ttlSeconds")
let prefetchLeadMinutes = options["prefetchLeadMinutes"] as? Int let prefetchLeadMinutes = call.getInt("prefetchLeadMinutes")
let maxNotificationsPerDay = options["maxNotificationsPerDay"] as? Int let maxNotificationsPerDay = call.getInt("maxNotificationsPerDay")
let retentionDays = options["retentionDays"] as? Int let retentionDays = call.getInt("retentionDays")
// Phase 1: Process activeDidIntegration configuration (deferred to Phase 3) // Phase 1: Process activeDidIntegration configuration (deferred to Phase 3)
if let activeDidConfig = options["activeDidIntegration"] as? [String: Any] { if let activeDidConfig = call.getObject("activeDidIntegration") {
print("DNP-PLUGIN: activeDidIntegration config received (Phase 3 feature)") print("DNP-PLUGIN: activeDidIntegration config received (Phase 3 feature)")
// TODO: Implement activeDidIntegration configuration in Phase 3 // TODO: Implement activeDidIntegration configuration in Phase 3
} }
@@ -1486,7 +1483,7 @@ public class DailyNotificationPlugin: CAPPlugin {
var methods: [CAPPluginMethod] = [] var methods: [CAPPluginMethod] = []
// Core methods // Core methods
methods.append(CAPPluginMethod(name: "configure", returnType: CAPPluginReturnNone)) methods.append(CAPPluginMethod(name: "configure", returnType: CAPPluginReturnPromise))
methods.append(CAPPluginMethod(name: "scheduleDailyNotification", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "scheduleDailyNotification", returnType: CAPPluginReturnPromise))
methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise))
methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise))

View File

@@ -126,6 +126,9 @@ fi
build_ios_test_app() { build_ios_test_app() {
log_step "Building iOS test app..." log_step "Building iOS test app..."
# Get repo root before changing directories (we're currently in repo root from main())
REPO_ROOT="$(pwd)"
# Navigate to iOS App directory (where workspace is located) # Navigate to iOS App directory (where workspace is located)
IOS_APP_DIR="$TEST_APP_DIR/ios/App" IOS_APP_DIR="$TEST_APP_DIR/ios/App"
if [ ! -d "$IOS_APP_DIR" ]; then if [ ! -d "$IOS_APP_DIR" ]; then
@@ -162,6 +165,61 @@ build_ios_test_app() {
log_warn "No Podfile found, skipping pod install" log_warn "No Podfile found, skipping pod install"
fi fi
# Copy canonical UI from www/index.html to test app
log_step "Copying canonical UI from www/index.html..."
# Use REPO_ROOT calculated before changing directories
CANONICAL_UI="$REPO_ROOT/www/index.html"
IOS_UI_SOURCE="$REPO_ROOT/$TEST_APP_DIR/App/App/Public/index.html"
IOS_UI_RUNTIME="$REPO_ROOT/$TEST_APP_DIR/ios/App/App/public/index.html"
if [ -f "$CANONICAL_UI" ]; then
# Copy to source location (for Capacitor sync)
if cp "$CANONICAL_UI" "$IOS_UI_SOURCE"; then
log_info "Copied canonical UI to iOS test app source"
else
log_error "Failed to copy canonical UI to source"
exit 1
fi
# Also copy directly to runtime location (in case sync doesn't run or is cached)
if [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then
if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then
log_info "Copied canonical UI to iOS test app runtime"
else
log_warn "Failed to copy canonical UI to runtime (may be synced later)"
fi
else
log_warn "Runtime directory not found, will be created by Capacitor sync"
fi
else
log_warn "Canonical UI not found at $CANONICAL_UI, skipping copy"
fi
# Sync Capacitor from test app root (where capacitor.config.json is located)
# This must run from test-apps/ios-test-app, not from ios/App/App
log_step "Syncing Capacitor..."
TEST_APP_ROOT="$REPO_ROOT/$TEST_APP_DIR"
if [ -f "$TEST_APP_ROOT/capacitor.config.json" ] || [ -f "$TEST_APP_ROOT/capacitor.config.ts" ]; then
if command -v npx &> /dev/null; then
# Save current directory
CURRENT_DIR="$(pwd)"
# Change to test app root for sync
cd "$TEST_APP_ROOT" || exit 1
if ! npx cap sync ios; then
log_error "Capacitor sync failed"
cd "$CURRENT_DIR" || exit 1
exit 1
fi
# Return to ios/App directory
cd "$CURRENT_DIR" || exit 1
log_info "Capacitor synced"
else
log_warn "npx not found, skipping Capacitor sync"
fi
else
log_warn "Capacitor config not found at $TEST_APP_ROOT, skipping sync"
fi
# Build TypeScript/JavaScript if package.json exists # Build TypeScript/JavaScript if package.json exists
if [ -f "package.json" ]; then if [ -f "package.json" ]; then
log_step "Building web assets..." log_step "Building web assets..."
@@ -172,32 +230,6 @@ build_ios_test_app() {
fi fi
log_info "Web assets built" log_info "Web assets built"
fi fi
# Sync Capacitor if needed
# Check for config in current directory or App subdirectory
CAP_CONFIG_DIR=""
if [ -f "capacitor.config.ts" ] || [ -f "capacitor.config.json" ]; then
CAP_CONFIG_DIR="."
elif [ -f "App/capacitor.config.json" ] || [ -f "App/capacitor.config.ts" ]; then
CAP_CONFIG_DIR="App"
fi
if command -v npx &> /dev/null && [ -n "$CAP_CONFIG_DIR" ]; then
log_step "Syncing Capacitor..."
# Run sync from directory containing config
if [ "$CAP_CONFIG_DIR" != "." ]; then
cd "$CAP_CONFIG_DIR" || exit 1
fi
if ! npx cap sync ios; then
log_error "Capacitor sync failed"
exit 1
fi
# Return to ios/App directory if we changed
if [ "$CAP_CONFIG_DIR" != "." ]; then
cd .. || exit 1
fi
log_info "Capacitor synced"
fi
fi fi
# Determine SDK and destination # Determine SDK and destination
@@ -244,7 +276,16 @@ build_ios_test_app() {
ARCHIVE_PATH="build/ios-test-app-device.xcarchive" ARCHIVE_PATH="build/ios-test-app-device.xcarchive"
fi fi
# Clean build folder # Ensure UI is copied to runtime location one more time before build
# (in case sync didn't run or was cached)
log_step "Ensuring canonical UI is in runtime location before build..."
if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then
if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then
log_info "Canonical UI copied to runtime location (pre-build)"
fi
fi
# Clean build folder (removes old DerivedData)
log_step "Cleaning build folder..." log_step "Cleaning build folder..."
if [ -n "$WORKSPACE" ]; then if [ -n "$WORKSPACE" ]; then
xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
@@ -252,6 +293,15 @@ build_ios_test_app() {
xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
fi fi
# Also clean DerivedData for this specific project to remove cached HTML
log_step "Cleaning DerivedData for fresh build..."
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
if [ -d "$DERIVED_DATA_PATH" ]; then
# Find and remove DerivedData folders for this project
find "$DERIVED_DATA_PATH" -maxdepth 1 -type d -name "App-*" -exec rm -rf {} \; 2>/dev/null || true
log_info "DerivedData cleaned"
fi
# Build # Build
log_step "Building for $TARGET ($BUILD_CONFIG)..." log_step "Building for $TARGET ($BUILD_CONFIG)..."
if [ -n "$WORKSPACE" ]; then if [ -n "$WORKSPACE" ]; then
@@ -292,6 +342,17 @@ build_ios_test_app() {
if [ -n "$APP_PATH" ]; then if [ -n "$APP_PATH" ]; then
log_info "App built at: $APP_PATH" log_info "App built at: $APP_PATH"
# Force copy canonical UI directly into built app bundle (ensures latest version)
log_step "Copying canonical UI into built app bundle..."
BUILT_APP_HTML="$APP_PATH/public/index.html"
if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$BUILT_APP_HTML")" ]; then
if cp "$CANONICAL_UI" "$BUILT_APP_HTML"; then
log_info "✅ Canonical UI copied directly into app bundle"
else
log_warn "Failed to copy UI into app bundle (may use cached version)"
fi
fi
log_info "" log_info ""
# Boot simulator if not already booted # Boot simulator if not already booted
@@ -363,6 +424,19 @@ build_ios_test_app() {
fi fi
fi fi
# Uninstall existing app (if present) to ensure clean install
log_step "Uninstalling existing app (if present)..."
APP_BUNDLE_ID="com.timesafari.dailynotification.test"
if xcrun simctl uninstall "$SIMULATOR_ID" "$APP_BUNDLE_ID" 2>&1; then
log_info "Existing app uninstalled"
else
# App may not be installed, which is fine
log_info "No existing app to uninstall (or uninstall failed - continuing anyway)"
fi
# Wait a moment after uninstall
sleep 1
# Install the app # Install the app
log_step "Installing app on simulator..." log_step "Installing app on simulator..."
if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then
@@ -383,9 +457,9 @@ build_ios_test_app() {
sleep 2 sleep 2
# Method 1: Direct launch (capture output to check for errors) # Method 1: Direct launch (capture output to check for errors)
# Note: Bundle ID is com.timesafari.dailynotification (not .test) # Note: Bundle ID is com.timesafari.dailynotification.test
log_info "Attempting to launch app..." log_info "Attempting to launch app..."
LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification 2>&1) LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" "$APP_BUNDLE_ID" 2>&1)
LAUNCH_EXIT_CODE=$? LAUNCH_EXIT_CODE=$?
if [ $LAUNCH_EXIT_CODE -eq 0 ]; then if [ $LAUNCH_EXIT_CODE -eq 0 ]; then
@@ -410,19 +484,19 @@ build_ios_test_app() {
if [ "$LAUNCH_SUCCESS" = false ]; then if [ "$LAUNCH_SUCCESS" = false ]; then
log_info "Checking if app is already running..." log_info "Checking if app is already running..."
sleep 2 sleep 2
RUNNING_APPS=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 5 "com.timesafari.dailynotification" || echo "") RUNNING_APPS=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 5 "$APP_BUNDLE_ID" || echo "")
if [ -n "$RUNNING_APPS" ]; then if [ -n "$RUNNING_APPS" ]; then
log_info "App appears to be installed. Trying to verify it's running..." log_info "App appears to be installed. Trying to verify it's running..."
# Try to get app state # Try to get app state
APP_STATE=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 10 "com.timesafari.dailynotification" | grep "ApplicationType" || echo "") APP_STATE=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 10 "$APP_BUNDLE_ID" | grep "ApplicationType" || echo "")
if [ -n "$APP_STATE" ]; then if [ -n "$APP_STATE" ]; then
log_info "App found in simulator. Attempting manual launch..." log_info "App found in simulator. Attempting manual launch..."
# Try opening via Simulator app # Try opening via Simulator app
open -a Simulator open -a Simulator
sleep 1 sleep 1
# Try launch one more time # Try launch one more time
if xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then if xcrun simctl launch "$SIMULATOR_ID" "$APP_BUNDLE_ID" >/dev/null 2>&1; then
LAUNCH_SUCCESS=true LAUNCH_SUCCESS=true
log_info "✅ App launched successfully on retry!" log_info "✅ App launched successfully on retry!"
fi fi
@@ -434,7 +508,7 @@ build_ios_test_app() {
if [ "$LAUNCH_SUCCESS" = true ]; then if [ "$LAUNCH_SUCCESS" = true ]; then
sleep 2 sleep 2
# Try to verify app is running by checking if we can get its container # Try to verify app is running by checking if we can get its container
if xcrun simctl get_app_container "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then if xcrun simctl get_app_container "$SIMULATOR_ID" "$APP_BUNDLE_ID" >/dev/null 2>&1; then
log_info "✅ Verified: App is installed and accessible" log_info "✅ Verified: App is installed and accessible"
else else
log_warn "⚠️ Launch reported success but app verification failed" log_warn "⚠️ Launch reported success but app verification failed"
@@ -446,7 +520,7 @@ build_ios_test_app() {
log_info "The app is installed. To launch manually:" log_info "The app is installed. To launch manually:"
log_info " 1. Open Simulator app (if not already open)" log_info " 1. Open Simulator app (if not already open)"
log_info " 2. Find the app icon on the home screen and tap it" log_info " 2. Find the app icon on the home screen and tap it"
log_info " 3. Or run: xcrun simctl launch $SIMULATOR_ID com.timesafari.dailynotification" log_info " 3. Or run: xcrun simctl launch $SIMULATOR_ID $APP_BUNDLE_ID"
if [ -n "$LAUNCH_ERROR" ]; then if [ -n "$LAUNCH_ERROR" ]; then
log_info "" log_info ""
log_info "Launch error details:" log_info "Launch error details:"
@@ -460,7 +534,7 @@ build_ios_test_app() {
log_info "" log_info ""
log_info "To run on simulator manually:" log_info "To run on simulator manually:"
log_info " xcrun simctl install booted \"$APP_PATH\"" log_info " xcrun simctl install booted \"$APP_PATH\""
log_info " xcrun simctl launch booted com.timesafari.dailynotification" log_info " xcrun simctl launch booted com.timesafari.dailynotification.test"
fi fi
else else
log_warn "Could not find built app in DerivedData" log_warn "Could not find built app in DerivedData"

View File

@@ -52,6 +52,28 @@ dependencies {
apply from: 'capacitor.build.gradle' apply from: 'capacitor.build.gradle'
// Copy canonical UI from www/index.html before each build
task copyCanonicalUI(type: Copy) {
description = 'Copies canonical UI from www/index.html to test app'
group = 'build'
def repoRoot = project.rootProject.projectDir.parentFile.parentFile
def canonicalUI = new File(repoRoot, 'www/index.html')
def targetUI = new File(projectDir, 'src/main/assets/public/index.html')
if (canonicalUI.exists()) {
from canonicalUI
into targetUI.parentFile
rename { 'index.html' }
println "✅ Copied canonical UI from ${canonicalUI} to ${targetUI}"
} else {
println "⚠️ Canonical UI not found at ${canonicalUI}, skipping copy"
}
}
// Make copy task run before build
preBuild.dependsOn copyCanonicalUI
try { try {
def servicesJSON = file('google-services.json') def servicesJSON = file('google-services.json')
if (servicesJSON.text) { if (servicesJSON.text) {

View File

@@ -17,627 +17,417 @@
color: white; color: white;
} }
.container { .container {
max-width: 800px; max-width: 600px;
margin: 0 auto; margin: 0 auto;
text-align: center;
} }
h1 { h1 {
text-align: center;
margin-bottom: 30px; margin-bottom: 30px;
font-size: 2.5em; font-size: 2.5em;
} }
.section {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.section h2 {
margin-top: 0;
color: #ffd700;
}
.button { .button {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3); border: 2px solid rgba(255, 255, 255, 0.3);
color: white; color: white;
padding: 12px 24px; padding: 15px 30px;
margin: 8px; margin: 10px;
border-radius: 20px; border-radius: 25px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 16px;
transition: all 0.3s ease; transition: all 0.3s ease;
display: inline-block;
} }
.button:hover { .button:hover {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px); transform: translateY(-2px);
} }
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.status { .status {
margin-top: 15px; margin-top: 30px;
padding: 15px; padding: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
.success { color: #4CAF50; }
.error { color: #f44336; }
.warning { color: #ff9800; }
.info { color: #2196F3; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 15px 0;
}
.input-group {
margin: 10px 0;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.input-group input, .input-group select {
width: 100%;
padding: 8px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
color: white; border-radius: 10px;
} font-family: monospace;
.input-group input::placeholder {
color: rgba(255, 255, 255, 0.7);
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>🔔 DailyNotification Plugin Test</h1> <div id="statusCard" class="status" style="margin-bottom: 20px; font-size: 14px;">
<strong>Plugin Status</strong><br>
<!-- Plugin Status Section --> <div style="margin-top: 10px;">
<div class="section"> ⚙️ Plugin Settings: <span id="configStatus">Not configured</span><br>
<h2>📊 Plugin Status</h2> 🔌 Native Fetcher: <span id="fetcherStatus">Not configured</span><br>
<div class="grid"> 🔔 Notifications: <span id="notificationPermStatus">Checking...</span><br>
<button class="button" onclick="checkPluginAvailability()">Check Availability</button> ⏰ Exact Alarms: <span id="exactAlarmPermStatus">Checking...</span><br>
<button class="button" onclick="getNotificationStatus()">Get Status</button> 📢 Channel: <span id="channelStatus">Checking...</span><br>
<button class="button" onclick="checkPermissions()">Check Permissions</button> <div id="pluginStatusContent" style="margin-top: 8px;">
<button class="button" onclick="getBatteryStatus()">Battery Status</button> Loading plugin status...
</div> </div>
<div id="status" class="status">Ready to test...</div>
</div>
<!-- Permission Management Section -->
<div class="section">
<h2>🔐 Permission Management</h2>
<div class="grid">
<button class="button" onclick="requestPermissions()">Request Permissions</button>
<button class="button" onclick="requestExactAlarmPermission()">Request Exact Alarm</button>
<button class="button" onclick="openExactAlarmSettings()">Open Settings</button>
<button class="button" onclick="requestBatteryOptimizationExemption()">Battery Exemption</button>
</div> </div>
</div> </div>
<!-- Notification Scheduling Section -->
<div class="section">
<h2>⏰ Notification Scheduling</h2>
<div class="input-group">
<label for="notificationUrl">Content URL:</label>
<input type="text" id="notificationUrl" placeholder="https://api.example.com/daily-content" value="https://api.example.com/daily-content">
</div>
<div class="input-group">
<label for="notificationTime">Schedule Time:</label>
<input type="time" id="notificationTime" value="09:00">
</div>
<div class="input-group">
<label for="notificationTitle">Title:</label>
<input type="text" id="notificationTitle" placeholder="Daily Notification" value="Daily Notification">
</div>
<div class="input-group">
<label for="notificationBody">Body:</label>
<input type="text" id="notificationBody" placeholder="Your daily content is ready!" value="Your daily content is ready!">
</div>
<div class="grid">
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
<button class="button" onclick="cancelAllNotifications()">Cancel All</button>
<button class="button" onclick="getLastNotification()">Get Last</button>
</div>
</div>
<!-- Configuration Section -->
<div class="section">
<h2>⚙️ Plugin Configuration</h2>
<div class="input-group">
<label for="configUrl">Fetch URL:</label>
<input type="text" id="configUrl" placeholder="https://api.example.com/content" value="https://api.example.com/content">
</div>
<div class="input-group">
<label for="configTime">Schedule Time:</label>
<input type="time" id="configTime" value="09:00">
</div>
<div class="input-group">
<label for="configRetryCount">Retry Count:</label>
<input type="number" id="configRetryCount" value="3" min="0" max="10">
</div>
<div class="grid">
<button class="button" onclick="configurePlugin()">Configure Plugin</button> <button class="button" onclick="configurePlugin()">Configure Plugin</button>
<button class="button" onclick="updateSettings()">Update Settings</button> <button class="button" onclick="requestPermissions()">Request Permissions</button>
</div> <button class="button" onclick="testNotification()">Test Notification</button>
</div> <button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
<!-- Advanced Features Section --> <div id="status" class="status">
<div class="section"> Ready to test...
<h2>🚀 Advanced Features</h2>
<div class="grid">
<button class="button" onclick="getExactAlarmStatus()">Exact Alarm Status</button>
<button class="button" onclick="getRebootRecoveryStatus()">Reboot Recovery</button>
<button class="button" onclick="getRollingWindowStats()">Rolling Window</button>
<button class="button" onclick="maintainRollingWindow()">Maintain Window</button>
<button class="button" onclick="getContentCache()">Content Cache</button>
<button class="button" onclick="clearContentCache()">Clear Cache</button>
<button class="button" onclick="getContentHistory()">Content History</button>
<button class="button" onclick="getDualScheduleStatus()">Dual Schedule</button>
</div>
</div> </div>
</div> </div>
<script> <script>
console.log('🔔 DailyNotification Plugin Test Interface Loading...'); console.log('Script loading...');
console.log('JavaScript is working!');
// Global variables // Use real DailyNotification plugin
let plugin = null; console.log('Using real DailyNotification plugin...');
let isPluginAvailable = false; window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
// Initialize plugin on page load // Define functions immediately and attach to window
document.addEventListener('DOMContentLoaded', async function() {
console.log('📱 DOM loaded, initializing plugin...'); function configurePlugin() {
await initializePlugin(); console.log('configurePlugin called');
}); const status = document.getElementById('status');
const configStatus = document.getElementById('configStatus');
const fetcherStatus = document.getElementById('fetcherStatus');
status.innerHTML = 'Configuring plugin...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
// Update top status to show configuring
configStatus.innerHTML = '⏳ Configuring...';
fetcherStatus.innerHTML = '⏳ Waiting...';
// Initialize the real DailyNotification plugin
async function initializePlugin() {
try { try {
// Try to access the real plugin through Capacitor if (!window.DailyNotification) {
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DailyNotification) { status.innerHTML = 'DailyNotification plugin not available';
plugin = window.Capacitor.Plugins.DailyNotification; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
isPluginAvailable = true; configStatus.innerHTML = '❌ Plugin unavailable';
console.log('✅ Real DailyNotification plugin found!'); fetcherStatus.innerHTML = '❌ Plugin unavailable';
updateStatus('success', '✅ Real DailyNotification plugin loaded successfully!'); return;
} else {
// Fallback to mock for development
console.log('⚠️ Real plugin not available, using mock for development');
plugin = createMockPlugin();
isPluginAvailable = false;
updateStatus('warning', '⚠️ Using mock plugin (real plugin not available)');
} }
// Configure plugin settings
window.DailyNotification.configure({
storage: 'tiered',
ttlSeconds: 86400,
prefetchLeadMinutes: 60,
maxNotificationsPerDay: 3,
retentionDays: 7
})
.then(() => {
console.log('Plugin settings configured, now configuring native fetcher...');
// Update top status
configStatus.innerHTML = '✅ Configured';
// Configure native fetcher with demo credentials
// Note: DemoNativeFetcher uses hardcoded mock data, so this is optional
// but demonstrates the API. In production, this would be real credentials.
return window.DailyNotification.configureNativeFetcher({
apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
activeDid: 'did:ethr:0xDEMO1234567890', // Demo DID
jwtSecret: 'demo-jwt-secret-for-development-testing'
});
})
.then(() => {
// Update top status
fetcherStatus.innerHTML = '✅ Configured';
// Update bottom status for user feedback
status.innerHTML = 'Plugin configured successfully!';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
})
.catch(error => {
// Update top status with error
if (configStatus.innerHTML.includes('Configuring')) {
configStatus.innerHTML = '❌ Failed';
}
if (fetcherStatus.innerHTML.includes('Waiting') || fetcherStatus.innerHTML.includes('Configuring')) {
fetcherStatus.innerHTML = '❌ Failed';
}
status.innerHTML = `Configuration failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
console.error('❌ Plugin initialization failed:', error); configStatus.innerHTML = '❌ Error';
updateStatus('error', `❌ Plugin initialization failed: ${error.message}`); fetcherStatus.innerHTML = '❌ Error';
status.innerHTML = `Configuration failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Create mock plugin for development/testing function loadPluginStatus() {
function createMockPlugin() { console.log('loadPluginStatus called');
return { const pluginStatusContent = document.getElementById('pluginStatusContent');
configure: async (options) => { const statusCard = document.getElementById('statusCard');
console.log('Mock configure called with:', options);
return Promise.resolve(); try {
}, if (!window.DailyNotification) {
getNotificationStatus: async () => { pluginStatusContent.innerHTML = '❌ DailyNotification plugin not available';
return Promise.resolve({ statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
isEnabled: true, return;
isScheduled: true,
lastNotificationTime: Date.now() - 86400000,
nextNotificationTime: Date.now() + 3600000,
pending: 1,
settings: { url: 'https://api.example.com/content', time: '09:00' },
error: null
});
},
checkPermissions: async () => {
return Promise.resolve({
notifications: 'granted',
backgroundRefresh: 'granted',
alert: true,
badge: true,
sound: true
});
},
requestPermissions: async () => {
return Promise.resolve({
notifications: 'granted',
backgroundRefresh: 'granted',
alert: true,
badge: true,
sound: true
});
},
scheduleDailyNotification: async (options) => {
console.log('Mock scheduleDailyNotification called with:', options);
return Promise.resolve();
},
cancelAllNotifications: async () => {
console.log('Mock cancelAllNotifications called');
return Promise.resolve();
},
getLastNotification: async () => {
return Promise.resolve({
id: 'mock-123',
title: 'Mock Notification',
body: 'This is a mock notification',
timestamp: Date.now() - 3600000,
url: 'https://example.com'
});
},
getBatteryStatus: async () => {
return Promise.resolve({
level: 85,
isCharging: false,
powerState: 1,
isOptimizationExempt: false
});
},
getExactAlarmStatus: async () => {
return Promise.resolve({
supported: true,
enabled: true,
canSchedule: true,
fallbackWindow: '±15 minutes'
});
},
requestExactAlarmPermission: async () => {
console.log('Mock requestExactAlarmPermission called');
return Promise.resolve();
},
openExactAlarmSettings: async () => {
console.log('Mock openExactAlarmSettings called');
return Promise.resolve();
},
requestBatteryOptimizationExemption: async () => {
console.log('Mock requestBatteryOptimizationExemption called');
return Promise.resolve();
},
getRebootRecoveryStatus: async () => {
return Promise.resolve({
inProgress: false,
lastRecoveryTime: Date.now() - 86400000,
timeSinceLastRecovery: 86400000,
recoveryNeeded: false
});
},
getRollingWindowStats: async () => {
return Promise.resolve({
stats: 'Window: 7 days, Notifications: 5, Success rate: 100%',
maintenanceNeeded: false,
timeUntilNextMaintenance: 3600000
});
},
maintainRollingWindow: async () => {
console.log('Mock maintainRollingWindow called');
return Promise.resolve();
},
getContentCache: async () => {
return Promise.resolve({
'cache-key-1': { content: 'Mock cached content', timestamp: Date.now() },
'cache-key-2': { content: 'Another mock item', timestamp: Date.now() - 3600000 }
});
},
clearContentCache: async () => {
console.log('Mock clearContentCache called');
return Promise.resolve();
},
getContentHistory: async () => {
return Promise.resolve([
{ id: '1', timestamp: Date.now() - 86400000, success: true, content: 'Mock content 1' },
{ id: '2', timestamp: Date.now() - 172800000, success: true, content: 'Mock content 2' }
]);
},
getDualScheduleStatus: async () => {
return Promise.resolve({
isActive: true,
contentSchedule: { nextRun: Date.now() + 3600000, isEnabled: true },
userSchedule: { nextRun: Date.now() + 7200000, isEnabled: true },
lastContentFetch: Date.now() - 3600000,
lastUserNotification: Date.now() - 7200000
});
} }
}; window.DailyNotification.getNotificationStatus()
.then(result => {
const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled';
const hasSchedules = result.isEnabled || (result.pending && result.pending > 0);
const statusIcon = hasSchedules ? '✅' : '⏸️';
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
📅 Next Notification: ${nextTime}<br>
⏳ Pending: ${result.pending || 0}`;
statusCard.style.background = hasSchedules ?
'rgba(0, 255, 0, 0.15)' : 'rgba(255, 255, 255, 0.1)'; // Green if active, light gray if none
})
.catch(error => {
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) {
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
}
} }
// Utility function to update status display // Notification test functions
function updateStatus(type, message) { function testNotification() {
const statusEl = document.getElementById('status'); console.log('testNotification called');
statusEl.className = `status ${type}`;
statusEl.textContent = message; // Quick sanity check - test plugin availability
console.log(`[${type.toUpperCase()}] ${message}`); if (window.Capacitor && window.Capacitor.isPluginAvailable) {
} const isAvailable = window.Capacitor.isPluginAvailable('DailyNotification');
console.log('is plugin available?', isAvailable);
}
const status = document.getElementById('status');
status.innerHTML = 'Testing plugin connection...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
// Plugin availability check
async function checkPluginAvailability() {
updateStatus('info', '🔍 Checking plugin availability...');
try { try {
if (plugin) { if (!window.DailyNotification) {
updateStatus('success', `✅ Plugin available: ${isPluginAvailable ? 'Real plugin' : 'Mock plugin'}`); status.innerHTML = 'DailyNotification plugin not available';
} else { status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
updateStatus('error', '❌ Plugin not available'); return;
} }
} catch (error) {
updateStatus('error', `❌ Availability check failed: ${error.message}`);
}
}
// Get notification status // Test the notification method directly
async function getNotificationStatus() { console.log('Testing notification scheduling...');
updateStatus('info', '📊 Getting notification status...');
try {
const status = await plugin.getNotificationStatus();
updateStatus('success', `📊 Status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Status check failed: ${error.message}`);
}
}
// Check permissions
async function checkPermissions() {
updateStatus('info', '🔐 Checking permissions...');
try {
const permissions = await plugin.checkPermissions();
updateStatus('success', `🔐 Permissions: ${JSON.stringify(permissions, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Permission check failed: ${error.message}`);
}
}
// Request permissions
async function requestPermissions() {
updateStatus('info', '🔐 Requesting permissions...');
try {
const result = await plugin.requestPermissions();
updateStatus('success', `🔐 Permission result: ${JSON.stringify(result, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Permission request failed: ${error.message}`);
}
}
// Get battery status
async function getBatteryStatus() {
updateStatus('info', '🔋 Getting battery status...');
try {
const battery = await plugin.getBatteryStatus();
updateStatus('success', `🔋 Battery: ${JSON.stringify(battery, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Battery check failed: ${error.message}`);
}
}
// Schedule notification
async function scheduleNotification() {
updateStatus('info', '⏰ Scheduling notification...');
try {
const timeInput = document.getElementById('notificationTime').value;
const [hours, minutes] = timeInput.split(':');
const now = new Date(); const now = new Date();
const scheduledTime = new Date(); const notificationTime = new Date(now.getTime() + 240000); // 4 minutes from now
scheduledTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); const prefetchTime = new Date(now.getTime() + 120000); // 2 minutes from now (2 min before notification)
const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' +
// If scheduled time is in the past, schedule for tomorrow notificationTime.getMinutes().toString().padStart(2, '0');
if (scheduledTime <= now) {
scheduledTime.setDate(scheduledTime.getDate() + 1);
}
// Calculate prefetch time (2 minutes before notification)
const prefetchTime = new Date(scheduledTime.getTime() - 120000); // 2 minutes
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
const notificationTimeReadable = scheduledTime.toLocaleTimeString();
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' + const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
prefetchTime.getMinutes().toString().padStart(2, '0'); prefetchTime.getMinutes().toString().padStart(2, '0');
const notificationTimeString = scheduledTime.getHours().toString().padStart(2, '0') + ':' +
scheduledTime.getMinutes().toString().padStart(2, '0');
const options = { window.DailyNotification.scheduleDailyNotification({
url: document.getElementById('notificationUrl').value, time: notificationTimeString,
time: timeInput, title: 'Test Notification',
title: document.getElementById('notificationTitle').value, body: 'This is a test notification from the DailyNotification plugin!',
body: document.getElementById('notificationBody').value,
sound: true, sound: true,
priority: 'high' priority: 'high'
}; })
await plugin.scheduleDailyNotification(options); .then(() => {
updateStatus('success', `✅ Notification scheduled!<br>` + const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
`📥 Prefetch: ${prefetchTimeReadable} (${prefetchTimeString})<br>` + const notificationTimeReadable = notificationTime.toLocaleTimeString();
`🔔 Notification: ${notificationTimeReadable} (${notificationTimeString})`); status.innerHTML = '✅ Notification scheduled!<br>' +
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
// Refresh plugin status display
setTimeout(() => loadPluginStatus(), 500);
})
.catch(error => {
// Check if this is an exact alarm permission error
if (error.code === 'EXACT_ALARM_PERMISSION_REQUIRED' ||
error.message.includes('Exact alarm permission') ||
error.message.includes('Alarms & reminders')) {
status.innerHTML = '⚠️ Exact Alarm Permission Required<br><br>' +
'Settings opened automatically.<br>' +
'Please enable "Allow exact alarms" and return to try again.';
status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background
} else {
status.innerHTML = `❌ Notification failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
}
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Scheduling failed: ${error.message}`); status.innerHTML = `Notification test failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Cancel all notifications
async function cancelAllNotifications() { // Permission management functions
updateStatus('info', '❌ Cancelling all notifications...'); function requestPermissions() {
console.log('requestPermissions called');
const status = document.getElementById('status');
status.innerHTML = 'Requesting permissions...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
try { try {
await plugin.cancelAllNotifications(); if (!window.DailyNotification) {
updateStatus('success', '❌ All notifications cancelled'); status.innerHTML = 'DailyNotification plugin not available';
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
return;
}
window.DailyNotification.requestNotificationPermissions()
.then(() => {
status.innerHTML = 'Permission request completed! Check your device settings if needed.';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
// Refresh permission and channel status display after request
setTimeout(() => {
loadPermissionStatus();
loadChannelStatus();
}, 1000);
})
.catch(error => {
status.innerHTML = `Permission request failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Cancel failed: ${error.message}`); status.innerHTML = `Permission request failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Get last notification function loadChannelStatus() {
async function getLastNotification() { const channelStatus = document.getElementById('channelStatus');
updateStatus('info', '📱 Getting last notification...');
try { try {
const notification = await plugin.getLastNotification(); if (!window.DailyNotification) {
updateStatus('success', `📱 Last notification: ${JSON.stringify(notification, null, 2)}`); channelStatus.innerHTML = '❌ Plugin unavailable';
return;
}
window.DailyNotification.isChannelEnabled()
.then(result => {
const importanceText = getImportanceText(result.importance);
if (result.enabled) {
channelStatus.innerHTML = `✅ Enabled (${importanceText})`;
} else {
channelStatus.innerHTML = `❌ Disabled (${importanceText})`;
}
})
.catch(error => {
channelStatus.innerHTML = '⚠️ Error';
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Get last notification failed: ${error.message}`); channelStatus.innerHTML = '⚠️ Error';
} }
} }
// Configure plugin function checkComprehensiveStatus() {
async function configurePlugin() { const status = document.getElementById('status');
updateStatus('info', '⚙️ Configuring plugin...'); status.innerHTML = 'Checking comprehensive status...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
try { try {
const config = { if (!window.DailyNotification) {
fetchUrl: document.getElementById('configUrl').value, status.innerHTML = 'DailyNotification plugin not available';
scheduleTime: document.getElementById('configTime').value, status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
retryCount: parseInt(document.getElementById('configRetryCount').value), return;
enableNotifications: true, }
offlineFallback: true
}; window.DailyNotification.checkStatus()
await plugin.configure(config); .then(result => {
updateStatus('success', `⚙️ Plugin configured: ${JSON.stringify(config, null, 2)}`); const canSchedule = result.canScheduleNow;
const issues = [];
if (!result.postNotificationsGranted) {
issues.push('POST_NOTIFICATIONS permission');
}
if (!result.channelEnabled) {
issues.push('notification channel disabled');
}
if (!result.exactAlarmsGranted) {
issues.push('exact alarm permission');
}
let statusText = `Status: ${canSchedule ? 'Ready to schedule' : 'Issues found'}`;
if (issues.length > 0) {
statusText += `\nIssues: ${issues.join(', ')}`;
}
statusText += `\nChannel: ${getImportanceText(result.channelImportance)}`;
statusText += `\nChannel ID: ${result.channelId}`;
status.innerHTML = statusText;
status.style.background = canSchedule ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
})
.catch(error => {
status.innerHTML = `Status check failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Configuration failed: ${error.message}`); status.innerHTML = `Status check failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Update settings function getImportanceText(importance) {
async function updateSettings() { switch (importance) {
updateStatus('info', '⚙️ Updating settings...'); case 0: return 'None (blocked)';
case 1: return 'Min';
case 2: return 'Low';
case 3: return 'Default';
case 4: return 'High';
case 5: return 'Max';
default: return `Unknown (${importance})`;
}
}
// Attach to window object
window.configurePlugin = configurePlugin;
window.testNotification = testNotification;
window.requestPermissions = requestPermissions;
window.checkComprehensiveStatus = checkComprehensiveStatus;
function loadPermissionStatus() {
const notificationPermStatus = document.getElementById('notificationPermStatus');
const exactAlarmPermStatus = document.getElementById('exactAlarmPermStatus');
try { try {
const settings = { if (!window.DailyNotification) {
url: document.getElementById('configUrl').value, notificationPermStatus.innerHTML = '❌ Plugin unavailable';
time: document.getElementById('configTime').value, exactAlarmPermStatus.innerHTML = '❌ Plugin unavailable';
retryCount: parseInt(document.getElementById('configRetryCount').value), return;
sound: true, }
priority: 'high'
}; window.DailyNotification.checkPermissionStatus()
await plugin.updateSettings(settings); .then(result => {
updateStatus('success', `⚙️ Settings updated: ${JSON.stringify(settings, null, 2)}`); notificationPermStatus.innerHTML = result.notificationsEnabled ? '✅ Granted' : '❌ Not granted';
exactAlarmPermStatus.innerHTML = result.exactAlarmEnabled ? '✅ Granted' : '❌ Not granted';
})
.catch(error => {
notificationPermStatus.innerHTML = '⚠️ Error';
exactAlarmPermStatus.innerHTML = '⚠️ Error';
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Settings update failed: ${error.message}`); notificationPermStatus.innerHTML = '⚠️ Error';
exactAlarmPermStatus.innerHTML = '⚠️ Error';
} }
} }
// Get exact alarm status // Load plugin status automatically on page load
async function getExactAlarmStatus() { window.addEventListener('load', () => {
updateStatus('info', '⏰ Getting exact alarm status...'); console.log('Page loaded, loading plugin status...');
try { // Small delay to ensure Capacitor is ready
const status = await plugin.getExactAlarmStatus(); setTimeout(() => {
updateStatus('success', `⏰ Exact alarm status: ${JSON.stringify(status, null, 2)}`); loadPluginStatus();
} catch (error) { loadPermissionStatus();
updateStatus('error', `❌ Exact alarm check failed: ${error.message}`); loadChannelStatus();
} }, 500);
} });
// Request exact alarm permission
async function requestExactAlarmPermission() {
updateStatus('info', '⏰ Requesting exact alarm permission...');
try {
await plugin.requestExactAlarmPermission();
updateStatus('success', '⏰ Exact alarm permission requested');
} catch (error) {
updateStatus('error', `❌ Exact alarm permission request failed: ${error.message}`);
}
}
// Open exact alarm settings
async function openExactAlarmSettings() {
updateStatus('info', '⚙️ Opening exact alarm settings...');
try {
await plugin.openExactAlarmSettings();
updateStatus('success', '⚙️ Exact alarm settings opened');
} catch (error) {
updateStatus('error', `❌ Open settings failed: ${error.message}`);
}
}
// Request battery optimization exemption console.log('Functions attached to window:', {
async function requestBatteryOptimizationExemption() { configurePlugin: typeof window.configurePlugin,
updateStatus('info', '🔋 Requesting battery optimization exemption...'); testNotification: typeof window.testNotification
try { });
await plugin.requestBatteryOptimizationExemption();
updateStatus('success', '🔋 Battery optimization exemption requested');
} catch (error) {
updateStatus('error', `❌ Battery exemption request failed: ${error.message}`);
}
}
// Get reboot recovery status
async function getRebootRecoveryStatus() {
updateStatus('info', '🔄 Getting reboot recovery status...');
try {
const status = await plugin.getRebootRecoveryStatus();
updateStatus('success', `🔄 Reboot recovery status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Reboot recovery check failed: ${error.message}`);
}
}
// Get rolling window stats
async function getRollingWindowStats() {
updateStatus('info', '📊 Getting rolling window stats...');
try {
const stats = await plugin.getRollingWindowStats();
updateStatus('success', `📊 Rolling window stats: ${JSON.stringify(stats, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Rolling window stats failed: ${error.message}`);
}
}
// Maintain rolling window
async function maintainRollingWindow() {
updateStatus('info', '🔧 Maintaining rolling window...');
try {
await plugin.maintainRollingWindow();
updateStatus('success', '🔧 Rolling window maintenance completed');
} catch (error) {
updateStatus('error', `❌ Rolling window maintenance failed: ${error.message}`);
}
}
// Get content cache
async function getContentCache() {
updateStatus('info', '💾 Getting content cache...');
try {
const cache = await plugin.getContentCache();
updateStatus('success', `💾 Content cache: ${JSON.stringify(cache, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Content cache check failed: ${error.message}`);
}
}
// Clear content cache
async function clearContentCache() {
updateStatus('info', '🗑️ Clearing content cache...');
try {
await plugin.clearContentCache();
updateStatus('success', '🗑️ Content cache cleared');
} catch (error) {
updateStatus('error', `❌ Clear cache failed: ${error.message}`);
}
}
// Get content history
async function getContentHistory() {
updateStatus('info', '📚 Getting content history...');
try {
const history = await plugin.getContentHistory();
updateStatus('success', `📚 Content history: ${JSON.stringify(history, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Content history check failed: ${error.message}`);
}
}
// Get dual schedule status
async function getDualScheduleStatus() {
updateStatus('info', '🔄 Getting dual schedule status...');
try {
const status = await plugin.getDualScheduleStatus();
updateStatus('success', `🔄 Dual schedule status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Dual schedule check failed: ${error.message}`);
}
}
console.log('🔔 DailyNotification Plugin Test Interface Loaded Successfully!');
</script> </script>
</body> </body>
</html> </html>

View File

@@ -17,627 +17,417 @@
color: white; color: white;
} }
.container { .container {
max-width: 800px; max-width: 600px;
margin: 0 auto; margin: 0 auto;
text-align: center;
} }
h1 { h1 {
text-align: center;
margin-bottom: 30px; margin-bottom: 30px;
font-size: 2.5em; font-size: 2.5em;
} }
.section {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.section h2 {
margin-top: 0;
color: #ffd700;
}
.button { .button {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3); border: 2px solid rgba(255, 255, 255, 0.3);
color: white; color: white;
padding: 12px 24px; padding: 15px 30px;
margin: 8px; margin: 10px;
border-radius: 20px; border-radius: 25px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 16px;
transition: all 0.3s ease; transition: all 0.3s ease;
display: inline-block;
} }
.button:hover { .button:hover {
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px); transform: translateY(-2px);
} }
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.status { .status {
margin-top: 15px; margin-top: 30px;
padding: 15px; padding: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
.success { color: #4CAF50; }
.error { color: #f44336; }
.warning { color: #ff9800; }
.info { color: #2196F3; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 15px 0;
}
.input-group {
margin: 10px 0;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.input-group input, .input-group select {
width: 100%;
padding: 8px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
color: white; border-radius: 10px;
} font-family: monospace;
.input-group input::placeholder {
color: rgba(255, 255, 255, 0.7);
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>🔔 DailyNotification Plugin Test</h1> <div id="statusCard" class="status" style="margin-bottom: 20px; font-size: 14px;">
<strong>Plugin Status</strong><br>
<!-- Plugin Status Section --> <div style="margin-top: 10px;">
<div class="section"> ⚙️ Plugin Settings: <span id="configStatus">Not configured</span><br>
<h2>📊 Plugin Status</h2> 🔌 Native Fetcher: <span id="fetcherStatus">Not configured</span><br>
<div class="grid"> 🔔 Notifications: <span id="notificationPermStatus">Checking...</span><br>
<button class="button" onclick="checkPluginAvailability()">Check Availability</button> ⏰ Exact Alarms: <span id="exactAlarmPermStatus">Checking...</span><br>
<button class="button" onclick="getNotificationStatus()">Get Status</button> 📢 Channel: <span id="channelStatus">Checking...</span><br>
<button class="button" onclick="checkPermissions()">Check Permissions</button> <div id="pluginStatusContent" style="margin-top: 8px;">
<button class="button" onclick="getBatteryStatus()">Battery Status</button> Loading plugin status...
</div> </div>
<div id="status" class="status">Ready to test...</div>
</div>
<!-- Permission Management Section -->
<div class="section">
<h2>🔐 Permission Management</h2>
<div class="grid">
<button class="button" onclick="requestPermissions()">Request Permissions</button>
<button class="button" onclick="requestExactAlarmPermission()">Request Exact Alarm</button>
<button class="button" onclick="openExactAlarmSettings()">Open Settings</button>
<button class="button" onclick="requestBatteryOptimizationExemption()">Battery Exemption</button>
</div> </div>
</div> </div>
<!-- Notification Scheduling Section -->
<div class="section">
<h2>⏰ Notification Scheduling</h2>
<div class="input-group">
<label for="notificationUrl">Content URL:</label>
<input type="text" id="notificationUrl" placeholder="https://api.example.com/daily-content" value="https://api.example.com/daily-content">
</div>
<div class="input-group">
<label for="notificationTime">Schedule Time:</label>
<input type="time" id="notificationTime" value="09:00">
</div>
<div class="input-group">
<label for="notificationTitle">Title:</label>
<input type="text" id="notificationTitle" placeholder="Daily Notification" value="Daily Notification">
</div>
<div class="input-group">
<label for="notificationBody">Body:</label>
<input type="text" id="notificationBody" placeholder="Your daily content is ready!" value="Your daily content is ready!">
</div>
<div class="grid">
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
<button class="button" onclick="cancelAllNotifications()">Cancel All</button>
<button class="button" onclick="getLastNotification()">Get Last</button>
</div>
</div>
<!-- Configuration Section -->
<div class="section">
<h2>⚙️ Plugin Configuration</h2>
<div class="input-group">
<label for="configUrl">Fetch URL:</label>
<input type="text" id="configUrl" placeholder="https://api.example.com/content" value="https://api.example.com/content">
</div>
<div class="input-group">
<label for="configTime">Schedule Time:</label>
<input type="time" id="configTime" value="09:00">
</div>
<div class="input-group">
<label for="configRetryCount">Retry Count:</label>
<input type="number" id="configRetryCount" value="3" min="0" max="10">
</div>
<div class="grid">
<button class="button" onclick="configurePlugin()">Configure Plugin</button> <button class="button" onclick="configurePlugin()">Configure Plugin</button>
<button class="button" onclick="updateSettings()">Update Settings</button> <button class="button" onclick="requestPermissions()">Request Permissions</button>
</div> <button class="button" onclick="testNotification()">Test Notification</button>
</div> <button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
<!-- Advanced Features Section --> <div id="status" class="status">
<div class="section"> Ready to test...
<h2>🚀 Advanced Features</h2>
<div class="grid">
<button class="button" onclick="getExactAlarmStatus()">Exact Alarm Status</button>
<button class="button" onclick="getRebootRecoveryStatus()">Reboot Recovery</button>
<button class="button" onclick="getRollingWindowStats()">Rolling Window</button>
<button class="button" onclick="maintainRollingWindow()">Maintain Window</button>
<button class="button" onclick="getContentCache()">Content Cache</button>
<button class="button" onclick="clearContentCache()">Clear Cache</button>
<button class="button" onclick="getContentHistory()">Content History</button>
<button class="button" onclick="getDualScheduleStatus()">Dual Schedule</button>
</div>
</div> </div>
</div> </div>
<script> <script>
console.log('🔔 DailyNotification Plugin Test Interface Loading...'); console.log('Script loading...');
console.log('JavaScript is working!');
// Global variables // Use real DailyNotification plugin
let plugin = null; console.log('Using real DailyNotification plugin...');
let isPluginAvailable = false; window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
// Initialize plugin on page load // Define functions immediately and attach to window
document.addEventListener('DOMContentLoaded', async function() {
console.log('📱 DOM loaded, initializing plugin...'); function configurePlugin() {
await initializePlugin(); console.log('configurePlugin called');
}); const status = document.getElementById('status');
const configStatus = document.getElementById('configStatus');
const fetcherStatus = document.getElementById('fetcherStatus');
status.innerHTML = 'Configuring plugin...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
// Update top status to show configuring
configStatus.innerHTML = '⏳ Configuring...';
fetcherStatus.innerHTML = '⏳ Waiting...';
// Initialize the real DailyNotification plugin
async function initializePlugin() {
try { try {
// Try to access the real plugin through Capacitor if (!window.DailyNotification) {
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DailyNotification) { status.innerHTML = 'DailyNotification plugin not available';
plugin = window.Capacitor.Plugins.DailyNotification; status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
isPluginAvailable = true; configStatus.innerHTML = '❌ Plugin unavailable';
console.log('✅ Real DailyNotification plugin found!'); fetcherStatus.innerHTML = '❌ Plugin unavailable';
updateStatus('success', '✅ Real DailyNotification plugin loaded successfully!'); return;
} else {
// Fallback to mock for development
console.log('⚠️ Real plugin not available, using mock for development');
plugin = createMockPlugin();
isPluginAvailable = false;
updateStatus('warning', '⚠️ Using mock plugin (real plugin not available)');
} }
// Configure plugin settings
window.DailyNotification.configure({
storage: 'tiered',
ttlSeconds: 86400,
prefetchLeadMinutes: 60,
maxNotificationsPerDay: 3,
retentionDays: 7
})
.then(() => {
console.log('Plugin settings configured, now configuring native fetcher...');
// Update top status
configStatus.innerHTML = '✅ Configured';
// Configure native fetcher with demo credentials
// Note: DemoNativeFetcher uses hardcoded mock data, so this is optional
// but demonstrates the API. In production, this would be real credentials.
return window.DailyNotification.configureNativeFetcher({
apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
activeDid: 'did:ethr:0xDEMO1234567890', // Demo DID
jwtSecret: 'demo-jwt-secret-for-development-testing'
});
})
.then(() => {
// Update top status
fetcherStatus.innerHTML = '✅ Configured';
// Update bottom status for user feedback
status.innerHTML = 'Plugin configured successfully!';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
})
.catch(error => {
// Update top status with error
if (configStatus.innerHTML.includes('Configuring')) {
configStatus.innerHTML = '❌ Failed';
}
if (fetcherStatus.innerHTML.includes('Waiting') || fetcherStatus.innerHTML.includes('Configuring')) {
fetcherStatus.innerHTML = '❌ Failed';
}
status.innerHTML = `Configuration failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
console.error('❌ Plugin initialization failed:', error); configStatus.innerHTML = '❌ Error';
updateStatus('error', `❌ Plugin initialization failed: ${error.message}`); fetcherStatus.innerHTML = '❌ Error';
status.innerHTML = `Configuration failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Create mock plugin for development/testing function loadPluginStatus() {
function createMockPlugin() { console.log('loadPluginStatus called');
return { const pluginStatusContent = document.getElementById('pluginStatusContent');
configure: async (options) => { const statusCard = document.getElementById('statusCard');
console.log('Mock configure called with:', options);
return Promise.resolve(); try {
}, if (!window.DailyNotification) {
getNotificationStatus: async () => { pluginStatusContent.innerHTML = '❌ DailyNotification plugin not available';
return Promise.resolve({ statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
isEnabled: true, return;
isScheduled: true,
lastNotificationTime: Date.now() - 86400000,
nextNotificationTime: Date.now() + 3600000,
pending: 1,
settings: { url: 'https://api.example.com/content', time: '09:00' },
error: null
});
},
checkPermissions: async () => {
return Promise.resolve({
notifications: 'granted',
backgroundRefresh: 'granted',
alert: true,
badge: true,
sound: true
});
},
requestPermissions: async () => {
return Promise.resolve({
notifications: 'granted',
backgroundRefresh: 'granted',
alert: true,
badge: true,
sound: true
});
},
scheduleDailyNotification: async (options) => {
console.log('Mock scheduleDailyNotification called with:', options);
return Promise.resolve();
},
cancelAllNotifications: async () => {
console.log('Mock cancelAllNotifications called');
return Promise.resolve();
},
getLastNotification: async () => {
return Promise.resolve({
id: 'mock-123',
title: 'Mock Notification',
body: 'This is a mock notification',
timestamp: Date.now() - 3600000,
url: 'https://example.com'
});
},
getBatteryStatus: async () => {
return Promise.resolve({
level: 85,
isCharging: false,
powerState: 1,
isOptimizationExempt: false
});
},
getExactAlarmStatus: async () => {
return Promise.resolve({
supported: true,
enabled: true,
canSchedule: true,
fallbackWindow: '±15 minutes'
});
},
requestExactAlarmPermission: async () => {
console.log('Mock requestExactAlarmPermission called');
return Promise.resolve();
},
openExactAlarmSettings: async () => {
console.log('Mock openExactAlarmSettings called');
return Promise.resolve();
},
requestBatteryOptimizationExemption: async () => {
console.log('Mock requestBatteryOptimizationExemption called');
return Promise.resolve();
},
getRebootRecoveryStatus: async () => {
return Promise.resolve({
inProgress: false,
lastRecoveryTime: Date.now() - 86400000,
timeSinceLastRecovery: 86400000,
recoveryNeeded: false
});
},
getRollingWindowStats: async () => {
return Promise.resolve({
stats: 'Window: 7 days, Notifications: 5, Success rate: 100%',
maintenanceNeeded: false,
timeUntilNextMaintenance: 3600000
});
},
maintainRollingWindow: async () => {
console.log('Mock maintainRollingWindow called');
return Promise.resolve();
},
getContentCache: async () => {
return Promise.resolve({
'cache-key-1': { content: 'Mock cached content', timestamp: Date.now() },
'cache-key-2': { content: 'Another mock item', timestamp: Date.now() - 3600000 }
});
},
clearContentCache: async () => {
console.log('Mock clearContentCache called');
return Promise.resolve();
},
getContentHistory: async () => {
return Promise.resolve([
{ id: '1', timestamp: Date.now() - 86400000, success: true, content: 'Mock content 1' },
{ id: '2', timestamp: Date.now() - 172800000, success: true, content: 'Mock content 2' }
]);
},
getDualScheduleStatus: async () => {
return Promise.resolve({
isActive: true,
contentSchedule: { nextRun: Date.now() + 3600000, isEnabled: true },
userSchedule: { nextRun: Date.now() + 7200000, isEnabled: true },
lastContentFetch: Date.now() - 3600000,
lastUserNotification: Date.now() - 7200000
});
} }
}; window.DailyNotification.getNotificationStatus()
.then(result => {
const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled';
const hasSchedules = result.isEnabled || (result.pending && result.pending > 0);
const statusIcon = hasSchedules ? '✅' : '⏸️';
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
📅 Next Notification: ${nextTime}<br>
⏳ Pending: ${result.pending || 0}`;
statusCard.style.background = hasSchedules ?
'rgba(0, 255, 0, 0.15)' : 'rgba(255, 255, 255, 0.1)'; // Green if active, light gray if none
})
.catch(error => {
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) {
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
}
} }
// Utility function to update status display // Notification test functions
function updateStatus(type, message) { function testNotification() {
const statusEl = document.getElementById('status'); console.log('testNotification called');
statusEl.className = `status ${type}`;
statusEl.textContent = message; // Quick sanity check - test plugin availability
console.log(`[${type.toUpperCase()}] ${message}`); if (window.Capacitor && window.Capacitor.isPluginAvailable) {
} const isAvailable = window.Capacitor.isPluginAvailable('DailyNotification');
console.log('is plugin available?', isAvailable);
}
const status = document.getElementById('status');
status.innerHTML = 'Testing plugin connection...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
// Plugin availability check
async function checkPluginAvailability() {
updateStatus('info', '🔍 Checking plugin availability...');
try { try {
if (plugin) { if (!window.DailyNotification) {
updateStatus('success', `✅ Plugin available: ${isPluginAvailable ? 'Real plugin' : 'Mock plugin'}`); status.innerHTML = 'DailyNotification plugin not available';
} else { status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
updateStatus('error', '❌ Plugin not available'); return;
} }
} catch (error) {
updateStatus('error', `❌ Availability check failed: ${error.message}`);
}
}
// Get notification status // Test the notification method directly
async function getNotificationStatus() { console.log('Testing notification scheduling...');
updateStatus('info', '📊 Getting notification status...');
try {
const status = await plugin.getNotificationStatus();
updateStatus('success', `📊 Status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Status check failed: ${error.message}`);
}
}
// Check permissions
async function checkPermissions() {
updateStatus('info', '🔐 Checking permissions...');
try {
const permissions = await plugin.checkPermissions();
updateStatus('success', `🔐 Permissions: ${JSON.stringify(permissions, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Permission check failed: ${error.message}`);
}
}
// Request permissions
async function requestPermissions() {
updateStatus('info', '🔐 Requesting permissions...');
try {
const result = await plugin.requestPermissions();
updateStatus('success', `🔐 Permission result: ${JSON.stringify(result, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Permission request failed: ${error.message}`);
}
}
// Get battery status
async function getBatteryStatus() {
updateStatus('info', '🔋 Getting battery status...');
try {
const battery = await plugin.getBatteryStatus();
updateStatus('success', `🔋 Battery: ${JSON.stringify(battery, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Battery check failed: ${error.message}`);
}
}
// Schedule notification
async function scheduleNotification() {
updateStatus('info', '⏰ Scheduling notification...');
try {
const timeInput = document.getElementById('notificationTime').value;
const [hours, minutes] = timeInput.split(':');
const now = new Date(); const now = new Date();
const scheduledTime = new Date(); const notificationTime = new Date(now.getTime() + 240000); // 4 minutes from now
scheduledTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); const prefetchTime = new Date(now.getTime() + 120000); // 2 minutes from now (2 min before notification)
const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' +
// If scheduled time is in the past, schedule for tomorrow notificationTime.getMinutes().toString().padStart(2, '0');
if (scheduledTime <= now) {
scheduledTime.setDate(scheduledTime.getDate() + 1);
}
// Calculate prefetch time (2 minutes before notification)
const prefetchTime = new Date(scheduledTime.getTime() - 120000); // 2 minutes
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
const notificationTimeReadable = scheduledTime.toLocaleTimeString();
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' + const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
prefetchTime.getMinutes().toString().padStart(2, '0'); prefetchTime.getMinutes().toString().padStart(2, '0');
const notificationTimeString = scheduledTime.getHours().toString().padStart(2, '0') + ':' +
scheduledTime.getMinutes().toString().padStart(2, '0');
const options = { window.DailyNotification.scheduleDailyNotification({
url: document.getElementById('notificationUrl').value, time: notificationTimeString,
time: timeInput, title: 'Test Notification',
title: document.getElementById('notificationTitle').value, body: 'This is a test notification from the DailyNotification plugin!',
body: document.getElementById('notificationBody').value,
sound: true, sound: true,
priority: 'high' priority: 'high'
}; })
await plugin.scheduleDailyNotification(options); .then(() => {
updateStatus('success', `✅ Notification scheduled!<br>` + const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
`📥 Prefetch: ${prefetchTimeReadable} (${prefetchTimeString})<br>` + const notificationTimeReadable = notificationTime.toLocaleTimeString();
`🔔 Notification: ${notificationTimeReadable} (${notificationTimeString})`); status.innerHTML = '✅ Notification scheduled!<br>' +
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
// Refresh plugin status display
setTimeout(() => loadPluginStatus(), 500);
})
.catch(error => {
// Check if this is an exact alarm permission error
if (error.code === 'EXACT_ALARM_PERMISSION_REQUIRED' ||
error.message.includes('Exact alarm permission') ||
error.message.includes('Alarms & reminders')) {
status.innerHTML = '⚠️ Exact Alarm Permission Required<br><br>' +
'Settings opened automatically.<br>' +
'Please enable "Allow exact alarms" and return to try again.';
status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background
} else {
status.innerHTML = `❌ Notification failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
}
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Scheduling failed: ${error.message}`); status.innerHTML = `Notification test failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Cancel all notifications
async function cancelAllNotifications() { // Permission management functions
updateStatus('info', '❌ Cancelling all notifications...'); function requestPermissions() {
console.log('requestPermissions called');
const status = document.getElementById('status');
status.innerHTML = 'Requesting permissions...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
try { try {
await plugin.cancelAllNotifications(); if (!window.DailyNotification) {
updateStatus('success', '❌ All notifications cancelled'); status.innerHTML = 'DailyNotification plugin not available';
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
return;
}
window.DailyNotification.requestNotificationPermissions()
.then(() => {
status.innerHTML = 'Permission request completed! Check your device settings if needed.';
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
// Refresh permission and channel status display after request
setTimeout(() => {
loadPermissionStatus();
loadChannelStatus();
}, 1000);
})
.catch(error => {
status.innerHTML = `Permission request failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Cancel failed: ${error.message}`); status.innerHTML = `Permission request failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Get last notification function loadChannelStatus() {
async function getLastNotification() { const channelStatus = document.getElementById('channelStatus');
updateStatus('info', '📱 Getting last notification...');
try { try {
const notification = await plugin.getLastNotification(); if (!window.DailyNotification) {
updateStatus('success', `📱 Last notification: ${JSON.stringify(notification, null, 2)}`); channelStatus.innerHTML = '❌ Plugin unavailable';
return;
}
window.DailyNotification.isChannelEnabled()
.then(result => {
const importanceText = getImportanceText(result.importance);
if (result.enabled) {
channelStatus.innerHTML = `✅ Enabled (${importanceText})`;
} else {
channelStatus.innerHTML = `❌ Disabled (${importanceText})`;
}
})
.catch(error => {
channelStatus.innerHTML = '⚠️ Error';
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Get last notification failed: ${error.message}`); channelStatus.innerHTML = '⚠️ Error';
} }
} }
// Configure plugin function checkComprehensiveStatus() {
async function configurePlugin() { const status = document.getElementById('status');
updateStatus('info', '⚙️ Configuring plugin...'); status.innerHTML = 'Checking comprehensive status...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
try { try {
const config = { if (!window.DailyNotification) {
fetchUrl: document.getElementById('configUrl').value, status.innerHTML = 'DailyNotification plugin not available';
scheduleTime: document.getElementById('configTime').value, status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
retryCount: parseInt(document.getElementById('configRetryCount').value), return;
enableNotifications: true, }
offlineFallback: true
}; window.DailyNotification.checkStatus()
await plugin.configure(config); .then(result => {
updateStatus('success', `⚙️ Plugin configured: ${JSON.stringify(config, null, 2)}`); const canSchedule = result.canScheduleNow;
const issues = [];
if (!result.postNotificationsGranted) {
issues.push('POST_NOTIFICATIONS permission');
}
if (!result.channelEnabled) {
issues.push('notification channel disabled');
}
if (!result.exactAlarmsGranted) {
issues.push('exact alarm permission');
}
let statusText = `Status: ${canSchedule ? 'Ready to schedule' : 'Issues found'}`;
if (issues.length > 0) {
statusText += `\nIssues: ${issues.join(', ')}`;
}
statusText += `\nChannel: ${getImportanceText(result.channelImportance)}`;
statusText += `\nChannel ID: ${result.channelId}`;
status.innerHTML = statusText;
status.style.background = canSchedule ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
})
.catch(error => {
status.innerHTML = `Status check failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Configuration failed: ${error.message}`); status.innerHTML = `Status check failed: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
} }
} }
// Update settings function getImportanceText(importance) {
async function updateSettings() { switch (importance) {
updateStatus('info', '⚙️ Updating settings...'); case 0: return 'None (blocked)';
case 1: return 'Min';
case 2: return 'Low';
case 3: return 'Default';
case 4: return 'High';
case 5: return 'Max';
default: return `Unknown (${importance})`;
}
}
// Attach to window object
window.configurePlugin = configurePlugin;
window.testNotification = testNotification;
window.requestPermissions = requestPermissions;
window.checkComprehensiveStatus = checkComprehensiveStatus;
function loadPermissionStatus() {
const notificationPermStatus = document.getElementById('notificationPermStatus');
const exactAlarmPermStatus = document.getElementById('exactAlarmPermStatus');
try { try {
const settings = { if (!window.DailyNotification) {
url: document.getElementById('configUrl').value, notificationPermStatus.innerHTML = '❌ Plugin unavailable';
time: document.getElementById('configTime').value, exactAlarmPermStatus.innerHTML = '❌ Plugin unavailable';
retryCount: parseInt(document.getElementById('configRetryCount').value), return;
sound: true, }
priority: 'high'
}; window.DailyNotification.checkPermissionStatus()
await plugin.updateSettings(settings); .then(result => {
updateStatus('success', `⚙️ Settings updated: ${JSON.stringify(settings, null, 2)}`); notificationPermStatus.innerHTML = result.notificationsEnabled ? '✅ Granted' : '❌ Not granted';
exactAlarmPermStatus.innerHTML = result.exactAlarmEnabled ? '✅ Granted' : '❌ Not granted';
})
.catch(error => {
notificationPermStatus.innerHTML = '⚠️ Error';
exactAlarmPermStatus.innerHTML = '⚠️ Error';
});
} catch (error) { } catch (error) {
updateStatus('error', `❌ Settings update failed: ${error.message}`); notificationPermStatus.innerHTML = '⚠️ Error';
exactAlarmPermStatus.innerHTML = '⚠️ Error';
} }
} }
// Get exact alarm status // Load plugin status automatically on page load
async function getExactAlarmStatus() { window.addEventListener('load', () => {
updateStatus('info', '⏰ Getting exact alarm status...'); console.log('Page loaded, loading plugin status...');
try { // Small delay to ensure Capacitor is ready
const status = await plugin.getExactAlarmStatus(); setTimeout(() => {
updateStatus('success', `⏰ Exact alarm status: ${JSON.stringify(status, null, 2)}`); loadPluginStatus();
} catch (error) { loadPermissionStatus();
updateStatus('error', `❌ Exact alarm check failed: ${error.message}`); loadChannelStatus();
} }, 500);
} });
// Request exact alarm permission
async function requestExactAlarmPermission() {
updateStatus('info', '⏰ Requesting exact alarm permission...');
try {
await plugin.requestExactAlarmPermission();
updateStatus('success', '⏰ Exact alarm permission requested');
} catch (error) {
updateStatus('error', `❌ Exact alarm permission request failed: ${error.message}`);
}
}
// Open exact alarm settings
async function openExactAlarmSettings() {
updateStatus('info', '⚙️ Opening exact alarm settings...');
try {
await plugin.openExactAlarmSettings();
updateStatus('success', '⚙️ Exact alarm settings opened');
} catch (error) {
updateStatus('error', `❌ Open settings failed: ${error.message}`);
}
}
// Request battery optimization exemption console.log('Functions attached to window:', {
async function requestBatteryOptimizationExemption() { configurePlugin: typeof window.configurePlugin,
updateStatus('info', '🔋 Requesting battery optimization exemption...'); testNotification: typeof window.testNotification
try { });
await plugin.requestBatteryOptimizationExemption();
updateStatus('success', '🔋 Battery optimization exemption requested');
} catch (error) {
updateStatus('error', `❌ Battery exemption request failed: ${error.message}`);
}
}
// Get reboot recovery status
async function getRebootRecoveryStatus() {
updateStatus('info', '🔄 Getting reboot recovery status...');
try {
const status = await plugin.getRebootRecoveryStatus();
updateStatus('success', `🔄 Reboot recovery status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Reboot recovery check failed: ${error.message}`);
}
}
// Get rolling window stats
async function getRollingWindowStats() {
updateStatus('info', '📊 Getting rolling window stats...');
try {
const stats = await plugin.getRollingWindowStats();
updateStatus('success', `📊 Rolling window stats: ${JSON.stringify(stats, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Rolling window stats failed: ${error.message}`);
}
}
// Maintain rolling window
async function maintainRollingWindow() {
updateStatus('info', '🔧 Maintaining rolling window...');
try {
await plugin.maintainRollingWindow();
updateStatus('success', '🔧 Rolling window maintenance completed');
} catch (error) {
updateStatus('error', `❌ Rolling window maintenance failed: ${error.message}`);
}
}
// Get content cache
async function getContentCache() {
updateStatus('info', '💾 Getting content cache...');
try {
const cache = await plugin.getContentCache();
updateStatus('success', `💾 Content cache: ${JSON.stringify(cache, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Content cache check failed: ${error.message}`);
}
}
// Clear content cache
async function clearContentCache() {
updateStatus('info', '🗑️ Clearing content cache...');
try {
await plugin.clearContentCache();
updateStatus('success', '🗑️ Content cache cleared');
} catch (error) {
updateStatus('error', `❌ Clear cache failed: ${error.message}`);
}
}
// Get content history
async function getContentHistory() {
updateStatus('info', '📚 Getting content history...');
try {
const history = await plugin.getContentHistory();
updateStatus('success', `📚 Content history: ${JSON.stringify(history, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Content history check failed: ${error.message}`);
}
}
// Get dual schedule status
async function getDualScheduleStatus() {
updateStatus('info', '🔄 Getting dual schedule status...');
try {
const status = await plugin.getDualScheduleStatus();
updateStatus('success', `🔄 Dual schedule status: ${JSON.stringify(status, null, 2)}`);
} catch (error) {
updateStatus('error', `❌ Dual schedule check failed: ${error.message}`);
}
}
console.log('🔔 DailyNotification Plugin Test Interface Loaded Successfully!');
</script> </script>
</body> </body>
</html> </html>