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

Fix #8521: ASA Custom Onboarding Feature Linkage #8591

Merged
merged 20 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
98aa6b5
Moving DAU logic to separate file outside scene delegate
soner-yuksel Dec 6, 2023
6c4b1d3
Handling server ping app first launch properly
soner-yuksel Dec 7, 2023
28649f7
Adding daily user ping user p3a consent terms
soner-yuksel Dec 11, 2023
4601c59
Adding trigger loading at last stage of onboarding on button consent
soner-yuksel Dec 13, 2023
4609c17
Adding welcome controller means to run dau referral call API
soner-yuksel Dec 15, 2023
8519a0d
Search Attribution Ad async api call
soner-yuksel Dec 18, 2023
59fd4c5
AttributionManager was created so daily active user and user referral…
soner-yuksel Dec 19, 2023
1164d8d
Generate Code is moved
soner-yuksel Dec 19, 2023
42cac3f
Adding notification for onboarding to browser controller
soner-yuksel Dec 20, 2023
5bf83a4
Adding fields to attribution data, sending ad feature link
soner-yuksel Dec 21, 2023
36b2665
Add Report API calls and link keyword data
soner-yuksel Dec 21, 2023
26974cf
Adding linage and showing proper feature handle screen
soner-yuksel Dec 22, 2023
f5568ad
Handling all problem with early API call errors and responses
soner-yuksel Jan 2, 2024
09c092d
Ad Install Attribution is changed to campaignId
soner-yuksel Jan 3, 2024
5f14908
Adding timeout to handle search ads
soner-yuksel Jan 3, 2024
526e31c
Campaign Identifier reals added
soner-yuksel Jan 3, 2024
8fd1225
Fixing small problems and rebase problems
soner-yuksel Jan 5, 2024
2f50527
Moving left BV classes folder structure
soner-yuksel Jan 5, 2024
032aefb
Pull Request Comments are addressed enum case and log problem
soner-yuksel Jan 5, 2024
b923721
Pull Request Comments are addressed append campaign keywords groups id
soner-yuksel Jan 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 9 additions & 20 deletions App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,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 @@ -202,30 +203,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
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.
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")
UrpLog.log("Failed to initialize user referral program")
// 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
}

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.shouldHandleUrpLookup = true
SceneDelegate.shouldHandleInstallAttributionFetch = 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 @@ -137,6 +137,7 @@ public class BrowserViewController: UIViewController {

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

/// Voice Search
Expand All @@ -162,6 +163,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 @@ -272,6 +274,7 @@ public class BrowserViewController: UIViewController {
public init(
windowId: UUID,
profile: Profile,
attributionManager: AttributionManager,
diskImageStore: DiskImageStore?,
braveCore: BraveCoreMain,
rewards: BraveRewards,
Expand All @@ -282,6 +285,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 @@ -948,6 +952,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
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 @@ -861,7 +864,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