diff --git a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift index 370867d6c..40eaad9da 100644 --- a/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift +++ b/Sources/BrowserServicesKit/PrivacyConfig/Features/PrivacyFeature.swift @@ -30,6 +30,7 @@ public enum PrivacyFeature: String { case autoconsent case clickToLoad case autofill + case autofillBreakageReporter case ampLinks case trackingParameters case customUserAgent diff --git a/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReportEntry.swift b/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReportEntry.swift index 61e612465..9139ac32b 100644 --- a/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReportEntry.swift +++ b/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReportEntry.swift @@ -29,10 +29,10 @@ public struct BrokenSiteReportEntry { /// Object Creation + 30 days let expiryDate: Date? - public init?(report: BrokenSiteReport, currentDate: Date) { + public init?(report: BrokenSiteReport, currentDate: Date, daysToExpiry: Int) { guard let domainIdentifier = report.siteUrl.privacySafeDomainIdentifier, - let expiryDate = Calendar.current.date(byAdding: .day, value: 30, to: currentDate) else { + let expiryDate = Calendar.current.date(byAdding: .day, value: daysToExpiry, to: currentDate) else { return nil } self.identifier = domainIdentifier @@ -50,7 +50,7 @@ public struct BrokenSiteReportEntry { } -fileprivate extension URL { +public extension URL { /// A string containing the first 6 chars of the sha256 hash of the URL's domain part var privacySafeDomainIdentifier: String? { diff --git a/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReporter.swift b/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReporter.swift index 2ce4fc533..4af160149 100644 --- a/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReporter.swift +++ b/Sources/PrivacyDashboard/BrokenSiteReporting/BrokenSiteReporter.swift @@ -42,15 +42,17 @@ public class BrokenSiteReporter { public typealias PixelHandler = (_ parameters: [String: String]) -> Void /// Pixels are sent by the main apps not by BSK, this is the closure called by the class when a pixel need to be sent let pixelHandler: PixelHandler - let persistencyManager: ExpiryStorage + public let persistencyManager: ExpiryStorage - public init(pixelHandler: @escaping PixelHandler, keyValueStoring: KeyValueStoringDictionaryRepresentable) { + public init(pixelHandler: @escaping PixelHandler, + keyValueStoring: KeyValueStoringDictionaryRepresentable, + storageConfiguration: ExpiryStorageConfiguration = .defaultConfig) { self.pixelHandler = pixelHandler - self.persistencyManager = ExpiryStorage(keyValueStoring: keyValueStoring) + self.persistencyManager = ExpiryStorage(keyValueStoring: keyValueStoring, configuration: storageConfiguration) } /// Report the site breakage - public func report(_ report: BrokenSiteReport, reportMode: BrokenSiteReport.Mode) throws { + public func report(_ report: BrokenSiteReport, reportMode: BrokenSiteReport.Mode, daysToExpiry: Int = 30) throws { let now = Date() let removedCount = persistencyManager.removeExpiredItems(currentDate: now) @@ -61,7 +63,7 @@ public class BrokenSiteReporter { var report = report // Create history entry - guard let entry = BrokenSiteReportEntry(report: report, currentDate: now) else { + guard let entry = BrokenSiteReportEntry(report: report, currentDate: now, daysToExpiry: daysToExpiry) else { os_log(.error, "Failed to create a history entry for broken site report") throw BrokenSiteReporterError.failedToGenerateHistoryEntry } diff --git a/Sources/PrivacyDashboard/BrokenSiteReporting/ExpiryStorage.swift b/Sources/PrivacyDashboard/BrokenSiteReporting/ExpiryStorage.swift index 39d5dfb7f..8cb9fb871 100644 --- a/Sources/PrivacyDashboard/BrokenSiteReporting/ExpiryStorage.swift +++ b/Sources/PrivacyDashboard/BrokenSiteReporting/ExpiryStorage.swift @@ -21,22 +21,37 @@ import Persistence public typealias KeyValueStoringDictionaryRepresentable = KeyValueStoring & DictionaryRepresentable +public struct ExpiryStorageConfiguration { + + var expiryDatesStorageKey: String + var valueExpiryDateKey: String + var valueDataKey: String + + public static let defaultConfig = ExpiryStorageConfiguration( + expiryDatesStorageKey: "com.duckduckgo.UserDefaultExpiryStorage", + valueExpiryDateKey: "com.duckduckgo.UserDefaultExpiryStorage.valueExpiryDate", + valueDataKey: "com.duckduckgo.UserDefaultExpiryStorage.valueData" + ) + + public static let autofillConfig = ExpiryStorageConfiguration( + expiryDatesStorageKey: "com.duckduckgo.AutofillUserDefaultExpiryStorage", + valueExpiryDateKey: "com.duckduckgo.AutofillUserDefaultExpiryStorage.valueExpiryDate", + valueDataKey: "com.duckduckgo.AutofillUserDefaultExpiryStorage.valueData" + ) +} + /// A storage solution were each entry has an expiry date and a function for removing all expired entries is provided. /// Any persistency solution implementing `KeyValueStoringDictionaryRepresentable` can be used. public class ExpiryStorage { - enum Keys: String { - case expiryDatesStorage = "com.duckduckgo.UserDefaultExpiryStorage" - case valueExpiryDate = "com.duckduckgo.UserDefaultExpiryStorage.valueExpiryDate" - case valueData = "com.duckduckgo.UserDefaultExpiryStorage.valueData" - } - let localStorage: KeyValueStoringDictionaryRepresentable + let config: ExpiryStorageConfiguration /// Default initialiser /// - Parameter keyValueStoring: An object managing the persistency of the key-value pairs that implements `KeyValueStoringDictionaryRepresentable` - public init(keyValueStoring: KeyValueStoringDictionaryRepresentable) { + public init(keyValueStoring: KeyValueStoringDictionaryRepresentable, configuration: ExpiryStorageConfiguration = .defaultConfig) { self.localStorage = keyValueStoring + self.config = configuration } /// Store a value and the desired expiry date (or removes the value if nil is passed as the value) for the provided key @@ -46,11 +61,11 @@ public class ExpiryStorage { /// - expiryDate: A date stored alongside the value, used by `removeExpiredItems(...)` for removing expired values. public func set(value: Any?, forKey key: String, expiryDate: Date) { - let valueDic = [Keys.valueExpiryDate.rawValue: expiryDate, Keys.valueData.rawValue: value] + let valueDic = [config.valueExpiryDateKey: expiryDate, config.valueDataKey: value] localStorage.set(valueDic, forKey: key) } - /// - Returns: The stored value assiciated to the key, nil if not existent + /// - Returns: The stored value associated to the key, nil if not existent public func value(forKey key: String) -> Any? { return entry(forKey: key)?.value @@ -59,8 +74,8 @@ public class ExpiryStorage { /// - Returns: The tuple expiryDate+value associated to the key, nil if they don't exist public func entry(forKey key: String) -> (expiryDate: Date, value: Any)? { guard let valueDic = localStorage.object(forKey: key) as? [String: Any], - let expiryDate = valueDic[Keys.valueExpiryDate.rawValue] as? Date, - let value = valueDic[Keys.valueData.rawValue] + let expiryDate = valueDic[config.valueExpiryDateKey] as? Date, + let value = valueDic[config.valueDataKey] else { return nil } diff --git a/Tests/PrivacyDashboardTests/BrokenSiteReportHistoryEntryTests.swift b/Tests/PrivacyDashboardTests/BrokenSiteReportHistoryEntryTests.swift index 3d428922a..41b2be6df 100644 --- a/Tests/PrivacyDashboardTests/BrokenSiteReportHistoryEntryTests.swift +++ b/Tests/PrivacyDashboardTests/BrokenSiteReportHistoryEntryTests.swift @@ -21,9 +21,11 @@ import XCTest final class BrokenSiteReportHistoryEntryTests: XCTestCase { + private let daysToExpiry: Int = 30 + func testDates() throws { let testDate = Date(timeIntervalSince1970: 1704795829) - let entry = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate) + let entry = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate, daysToExpiry: daysToExpiry) XCTAssertNotNil(entry) XCTAssertEqual("2024-01-09", entry?.lastSentDayString) @@ -32,11 +34,11 @@ final class BrokenSiteReportHistoryEntryTests: XCTestCase { func testUniqueIdentifier() throws { let testDate = Date(timeIntervalSince1970: 1704795829) - let entry = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate) - let entry2 = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report2, currentDate: testDate) + let entry = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate, daysToExpiry: daysToExpiry) + let entry2 = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report2, currentDate: testDate, daysToExpiry: daysToExpiry) XCTAssertNotEqual(entry?.identifier, entry2?.identifier) - let entry3 = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate) + let entry3 = BrokenSiteReportEntry(report: BrokenSiteReportMocks.report, currentDate: testDate, daysToExpiry: daysToExpiry) XCTAssertEqual(entry?.identifier, entry3?.identifier) }