Add an AnalyticsService to handle account data.

This commit is contained in:
Doug 2021-12-16 13:48:14 +00:00
parent 1df8514fcd
commit 919fd0ee3a
3 changed files with 89 additions and 25 deletions

View file

@ -89,23 +89,17 @@ import AnalyticsEvents
func useAnalyticsSettings(from session: MXSession) {
guard
RiotSettings.shared.enableAnalytics,
!RiotSettings.shared.isIdentifiedForAnalytics,
session.state == .running // Only use the session if it is running otherwise we could wipe out an existing analytics ID.
!RiotSettings.shared.isIdentifiedForAnalytics
else { return }
var settings = AnalyticsSettings(session: session)
if settings.id == nil {
settings.generateID()
session.setAccountData(settings.dictionary, forType: AnalyticsSettings.eventType) {
MXLog.debug("[Analytics] Successfully updated analytics settings in account data.")
let service = AnalyticsService(session: session)
service.settings { result in
switch result {
case .success(let settings):
self.identify(with: settings)
} failure: { error in
MXLog.error("[Analytics] Failed to update analytics settings.")
case .failure:
MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.")
}
} else {
self.identify(with: settings)
}
}
@ -135,7 +129,7 @@ import AnalyticsEvents
/// - Parameter settings: The settings to use for identification. The ID must be set *before* calling this method.
private func identify(with settings: AnalyticsSettings) {
guard let id = settings.id else {
MXLog.warning("[Analytics] identify(with:) called before an ID has been generated.")
MXLog.error("[Analytics] identify(with:) called before an ID has been generated.")
return
}

View file

@ -0,0 +1,72 @@
//
// Copyright 2021 New Vector Ltd
//
// 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
enum AnalyticsServiceError: Error {
/// The session supplied to the service does not have a state of `MXSessionStateRunning`.
case sessionIsNotRunning
/// An error occurred but the session did not report what it was.
case unknown
}
/// A service responsible for handling the `im.vector.analytics` event from the user's account data.
class AnalyticsService {
let session: MXSession
/// Creates an analytics service with the supplied session.
/// - Parameter session: The session to use when reading analytics settings from account data.
init(session: MXSession) {
self.session = session
}
/// The analytics settings for the current user. Calling this method will check whether the settings already
/// contain an `id` property and if not, will add one to the account data before calling the completion.
/// - Parameter completion: A completion handler that will be called when the request completes.
///
/// The request will fail if the service's session does not have the `MXSessionStateRunning` state.
func settings(completion: @escaping (Result<AnalyticsSettings, Error>) -> Void) {
// Only use the session if it is running otherwise we could wipe out an existing analytics ID.
guard session.state == .running else {
MXLog.warning("[AnalyticsService] Aborting attempt to read analytics settings. The session may not be up-to-date.")
completion(.failure(AnalyticsServiceError.sessionIsNotRunning))
return
}
let settings = AnalyticsSettings(accountData: session.accountData)
// The id has already be set so we are done here.
if settings.id != nil {
completion(.success(settings))
return
}
// Create a new ID and modify the event dictionary.
let id = UUID().uuidString
var eventDictionary = settings.dictionary
eventDictionary[AnalyticsSettings.Constants.idKey] = id
session.setAccountData(eventDictionary, forType: AnalyticsSettings.eventType) {
MXLog.debug("[AnalyticsService] Successfully updated analytics settings in account data.")
let settings = AnalyticsSettings(accountData: self.session.accountData)
completion(.success(settings))
} failure: { error in
MXLog.warning("[AnalyticsService] Failed to update analytics settings.")
completion(.failure(error ?? AnalyticsServiceError.unknown))
}
}
}

View file

@ -16,17 +16,18 @@
import Foundation
/// An analytics settings event from the user's account data.
struct AnalyticsSettings {
static let eventType = "im.vector.analytics"
private enum Constants {
enum Constants {
static let idKey = "id"
static let webOptInKey = "pseudonymousAnalyticsOptIn"
}
/// A randomly generated analytics token for this user.
/// This is suggested to be a 128-bit hex encoded string.
private(set) var id: String?
/// This is suggested to be a UUID string.
let id: String?
/// Whether the user has opted in on web or not. This is unused on iOS but necessary
/// to store here so that it's value is preserved when updating the account data if we
@ -34,12 +35,6 @@ struct AnalyticsSettings {
///
/// `true` if opted in on web, `false` if opted out on web and `nil` if the web prompt is not yet seen.
private let webOptIn: Bool?
/// Generate a new random analytics ID. This method has no effect if an ID already exists.
mutating func generateID() {
guard id == nil else { return }
id = UUID().uuidString
}
}
extension AnalyticsSettings {
@ -49,6 +44,7 @@ extension AnalyticsSettings {
self.webOptIn = dictionary?[Constants.webOptInKey] as? Bool
}
/// A dictionary representation of the settings.
var dictionary: Dictionary<AnyHashable, Any> {
var dictionary = [AnyHashable: Any]()
dictionary[Constants.idKey] = id
@ -61,7 +57,9 @@ extension AnalyticsSettings {
// MARK: - Public initializer
extension AnalyticsSettings {
init(session: MXSession) {
self.init(dictionary: session.accountData.accountData(forEventType: AnalyticsSettings.eventType))
/// Create the analytics settings from account data.
/// - Parameter accountData: The account data to read the event from.
init(accountData: MXAccountData) {
self.init(dictionary: accountData.accountData(forEventType: AnalyticsSettings.eventType))
}
}