diff --git a/BUILDING.md b/BUILDING.md index 7446379..d80d702 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -361,12 +361,16 @@ npm install # Build Vue 3 app npm run build -# Add Capacitor -npm install @capacitor/android +# Add Capacitor platforms +npm install @capacitor/android @capacitor/ios # Sync with Capacitor npx cap sync android +# For iOS: Use the npm script (handles Podfile fixes automatically) +npm run cap:sync:ios +# This runs: cap copy ios + fix Podfile + pod install + # Run on Android device/emulator npx cap run android @@ -374,6 +378,149 @@ npx cap run android npx cap run ios ``` +**iOS Setup (Vue 3 Test App)** + +The iOS setup requires additional steps to configure the plugin correctly: + +**1. Install Dependencies** +```bash +cd test-apps/daily-notification-test +npm install +``` + +**2. Build Vue App** +```bash +npm run build +``` + +**3. Add iOS Platform (if not already added)** +```bash +npx cap add ios +``` + +**4. Fix Podfile Configuration** + +**Critical**: Capacitor's `npx cap sync ios` regenerates the Podfile with incorrect plugin references (`TimesafariDailyNotificationPlugin` instead of `DailyNotificationPlugin`). + +**Solution**: Use the npm script `npm run cap:sync:ios` which: +1. Copies assets without running pod install (`npx cap copy ios`) +2. Automatically fixes the Podfile +3. Then runs `pod install` with the corrected Podfile + +```bash +# Use the npm script (recommended) +npm run cap:sync:ios + +# Or manually fix after copy +npx cap copy ios +node scripts/fix-capacitor-plugins.js +cd ios/App && pod install && cd ../.. +``` + +The fix script will: +- Change `TimesafariDailyNotificationPlugin` → `DailyNotificationPlugin` +- Fix the path from `'../../../..'` → `'../../node_modules/@timesafari/daily-notification-plugin/ios'` + +**5. Install CocoaPods Dependencies** + +After the Podfile is fixed, install the iOS dependencies: + +```bash +cd ios/App +pod install +cd ../.. +``` + +**Expected Podfile Configuration:** + +The Podfile should reference the plugin like this: + +```ruby +def capacitor_pods + pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios' +end +``` + +**Important Notes:** +- The pod name must be `DailyNotificationPlugin` (not `TimesafariDailyNotificationPlugin`) +- The path must point to `../../node_modules/@timesafari/daily-notification-plugin/ios` +- The plugin must be installed in `node_modules` via `npm install` (it's installed as a local file dependency) + +**6. Sync and Build** + +**Important**: `npx cap sync ios` tries to run `pod install` automatically, but it will fail because the Podfile has incorrect plugin references. Use the npm script instead: + +```bash +# Option 1: Use the npm script (recommended - handles everything) +npm run cap:sync:ios + +# This script: +# 1. Copies web assets (npx cap copy ios) +# 2. Fixes the Podfile (node scripts/fix-capacitor-plugins.js) +# 3. Installs pods (cd ios/App && pod install) + +# Option 2: Manual steps (if you need more control) +npx cap copy ios # Copy assets without pod install +node scripts/fix-capacitor-plugins.js # Fix Podfile +cd ios/App && pod install && cd ../.. # Install pods + +# Open in Xcode +npx cap open ios +``` + +**Why this approach?** +- `npx cap sync ios` regenerates the Podfile with wrong references, then tries to run `pod install` which fails +- `npx cap copy ios` only copies files, allowing us to fix the Podfile before `pod install` +- The npm script automates the entire workflow correctly + +**Troubleshooting iOS Setup:** + +**Error: `[!] No podspec found for 'TimesafariDailyNotificationPlugin'`** + +This means the Podfile has the wrong pod name or path. Solutions: + +1. **Run the fix script:** + ```bash + node scripts/fix-capacitor-plugins.js + ``` + +2. **Manually fix the Podfile:** + - Open `ios/App/Podfile` + - Change `TimesafariDailyNotificationPlugin` to `DailyNotificationPlugin` + - Change path from `'../../../..'` to `'../../node_modules/@timesafari/daily-notification-plugin/ios'` + +3. **Verify plugin is installed:** + ```bash + ls -la node_modules/@timesafari/daily-notification-plugin/ios/DailyNotificationPlugin.podspec + ``` + +4. **Reinstall dependencies if needed:** + ```bash + rm -rf node_modules package-lock.json + npm install + ``` + +**Error: `pod install` fails** + +1. **Update CocoaPods:** + ```bash + sudo gem install cocoapods + ``` + +2. **Clean CocoaPods cache:** + ```bash + cd ios/App + rm -rf Pods Podfile.lock + pod install --repo-update + ``` + +3. **Verify Xcode Command Line Tools:** + ```bash + xcode-select --install + ``` + **Test App Features:** - Interactive plugin testing interface @@ -390,8 +537,13 @@ test-apps/daily-notification-test/ │ ├── components/ # Reusable UI components │ └── stores/ # Pinia state management ├── android/ # Android Capacitor app +├── ios/ # iOS Capacitor app +│ └── App/ +│ ├── Podfile # CocoaPods dependencies +│ └── App.xcworkspace # Xcode workspace ├── docs/ # Test app documentation └── scripts/ # Test app build scripts +│ └── fix-capacitor-plugins.js # Auto-fixes Podfile ``` #### Android Test Apps diff --git a/ios/Plugin/DailyNotificationDatabase.swift b/ios/Plugin/DailyNotificationDatabase.swift index 046aee7..7fcd234 100644 --- a/ios/Plugin/DailyNotificationDatabase.swift +++ b/ios/Plugin/DailyNotificationDatabase.swift @@ -263,12 +263,12 @@ class DailyNotificationDatabase { return } - sqlite3_bind_text(stmt, 1, (content.id as NSString).utf8String, -1, SQLITE_TRANSIENT) - sqlite3_bind_text(stmt, 2, (json as NSString).utf8String, -1, SQLITE_TRANSIENT) + sqlite3_bind_text(stmt, 1, (content.id as NSString).utf8String, -1, nil) + sqlite3_bind_text(stmt, 2, (json as NSString).utf8String, -1, nil) sqlite3_bind_int64(stmt, 3, sqlite3_int64(content.fetchedAt)) if let etag = content.etag { - sqlite3_bind_text(stmt, 4, (etag as NSString).utf8String, -1, SQLITE_TRANSIENT) + sqlite3_bind_text(stmt, 4, (etag as NSString).utf8String, -1, nil) } else { sqlite3_bind_null(stmt, 4) } @@ -310,7 +310,7 @@ class DailyNotificationDatabase { return } - sqlite3_bind_text(stmt, 1, (id as NSString).utf8String, -1, SQLITE_TRANSIENT) + sqlite3_bind_text(stmt, 1, (id as NSString).utf8String, -1, nil) if sqlite3_step(stmt) != SQLITE_DONE { print("\(Self.TAG): deleteNotificationContent step failed: \(String(cString: sqlite3_errmsg(db)))") diff --git a/ios/Plugin/DailyNotificationModel.swift b/ios/Plugin/DailyNotificationModel.swift index 623225f..ab0aef9 100644 --- a/ios/Plugin/DailyNotificationModel.swift +++ b/ios/Plugin/DailyNotificationModel.swift @@ -261,17 +261,8 @@ class PersistenceController { description?.shouldMigrateStoreAutomatically = true description?.shouldInferMappingModelAutomatically = true - // Set initial schema version metadata (for new stores) - if !inMemory { - var metadata = description?.metadata ?? [:] - if metadata["schema_version"] == nil { - metadata["schema_version"] = PersistenceController.SCHEMA_VERSION - description?.metadata = metadata - } - } - var loadError: Error? = nil - tempContainer?.loadPersistentStores { description, error in + tempContainer?.loadPersistentStores { storeDescription, error in if let error = error as NSError? { loadError = error print("DNP-PLUGIN: CoreData store load error: \(error.localizedDescription)") @@ -281,7 +272,19 @@ class PersistenceController { } } else { print("DNP-PLUGIN: CoreData store loaded successfully") - print("DNP-PLUGIN: Store URL: \(description.url?.absoluteString ?? "unknown")") + print("DNP-PLUGIN: Store URL: \(storeDescription.url?.absoluteString ?? "unknown")") + + // Set initial schema version metadata (for new stores) + // Metadata must be set using the coordinator after the store is loaded + if !inMemory, + let coordinator = tempContainer?.persistentStoreCoordinator, + let store = coordinator.persistentStores.first, + let metadata = store.metadata, + metadata["schema_version"] == nil { + var newMetadata = metadata + newMetadata["schema_version"] = PersistenceController.SCHEMA_VERSION + coordinator.setMetadata(newMetadata, for: store) + } } } @@ -373,7 +376,13 @@ class PersistenceController { return } - let currentVersion = store.metadata["schema_version"] as? Int ?? 1 + // store.metadata is optional, so we need to unwrap it + guard let metadata = store.metadata else { + print("DNP-PLUGIN: Store metadata is nil, using default schema version") + return + } + + let currentVersion = metadata["schema_version"] as? Int ?? 1 let expectedVersion = PersistenceController.SCHEMA_VERSION if currentVersion != expectedVersion { @@ -381,9 +390,13 @@ class PersistenceController { print("DNP-PLUGIN: CoreData auto-migration will handle schema changes") // Update metadata for future reference (does not trigger migration) - var metadata = store.metadata - metadata["schema_version"] = expectedVersion - // Note: Metadata persists on next store save + // Use the coordinator to set metadata + if let coordinator = container?.persistentStoreCoordinator { + var newMetadata = metadata + newMetadata["schema_version"] = expectedVersion + coordinator.setMetadata(newMetadata, for: store) + // Note: Metadata persists on next store save + } } else { print("DNP-PLUGIN: Schema version verified: \(currentVersion)") } diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index 7fc60e7..2af6473 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -419,58 +419,58 @@ public class DailyNotificationPlugin: CAPPlugin { // Phase 3: Check for JWT-signed fetcher configuration // If native fetcher is configured, use it; otherwise fall back to dummy content let nativeFetcherConfig = UserDefaults.standard.string(forKey: "native_fetcher_config") - let content: NotificationContent - if let configJson = nativeFetcherConfig, - let configData = configJson.data(using: .utf8), - let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any], - let apiBaseUrl = config["apiBaseUrl"] as? String, - let activeDid = config["activeDid"] as? String, - let jwtToken = config["jwtToken"] as? String { - // Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch - print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)") + // Save content to storage via state actor (thread-safe) + Task { + let content: NotificationContent - // Attempt to fetch content from TimeSafari API - // Note: This is a minimal implementation - can be extended with full API client - do { - let fetchedContent = try await fetchContentFromAPI( - apiBaseUrl: apiBaseUrl, - activeDid: activeDid, - jwtToken: jwtToken - ) - content = fetchedContent - print("DNP-FETCH: Successfully fetched content from API") - } catch { - // Fallback to dummy content on fetch failure - print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content") + if let configJson = nativeFetcherConfig, + let configData = configJson.data(using: .utf8), + let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any], + let apiBaseUrl = config["apiBaseUrl"] as? String, + let activeDid = config["activeDid"] as? String, + let jwtToken = config["jwtToken"] as? String { + // Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch + print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)") + + // Attempt to fetch content from TimeSafari API + // Note: This is a minimal implementation - can be extended with full API client + do { + let fetchedContent = try await fetchContentFromAPI( + apiBaseUrl: apiBaseUrl, + activeDid: activeDid, + jwtToken: jwtToken + ) + content = fetchedContent + print("DNP-FETCH: Successfully fetched content from API") + } catch { + // Fallback to dummy content on fetch failure + print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content") + content = NotificationContent( + id: "fallback_\(Date().timeIntervalSince1970)", + title: "Daily Update", + body: "Your daily notification is ready", + scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000), + fetchedAt: Int64(Date().timeIntervalSince1970 * 1000), + url: nil, + payload: ["fetchError": error.localizedDescription], + etag: nil + ) + } + } else { + // Fallback: Dummy content fetch (no network) + print("DNP-FETCH: Using dummy content (native fetcher not configured)") content = NotificationContent( - id: "fallback_\(Date().timeIntervalSince1970)", + id: "dummy_\(Date().timeIntervalSince1970)", title: "Daily Update", body: "Your daily notification is ready", scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000), fetchedAt: Int64(Date().timeIntervalSince1970 * 1000), url: nil, - payload: ["fetchError": error.localizedDescription], + payload: nil, etag: nil ) } - } else { - // Fallback: Dummy content fetch (no network) - print("DNP-FETCH: Using dummy content (native fetcher not configured)") - content = NotificationContent( - id: "dummy_\(Date().timeIntervalSince1970)", - title: "Daily Update", - body: "Your daily notification is ready", - scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000), - fetchedAt: Int64(Date().timeIntervalSince1970 * 1000), - url: nil, - payload: nil, - etag: nil - ) - } - - // Save content to storage via state actor (thread-safe) - Task { do { // Use the content (either from JWT fetcher or dummy) if #available(iOS 13.0, *) { @@ -513,11 +513,16 @@ public class DailyNotificationPlugin: CAPPlugin { // Phase 3.3: Schedule next background task // Calculate next fetch time based on notification schedule - if let nextScheduledTime = self.getNextScheduledNotificationTime() { - self.scheduleBackgroundFetch(scheduledTime: nextScheduledTime) - print("DNP-FETCH: Next background fetch scheduled") + if let scheduler = self.scheduler { + let nextScheduledTime = await scheduler.getNextNotificationTime() + if let nextTime = nextScheduledTime { + self.scheduleBackgroundFetch(scheduledTime: nextTime) + print("DNP-FETCH: Next background fetch scheduled") + } else { + print("DNP-FETCH: No future notifications found, skipping next task schedule") + } } else { - print("DNP-FETCH: No future notifications found, skipping next task schedule") + print("DNP-FETCH: Scheduler not available, skipping next task schedule") } guard !taskCompleted else { return } @@ -575,10 +580,13 @@ public class DailyNotificationPlugin: CAPPlugin { // Phase 3.3: Schedule next background task if needed // For notify task, schedule next occurrence if applicable - if let nextScheduledTime = self.getNextScheduledNotificationTime() { - // Calculate next notify task time (if applicable) - // Note: Notify tasks are typically scheduled less frequently than fetch tasks - print("DNP-NOTIFY: Next notification scheduled at \(nextScheduledTime)") + if let scheduler = self.scheduler { + let nextScheduledTime = await scheduler.getNextNotificationTime() + if let nextTime = nextScheduledTime { + // Calculate next notify task time (if applicable) + // Note: Notify tasks are typically scheduled less frequently than fetch tasks + print("DNP-NOTIFY: Next notification scheduled at \(nextTime)") + } } guard !taskCompleted else { return } @@ -1091,7 +1099,7 @@ public class DailyNotificationPlugin: CAPPlugin { scheduler: scheduler, storage: self.storage, stateActor: await self.stateActor, - scheduleBackgroundFetch: { [weak self] scheduledTime in + scheduleBackgroundFetch: { [weak self] (scheduledTime: Int64) -> Void in self?.scheduleBackgroundFetch(scheduledTime: scheduledTime) } ) @@ -1532,8 +1540,8 @@ public class DailyNotificationPlugin: CAPPlugin { // User must check in Settings app // Delegate storage access to storage service - let lastFetchExecution = storage?.getLastSuccessfulRun() ?? NSNull() - let lastNotifyExecution = storage?.getLastNotifyExecution() ?? NSNull() + let lastFetchExecution: Any = storage?.getLastSuccessfulRun() ?? NSNull() + let lastNotifyExecution: Any = storage?.getLastNotifyExecution() ?? NSNull() let result: [String: Any] = [ "fetchTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called @@ -1630,6 +1638,7 @@ public class DailyNotificationPlugin: CAPPlugin { // Get channelId from call (optional, for API parity with Android) // iOS doesn't have per-channel control, so check app-wide notification authorization + let channelId = call.getString("channelId") ?? "default" Task { // Delegate to scheduler for permission status check let status = await scheduler.checkPermissionStatus() @@ -1677,9 +1686,6 @@ public class DailyNotificationPlugin: CAPPlugin { } } } - call.reject("Invalid settings URL") - } - } /** * Update notification settings diff --git a/test-apps/daily-notification-test/ios/App/Podfile b/test-apps/daily-notification-test/ios/App/Podfile index bf7e1a8..e9d90b2 100644 --- a/test-apps/daily-notification-test/ios/App/Podfile +++ b/test-apps/daily-notification-test/ios/App/Podfile @@ -1,4 +1,4 @@ -require_relative '../../../../node_modules/@capacitor/ios/scripts/pods_helpers' +require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! @@ -9,8 +9,8 @@ use_frameworks! install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods - pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' - pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' + pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios' end diff --git a/test-apps/daily-notification-test/package-lock.json b/test-apps/daily-notification-test/package-lock.json index 6b09a41..a6db6d6 100644 --- a/test-apps/daily-notification-test/package-lock.json +++ b/test-apps/daily-notification-test/package-lock.json @@ -12,6 +12,7 @@ "@capacitor/android": "^6.2.1", "@capacitor/cli": "^6.2.1", "@capacitor/core": "^6.2.1", + "@capacitor/ios": "^6.2.1", "@timesafari/daily-notification-plugin": "file:../../", "date-fns": "^4.1.0", "did-jwt": "^7.4.7", @@ -117,6 +118,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -416,6 +418,7 @@ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.28.4" }, @@ -634,10 +637,20 @@ "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz", "integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.1.0" } }, + "node_modules/@capacitor/ios": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-6.2.1.tgz", + "integrity": "sha512-tbMlQdQjxe1wyaBvYVU1yTojKJjgluZQsJkALuJxv/6F8QTw5b6vd7X785O/O7cMpIAZfUWo/vtAHzFkRV+kXw==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^6.2.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -2065,6 +2078,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -2663,6 +2677,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2911,6 +2926,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3373,6 +3389,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3434,6 +3451,7 @@ "integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", @@ -4293,6 +4311,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -5721,6 +5740,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5798,6 +5818,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6031,6 +6052,7 @@ "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -6301,6 +6323,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6320,6 +6343,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/compiler-sfc": "3.5.22", diff --git a/test-apps/daily-notification-test/package.json b/test-apps/daily-notification-test/package.json index 8946f48..70ac1b8 100644 --- a/test-apps/daily-notification-test/package.json +++ b/test-apps/daily-notification-test/package.json @@ -15,14 +15,14 @@ "lint": "eslint . --fix", "cap:sync": "npx cap sync && node scripts/fix-capacitor-plugins.js", "cap:sync:android": "npx cap sync android && node scripts/fix-capacitor-plugins.js", - "cap:sync:ios": "npx cap sync ios", + "cap:sync:ios": "npx cap copy ios && node scripts/fix-capacitor-plugins.js && cd ios/App && pod install && cd ../..", "postinstall": "node scripts/fix-capacitor-plugins.js" }, "dependencies": { "@capacitor/android": "^6.2.1", - "@capacitor/ios": "^6.2.1", "@capacitor/cli": "^6.2.1", "@capacitor/core": "^6.2.1", + "@capacitor/ios": "^6.2.1", "@timesafari/daily-notification-plugin": "file:../../", "date-fns": "^4.1.0", "did-jwt": "^7.4.7", diff --git a/test-apps/daily-notification-test/src/lib/bridge.ts b/test-apps/daily-notification-test/src/lib/bridge.ts index e77583f..06ecd3b 100644 --- a/test-apps/daily-notification-test/src/lib/bridge.ts +++ b/test-apps/daily-notification-test/src/lib/bridge.ts @@ -40,6 +40,9 @@ export interface ScheduleResponse { export interface PermissionStatus { notifications: 'granted' | 'denied' notificationsEnabled: boolean + exactAlarmEnabled?: boolean + wakeLockEnabled?: boolean + allPermissionsGranted?: boolean } export interface NotificationStatus { diff --git a/test-apps/daily-notification-test/src/views/HomeView.vue b/test-apps/daily-notification-test/src/views/HomeView.vue index 448333f..18db34c 100644 --- a/test-apps/daily-notification-test/src/views/HomeView.vue +++ b/test-apps/daily-notification-test/src/views/HomeView.vue @@ -402,7 +402,7 @@ const checkAndRequestPermissions = async (): Promise => { // Request permissions - this will show the iOS system dialog // Try requestNotificationPermissions first (iOS), fallback to requestPermissions if (typeof (plugin as any).requestNotificationPermissions === 'function') { - await (plugin as { requestNotificationPermissions: () => Promise }).requestNotificationPermissions() + await (plugin as { requestNotificationPermissions: () => Promise }).requestNotificationPermissions() } else if (typeof (plugin as any).requestPermissions === 'function') { await (plugin as { requestPermissions: () => Promise }).requestPermissions() } else {