[iOS] FreemiumDBPUserStateManager implementation#4471
[iOS] FreemiumDBPUserStateManager implementation#4471jozsef-vesza wants to merge 20 commits intojozsef/freemium-pir-auth-gatefrom
Conversation
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…erStateManager Extracts lock/unlock calls from async methods into private synchronous helpers to silence Swift 6 unavailability warnings on NSLock in async contexts. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Also make DisabledFreemiumDBPUserStateManager public (with explicit init and public member/method declarations) so it can be used as a default argument value in the public BrokerProfileJobEventsHandler initializer. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
A transient database read error must not permanently record `.noMatches`: `firstScanResult` is first-write-wins, so a bogus value would stick forever. `handleScanCompletion` now returns early on error; a later successful scan records the correct outcome. Tests: strengthened authenticated cases to prove the completion callback actually ran (via `firstScanCompletedAndMatchesFoundFired`) rather than relying on a time-based wait against `firstScanResult`, which the auth case would satisfy by default. Added a test for the new DB-error path.
…est files - Restores SharedPackages/DataBrokerProtectionCore/Package.resolved to the auth-gate baseline (the content-scope-scripts / privacy-dashboard bumps already live in the earlier CSS-bump commit on main; they don't belong here). - Drops the stray '// DuckDuckGo' line from three new test file headers; SwiftLint's file_header rule flagged them for not matching the neighbor pattern.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 424b9f2. Configure here.
| Task { | ||
| await freemiumUserStateManager.recordProfileSavedIfNeeded() | ||
| onComplete(nil) | ||
| } |
There was a problem hiding this comment.
Fire-and-forget Task may delay didActivate write
Medium Severity
The .profileSaved handler wraps recordProfileSavedIfNeeded() in an unstructured fire-and-forget Task. In production, eventsHandler.fire(.profileSaved) is called without an onComplete handler, so the Task is never awaited. The caller (saveProfileAndPrepareForInitialScans) proceeds immediately to refreshFreeScanState() and then startImmediateScanOperations(). Since didActivate is set inside the unawaited Task, there's a race window where canRunFreemiumScans (which reads didActivate) evaluates to false even though the profile was just saved. The PR description states this flag "activates the auth-gate relaxation" so freemium users can run scans — if the flag isn't set in time, the scan could be blocked.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 424b9f2. Configure here.


Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1210938375848294
Tech Design URL:
CC: @THISISDINOSAUR
Description
Adds the concrete
DefaultFreemiumDBPUserStateManageron iOS and wires it into the PIR flow. Completing this subtask activates the auth-gate relaxation in #4461:didActivatenow flips totrueat profile-save, so freemium users can actually run scans.Production changes (stacked on top of
jozsef/freemium-pir-auth-gate):FreemiumDBPUserStateManagingadds read/write methods.DefaultFreemiumDBPUserStateManagerUserDefaults-backed default implementation.BrokerProfileJobEventsHandlerwill now update the state manager when profile is saved.handleScanCompletion()helper onDataBrokerProtectionIOSManager. Deduplicates the scan-completion logic across both scan paths and records the freemium scan result. Skips the write if the DB read fails, so a transient error can't permanently stamp the wrong result.Out of scope:
recordSubscriptionUpgradeIfNeeded()exists on the protocol and manager but has no production caller yet. This will come later when working on pixels.Testing Steps
Green CI, there are no entry points yet.
Impact and Risks
Low: No entry points + guarded by feature flag.
What could go wrong?
Quality Considerations
.dbpsuite, key namespaceios.browser.freemium.dbp.*— iOS-scoped so there can be no collision with macOS'smacos.browser.freemium.dbp.*keys.NSLock. Lock acquisition happens after the async auth check, so the lock is never held across a suspension point — avoids the Swift 6 concurrency checker error.macOS/LocalPackages/Freemium/FreemiumDBPUserStateManager.swift: simpler enum (FreemiumFirstScanResult) instead of macOS'sFreemiumDBPMatchResultsstruct; drops macOS-only notification/dismissal flags. Spec call: RMF on iOS can't template counts, so the struct adds no value there.Notes to Reviewer
jozsef/freemium-pir-auth-gate. Review that one first; I'll retarget this tomainonce it merges.Note
Medium Risk
Introduces new UserDefaults-backed freemium state that affects whether unauthenticated users can run scans and records first-scan results; mistakes could permanently persist incorrect state or change scan behavior across sessions.
Overview
Adds a concrete
DefaultFreemiumDBPUserStateManager(UserDefaults + lock) and expandsFreemiumDBPUserStateManagingto persist freemium activation, timestamps, and a first-scan result (noMatchesvsmatchesFound).Wires this state into production by instantiating it in
DBPService, passing it throughDataBrokerProtectionIOSManagerProvider, and updatingBrokerProfileJobEventsHandlerto record activation on.profileSaved.Refactors iOS scan completion into a shared
handleScanCompletion()used by both immediate scans and continued-processing scans; it triggers the existing matches-found event and records the first-scan result, but skips recording ifdatabase.hasMatches()throws to avoid permanently storing a wrong outcome. Adds extensive unit/integration tests and enhances repository mocks to controlhasMatches()return/throw behavior.Reviewed by Cursor Bugbot for commit 424b9f2. Bugbot is set up for automated code reviews on this repo. Configure here.