Fix iOS build errors and test app setup

- Fix async/await usage in background fetch handler
- Fix Core Data metadata access errors
- Replace SQLITE_TRANSIENT with nil for Swift compatibility
- Fix PermissionStatus interface and type casts in test app
- Add iOS setup documentation to BUILDING.md
- Update iOS sync workflow to handle Podfile regeneration

Resolves all iOS compilation errors and improves test app setup process.
This commit is contained in:
Jose Olarte III
2025-12-30 12:35:10 +08:00
parent 36e15633be
commit 9565191101
9 changed files with 280 additions and 82 deletions

View File

@@ -361,12 +361,16 @@ npm install
# Build Vue 3 app # Build Vue 3 app
npm run build npm run build
# Add Capacitor # Add Capacitor platforms
npm install @capacitor/android npm install @capacitor/android @capacitor/ios
# Sync with Capacitor # Sync with Capacitor
npx cap sync android 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 # Run on Android device/emulator
npx cap run android npx cap run android
@@ -374,6 +378,149 @@ npx cap run android
npx cap run ios 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:** **Test App Features:**
- Interactive plugin testing interface - Interactive plugin testing interface
@@ -390,8 +537,13 @@ test-apps/daily-notification-test/
│ ├── components/ # Reusable UI components │ ├── components/ # Reusable UI components
│ └── stores/ # Pinia state management │ └── stores/ # Pinia state management
├── android/ # Android Capacitor app ├── android/ # Android Capacitor app
├── ios/ # iOS Capacitor app
│ └── App/
│ ├── Podfile # CocoaPods dependencies
│ └── App.xcworkspace # Xcode workspace
├── docs/ # Test app documentation ├── docs/ # Test app documentation
└── scripts/ # Test app build scripts └── scripts/ # Test app build scripts
│ └── fix-capacitor-plugins.js # Auto-fixes Podfile
``` ```
#### Android Test Apps #### Android Test Apps

View File

@@ -263,12 +263,12 @@ class DailyNotificationDatabase {
return return
} }
sqlite3_bind_text(stmt, 1, (content.id 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, SQLITE_TRANSIENT) sqlite3_bind_text(stmt, 2, (json as NSString).utf8String, -1, nil)
sqlite3_bind_int64(stmt, 3, sqlite3_int64(content.fetchedAt)) sqlite3_bind_int64(stmt, 3, sqlite3_int64(content.fetchedAt))
if let etag = content.etag { 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 { } else {
sqlite3_bind_null(stmt, 4) sqlite3_bind_null(stmt, 4)
} }
@@ -310,7 +310,7 @@ class DailyNotificationDatabase {
return 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 { if sqlite3_step(stmt) != SQLITE_DONE {
print("\(Self.TAG): deleteNotificationContent step failed: \(String(cString: sqlite3_errmsg(db)))") print("\(Self.TAG): deleteNotificationContent step failed: \(String(cString: sqlite3_errmsg(db)))")

View File

@@ -261,17 +261,8 @@ class PersistenceController {
description?.shouldMigrateStoreAutomatically = true description?.shouldMigrateStoreAutomatically = true
description?.shouldInferMappingModelAutomatically = 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 var loadError: Error? = nil
tempContainer?.loadPersistentStores { description, error in tempContainer?.loadPersistentStores { storeDescription, error in
if let error = error as NSError? { if let error = error as NSError? {
loadError = error loadError = error
print("DNP-PLUGIN: CoreData store load error: \(error.localizedDescription)") print("DNP-PLUGIN: CoreData store load error: \(error.localizedDescription)")
@@ -281,7 +272,19 @@ class PersistenceController {
} }
} else { } else {
print("DNP-PLUGIN: CoreData store loaded successfully") 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 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 let expectedVersion = PersistenceController.SCHEMA_VERSION
if currentVersion != expectedVersion { if currentVersion != expectedVersion {
@@ -381,9 +390,13 @@ class PersistenceController {
print("DNP-PLUGIN: CoreData auto-migration will handle schema changes") print("DNP-PLUGIN: CoreData auto-migration will handle schema changes")
// Update metadata for future reference (does not trigger migration) // Update metadata for future reference (does not trigger migration)
var metadata = store.metadata // Use the coordinator to set metadata
metadata["schema_version"] = expectedVersion if let coordinator = container?.persistentStoreCoordinator {
// Note: Metadata persists on next store save var newMetadata = metadata
newMetadata["schema_version"] = expectedVersion
coordinator.setMetadata(newMetadata, for: store)
// Note: Metadata persists on next store save
}
} else { } else {
print("DNP-PLUGIN: Schema version verified: \(currentVersion)") print("DNP-PLUGIN: Schema version verified: \(currentVersion)")
} }

View File

@@ -419,58 +419,58 @@ public class DailyNotificationPlugin: CAPPlugin {
// Phase 3: Check for JWT-signed fetcher configuration // Phase 3: Check for JWT-signed fetcher configuration
// If native fetcher is configured, use it; otherwise fall back to dummy content // If native fetcher is configured, use it; otherwise fall back to dummy content
let nativeFetcherConfig = UserDefaults.standard.string(forKey: "native_fetcher_config") let nativeFetcherConfig = UserDefaults.standard.string(forKey: "native_fetcher_config")
let content: NotificationContent
if let configJson = nativeFetcherConfig, // Save content to storage via state actor (thread-safe)
let configData = configJson.data(using: .utf8), Task {
let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any], let content: NotificationContent
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 if let configJson = nativeFetcherConfig,
// Note: This is a minimal implementation - can be extended with full API client let configData = configJson.data(using: .utf8),
do { let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any],
let fetchedContent = try await fetchContentFromAPI( let apiBaseUrl = config["apiBaseUrl"] as? String,
apiBaseUrl: apiBaseUrl, let activeDid = config["activeDid"] as? String,
activeDid: activeDid, let jwtToken = config["jwtToken"] as? String {
jwtToken: jwtToken // Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch
) print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)")
content = fetchedContent
print("DNP-FETCH: Successfully fetched content from API") // Attempt to fetch content from TimeSafari API
} catch { // Note: This is a minimal implementation - can be extended with full API client
// Fallback to dummy content on fetch failure do {
print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content") 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( content = NotificationContent(
id: "fallback_\(Date().timeIntervalSince1970)", id: "dummy_\(Date().timeIntervalSince1970)",
title: "Daily Update", title: "Daily Update",
body: "Your daily notification is ready", body: "Your daily notification is ready",
scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000), scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000),
fetchedAt: Int64(Date().timeIntervalSince1970 * 1000), fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
url: nil, url: nil,
payload: ["fetchError": error.localizedDescription], payload: nil,
etag: 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 { do {
// Use the content (either from JWT fetcher or dummy) // Use the content (either from JWT fetcher or dummy)
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
@@ -513,11 +513,16 @@ public class DailyNotificationPlugin: CAPPlugin {
// Phase 3.3: Schedule next background task // Phase 3.3: Schedule next background task
// Calculate next fetch time based on notification schedule // Calculate next fetch time based on notification schedule
if let nextScheduledTime = self.getNextScheduledNotificationTime() { if let scheduler = self.scheduler {
self.scheduleBackgroundFetch(scheduledTime: nextScheduledTime) let nextScheduledTime = await scheduler.getNextNotificationTime()
print("DNP-FETCH: Next background fetch scheduled") 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 { } 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 } guard !taskCompleted else { return }
@@ -575,10 +580,13 @@ public class DailyNotificationPlugin: CAPPlugin {
// Phase 3.3: Schedule next background task if needed // Phase 3.3: Schedule next background task if needed
// For notify task, schedule next occurrence if applicable // For notify task, schedule next occurrence if applicable
if let nextScheduledTime = self.getNextScheduledNotificationTime() { if let scheduler = self.scheduler {
// Calculate next notify task time (if applicable) let nextScheduledTime = await scheduler.getNextNotificationTime()
// Note: Notify tasks are typically scheduled less frequently than fetch tasks if let nextTime = nextScheduledTime {
print("DNP-NOTIFY: Next notification scheduled at \(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 } guard !taskCompleted else { return }
@@ -1091,7 +1099,7 @@ public class DailyNotificationPlugin: CAPPlugin {
scheduler: scheduler, scheduler: scheduler,
storage: self.storage, storage: self.storage,
stateActor: await self.stateActor, stateActor: await self.stateActor,
scheduleBackgroundFetch: { [weak self] scheduledTime in scheduleBackgroundFetch: { [weak self] (scheduledTime: Int64) -> Void in
self?.scheduleBackgroundFetch(scheduledTime: scheduledTime) self?.scheduleBackgroundFetch(scheduledTime: scheduledTime)
} }
) )
@@ -1532,8 +1540,8 @@ public class DailyNotificationPlugin: CAPPlugin {
// User must check in Settings app // User must check in Settings app
// Delegate storage access to storage service // Delegate storage access to storage service
let lastFetchExecution = storage?.getLastSuccessfulRun() ?? NSNull() let lastFetchExecution: Any = storage?.getLastSuccessfulRun() ?? NSNull()
let lastNotifyExecution = storage?.getLastNotifyExecution() ?? NSNull() let lastNotifyExecution: Any = storage?.getLastNotifyExecution() ?? NSNull()
let result: [String: Any] = [ let result: [String: Any] = [
"fetchTaskRegistered": true, // Assumed registered if setupBackgroundTasks() was called "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) // Get channelId from call (optional, for API parity with Android)
// iOS doesn't have per-channel control, so check app-wide notification authorization // iOS doesn't have per-channel control, so check app-wide notification authorization
let channelId = call.getString("channelId") ?? "default"
Task { Task {
// Delegate to scheduler for permission status check // Delegate to scheduler for permission status check
let status = await scheduler.checkPermissionStatus() let status = await scheduler.checkPermissionStatus()
@@ -1677,9 +1686,6 @@ public class DailyNotificationPlugin: CAPPlugin {
} }
} }
} }
call.reject("Invalid settings URL")
}
}
/** /**
* Update notification settings * Update notification settings

View File

@@ -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' platform :ios, '13.0'
use_frameworks! use_frameworks!
@@ -9,8 +9,8 @@ use_frameworks!
install! 'cocoapods', :disable_input_output_paths => true install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios' pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'
end end

View File

@@ -12,6 +12,7 @@
"@capacitor/android": "^6.2.1", "@capacitor/android": "^6.2.1",
"@capacitor/cli": "^6.2.1", "@capacitor/cli": "^6.2.1",
"@capacitor/core": "^6.2.1", "@capacitor/core": "^6.2.1",
"@capacitor/ios": "^6.2.1",
"@timesafari/daily-notification-plugin": "file:../../", "@timesafari/daily-notification-plugin": "file:../../",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"did-jwt": "^7.4.7", "did-jwt": "^7.4.7",
@@ -117,6 +118,7 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.3",
@@ -416,6 +418,7 @@
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4" "@babel/types": "^7.28.4"
}, },
@@ -634,10 +637,20 @@
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==", "integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "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": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.11", "version": "0.25.11",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
@@ -2065,6 +2078,7 @@
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/scope-manager": "8.46.1",
"@typescript-eslint/types": "8.46.1", "@typescript-eslint/types": "8.46.1",
@@ -2663,6 +2677,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2911,6 +2926,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.9", "baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746", "caniuse-lite": "^1.0.30001746",
@@ -3373,6 +3389,7 @@
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -3434,6 +3451,7 @@
"integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==", "integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -4293,6 +4311,7 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
@@ -5721,6 +5740,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -5798,6 +5818,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -6031,6 +6052,7 @@
"integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -6301,6 +6323,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -6320,6 +6343,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.22", "@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22", "@vue/compiler-sfc": "3.5.22",

View File

@@ -15,14 +15,14 @@
"lint": "eslint . --fix", "lint": "eslint . --fix",
"cap:sync": "npx cap sync && node scripts/fix-capacitor-plugins.js", "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: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" "postinstall": "node scripts/fix-capacitor-plugins.js"
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^6.2.1", "@capacitor/android": "^6.2.1",
"@capacitor/ios": "^6.2.1",
"@capacitor/cli": "^6.2.1", "@capacitor/cli": "^6.2.1",
"@capacitor/core": "^6.2.1", "@capacitor/core": "^6.2.1",
"@capacitor/ios": "^6.2.1",
"@timesafari/daily-notification-plugin": "file:../../", "@timesafari/daily-notification-plugin": "file:../../",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"did-jwt": "^7.4.7", "did-jwt": "^7.4.7",

View File

@@ -40,6 +40,9 @@ export interface ScheduleResponse {
export interface PermissionStatus { export interface PermissionStatus {
notifications: 'granted' | 'denied' notifications: 'granted' | 'denied'
notificationsEnabled: boolean notificationsEnabled: boolean
exactAlarmEnabled?: boolean
wakeLockEnabled?: boolean
allPermissionsGranted?: boolean
} }
export interface NotificationStatus { export interface NotificationStatus {

View File

@@ -402,7 +402,7 @@ const checkAndRequestPermissions = async (): Promise<void> => {
// Request permissions - this will show the iOS system dialog // Request permissions - this will show the iOS system dialog
// Try requestNotificationPermissions first (iOS), fallback to requestPermissions // Try requestNotificationPermissions first (iOS), fallback to requestPermissions
if (typeof (plugin as any).requestNotificationPermissions === 'function') { if (typeof (plugin as any).requestNotificationPermissions === 'function') {
await (plugin as { requestNotificationPermissions: () => Promise<void> }).requestNotificationPermissions() await (plugin as { requestNotificationPermissions: () => Promise<any> }).requestNotificationPermissions()
} else if (typeof (plugin as any).requestPermissions === 'function') { } else if (typeof (plugin as any).requestPermissions === 'function') {
await (plugin as { requestPermissions: () => Promise<any> }).requestPermissions() await (plugin as { requestPermissions: () => Promise<any> }).requestPermissions()
} else { } else {