// ── Audit Logger ──────────────────────────────────────────── // Generic local audit logger that tracks user actions for debugging. // Stores events in a rotating JSON file (configurable max entries). // Product apps configure with a product-specific file name. import Foundation /// Audit event stored locally. public struct BLAuditEvent: Codable, Sendable { public let id: String public let action: String public let details: String? public let timestamp: Date public init(action: String, details: String? = nil) { self.id = UUID().uuidString self.action = action self.details = details self.timestamp = Date() } } /// Generic local audit logger for all ByteLyst iOS apps. /// Stores events in a rotating JSON file in the Documents directory. public enum BLAuditLogger { private static var maxEvents = 1000 private static var fileName = "audit_log.json" /// Configure the logger with a product-specific file name and max events. public static func configure(fileName: String = "audit_log.json", maxEvents: Int = 1000) { self.fileName = fileName self.maxEvents = maxEvents } private static var fileURL: URL { let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! return docs.appendingPathComponent(fileName) } /// Log a user action. public static func log(_ action: String, details: String? = nil) { let event = BLAuditEvent(action: action, details: details) var events = loadEvents() events.append(event) // Rotate: keep only the most recent maxEvents if events.count > maxEvents { events = Array(events.suffix(maxEvents)) } saveEvents(events) } /// Get all logged events (newest first). public static func getEvents(limit: Int = 100) -> [BLAuditEvent] { let events = loadEvents() return Array(events.suffix(limit).reversed()) } /// Clear all audit logs. public static func clear() { try? FileManager.default.removeItem(at: fileURL) } // MARK: - Persistence private static func loadEvents() -> [BLAuditEvent] { guard let data = try? Data(contentsOf: fileURL) else { return [] } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 return (try? decoder.decode([BLAuditEvent].self, from: data)) ?? [] } private static func saveEvents(_ events: [BLAuditEvent]) { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 guard let data = try? encoder.encode(events) else { return } try? data.write(to: fileURL, options: .atomic) } }