mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Implement support for ElementCall through Rust side widgets (#1906)
* Implement support for ElementCall through Rust side widgets * Add Element Call feature flag * Fix the unit tests * Address PR comments
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Copyright 2022 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 Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
private struct ElementCallWidgetMessage: Codable {
|
||||
enum Direction: String, Codable {
|
||||
case fromWidget
|
||||
case toWidget
|
||||
}
|
||||
|
||||
enum Action: String, Codable {
|
||||
case hangup = "im.vector.hangup"
|
||||
}
|
||||
|
||||
let direction: Direction
|
||||
let action: Action
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case direction = "api"
|
||||
case action
|
||||
}
|
||||
}
|
||||
|
||||
class ElementCallWidgetDriver: WidgetPermissionsProvider, ElementCallWidgetDriverProtocol {
|
||||
private let room: RoomProtocol
|
||||
private var widgetDriver: WidgetDriverAndHandle?
|
||||
|
||||
let messagePublisher = PassthroughSubject<String, Never>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ElementCallWidgetDriverAction, Never> = .init()
|
||||
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(room: RoomProtocol) {
|
||||
self.room = room
|
||||
}
|
||||
|
||||
func start(baseURL: URL, clientID: String) async -> Result<URL, ElementCallWidgetDriverError> {
|
||||
guard let room = room as? Room else {
|
||||
return .failure(.roomInvalid)
|
||||
}
|
||||
|
||||
guard let widgetSettings = try? newVirtualElementCallWidget(props: .init(elementCallUrl: baseURL.absoluteString,
|
||||
widgetId: UUID().uuidString,
|
||||
parentUrl: nil,
|
||||
hideHeader: nil,
|
||||
preload: nil,
|
||||
fontScale: nil,
|
||||
appPrompt: false,
|
||||
skipLobby: true,
|
||||
confineToRoom: true,
|
||||
fonts: nil,
|
||||
analyticsId: nil)) else {
|
||||
return .failure(.failedBuildingWidgetSettings)
|
||||
}
|
||||
|
||||
guard let urlString = try? await generateWebviewUrl(widgetSettings: widgetSettings, room: room,
|
||||
props: .init(clientId: clientID,
|
||||
languageTag: nil,
|
||||
theme: nil)) else {
|
||||
return .failure(.failedBuildingCallURL)
|
||||
}
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
return .failure(.failedParsingCallURL)
|
||||
}
|
||||
|
||||
guard let widgetDriver = try? makeWidgetDriver(settings: widgetSettings) else {
|
||||
return .failure(.failedBuildingWidgetDriver)
|
||||
}
|
||||
|
||||
self.widgetDriver = widgetDriver
|
||||
|
||||
Task.detached { [weak self, widgetDriver, messagePublisher] in
|
||||
MXLog.debug("Started message receiving loop")
|
||||
|
||||
defer {
|
||||
MXLog.debug("Stopped message receiving loop")
|
||||
}
|
||||
|
||||
while true {
|
||||
guard let receivedMessage = await widgetDriver.handle.recv() else {
|
||||
return
|
||||
}
|
||||
|
||||
messagePublisher.send(receivedMessage)
|
||||
MXLog.debug("Received message: \(receivedMessage)")
|
||||
|
||||
self?.handleMessageIfNeeded(receivedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Task.detached { [widgetDriver] in
|
||||
MXLog.debug("Started widget driver")
|
||||
|
||||
defer {
|
||||
MXLog.debug("Stopped widget driver")
|
||||
}
|
||||
|
||||
await widgetDriver.driver.run(room: room, permissionsProvider: self)
|
||||
}
|
||||
|
||||
return .success(url)
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
|
||||
guard let widgetDriver else {
|
||||
return .failure(.driverNotSetup)
|
||||
}
|
||||
|
||||
let result = await widgetDriver.handle.send(msg: message)
|
||||
MXLog.debug("Sent message: \(message) with result: \(result)")
|
||||
|
||||
handleMessageIfNeeded(message)
|
||||
|
||||
return .success(result)
|
||||
}
|
||||
|
||||
// MARK: - WidgetPermissionsProvider
|
||||
|
||||
func acquirePermissions(permissions: MatrixRustSDK.WidgetPermissions) -> MatrixRustSDK.WidgetPermissions {
|
||||
permissions
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
func handleMessageIfNeeded(_ message: String) {
|
||||
guard let data = message.data(using: .utf8),
|
||||
let widgetMessage = try? JSONDecoder().decode(ElementCallWidgetMessage.self, from: data) else {
|
||||
return
|
||||
}
|
||||
|
||||
if widgetMessage.direction == .fromWidget {
|
||||
switch widgetMessage.action {
|
||||
case .hangup:
|
||||
actionsSubject.send(.callEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2023 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 Combine
|
||||
import Foundation
|
||||
|
||||
enum ElementCallWidgetDriverError: Error {
|
||||
case roomInvalid
|
||||
case failedBuildingCallURL
|
||||
case failedBuildingWidgetSettings
|
||||
case failedBuildingWidgetDriver
|
||||
case failedParsingCallURL
|
||||
case driverNotSetup
|
||||
}
|
||||
|
||||
enum ElementCallWidgetDriverAction {
|
||||
case callEnded
|
||||
case errorReceived(ElementCallWidgetDriverError)
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol ElementCallWidgetDriverProtocol {
|
||||
var messagePublisher: PassthroughSubject<String, Never> { get }
|
||||
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> { get }
|
||||
|
||||
func start(baseURL: URL, clientID: String) async -> Result<URL, ElementCallWidgetDriverError>
|
||||
|
||||
func sendMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError>
|
||||
}
|
||||
Reference in New Issue
Block a user