diff --git a/ios/ChronoMind/Shared/Diagnostics/CrashReporter.swift b/ios/ChronoMind/Shared/Diagnostics/CrashReporter.swift index 256abf6..b13914c 100644 --- a/ios/ChronoMind/Shared/Diagnostics/CrashReporter.swift +++ b/ios/ChronoMind/Shared/Diagnostics/CrashReporter.swift @@ -1,152 +1,33 @@ // ── Crash Reporter ──────────────────────────────────────────── -// MetricKit-based crash and performance reporting for TestFlight/Production +// Wrapper over ByteLystPlatformSDK's BLCrashReporter. +// Preserves existing call-site API (CrashReporter.shared, loadCrashReports, clearReports). import Foundation -import MetricKit +import ByteLystPlatformSDK + +typealias CrashReport = BLCrashReport @MainActor -final class CrashReporter: NSObject, ObservableObject, MXMetricManagerSubscriber { +final class CrashReporter: ObservableObject { static let shared = CrashReporter() @Published var lastCrashReport: Date? @Published var diagnosticCount: Int = 0 - private let persistenceKey = "chronomind-crash-reports" + private let reporter: BLCrashReporter - override private init() { - super.init() - MXMetricManager.shared.add(self) - loadStats() - } - - deinit { - MXMetricManager.shared.remove(self) - } - - // MARK: - MXMetricManagerSubscriber - - nonisolated func didReceive(_ payloads: [MXMetricPayload]) { - // MetricKit delivers daily aggregated metrics - Task { @MainActor in - for payload in payloads { - processMetricPayload(payload) - } - } - } - - nonisolated func didReceive(_ payloads: [MXDiagnosticPayload]) { - // Crash diagnostics — delivered after app restart following crash - Task { @MainActor in - for payload in payloads { - processDiagnosticPayload(payload) - } - self.diagnosticCount += payloads.count - self.lastCrashReport = Date() - self.saveStats() - } - } - - // MARK: - Processing - - private func processMetricPayload(_ payload: MXMetricPayload) { - // Log key performance metrics - if let launchTime = payload.applicationLaunchMetrics { - let resumeTime = launchTime.histogrammedResumeTime - logMetric("app_resume_time", histogram: resumeTime) - } - - if let responsiveness = payload.applicationResponsivenessMetrics { - let hangTime = responsiveness.histogrammedApplicationHangTime - logMetric("hang_time", histogram: hangTime) - } - } - - private func processDiagnosticPayload(_ payload: MXDiagnosticPayload) { - // Store crash data locally for feedback form - if let crashDiagnostics = payload.crashDiagnostics { - for crash in crashDiagnostics { - let report = CrashReport( - date: Date(), - exceptionType: crash.exceptionType?.description, - signal: crash.signal?.description, - terminationReason: crash.terminationReason?.description, - callStackTree: crash.callStackTree.jsonRepresentation() - ) - storeCrashReport(report) - } - } - - if let hangDiagnostics = payload.hangDiagnostics { - for hang in hangDiagnostics { - let report = CrashReport( - date: Date(), - exceptionType: nil, - signal: nil, - terminationReason: "Hang: \(hang.hangDuration.description)", - callStackTree: hang.callStackTree.jsonRepresentation() - ) - storeCrashReport(report) - } - } - } - - // MARK: - Storage - - private func storeCrashReport(_ report: CrashReport) { - var reports = loadCrashReports() - reports.append(report) - // Keep last 50 reports - if reports.count > 50 { - reports = Array(reports.suffix(50)) - } - if let data = try? JSONEncoder().encode(reports) { - UserDefaults.standard.set(data, forKey: persistenceKey) - } + private init() { + reporter = BLCrashReporter(productId: "chronomind") + diagnosticCount = reporter.diagnosticCount + lastCrashReport = reporter.lastCrashReport } func loadCrashReports() -> [CrashReport] { - guard let data = UserDefaults.standard.data(forKey: persistenceKey), - let reports = try? JSONDecoder().decode([CrashReport].self, from: data) else { - return [] - } - return reports + reporter.loadCrashReports() } func clearReports() { - UserDefaults.standard.removeObject(forKey: persistenceKey) + reporter.clearReports() diagnosticCount = 0 } - - private func loadStats() { - diagnosticCount = loadCrashReports().count - } - - private func saveStats() { - // Stats are derived from stored reports - } - - private func logMetric(_ name: String, histogram: MXHistogram) { - // In production, send to analytics service - // For now, just track locally - } -} - -// MARK: - Crash Report Model - -struct CrashReport: Codable, Identifiable { - let id: String - let date: Date - let exceptionType: String? - let signal: String? - let terminationReason: String? - let callStackData: Data? - - init(date: Date, exceptionType: String?, signal: String?, terminationReason: String?, callStackTree: Data) { - self.id = UUID().uuidString - self.date = date - self.exceptionType = exceptionType - self.signal = signal - self.terminationReason = terminationReason - self.callStackData = callStackTree - } }