diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index b76b73632..91a5c097d 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -76,6 +76,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg appMediator = AppMediator(windowManager: windowManager, networkMonitor: networkMonitor) let appSettings = appHooks.appSettingsHook.configure(AppSettings()) + ServiceLocator.shared.register(appSettings: appSettings) targetConfiguration = Target.mainApp.configure(logLevel: appSettings.logLevel, traceLogPacks: appSettings.traceLogPacks, @@ -95,8 +96,16 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg self.appDelegate = appDelegate self.appSettings = appSettings self.appHooks = appHooks + appRouteURLParser = AppRouteURLParser(appSettings: appSettings) + ServiceLocator.shared.register(userIndicatorController: UserIndicatorController()) + + let posthogAnalyticsClient = PostHogAnalyticsClient() + posthogAnalyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: .EXI, cryptoSDK: .Rust, cryptoSDKVersion: sdkGitSha())) + let analyticsService = AnalyticsService(client: posthogAnalyticsClient, appSettings: appSettings) + ServiceLocator.shared.register(analytics: analyticsService) + elementCallService = ElementCallService() navigationRootCoordinator = NavigationRootCoordinator() @@ -107,7 +116,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) - userSessionStore = UserSessionStore(keychainController: keychainController, appSettings: appSettings, appHooks: appHooks, networkMonitor: networkMonitor) + userSessionStore = UserSessionStore(keychainController: keychainController, + appSettings: appSettings, + analyticsService: analyticsService, + appHooks: appHooks, + networkMonitor: networkMonitor) let appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings) let appLockNavigationCoordinator = NavigationRootCoordinator() @@ -123,11 +136,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg sdkGitSHA: sdkGitSha(), maxUploadSize: appSettings.bugReportMaxUploadSize, appHooks: appHooks) - Self.setupServiceLocator(appSettings: appSettings, appHooks: appHooks) + Self.setupSentry(bugReportService: bugReportService, appSettings: appSettings) - ServiceLocator.shared.analytics.signpost.start() - ServiceLocator.shared.analytics.startIfEnabled() + analyticsService.startIfEnabled() windowManager.delegate = self @@ -356,6 +368,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg } else if appSettings.threadsEnabled, let threadRootEventID = content.threadRootEventID { handleAppRoute(.thread(roomID: roomID, threadRootEventID: threadRootEventID, focusEventID: eventID)) } else if let eventID { + // Only track main timeline event deeplinking + ServiceLocator.shared.analytics.signpost.startTransaction(.notificationToMessage) handleAppRoute(.event(eventID: eventID, roomID: roomID, via: [])) } else { handleAppRoute(.room(roomID: roomID, via: [])) @@ -380,16 +394,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg // MARK: - Private - private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) { - ServiceLocator.shared.register(userIndicatorController: UserIndicatorController()) - ServiceLocator.shared.register(appSettings: appSettings) - - let posthogAnalyticsClient = PostHogAnalyticsClient() - posthogAnalyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: .EXI, cryptoSDK: .Rust, cryptoSDKVersion: sdkGitSha())) - ServiceLocator.shared.register(analytics: AnalyticsService(client: posthogAnalyticsClient, - appSettings: appSettings)) - } - /// Perform any required migrations for the app to function correctly. private func performMigrationsIfNecessary(from oldVersion: Version, to newVersion: Version) { guard oldVersion != newVersion else { return } @@ -693,6 +697,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg fatalError("User session not setup") } + if let serverName = userSession.clientProxy.userIDServerName { + ServiceLocator.shared.analytics.signpost.addGlobalTag(.homeserver, value: serverName) + } + + if !isNewLogin { + ServiceLocator.shared.analytics.signpost.startTransaction(.cachedRoomList) + } + let flowParameters = CommonFlowParameters(userSession: userSession, bugReportService: bugReportService, elementCallService: elementCallService, @@ -1061,9 +1073,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private func startSync() { guard let userSession else { return } - let serverName = String(userSession.clientProxy.userIDServerName ?? "Unknown") - - ServiceLocator.shared.analytics.signpost.beginFirstSync(serverName: serverName) + ServiceLocator.shared.analytics.signpost.startTransaction(.upToDateRoomList) + userSession.clientProxy.startSync() guard clientProxyObserver == nil else { @@ -1072,6 +1083,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg clientProxyObserver = userSession.clientProxy .loadingStatePublisher + .dropFirst() .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak self] state in @@ -1084,7 +1096,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: toastIdentifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) } case .notLoading: - ServiceLocator.shared.analytics.signpost.endFirstSync() + ServiceLocator.shared.analytics.signpost.finishTransaction(.upToDateRoomList) ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(toastIdentifier) } } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 4abaede60..8ce393b94 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -111,8 +111,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { self.flowParameters = flowParameters setupStateMachine() - - flowParameters.analytics.signpost.beginRoomFlow(roomID) } // MARK: - FlowCoordinatorProtocol @@ -129,6 +127,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { switch appRoute { case .room(let roomID, let via): + flowParameters.analytics.signpost.startTransaction(.openRoom) Task { await handleRoomRoute(roomID: roomID, via: via, animated: animated) } @@ -897,7 +896,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } else { actionsSubject.send(.finished) } - flowParameters.analytics.signpost.endRoomFlow() } private func presentRoomDetails(isRoot: Bool, animated: Bool) async { diff --git a/ElementX/Sources/Other/SDKListener.swift b/ElementX/Sources/Other/SDKListener.swift index a4bba795e..67dd11e63 100644 --- a/ElementX/Sources/Other/SDKListener.swift +++ b/ElementX/Sources/Other/SDKListener.swift @@ -87,6 +87,12 @@ extension SDKListener: SendQueueRoomErrorListener where T == (String, ClientErro } } +extension SDKListener: SendQueueRoomUpdateListener where T == (String, RoomSendQueueUpdate) { + func onUpdate(roomId: String, update: RoomSendQueueUpdate) { + onUpdateClosure((roomId, update)) + } +} + // MARK: SecureBackupController extension SDKListener: BackupStateListener where T == BackupState { diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift index e337a4061..cd9f0a64a 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginScreenViewModel.swift @@ -95,18 +95,15 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc startLoading(isInteractionBlocking: true) Task { - analytics.signpost.beginLogin() switch await authenticationService.login(username: state.bindings.username, password: state.bindings.password, initialDeviceName: UIDevice.current.initialDeviceName, deviceID: nil) { case .success(let userSession): actionsSubject.send(.signedIn(userSession)) - analytics.signpost.endLogin() stopLoading() case .failure(let error): stopLoading() - analytics.signpost.endLogin() handleError(error) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index e0b5df320..f96cbeb8d 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -292,8 +292,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - analyticsService.signpost.beginFirstRooms() - roomSummaryProvider.statePublisher .receive(on: DispatchQueue.main) .sink { [weak self] state in @@ -329,7 +327,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } if roomListMode == .rooms, state.roomListMode == .skeletons { - analyticsService.signpost.endFirstRooms() + analyticsService.signpost.finishTransaction(.cachedRoomList) } state.roomListMode = roomListMode diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index adf96ba04..4be4ae06a 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -256,7 +256,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { } showFocusLoadingIndicator() - defer { hideFocusLoadingIndicator() } + defer { + hideFocusLoadingIndicator() + } switch await timelineController.focusOnEvent(eventID, timelineSize: Constants.detachedTimelineSize) { case .success: @@ -318,6 +320,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { focussedEvent.appearance = .hasAppeared state.timelineState.focussedEvent = focussedEvent hideFocusLoadingIndicator() + analyticsService.signpost.finishTransaction(.notificationToMessage) } } @@ -427,6 +430,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { switch callback { case .updatedTimelineItems(let updatedItems, let isSwitchingTimelines): buildTimelineViews(timelineItems: updatedItems, isSwitchingTimelines: isSwitchingTimelines) + + if !updatedItems.isEmpty { + analyticsService.signpost.finishTransaction(.openRoom) + } case .paginationState(let paginationState): if state.timelineState.paginationState != paginationState { state.timelineState.paginationState = paginationState diff --git a/ElementX/Sources/Services/Analytics/Signposter.swift b/ElementX/Sources/Services/Analytics/Signposter.swift index b2ad21e36..ada63b877 100644 --- a/ElementX/Sources/Services/Analytics/Signposter.swift +++ b/ElementX/Sources/Services/Analytics/Signposter.swift @@ -6,140 +6,106 @@ // Please see LICENSE files in the repository root for full details. // -import OSLog +import CryptoKit import Sentry -/// A simple wrapper around OSSignposter for easy performance testing. +/// A simple wrapper around Sentry for easy instrumentation class Signposter { - /// The underlying signposter. - private let signposter = OSSignposter(subsystem: subsystem, category: category) - /// A logger instance to capture any errors. - private let logger = Logger(subsystem: subsystem, category: category) + private var transactions = [TransactionName: any Sentry.Span]() - /// Signpost name constants. - enum Name { - static let login: StaticString = "Login" - static let firstSync: StaticString = "FirstSync" - static let firstRooms: StaticString = "FirstRooms" - static let roomFlow: StaticString = "RoomFlow" + private var globalTags = [TagName: String]() + + enum TransactionName: Hashable { + case cachedRoomList + case upToDateRoomList + case notificationToMessage + case openRoom + case sendMessage(uuid: String) - static let appStartup = "AppStartup" - static let appStarted = "AppStarted" + var id: String { + switch self { + case .cachedRoomList: + "Cached room list" + case .upToDateRoomList: + "Up-to-date room list" + case .notificationToMessage: + "Notification to message" + case .openRoom: + "Open a room" + case .sendMessage: + "Send a message" + } + } + } + + enum SpanName: String { + case timelineLoad = "Timeline load" + } + + struct Span { + fileprivate let innerSpan: Sentry.Span - static let homeserver = "homeserver" + func finish() { + innerSpan.finish() + } } - static let subsystem = "ElementX" - static let category = "PerformanceTests" - - // MARK: - App Startup - - private var appStartupTransaction: Span? - - /// We have a manual start method because we need to configure the ServiceLocator *before* we configure - /// Sentry but this class is created in the AnalyticsService and so spans and transactions are dropped - /// until Sentry has been configured. Therefore doing this in the init doesn't work. - func start() { - appStartupTransaction = SentrySDK.startTransaction(name: Name.appStartup, operation: Name.appStarted) + enum TagName: String { + case homeserver = "Homeserver" } - // MARK: - Login + // MARK: - Transactions - private var loginState: OSSignpostIntervalState? - private var loginTransaction: Span? - private var loginSpan: Span? - - func beginLogin() { - loginState = signposter.beginInterval(Name.login) - loginTransaction = SentrySDK.startTransaction(name: "\(Name.login)", operation: "\(Name.login)") - loginSpan = appStartupTransaction?.startChild(operation: "\(Name.login)", description: "\(Name.login)") + func startTransaction(_ transactionName: TransactionName, operation: String = "ux", tags: [TagName: String] = [:]) { + let span = SentrySDK.startTransaction(name: transactionName.id, operation: operation) + + tags + .merging(globalTags) { tagValue, _ in + tagValue + } + .forEach { (key: TagName, value: String) in + span.setTag(value: value, key: key.rawValue) + } + + transactions[transactionName] = span } - func endLogin() { - guard let loginState else { - logger.error("Missing login state.") - return + func finishTransaction(_ transactionName: TransactionName) { + transactions[transactionName]?.finish() + transactions[transactionName] = nil + } + + // MARK: - Spans + + func addSpan(_ spanName: SpanName, toTransaction transactionName: TransactionName) -> Span? { + guard let transaction = transactions[transactionName] else { + MXLog.error("Transaction not started or already finished") + return nil } - signposter.endInterval(Name.login, loginState) - loginTransaction?.finish() - loginSpan?.finish() - - self.loginState = nil - loginTransaction = nil - loginSpan = nil + return Span(innerSpan: transaction.startChild(operation: spanName.rawValue)) } - // MARK: - FirstSync + // MARK: - Tags - private var firstSyncState: OSSignpostIntervalState? - private var firstSyncTransaction: Span? - private var firstSyncSpan: Span? - - func beginFirstSync(serverName: String) { - appStartupTransaction?.setTag(value: serverName, key: Name.homeserver) - - firstSyncState = signposter.beginInterval(Name.firstSync) - - firstSyncTransaction = SentrySDK.startTransaction(name: "\(Name.firstSync)", operation: "\(Name.firstSync)") - firstSyncTransaction?.setTag(value: serverName, key: Name.homeserver) - - firstSyncSpan = appStartupTransaction?.startChild(operation: "\(Name.firstSync)", description: "\(Name.firstSync)") - } - - func endFirstSync() { - guard let firstSyncState else { return } - - signposter.endInterval(Name.firstSync, firstSyncState) - firstSyncTransaction?.finish() - firstSyncSpan?.finish() - - self.firstSyncState = nil - firstSyncTransaction = nil - firstSyncSpan = nil - } - - // MARK: - FirstRooms - - private var firstRoomsState: OSSignpostIntervalState? - private var firstRoomsTransaction: Span? - private var firstRoomsSpan: Span? - - func beginFirstRooms() { - firstRoomsState = signposter.beginInterval(Name.firstRooms) - firstRoomsTransaction = SentrySDK.startTransaction(name: "\(Name.firstRooms)", operation: "\(Name.firstRooms)") - firstRoomsSpan = appStartupTransaction?.startChild(operation: "\(Name.firstRooms)", description: "\(Name.firstRooms)") - } - - func endFirstRooms() { - defer { - appStartupTransaction?.finish() + func addGlobalTag(_ tagName: TagName, value: String) { + let value = switch tagName { + case .homeserver: + sha512(value) } - guard let firstRoomsState else { return } - - signposter.endInterval(Name.firstRooms, firstRoomsState) - firstRoomsTransaction?.finish() - firstRoomsSpan?.finish() - - self.firstRoomsState = nil - firstRoomsTransaction = nil - firstRoomsSpan = nil + globalTags[tagName] = value } - // MARK: - Room Flow - - private var roomFlowState: OSSignpostIntervalState? - - func beginRoomFlow(_ name: String) { - roomFlowState = signposter.beginInterval(Name.roomFlow) - signposter.emitEvent("RoomName", "\(name, privacy: .auto(mask: .hash))") + func removeGlobalTag(_ tagName: TagName) { + globalTags[tagName] = nil } - func endRoomFlow() { - guard let roomFlowState else { return } - - signposter.endInterval(Name.roomFlow, roomFlowState) - self.roomFlowState = nil + // MARK: - Private + + func sha512(_ string: String) -> String { + let data = Data(string.utf8) + let hash = SHA512.hash(data: data) + return hash.map { String(format: "%02x", $0) }.joined() } } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index d20a70c11..0dcb736df 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -12,10 +12,12 @@ import Foundation import MatrixRustSDK import OrderedCollections +// swiftlint:disable:next type_body_length class ClientProxy: ClientProxyProtocol { private let client: ClientProtocol private let networkMonitor: NetworkMonitorProtocol private let appSettings: AppSettings + private let analyticsService: AnalyticsService let mediaLoader: MediaLoaderProtocol private let clientQueue: DispatchQueue @@ -37,7 +39,10 @@ class ClientProxy: ClientProxyProtocol { private var verificationStateListenerTaskHandle: TaskHandle? // periphery:ignore - required for instance retention in the rust codebase - private var sendQueueListenerTaskHandle: TaskHandle? + private var sendQueueStatusListenerTaskHandle: TaskHandle? + + // periphery:ignore - required for instance retention in the rust codebase + private var sendQueueUpdatesListenerTaskHandle: TaskHandle? // periphery:ignore - required for instance retention in the rust codebase private var mediaPreviewConfigListenerTaskHandle: TaskHandle? @@ -184,10 +189,12 @@ class ClientProxy: ClientProxyProtocol { init(client: ClientProtocol, networkMonitor: NetworkMonitorProtocol, - appSettings: AppSettings) async throws { + appSettings: AppSettings, + analyticsService: AnalyticsService) async throws { self.client = client self.networkMonitor = networkMonitor self.appSettings = appSettings + self.analyticsService = analyticsService clientQueue = .init(label: "ClientProxyQueue", attributes: .concurrent) @@ -243,11 +250,22 @@ class ClientProxy: ClientProxyProtocol { Task { await self?.updateVerificationState(verificationState) } }) - sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SDKListener { [weak self] roomID, error in + sendQueueStatusListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SDKListener { [weak self] roomID, error in MXLog.error("Send queue failed in room: \(roomID) with error: \(error)") self?.sendQueueStatusSubject.send(false) }) + sendQueueUpdatesListenerTaskHandle = try? await client.subscribeToSendQueueUpdates(listener: SDKListener { _, update in + switch update { + case .newLocalEvent(let transactionID): + analyticsService.signpost.startTransaction(.sendMessage(uuid: transactionID)) + case .sentEvent(let transactionID, _): + analyticsService.signpost.finishTransaction(.sendMessage(uuid: transactionID)) + default: + break + } + }) + sendQueueStatusSubject .combineLatest(homeserverReachabilityPublisher) .debounce(for: 1.0, scheduler: DispatchQueue.main) @@ -1136,7 +1154,8 @@ class ClientProxy: ClientProxyProtocol { case .joined: let roomProxy = try await JoinedRoomProxy(roomListService: roomListService, room: room, - appSettings: appSettings) + appSettings: appSettings, + analyticsService: analyticsService) return .joined(roomProxy) case .left: diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index df183d8a8..e96eafa3e 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -15,6 +15,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { private let roomListService: RoomListServiceProtocol private let room: RoomProtocol private let appSettings: AppSettings + private let analyticsService: AnalyticsService // periphery:ignore - required for instance retention in the rust codebase private var roomInfoObservationToken: TaskHandle? @@ -76,13 +77,16 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { init(roomListService: RoomListServiceProtocol, room: RoomProtocol, - appSettings: AppSettings) async throws { + appSettings: AppSettings, + analyticsService: AnalyticsService) async throws { self.roomListService = roomListService self.room = room self.appSettings = appSettings + self.analyticsService = analyticsService infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo())) + let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .openRoom) timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: appSettings.threadsEnabled), filter: .eventTypeFilter(filter: excludedEventsFilter), internalIdPrefix: nil, @@ -90,6 +94,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { trackReadReceipts: .messageLikeEvents, reportUtds: true)), kind: .live) + openRoomSpan?.finish() Task { await updateMembers() @@ -146,6 +151,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result { do { + let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .notificationToMessage) let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .event(eventId: eventID, numContextEvents: numberOfEvents, hideThreadedEvents: appSettings.threadsEnabled), @@ -154,6 +160,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { dateDividerMode: .daily, trackReadReceipts: .disabled, reportUtds: true)) + openRoomSpan?.finish() return .success(TimelineProxy(timeline: sdkTimeline, kind: .detached)) } catch let error as FocusEventError { diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 4ecabca01..41326d798 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -12,6 +12,7 @@ import MatrixRustSDK class UserSessionStore: UserSessionStoreProtocol { private let keychainController: KeychainControllerProtocol private let appSettings: AppSettings + private let analyticsService: AnalyticsService private let networkMonitor: NetworkMonitorProtocol private let appHooks: AppHooks @@ -31,10 +32,12 @@ class UserSessionStore: UserSessionStoreProtocol { init(keychainController: KeychainControllerProtocol, appSettings: AppSettings, + analyticsService: AnalyticsService, appHooks: AppHooks, networkMonitor: NetworkMonitorProtocol) { self.keychainController = keychainController self.appSettings = appSettings + self.analyticsService = analyticsService self.appHooks = appHooks self.networkMonitor = networkMonitor } @@ -159,7 +162,8 @@ class UserSessionStore: UserSessionStoreProtocol { do { return try await ClientProxy(client: client, networkMonitor: networkMonitor, - appSettings: appSettings) + appSettings: appSettings, + analyticsService: analyticsService) } catch { throw UserSessionStoreError.failedSettingUpClientProxy(error) }