diff --git a/Podfile b/Podfile index c31896198..0b4deedaa 100644 --- a/Podfile +++ b/Podfile @@ -74,7 +74,7 @@ abstract_target 'RiotPods' do pod 'WeakDictionary', '~> 2.0' # Piwik for analytics - pod 'MatomoTracker', '~> 7.4.1' + pod 'MatomoTracker', '~> 7.5.2' # PostHog for analytics pod 'PostHog', '~> 2.0.0' diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 3d260d445..40644d232 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -1867,7 +1867,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni matrixSessionStateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXSession *mxSession = (MXSession*)notif.object; - [BWIAnalytics.sharedTracker trackEvent:@"initMatrixSessions" action:[NSString stringWithFormat:@"%ld", mxSession.state]]; + //[BWIAnalytics.sharedTracker trackEvent:@"initMatrixSessions" action:[NSString stringWithFormat:@"%ld", mxSession.state]]; // Check whether the concerned session is a new one if (mxSession.state == MXSessionStateInitialised) @@ -2251,6 +2251,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [topVC startActivityIndicator]; } + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + [self logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { if (completion) { @@ -2258,7 +2260,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { //[RiotSettings.shared reset]; [BWIBuildSettings.shared reset]; - [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + [BWIAnalytics.sharedTracker resetUserdefaults]; } completion (YES); @@ -2329,6 +2331,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [topVC startActivityIndicator]; } + [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + [self logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { if (completion) { @@ -2336,7 +2340,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { //[RiotSettings.shared reset]; [BWIBuildSettings.shared reset]; - [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Logout"]; + [BWIAnalytics.sharedTracker resetUserdefaults]; } completion (YES); @@ -2431,7 +2435,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (mainSession) { - [BWIAnalytics.sharedTracker trackEvent:@"handleAppState" action:[MXTools readableSessionState:mainSession.state]]; + //[BWIAnalytics.sharedTracker trackEvent:@"handleAppState" action:[MXTools readableSessionState:mainSession.state]]; switch (mainSession.state) { @@ -2616,7 +2620,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni MXLogDebug(@"[AppDelegate] hideLaunchAnimation: LaunchAnimation was shown for %.3fms", launchTaskProfile.duration * 1000); } - [BWIAnalytics.sharedTracker trackEvent:@"hideLaunchAnimation" action:@"Hide"]; + //[BWIAnalytics.sharedTracker trackEvent:@"hideLaunchAnimation" action:@"Hide"]; [self->launchAnimationContainerView removeFromSuperview]; self->launchAnimationContainerView = nil; } @@ -3980,7 +3984,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [(MXLegacyCrypto *)crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; } [self dismissKeyVerificationCoordinatorBridgePresenter]; - [BWIAnalytics.sharedTracker trackEvent:@"Session" action:@"Login"]; } - (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index ef1e46101..ef84d8f75 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -154,6 +154,8 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { }) } } + + self.bwiCheckForMatomoPromt() } func bwiOnUnlockedByPin() { @@ -702,6 +704,13 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { service.createPersonalNotesRoomIfNeeded() } } + + // bwi: check if matomo promt was shown for this session + private func bwiCheckForMatomoPromt() { + if BWIBuildSettings.shared.bwiMatomoEnabled && BWIAnalytics.sharedTracker.needsToShowPromt() { + self.allChatsViewController.bwiPresentMatomoConsentAlert() + } + } } extension AllChatsCoordinator: SignOutFlowPresenterDelegate { @@ -724,6 +733,7 @@ extension AllChatsCoordinator: SignOutFlowPresenterDelegate { extension AllChatsCoordinator: AllChatsViewControllerDelegate { func allChatsViewControllerDidCompleteAuthentication(_ allChatsViewController: AllChatsViewController) { self.delegate?.splitViewMasterCoordinatorDidCompleteAuthentication(self) + self.bwiCheckForMatomoPromt() } func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) { diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 5b3ca41c4..ecfda48b7 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -292,6 +292,37 @@ class AllChatsViewController: HomeViewController { super.startActivityIndicator() } + func bwiPresentMatomoConsentAlert() { + let alert = UIAlertController(title: BWIL10n.bwiAnalyticsAlertTitle, + message: BWIL10n.bwiAnalyticsAlertBody(AppInfo.current.displayName), + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: BWIL10n.bwiAnalyticsAlertInfoButton, + style: .default, + handler: { action in + if let webviewController = WebViewViewController(url: BWIBuildSettings.shared.applicationPrivacyPolicyUrlString) { + webviewController.title = VectorL10n.settingsPrivacyPolicy + + self.present(webviewController, animated: true) + } + })) + + alert.addAction(UIAlertAction(title: BWIL10n.bwiAnalyticsAlertCancelButton, + style: .default, + handler: { action in + BWIAnalytics.sharedTracker.running = false + })) + + alert.addAction(UIAlertAction(title: BWIL10n.bwiAnalyticsAlertOkButton, + style: .cancel, + handler: { action in + BWIAnalytics.sharedTracker.running = true + BWIAnalytics.sharedTracker.trackBwiValue(0, "General", "ConsentGiven", "popup") + })) + + self.present(alert, animated: true) + } + // MARK: - Actions @objc private func showSpaceSelectorAction(sender: AnyObject) { diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index f575d7ac3..053847cac 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -1916,6 +1916,10 @@ TableViewSectionsDelegate> BOOL isOn = sender.on; BWIAnalytics.sharedTracker.running = isOn; + + if (isOn) { + [BWIAnalytics.sharedTracker trackBwiValue:@0 :@"General" :@"ConsentGiven" :@"settings"]; + } } @end diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 7b72dee7f..ac9b24e3c 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1123,9 +1123,7 @@ [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertOkButton style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { BWIAnalytics.sharedTracker.running = YES; }]]; - [self presentViewController:alert animated:YES completion:^() { - [BWIAnalytics.sharedTracker setPromtShown:YES]; - }]; + [self presentViewController:alert animated:YES completion:nil]; } @end diff --git a/bwi/DeveloperSettings/DeveloperSettingsView.swift b/bwi/DeveloperSettings/DeveloperSettingsView.swift index 822ce4fe6..8ceefdf21 100644 --- a/bwi/DeveloperSettings/DeveloperSettingsView.swift +++ b/bwi/DeveloperSettings/DeveloperSettingsView.swift @@ -48,7 +48,7 @@ struct DeveloperSettingsView: View { Alert(title: Text(BWIL10n.bwiSettingsDeveloper), dismissButton: .default(Text("Ok"))) } - Button(action: { showAlert = resetMatomoInfoScreen() }) { + Button(action: { showAlert = resetMatomoInfoScreen(mxSession: session) }) { Text(BWIL10n.bwiSettingsDeveloperResetMatomoInfo) .foregroundColor(Color(ThemeService.shared().theme.tintColor)) .font(.system(size: 17)) @@ -122,8 +122,12 @@ fileprivate func createNewPersonalNotesRoom(mxSession: MXSession?) -> Bool { return true } -fileprivate func resetMatomoInfoScreen() -> Bool { - BWIAnalytics.sharedTracker.setPromtShown(false) +fileprivate func resetMatomoInfoScreen(mxSession: MXSession?) -> Bool { + guard let mxSession = mxSession else { + return false + } + // FRROT + BWIAnalyticsAccountDataService(mxSession: mxSession) return true } diff --git a/bwi/MatomoAnalytics/BWIAnalytics.swift b/bwi/MatomoAnalytics/BWIAnalytics.swift index 3b3faf4a4..6981034e9 100644 --- a/bwi/MatomoAnalytics/BWIAnalytics.swift +++ b/bwi/MatomoAnalytics/BWIAnalytics.swift @@ -26,13 +26,13 @@ import MatomoTracker guard let session = session else { return false } - return BWIAnalyticsAccountDataService(mxSession: session).isEnabled() } set(enabled) { guard let session = session else { return } + fastRunning = enabled BWIAnalyticsAccountDataService(mxSession: session).setEnabled(enabled) } } @@ -42,6 +42,9 @@ import MatomoTracker private let appVersion: String private let appPlattform = "iOS" + // use internally to not always check for Account data + private var fastRunning: Bool = false + var session: MXSession? = nil private override init() { @@ -66,6 +69,7 @@ import MatomoTracker matomo?.setCustomVariable(withIndex: 1, name: "App Platform", value: appPlattform) matomo?.setCustomVariable(withIndex: 2, name: "App Version", value: appVersion) + } else { matomo = nil } @@ -75,6 +79,24 @@ import MatomoTracker return matomo != nil } + // reset clientId on logout using internal matomo keys + func resetUserdefaults() { + + if let userdefaults = UserDefaults(suiteName: "1https://matomo.example.com/matomo.php") { + userdefaults.removeObject(forKey: "PiwikTotalNumberOfVistsKey") + userdefaults.removeObject(forKey: "PiwikCurrentVisitTimestampKey") + userdefaults.removeObject(forKey: "PiwikPreviousVistsTimestampKey") + userdefaults.removeObject(forKey: "PiwikFirstVistsTimestampKey") + userdefaults.removeObject(forKey: "PiwikVisitorIDKey") + userdefaults.removeObject(forKey: "PiwikForcedVisitorIDKey") + userdefaults.removeObject(forKey: "PiwikVisitorUserIDKey") + userdefaults.removeObject(forKey: "PiwikOptOutKey") + userdefaults.removeObject(forKey: "PiwikLastOrderDateKey") + } + + } + + // Uses Userdefaults instead of Account Data because it needs to be shown on every login func needsToShowPromt() -> Bool { if !BWIBuildSettings.shared.bwiEnableErrorTracking { return false @@ -84,43 +106,35 @@ import MatomoTracker return false } - return !BWIAccountNotificationService(mxSession: session).isNotificationShown(BWIAccountNotificationService.AccountNotifications.CodingKeys.analyticsPromt.rawValue) - } - - func setPromtShown(_ shown: Bool) { - guard let session = session else { - return - } - - BWIAccountNotificationService(mxSession: session).setNotification(BWIAccountNotificationService.AccountNotifications.CodingKeys.analyticsPromt.rawValue, shown: shown) + return BWIAnalyticsAccountDataService(mxSession: session).needsToShowPromt() } func trackScreen(_ screenName: String) { - if running { + if fastRunning { matomo?.track(view: ["ios", appName, appVersion, screenName], url: nil) } } func trackEvent(_ category: String, action: String) { - if running { + if fastRunning { matomo?.track(eventWithCategory: category, action: action, url:nil) } } func trackBwiDuration(_ duration: TimeInterval, _ category: String, _ name: String) { - if running { + if fastRunning { matomo?.track(eventWithCategory: "Metrics", action: category, name: name, number: NSNumber(value: duration), url:nil) } } func trackBwiValue(_ value: NSNumber, _ category: String, _ action: String) { - if running { + if fastRunning { matomo?.track(eventWithCategory: category, action: action, name: nil, number: value, url:nil) } } func trackBwiValue(_ value: NSNumber, _ category: String, _ action: String, _ name: String) { - if running { + if fastRunning { matomo?.track(eventWithCategory: category, action: action, name: name, number: value, url:nil) } } diff --git a/bwi/MatomoAnalytics/BWIAnalyticsAccountDataService.swift b/bwi/MatomoAnalytics/BWIAnalyticsAccountDataService.swift index 307aaff57..5a3bb06ae 100644 --- a/bwi/MatomoAnalytics/BWIAnalyticsAccountDataService.swift +++ b/bwi/MatomoAnalytics/BWIAnalyticsAccountDataService.swift @@ -22,10 +22,21 @@ import Foundation private struct AccountAnalytics: Codable { enum CodingKeys: String, CodingKey { - case analyticsEnabled = "enabled" + case consent + case time + case platform + case appVersion = "app_version" + } + + let consent: Bool + let time: Int + let platform: String + let appVersion: String + + func toJsonDict() -> [String : Any] { + return [CodingKeys.consent.rawValue:consent, CodingKeys.time.rawValue:time, CodingKeys.platform.rawValue:platform, CodingKeys.appVersion.rawValue:appVersion] } - let analyticsEnabled: Bool } private enum AccountDataTypes { @@ -36,17 +47,32 @@ import Foundation private lazy var serializationService: SerializationServiceType = SerializationService() init(mxSession: MXSession) { - self.session = mxSession + session = mxSession + } + + func needsToShowPromt() -> Bool { + guard let analyticsArray = session.accountData.accountData(forEventType: AccountDataTypes.analytics) as? [String: Any] else { + return true + } + guard analyticsArray[session.myDeviceId] is [String: Any] else { + return true + } + return false } func isEnabled() -> Bool { - guard let analyticsDict = session.accountData.accountData(forEventType: AccountDataTypes.analytics) as? [String: Any] else { + guard let analyticsArray = session.accountData.accountData(forEventType: AccountDataTypes.analytics) as? [String: Any] else { return false } + + guard let deviceId = session.myDeviceId, let analyticsDict = analyticsArray[deviceId] as? [String: Any] else { + return false + } + do { let analytics: AccountAnalytics = try serializationService.deserialize(analyticsDict) - return analytics.analyticsEnabled + return analytics.consent } catch { } @@ -56,8 +82,11 @@ import Foundation func setEnabled(_ enabled: Bool) { var analyticsDict = session.accountData.accountData(forEventType: AccountDataTypes.analytics) ?? [:] - analyticsDict[AccountAnalytics.CodingKeys.analyticsEnabled.rawValue] = enabled + let currentConsent = AccountAnalytics(consent: enabled, time: Int(Date().timeIntervalSince1970*1000), platform: "ios", appVersion: AppInfo.current.appVersion?.bundleShortVersion ?? "") + + analyticsDict[session.myDeviceId] = currentConsent.toJsonDict() + session.setAccountData(analyticsDict, forType: AccountDataTypes.analytics, success: nil, failure: nil) } }