From 92bb566631ab3bc8a5d9ca9c822c9e38042da572 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 19 Nov 2025 20:09:01 -0800 Subject: [PATCH] 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. --- ios/Plugin/DailyNotificationPlugin.swift | 25 +- scripts/build-ios-test-app.sh | 144 ++- test-apps/android-test-app/app/build.gradle | 22 + .../app/src/main/assets/public/index.html | 890 +++++++----------- .../ios-test-app/App/App/Public/index.html | 890 +++++++----------- 5 files changed, 822 insertions(+), 1149 deletions(-) diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index af4a0ee..332c589 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -80,24 +80,21 @@ public class DailyNotificationPlugin: CAPPlugin { * @param call Plugin call containing configuration parameters */ @objc func configure(_ call: CAPPluginCall) { - guard let options = call.getObject("options") else { - call.reject("Configuration options required") - return - } - + // Capacitor passes the options object directly as call data + // Read parameters directly from call (matching Android implementation) print("DNP-PLUGIN: Configuring plugin with new options") do { - // Get configuration options - let dbPath = options["dbPath"] as? String - let storageMode = options["storage"] as? String ?? "tiered" - let ttlSeconds = options["ttlSeconds"] as? Int - let prefetchLeadMinutes = options["prefetchLeadMinutes"] as? Int - let maxNotificationsPerDay = options["maxNotificationsPerDay"] as? Int - let retentionDays = options["retentionDays"] as? Int + // Get configuration options directly from call (matching Android) + let dbPath = call.getString("dbPath") + let storageMode = call.getString("storage") ?? "tiered" + let ttlSeconds = call.getInt("ttlSeconds") + let prefetchLeadMinutes = call.getInt("prefetchLeadMinutes") + let maxNotificationsPerDay = call.getInt("maxNotificationsPerDay") + let retentionDays = call.getInt("retentionDays") // 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)") // TODO: Implement activeDidIntegration configuration in Phase 3 } @@ -1486,7 +1483,7 @@ public class DailyNotificationPlugin: CAPPlugin { var methods: [CAPPluginMethod] = [] // 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: "getLastNotification", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise)) diff --git a/scripts/build-ios-test-app.sh b/scripts/build-ios-test-app.sh index f332f30..dae282a 100755 --- a/scripts/build-ios-test-app.sh +++ b/scripts/build-ios-test-app.sh @@ -126,6 +126,9 @@ fi build_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) IOS_APP_DIR="$TEST_APP_DIR/ios/App" if [ ! -d "$IOS_APP_DIR" ]; then @@ -162,6 +165,61 @@ build_ios_test_app() { log_warn "No Podfile found, skipping pod install" 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 if [ -f "package.json" ]; then log_step "Building web assets..." @@ -172,32 +230,6 @@ build_ios_test_app() { fi log_info "Web assets built" 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 # Determine SDK and destination @@ -244,7 +276,16 @@ build_ios_test_app() { ARCHIVE_PATH="build/ios-test-app-device.xcarchive" 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..." if [ -n "$WORKSPACE" ]; then 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 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 log_step "Building for $TARGET ($BUILD_CONFIG)..." if [ -n "$WORKSPACE" ]; then @@ -292,6 +342,17 @@ build_ios_test_app() { if [ -n "$APP_PATH" ]; then 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 "" # Boot simulator if not already booted @@ -363,6 +424,19 @@ build_ios_test_app() { 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 log_step "Installing app on simulator..." if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then @@ -383,9 +457,9 @@ build_ios_test_app() { sleep 2 # 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..." - 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=$? if [ $LAUNCH_EXIT_CODE -eq 0 ]; then @@ -410,19 +484,19 @@ build_ios_test_app() { if [ "$LAUNCH_SUCCESS" = false ]; then log_info "Checking if app is already running..." 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 log_info "App appears to be installed. Trying to verify it's running..." # 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 log_info "App found in simulator. Attempting manual launch..." # Try opening via Simulator app open -a Simulator sleep 1 # 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 log_info "✅ App launched successfully on retry!" fi @@ -434,7 +508,7 @@ build_ios_test_app() { if [ "$LAUNCH_SUCCESS" = true ]; then sleep 2 # 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" else 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 " 1. Open Simulator app (if not already open)" 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 log_info "" log_info "Launch error details:" @@ -460,7 +534,7 @@ build_ios_test_app() { log_info "" log_info "To run on simulator manually:" 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 else log_warn "Could not find built app in DerivedData" diff --git a/test-apps/android-test-app/app/build.gradle b/test-apps/android-test-app/app/build.gradle index 9cb7f2c..4d9e2ca 100644 --- a/test-apps/android-test-app/app/build.gradle +++ b/test-apps/android-test-app/app/build.gradle @@ -52,6 +52,28 @@ dependencies { 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 { def servicesJSON = file('google-services.json') if (servicesJSON.text) { 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 48908a4..99d4950 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,627 +17,417 @@ color: white; } .container { - max-width: 800px; + max-width: 600px; 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: 12px 24px; - margin: 8px; - border-radius: 20px; + padding: 15px 30px; + margin: 10px; + border-radius: 25px; cursor: pointer; - font-size: 14px; + font-size: 16px; 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: 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); + margin-top: 30px; + padding: 20px; background: rgba(255, 255, 255, 0.1); - color: white; - } - .input-group input::placeholder { - color: rgba(255, 255, 255, 0.7); + border-radius: 10px; + font-family: monospace; }
-

🔔 DailyNotification Plugin Test

+
+ Plugin Status
+
+ ⚙️ Plugin Settings: Not configured
+ 🔌 Native Fetcher: Not configured
+ 🔔 Notifications: Checking...
+ ⏰ Exact Alarms: Checking...
+ 📢 Channel: Checking...
+
+ Loading plugin status... +
+
+
- -
-

📊 Plugin Status

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

🔐 Permission Management

-
- - - - -
-
- - -
-

⏰ Notification Scheduling

-
- - -
-
- - -
-
- - -
-
- - -
-
- - - -
-
- - -
-

⚙️ Plugin Configuration

-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-

🚀 Advanced Features

-
- - - - - - - - -
+ + + + +
+ Ready to test...
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 48908a4..99d4950 100644 --- a/test-apps/ios-test-app/App/App/Public/index.html +++ b/test-apps/ios-test-app/App/App/Public/index.html @@ -17,627 +17,417 @@ color: white; } .container { - max-width: 800px; + max-width: 600px; 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: 12px 24px; - margin: 8px; - border-radius: 20px; + padding: 15px 30px; + margin: 10px; + border-radius: 25px; cursor: pointer; - font-size: 14px; + font-size: 16px; 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: 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); + margin-top: 30px; + padding: 20px; background: rgba(255, 255, 255, 0.1); - color: white; - } - .input-group input::placeholder { - color: rgba(255, 255, 255, 0.7); + border-radius: 10px; + font-family: monospace; }
-

🔔 DailyNotification Plugin Test

+
+ Plugin Status
+
+ ⚙️ Plugin Settings: Not configured
+ 🔌 Native Fetcher: Not configured
+ 🔔 Notifications: Checking...
+ ⏰ Exact Alarms: Checking...
+ 📢 Channel: Checking...
+
+ Loading plugin status... +
+
+
- -
-

📊 Plugin Status

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

🔐 Permission Management

-
- - - - -
-
- - -
-

⏰ Notification Scheduling

-
- - -
-
- - -
-
- - -
-
- - -
-
- - - -
-
- - -
-

⚙️ Plugin Configuration

-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-

🚀 Advanced Features

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