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:
@@ -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)))")
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user