learning_ai_common_plat/packages/swift-platform-sdk/Sources/BLCrashReporter.swift
saravanakumardb1 b068e4bc1a fix(swift-sdk): guard BLBiometricAuth and BLCrashReporter for watchOS compatibility
BLBiometricAuth imports LocalAuthentication (unavailable on watchOS).
BLCrashReporter imports MetricKit (unavailable on watchOS).
Package.swift declares watchOS 10+ as a platform target.

Fix: wrap both files in #if canImport() guards.
BLCrashReport model struct stays outside the guard (data-only, all platforms).
2026-02-28 22:51:26 -08:00

136 lines
4.6 KiB
Swift

// Crash Reporter
// Generic MetricKit-based crash and performance reporting.
// Stores crash diagnostics locally for debugging and feedback forms.
// Product apps configure with a product-specific persistence key.
// Not available on watchOS MetricKit is iOS/macOS only.
import Foundation
/// Crash report model stored locally.
/// Available on all platforms (data-only struct).
public struct BLCrashReport: Codable, Identifiable, Sendable {
public let id: String
public let date: Date
public let exceptionType: String?
public let signal: String?
public let terminationReason: String?
public let callStackData: Data?
public 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
}
}
#if canImport(MetricKit)
import MetricKit
/// Generic MetricKit crash reporter for all ByteLyst iOS/macOS apps.
/// Subscribes to MetricKit, stores crash reports in UserDefaults.
/// Not available on watchOS (MetricKit is iOS 13+ / macOS 12+ only).
@MainActor
public final class BLCrashReporter: NSObject, ObservableObject, MXMetricManagerSubscriber {
private let persistenceKey: String
private let maxReports: Int
@Published public var lastCrashReport: Date?
@Published public var diagnosticCount: Int = 0
public init(productId: String, maxReports: Int = 50) {
self.persistenceKey = "\(productId)-crash-reports"
self.maxReports = maxReports
super.init()
MXMetricManager.shared.add(self)
loadStats()
}
deinit {
MXMetricManager.shared.remove(self)
}
// MARK: - MXMetricManagerSubscriber
nonisolated public func didReceive(_ payloads: [MXMetricPayload]) {
// MetricKit delivers daily aggregated metrics no action needed by default
}
nonisolated public func didReceive(_ payloads: [MXDiagnosticPayload]) {
Task { @MainActor [weak self] in
guard let self else { return }
for payload in payloads {
self.processDiagnosticPayload(payload)
}
self.diagnosticCount += payloads.count
self.lastCrashReport = Date()
}
}
// MARK: - Public API
/// Get all stored crash reports.
public func loadCrashReports() -> [BLCrashReport] {
guard let data = UserDefaults.standard.data(forKey: persistenceKey),
let reports = try? JSONDecoder().decode([BLCrashReport].self, from: data) else {
return []
}
return reports
}
/// Clear all stored crash reports.
public func clearReports() {
UserDefaults.standard.removeObject(forKey: persistenceKey)
diagnosticCount = 0
}
// MARK: - Private
private func processDiagnosticPayload(_ payload: MXDiagnosticPayload) {
if let crashDiagnostics = payload.crashDiagnostics {
for crash in crashDiagnostics {
let report = BLCrashReport(
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 = BLCrashReport(
date: Date(),
exceptionType: nil,
signal: nil,
terminationReason: "Hang: \(hang.hangDuration.description)",
callStackTree: hang.callStackTree.jsonRepresentation()
)
storeCrashReport(report)
}
}
}
private func storeCrashReport(_ report: BLCrashReport) {
var reports = loadCrashReports()
reports.append(report)
if reports.count > maxReports {
reports = Array(reports.suffix(maxReports))
}
if let data = try? JSONEncoder().encode(reports) {
UserDefaults.standard.set(data, forKey: persistenceKey)
}
}
private func loadStats() {
diagnosticCount = loadCrashReports().count
}
}
#endif