mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 03:48:27 +00:00
Incoming session verification support (#3428)
* Fixes #1227 - Add support for receiving and interacting with incoming session verification requests. * Fix a couple of random small warnings * Move static view config to the view state * Update snapshots
This commit is contained in:
@@ -50,6 +50,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
let secureBackupController: SecureBackupControllerProtocol
|
||||
|
||||
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
|
||||
|
||||
private static var roomCreationPowerLevelOverrides: PowerLevels {
|
||||
.init(usersDefault: nil,
|
||||
eventsDefault: nil,
|
||||
@@ -157,7 +159,16 @@ class ClientProxy: ClientProxyProtocol {
|
||||
updateVerificationState(client.encryption().verificationState())
|
||||
|
||||
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
|
||||
self?.updateVerificationState(verificationState)
|
||||
guard let self else { return }
|
||||
|
||||
updateVerificationState(verificationState)
|
||||
|
||||
// The session verification controller requires the user's identity which
|
||||
// isn't available before a keys query response. Use the verification
|
||||
// state updates as an aproximation for when that happens.
|
||||
Task {
|
||||
await self.buildSessionVerificationControllerProxyIfPossible(verificationState: verificationState)
|
||||
}
|
||||
})
|
||||
|
||||
sendQueueListenerTaskHandle = client.subscribeToSendQueueStatus(listener: SendQueueRoomErrorListenerProxy { [weak self] roomID, error in
|
||||
@@ -484,7 +495,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
func roomPreviewForIdentifier(_ identifier: String, via: [String]) async -> Result<RoomPreviewDetails, ClientProxyError> {
|
||||
do {
|
||||
let roomPreview = try await client.getRoomPreviewFromRoomId(roomId: identifier, viaServers: via)
|
||||
return .success(.init(roomPreview))
|
||||
let roomPreviewInfo = try roomPreview.info()
|
||||
return .success(.init(roomPreviewInfo))
|
||||
} catch let error as ClientError where error.code == .forbidden {
|
||||
return .failure(.roomPreviewIsPrivate)
|
||||
} catch {
|
||||
@@ -561,16 +573,6 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError> {
|
||||
do {
|
||||
let sessionVerificationController = try await client.getSessionVerificationController()
|
||||
return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController))
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
func logout() async -> URL? {
|
||||
do {
|
||||
return try await client.logout().flatMap(URL.init(string:))
|
||||
@@ -728,6 +730,19 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
verificationStateSubject.send(verificationState)
|
||||
}
|
||||
|
||||
private func buildSessionVerificationControllerProxyIfPossible(verificationState: VerificationState) async {
|
||||
guard sessionVerificationController == nil, verificationState != .unknown else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let sessionVerificationController = try await client.getSessionVerificationController()
|
||||
self.sessionVerificationController = SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func loadUserAvatarURLFromCache() {
|
||||
loadCachedAvatarURLTask = Task {
|
||||
@@ -1079,17 +1094,17 @@ private class SendQueueRoomErrorListenerProxy: SendQueueRoomErrorListener {
|
||||
}
|
||||
|
||||
private extension RoomPreviewDetails {
|
||||
init(_ roomPreview: RoomPreview) {
|
||||
self = RoomPreviewDetails(roomID: roomPreview.roomId,
|
||||
name: roomPreview.name,
|
||||
canonicalAlias: roomPreview.canonicalAlias,
|
||||
topic: roomPreview.topic,
|
||||
avatarURL: roomPreview.avatarUrl.flatMap(URL.init(string:)),
|
||||
memberCount: UInt(roomPreview.numJoinedMembers),
|
||||
isHistoryWorldReadable: roomPreview.isHistoryWorldReadable,
|
||||
isJoined: roomPreview.isJoined,
|
||||
isInvited: roomPreview.isInvited,
|
||||
isPublic: roomPreview.isPublic,
|
||||
canKnock: roomPreview.canKnock)
|
||||
init(_ roomPreviewInfo: RoomPreviewInfo) {
|
||||
self = RoomPreviewDetails(roomID: roomPreviewInfo.roomId,
|
||||
name: roomPreviewInfo.name,
|
||||
canonicalAlias: roomPreviewInfo.canonicalAlias,
|
||||
topic: roomPreviewInfo.topic,
|
||||
avatarURL: roomPreviewInfo.avatarUrl.flatMap(URL.init(string:)),
|
||||
memberCount: UInt(roomPreviewInfo.numJoinedMembers),
|
||||
isHistoryWorldReadable: roomPreviewInfo.isHistoryWorldReadable,
|
||||
isJoined: roomPreviewInfo.membership == .joined,
|
||||
isInvited: roomPreviewInfo.membership == .invited,
|
||||
isPublic: roomPreviewInfo.joinRule == .public,
|
||||
canKnock: roomPreviewInfo.joinRule == .knock)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
var secureBackupController: SecureBackupControllerProtocol { get }
|
||||
|
||||
var sessionVerificationController: SessionVerificationControllerProxyProtocol? { get }
|
||||
|
||||
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
||||
|
||||
func startSync()
|
||||
@@ -155,8 +157,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
|
||||
|
||||
func removeUserAvatar() async -> Result<Void, ClientProxyError>
|
||||
|
||||
func sessionVerificationControllerProxy() async -> Result<SessionVerificationControllerProxyProtocol, ClientProxyError>
|
||||
|
||||
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
|
||||
|
||||
|
||||
@@ -246,10 +246,8 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
|
||||
#if targetEnvironment(simulator)
|
||||
// This gets called for no reason on simulators, where CallKit
|
||||
// isn't even supported. Ignore
|
||||
return
|
||||
#endif
|
||||
|
||||
// isn't even supported, ignore it.
|
||||
#else
|
||||
if let ongoingCallID {
|
||||
actionsSubject.send(.endCall(roomID: ongoingCallID.roomID))
|
||||
}
|
||||
@@ -257,6 +255,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
||||
tearDownCallSession(sendEndCallAction: false)
|
||||
|
||||
action.fulfill()
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
+68
-8
@@ -18,6 +18,10 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl
|
||||
|
||||
// MARK: - SessionVerificationControllerDelegate
|
||||
|
||||
func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||
proxy?.didReceiveVerificationRequest(details: details)
|
||||
}
|
||||
|
||||
func didReceiveVerificationData(data: MatrixRustSDK.SessionVerificationData) {
|
||||
switch data {
|
||||
// We can handle only emojis for now
|
||||
@@ -54,86 +58,142 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
||||
|
||||
init(sessionVerificationController: SessionVerificationController) {
|
||||
self.sessionVerificationController = sessionVerificationController
|
||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||
}
|
||||
|
||||
deinit {
|
||||
sessionVerificationController.setDelegate(delegate: nil)
|
||||
}
|
||||
|
||||
let callbacks = PassthroughSubject<SessionVerificationControllerProxyCallback, Never>()
|
||||
let actions = PassthroughSubject<SessionVerificationControllerProxyAction, Never>()
|
||||
|
||||
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Acknowledging verification request")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.acknowledgeVerificationRequest(senderId: details.senderID, flowId: details.flowID)
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||
return .failure(.failedAcknowledgingVerificationRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Accepting verification request")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.acceptVerificationRequest()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||
return .failure(.failedAcceptingVerificationRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
sessionVerificationController.setDelegate(delegate: WeakSessionVerificationControllerProxy(proxy: self))
|
||||
MXLog.info("Requesting session verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.requestVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed requesting session verification with error: \(error)")
|
||||
return .failure(.failedRequestingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Starting SAS verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.startSasVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed starting SAS verification with error: \(error)")
|
||||
return .failure(.failedStartingSasVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Approving verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.approveVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed approving verification with error: \(error)")
|
||||
return .failure(.failedApprovingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Declining verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.declineVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed declining verification with error: \(error)")
|
||||
return .failure(.failedDecliningVerification)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||
MXLog.info("Cancelling verification")
|
||||
|
||||
do {
|
||||
try await sessionVerificationController.cancelVerification()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed cancelling verification with error: \(error)")
|
||||
return .failure(.failedCancellingVerification)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
fileprivate func didReceiveVerificationRequest(details: MatrixRustSDK.SessionVerificationRequestDetails) {
|
||||
MXLog.info("Received verification request \(details)")
|
||||
|
||||
let details = SessionVerificationRequestDetails(senderID: details.senderId,
|
||||
flowID: details.flowId,
|
||||
deviceID: details.deviceId,
|
||||
displayName: details.displayName,
|
||||
firstSeenDate: Date(timeIntervalSince1970: TimeInterval(details.firstSeenTimestamp / 1000)))
|
||||
|
||||
actions.send(.receivedVerificationRequest(details: details))
|
||||
}
|
||||
|
||||
fileprivate func didAcceptVerificationRequest() {
|
||||
callbacks.send(.acceptedVerificationRequest)
|
||||
MXLog.info("Accepted verification request")
|
||||
|
||||
actions.send(.acceptedVerificationRequest)
|
||||
}
|
||||
|
||||
fileprivate func didStartSasVerification() {
|
||||
callbacks.send(.startedSasVerification)
|
||||
MXLog.info("Started SAS verification")
|
||||
|
||||
actions.send(.startedSasVerification)
|
||||
}
|
||||
|
||||
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||
callbacks.send(.receivedVerificationData(data.map { emoji in
|
||||
MXLog.info("Received verification data")
|
||||
|
||||
actions.send(.receivedVerificationData(data.map { emoji in
|
||||
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
||||
}))
|
||||
}
|
||||
|
||||
fileprivate func didFail() {
|
||||
callbacks.send(.failed)
|
||||
actions.send(.failed)
|
||||
}
|
||||
|
||||
fileprivate func didFinish() {
|
||||
callbacks.send(.finished)
|
||||
actions.send(.finished)
|
||||
}
|
||||
|
||||
fileprivate func didCancel() {
|
||||
callbacks.send(.cancelled)
|
||||
actions.send(.cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -7,8 +7,11 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum SessionVerificationControllerProxyError: Error {
|
||||
case failedAcknowledgingVerificationRequest
|
||||
case failedAcceptingVerificationRequest
|
||||
case failedRequestingVerification
|
||||
case failedStartingSasVerification
|
||||
case failedApprovingVerification
|
||||
@@ -16,7 +19,8 @@ enum SessionVerificationControllerProxyError: Error {
|
||||
case failedCancellingVerification
|
||||
}
|
||||
|
||||
enum SessionVerificationControllerProxyCallback {
|
||||
enum SessionVerificationControllerProxyAction {
|
||||
case receivedVerificationRequest(details: SessionVerificationRequestDetails)
|
||||
case acceptedVerificationRequest
|
||||
case startedSasVerification
|
||||
case receivedVerificationData([SessionVerificationEmoji])
|
||||
@@ -25,6 +29,14 @@ enum SessionVerificationControllerProxyCallback {
|
||||
case failed
|
||||
}
|
||||
|
||||
struct SessionVerificationRequestDetails {
|
||||
let senderID: String
|
||||
let flowID: String
|
||||
let deviceID: String
|
||||
let displayName: String?
|
||||
let firstSeenDate: Date
|
||||
}
|
||||
|
||||
struct SessionVerificationEmoji: Hashable {
|
||||
let symbol: String
|
||||
let description: String
|
||||
@@ -36,7 +48,11 @@ struct SessionVerificationEmoji: Hashable {
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> { get }
|
||||
var actions: PassthroughSubject<SessionVerificationControllerProxyAction, Never> { get }
|
||||
|
||||
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user