Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #8521: ASA Custom Onboarding Feature Linkage (#8591)
Browse files Browse the repository at this point in the history
  • Loading branch information
soner-yuksel committed Feb 28, 2024
1 parent fef91c0 commit 388b50a
Show file tree
Hide file tree
Showing 39 changed files with 566 additions and 163 deletions.
34 changes: 11 additions & 23 deletions App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let isFirstLaunch = Preferences.General.isFirstLaunch.value

Preferences.AppState.isOnboardingActive.value = isFirstLaunch
Preferences.AppState.dailyUserPingAwaitingUserConsent.value = isFirstLaunch

if Preferences.Onboarding.basicOnboardingCompleted.value == OnboardingState.undetermined.rawValue {
Preferences.Onboarding.basicOnboardingCompleted.value =
Expand Down Expand Up @@ -205,31 +206,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Always load YouTube in Brave for new users
Preferences.General.keepYouTubeInBrave.value = true
}

if UserReferralProgram.shared != nil {
if Preferences.URP.referralLookupOutstanding.value == nil {
// This preference has never been set, and this means it is a new or upgraded user.
// That distinction must be made to know if a network request for ref-code look up should be made.

// Setting this to an explicit value so it will never get overwritten on subsequent launches.
// Upgrade users should not have ref code ping happening.
Preferences.URP.referralLookupOutstanding.value = isFirstLaunch
}

SceneDelegate.shouldHandleUrpLookup = true
} else {
log.error("Failed to initialize user referral program")
DebugLogger.log(for: .urp, text: "Failed to initialize user referral program")
}

if Preferences.URP.installAttributionLookupOutstanding.value == nil {
// Similarly to referral lookup, this prefrence should be set if it is a new user
// Trigger install attribution fetch only first launch
Preferences.URP.installAttributionLookupOutstanding.value = isFirstLaunch

SceneDelegate.shouldHandleInstallAttributionFetch = true

if Preferences.URP.referralLookupOutstanding.value == nil {
// This preference has never been set, and this means it is a new or upgraded user.
// That distinction must be made to know if a network request for ref-code look up should be made.
// Setting this to an explicit value so it will never get overwritten on subsequent launches.
// Upgrade users should not have ref code ping happening.
Preferences.URP.referralLookupOutstanding.value = isFirstLaunch
}

SceneDelegate.shouldHandleUrpLookup = true
SceneDelegate.shouldHandleInstallAttributionFetch = true

#if canImport(BraveTalk)
BraveTalkJitsiCoordinator.sendAppLifetimeEvent(
.didFinishLaunching(options: launchOptions ?? [:])
Expand Down
104 changes: 40 additions & 64 deletions App/iOS/Delegates/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

let attributionManager = AttributionManager(dau: AppState.shared.dau, urp: UserReferralProgram.shared)

let browserViewController = createBrowserWindow(
scene: windowScene,
braveCore: AppState.shared.braveCore,
profile: AppState.shared.profile,
attributionManager: attributionManager,
diskImageStore: AppState.shared.diskImageStore,
migration: AppState.shared.migration,
rewards: AppState.shared.rewards,
Expand Down Expand Up @@ -89,18 +92,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Handle URP Lookup at first launch
if SceneDelegate.shouldHandleUrpLookup {
SceneDelegate.shouldHandleUrpLookup = false

if let urp = UserReferralProgram.shared {
browserViewController.handleReferralLookup(urp)
}
}

// Handle Install Attribution Fetch at first launch
if SceneDelegate.shouldHandleInstallAttributionFetch {
SceneDelegate.shouldHandleInstallAttributionFetch = false

if let urp = UserReferralProgram.shared {
browserViewController.handleSearchAdsInstallAttribution(urp)
attributionManager.handleReferralLookup { [weak browserViewController] url in
browserViewController?.openReferralLink(url: url)
}
}

Expand All @@ -118,6 +112,38 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
windowScene: windowScene,
connectionOptions: connectionOptions
)

// Handle Install Attribution Fetch at first launch
if SceneDelegate.shouldHandleInstallAttributionFetch {
SceneDelegate.shouldHandleInstallAttributionFetch = false

// First time user should send dau ping after onboarding last stage _ p3a consent screen
// The reason p3a user consent is necesserray to call search ad install attribution API methods
if !Preferences.AppState.dailyUserPingAwaitingUserConsent.value {
// If P3A is not enabled, send the organic install code at daily pings which is BRV001
// User has not opted in to share completely private and anonymous product insights
if AppState.shared.braveCore.p3aUtils.isP3AEnabled {
Task { @MainActor in
do {
try await attributionManager.handleSearchAdsInstallAttribution()
} catch {
Logger.module.debug("Error fetching ads attribution default code is sent \(error)")
// Sending default organic install code for dau
attributionManager.setupReferralCodeAndPingServer()
}
}
} else {
// Sending default organic install code for dau
attributionManager.setupReferralCodeAndPingServer()
}
}
}

if Preferences.URP.installAttributionLookupOutstanding.value == nil {
// Similarly to referral lookup, this prefrence should be set if it is a new user
// Trigger install attribution fetch only first launch
Preferences.URP.installAttributionLookupOutstanding.value = Preferences.General.isFirstLaunch.value
}

PrivacyReportsManager.scheduleNotification(debugMode: !AppConstants.buildChannel.isPublic)
PrivacyReportsManager.consolidateData()
Expand Down Expand Up @@ -212,7 +238,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// We try to send DAU ping each time the app goes to foreground to work around network edge cases
// (offline, bad connection etc.).
// Also send the ping only after the URP lookup and install attribution has processed.
if Preferences.URP.referralLookupOutstanding.value == false, Preferences.URP.installAttributionLookupOutstanding.value == false {
if Preferences.URP.referralLookupOutstanding.value == true, Preferences.URP.installAttributionLookupOutstanding.value == true {
AppState.shared.dau.sendPingToServer()
}

Expand Down Expand Up @@ -422,6 +448,7 @@ extension SceneDelegate {
private func createBrowserWindow(scene: UIWindowScene,
braveCore: BraveCoreMain,
profile: Profile,
attributionManager: AttributionManager,
diskImageStore: DiskImageStore?,
migration: Migration?,
rewards: Brave.BraveRewards,
Expand Down Expand Up @@ -480,6 +507,7 @@ extension SceneDelegate {
let browserViewController = BrowserViewController(
windowId: windowId,
profile: profile,
attributionManager: attributionManager,
diskImageStore: diskImageStore,
braveCore: braveCore,
rewards: rewards,
Expand Down Expand Up @@ -574,58 +602,6 @@ extension SceneDelegate: UIViewControllerRestoration {
}
}

extension BrowserViewController {
func handleReferralLookup(_ urp: UserReferralProgram) {
if Preferences.URP.referralLookupOutstanding.value == true {
performProgramReferralLookup(urp, refCode: UserReferralProgram.getReferralCode())
} else {
urp.pingIfEnoughTimePassed()
}
}

func handleSearchAdsInstallAttribution(_ urp: UserReferralProgram) {
urp.adCampaignLookup() { [weak self] response, error in
guard let self = self else { return }

let refCode = self.generateReferralCode(attributionData: response, fetchError: error)
// Setting up referral code value
// This value should be set before first DAU ping
Preferences.URP.referralCode.value = refCode
Preferences.URP.installAttributionLookupOutstanding.value = false
}
}

private func generateReferralCode(attributionData: AdAttributionData?, fetchError: Error?) -> String {
// Prefix code "001" with BRV for organic iOS installs
var referralCode = "BRV001"

if fetchError == nil, attributionData?.attribution == true, let campaignId = attributionData?.campaignId {
// Adding ASA User refcode prefix to indicate
// Apple Ads Attribution is true
referralCode = "ASA\(String(campaignId))"
}

return referralCode
}

private func performProgramReferralLookup(_ urp: UserReferralProgram, refCode: String?) {
urp.referralLookup(refCode: refCode) { referralCode, offerUrl in
// Attempting to send ping after first urp lookup.
// This way we can grab the referral code if it exists, see issue #2586.
if Preferences.URP.installAttributionLookupOutstanding.value == false {
AppState.shared.dau.sendPingToServer()
}
let retryTime = AppConstants.buildChannel.isPublic ? 1.days : 10.minutes
let retryDeadline = Date() + retryTime

Preferences.NewTabPage.superReferrerThemeRetryDeadline.value = retryDeadline

guard let url = offerUrl?.asURL else { return }
self.openReferralLink(url: url)
}
}
}

extension UIWindowScene {
/// A single scene should only have ONE browserViewController
/// However, it is possible that someone can create multiple,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ extension BrowserViewController {
self?.dismiss(animated: false)
}
)
), p3aUtilities: braveCore.p3aUtils
),
p3aUtilities: braveCore.p3aUtils,
attributionManager: attributionManager
)

present(onboardingController, animated: true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ extension BrowserViewController {
rewards: self.rewards,
windowProtection: self.windowProtection,
braveCore: self.braveCore,
attributionManager: attributionManager,
keyringStore: keyringStore,
cryptoStore: cryptoStore
)
Expand All @@ -207,7 +208,7 @@ extension BrowserViewController {
}
}

private func presentPlaylistController() {
public func presentPlaylistController() {
if PlaylistCarplayManager.shared.isPlaylistControllerPresented {
let alert = UIAlertController(title: Strings.PlayList.playlistAlreadyShowingTitle,
message: Strings.PlayList.playlistAlreadyShowingBody,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ extension BrowserViewController {
// 2. User hasn't completed onboarding
if Preferences.Onboarding.basicOnboardingCompleted.value != OnboardingState.completed.rawValue,
Preferences.Onboarding.isNewRetentionUser.value == true {
let onboardingController = WelcomeViewController(p3aUtilities: braveCore.p3aUtils)
let onboardingController = WelcomeViewController(p3aUtilities: braveCore.p3aUtils, attributionManager: attributionManager)
onboardingController.modalPresentationStyle = .fullScreen
parentController.present(onboardingController, animated: false)
isOnboardingOrFullScreenCalloutPresented = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public class BrowserViewController: UIViewController {

private var privateModeCancellable: AnyCancellable?
private var appReviewCancelable: AnyCancellable?
private var adFeatureLinkageCancelable: AnyCancellable?
var onPendingRequestUpdatedCancellable: AnyCancellable?

/// Voice Search
Expand All @@ -163,6 +164,7 @@ public class BrowserViewController: UIViewController {

public let windowId: UUID
let profile: Profile
let attributionManager: AttributionManager
let braveCore: BraveCoreMain
let tabManager: TabManager
let migration: Migration?
Expand Down Expand Up @@ -273,6 +275,7 @@ public class BrowserViewController: UIViewController {
public init(
windowId: UUID,
profile: Profile,
attributionManager: AttributionManager,
diskImageStore: DiskImageStore?,
braveCore: BraveCoreMain,
rewards: BraveRewards,
Expand All @@ -283,6 +286,7 @@ public class BrowserViewController: UIViewController {
) {
self.windowId = windowId
self.profile = profile
self.attributionManager = attributionManager
self.braveCore = braveCore
self.bookmarkManager = BookmarkManager(bookmarksAPI: braveCore.bookmarksAPI)
self.rewards = rewards
Expand Down Expand Up @@ -949,6 +953,21 @@ public class BrowserViewController: UIViewController {
}
})

adFeatureLinkageCancelable = attributionManager
.$adFeatureLinkage
.removeDuplicates()
.sink(receiveValue: { [weak self] featureLinkageType in
guard let self = self else { return }
switch featureLinkageType {
case .playlist:
self.presentPlaylistController()
case .vpn:
self.navigationHelper.openVPNBuyScreen(iapObserver: self.iapObserver)
default:
return
}
})

Preferences.General.isUsingBottomBar.objectWillChange
.receive(on: RunLoop.main)
.sink { [weak self] _ in
Expand Down
4 changes: 0 additions & 4 deletions Sources/Brave/Frontend/ClientPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,6 @@ extension Preferences {
/// List of currently installed themes on the device.
static let installedCustomThemes =
Option<[String]>(key: "newtabpage.installed-custom-themes", default: [])

/// Tells the app whether we should try to fetch super referrer assets again in case of network error.
public static let superReferrerThemeRetryDeadline =
Option<Date?>(key: "newtabpage.superreferrer-retry-deadline", default: nil)

/// Tells the app whether we should show Privacy Hub in new tab page view controller
public static let showNewTabPrivacyHub =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import UIKit
import BraveUI
import Onboarding
import BraveCore
import Growth

class RetentionPreferencesDebugMenuViewController: TableViewController {
private let p3aUtilities: BraveP3AUtils
private let attributionManager: AttributionManager

init(p3aUtilities: BraveP3AUtils) {
init(p3aUtilities: BraveP3AUtils, attributionManager: AttributionManager) {
self.p3aUtilities = p3aUtilities
self.attributionManager = attributionManager

super.init(style: .insetGrouped)
}

Expand Down Expand Up @@ -55,7 +59,7 @@ class RetentionPreferencesDebugMenuViewController: TableViewController {
.init(
text: "Start Onboarding",
selection: { [unowned self] in
let onboardingController = WelcomeViewController(state: .loading, p3aUtilities: self.p3aUtilities)
let onboardingController = WelcomeViewController(state: .loading, p3aUtilities: self.p3aUtilities, attributionManager: attributionManager)
onboardingController.modalPresentationStyle = .fullScreen

present(onboardingController, animated: false)
Expand Down
7 changes: 6 additions & 1 deletion Sources/Brave/Frontend/Settings/SettingsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class SettingsViewController: TableViewController {
private let syncAPI: BraveSyncAPI
private let syncProfileServices: BraveSyncProfileServiceIOS
private let p3aUtilities: BraveP3AUtils
private let attributionManager: AttributionManager
private let keyringStore: KeyringStore?
private let cryptoStore: CryptoStore?
private let windowProtection: WindowProtection?
Expand All @@ -73,6 +74,7 @@ class SettingsViewController: TableViewController {
rewards: BraveRewards? = nil,
windowProtection: WindowProtection?,
braveCore: BraveCoreMain,
attributionManager: AttributionManager,
keyringStore: KeyringStore? = nil,
cryptoStore: CryptoStore? = nil
) {
Expand All @@ -86,6 +88,7 @@ class SettingsViewController: TableViewController {
self.syncAPI = braveCore.syncAPI
self.syncProfileServices = braveCore.syncProfileService
self.p3aUtilities = braveCore.p3aUtils
self.attributionManager = attributionManager
self.keyringStore = keyringStore
self.cryptoStore = cryptoStore
self.ipfsAPI = braveCore.ipfsAPI
Expand Down Expand Up @@ -885,7 +888,9 @@ class SettingsViewController: TableViewController {
Row(
text: "Retention Preferences Debug Menu",
selection: { [unowned self] in
self.navigationController?.pushViewController(RetentionPreferencesDebugMenuViewController(p3aUtilities: p3aUtilities), animated: true)
self.navigationController?.pushViewController(
RetentionPreferencesDebugMenuViewController(p3aUtilities: p3aUtilities, attributionManager: attributionManager),
animated: true)
}, accessory: .disclosureIndicator, cellClass: MultilineValue1Cell.self),
Row(
text: "Load all QA Links",
Expand Down
Loading

0 comments on commit 388b50a

Please sign in to comment.