diff --git a/ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift b/ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift index 6790046..67ea18d 100644 --- a/ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift +++ b/ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift @@ -46,16 +46,16 @@ class DailyNotificationBackgroundTaskTestHarness { // MARK: - Structured Logging - /// Swift Logger categories for structured logging - static let pluginLogger = Logger(subsystem: "com.timesafari.dailynotification", category: "plugin") - static let fetchLogger = Logger(subsystem: "com.timesafari.dailynotification", category: "fetch") - static let schedulerLogger = Logger(subsystem: "com.timesafari.dailynotification", category: "scheduler") - static let storageLogger = Logger(subsystem: "com.timesafari.dailynotification", category: "storage") + /// OSLog categories for structured logging (iOS 13.0+ compatible) + private static let pluginLog = OSLog(subsystem: "com.timesafari.dailynotification", category: "plugin") + private static let fetchLog = OSLog(subsystem: "com.timesafari.dailynotification", category: "fetch") + private static let schedulerLog = OSLog(subsystem: "com.timesafari.dailynotification", category: "scheduler") + private static let storageLog = OSLog(subsystem: "com.timesafari.dailynotification", category: "storage") /// Log telemetry snapshot for validation static func logTelemetrySnapshot(prefix: String = "DNP-") { // Phase 2: Capture telemetry counters from structured logs - fetchLogger.info("Telemetry snapshot: \(prefix)prefetch_scheduled_total, \(prefix)prefetch_executed_total, \(prefix)prefetch_success_total") + os_log("[DNP-FETCH] Telemetry snapshot: %{public}@prefetch_scheduled_total, %{public}@prefetch_executed_total, %{public}@prefetch_success_total", log: fetchLog, type: .info, prefix, prefix, prefix) } // MARK: - Registration @@ -106,16 +106,16 @@ class DailyNotificationBackgroundTaskTestHarness { do { try BGTaskScheduler.shared.submit(request) - fetchLogger.info("[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=\(String(describing: request.earliestBeginDate)))") + os_log("[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=%{public}@)", log: fetchLog, type: .info, String(describing: request.earliestBeginDate)) return true } catch { // Handle simulator limitation (Code=1 is expected on simulator) if let nsError = error as NSError?, nsError.domain == "BGTaskSchedulerErrorDomain", nsError.code == 1 { - fetchLogger.warning("[DNP-FETCH] BGTask scheduling failed on simulator (expected): Code=1 notPermitted") + os_log("[DNP-FETCH] BGTask scheduling failed on simulator (expected): Code=1 notPermitted", log: fetchLog, type: .default) print("[DNP-FETCH] NOTE: BGTaskScheduler doesn't work on simulator - this is expected. Use Xcode β†’ Debug β†’ Simulate Background Fetch for testing.") return false } - fetchLogger.error("[DNP-FETCH] Failed to schedule BGAppRefreshTask: \(error.localizedDescription)") + os_log("[DNP-FETCH] Failed to schedule BGAppRefreshTask: %{public}@", log: fetchLog, type: .error, error.localizedDescription) print("[DNP-FETCH] Failed to schedule BGAppRefreshTask: \(error)") return false } @@ -130,7 +130,7 @@ class DailyNotificationBackgroundTaskTestHarness { // In real implementation, check if this request matches the notificationId // For now, cancel all prefetch tasks (Phase 1: single notification) BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: prefetchTaskIdentifier) - fetchLogger.info("[DNP-FETCH] Cancelled existing pending task for notificationId=\(notificationId)") + os_log("[DNP-FETCH] Cancelled existing pending task for notificationId=%{public}@", log: fetchLog, type: .info, notificationId) } } } @@ -149,7 +149,7 @@ class DailyNotificationBackgroundTaskTestHarness { semaphore.wait() if pendingCount > 1 { - fetchLogger.warning("[DNP-FETCH] WARNING: Multiple pending tasks detected (\(pendingCount)) - expected 1") + os_log("[DNP-FETCH] WARNING: Multiple pending tasks detected (%d) - expected 1", log: fetchLog, type: .default, pendingCount) return false } @@ -203,7 +203,7 @@ class DailyNotificationBackgroundTaskTestHarness { /// This is called by the system when the background task is launched. /// Replace PrefetchOperation with your actual prefetch logic. private static func handlePrefetchTask(task: BGAppRefreshTask) { - fetchLogger.info("[DNP-FETCH] BGTask handler invoked (task.identifier=\(task.identifier))") + os_log("[DNP-FETCH] BGTask handler invoked (task.identifier=%{public}@)", log: fetchLog, type: .info, task.identifier) // STEP 1: Schedule the next task IMMEDIATELY (Apple best practice) // This ensures continuity even if app is terminated shortly after @@ -219,14 +219,14 @@ class DailyNotificationBackgroundTaskTestHarness { // STEP 4: Set expiration handler (called if iOS terminates task early, ~30 seconds) task.expirationHandler = { - fetchLogger.warning("[DNP-FETCH] Task expired - cancelling operations") + os_log("[DNP-FETCH] Task expired - cancelling operations", log: fetchLog, type: .default) operation.cancel() // Ensure task is marked complete even on expiration if !taskCompleted { taskCompleted = true task.setTaskCompleted(success: false) - fetchLogger.info("[DNP-FETCH] Task marked complete (success=false) due to expiration") + os_log("[DNP-FETCH] Task marked complete (success=false) due to expiration", log: fetchLog, type: .info) } } @@ -239,9 +239,9 @@ class DailyNotificationBackgroundTaskTestHarness { if !taskCompleted { taskCompleted = true task.setTaskCompleted(success: success) - fetchLogger.info("[DNP-FETCH] Task marked complete (success=\(success))") + os_log("[DNP-FETCH] Task marked complete (success=%{public}@)", log: fetchLog, type: .info, success ? "true" : "false") } else { - fetchLogger.warning("[DNP-FETCH] WARNING: Attempted to complete task twice") + os_log("[DNP-FETCH] WARNING: Attempted to complete task twice", log: fetchLog, type: .default) } } @@ -262,12 +262,12 @@ class DailyNotificationBackgroundTaskTestHarness { class PrefetchOperation: Operation { var isFailed = false - private let fetchLogger = Logger(subsystem: "com.timesafari.dailynotification", category: "fetch") + private static let fetchLog = OSLog(subsystem: "com.timesafari.dailynotification", category: "fetch") override func main() { if isCancelled { return } - fetchLogger.info("[DNP-FETCH] PrefetchOperation: starting fetch...") + os_log("[DNP-FETCH] PrefetchOperation: starting fetch...", log: Self.fetchLog, type: .info) // Simulate some work // In real implementation, this would be: @@ -287,11 +287,11 @@ class PrefetchOperation: Operation { let success = true // Replace with actual fetch result if success { - fetchLogger.info("[DNP-FETCH] PrefetchOperation: fetch success") + os_log("[DNP-FETCH] PrefetchOperation: fetch success", log: Self.fetchLog, type: .info) // In real implementation: persist cache here before completion } else { isFailed = true - fetchLogger.error("[DNP-FETCH] PrefetchOperation: fetch failed") + os_log("[DNP-FETCH] PrefetchOperation: fetch failed", log: Self.fetchLog, type: .error) } } } diff --git a/scripts/build-ios-test-app.sh b/scripts/build-ios-test-app.sh index 0c8aadd..f332f30 100755 --- a/scripts/build-ios-test-app.sh +++ b/scripts/build-ios-test-app.sh @@ -174,12 +174,28 @@ build_ios_test_app() { fi # Sync Capacitor if needed - if command -v npx &> /dev/null && [ -f "capacitor.config.ts" ] || [ -f "capacitor.config.json" ]; then + # 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 diff --git a/test-apps/android-test-app/app/src/main/assets/public/index.html b/test-apps/android-test-app/app/src/main/assets/public/index.html index 99d4950..48908a4 100644 --- a/test-apps/android-test-app/app/src/main/assets/public/index.html +++ b/test-apps/android-test-app/app/src/main/assets/public/index.html @@ -17,417 +17,627 @@ color: white; } .container { - max-width: 600px; + max-width: 800px; margin: 0 auto; - text-align: center; } h1 { + text-align: center; margin-bottom: 30px; 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 { background: rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.3); color: white; - padding: 15px 30px; - margin: 10px; - border-radius: 25px; + padding: 12px 24px; + margin: 8px; + border-radius: 20px; cursor: pointer; - font-size: 16px; + font-size: 14px; transition: all 0.3s ease; + display: inline-block; } .button:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-2px); } + .button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } .status { - margin-top: 30px; - padding: 20px; - background: rgba(255, 255, 255, 0.1); - border-radius: 10px; + margin-top: 15px; + padding: 15px; + 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); + color: white; + } + .input-group input::placeholder { + color: rgba(255, 255, 255, 0.7); }
-
- Plugin Status
-
- βš™οΈ Plugin Settings: Not configured
- πŸ”Œ Native Fetcher: Not configured
- πŸ”” Notifications: Checking...
- ⏰ Exact Alarms: Checking...
- πŸ“’ Channel: Checking...
-
- Loading plugin status... -
+

πŸ”” DailyNotification Plugin Test

+ + +
+

πŸ“Š Plugin Status

+
+ + + + +
+
Ready to test...
+
+ + +
+

πŸ” Permission Management

+
+ + + +
- + + +
+

⏰ Notification Scheduling

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+

βš™οΈ Plugin Configuration

+
+ + +
+
+ + +
+
+ + +
+
- - - - -
- Ready to test... + +
+
+ + +
+

πŸš€ Advanced Features

+
+ + + + + + + + +
diff --git a/test-apps/ios-test-app/App/App/Public/index.html b/test-apps/ios-test-app/App/App/Public/index.html index 20d5a78..48908a4 100644 --- a/test-apps/ios-test-app/App/App/Public/index.html +++ b/test-apps/ios-test-app/App/App/Public/index.html @@ -17,602 +17,627 @@ color: white; } .container { - max-width: 600px; + max-width: 800px; margin: 0 auto; - text-align: center; } h1 { + text-align: center; margin-bottom: 30px; 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 { background: rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.3); color: white; - padding: 15px 30px; - margin: 10px; - border-radius: 25px; + padding: 12px 24px; + margin: 8px; + border-radius: 20px; cursor: pointer; - font-size: 16px; + font-size: 14px; transition: all 0.3s ease; + display: inline-block; } .button:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-2px); } + .button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } .status { - margin-top: 30px; - padding: 20px; - background: rgba(255, 255, 255, 0.1); - border-radius: 10px; + margin-top: 15px; + padding: 15px; + 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); + color: white; + } + .input-group input::placeholder { + color: rgba(255, 255, 255, 0.7); }
-

πŸ”” DailyNotification Plugin Test

-

Test the DailyNotification plugin functionality

-

Build: 2025-10-14 05:00:00 UTC

+

πŸ”” DailyNotification Plugin Test

- + +
+

πŸ“Š Plugin Status

+
+ + + + +
+
Ready to test...
+
+ + +
+

πŸ” Permission Management

+
+ + + + +
+
+ + +
+

⏰ Notification Scheduling

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+

βš™οΈ Plugin Configuration

+
+ + +
+
+ + +
+
+ + +
+
- - -

πŸ”” Notification Tests

- - - - -

πŸ” Permission Management

- - - - -

πŸ“’ Channel Management

- - - - -
- Ready to test... + +
+
+ + +
+

πŸš€ Advanced Features

+
+ + + + + + + + +