feat(ios): implement Phase 1 permission methods and fix build issues
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
This commit is contained in:
@@ -68,7 +68,7 @@ class DailyNotificationErrorHandler {
|
||||
self.logger = logger
|
||||
self.config = ErrorConfiguration()
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ class DailyNotificationErrorHandler {
|
||||
self.logger = logger
|
||||
self.config = config
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): ErrorHandler initialized with max retries: \(config.maxRetries)")
|
||||
}
|
||||
|
||||
// MARK: - Error Handling
|
||||
@@ -96,7 +96,7 @@ class DailyNotificationErrorHandler {
|
||||
*/
|
||||
func handleError(operationId: String, error: Error, retryable: Bool) -> ErrorResult {
|
||||
do {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Handling error for operation: \(operationId)")
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Handling error for operation: \(operationId)")
|
||||
|
||||
// Categorize error
|
||||
let errorInfo = categorizeError(error)
|
||||
@@ -112,7 +112,7 @@ class DailyNotificationErrorHandler {
|
||||
}
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler: \(error)")
|
||||
logger.log(.error, "\(DailyNotificationErrorHandler.TAG): Error in error handler: \(error)")
|
||||
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ class DailyNotificationErrorHandler {
|
||||
*/
|
||||
func handleError(operationId: String, error: Error, retryConfig: RetryConfiguration) -> ErrorResult {
|
||||
do {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Handling error with custom retry config for operation: \(operationId)")
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Handling error with custom retry config for operation: \(operationId)")
|
||||
|
||||
// Categorize error
|
||||
let errorInfo = categorizeError(error)
|
||||
@@ -143,7 +143,7 @@ class DailyNotificationErrorHandler {
|
||||
}
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler with custom config: \(error)")
|
||||
logger.log(.error, "DailyNotificationErrorHandler.TAG: Error in error handler with custom config: \(error)")
|
||||
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@@ -170,11 +170,11 @@ class DailyNotificationErrorHandler {
|
||||
timestamp: Date()
|
||||
)
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Error categorized: \(errorInfo)")
|
||||
logger.log(.debug, "DailyNotificationErrorHandler.TAG: Error categorized: \(errorInfo)")
|
||||
return errorInfo
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error during categorization: \(error)")
|
||||
logger.log(.error, "DailyNotificationErrorHandler.TAG: Error during categorization: \(error)")
|
||||
return ErrorInfo(
|
||||
error: error,
|
||||
category: .unknown,
|
||||
@@ -299,29 +299,30 @@ class DailyNotificationErrorHandler {
|
||||
private func shouldRetry(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> Bool {
|
||||
do {
|
||||
// Get retry state
|
||||
var state: RetryState
|
||||
var attemptCount: Int = 0
|
||||
retryQueue.sync {
|
||||
if retryStates[operationId] == nil {
|
||||
retryStates[operationId] = RetryState()
|
||||
}
|
||||
state = retryStates[operationId]!
|
||||
let state = retryStates[operationId]!
|
||||
attemptCount = state.attemptCount
|
||||
}
|
||||
|
||||
// Check retry limits
|
||||
let maxRetries = retryConfig?.maxRetries ?? config.maxRetries
|
||||
if state.attemptCount >= maxRetries {
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Max retries exceeded for operation: \(operationId)")
|
||||
if attemptCount >= maxRetries {
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Max retries exceeded for operation: \(operationId)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if error is retryable based on category
|
||||
let isRetryable = isErrorRetryable(errorInfo.category)
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Should retry: \(isRetryable) (attempt: \(state.attemptCount)/\(maxRetries))")
|
||||
logger.log(.debug, "\(DailyNotificationErrorHandler.TAG): Should retry: \(isRetryable) (attempt: \(attemptCount)/\(maxRetries))")
|
||||
return isRetryable
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error checking retry eligibility: \(error)")
|
||||
logger.log(.error, "\(DailyNotificationErrorHandler.TAG): Error checking retry eligibility: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -336,7 +337,7 @@ class DailyNotificationErrorHandler {
|
||||
switch category {
|
||||
case .network, .storage:
|
||||
return true
|
||||
case .permission, .configuration, .system, .unknown:
|
||||
case .scheduling, .permission, .configuration, .system, .unknown:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -362,22 +363,29 @@ class DailyNotificationErrorHandler {
|
||||
*/
|
||||
private func handleRetryableError(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> ErrorResult {
|
||||
do {
|
||||
var state: RetryState
|
||||
var state: RetryState!
|
||||
var attemptCount: Int = 0
|
||||
retryQueue.sync {
|
||||
if retryStates[operationId] == nil {
|
||||
retryStates[operationId] = RetryState()
|
||||
}
|
||||
state = retryStates[operationId]!
|
||||
state.attemptCount += 1
|
||||
attemptCount = state.attemptCount
|
||||
}
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
let delay = calculateRetryDelay(attemptCount: state.attemptCount, retryConfig: retryConfig)
|
||||
state.nextRetryTime = Date().addingTimeInterval(delay)
|
||||
let delay = calculateRetryDelay(attemptCount: attemptCount, retryConfig: retryConfig)
|
||||
retryQueue.async(flags: .barrier) {
|
||||
state.nextRetryTime = Date().addingTimeInterval(delay)
|
||||
}
|
||||
|
||||
logger.info(DailyNotificationErrorHandler.TAG, "Retryable error handled - retry in \(delay)s (attempt \(state.attemptCount))")
|
||||
logger.log(.info, "\(DailyNotificationErrorHandler.TAG): Retryable error handled - retry in \(delay)s (attempt \(attemptCount))")
|
||||
|
||||
return ErrorResult.retryable(errorInfo: errorInfo, retryDelaySeconds: delay, attemptCount: state.attemptCount)
|
||||
return ErrorResult.retryable(errorInfo: errorInfo, retryDelaySeconds: delay, attemptCount: attemptCount)
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error handling retryable error: \(error)")
|
||||
logger.log(.error, "DailyNotificationErrorHandler.TAG: Error handling retryable error: \(error)")
|
||||
return ErrorResult.fatal(message: "Retry handling failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@@ -391,7 +399,7 @@ class DailyNotificationErrorHandler {
|
||||
*/
|
||||
private func handleNonRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
|
||||
do {
|
||||
logger.warning(DailyNotificationErrorHandler.TAG, "Non-retryable error handled for operation: \(operationId)")
|
||||
logger.log(.warning, "\(DailyNotificationErrorHandler.TAG): Non-retryable error handled for operation: \(operationId)")
|
||||
|
||||
// Clean up retry state
|
||||
retryQueue.async(flags: .barrier) {
|
||||
@@ -401,7 +409,7 @@ class DailyNotificationErrorHandler {
|
||||
return ErrorResult.fatal(errorInfo: errorInfo)
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error handling non-retryable error: \(error)")
|
||||
logger.log(.error, "DailyNotificationErrorHandler.TAG: Error handling non-retryable error: \(error)")
|
||||
return ErrorResult.fatal(message: "Non-retryable error handling failure: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@@ -429,11 +437,11 @@ class DailyNotificationErrorHandler {
|
||||
let jitter = delay * 0.1 * Double.random(in: 0...1)
|
||||
delay += jitter
|
||||
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Calculated retry delay: \(delay)s (attempt \(attemptCount))")
|
||||
logger.log(.debug, "DailyNotificationErrorHandler.TAG: Calculated retry delay: \(delay)s (attempt \(attemptCount))")
|
||||
return delay
|
||||
|
||||
} catch {
|
||||
logger.error(DailyNotificationErrorHandler.TAG, "Error calculating retry delay: \(error)")
|
||||
logger.log(.error, "DailyNotificationErrorHandler.TAG: Error calculating retry delay: \(error)")
|
||||
return config.baseDelaySeconds
|
||||
}
|
||||
}
|
||||
@@ -454,7 +462,7 @@ class DailyNotificationErrorHandler {
|
||||
*/
|
||||
func resetMetrics() {
|
||||
metrics.reset()
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Error metrics reset")
|
||||
logger.log(.debug, "DailyNotificationErrorHandler.TAG: Error metrics reset")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,7 +495,7 @@ class DailyNotificationErrorHandler {
|
||||
retryQueue.async(flags: .barrier) {
|
||||
self.retryStates.removeAll()
|
||||
}
|
||||
logger.debug(DailyNotificationErrorHandler.TAG, "Retry states cleared")
|
||||
logger.log(.debug, "DailyNotificationErrorHandler.TAG: Retry states cleared")
|
||||
}
|
||||
|
||||
// MARK: - Data Classes
|
||||
|
||||
Reference in New Issue
Block a user