mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-15 08:53:07 +09:00
320 lines
14 KiB
Swift
320 lines
14 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import Foundation
|
|
import AdSupport
|
|
import Shared
|
|
import Leanplum
|
|
|
|
private let LPAppIdKey = "LeanplumAppId"
|
|
private let LPProductionKeyKey = "LeanplumProductionKey"
|
|
private let LPDevelopmentKeyKey = "LeanplumDevelopmentKey"
|
|
private let AppRequestedUserNotificationsPrefKey = "applicationDidRequestUserNotificationPermissionPrefKey"
|
|
|
|
// FxA Custom Leanplum message template for A/B testing push notifications.
|
|
private struct LPMessage {
|
|
static let FxAPrePush = "FxA Prepush v1"
|
|
static let ArgAcceptAction = "Accept action"
|
|
static let ArgCancelAction = "Cancel action"
|
|
static let ArgTitleText = "Title.Text"
|
|
static let ArgTitleColor = "Title.Color"
|
|
static let ArgMessageText = "Message.Text"
|
|
static let ArgMessageColor = "Message.Color"
|
|
static let ArgAcceptButtonText = "Accept button.Text"
|
|
static let ArgCancelButtonText = "Cancel button.Text"
|
|
static let ArgCancelButtonTextColor = "Cancel button.Text color"
|
|
// These defaults are overridden though Leanplum webUI
|
|
static let DefaultAskToAskTitle = NSLocalizedString("Firefox Sync Requires Push", comment: "Default push to ask title")
|
|
static let DefaultAskToAskMessage = NSLocalizedString("Firefox will stay in sync faster with Push Notifications enabled.", comment: "Default push to ask message")
|
|
static let DefaultOkButtonText = NSLocalizedString("Enable Push", comment: "Default push alert ok button text")
|
|
static let DefaultLaterButtonText = NSLocalizedString("Don't Enable", comment: "Default push alert cancel button text")
|
|
}
|
|
|
|
private let log = Logger.browserLogger
|
|
|
|
enum LPEvent: String {
|
|
case firstRun = "E_First_Run"
|
|
case secondRun = "E_Second_Run"
|
|
case openedApp = "E_Opened_App"
|
|
case dismissedOnboarding = "E_Dismissed_Onboarding"
|
|
case openedLogins = "Opened Login Manager"
|
|
case openedBookmark = "E_Opened_Bookmark"
|
|
case openedNewTab = "E_Opened_New_Tab"
|
|
case openedPocketStory = "E_Opened_Pocket_Story"
|
|
case interactWithURLBar = "E_Interact_With_Search_URL_Area"
|
|
case savedBookmark = "E_Saved_Bookmark"
|
|
case openedTelephoneLink = "Opened Telephone Link"
|
|
case openedMailtoLink = "E_Opened_Mailto_Link"
|
|
case saveImage = "E_Download_Media_Saved_Image"
|
|
case savedLoginAndPassword = "E_Saved_Login_And_Password"
|
|
case clearPrivateData = "E_Cleared_Private_Data"
|
|
case downloadedFocus = "E_User_Downloaded_Focus"
|
|
case downloadedPocket = "E_User_Downloaded_Pocket"
|
|
case userSharedWebpage = "E_User_Tapped_Share_Button"
|
|
case signsInFxa = "E_User_Signed_In_To_FxA"
|
|
case useReaderView = "E_User_Used_Reader_View"
|
|
case trackingProtectionSettings = "E_Tracking_Protection_Settings_Changed"
|
|
}
|
|
|
|
struct LPAttributeKey {
|
|
static let focusInstalled = "Focus Installed"
|
|
static let klarInstalled = "Klar Installed"
|
|
static let signedInSync = "Signed In Sync"
|
|
static let mailtoIsDefault = "Mailto Is Default"
|
|
static let pocketInstalled = "Pocket Installed"
|
|
static let telemetryOptIn = "Telemetry Opt In"
|
|
}
|
|
|
|
struct MozillaAppSchemes {
|
|
static let focus = "firefox-focus"
|
|
static let focusDE = "firefox-klar"
|
|
static let pocket = "pocket"
|
|
}
|
|
|
|
private let supportedLocales = ["en_US", "de_DE", "en_GB", "en_CA", "en_AU", "zh_TW", "en_HK", "en_SG",
|
|
"fr_FR", "it_IT", "id_ID", "id_ID", "pt_BR", "pl_PL", "ru_RU", "es_ES", "es_MX"]
|
|
|
|
private struct LPSettings {
|
|
var appId: String
|
|
var developmentKey: String
|
|
var productionKey: String
|
|
}
|
|
|
|
class LeanPlumClient {
|
|
static let shared = LeanPlumClient()
|
|
|
|
// Setup
|
|
private weak var profile: Profile?
|
|
private var prefs: Prefs? { return profile?.prefs }
|
|
private var enabled: Bool = true
|
|
|
|
// This defines an external Leanplum varible to enable/disable FxA prepush dialogs.
|
|
// The primary result is having a feature flag controlled by Leanplum, and falling back
|
|
// to prompting with native push permissions.
|
|
private var useFxAPrePush: LPVar = LPVar.define("useFxAPrePush", with: false)
|
|
|
|
private func isPrivateMode() -> Bool {
|
|
// Need to be run on main thread since isInPrivateMode requires to be on the main thread.
|
|
assert(Thread.isMainThread)
|
|
return UIApplication.isInPrivateMode
|
|
}
|
|
|
|
func isLPEnabled() -> Bool {
|
|
return enabled && Leanplum.hasStarted()
|
|
}
|
|
|
|
static func shouldEnable(profile: Profile) -> Bool {
|
|
return AppConstants.MOZ_ENABLE_LEANPLUM && (profile.prefs.boolForKey(AppConstants.PrefSendUsageData) ?? true)
|
|
}
|
|
|
|
func setup(profile: Profile) {
|
|
self.profile = profile
|
|
}
|
|
|
|
fileprivate func start() {
|
|
guard let settings = getSettings(), supportedLocales.contains(Locale.current.identifier), !Leanplum.hasStarted() else {
|
|
enabled = false
|
|
log.error("LeanplumIntegration - Could not be started")
|
|
return
|
|
}
|
|
|
|
if UIDevice.current.name.contains("MozMMADev") {
|
|
log.info("LeanplumIntegration - Setting up for Development")
|
|
Leanplum.setDeviceId(UIDevice.current.identifierForVendor?.uuidString)
|
|
Leanplum.setAppId(settings.appId, withDevelopmentKey: settings.developmentKey)
|
|
} else {
|
|
log.info("LeanplumIntegration - Setting up for Production")
|
|
Leanplum.setAppId(settings.appId, withProductionKey: settings.productionKey)
|
|
}
|
|
|
|
Leanplum.syncResourcesAsync(true)
|
|
|
|
let attributes: [AnyHashable: Any] = [
|
|
LPAttributeKey.mailtoIsDefault: mailtoIsDefault(),
|
|
LPAttributeKey.focusInstalled: focusInstalled(),
|
|
LPAttributeKey.klarInstalled: klarInstalled(),
|
|
LPAttributeKey.pocketInstalled: pocketInstalled(),
|
|
LPAttributeKey.signedInSync: profile?.hasAccount() ?? false
|
|
]
|
|
|
|
self.setupCustomTemplates()
|
|
|
|
Leanplum.start(withUserId: nil, userAttributes: attributes, responseHandler: { _ in
|
|
self.track(event: .openedApp)
|
|
|
|
// We need to check if the app is a clean install to use for
|
|
// preventing the What's New URL from appearing.
|
|
if self.prefs?.intForKey(IntroViewControllerSeenProfileKey) == nil {
|
|
self.prefs?.setString(AppInfo.appVersion, forKey: LatestAppVersionProfileKey)
|
|
self.track(event: .firstRun)
|
|
} else if self.prefs?.boolForKey("SecondRun") == nil {
|
|
self.prefs?.setBool(true, forKey: "SecondRun")
|
|
self.track(event: .secondRun)
|
|
}
|
|
|
|
self.checkIfAppWasInstalled(key: PrefsKeys.HasFocusInstalled, isAppInstalled: self.focusInstalled(), lpEvent: .downloadedFocus)
|
|
self.checkIfAppWasInstalled(key: PrefsKeys.HasPocketInstalled, isAppInstalled: self.pocketInstalled(), lpEvent: .downloadedPocket)
|
|
})
|
|
}
|
|
|
|
// Events
|
|
func track(event: LPEvent, withParameters parameters: [String: AnyObject]? = nil) {
|
|
guard isLPEnabled() else {
|
|
return
|
|
}
|
|
ensureMainThread {
|
|
guard !self.isPrivateMode() else {
|
|
return
|
|
}
|
|
if let params = parameters {
|
|
Leanplum.track(event.rawValue, withParameters: params)
|
|
} else {
|
|
Leanplum.track(event.rawValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func set(attributes: [AnyHashable: Any]) {
|
|
guard isLPEnabled() else {
|
|
return
|
|
}
|
|
ensureMainThread {
|
|
if !self.isPrivateMode() {
|
|
Leanplum.setUserAttributes(attributes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func set(enabled: Bool) {
|
|
// Setting up Test Mode stops sending things to server.
|
|
if enabled { start() }
|
|
self.enabled = enabled
|
|
Leanplum.setTestModeEnabled(!enabled)
|
|
}
|
|
|
|
func isFxAPrePushEnabled() -> Bool {
|
|
return AppConstants.MOZ_FXA_LEANPLUM_AB_PUSH_TEST && useFxAPrePush.boolValue()
|
|
}
|
|
|
|
/*
|
|
This is used to determine if an app was installed after firefox was installed
|
|
*/
|
|
private func checkIfAppWasInstalled(key: String, isAppInstalled: Bool, lpEvent: LPEvent) {
|
|
// if no key is present. create one and set it.
|
|
// if the app is already installed then the flag will set true and the second block will never run
|
|
if self.prefs?.boolForKey(key) == nil {
|
|
self.prefs?.setBool(isAppInstalled, forKey: key)
|
|
}
|
|
// on a subsquent launch if the app is installed and the key is false then switch the flag to true
|
|
if !(self.prefs?.boolForKey(key) ?? false), isAppInstalled {
|
|
self.prefs?.setBool(isAppInstalled, forKey: key)
|
|
self.track(event: lpEvent)
|
|
}
|
|
}
|
|
|
|
private func canOpenApp(scheme: String) -> Bool {
|
|
return URL(string: "\(scheme)://").flatMap { UIApplication.shared.canOpenURL($0) } ?? false
|
|
}
|
|
|
|
private func focusInstalled() -> Bool {
|
|
return canOpenApp(scheme: MozillaAppSchemes.focus)
|
|
}
|
|
|
|
private func klarInstalled() -> Bool {
|
|
return canOpenApp(scheme: MozillaAppSchemes.focusDE)
|
|
}
|
|
|
|
private func pocketInstalled() -> Bool {
|
|
return canOpenApp(scheme: MozillaAppSchemes.pocket)
|
|
}
|
|
|
|
private func mailtoIsDefault() -> Bool {
|
|
return (prefs?.stringForKey(PrefsKeys.KeyMailToOption) ?? "mailto:") == "mailto:"
|
|
}
|
|
|
|
private func getSettings() -> LPSettings? {
|
|
let bundle = Bundle.main
|
|
guard let appId = bundle.object(forInfoDictionaryKey: LPAppIdKey) as? String,
|
|
let productionKey = bundle.object(forInfoDictionaryKey: LPProductionKeyKey) as? String,
|
|
let developmentKey = bundle.object(forInfoDictionaryKey: LPDevelopmentKeyKey) as? String else {
|
|
return nil
|
|
}
|
|
return LPSettings(appId: appId, developmentKey: developmentKey, productionKey: productionKey)
|
|
}
|
|
|
|
// This must be called before `Leanplum.start` in order to correctly setup
|
|
// custom message templates.
|
|
private func setupCustomTemplates() {
|
|
// These properties are exposed through the Leanplum web interface.
|
|
// Ref: https://github.com/Leanplum/Leanplum-iOS-Samples/blob/master/iOS_customMessageTemplates/iOS_customMessageTemplates/LPMessageTemplates.m
|
|
let args: [LPActionArg] = [
|
|
LPActionArg(named: LPMessage.ArgTitleText, with: LPMessage.DefaultAskToAskTitle),
|
|
LPActionArg(named: LPMessage.ArgTitleColor, with: UIColor.black),
|
|
LPActionArg(named: LPMessage.ArgMessageText, with: LPMessage.DefaultAskToAskMessage),
|
|
LPActionArg(named: LPMessage.ArgMessageColor, with: UIColor.black),
|
|
LPActionArg(named: LPMessage.ArgAcceptButtonText, with: LPMessage.DefaultOkButtonText),
|
|
LPActionArg(named: LPMessage.ArgCancelAction, withAction: nil),
|
|
LPActionArg(named: LPMessage.ArgCancelButtonText, with: LPMessage.DefaultLaterButtonText),
|
|
LPActionArg(named: LPMessage.ArgCancelButtonTextColor, with: UIColor.gray)
|
|
]
|
|
|
|
let responder: LeanplumActionBlock = { (context) -> Bool in
|
|
// Before proceeding, double check that Leanplum FxA prepush config value has been enabled.
|
|
if !self.isFxAPrePushEnabled() {
|
|
return false
|
|
}
|
|
|
|
guard let context = context else {
|
|
return false
|
|
}
|
|
|
|
// Don't display permission screen if they have already allowed/disabled push permissions
|
|
if self.prefs?.boolForKey(AppRequestedUserNotificationsPrefKey) ?? false {
|
|
FxALoginHelper.sharedInstance.readyForSyncing()
|
|
return false
|
|
}
|
|
|
|
// Present Alert View onto the current top view controller
|
|
let rootViewController = UIApplication.topViewController()
|
|
let alert = UIAlertController(title: context.stringNamed(LPMessage.ArgTitleText), message: context.stringNamed(LPMessage.ArgMessageText), preferredStyle: .alert)
|
|
|
|
alert.addAction(UIAlertAction(title: context.stringNamed(LPMessage.ArgCancelButtonText), style: .cancel, handler: { (action) -> Void in
|
|
// Log cancel event and call ready for syncing
|
|
context.runTrackedActionNamed(LPMessage.ArgCancelAction)
|
|
FxALoginHelper.sharedInstance.readyForSyncing()
|
|
}))
|
|
|
|
alert.addAction(UIAlertAction(title: context.stringNamed(LPMessage.ArgAcceptButtonText), style: .default, handler: { (action) -> Void in
|
|
// Log accept event and present push permission modal
|
|
context.runTrackedActionNamed(LPMessage.ArgAcceptAction)
|
|
FxALoginHelper.sharedInstance.requestUserNotifications(UIApplication.shared)
|
|
self.prefs?.setBool(true, forKey: AppRequestedUserNotificationsPrefKey)
|
|
}))
|
|
|
|
rootViewController?.present(alert, animated: true, completion: nil)
|
|
return true
|
|
}
|
|
|
|
// Register or update the custom Leanplum message
|
|
Leanplum.defineAction(LPMessage.FxAPrePush, of: kLeanplumActionKindMessage, withArguments: args, withOptions: [:], withResponder: responder)
|
|
}
|
|
}
|
|
|
|
extension UIApplication {
|
|
// Extension to get the current top most view controller
|
|
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
|
|
if let nav = base as? UINavigationController {
|
|
return topViewController(base: nav.visibleViewController)
|
|
}
|
|
if let tab = base as? UITabBarController {
|
|
if let selected = tab.selectedViewController {
|
|
return topViewController(base: selected)
|
|
}
|
|
}
|
|
if let presented = base?.presentedViewController {
|
|
return topViewController(base: presented)
|
|
}
|
|
return base
|
|
}
|
|
}
|