feat: complete Tasks 21-28 — backend integration, security hardening, UI tests & CI

- Add Apple Sign-In backend (JWKS verification, account linking, session management)
- Implement push notification deep linking with NotificationDeepLinkRouter
- Add jailbreak detection, runtime integrity monitoring, secure enclave service
- Implement OAuth social login, token refresh, and secure logout flows
- Add image caching (memory/disk), optimizer, upload queue, async semaphore
- Implement notification analytics, type preferences, and category setup
- Expand UI test suite with UITestBase, accessibility, auth flow, performance tests
- Add CI pipeline for iOS UI tests (3 device sizes) and performance benchmarks
- Restructure Xcode project to manual groups with KordantWidgets target
- Add SwiftLint, Swift Collections/Algorithms/GoogleSignIn dependencies
- Update project.yml for XcodeGen with new targets and configurations
This commit is contained in:
2026-06-02 15:01:38 -04:00
parent ab0d4857db
commit e33ddf3002
49 changed files with 10472 additions and 421 deletions

View File

@@ -0,0 +1,105 @@
import Foundation
/// Data model shared between the main app and widget extension via App Group UserDefaults.
struct WidgetData: Codable, Equatable {
let threatScore: Double
let recentAlerts: [WidgetAlert]
let alertCount: Int
let unreadCount: Int
let criticalCount: Int
let exposureCount: Int
let lastUpdated: Date
static let placeholder = WidgetData(
threatScore: 0.25,
recentAlerts: WidgetAlert.placeholders,
alertCount: 5,
unreadCount: 3,
criticalCount: 1,
exposureCount: 2,
lastUpdated: Date()
)
static let unavailable = WidgetData(
threatScore: 0,
recentAlerts: [],
alertCount: 0,
unreadCount: 0,
criticalCount: 0,
exposureCount: 0,
lastUpdated: Date()
)
var threatLevel: ThreatLevel {
let pct = threatScore
if pct >= 0.7 { return .critical }
if pct >= 0.4 { return .high }
if pct >= 0.2 { return .medium }
return .low
}
}
enum ThreatLevel: String, Codable {
case low, medium, high, critical
var label: String { rawValue.capitalized }
}
struct WidgetAlert: Codable, Identifiable, Equatable {
let id: String
let title: String
let message: String
let severity: String
let type: String
let createdAt: Date?
var severityEnum: AlertSeverity {
AlertSeverity(rawValue: severity) ?? .low
}
var typeEnum: AlertType {
AlertType(rawValue: type) ?? .exposure
}
static let placeholders: [WidgetAlert] = [
WidgetAlert(
id: "placeholder-1",
title: "Data Breach Detected",
message: "Your email was found in a recent data breach.",
severity: "critical",
type: "breach",
createdAt: Date()
),
WidgetAlert(
id: "placeholder-2",
title: "New Exposure Found",
message: "Personal information exposed on a public forum.",
severity: "high",
type: "exposure",
createdAt: Date().addingTimeInterval(-3600)
),
WidgetAlert(
id: "placeholder-3",
title: "Voice Match Detected",
message: "Suspicious call pattern flagged.",
severity: "medium",
type: "voiceMatch",
createdAt: Date().addingTimeInterval(-7200)
),
]
}
enum AlertSeverity: String, Codable {
case low = "low"
case medium = "medium"
case high = "high"
case critical = "critical"
}
enum AlertType: String, Codable {
case exposure = "exposure"
case breach = "breach"
case login = "login"
case voiceMatch = "voiceMatch"
case removal = "removal"
}

View File

@@ -0,0 +1,46 @@
import Foundation
/// Reads and writes WidgetData from the shared App Group UserDefaults container.
/// Compiled into both the main app and widget extension targets.
final class WidgetDataManager {
static let shared = WidgetDataManager()
private let suiteName = "group.com.frenocorp.kordant"
private let dataKey = "com.frenocorp.kordant.widgetData"
private init() {}
/// Persist widget data to the shared container.
func save(_ data: WidgetData) {
guard let defaults = UserDefaults(suiteName: suiteName) else {
assertionFailure("WidgetDataManager: Unable to access App Group UserDefaults")
return
}
do {
let encoded = try JSONEncoder().encode(data)
defaults.set(encoded, forKey: dataKey)
} catch {
assertionFailure("WidgetDataManager: Failed to encode widget data: \(error)")
}
}
/// Load widget data from the shared container.
/// Returns `nil` if no data has been written yet.
func load() -> WidgetData? {
guard let defaults = UserDefaults(suiteName: suiteName),
let data = defaults.data(forKey: dataKey) else {
return nil
}
do {
return try JSONDecoder().decode(WidgetData.self, from: data)
} catch {
assertionFailure("WidgetDataManager: Failed to decode widget data: \(error)")
return nil
}
}
/// Remove all stored widget data (e.g. on logout).
func clear() {
UserDefaults(suiteName: suiteName)?.removeObject(forKey: dataKey)
}
}