// // Copyright 2025 Element Creations Ltd. // Copyright 2022-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 Algorithms import Combine import Foundation import MatrixRustSDK enum RoomProxyError: Error { case sdkError(Error) case invalidURL case invalidMedia case eventNotFound case missingTransactionID case failedCreatingPinnedTimeline case timelineError(TimelineProxyError) case liveLocationSessionIsNotActive } /// An enum that describes the relationship between the current user and the room, and contains a reference to the specific implementation of the `RoomProxy`. enum RoomProxyType { case joined(JoinedRoomProxyProtocol) case invited(InvitedRoomProxyProtocol) case knocked(KnockedRoomProxyProtocol) case banned(BannedRoomProxyProtocol) case left } // sourcery: AutoMockable protocol RoomProxyProtocol { var id: String { get } var ownUserID: String { get } } // sourcery: AutoMockable protocol InvitedRoomProxyProtocol: RoomProxyProtocol { var info: BaseRoomInfoProxyProtocol { get } var inviter: RoomMemberProxyProtocol? { get } func rejectInvitation() async -> Result } // sourcery: AutoMockable protocol KnockedRoomProxyProtocol: RoomProxyProtocol { var info: BaseRoomInfoProxyProtocol { get } func cancelKnock() async -> Result } // sourcery: AutoMockable protocol BannedRoomProxyProtocol: RoomProxyProtocol { var info: BaseRoomInfoProxyProtocol { get } func forgetRoom() async -> Result } enum JoinedRoomProxyAction: Equatable { case roomInfoUpdate } enum KnockRequestsState { case loading case loaded([KnockRequestProxyProtocol]) } struct RTCDeclinedEvent { /// The sender of the decline event let sender: String /// The rtc.notification event that is beeing declined let notificationEventID: String } // sourcery: AutoMockable protocol JoinedRoomProxyProtocol: RoomProxyProtocol { var infoPublisher: CurrentValuePublisher { get } var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get } var typingMembersPublisher: CurrentValuePublisher<[String], Never> { get } var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get } var knockRequestsStatePublisher: CurrentValuePublisher { get } var timeline: TimelineProxyProtocol { get } var predecessorRoom: PredecessorRoom? { get } var successorRoom: SuccessorRoom? { get } func subscribeForUpdates() async func subscribeToRoomInfoUpdates() func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result func threadTimeline(eventID: String) async -> Result func threadListService() -> RoomThreadListServiceProxyProtocol func loadOrFetchEventDetails(for eventID: String) async -> Result func messageFilteredTimeline(focus: TimelineFocus, allowedMessageTypes: [TimelineAllowedMessageType], presentation: TimelineKind.MediaPresentation) async -> Result func pinnedEventsTimeline() async -> Result func enableEncryption() async -> Result func redact(_ eventID: String) async -> Result func reportContent(_ eventID: String, reason: String?) async -> Result func reportRoom(reason: String) async -> Result func leaveRoom() async -> Result func updateMembers() async func getMember(userID: String) async -> Result func invite(userID: String) async -> Result func setName(_ name: String) async -> Result func setTopic(_ topic: String) async -> Result func removeAvatar() async -> Result func uploadAvatar(media: MediaInfo) async -> Result func markAsRead(receiptType: ReceiptType) async -> Result func edit(eventID: String, newContent: RoomMessageEventContentWithoutRelation) async -> Result /// https://spec.matrix.org/v1.9/client-server-api/#typing-notifications @discardableResult func sendTypingNotification(isTyping: Bool) async -> Result func ignoreDeviceTrustAndResend(devices: [String: [String]], sendHandle: SendHandleProxy) async -> Result func withdrawVerificationAndResend(userIDs: [String], sendHandle: SendHandleProxy) async -> Result // MARK: - Privacy settings func updateJoinRule(_ rule: JoinRule) async -> Result func updateHistoryVisibility(_ visibility: RoomHistoryVisibility) async -> Result func isVisibleInRoomDirectory() async -> Result func updateRoomDirectoryVisibility(_ visibility: RoomVisibility) async -> Result // MARK: - Canonical Alias func updateCanonicalAlias(_ alias: String?, altAliases: [String]) async -> Result func publishRoomAliasInRoomDirectory(_ alias: String) async -> Result func removeRoomAliasFromRoomDirectory(_ alias: String) async -> Result // MARK: - Room Flags func flagAsUnread(_ isUnread: Bool) async -> Result func flagAsFavourite(_ isFavourite: Bool) async -> Result // MARK: - Power Levels func powerLevels() async -> Result func applyPowerLevelChanges(_ changes: RoomPowerLevelChanges) async -> Result func resetPowerLevels() async -> Result func suggestedRole(for userID: String) async -> Result func updatePowerLevelsForUsers(_ updates: [(userID: String, powerLevel: Int64)]) async -> Result // MARK: - Moderation func kickUser(_ userID: String, reason: String?) async -> Result func banUser(_ userID: String, reason: String?) async -> Result func unbanUser(_ userID: String) async -> Result // MARK: - Element Call func elementCallWidgetDriver(deviceID: String) -> ElementCallWidgetDriverProtocol func declineCall(notificationID: String) async -> Result func subscribeToCallDeclineEvents(rtcNotificationEventID: String, listener: CallDeclineListener) -> Result // MARK: - Permalinks func matrixToPermalink() async -> Result func matrixToEventPermalink(_ eventID: String) async -> Result // MARK: - Drafts func saveDraft(_ draft: ComposerDraft, threadRootEventID: String?) async -> Result func loadDraft(threadRootEventID: String?) async -> Result func clearDraft(threadRootEventID: String?) async -> Result // MARK: - Live Location func makeLiveLocationService() async -> RoomLiveLocationServiceProtocol func startLiveLocationShare(duration: Duration) async -> Result func sendLiveLocation(geoURI: GeoURI) async -> Result func stopLiveLocationShare() async -> Result } extension JoinedRoomProxyProtocol { var details: RoomDetails { let historySharingState: RoomHistorySharingState? = if infoPublisher.value.isEncrypted { infoPublisher.value.historySharingState } else { nil } return RoomDetails(id: id, name: infoPublisher.value.displayName, avatar: infoPublisher.value.avatar, canonicalAlias: infoPublisher.value.canonicalAlias, isEncrypted: infoPublisher.value.isEncrypted, isPublic: !(infoPublisher.value.isPrivate ?? false), isDirect: infoPublisher.value.isDirect, historySharingState: historySharingState) } var isDirectOneToOneRoom: Bool { infoPublisher.value.isDM } func members() async -> [RoomMemberProxyProtocol]? { await updateMembers() return membersPublisher.value } /// This is a horrible workaround for not having any server names available when using tombstone links with v12 room IDs. func knownServerNames(maxCount: Int) -> any Sequence { membersPublisher.value .prefix(1000) // No need to go crazy here… .compactMap { $0.userID.split(separator: ":").last.map(String.init) } .uniqued() .prefix(maxCount) } }