Make Element Call widget URL configurable (#2971)

This commit is contained in:
Stefan Ceriu
2024-07-02 11:35:05 +03:00
committed by GitHub
parent 78b0c7068d
commit b2c9878df6
15 changed files with 342 additions and 114 deletions
@@ -39,7 +39,7 @@ final class AppSettings {
case sharePresence
case hideUnreadMessagesBadge
case elementCallBaseURL
case elementCallBaseURLOverride
case elementCallEncryptionEnabled
// Feature flags
@@ -249,8 +249,10 @@ final class AppSettings {
// MARK: - Element Call
@UserPreference(key: UserDefaultsKeys.elementCallBaseURL, defaultValue: "https://call.element.io", storageType: .userDefaults(store))
var elementCallBaseURL: URL
let elementCallBaseURL: URL = "https://call.element.io"
@UserPreference(key: UserDefaultsKeys.elementCallBaseURLOverride, defaultValue: nil, storageType: .userDefaults(store))
var elementCallBaseURLOverride: URL?
// MARK: - Users
@@ -555,9 +555,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private func presentCallScreen(roomProxy: RoomProxyProtocol) {
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: elementCallService,
clientProxy: userSession.clientProxy,
roomProxy: roomProxy,
callBaseURL: appSettings.elementCallBaseURL,
clientID: InfoPlistReader.main.bundleIdentifier))
clientID: InfoPlistReader.main.bundleIdentifier,
elementCallBaseURL: appSettings.elementCallBaseURL,
elementCallBaseURLOverride: appSettings.elementCallBaseURLOverride))
callScreenCoordinator.actions
.sink { [weak self] action in
@@ -1920,6 +1920,7 @@ class ClientProxyMock: ClientProxyProtocol {
set(value) { underlyingHomeserver = value }
}
var underlyingHomeserver: String!
var userIDServerName: String?
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> {
get { return underlyingUserDisplayNamePublisher }
set(value) { underlyingUserDisplayNamePublisher = value }
@@ -3564,6 +3565,70 @@ class ClientProxyMock: ClientProxyProtocol {
return resolveRoomAliasReturnValue
}
}
//MARK: - getElementWellKnown
var getElementWellKnownUnderlyingCallsCount = 0
var getElementWellKnownCallsCount: Int {
get {
if Thread.isMainThread {
return getElementWellKnownUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = getElementWellKnownUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
getElementWellKnownUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
getElementWellKnownUnderlyingCallsCount = newValue
}
}
}
}
var getElementWellKnownCalled: Bool {
return getElementWellKnownCallsCount > 0
}
var getElementWellKnownUnderlyingReturnValue: Result<ElementWellKnown?, ClientProxyError>!
var getElementWellKnownReturnValue: Result<ElementWellKnown?, ClientProxyError>! {
get {
if Thread.isMainThread {
return getElementWellKnownUnderlyingReturnValue
} else {
var returnValue: Result<ElementWellKnown?, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = getElementWellKnownUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
getElementWellKnownUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
getElementWellKnownUnderlyingReturnValue = newValue
}
}
}
}
var getElementWellKnownClosure: (() async -> Result<ElementWellKnown?, ClientProxyError>)?
func getElementWellKnown() async -> Result<ElementWellKnown?, ClientProxyError> {
getElementWellKnownCallsCount += 1
if let getElementWellKnownClosure = getElementWellKnownClosure {
return await getElementWellKnownClosure()
} else {
return getElementWellKnownReturnValue
}
}
//MARK: - ignoreUser
var ignoreUserUnderlyingCallsCount = 0
@@ -1439,6 +1439,81 @@ open class ClientSDKMock: MatrixRustSDK.Client {
}
}
//MARK: - getUrl
open var getUrlUrlThrowableError: Error?
var getUrlUrlUnderlyingCallsCount = 0
open var getUrlUrlCallsCount: Int {
get {
if Thread.isMainThread {
return getUrlUrlUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = getUrlUrlUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
getUrlUrlUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
getUrlUrlUnderlyingCallsCount = newValue
}
}
}
}
open var getUrlUrlCalled: Bool {
return getUrlUrlCallsCount > 0
}
open var getUrlUrlReceivedUrl: String?
open var getUrlUrlReceivedInvocations: [String] = []
var getUrlUrlUnderlyingReturnValue: String!
open var getUrlUrlReturnValue: String! {
get {
if Thread.isMainThread {
return getUrlUrlUnderlyingReturnValue
} else {
var returnValue: String? = nil
DispatchQueue.main.sync {
returnValue = getUrlUrlUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
getUrlUrlUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
getUrlUrlUnderlyingReturnValue = newValue
}
}
}
}
open var getUrlUrlClosure: ((String) async throws -> String)?
open override func getUrl(url: String) async throws -> String {
if let error = getUrlUrlThrowableError {
throw error
}
getUrlUrlCallsCount += 1
getUrlUrlReceivedUrl = url
DispatchQueue.main.async {
self.getUrlUrlReceivedInvocations.append(url)
}
if let getUrlUrlClosure = getUrlUrlClosure {
return try await getUrlUrlClosure(url)
} else {
return getUrlUrlReturnValue
}
}
//MARK: - homeserver
var homeserverUnderlyingCallsCount = 0
@@ -3277,6 +3352,75 @@ open class ClientSDKMock: MatrixRustSDK.Client {
return userIdReturnValue
}
}
//MARK: - userIdServerName
open var userIdServerNameThrowableError: Error?
var userIdServerNameUnderlyingCallsCount = 0
open var userIdServerNameCallsCount: Int {
get {
if Thread.isMainThread {
return userIdServerNameUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = userIdServerNameUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
userIdServerNameUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
userIdServerNameUnderlyingCallsCount = newValue
}
}
}
}
open var userIdServerNameCalled: Bool {
return userIdServerNameCallsCount > 0
}
var userIdServerNameUnderlyingReturnValue: String!
open var userIdServerNameReturnValue: String! {
get {
if Thread.isMainThread {
return userIdServerNameUnderlyingReturnValue
} else {
var returnValue: String? = nil
DispatchQueue.main.sync {
returnValue = userIdServerNameUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
userIdServerNameUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
userIdServerNameUnderlyingReturnValue = newValue
}
}
}
}
open var userIdServerNameClosure: (() throws -> String)?
open override func userIdServerName() throws -> String {
if let error = userIdServerNameThrowableError {
throw error
}
userIdServerNameCallsCount += 1
if let userIdServerNameClosure = userIdServerNameClosure {
return try userIdServerNameClosure()
} else {
return userIdServerNameReturnValue
}
}
}
open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
init() {
@@ -9170,82 +9314,6 @@ open class NotificationSettingsSDKMock: MatrixRustSDK.NotificationSettings {
try await unmuteRoomRoomIdIsEncryptedIsOneToOneClosure?(roomId, isEncrypted, isOneToOne)
}
}
open class OidcAuthorizationDataSDKMock: MatrixRustSDK.OidcAuthorizationData {
init() {
super.init(noPointer: .init())
}
public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
fileprivate var pointer: UnsafeMutableRawPointer!
//MARK: - loginUrl
var loginUrlUnderlyingCallsCount = 0
open var loginUrlCallsCount: Int {
get {
if Thread.isMainThread {
return loginUrlUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = loginUrlUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
loginUrlUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
loginUrlUnderlyingCallsCount = newValue
}
}
}
}
open var loginUrlCalled: Bool {
return loginUrlCallsCount > 0
}
var loginUrlUnderlyingReturnValue: String!
open var loginUrlReturnValue: String! {
get {
if Thread.isMainThread {
return loginUrlUnderlyingReturnValue
} else {
var returnValue: String? = nil
DispatchQueue.main.sync {
returnValue = loginUrlUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
loginUrlUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
loginUrlUnderlyingReturnValue = newValue
}
}
}
}
open var loginUrlClosure: (() -> String)?
open override func loginUrl() -> String {
loginUrlCallsCount += 1
if let loginUrlClosure = loginUrlClosure {
return loginUrlClosure()
} else {
return loginUrlReturnValue
}
}
}
open class QrCodeDataSDKMock: MatrixRustSDK.QrCodeData {
init() {
super.init(noPointer: .init())
@@ -19,9 +19,11 @@ import SwiftUI
struct CallScreenCoordinatorParameters {
let elementCallService: ElementCallServiceProtocol
let clientProxy: ClientProxyProtocol
let roomProxy: RoomProxyProtocol
let callBaseURL: URL
let clientID: String
let elementCallBaseURL: URL
let elementCallBaseURLOverride: URL?
}
enum CallScreenCoordinatorAction {
@@ -39,9 +41,11 @@ final class CallScreenCoordinator: CoordinatorProtocol {
init(parameters: CallScreenCoordinatorParameters) {
viewModel = CallScreenViewModel(elementCallService: parameters.elementCallService,
clientProxy: parameters.clientProxy,
roomProxy: parameters.roomProxy,
callBaseURL: parameters.callBaseURL,
clientID: parameters.clientID)
clientID: parameters.clientID,
elementCallBaseURL: parameters.elementCallBaseURL,
elementCallBaseURLOverride: parameters.elementCallBaseURLOverride)
}
func start() {
@@ -38,9 +38,11 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
/// - callBaseURL: Which Element Call instance should be used
/// - clientID: Something to identify the current client on the Element Call side
init(elementCallService: ElementCallServiceProtocol,
clientProxy: ClientProxyProtocol,
roomProxy: RoomProxyProtocol,
callBaseURL: URL,
clientID: String) {
clientID: String,
elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?) {
self.elementCallService = elementCallService
self.roomProxy = roomProxy
@@ -90,7 +92,15 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
.store(in: &cancellables)
Task {
switch await widgetDriver.start(baseURL: callBaseURL, clientID: clientID) {
let baseURL = if let elementCallBaseURLOverride {
elementCallBaseURLOverride
} else if case .success(let wellKnown) = await clientProxy.getElementWellKnown(), let wellKnownCall = wellKnown?.call {
wellKnownCall.widgetURL
} else {
elementCallBaseURL
}
switch await widgetDriver.start(baseURL: baseURL, clientID: clientID) {
case .success(let url):
state.url = url
case .failure(let error):
@@ -172,7 +172,11 @@ private struct WebView: UIViewRepresentable {
struct CallScreen_Previews: PreviewProvider {
static let viewModel = {
let clientProxy = ClientProxyMock()
clientProxy.getElementWellKnownReturnValue = .success(nil)
let roomProxy = RoomProxyMock()
roomProxy.sendCallNotificationIfNeeededReturnValue = .success(())
let widgetDriver = ElementCallWidgetDriverMock()
widgetDriver.underlyingMessagePublisher = .init()
@@ -182,9 +186,11 @@ struct CallScreen_Previews: PreviewProvider {
roomProxy.elementCallWidgetDriverReturnValue = widgetDriver
return CallScreenViewModel(elementCallService: ElementCallServiceMock(.init()),
clientProxy: clientProxy,
roomProxy: roomProxy,
callBaseURL: "https://call.element.io",
clientID: "io.element.elementx")
clientID: "io.element.elementx",
elementCallBaseURL: "https://call.element.io",
elementCallBaseURLOverride: nil)
}()
static var previews: some View {
@@ -32,7 +32,8 @@ final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol {
}
init() {
viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings)
viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings,
elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL)
viewModel.actions
.sink { [weak self] action in
@@ -21,6 +21,7 @@ enum DeveloperOptionsScreenViewModelAction {
}
struct DeveloperOptionsScreenViewState: BindableState {
let elementCallBaseURL: URL
var bindings: DeveloperOptionsScreenViewStateBindings
}
@@ -46,7 +47,7 @@ enum DeveloperOptionsScreenViewAction {
protocol DeveloperOptionsProtocol: AnyObject {
var logLevel: TracingConfiguration.LogLevel { get set }
var hideUnreadMessagesBadge: Bool { get set }
var elementCallBaseURL: URL { get set }
var elementCallBaseURLOverride: URL? { get set }
var fuzzyRoomListSearchEnabled: Bool { get set }
}
@@ -26,9 +26,9 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
actionsSubject.eraseToAnyPublisher()
}
init(developerOptions: DeveloperOptionsProtocol) {
init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL) {
let bindings = DeveloperOptionsScreenViewStateBindings(developerOptions: developerOptions)
let state = DeveloperOptionsScreenViewState(bindings: bindings)
let state = DeveloperOptionsScreenViewState(elementCallBaseURL: elementCallBaseURL, bindings: bindings)
super.init(initialViewState: state)
}
@@ -38,14 +38,11 @@ struct DeveloperOptionsScreen: View {
}
Section("Element Call") {
TextField(context.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString)
TextField(context.viewState.elementCallBaseURL.absoluteString, text: $elementCallBaseURLString)
.submitLabel(.done)
.onSubmit {
guard let url = URL(string: elementCallBaseURLString) else {
return
}
context.elementCallBaseURL = url
guard let url = URL(string: elementCallBaseURLString) else { return }
context.elementCallBaseURLOverride = url
}
.autocorrectionDisabled(true)
.autocapitalization(.none)
@@ -148,7 +145,8 @@ private struct LogLevelConfigurationView: View {
// MARK: - Previews
struct DeveloperOptionsScreen_Previews: PreviewProvider {
static let viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings)
static let viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings,
elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL)
static var previews: some View {
NavigationStack {
DeveloperOptionsScreen(context: viewModel.context)
@@ -21,6 +21,7 @@ import OrderedCollections
import MatrixRustSDK
// swiftlint:disable file_length
class ClientProxy: ClientProxyProtocol {
private let client: ClientProtocol
private let networkMonitor: NetworkMonitorProtocol
@@ -188,19 +189,6 @@ class ClientProxy: ClientProxyProtocol {
.store(in: &cancellables)
}
private func updateVerificationState(_ verificationState: VerificationState) {
let verificationState: SessionVerificationState = switch verificationState {
case .unknown:
.unknown
case .unverified:
.unverified
case .verified:
.verified
}
verificationStateSubject.send(verificationState)
}
var userID: String {
do {
return try client.userId()
@@ -214,7 +202,7 @@ class ClientProxy: ClientProxyProtocol {
do {
return try client.deviceId()
} catch {
MXLog.error("Failed retrieving device id with error: \(error)")
MXLog.error("Failed retrieving deviceID with error: \(error)")
return nil
}
}
@@ -222,6 +210,15 @@ class ClientProxy: ClientProxyProtocol {
var homeserver: String {
client.homeserver()
}
var userIDServerName: String? {
do {
return try client.userIdServerName()
} catch {
MXLog.error("Failed retrieving userID server name with error: \(error)")
return nil
}
}
private(set) lazy var pusherNotificationClientIdentifier: String? = {
// NOTE: The result is stored as part of the restoration token. Any changes
@@ -616,6 +613,23 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func getElementWellKnown() async -> Result<ElementWellKnown?, ClientProxyError> {
guard let userIDServerName,
var url = URL(string: userIDServerName) else {
return .failure(.invalidUserIDServerName)
}
url.append(path: "/.well-known/element/element.json")
do {
let response = try await client.getUrl(url: url.absoluteString)
let sdkWellKnown = try makeElementWellKnown(string: response)
return .success(ElementWellKnown(sdkWellKnown))
} catch {
return .failure(.sdkError(error))
}
}
// MARK: Ignored users
@@ -691,6 +705,19 @@ class ClientProxy: ClientProxyProtocol {
}
// MARK: - Private
private func updateVerificationState(_ verificationState: VerificationState) {
let verificationState: SessionVerificationState = switch verificationState {
case .unknown:
.unknown
case .unverified:
.unverified
case .verified:
.verified
}
verificationStateSubject.send(verificationState)
}
private func loadUserAvatarURLFromCache() {
loadCachedAvatarURLTask = Task {
@@ -41,6 +41,7 @@ enum ClientProxyError: Error {
case sdkError(Error)
case invalidMedia
case invalidUserIDServerName
case failedUploadingMedia(Error, MatrixErrorCode)
}
@@ -94,6 +95,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var deviceID: String? { get }
var homeserver: String { get }
var userIDServerName: String? { get }
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> { get }
@@ -162,6 +165,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol
func resolveRoomAlias(_ alias: String) async -> Result<ResolvedRoomAlias, ClientProxyError>
func getElementWellKnown() async -> Result<ElementWellKnown?, ClientProxyError>
// MARK: - Ignored users
@@ -0,0 +1,35 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import MatrixRustSDK
struct ElementWellKnown {
struct Call {
let widgetURL: URL
init?(_ wellKnown: MatrixRustSDK.ElementCallWellKnown) {
guard let widgetURL = URL(string: wellKnown.widgetUrl) else { return nil }
self.widgetURL = widgetURL
}
}
let call: Call?
init?(_ wellKnown: MatrixRustSDK.ElementWellKnown) {
call = Call(wellKnown.call)
}
}