mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 08:03:50 +02:00
JitsiService: Handle Well Known retrieval and Jitsi widget content creation.
This commit is contained in:
@@ -19,12 +19,26 @@ import Foundation
|
||||
#if canImport(JitsiMeet)
|
||||
import JitsiMeet
|
||||
|
||||
enum JitsiServiceError: Error {
|
||||
case widgetContentCreationFailed
|
||||
case emptyResponse
|
||||
case unknown
|
||||
}
|
||||
|
||||
/// JitsiService enables to abstract and configure Jitsi Meet SDK
|
||||
@objcMembers
|
||||
final class JitsiService: NSObject {
|
||||
|
||||
static let shared = JitsiService()
|
||||
|
||||
private enum Constants {
|
||||
static let widgetIdLength = 7
|
||||
}
|
||||
|
||||
private struct Route {
|
||||
static let wellKnown = "/.well-known/element/jitsi"
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
var enableCallKit: Bool = true {
|
||||
@@ -38,6 +52,10 @@ final class JitsiService: NSObject {
|
||||
}
|
||||
|
||||
private let jitsiMeet = JitsiMeet.sharedInstance()
|
||||
private var httpClient: MXHTTPClient?
|
||||
private let serializationService: SerializationServiceType = SerializationService()
|
||||
|
||||
private var httpClients: [String: MXHTTPClient] = [:]
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@@ -59,6 +77,52 @@ final class JitsiService: NSObject {
|
||||
JMCallKitProxy.configureProvider(localizedName: localizedName, ringtoneSound: ringtoneName, iconTemplateImageData: iconTemplateImageData)
|
||||
}
|
||||
|
||||
// MARK: WellKnown
|
||||
|
||||
@discardableResult
|
||||
func getWellKnown(for jitsiServerURL: URL, completion: @escaping (Result<JitsiWellKnown, Error>) -> Void) -> MXHTTPOperation? {
|
||||
guard let httpClient = self.httpClient(for: jitsiServerURL) else {
|
||||
completion(.failure(JitsiServiceError.unknown))
|
||||
return nil
|
||||
}
|
||||
|
||||
return httpClient.request(withMethod: "GET", path: Route.wellKnown, parameters: nil, success: { response in
|
||||
guard let response = response else {
|
||||
completion(.failure(JitsiServiceError.emptyResponse))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let jitsiWellKnown: JitsiWellKnown = try self.serializationService.deserialize(response)
|
||||
completion(.success(jitsiWellKnown))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}, failure: { (error) in
|
||||
completion(.failure(error ?? JitsiServiceError.unknown))
|
||||
})
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func createJitsiWidgetContent(jitsiServerURL: URL, roomID: String, isAudioOnly: Bool, success: @escaping ([AnyHashable: Any]) -> Void, failure: @escaping ((Error) -> Void)) -> MXHTTPOperation? {
|
||||
return self.getWellKnown(for: jitsiServerURL) { (result) in
|
||||
switch result {
|
||||
case .success(let jitsiWellKnown):
|
||||
if let serverDomain = jitsiServerURL.host, let widgetContent = self.createJitsiWidgetContent(serverDomain: serverDomain,
|
||||
authenticationType: jitsiWellKnown.authenticationType,
|
||||
roomID: roomID,
|
||||
isAudioOnly: isAudioOnly) {
|
||||
success(widgetContent)
|
||||
} else {
|
||||
failure(JitsiServiceError.widgetContentCreationFailed)
|
||||
}
|
||||
case .failure(let error):
|
||||
NSLog("[JitsiService] Fail to get Jitsi Well Known with error: \(error)")
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: AppDelegate methods
|
||||
|
||||
@discardableResult
|
||||
@@ -73,5 +137,113 @@ final class JitsiService: NSObject {
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
return self.jitsiMeet.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func httpClient(for jitsiServerURL: URL) -> MXHTTPClient? {
|
||||
let httpClient: MXHTTPClient?
|
||||
|
||||
let baseStringURL = jitsiServerURL.absoluteString
|
||||
|
||||
if let existingHttpClient = self.httpClients[baseStringURL] {
|
||||
httpClient = existingHttpClient
|
||||
} else if let createdHttpClient = MXHTTPClient(baseURL: baseStringURL, andOnUnrecognizedCertificateBlock: nil) {
|
||||
|
||||
httpClient = createdHttpClient
|
||||
self.httpClients[baseStringURL] = httpClient
|
||||
} else {
|
||||
httpClient = nil
|
||||
}
|
||||
|
||||
return httpClient
|
||||
}
|
||||
|
||||
private func createJitsiWidgetContent(serverDomain: String,
|
||||
authenticationType: JitsiAuthenticationType?,
|
||||
roomID: String,
|
||||
isAudioOnly: Bool) -> [AnyHashable: Any]? {
|
||||
guard MXTools.isMatrixRoomIdentifier(roomID) else {
|
||||
NSLog("[JitsiService] createJitsiWidgetContent the roomID is not valid")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a random enough jitsi conference id
|
||||
// Note: the jitsi server automatically creates conference when the conference
|
||||
// id does not exist yet
|
||||
let widgetSessionId = (ProcessInfo.processInfo.globallyUniqueString as NSString).substring(to: Constants.widgetIdLength).lowercased()
|
||||
|
||||
let conferenceID: String
|
||||
|
||||
let authenticationTypeString: String?
|
||||
|
||||
if let authenticationType = authenticationType, authenticationType == .openIDTokenJWT {
|
||||
|
||||
// For compatibility with Jitsi, use base32 without padding.
|
||||
// More details here:
|
||||
// https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||
conferenceID = Base32Coder.encodedString(roomID, padding: false)
|
||||
authenticationTypeString = authenticationType.identifier
|
||||
} else {
|
||||
let localRoomId = roomID.components(separatedBy: ":").first ?? ""
|
||||
conferenceID = localRoomId + widgetSessionId
|
||||
authenticationTypeString = nil
|
||||
}
|
||||
|
||||
// Build widget url
|
||||
// Riot-iOS does not directly use it but extracts params from it (see `[JitsiViewController openWidget:withVideo:]`)
|
||||
// This url can be used as is inside a web container (like iframe for Riot-web)
|
||||
|
||||
// Build it from the riot-web app
|
||||
let appUrlString = BuildSettings.applicationWebAppUrlString
|
||||
|
||||
// We mix v1 and v2 param for backward compability
|
||||
let v1queryStringParts = [
|
||||
"confId=\(conferenceID)",
|
||||
"isAudioConf=\(isAudioOnly ? "true" : "false")",
|
||||
"displayName=$matrix_display_name",
|
||||
"avatarUrl=$matrix_avatar_url",
|
||||
"email=$matrix_user_id"
|
||||
]
|
||||
|
||||
let v1Params = v1queryStringParts.joined(separator: "&")
|
||||
|
||||
var v2queryStringParts = [
|
||||
"conferenceDomain=$domain",
|
||||
"conferenceId=$conferenceId",
|
||||
"isAudioOnly=$isAudioOnly",
|
||||
"displayName=$matrix_display_name",
|
||||
"avatarUrl=$matrix_avatar_url",
|
||||
"userId=$matrix_user_id"
|
||||
]
|
||||
|
||||
if let authenticationTypeString = authenticationTypeString {
|
||||
v2queryStringParts.append("auth=\(authenticationTypeString)")
|
||||
}
|
||||
|
||||
let v2Params = v2queryStringParts.joined(separator: "&")
|
||||
|
||||
let widgetStringURL = "\(appUrlString)/widgets/jitsi.html?\(v1Params)#\(v2Params)"
|
||||
|
||||
// Build widget data
|
||||
// We mix v1 and v2 widget data for backward compability
|
||||
let jitsiWidgetData = JitsiWidgetData()
|
||||
jitsiWidgetData.domain = serverDomain
|
||||
jitsiWidgetData.conferenceId = conferenceID
|
||||
jitsiWidgetData.isAudioOnly = isAudioOnly
|
||||
jitsiWidgetData.authenticationType = authenticationType?.identifier
|
||||
|
||||
let v2WidgetData: [AnyHashable: Any] = jitsiWidgetData.jsonDictionary()
|
||||
|
||||
var v1AndV2WidgetData = v2WidgetData
|
||||
v1AndV2WidgetData["widgetSessionId"] = widgetSessionId
|
||||
|
||||
let widgetContent: [AnyHashable: Any] = [
|
||||
"url": widgetStringURL,
|
||||
"type": kWidgetTypeJitsiV1,
|
||||
"data": v1AndV2WidgetData
|
||||
]
|
||||
|
||||
return widgetContent
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user