Files
bundesmessenger-ios/bwi/MatomoAnalytics/BWIAnalytics.swift
2023-11-09 16:47:43 +01:00

307 lines
11 KiB
Swift

//
/*
* Copyright (c) 2021 BWI GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import MatomoTracker
@objcMembers class BWIAnalytics : NSObject {
static let sharedTracker = BWIAnalytics()
public var running: Bool {
get {
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)
}
}
private var matomo: MatomoTracker?
private let appName: String
private let appVersion: String
private let appPlattform = "iOS"
// use internally to not always check for Account data
private var fastRunning: Bool = false
private var session: MXSession? = nil
private var analyticsConfig: AnalyticsConfiguration
private override init() {
if !BWIBuildSettings.shared.secondaryAppName.isEmpty {
appName = BWIBuildSettings.shared.secondaryAppName
} else {
appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
}
appVersion = AppInfo.current.appVersion?.bundleShortVersion ?? ""
analyticsConfig = AnalyticsConfiguration()
super.init()
MXSDKOptions.sharedInstance().analyticsDelegate = self
resetMatomo()
}
func setSession(session: MXSession) {
self.session = session
fastRunning = running
}
func firstCall() {
fastRunning = running
}
func isEnabled() -> Bool {
return BWIBuildSettings.shared.bwiMatomoEnabled
}
// reset clientId on logout using internal matomo keys
func resetUserdefaults() {
// bwi: Analytics use custom config
if let siteID = analyticsConfig.selectedSiteID(),
let baseURL = analyticsConfig.selectedBaseURL(),
let userdefaults = UserDefaults(suiteName: siteID + baseURL) {
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")
}
resetMatomo()
}
// Uses Userdefaults instead of Account Data because it needs to be shown on every login
func needsToShowPromt() -> Bool {
if !BWIBuildSettings.shared.bwiMatomoEnabled {
return false
}
guard let session = session else {
return false
}
return BWIAnalyticsAccountDataService(mxSession: session).needsToShowPromt()
}
func resetMatomo() {
if BWIBuildSettings.shared.bwiMatomoEnabled {
readUserConfig()
// bwi: Analytics use custom config
guard let urlPath = analyticsConfig.selectedBaseURL(),
let url = URL(string: urlPath),
let siteID = analyticsConfig.selectedSiteID() else {
matomo = nil
return
}
matomo = MatomoTracker.init(siteId: siteID, baseURL:url, userAgent: nil)
matomo?.isOptedOut = false
matomo?.dispatchInterval = 5.0
matomo?.logger = DefaultLogger(minLevel: .verbose)
matomo?.setCustomVariable(withIndex: 1, name: "App Platform", value: appPlattform)
matomo?.setCustomVariable(withIndex: 2, name: "App Version", value: appVersion)
} else {
matomo = nil
}
}
func deleteMatomo() {
matomo = nil
}
func readUserConfig() {
if let path = Bundle.main.path(forResource: "trackingConfig", ofType: "json") {
analyticsConfig.readConfig(jsonFile: path)
}
}
func dispatchAll() {
if fastRunning {
matomo?.dispatch()
}
}
func trackScreen(_ screenName: String) {
if fastRunning {
matomo?.track(view: ["ios", appName, appVersion, screenName], url: nil)
}
}
func trackEvent(_ category: String, action: String) {
if fastRunning {
matomo?.track(eventWithCategory: category, action: action, url:nil)
}
}
func trackEvent(category: String, action: String, name: String, number: NSNumber?) {
if fastRunning {
matomo?.track(eventWithCategory: category, action: action, name: name, number: number, url: nil)
}
}
func trackBwiDuration(_ duration: TimeInterval, _ category: String, _ name: String) {
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 fastRunning {
matomo?.track(eventWithCategory: category, action: action, name: nil, number: value, url:nil)
}
}
func trackBwiValue(_ value: NSNumber, _ category: String, _ action: String, _ name: String) {
if fastRunning {
matomo?.track(eventWithCategory: category, action: action, name: name, number: value, url:nil)
}
}
func trackSlowMessage( dimension: String, value: Int) {
if fastRunning {
// bwi: Analytics use custom config
if let dimensionIndex = analyticsConfig.selectedSendMessageDimensionIndex() {
matomo?.setDimension(dimension, forIndex: dimensionIndex)
matomo?.track(eventWithCategory: "Performance", action: "SendMessage", name: "send_message_default", number: NSNumber(value: value), url:nil)
matomo?.remove(dimensionAtIndex: dimensionIndex)
}
}
}
func trackEventWithDimension(category: String, action: String, dimension: String, value: NSNumber?, name: String?) {
if fastRunning {
// bwi: Analytics use custom config
if let dimensionIndex = analyticsConfig.selectedSendMessageDimensionIndex() {
matomo?.setDimension(dimension, forIndex: dimensionIndex)
matomo?.track(eventWithCategory: category, action: action, name: name, number: value, url:nil) // name optional unwrap?
matomo?.remove(dimensionAtIndex: dimensionIndex)
}
}
}
}
extension BWIAnalytics : MXAnalyticsDelegate {
func trackJoinedRoom(asDM isDM: Bool, isSpace: Bool, memberCount: UInt) {
// dont track NV specific logs
}
func trackValue(_ value: NSNumber, category: String, name: String) {
// dont track NV specific logs
}
func trackDuration(_ milliseconds: Int, name: MXTaskProfileName, units: UInt) {
// dont track NV specific logs
}
func startDurationTracking(forName name: String, operation: String) -> StopDurationTracking {
return {}
}
func trackCreatedRoom(asDM isDM: Bool) {
// dont track NV specific logs
}
func trackCallStarted(withVideo isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
// dont track NV specific logs
}
func trackCallEnded(withDuration duration: Int, video isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
// dont track NV specific logs
}
func trackCallError(with reason: __MXCallHangupReason, video isVideo: Bool, numberOfParticipants: Int, incoming isIncoming: Bool) {
// dont track NV specific logs
}
func trackJoinedRoom(asDM isDM: Bool, memberCount: UInt) {
// dont track NV specific logs
}
func trackContactsAccessGranted(_ granted: Bool) {
// dont track NV specific logs
}
func trackBwiSDKValue(_ value: NSNumber, category: String, action: String) {
self.trackBwiValue(value, category, action)
}
func trackBwiSDKValue(_ value: NSNumber, category: String, action: String, name: String) {
self.trackBwiValue(value, category, action, name);
}
func trackBwiEvent(_ category: String, action: String) {
self.trackEvent(category, action: action)
}
func trackComposerEvent(inThread: Bool, isEditing: Bool, isReply: Bool, startsThread: Bool) {
// dont track NV specific logs
}
func trackNonFatalIssue(_ issue: String, details: [String : Any]?) {
// dont track NV specific logs
}
func trackE2EEErrorWithDimension(_ errorCode: Int, deviceCount: String, unspecifiedErrorMessage: String?) {
if let errorCode = AnalyticsE2EEErrorCode(rawValue: errorCode) {
var eventName = errorCode.description
if let additionalMessage = unspecifiedErrorMessage {
eventName.append(contentsOf: ": " + additionalMessage)
}
self.trackEventWithDimension(category: "Encryption", action: "ViewMessage", dimension: deviceCount, value: NSNumber(value: 1), name: eventName)
} else {
self.trackEventWithDimension(category: "Encryption", action: "ViewMessage", dimension: deviceCount, value: NSNumber(value: 1), name: "Unknown_Error")
}
}
func trackE2EEErrors(_ reason: DecryptionFailureReason, count: Int) {
switch reason {
case .unspecified:
self.trackBwiValue(NSNumber(value: count), "Encryption", "ViewMessage", "Unknown_Error")
case .olmIndexError:
self.trackBwiValue(NSNumber(value: count), "Encryption", "ViewMessage", AnalyticsE2EEErrorCode(rawValue: 3)!.description)
case .olmKeysNotSent:
self.trackBwiValue(NSNumber(value: count), "Encryption", "ViewMessage", AnalyticsE2EEErrorCode(rawValue: 4)!.description)
}
}
func trackE2EEDecryptionTime(count: Int, max: Int, avg: Int) {
self.trackBwiValue(NSNumber(value: count), "Encryption", "DecryptionTime", "unknown_inbound_sessionid_count")
self.trackBwiValue(NSNumber(value: max), "Encryption", "DecryptionTime", "unknown_inbound_sessionid_max")
self.trackBwiValue(NSNumber(value: avg), "Encryption", "DecryptionTime", "unknown_inbound_sessionid_avg")
}
}