Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Update AutofillPixelReporter to capture iOS credential provider extension events #1177

Merged
merged 7 commits into from
Feb 1, 2025
33 changes: 30 additions & 3 deletions Sources/BrowserServicesKit/Autofill/AutofillPixelReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ public enum AutofillPixelEvent {

public final class AutofillPixelReporter {

enum Keys {
public enum Keys {
static let autofillSearchDauDateKey = "com.duckduckgo.app.autofill.SearchDauDate"
static let autofillFillDateKey = "com.duckduckgo.app.autofill.FillDate"
static let autofillOnboardedUserKey = "com.duckduckgo.app.autofill.OnboardedUser"
static public let autofillDauMigratedKey = "com.duckduckgo.app.autofill.DauDataMigrated"
}

enum BucketName: String {
Expand Down Expand Up @@ -69,23 +70,28 @@ public final class AutofillPixelReporter {
private var autofillSearchDauDate: Date? { userDefaults.object(forKey: Keys.autofillSearchDauDateKey) as? Date ?? .distantPast }
private var autofillFillDate: Date? { userDefaults.object(forKey: Keys.autofillFillDateKey) as? Date ?? .distantPast }
private var autofillOnboardedUser: Bool { userDefaults.object(forKey: Keys.autofillOnboardedUserKey) as? Bool ?? false }
private var autofillDauMigrated: Bool { userDefaults.object(forKey: Keys.autofillDauMigratedKey) as? Bool ?? false }

public init(userDefaults: UserDefaults,
public init(standardUserDefaults: UserDefaults,
appGroupUserDefaults: UserDefaults?,
autofillEnabled: Bool,
eventMapping: EventMapping<AutofillPixelEvent>,
secureVault: (any AutofillSecureVault)? = nil,
reporter: SecureVaultReporting? = nil,
passwordManager: PasswordManager? = nil,
installDate: Date? = nil
) {
self.userDefaults = userDefaults
self.userDefaults = appGroupUserDefaults ?? standardUserDefaults
self.autofillEnabled = autofillEnabled
self.eventMapping = eventMapping
self.secureVault = secureVault
self.reporter = reporter
self.passwordManager = passwordManager
self.installDate = installDate

if let appGroupUserDefaults {
migrateDataIfNeeded(from: standardUserDefaults, to: appGroupUserDefaults)
}
createNotificationObservers()
}

Expand All @@ -105,6 +111,27 @@ public final class AutofillPixelReporter {
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveSaveEvent), name: .autofillSaveEvent, object: nil)
}

private func migrateDataIfNeeded(from source: UserDefaults, to destination: UserDefaults) {
guard autofillDauMigrated == false else {
return
}

let keysToMigrate = [
Keys.autofillSearchDauDateKey,
Keys.autofillFillDateKey,
Keys.autofillOnboardedUserKey
]

for key in keysToMigrate {
if let value = source.object(forKey: key) {
destination.set(value, forKey: key)
source.removeObject(forKey: key) // Delete from source after migration
}
}

destination.set(true, forKey: Keys.autofillDauMigratedKey)
}

@objc
private func didReceiveSearchDAU() {
guard let autofillSearchDauDate = autofillSearchDauDate, !Date.isSameDay(Date(), autofillSearchDauDate) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@
private var mockKeystoreProvider = MockKeystoreProvider()
private var vault: (any AutofillSecureVault)!
private var eventMapping: MockEventMapping!
private var userDefaults: UserDefaults!
private let testGroupName = "autofill-reporter"
private var standardDefaults: UserDefaults!
private var appGroupDefaults: UserDefaults!
private let testStandardName = "autofill-reporter"
private let testGroupName = "autofill-reporter-group"

override func setUpWithError() throws {
try super.setUpWithError()

userDefaults = UserDefaults(suiteName: testGroupName)!
userDefaults.removePersistentDomain(forName: testGroupName)
standardDefaults = UserDefaults(suiteName: testStandardName)!
appGroupDefaults = UserDefaults(suiteName: testGroupName)!
standardDefaults.removePersistentDomain(forName: testStandardName)
appGroupDefaults.removePersistentDomain(forName: testGroupName)

let providers = SecureStorageProviders(crypto: mockCryptoProvider,
database: mockDatabaseProvider,
Expand All @@ -78,7 +82,8 @@
override func tearDownWithError() throws {
vault = nil
eventMapping = nil
userDefaults.removePersistentDomain(forName: testGroupName)
standardDefaults.removePersistentDomain(forName: testStandardName)
appGroupDefaults.removePersistentDomain(forName: testGroupName)

try super.tearDownWithError()
}
Expand Down Expand Up @@ -459,7 +464,7 @@
func testWhenSaveAndUserIsAlreadyOnboardedThenOnboardedUserPixelShouldNotBeFired() {
let autofillPixelReporter = createAutofillPixelReporter(installDate: Date().addingTimeInterval(.days(-1)))
autofillPixelReporter.resetStoreDefaults()
userDefaults.set(true, forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey)
standardDefaults.set(true, forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey)

NotificationCenter.default.post(name: .autofillSaveEvent, object: nil)

Expand Down Expand Up @@ -510,8 +515,53 @@
XCTAssertTrue(onboardedState)
}

private func createAutofillPixelReporter(installDate: Date? = Date(), autofillEnabled: Bool = true) -> AutofillPixelReporter {
return AutofillPixelReporter(userDefaults: userDefaults,
func testWhenMigrationRequiredAndDataExistsThenDataIsMigratedToAppGroupUserDefaults() {
let testDate = Date()
standardDefaults.set(testDate, forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey)
standardDefaults.set(testDate, forKey: AutofillPixelReporter.Keys.autofillFillDateKey)
standardDefaults.set(true, forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey)

let autofillPixelReporter = createAutofillPixelReporter(appGroupUserDefaults: appGroupDefaults)

Check warning on line 524 in Tests/BrowserServicesKitTests/Autofill/AutofillPixelReporterTests.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (iOS)

initialization of immutable value 'autofillPixelReporter' was never used; consider replacing with assignment to '_' or removing it

XCTAssertEqual(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey) as? Date, testDate)
XCTAssertEqual(appGroupDefaults.bool(forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey), true)
XCTAssertNil(standardDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey))
XCTAssertTrue(appGroupDefaults.bool(forKey: AutofillPixelReporter.Keys.autofillDauMigratedKey))
}

func testWhenMigrationRequiredAndNoDataExistsThenMigratedKeyIsTrue() {
let autofillPixelReporter = createAutofillPixelReporter(appGroupUserDefaults: appGroupDefaults)

Check warning on line 533 in Tests/BrowserServicesKitTests/Autofill/AutofillPixelReporterTests.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (iOS)

initialization of immutable value 'autofillPixelReporter' was never used; consider replacing with assignment to '_' or removing it

XCTAssertTrue(appGroupDefaults.bool(forKey: AutofillPixelReporter.Keys.autofillDauMigratedKey))
XCTAssertNil(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey))
XCTAssertNil(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillFillDateKey))
XCTAssertNil(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey))
}

func testWhenMigrationCompleteThenMigrationDoesNotSecondTime() {
let testDate = Date()
standardDefaults.set(testDate, forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey)
appGroupDefaults.set(true, forKey: AutofillPixelReporter.Keys.autofillDauMigratedKey)

let autofillPixelReporter = createAutofillPixelReporter(appGroupUserDefaults: appGroupDefaults)

Check warning on line 546 in Tests/BrowserServicesKitTests/Autofill/AutofillPixelReporterTests.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (iOS)

initialization of immutable value 'autofillPixelReporter' was never used; consider replacing with assignment to '_' or removing it

XCTAssertNotNil(standardDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey))
XCTAssertNil(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey))
}

func testWhenNilAppGroupUserDefaultsProvidedThenNoMigrationOccurs() {
let testDate = Date()
standardDefaults.set(testDate, forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey)

let autofillPixelReporter = createAutofillPixelReporter(appGroupUserDefaults: nil)

Check warning on line 556 in Tests/BrowserServicesKitTests/Autofill/AutofillPixelReporterTests.swift

View workflow job for this annotation

GitHub Actions / Run unit tests (iOS)

initialization of immutable value 'autofillPixelReporter' was never used; consider replacing with assignment to '_' or removing it

XCTAssertNotNil(standardDefaults.object(forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey))
XCTAssertNil(appGroupDefaults.object(forKey: AutofillPixelReporter.Keys.autofillDauMigratedKey))
}

private func createAutofillPixelReporter(appGroupUserDefaults: UserDefaults? = nil, installDate: Date? = Date(), autofillEnabled: Bool = true) -> AutofillPixelReporter {
return AutofillPixelReporter(standardUserDefaults: standardDefaults,
appGroupUserDefaults: appGroupUserDefaults,
autofillEnabled: autofillEnabled,
eventMapping: eventMapping,
secureVault: vault,
Expand Down Expand Up @@ -562,16 +612,16 @@

private func setAutofillSearchDauDate(daysAgo: Int) {
let date = Date().addingTimeInterval(.days(-daysAgo))
userDefaults.set(date, forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey)
standardDefaults.set(date, forKey: AutofillPixelReporter.Keys.autofillSearchDauDateKey)
}

private func setAutofillFillDate(daysAgo: Int) {
let date = Date().addingTimeInterval(.days(-daysAgo))
userDefaults.set(date, forKey: AutofillPixelReporter.Keys.autofillFillDateKey)
standardDefaults.set(date, forKey: AutofillPixelReporter.Keys.autofillFillDateKey)
}

private func getAutofillOnboardedUserState() -> Bool? {
return userDefaults.object(forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey) as? Bool
return standardDefaults.object(forKey: AutofillPixelReporter.Keys.autofillOnboardedUserKey) as? Bool
}

}
Loading