Загрузка данных
import UIKit
import WidgetKit
import SBColorKit
import UserNotifications
import NotificationCenter
import CerberSDK
import Dynatrace
import CoreDataKit
import SBModelKit
import SBGlobalKit
import SBAnalyticsKit
import SBImageResourcesKit
#if DEBUG
import ClientCertificateKit
#endif
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var count = 1
let cerber = Cerber.shared
private(set) var cerberEnable: Bool = false
/// Так как Cerber не сохраняет состояние авторизации, то пришлось использовать этот Bool
private var isAuthSuccess: Bool = false
/// Задача по извлечению SecIdentity из хранилища CerberSDK
private var getSecTask: Task<Void, Never>?
private var registrationCompleted = false
private var dataDeleting = false {
didSet {
guard !dataDeleting else { return }
checkLoginState()
}
}
func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
configurationCerber()
Task {
await constructRemoteNotifications(application)
}
return true
}
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
AppDelegateHelper.check(status: .appVersion(version: "10_05_020",
memoryTypeList: [.all]))
configureAnalyticsManager()
startDynatrace()
constructNavigationBar()
logSystemWidgetSize()
/// Выводим Launch
AppCoordinator.shared.startLaunch()
// MARK: Костыли из-за ngam-а
// #if DEBUG
// cerberEnable = false
// var rootViewController: UIViewController?
// if #available(iOS 15.0, *) {
// rootViewController = application.delegate?.window??.rootViewController
// } else {
// rootViewController = application.windows.first?.rootViewController
// }
// let directory = ClientCertificateManager.getDocumentDirectory()
// debugPrint("Каталог для размещения сертификата: \(directory)")
//
// if let password = ClientCertificateManager.getTextPassword(),
// let identity = try? ClientCertificateManager.checkForAvailableCertificate(with: password) {
// // Заменяем пользовательский Certificate and SecIdentity
// Task { @MainActor in
// await AppCoordinator.shared.update(data: nil, identity: identity)
// await AppCoordinator.shared.startAuthorization()
// }
// } else {
// let viewController = ClientCertificateKit.CCMInfoViewController()
// viewController.successHandler = { password in
// ClientCertificateManager.saveText(password: password)
// if let identity = try? ClientCertificateManager.checkForAvailableCertificate(with: password) {
// // Заменяем пользовательский Certificate and SecIdentity
// Task { @MainActor in
// await AppCoordinator.shared.update(data: nil, identity: identity)
// await AppCoordinator.shared.startAuthorization()
// }
// }
// rootViewController?.dismiss(animated: true)
// }
//
// Task { @MainActor in
// try? await Task.sleep(nanoseconds: 2_000_000_000)
// rootViewController?.present(viewController, animated: true)
// }
// }
// #else
/// Стартуем CerberSDK
cerber.start()
// #endif
NotificationCenter.default.addObserver(
self,
selector: #selector(methodOfReceivedExitNotification(_:)),
name: Notification.Name("SBH_EXIT_NOTIFICATION"),
object: nil
)
application.setIconBadgeNumber(.zero)
// Hide Autolayout Warning
UserDefaults.standard.set(true, forKey: "_UIConstraintBasedLayoutLogUnsatisfiing")
// миграция на новое название приоритета, убрать когда все пользователи перейдут
updateVeryImportantPriorityInDefaults()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppDelegateManager.shared.applicationDidBecomeActive(application)
}
func applicationWillResignActive(_ application: UIApplication) {
AppDelegateManager.shared.applicationWillResignActive(application)
}
func applicationWillEnterForeground(_ application: UIApplication) {
startDynatrace()
Task {
_ = try await AppCoordinator.shared.updateStateBeforeApplicationBecomeActive()
await MainActor.run {
AppDelegateManager.shared.applicationWillEnterForeground(application)
}
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
Task {
try? await CoreDataKit.CoreDataManager.shared.saveAllContexts()
}
cleanLoggedSystemWidgetSizes()
WidgetCenter.shared.reloadAllTimelines()
updateAppBadge(application)
AppDelegateManager.shared.applicationDidEnterBackground(application)
Dynatrace.endVisit()
Dynatrace.shutdown()
}
func applicationWillTerminate(_ application: UIApplication) {
AppDelegateManager.shared.applicationWillTerminate(application)
}
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme == "open" { return true }
AppCoordinator.open(url: url)
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
AppCoordinator.pushDeviceTokenData = deviceToken
}
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
AppCoordinator.processUserNotificationAction(identifier: userInfo)
completionHandler(.newData)
}
func application(
_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?
) -> UIInterfaceOrientationMask {
return AppDelegateHelper.supportedInterfaceOrientationsFor(window: window)
}
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true)
else {
return false
}
guard let path = components.path, let params = components.queryItems else {
return false
}
if path == "/sber911/incidents/" {
if let value = params.first(where: { $0.name == "id" } )?.value, let id = Int(value) {
AppCoordinator.showInformation(
in: SBModelKit.PushPlain(eventType: .detail(.it), id: id)
)
}
return true
}
return false
}
}
extension AppDelegate : @MainActor UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler(.list)
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == UNNotificationDismissActionIdentifier {
// MARK: - Пользователь отклонил уведомление, не предпринимая действий
} else if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
// MARK: - Пользователь запустил приложение
AppCoordinator.processUserNotificationAction(
identifier: response.notification.request.content.userInfo
)
}
completionHandler()
}
func wasAppOpenedFromBackground(application: UIApplication) -> Bool {
return application.applicationState == .inactive || application.applicationState == .background
}
}
private extension AppDelegate {
func constructRemoteNotifications(_ application: UIApplication) async {
let notificationCenter = UNUserNotificationCenter.current()
do {
notificationCenter.delegate = self
let granted = try await notificationCenter.requestAuthorization(
options: [.alert, .badge, .sound]
)
guard granted else { return }
let category = UNNotificationCategory(
identifier: "UpdateAppNotificationServiceExtension",
actions: [],
intentIdentifiers: [],
options: []
)
notificationCenter.setNotificationCategories([category])
Task { @MainActor in
application.registerForRemoteNotifications()
}
} catch {
print("Ошибка авторизации в UNUserNotificationCenter: \(error)")
}
}
func startDynatrace() {
// Старт Dynatrace по настройкам из "Dynatrace.plist"
let privacyConfig = Dynatrace.userPrivacyOptions()
privacyConfig.dataCollectionLevel = .userBehavior
privacyConfig.crashReportingOptedIn = true
Dynatrace.startupWithInfoPlistSettings()
Dynatrace.applyUserPrivacyOptions(privacyConfig) { _ in }
if let user = AppCoordinator.shared.userProfile {
Dynatrace.identifyUser(user.email)
}
}
func configurationCerber() {
cerberEnable = true
let jailbreakType: JailbreakCheckerType
let loggerOptions: [CerberSDK.LoggerOptions]
let mode: CerberSDK.Mode
switch Config.Sber911.currentStand {
case .ift:
mode = .majorCheck
jailbreakType = .lite
loggerOptions = [.logEnabled]
case .psi:
mode = .majorGo
jailbreakType = .lite
loggerOptions = [.logEnabled]
case .prom:
mode = .prod
jailbreakType = .full
loggerOptions = []
}
cerber.options = SetupOptions(
mode: mode,
loginOptions: [.pao],
jailbreakCheckerType: jailbreakType,
accessGroupID: nil,
loggerOptions: loggerOptions,
enableTestSSLPinning: mode != .prod
)
cerber.setDelegateManager(delegate: self, delegateQueue: .main)
// Настраиваем SberMAG
cerber.sberMagOptions = SberMAGOptions(
enabled: true,
unavailableProxyAction: .bypass,
unavailablePolicyAction: .bypass
)
}
func configureAnalyticsManager() {
var analyticStand: AnalyticsStand = .ift
if Config.Sber911.currentStand == .prom, !Config.Sber911.isMultiServerAppVersion {
analyticStand = .prom
}
let email = AppCoordinator.shared.userProfile?.email
AnalyticsManager.shared.setup(stand: analyticStand,
userLoginId: email)
AnalyticsManager.shared.setupCrashReporter(stand: analyticStand,
userLoginId: email)
AnalyticsManager.shared.setSession(AppCoordinator.shared.createSession())
}
func constructNavigationBar() {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = Color.Old.dayNight
appearance.shadowColor = .clear
appearance.largeTitleTextAttributes = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 28)
]
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().barTintColor = Color.Old.dayNight
UINavigationBar.appearance().tintColor = Color.Old.blue
UINavigationBar.appearance().isTranslucent = false
}
func logSystemWidgetSize() {
guard let widgetFamilyList = getWidgetFamilyList() else { return }
var params: [AnalyticsParam: String] = [:]
var widgetFamilyParamList: [String] = []
if widgetFamilyList.contains(.systemSmall) {
widgetFamilyParamList.append("small")
}
if widgetFamilyList.contains(.systemMedium) {
widgetFamilyParamList.append("medium")
}
if widgetFamilyList.contains(.systemLarge) {
widgetFamilyParamList.append("large")
}
if !widgetFamilyParamList.isEmpty {
params[.type(true)] = widgetFamilyParamList.joined(separator: ", ")
}
AnalyticsManager.shared.sendEventWith(name: AnalyticsEvent.UserInfo.SystemWidget.info, params: params)
}
func cleanLoggedSystemWidgetSizes() {
let sharedDefaults = UserDefaults(suiteName: "group.ext.corporateHealth")
sharedDefaults?.removeObject(forKey: "widgetFamilies")
}
func getWidgetFamilyList() -> [WidgetFamily]? {
let sharedDefaults = UserDefaults(suiteName: "group.ext.corporateHealth")
guard let widgetFamilyList = sharedDefaults?.array(forKey: "widgetFamilies") as? [Int] else {
return nil
}
return widgetFamilyList.compactMap { familyRawValue in
return WidgetFamily(rawValue: familyRawValue)
}
}
@objc
func methodOfReceivedExitNotification(_ notification: Foundation.Notification) {
Task { await AppCoordinator.shared.logOutOfAccount() }
cerber.logout()
}
func checkLoginState() {
guard registrationCompleted, !dataDeleting else { return }
Task { @MainActor in await AppCoordinator.shared.startAuthorization() }
}
func updateAppBadge(_ application: UIApplication) {
let badgeModel = ViewObjectManager.shared.badgeModel
let count = badgeModel.activeCount + badgeModel.completedCount
Task { @MainActor in
application.setIconBadgeNumber(count)
}
}
func updateVeryImportantPriorityInDefaults() {
let key = "Sber_Incident_Filter_Priority"
if var incidentPriority = UserDefaults.standard.object(forKey: key) as? [String] {
if let index = incidentPriority.firstIndex(of: "Очень важно") {
incidentPriority[index] = "Очень важный"
UserDefaults.standard.set(incidentPriority, forKey: key)
}
}
}
func clear(_ completion: @escaping () -> Void) {
getSecTask?.cancel()
getSecTask = nil
registrationCompleted = false
dataDeleting = true
isAuthSuccess = false
AppCoordinator.clearAll { [weak self] in
Task { @MainActor [weak self] in
self?.dataDeleting = false
}
// ничего не сделать с warning и не убрать completion, пока Cerber не даст новый апи
completion()
}
}
}
extension AppDelegate: @MainActor CerberDelegate {
/// SDK будет захватывать основное окно приложения
func willReplaceWindow() {}
/// SDK собирается возвращать основное окно приложения.
func willReturnWindow() {}
/// SDK после возврата основного окна приложения.
func didReturnWindow() {}
/// Метод вызывается при успешной авторизации.
///
/// - Parameter type: тип авторизации.
/// - Parameter isFirstLogin: Первый/повторный вход (для аналитики).
/// - Parameter isCertificateRenewed: признак того, что сертификат обновился
func didLogin(type: CerberSDK.LoginOptions, isFirstLogin: Bool, isCertificateRenewed: Bool) {
guard isAuthSuccess == false || isCertificateRenewed else { return }
if isCertificateRenewed || isFirstLogin {
getSecTask?.cancel()
getSecTask = nil
}
guard getSecTask == nil else { return }
getSecTask = Task { @MainActor [weak self] in
do {
let dic = try await Cerber.shared.getCertificate()
let identity = dic[CerberUsageKeys.kIdentityKey]
if identity != nil {
// Заменяем пользовательский Certificate and SecIdentity
await AppCoordinator.shared.update(
data: dic[CerberUsageKeys.kCertificateChainKey] as? Data,
identity: (identity as! SecIdentity)
)
self?.isAuthSuccess = true
}
self?.registrationCompleted = true
if isCertificateRenewed {
DynatraceManager.shared.logActionWith(name: "cerber certificate renewed")
}
if !(self?.dataDeleting ?? true) {
await AppCoordinator.shared.startAuthorization()
}
} catch {
AppCoordinator.shared.doNotUserCertificate(cerberError: error)
}
}
}
/// Метод вызывается при неуспешной авторизации. Добавили для сбора аналитики
///
/// - Parameter errorCode: Код ошибки.
/// - Parameter errorDescription: Описание ошибки.
/// - Parameter isFirstLogin: Первый/повторный вход (для аналитики).
func didFailLogin(errorCode: String, errorDescription: String, isFirstLogin: Bool) {
getSecTask?.cancel()
getSecTask = nil
DynatraceManager.shared.logError(
nil,
forNetwork: "errorCode: \(errorCode), errorDescription: \(errorDescription)",
to: "Cerber.didFailLogin"
)
let definition: String = "Код ошибки: \(errorCode)\nОписание ошибки: \(errorDescription)"
Task { @MainActor in
AppCoordinator.shared.doNotUserCertificate(
cerberError: NSError(domain: definition, code: -300)
)
}
}
func didChangeDeviceType(type: CerberSDK.DeviceType, completion: @escaping () -> Void) {
completion()
}
/// Метод вызывается при успешном обновлении токена авторизации.
///
/// - Parameter token: обновленный токен.
func didRefreshTeamIDToken(token: String) {
}
/// Необходима очистка данных приложения.
///
/// - Parameter reason: Причина разлогина ( для аналитики ).
func wipe(reason: String) {
DynatraceManager.shared.logError(
nil,
forNetwork: "wipe reason: \(reason)",
to: "Cerber.wipe"
)
clear { }
}
/// Необходима очистка данных пользователя.
func userChanged(completion: @escaping () -> Void) {
clear(completion)
}
}