diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index b7e0e5423..79686461c 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -254,6 +254,7 @@ 2D0E3983288E2D35613AD681 /* SecureBackupControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */; }; 2D2D8A53B35BE8D8A01449C6 /* PinnedEventsBannerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */; }; 2D38D39B1789B91AE69F477F /* PhotoLibraryManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD955A0380C287C418F1A74D /* PhotoLibraryManagerMock.swift */; }; + 2D45A04699BB6BA3B3A0CB9A /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; }; 2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; }; 2DD9D0FE7CB5CFC80D071451 /* AppLockScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */; }; 2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; }; @@ -391,6 +392,7 @@ 494970EA811FE4D93AC68482 /* SettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE8A35B4AD5256F4B562274 /* SettingsScreenViewModelTests.swift */; }; 4949C8C12669D1B5E082366E /* QRCodeLoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA9EA59D5C0DA1BFC7B3621 /* QRCodeLoginScreen.swift */; }; 49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */; }; + 49BBEC46D523BF6A41400048 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34956C87731AB094DB33A /* URLTests.swift */; }; 4A4110369DBB79E4A314F415 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */; }; 4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; }; 4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */; }; @@ -434,6 +436,7 @@ 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0FF64B0E6470F66F42E182 /* EstimatedWaveformView.swift */; }; 5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; }; 510C4EDF826CA9C6CEEC6C95 /* ManageRoomMemberSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34D9BCA1A7D9A56E1EAF1D /* ManageRoomMemberSheetViewModel.swift */; }; + 51263FC33CC961A14F5D6BCA /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; }; 5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; }; 513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; }; 51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; }; @@ -1159,6 +1162,7 @@ DF8F1211F2B0B56F0FCCA5C2 /* CertificateValidatorHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3865AD7B7249C939D7C69C33 /* CertificateValidatorHook.swift */; }; DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; + E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; }; E0B6A569AC3E81D233B43D60 /* SettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E625B0EB2F86B37C14EF7E6 /* SettingsScreenViewModel.swift */; }; E0C167D41A48EDB30B447DE3 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A5C3F7C9C1DA10CAEC6A98 /* VoiceMessageRecordingComposer.swift */; }; E14E469CD97550D0FC58F3CA /* CancellableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52983FAFB4E0998C00EE8A /* CancellableTask.swift */; }; @@ -1650,6 +1654,7 @@ 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = ""; }; 2A2BB38DF61F5100B8723112 /* TimelineMediaPreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewModels.swift; sourceTree = ""; }; 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; + 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingHook.swift; sourceTree = ""; }; 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = ""; }; 2ADF12A50186B75C68017B61 /* DeclineAndBlockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenViewModelTests.swift; sourceTree = ""; }; 2AE807361805463F5AEDD1CA /* VoiceMessagePreviewComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessagePreviewComposer.swift; sourceTree = ""; }; @@ -1720,6 +1725,7 @@ 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerMock.swift; sourceTree = ""; }; 3A12D3D8138F1B71AFA7C858 /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = ""; }; 3A21027F05874B1BCC3E452B /* InvitedRoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitedRoomProxyMock.swift; sourceTree = ""; }; + 3AB34956C87731AB094DB33A /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = ""; }; 3AD253E7EFF88F308D644272 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/SAS.strings"; sourceTree = ""; }; 3B5E97E9615A158C76B2AB77 /* DateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTests.swift; sourceTree = ""; }; 3B927A399A5418DA40A5CA15 /* StartChatScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenTests.swift; sourceTree = ""; }; @@ -3306,6 +3312,7 @@ 8B89D6C760E8CAE29CA28FB1 /* CompoundHook.swift */, D5D186A6DB8FAC5C9D0E4D61 /* RemoteSettingsHook.swift */, B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */, + 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */, ); path = Hooks; sourceTree = ""; @@ -4479,6 +4486,7 @@ 5C1F000589F2CEE6B03ECFAB /* TimelineMediaPreviewViewModelTests.swift */, 6509708F54FC883604DFDC95 /* TimelineViewModelTests.swift */, 76310030C831D4610A705603 /* URLComponentsTests.swift */, + 3AB34956C87731AB094DB33A /* URLTests.swift */, EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */, 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */, BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */, @@ -6976,6 +6984,7 @@ CE4B342F9DD747CF4BEDB5AB /* TestablePreview.swift in Sources */, AC3C3D6D4AD31F13EE987390 /* TraceLogPack.swift in Sources */, B81840E45D8746A4692DA774 /* Tracing.swift in Sources */, + 2D45A04699BB6BA3B3A0CB9A /* TracingHook.swift in Sources */, DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */, 45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */, 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */, @@ -7109,6 +7118,7 @@ 8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */, AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */, 20C16A3F718802B0E4A19C83 /* URLComponentsTests.swift in Sources */, + 49BBEC46D523BF6A41400048 /* URLTests.swift in Sources */, 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */, E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */, A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */, @@ -7163,6 +7173,7 @@ 6E03A710799E6C65C0AB36BC /* TargetConfiguration.swift in Sources */, A5FD8284744E2FECFC842FC1 /* TraceLogPack.swift in Sources */, 89DF67AECBF9D0EE0DDB7737 /* Tracing.swift in Sources */, + 51263FC33CC961A14F5D6BCA /* TracingHook.swift in Sources */, 03BD83E8BDD23AE059802E0D /* UITestsScreenIdentifier.swift in Sources */, 26252AA9AED64010788F4C26 /* UIView.swift in Sources */, 66E9202BED03B5BB00E812A1 /* URL.swift in Sources */, @@ -8032,6 +8043,7 @@ B3D8AA9988F8A000B162DCB5 /* TombstonedAvatarImage.swift in Sources */, 6E44638FDF7D4B0F80EFA7EA /* TraceLogPack.swift in Sources */, 126CBCF5B0145FA1377C1316 /* Tracing.swift in Sources */, + E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */, 298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */, 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */, A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */, diff --git a/ElementX/Sources/AppHooks/AppHooks.swift b/ElementX/Sources/AppHooks/AppHooks.swift index 9e00b9546..25e934bdc 100644 --- a/ElementX/Sources/AppHooks/AppHooks.swift +++ b/ElementX/Sources/AppHooks/AppHooks.swift @@ -39,6 +39,11 @@ class AppHooks: AppHooksProtocol { } #endif + private(set) var tracingHook: TracingHookProtocol = DefaultTracingHook() + func registerTracingHook(_ hook: TracingHookProtocol) { + tracingHook = hook + } + private(set) var clientBuilderHook: ClientBuilderHookProtocol = DefaultClientBuilderHook() func registerClientBuilderHook(_ hook: ClientBuilderHookProtocol) { clientBuilderHook = hook diff --git a/ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift b/ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift index fcab8269e..0e020877a 100644 --- a/ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift +++ b/ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift @@ -18,7 +18,7 @@ protocol RemoteSettingsHookProtocol { func updateCache(using client: ClientProtocol) async func reset(_ appSettings: CommonSettingsProtocol) #endif - func loadCache(forHomeserver: String, applyingTo appSettings: CommonSettingsProtocol) + func loadCache(forHomeserver homeserver: String, applyingTo appSettings: CommonSettingsProtocol) } struct DefaultRemoteSettingsHook: RemoteSettingsHookProtocol { @@ -51,7 +51,7 @@ struct DefaultRemoteSettingsHook: RemoteSettingsHookProtocol { func reset(_ appSettings: any CommonSettingsProtocol) { } #endif - func loadCache(forHomeserver: String, applyingTo appSettings: CommonSettingsProtocol) { } + func loadCache(forHomeserver homeserver: String, applyingTo appSettings: CommonSettingsProtocol) { } } private struct ElementWellKnown: Decodable { diff --git a/ElementX/Sources/AppHooks/Hooks/TracingHook.swift b/ElementX/Sources/AppHooks/Hooks/TracingHook.swift new file mode 100644 index 000000000..c112f1c54 --- /dev/null +++ b/ElementX/Sources/AppHooks/Hooks/TracingHook.swift @@ -0,0 +1,16 @@ +// +// 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 MatrixRustSDK + +protocol TracingHookProtocol { + func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference) +} + +struct DefaultTracingHook: TracingHookProtocol { + func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference) { } +} diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index cc362e7cb..546ec23af 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -18,7 +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 targetConfiguration: Target.ConfigurationResult private let appMediator: AppMediator private let appSettings: AppSettings private let appDelegate: AppDelegate @@ -77,7 +77,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg targetConfiguration = Target.mainApp.configure(logLevel: appSettings.logLevel, traceLogPacks: appSettings.traceLogPacks, - sentryURL: appSettings.bugReportSentryRustURL) + sentryURL: appSettings.bugReportSentryRustURL, + rageshakeURL: appSettings.bugReportRageshakeURL, + appHooks: appHooks) let appName = InfoPlistReader.main.bundleDisplayName let appVersion = InfoPlistReader.main.bundleShortVersionString diff --git a/ElementX/Sources/Application/Settings/RemotePreference.swift b/ElementX/Sources/Application/Settings/RemotePreference.swift index 8c1eebc14..47c97e218 100644 --- a/ElementX/Sources/Application/Settings/RemotePreference.swift +++ b/ElementX/Sources/Application/Settings/RemotePreference.swift @@ -12,7 +12,7 @@ import Combine /// /// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the /// remote value persisted between app launches. -struct RemotePreference { +class RemotePreference { private let defaultValue: T private let subject: CurrentValueSubject var publisher: CurrentValuePublisher { subject.asCurrentValuePublisher() } diff --git a/ElementX/Sources/Application/TargetConfiguration.swift b/ElementX/Sources/Application/TargetConfiguration.swift index b1f338069..5bae1cd76 100644 --- a/ElementX/Sources/Application/TargetConfiguration.swift +++ b/ElementX/Sources/Application/TargetConfiguration.swift @@ -5,6 +5,7 @@ // Please see LICENSE files in the repository root for full details. // +import Combine import Foundation import MatrixRustSDK @@ -32,10 +33,14 @@ enum Target: String { /// Configures the target with logging and an appropriate runtime. /// - /// Returns a `Configuration` which should be stored to + /// Returns a `ConfigurationResult` 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, sentryURL: URL?) -> Configuration { + /// b) automatically reconfigure the platform as necessary. + func configure(logLevel: LogLevel, + traceLogPacks: Set, + sentryURL: URL?, + rageshakeURL: RemotePreference, + appHooks: AppHooks) -> ConfigurationResult { let tracingConfiguration = Tracing.buildConfiguration(logLevel: logLevel, traceLogPacks: traceLogPacks, currentTarget: rawValue, @@ -54,14 +59,21 @@ enum Target: String { MXLog.configure(currentTarget: rawValue) - return Configuration(tracingConfiguration: tracingConfiguration) + let hookCancellable = rageshakeURL.publisher + .sink { _ in + appHooks.tracingHook.update(tracingConfiguration, with: rageshakeURL) + } + + return ConfigurationResult(hookCancellable: hookCancellable) } - /// 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 + /// The result of calling ``configure(logLevel:traceLogPacks:sentryURL:)``. + /// This must be stored - see the docs on the configure method to learn more. + struct ConfigurationResult { + private let hookCancellable: AnyCancellable + + init(hookCancellable: AnyCancellable) { + self.hookCancellable = hookCancellable + } } } diff --git a/ElementX/Sources/Mocks/SDK/ClientSDKMock.swift b/ElementX/Sources/Mocks/SDK/ClientSDKMock.swift index 74ddfdda6..776c6e5b6 100644 --- a/ElementX/Sources/Mocks/SDK/ClientSDKMock.swift +++ b/ElementX/Sources/Mocks/SDK/ClientSDKMock.swift @@ -42,6 +42,7 @@ extension ClientSDKMock { slidingSyncVersionReturnValue = configuration.slidingSyncVersion userIdServerNameThrowableError = MockError.generic serverReturnValue = "https://\(configuration.serverAddress)" + homeserverReturnValue = configuration.homeserverURL urlForOidcOidcConfigurationPromptLoginHintDeviceIdReturnValue = OAuthAuthorizationDataSDKMock(configuration: configuration) loginUsernamePasswordInitialDeviceNameDeviceIdClosure = { username, password, _, _ in guard username == configuration.validCredentials.username, diff --git a/ElementX/Sources/Other/Extensions/URL.swift b/ElementX/Sources/Other/Extensions/URL.swift index 6ed52c0de..cff546c3a 100644 --- a/ElementX/Sources/Other/Extensions/URL.swift +++ b/ElementX/Sources/Other/Extensions/URL.swift @@ -7,15 +7,9 @@ import Foundation -extension URL: @retroactive ExpressibleByStringLiteral { - public init(stringLiteral value: StaticString) { - guard let url = URL(string: "\(value)") else { - fatalError("The static string used to create this URL is invalid") - } - - self = url - } +// MARK: - Custom URLs +extension URL { /// The URL of the primary app group container. static var appGroupContainerDirectory: URL { guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: InfoPlistReader.main.appGroupIdentifier) else { @@ -104,6 +98,45 @@ extension URL: @retroactive ExpressibleByStringLiteral { return nil } + // MARK: Mocks + + static var mockMXCAudio: URL { "mxc://matrix.org/1234567890AuDiO" } + static var mockMXCFile: URL { "mxc://matrix.org/1234567890FiLe" } + static var mockMXCImage: URL { "mxc://matrix.org/1234567890ImAgE" } + static var mockMXCVideo: URL { "mxc://matrix.org/1234567890ViDeO" } + static var mockMXCAvatar: URL { "mxc://matrix.org/1234567890AvAtAr" } + static var mockMXCUserAvatar: URL { "mxc://matrix.org/1234567890AvAtArUsEr" } +} + +// MARK: - Helpers + +extension URL: @retroactive ExpressibleByStringLiteral { + public init(stringLiteral value: StaticString) { + guard let url = URL(string: "\(value)") else { + fatalError("The static string used to create this URL is invalid") + } + + self = url + } + + /// Sanitises the URL for use as the name of a directory. + func asDirectoryName() -> String { + absoluteString.asURLDirectoryName() + } +} + +extension String { + /// Assumes that the string is a URL and sanitises it for use as the name of a directory. + func asURLDirectoryName() -> String { + replacingOccurrences(of: "https://", with: "") + .trimmingCharacters(in: CharacterSet(charactersIn: "/")) + .replacing(/[:\/\p{C}]/, with: "-") + } +} + +// MARK: - Phishing Confirmation URL + +extension URL { static let confirmationScheme = "confirm" var requiresConfirmation: Bool { @@ -117,15 +150,6 @@ extension URL: @retroactive ExpressibleByStringLiteral { } return ConfirmURLParameters(queryItems: queryItems) } - - // MARK: Mocks - - static var mockMXCAudio: URL { "mxc://matrix.org/1234567890AuDiO" } - static var mockMXCFile: URL { "mxc://matrix.org/1234567890FiLe" } - static var mockMXCImage: URL { "mxc://matrix.org/1234567890ImAgE" } - static var mockMXCVideo: URL { "mxc://matrix.org/1234567890ViDeO" } - static var mockMXCAvatar: URL { "mxc://matrix.org/1234567890AvAtAr" } - static var mockMXCUserAvatar: URL { "mxc://matrix.org/1234567890AvAtArUsEr" } } struct ConfirmURLParameters { diff --git a/Enterprise b/Enterprise index 2333bf989..6db7fe89b 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 2333bf9890127b567f056fb028ea06e64d2889d2 +Subproject commit 6db7fe89bdf0a4fe8be9278a5290002bb05f93d3 diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index b42209a08..50bb2ede6 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -5,6 +5,7 @@ // Please see LICENSE files in the repository root for full details. // +import Combine import MatrixRustSDK import UserNotifications @@ -28,7 +29,7 @@ import UserNotifications // notification. class NotificationServiceExtension: UNNotificationServiceExtension { - private static var targetConfiguration: Target.Configuration? + private static var targetConfiguration: Target.ConfigurationResult? private let settings: CommonSettingsProtocol = AppSettings() private let appHooks: AppHooks @@ -36,6 +37,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { private let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) + private var cancellables: Set = [] + // We can make the whole NSE a MainActor after https://github.com/swiftlang/swift-evolution/blob/main/proposals/0371-isolated-synchronous-deinit.md // otherwise we wouldn't be able to log the tag in the deinit. deinit { @@ -50,8 +53,12 @@ class NotificationServiceExtension: UNNotificationServiceExtension { if Self.targetConfiguration == nil { Self.targetConfiguration = Target.nse.configure(logLevel: settings.logLevel, traceLogPacks: settings.traceLogPacks, - sentryURL: nil) + sentryURL: nil, + rageshakeURL: settings.bugReportRageshakeURL, + appHooks: appHooks) } + + super.init() } override func didReceive(_ request: UNNotificationRequest, diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index faf8126f3..183d94cb1 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -81,6 +81,7 @@ targets: - path: ../Sources - path: ../SupportingFiles - path: ../../ElementX/Sources/AppHooks/AppHooks.swift + - path: ../../ElementX/Sources/AppHooks/Hooks/TracingHook.swift - path: ../../ElementX/Sources/AppHooks/Hooks/ClientBuilderHook.swift - path: ../../ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift - path: ../../Secrets/Secrets.swift diff --git a/ShareExtension/Sources/ShareExtensionViewController.swift b/ShareExtension/Sources/ShareExtensionViewController.swift index 00d8592b8..52d8c35a6 100644 --- a/ShareExtension/Sources/ShareExtensionViewController.swift +++ b/ShareExtension/Sources/ShareExtensionViewController.swift @@ -5,17 +5,20 @@ // Please see LICENSE files in the repository root for full details. // +import Combine import IntentsUI import SwiftUI class ShareExtensionViewController: UIViewController { - private static var targetConfiguration: Target.Configuration? + private static var targetConfiguration: Target.ConfigurationResult? private let appSettings: CommonSettingsProtocol = AppSettings() private var appHooks: AppHooks! private let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) + private var cancellables: Set = [] + private let hostingController = UIHostingController(rootView: ShareExtensionView()) override func viewDidLoad() { @@ -27,7 +30,9 @@ class ShareExtensionViewController: UIViewController { if Self.targetConfiguration == nil { Self.targetConfiguration = Target.shareExtension.configure(logLevel: appSettings.logLevel, traceLogPacks: appSettings.traceLogPacks, - sentryURL: nil) + sentryURL: nil, + rageshakeURL: appSettings.bugReportRageshakeURL, + appHooks: appHooks) } addChild(hostingController) diff --git a/ShareExtension/SupportingFiles/target.yml b/ShareExtension/SupportingFiles/target.yml index 3a7707137..9abf2f76b 100644 --- a/ShareExtension/SupportingFiles/target.yml +++ b/ShareExtension/SupportingFiles/target.yml @@ -81,6 +81,7 @@ targets: - path: ../SupportingFiles - path: ../../ElementX/Sources/ShareExtension - path: ../../ElementX/Sources/AppHooks/AppHooks.swift + - path: ../../ElementX/Sources/AppHooks/Hooks/TracingHook.swift - path: ../../ElementX/Sources/AppHooks/Hooks/ClientBuilderHook.swift - path: ../../ElementX/Sources/AppHooks/Hooks/RemoteSettingsHook.swift - path: ../../Secrets/Secrets.swift diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 703cae36b..1b5ea5a4e 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -10,7 +10,7 @@ import XCTest class LoggingTests: XCTestCase { - static var targetConfiguration: Target.Configuration? + static var targetConfiguration: Target.ConfigurationResult? private enum Constants { static let genericFailure = "Test failed" @@ -25,7 +25,11 @@ class LoggingTests: XCTestCase { XCTAssertTrue(Tracing.logFiles.isEmpty) if Self.targetConfiguration == nil { - Self.targetConfiguration = Target.tests.configure(logLevel: .info, traceLogPacks: [], sentryURL: nil) + Self.targetConfiguration = Target.tests.configure(logLevel: .info, + traceLogPacks: [], + sentryURL: nil, + rageshakeURL: ServiceLocator.shared.settings.bugReportRageshakeURL, + appHooks: AppHooks()) } // There is something weird with Rust logging where the file writing handle doesn't @@ -180,7 +184,11 @@ class LoggingTests: XCTestCase { // When logging that value if Self.targetConfiguration == nil { - Self.targetConfiguration = Target.tests.configure(logLevel: .info, traceLogPacks: [], sentryURL: nil) + Self.targetConfiguration = Target.tests.configure(logLevel: .info, + traceLogPacks: [], + sentryURL: nil, + rageshakeURL: ServiceLocator.shared.settings.bugReportRageshakeURL, + appHooks: AppHooks()) } MXLog.info(textMessage) diff --git a/UnitTests/Sources/URLTests.swift b/UnitTests/Sources/URLTests.swift new file mode 100644 index 000000000..b7a6004c4 --- /dev/null +++ b/UnitTests/Sources/URLTests.swift @@ -0,0 +1,44 @@ +// +// 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 XCTest + +@testable import ElementX + +class URLTests: XCTestCase { + func testURLDirectoryName() { + let url: URL = "https://matrix.example.com/foo/bar/" + let directoryName = url.asDirectoryName() + XCTAssertEqual(directoryName, "matrix.example.com-foo-bar") + createDirectory(with: directoryName) + } + + func testComplexURLDirectoryName() { + let url: URL = "https://us%3Aer:pa%40%3Ass@[2001:db8:85a3::8a2e:370:7334]:8443/..//folder/./fi%20le(1).html;p=1;q=2" + let directoryName = url.asDirectoryName() + XCTAssertEqual(directoryName, "us%3Aer-pa%40%3Ass@[2001-db8-85a3--8a2e-370-7334]-8443-..--folder-.-fi%20le(1).html;p=1;q=2") + createDirectory(with: directoryName) + } + + // MARK: - Helpers + + func createDirectory(with directoryName: String) { + let url = URL.temporaryDirectory.appending(path: directoryName) + try? FileManager.default.removeItem(at: url) + + do { + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) + } catch { + XCTFail("Invalid file path: \(error.localizedDescription)") + } + + guard FileManager.default.directoryExists(at: url) else { + XCTFail("Invalid file path") + return + } + } +}