diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 4f67f71e7..00ceaa59b 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -372,7 +372,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(baseURL: appSettings.bugReportServiceBaseURL, + ServiceLocator.shared.register(bugReportService: BugReportService(rageshakeURL: appSettings.bugReportRageshakeURL, applicationID: appSettings.bugReportApplicationID, sdkGitSHA: sdkGitSha(), maxUploadSize: appSettings.bugReportMaxUploadSize, diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index c4df2819d..01da0fa94 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -249,7 +249,7 @@ final class AppSettings { // MARK: - Bug report - let bugReportServiceBaseURL: URL? = Secrets.rageshakeServerURL.map { URL(string: $0)! } // swiftlint:disable:this force_unwrapping + let bugReportRageshakeURL: URL? = Secrets.rageshakeURL.map { URL(string: $0)! } // 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 diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 81d8e4622..042f7f85a 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2142,6 +2142,47 @@ 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 diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 0f8a7646a..166cafba3 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -191,7 +191,7 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol switch await widgetDriver.start(baseURL: baseURL, clientID: clientID, colorScheme: colorScheme, - rageshakeURL: appSettings.bugReportServiceBaseURL?.absoluteString, + rageshakeURL: appSettings.bugReportRageshakeURL?.absoluteString, analyticsConfiguration: analyticsConfiguration) { case .success(let url): state.url = url diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index 5a1823621..649f1e0f1 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -12,27 +12,30 @@ import Sentry import UIKit class BugReportService: NSObject, BugReportServiceProtocol { - private let baseURL: URL? + /// 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 let applicationID: String private let sdkGitSHA: String private let maxUploadSize: Int private let session: URLSession - private let appHooks: AppHooks private let progressSubject = PassthroughSubject() private var cancellables = Set() - var isEnabled: Bool { baseURL != nil } + var isEnabled: Bool { rageshakeURL != nil } var lastCrashEventID: String? - init(baseURL: URL?, + init(rageshakeURL: URL?, applicationID: String, sdkGitSHA: String, maxUploadSize: Int, session: URLSession = .shared, appHooks: AppHooks) { - self.baseURL = baseURL + defaultRageshakeURL = rageshakeURL + self.rageshakeURL = rageshakeURL self.applicationID = applicationID self.sdkGitSHA = sdkGitSHA self.maxUploadSize = maxUploadSize @@ -46,11 +49,22 @@ class BugReportService: NSObject, BugReportServiceProtocol { var crashedLastRun: Bool { 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) async -> Result { - guard let baseURL else { + guard let rageshakeURL else { fatalError("No bug report URL set, the screen should not be shown in this case.") } @@ -107,7 +121,7 @@ class BugReportService: NSObject, BugReportServiceProtocol { } body.appendString(string: "--\(boundary)--\r\n") - var request = URLRequest(url: baseURL) + var request = URLRequest(url: rageshakeURL) request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" diff --git a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift index b46c4c435..cb05d2a56 100644 --- a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift +++ b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift @@ -46,6 +46,15 @@ enum BugReportServiceError: LocalizedError { } } +enum RageshakeConfiguration { + /// 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 protocol BugReportServiceProtocol: AnyObject { var isEnabled: Bool { get } @@ -53,6 +62,8 @@ protocol BugReportServiceProtocol: AnyObject { var lastCrashEventID: String? { get set } + func applyConfiguration(_ configuration: RageshakeConfiguration) + func submitBugReport(_ bugReport: BugReport, progressListener: CurrentValueSubject) async -> Result } diff --git a/Enterprise b/Enterprise index a3ede7e4d..0c0b8ae6b 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit a3ede7e4de5925d1ab4847b65bb1577b1429f12c +Subproject commit 0c0b8ae6bca5dfa18efcbc4c4a480bffde92ce89 diff --git a/Secrets/Secrets.pkl b/Secrets/Secrets.pkl index 58cfb312f..70492b541 100644 --- a/Secrets/Secrets.pkl +++ b/Secrets/Secrets.pkl @@ -13,7 +13,7 @@ sentryDSN: String? = read?("env:SENTRY_DSN") sentryRustDSN: String? = read?("env:SENTRY_RUST_DSN") postHogHost: String? = read?("env:POSTHOG_HOST") postHogAPIKey: String? = read?("env:POSTHOG_API_KEY") -rageshakeServerURL: String? = read?("env:RAGESHAKE_SERVER_URL") +rageshakeURL: String? = read?("env:RAGESHAKE_URL") mapLibreAPIKey: String? = read?("env:MAPLIBRE_API_KEY") output { diff --git a/Secrets/Secrets.swift b/Secrets/Secrets.swift index e51d1f2e1..187b8d003 100644 --- a/Secrets/Secrets.swift +++ b/Secrets/Secrets.swift @@ -3,7 +3,7 @@ enum Secrets { static let sentryRustDSN: String? = "https://username@sentry.localhost/project_id" static let postHogHost: String? = "https://posthog.localhost" static let postHogAPIKey: String? = "your_key" - static let rageshakeServerURL: String? = "https://rageshake.localhost" + static let rageshakeURL: String? = "https://rageshake.localhost/submit" static let mapLibreAPIKey: String? = "your_key" } \ No newline at end of file diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 0b4ee9212..a5a8b88fd 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -42,7 +42,7 @@ class BugReportServiceTests: XCTestCase { } func testInitialStateWithRealService() throws { - let service = BugReportService(baseURL: "https://www.example.com", + let service = BugReportService(rageshakeURL: "https://example.com/submit", applicationID: "mock_app_id", sdkGitSHA: "1234", maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, @@ -53,7 +53,7 @@ class BugReportServiceTests: XCTestCase { } func testInitialStateWithRealServiceAndNoURL() throws { - let service = BugReportService(baseURL: nil, + let service = BugReportService(rageshakeURL: nil, applicationID: "mock_app_id", sdkGitSHA: "1234", maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, @@ -64,7 +64,7 @@ class BugReportServiceTests: XCTestCase { } @MainActor func testSubmitBugReportWithRealService() async throws { - let service = BugReportService(baseURL: "https://www.example.com", + let service = BugReportService(rageshakeURL: "https://example.com/submit", applicationID: "mock_app_id", sdkGitSHA: "1234", maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, @@ -86,6 +86,62 @@ class BugReportServiceTests: XCTestCase { XCTAssertEqual(response.reportURL, "https://example.com/123") } + @MainActor func testConfigurations() async throws { + let service = BugReportService(rageshakeURL: "https://example.com/submit", + applicationID: "mock_app_id", + sdkGitSHA: "1234", + maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, + session: .mock, + appHooks: AppHooks()) + XCTAssertTrue(service.isEnabled) + + service.applyConfiguration(.disabled) + XCTAssertFalse(service.isEnabled) + + service.applyConfiguration(.url("https://bugs.server.net/submit")) + XCTAssertTrue(service.isEnabled) + + let bugReport = BugReport(userID: "@mock:client.com", + deviceID: nil, + ed25519: nil, + curve25519: nil, + text: "i cannot send message", + logFiles: Tracing.logFiles, + canContact: false, + githubLabels: [], + files: []) + let progressSubject = CurrentValueSubject(0.0) + let customConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get() + + XCTAssertEqual(customConfigurationResponse.reportURL, "https://bugs.server.net/123") + + service.applyConfiguration(.default) + XCTAssertTrue(service.isEnabled) + + let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get() + + XCTAssertEqual(defaultConfigurationResponse.reportURL, "https://example.com/123") + } + + func testDisabledConfigurations() { + let service = BugReportService(rageshakeURL: nil, + applicationID: "mock_app_id", + sdkGitSHA: "1234", + maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, + session: .mock, + appHooks: AppHooks()) + XCTAssertFalse(service.isEnabled) + + service.applyConfiguration(.disabled) + XCTAssertFalse(service.isEnabled) + + service.applyConfiguration(.url("https://bugs.server.net/submit")) + XCTAssertTrue(service.isEnabled) + + service.applyConfiguration(.default) + XCTAssertFalse(service.isEnabled) + } + func testLogsMaxSize() { // Given a new set of logs var logs = BugReportService.Logs(maxFileSize: 1000) @@ -114,9 +170,11 @@ class BugReportServiceTests: XCTestCase { private class MockURLProtocol: URLProtocol { override func startLoading() { - let response = "{\"report_url\":\"https://example.com/123\"}" + guard let url = request.url else { return } + let reportURL = url.deletingLastPathComponent().appending(path: "123") + let response = "{\"report_url\":\"\(reportURL.absoluteString)\"}" + if let data = response.data(using: .utf8), - let url = request.url, let urlResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) { client?.urlProtocol(self, didReceive: urlResponse, cacheStoragePolicy: .allowedInMemoryOnly) client?.urlProtocol(self, didLoad: data)