Implement checkPermissionStatus() and requestNotificationPermissions() methods for iOS plugin, matching Android functionality. Fix compilation errors across plugin files and add comprehensive build/test infrastructure. Key Changes: - Add checkPermissionStatus() and requestNotificationPermissions() methods - Fix 13+ categories of Swift compilation errors (type conversions, logger API, access control, async/await, etc.) - Create DailyNotificationScheduler, DailyNotificationStorage, DailyNotificationStateActor, and DailyNotificationErrorCodes components - Fix CoreData initialization to handle missing model gracefully for Phase 1 - Add iOS test app build script with simulator auto-detection - Update directive with lessons learned from build and permission work Build Status: ✅ BUILD SUCCEEDED Test App: ✅ Ready for iOS Simulator testing Files Modified: - doc/directives/0003-iOS-Android-Parity-Directive.md (lessons learned) - ios/Plugin/DailyNotificationPlugin.swift (Phase 1 methods) - ios/Plugin/DailyNotificationModel.swift (CoreData fix) - 11+ other plugin files (compilation fixes) Files Added: - ios/Plugin/DailyNotificationScheduler.swift - ios/Plugin/DailyNotificationStorage.swift - ios/Plugin/DailyNotificationStateActor.swift - ios/Plugin/DailyNotificationErrorCodes.swift - scripts/build-ios-test-app.sh - scripts/setup-ios-test-app.sh - test-apps/ios-test-app/ (full test app) - Multiple Phase 1 documentation files
235 lines
8.6 KiB
Swift
235 lines
8.6 KiB
Swift
//
|
|
// DailyNotificationCallbacks.swift
|
|
// DailyNotificationPlugin
|
|
//
|
|
// Created by Matthew Raymer on 2025-09-22
|
|
// Copyright © 2025 TimeSafari. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Capacitor
|
|
import CoreData
|
|
|
|
/**
|
|
* Callback management for iOS Daily Notification Plugin
|
|
* Implements HTTP and local callback delivery with error handling
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.1.0
|
|
* @created 2025-09-22 09:22:32 UTC
|
|
*/
|
|
extension DailyNotificationPlugin {
|
|
|
|
// MARK: - Callback Management
|
|
|
|
@objc func registerCallback(_ call: CAPPluginCall) {
|
|
guard let name = call.getString("name"),
|
|
let callbackConfig = call.getObject("callback") else {
|
|
call.reject("Callback name and config required")
|
|
return
|
|
}
|
|
|
|
print("DNP-PLUGIN: Registering callback: \(name)")
|
|
|
|
do {
|
|
try registerCallback(name: name, config: callbackConfig)
|
|
call.resolve()
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to register callback: \(error)")
|
|
call.reject("Callback registration failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
@objc func unregisterCallback(_ call: CAPPluginCall) {
|
|
guard let name = call.getString("name") else {
|
|
call.reject("Callback name required")
|
|
return
|
|
}
|
|
|
|
print("DNP-PLUGIN: Unregistering callback: \(name)")
|
|
|
|
do {
|
|
try unregisterCallback(name: name)
|
|
call.resolve()
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to unregister callback: \(error)")
|
|
call.reject("Callback unregistration failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
@objc func getRegisteredCallbacks(_ call: CAPPluginCall) {
|
|
Task {
|
|
do {
|
|
let callbacks = try await getRegisteredCallbacks()
|
|
call.resolve(["callbacks": callbacks])
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to get registered callbacks: \(error)")
|
|
call.reject("Callback retrieval failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Content Management
|
|
|
|
@objc func getContentCache(_ call: CAPPluginCall) {
|
|
Task {
|
|
do {
|
|
let cache = try await getContentCache()
|
|
call.resolve(cache)
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to get content cache: \(error)")
|
|
call.reject("Content cache retrieval failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func clearContentCache(_ call: CAPPluginCall) {
|
|
Task {
|
|
do {
|
|
try await clearContentCache()
|
|
call.resolve()
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to clear content cache: \(error)")
|
|
call.reject("Content cache clearing failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func getContentHistory(_ call: CAPPluginCall) {
|
|
Task {
|
|
do {
|
|
let history = try await getContentHistory()
|
|
call.resolve(["history": history])
|
|
} catch {
|
|
print("DNP-PLUGIN: Failed to get content history: \(error)")
|
|
call.reject("Content history retrieval failed: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Callback Implementation
|
|
|
|
func fireCallbacks(eventType: String, payload: [String: Any]) async throws {
|
|
// Phase 1: Callbacks are not yet implemented
|
|
// TODO: Phase 2 - Implement callback system with CoreData
|
|
// For now, this is a no-op
|
|
print("DNP-CALLBACKS: fireCallbacks called for \(eventType) (Phase 2 - not implemented)")
|
|
// Phase 2 implementation will go here
|
|
}
|
|
|
|
private func deliverCallback(callback: Callback, eventType: String, payload: [String: Any]) async throws {
|
|
guard let callbackId = callback.id,
|
|
let kind = callback.kind else { return }
|
|
|
|
let event = [
|
|
"id": callbackId,
|
|
"at": Date().timeIntervalSince1970,
|
|
"type": eventType,
|
|
"payload": payload
|
|
] as [String: Any]
|
|
|
|
switch kind {
|
|
case "http":
|
|
try await deliverHttpCallback(callback: callback, event: event)
|
|
case "local":
|
|
try await deliverLocalCallback(callback: callback, event: event)
|
|
default:
|
|
print("DNP-CB-UNKNOWN: Unknown callback kind: \(kind)")
|
|
}
|
|
}
|
|
|
|
private func deliverHttpCallback(callback: Callback, event: [String: Any]) async throws {
|
|
guard let target = callback.target,
|
|
let url = URL(string: target) else {
|
|
throw NSError(domain: "DailyNotificationPlugin", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid callback target"])
|
|
}
|
|
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: event)
|
|
|
|
let (_, response) = try await URLSession.shared.data(for: request)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200 else {
|
|
throw NSError(domain: "DailyNotificationPlugin", code: 2, userInfo: [NSLocalizedDescriptionKey: "HTTP callback failed"])
|
|
}
|
|
|
|
print("DNP-CB-HTTP-SUCCESS: HTTP callback delivered to \(target)")
|
|
}
|
|
|
|
private func deliverLocalCallback(callback: Callback, event: [String: Any]) async throws {
|
|
// Local callback implementation would go here
|
|
print("DNP-CB-LOCAL: Local callback delivered for \(callback.id ?? "unknown")")
|
|
}
|
|
|
|
private func registerCallback(name: String, config: [String: Any]) throws {
|
|
// Phase 1: Callback registration not yet implemented
|
|
// TODO: Phase 2 - Implement callback registration with CoreData
|
|
print("DNP-CALLBACKS: registerCallback called for \(name) (Phase 2 - not implemented)")
|
|
// Phase 2 implementation will go here
|
|
}
|
|
|
|
private func unregisterCallback(name: String) throws {
|
|
// Phase 1: Callback unregistration not yet implemented
|
|
// TODO: Phase 2 - Implement callback unregistration with CoreData
|
|
print("DNP-CALLBACKS: unregisterCallback called for \(name) (Phase 2 - not implemented)")
|
|
}
|
|
|
|
private func getRegisteredCallbacks() async throws -> [String] {
|
|
// Phase 1: Callback retrieval not yet implemented
|
|
// TODO: Phase 2 - Implement callback retrieval with CoreData
|
|
print("DNP-CALLBACKS: getRegisteredCallbacks called (Phase 2 - not implemented)")
|
|
return []
|
|
}
|
|
|
|
private func getContentCache() async throws -> [String: Any] {
|
|
// Phase 1: Content cache retrieval not yet implemented
|
|
// TODO: Phase 2 - Implement content cache retrieval
|
|
print("DNP-CALLBACKS: getContentCache called (Phase 2 - not implemented)")
|
|
return [:]
|
|
}
|
|
|
|
private func clearContentCache() async throws {
|
|
// Phase 1: Content cache clearing not yet implemented
|
|
// TODO: Phase 2 - Implement content cache clearing with CoreData
|
|
print("DNP-CALLBACKS: clearContentCache called (Phase 2 - not implemented)")
|
|
}
|
|
|
|
private func getContentHistory() async throws -> [[String: Any]] {
|
|
// Phase 1: History retrieval not yet implemented
|
|
// TODO: Phase 2 - Implement history retrieval with CoreData
|
|
print("DNP-CALLBACKS: getContentHistory called (Phase 2 - not implemented)")
|
|
return []
|
|
}
|
|
|
|
private func getHealthStatus() async throws -> [String: Any] {
|
|
// Phase 1: Health status not yet implemented
|
|
// TODO: Phase 2 - Implement health status with CoreData
|
|
print("DNP-CALLBACKS: getHealthStatus called (Phase 2 - not implemented)")
|
|
// Get next runs (simplified)
|
|
let nextRuns = [Date().addingTimeInterval(3600).timeIntervalSince1970,
|
|
Date().addingTimeInterval(86400).timeIntervalSince1970]
|
|
|
|
// Phase 1: Return simplified health status
|
|
return [
|
|
"nextRuns": nextRuns,
|
|
"lastOutcomes": [],
|
|
"cacheAgeMs": 0,
|
|
"staleArmed": false,
|
|
"queueDepth": 0,
|
|
"circuitBreakers": [
|
|
"total": 0,
|
|
"open": 0,
|
|
"failures": 0
|
|
],
|
|
"performance": [
|
|
"avgFetchTime": 0,
|
|
"avgNotifyTime": 0,
|
|
"successRate": 1.0
|
|
]
|
|
]
|
|
}
|
|
}
|