diff --git a/Sources/LiveKit/Core/Room+Engine.swift b/Sources/LiveKit/Core/Room+Engine.swift index 73d0c9c53..29be75949 100644 --- a/Sources/LiveKit/Core/Room+Engine.swift +++ b/Sources/LiveKit/Core/Room+Engine.swift @@ -233,7 +233,7 @@ public enum StartReconnectReason { // Room+ConnectSequences extension Room { // full connect sequence, doesn't update connection state - func fullConnectSequence(_ url: String, _ token: String) async throws { + func fullConnectSequence(_ url: URL, _ token: String) async throws { let connectResponse = try await signalClient.connect(url, token, connectOptions: _state.connectOptions, diff --git a/Sources/LiveKit/Core/Room.swift b/Sources/LiveKit/Core/Room.swift index b6c2e6818..ee70ee513 100644 --- a/Sources/LiveKit/Core/Room.swift +++ b/Sources/LiveKit/Core/Room.swift @@ -77,7 +77,7 @@ public class Room: NSObject, ObservableObject, Loggable { // expose engine's vars @objc - public var url: String? { _state.url } + public var url: String? { _state.url?.absoluteString } @objc public var token: String? { _state.token } @@ -134,7 +134,7 @@ public class Room: NSObject, ObservableObject, Loggable { var serverInfo: Livekit_ServerInfo? // Engine - var url: String? + var url: URL? var token: String? // preferred reconnect mode which will be used only for next attempt var nextReconnectMode: ReconnectMode? @@ -283,7 +283,12 @@ public class Room: NSObject, ObservableObject, Loggable { connectOptions: ConnectOptions? = nil, roomOptions: RoomOptions? = nil) async throws { - log("connecting to room...", .info) + guard let url = URL(string: url), url.isValidForSocket else { + log("URL parse failed", .error) + throw LiveKitError(.failedToParseUrl) + } + + log("Connecting to room...", .info) var state = _state.copy() diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 73b57b368..8be8642d9 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -110,7 +110,7 @@ actor SignalClient: Loggable { } @discardableResult - func connect(_ urlString: String, + func connect(_ url: URL, _ token: String, connectOptions: ConnectOptions? = nil, reconnectMode: ReconnectMode? = nil, @@ -122,14 +122,11 @@ actor SignalClient: Loggable { log("[Connect] mode: \(String(describing: reconnectMode))") } - guard let url = Utils.buildUrl(urlString, - token, - connectOptions: connectOptions, - reconnectMode: reconnectMode, - adaptiveStream: adaptiveStream) - else { - throw LiveKitError(.failedToParseUrl) - } + let url = try Utils.buildUrl(url, + token, + connectOptions: connectOptions, + reconnectMode: reconnectMode, + adaptiveStream: adaptiveStream) if reconnectMode != nil { log("[Connect] with url: \(url)") @@ -179,14 +176,11 @@ actor SignalClient: Loggable { await cleanUp(withError: error) // Validate... - guard let validateUrl = Utils.buildUrl(urlString, - token, - connectOptions: connectOptions, - adaptiveStream: adaptiveStream, - validate: true) - else { - throw LiveKitError(.failedToParseUrl, message: "Failed to parse validation url") - } + let validateUrl = try Utils.buildUrl(url, + token, + connectOptions: connectOptions, + adaptiveStream: adaptiveStream, + validate: true) log("Validating with url: \(validateUrl)...") let validationResponse = try await HTTP.requestString(from: validateUrl) diff --git a/Sources/LiveKit/Extensions/Primitives.swift b/Sources/LiveKit/Extensions/Primitives.swift index bb5e789c0..a8771556f 100644 --- a/Sources/LiveKit/Extensions/Primitives.swift +++ b/Sources/LiveKit/Extensions/Primitives.swift @@ -42,12 +42,6 @@ extension Bool { } } -extension URL { - var isSecure: Bool { - scheme == "https" || scheme == "wss" - } -} - public extension Double { func rounded(to places: Int) -> Double { let divisor = pow(10.0, Double(places)) diff --git a/Sources/LiveKit/Extensions/URL.swift b/Sources/LiveKit/Extensions/URL.swift new file mode 100644 index 000000000..9e23a3cb4 --- /dev/null +++ b/Sources/LiveKit/Extensions/URL.swift @@ -0,0 +1,27 @@ +/* + * Copyright 2024 LiveKit + * + * 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 + +extension URL { + var isValidForSocket: Bool { + host != nil && (scheme == "ws" || scheme == "wss") + } + + var isSecure: Bool { + scheme == "https" || scheme == "wss" + } +} diff --git a/Sources/LiveKit/Support/Utils.swift b/Sources/LiveKit/Support/Utils.swift index 70c13b05a..3a4657a73 100644 --- a/Sources/LiveKit/Support/Utils.swift +++ b/Sources/LiveKit/Support/Utils.swift @@ -128,34 +128,34 @@ class Utils { } static func buildUrl( - _ url: String, + _ url: URL, _ token: String, connectOptions: ConnectOptions? = nil, reconnectMode: ReconnectMode? = nil, adaptiveStream: Bool, validate: Bool = false, forceSecure: Bool = false - ) -> URL? { + ) throws -> URL { // use default options if nil let connectOptions = connectOptions ?? ConnectOptions() - guard let parsedUrl = URL(string: url) else { return nil } + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) - let components = URLComponents(url: parsedUrl, resolvingAgainstBaseURL: false) - - guard var builder = components else { return nil } + guard var builder = components else { + throw LiveKitError(.failedToParseUrl) + } - let useSecure = parsedUrl.isSecure || forceSecure + let useSecure = url.isSecure || forceSecure let httpScheme = useSecure ? "https" : "http" let wsScheme = useSecure ? "wss" : "ws" - var pathSegments = parsedUrl.pathComponents + var pathSegments = url.pathComponents // strip empty & slashes pathSegments.removeAll(where: { $0.isEmpty || $0 == "/" }) // if already ending with `rtc` or `validate` // and is not a dir, remove it - if !parsedUrl.hasDirectoryPath, + if !url.hasDirectoryPath, !pathSegments.isEmpty, ["rtc", "validate"].contains(pathSegments.last!) { @@ -196,7 +196,11 @@ class Utils { builder.queryItems = queryItems - return builder.url + guard let result = builder.url else { + throw LiveKitError(.failedToParseUrl) + } + + return result } static func computeVideoEncodings(