JitsiService: Handle Well Known retrieval and Jitsi widget content creation.

This commit is contained in:
SBiOSoftWhare
2020-10-27 15:31:30 +01:00
parent bcd20d1cdb
commit b9c4e159a6
2 changed files with 173 additions and 0 deletions
@@ -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