Refactor Rageshake URL and Target configuration. (#4337)

* Move the rageshakeURL configuration from the BugReportService to AppSettings.

* Refactor Target to return a handle when configuring.

This helps with reconfiguration and removes the @MainActor constraint.
This commit is contained in:
Doug
2025-07-22 10:19:26 +01:00
committed by GitHub
parent 97f517129f
commit 82c659f3da
17 changed files with 241 additions and 179 deletions
@@ -18,6 +18,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
private let stateMachine: AppCoordinatorStateMachine
private let navigationRootCoordinator: NavigationRootCoordinator
private let userSessionStore: UserSessionStoreProtocol
private let targetConfiguration: Target.Configuration
private let appMediator: AppMediator
private let appSettings: AppSettings
private let appDelegate: AppDelegate
@@ -74,9 +75,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
let appSettings = appHooks.appSettingsHook.configure(AppSettings())
Target.mainApp.configure(logLevel: appSettings.logLevel,
traceLogPacks: appSettings.traceLogPacks,
sentryURL: appSettings.bugReportSentryRustURL)
targetConfiguration = Target.mainApp.configure(logLevel: appSettings.logLevel,
traceLogPacks: appSettings.traceLogPacks,
sentryURL: appSettings.bugReportSentryRustURL)
let appName = InfoPlistReader.main.bundleDisplayName
let appVersion = InfoPlistReader.main.bundleShortVersionString
@@ -372,7 +373,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
private static func setupServiceLocator(appSettings: AppSettings, appHooks: AppHooks) {
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
ServiceLocator.shared.register(appSettings: appSettings)
ServiceLocator.shared.register(bugReportService: BugReportService(rageshakeURL: appSettings.bugReportRageshakeURL,
ServiceLocator.shared.register(bugReportService: BugReportService(rageshakeURLPublisher: appSettings.bugReportRageshakeURL.publisher,
applicationID: appSettings.bugReportApplicationID,
sdkGitSHA: sdkGitSha(),
maxUploadSize: appSettings.bugReportMaxUploadSize,
@@ -403,7 +404,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
}
// This could be removed once the adotpion of 25.06.x is widespread.
// This could be removed once the adoption of 25.06.x is widespread.
private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) {
guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else {
return
@@ -65,7 +65,6 @@ final class AppSettings {
}
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
private static var remoteSuiteName = "\(InfoPlistReader.main.appGroupIdentifier).remote"
/// UserDefaults to be used on reads and writes.
private static var store: UserDefaults! = UserDefaults(suiteName: suiteName)
@@ -252,8 +251,8 @@ final class AppSettings {
var pusherProfileTag: String?
// MARK: - Bug report
let bugReportRageshakeURL: URL? = Secrets.rageshakeURL.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping
let bugReportRageshakeURL: ConfigurableSetting<RageshakeConfiguration> = .init(Secrets.rageshakeURL.map { .url(URL(string: $0)!) } ?? .disabled) // swiftlint:disable:this force_unwrapping
let bugReportSentryURL: URL? = Secrets.sentryDSN.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping
let bugReportSentryRustURL: URL? = Secrets.sentryRustDSN.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping
/// The name allocated by the bug report server
@@ -0,0 +1,35 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
/// A setting that can be instantiated with an initial value, overridden and reset back again,
/// automatically publishing the changes for downstream subscribers to react to.
///
/// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the
/// override persisted between app launches.
struct ConfigurableSetting<T: Equatable> {
private let initialValue: T
private let subject: CurrentValueSubject<T, Never>
var publisher: CurrentValuePublisher<T, Never> { subject.asCurrentValuePublisher() }
var isOverridden: Bool { subject.value != initialValue }
init(_ initialValue: T) {
self.initialValue = initialValue
subject = .init(initialValue)
}
func override(_ value: T) {
subject.send(value)
}
func reset() {
if isOverridden {
subject.send(initialValue)
}
}
}
@@ -8,51 +8,42 @@
import Foundation
import MatrixRustSDK
@MainActor
enum Target: String {
case mainApp = "elementx"
case nse
case shareExtension = "shareextension"
case tests
private static var isConfigured = false
func configure(logLevel: LogLevel, traceLogPacks: Set<TraceLogPack>, sentryURL: URL?) {
guard !Self.isConfigured else {
return
var useLightweightTokioRuntime: Bool {
switch self {
case .mainApp: false
case .nse: true
case .shareExtension: true
case .tests: false
}
}
var logFilePrefix: String? {
switch self {
case .mainApp: nil
default: rawValue
}
}
/// Configures the target with logging and an appropriate runtime.
///
/// Returns a `Configuration` which should be stored to
/// a) detect whether the platform is already configured.
/// b) reconfigure the platform if necessary.
func configure(logLevel: LogLevel, traceLogPacks: Set<TraceLogPack>, sentryURL: URL?) -> Configuration {
let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel,
traceLogPacks: traceLogPacks,
currentTarget: rawValue,
filePrefix: logFilePrefix,
sentryURL: sentryURL)
do {
switch self {
case .mainApp:
let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel,
traceLogPacks: traceLogPacks,
currentTarget: rawValue,
filePrefix: nil,
sentryURL: sentryURL)
try initPlatform(config: tracingConfiguration, useLightweightTokioRuntime: false)
case .nse:
let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel,
traceLogPacks: traceLogPacks,
currentTarget: rawValue,
filePrefix: rawValue,
sentryURL: sentryURL)
try initPlatform(config: tracingConfiguration, useLightweightTokioRuntime: true)
case .shareExtension:
let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel,
traceLogPacks: traceLogPacks,
currentTarget: rawValue,
filePrefix: rawValue,
sentryURL: sentryURL)
try initPlatform(config: tracingConfiguration, useLightweightTokioRuntime: true)
case .tests:
let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel,
traceLogPacks: traceLogPacks,
currentTarget: rawValue,
filePrefix: rawValue,
sentryURL: sentryURL)
try initPlatform(config: tracingConfiguration, useLightweightTokioRuntime: false)
}
try initPlatform(config: tracingConfiguration, useLightweightTokioRuntime: useLightweightTokioRuntime)
} catch {
fatalError("Failed configuring target \(self) with error: \(error)")
}
@@ -63,6 +54,14 @@ enum Target: String {
MXLog.configure(currentTarget: rawValue)
Self.isConfigured = true
return Configuration(tracingConfiguration: tracingConfiguration)
}
/// Represents the configuration that was applied by ``configure(logLevel:traceLogPacks:sentryURL:)``.
struct Configuration {
/// The configuration applied when calling ``configure(logLevel:traceLogPacks:sentryURL:)``.
///
/// **Note:** This is immutable and won't be updated to reflect further changes.
let tracingConfiguration: TracingConfiguration
}
}
@@ -1995,47 +1995,6 @@ class BugReportServiceMock: BugReportServiceProtocol, @unchecked Sendable {
var underlyingCrashedLastRun: Bool!
var lastCrashEventID: String?
//MARK: - applyConfiguration
var applyConfigurationUnderlyingCallsCount = 0
var applyConfigurationCallsCount: Int {
get {
if Thread.isMainThread {
return applyConfigurationUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = applyConfigurationUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
applyConfigurationUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
applyConfigurationUnderlyingCallsCount = newValue
}
}
}
}
var applyConfigurationCalled: Bool {
return applyConfigurationCallsCount > 0
}
var applyConfigurationReceivedConfiguration: RageshakeConfiguration?
var applyConfigurationReceivedInvocations: [RageshakeConfiguration] = []
var applyConfigurationClosure: ((RageshakeConfiguration) -> Void)?
func applyConfiguration(_ configuration: RageshakeConfiguration) {
applyConfigurationCallsCount += 1
applyConfigurationReceivedConfiguration = configuration
DispatchQueue.main.async {
self.applyConfigurationReceivedInvocations.append(configuration)
}
applyConfigurationClosure?(configuration)
}
//MARK: - submitBugReport
var submitBugReportProgressListenerUnderlyingCallsCount = 0
@@ -185,13 +185,23 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
}
// We only set the analytics configuration if analytics are enabled
let analyticsConfiguration = analyticsService.isEnabled ? ElementCallAnalyticsConfiguration(posthogAPIHost: appSettings.elementCallPosthogAPIHost,
posthogAPIKey: appSettings.elementCallPosthogAPIKey,
sentryDSN: appSettings.elementCallPosthogSentryDSN) : nil
let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled {
.init(posthogAPIHost: appSettings.elementCallPosthogAPIHost,
posthogAPIKey: appSettings.elementCallPosthogAPIKey,
sentryDSN: appSettings.elementCallPosthogSentryDSN)
} else {
nil
}
let rageshakeURL: String? = if case let .url(baseURL) = appSettings.bugReportRageshakeURL.publisher.value {
baseURL.absoluteString
} else {
nil
}
switch await widgetDriver.start(baseURL: baseURL,
clientID: clientID,
colorScheme: colorScheme,
rageshakeURL: appSettings.bugReportRageshakeURL?.absoluteString,
rageshakeURL: rageshakeURL,
analyticsConfiguration: analyticsConfiguration) {
case .success(let url):
state.url = url
@@ -12,10 +12,7 @@ import Sentry
import UIKit
class BugReportService: NSObject, BugReportServiceProtocol {
/// The rageshake URL as provided in the init.
private let defaultRageshakeURL: URL?
/// The rageshake URL currently being used by the service.
private var rageshakeURL: URL?
private var rageshakeURL: RageshakeConfiguration
private let applicationID: String
private let sdkGitSHA: String
private let maxUploadSize: Int
@@ -25,23 +22,27 @@ class BugReportService: NSObject, BugReportServiceProtocol {
private let progressSubject = PassthroughSubject<Double, Never>()
private var cancellables = Set<AnyCancellable>()
var isEnabled: Bool { rageshakeURL != nil }
var isEnabled: Bool { rageshakeURL != .disabled }
var lastCrashEventID: String?
init(rageshakeURL: URL?,
init(rageshakeURLPublisher: CurrentValuePublisher<RageshakeConfiguration, Never>,
applicationID: String,
sdkGitSHA: String,
maxUploadSize: Int,
session: URLSession = .shared,
appHooks: AppHooks) {
defaultRageshakeURL = rageshakeURL
self.rageshakeURL = rageshakeURL
rageshakeURL = rageshakeURLPublisher.value
self.applicationID = applicationID
self.sdkGitSHA = sdkGitSHA
self.maxUploadSize = maxUploadSize
self.session = session
self.appHooks = appHooks
super.init()
rageshakeURLPublisher
.weakAssign(to: \.rageshakeURL, on: self)
.store(in: &cancellables)
}
// MARK: - BugReportServiceProtocol
@@ -50,21 +51,10 @@ class BugReportService: NSObject, BugReportServiceProtocol {
SentrySDK.crashedLastRun
}
func applyConfiguration(_ configuration: RageshakeConfiguration) {
switch configuration {
case .url(let url):
rageshakeURL = url
case .disabled:
rageshakeURL = nil
case .default:
rageshakeURL = defaultRageshakeURL
}
}
// swiftlint:disable:next cyclomatic_complexity
func submitBugReport(_ bugReport: BugReport,
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
guard let rageshakeURL else {
guard case let .url(rageshakeURL) = rageshakeURL else {
fatalError("No bug report URL set, the screen should not be shown in this case.")
}
@@ -46,13 +46,11 @@ enum BugReportServiceError: LocalizedError {
}
}
enum RageshakeConfiguration {
enum RageshakeConfiguration: Equatable {
/// Rageshakes should be sent to the provided URL
case url(URL)
/// Rageshakes are disabled.
case disabled
/// No customisations are made, use the default configuration.
case `default`
}
// sourcery: AutoMockable
@@ -62,8 +60,6 @@ protocol BugReportServiceProtocol: AnyObject {
var lastCrashEventID: String? { get set }
func applyConfiguration(_ configuration: RageshakeConfiguration)
func submitBugReport(_ bugReport: BugReport,
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError>
}