From 92a4e0c2fa1f58bf73ee5a1f3932530c0bbcead9 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:49:11 +0800 Subject: [PATCH 1/8] Return .validation error for signal client --- .changes/validation-logic | 1 + Sources/LiveKit/Core/SignalClient.swift | 15 +++++--- Sources/LiveKit/Errors.swift | 3 ++ Sources/LiveKit/Support/HTTP.swift | 49 ++++++++++++++++++------- 4 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 .changes/validation-logic diff --git a/.changes/validation-logic b/.changes/validation-logic new file mode 100644 index 000000000..cf748497e --- /dev/null +++ b/.changes/validation-logic @@ -0,0 +1 @@ +patch type="changed" "Minor validation logic improvements" diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index cd2b8f3ca..a1b588b6d 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -181,18 +181,23 @@ actor SignalClient: Loggable { await cleanUp(withError: error) - // Validate... + // Attempt to validate with server let validateUrl = try Utils.buildUrl(url, connectOptions: connectOptions, participantSid: participantSid, adaptiveStream: adaptiveStream, validate: true) - log("Validating with url: \(validateUrl)...") - let validationResponse = try await HTTP.requestValidation(from: validateUrl, token: token) + let validationResponse = await HTTP.requestValidation(from: validateUrl, token: token) log("Validate response: \(validationResponse)") - // re-throw with validation response - throw LiveKitError(.network, message: "Validation response: \"\(validationResponse)\"") + + if case let .invalid(message) = validationResponse { + // Re-throw with validation response + throw LiveKitError(.validation, message: message) + } else { + // Re-throw original error + throw LiveKitError(.network, message: error.localizedDescription) + } } } diff --git a/Sources/LiveKit/Errors.swift b/Sources/LiveKit/Errors.swift index c509b8fbc..8bc386334 100644 --- a/Sources/LiveKit/Errors.swift +++ b/Sources/LiveKit/Errors.swift @@ -30,6 +30,7 @@ public enum LiveKitErrorType: Int, Sendable { case webRTC = 201 case network // Network issue + case validation // Network issue // Server case duplicateIdentity = 500 @@ -76,6 +77,8 @@ extension LiveKitErrorType: CustomStringConvertible { "WebRTC error" case .network: "Network error" + case .validation: + "Validation error" case .duplicateIdentity: "Duplicate Participant identity" case .serverShutdown: diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index f0a15e1db..bc8a9016d 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -16,6 +16,13 @@ import Foundation +enum ServerValidationResponse { + case valid + case invalid(message: String) + // Network error etc. + case unknown(error: Error) +} + class HTTP: NSObject { private static let operationQueue = OperationQueue() @@ -23,20 +30,34 @@ class HTTP: NSObject { delegate: nil, delegateQueue: operationQueue) - static func requestValidation(from url: URL, token: String) async throws -> String { - // let data = try await requestData(from: url, token: token) - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: .defaultHTTPConnect) - // Attach token to header - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - // Make the data request - let (data, _) = try await session.data(for: request) - // Convert to string - guard let string = String(data: data, encoding: .utf8) else { - throw LiveKitError(.failedToConvertData, message: "Failed to convert string") - } + static func requestValidation(from url: URL, token: String) async -> ServerValidationResponse { + do { + var request = URLRequest(url: url, + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, + timeoutInterval: .defaultHTTPConnect) + // Attach token to header + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + // Make the data request + let (data, response) = try await session.data(for: request) - return string + // Print HTTP status code + guard let httpResponse = response as? HTTPURLResponse else { + throw URLError(.badServerResponse) + } + + // Valid if 200 + if httpResponse.statusCode == 200 { + return .valid + } + + guard let string = String(data: data, encoding: .utf8) else { + throw URLError(.badServerResponse) + } + + return .invalid(message: string) + } catch { + return .unknown(error: error) + } } } From ac3d3c43fe29c3c7c47a6538ce7a3a2ff7be68d0 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:04:27 +0800 Subject: [PATCH 2/8] Const status code --- Sources/LiveKit/Support/HTTP.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index bc8a9016d..5fa33916b 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -24,6 +24,8 @@ enum ServerValidationResponse { } class HTTP: NSObject { + static let statusCodeOK = 200 + private static let operationQueue = OperationQueue() private static let session: URLSession = .init(configuration: .default, @@ -47,7 +49,7 @@ class HTTP: NSObject { } // Valid if 200 - if httpResponse.statusCode == 200 { + if httpResponse.statusCode == statusCodeOK { return .valid } @@ -55,6 +57,7 @@ class HTTP: NSObject { throw URLError(.badServerResponse) } + // Consider anything other than 200 invalid return .invalid(message: string) } catch { return .unknown(error: error) From e89894171ea529ac1b16375fd7058766e28f29d8 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:04:40 +0800 Subject: [PATCH 3/8] Error type fixes --- Sources/LiveKit/Core/SignalClient.swift | 6 +++--- Sources/LiveKit/Errors.swift | 13 ++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index a1b588b6d..8eef1fe8d 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -170,13 +170,13 @@ actor SignalClient: Loggable { // Skip validation if user cancelled if error is CancellationError { await cleanUp(withError: error) - throw error + throw LiveKitError(.cancelled, internalError: error) } // Skip validation if reconnect mode if reconnectMode != nil { await cleanUp(withError: error) - throw error + throw LiveKitError(.network, internalError: error) } await cleanUp(withError: error) @@ -196,7 +196,7 @@ actor SignalClient: Loggable { throw LiveKitError(.validation, message: message) } else { // Re-throw original error - throw LiveKitError(.network, message: error.localizedDescription) + throw LiveKitError(.network, internalError: error) } } } diff --git a/Sources/LiveKit/Errors.swift b/Sources/LiveKit/Errors.swift index 8bc386334..a9a3ea826 100644 --- a/Sources/LiveKit/Errors.swift +++ b/Sources/LiveKit/Errors.swift @@ -114,10 +114,10 @@ extension LiveKitErrorType: CustomStringConvertible { public class LiveKitError: NSError, @unchecked Sendable { public let type: LiveKitErrorType public let message: String? - public let underlyingError: Error? + public let internalError: Error? override public var underlyingErrors: [Error] { - [underlyingError].compactMap { $0 } + [internalError].compactMap { $0 } } public init(_ type: LiveKitErrorType, @@ -125,15 +125,18 @@ public class LiveKitError: NSError, @unchecked Sendable { internalError: Error? = nil) { func _computeDescription() -> String { + var suffix = "" if let message { - return "\(String(describing: type))(\(message))" + suffix = "(\(message))" + } else if let internalError { + suffix = "(\(internalError.localizedDescription))" } - return String(describing: type) + return String(describing: type) + suffix } self.type = type self.message = message - underlyingError = internalError + self.internalError = internalError super.init(domain: "io.livekit.swift-sdk", code: type.rawValue, userInfo: [NSLocalizedDescriptionKey: _computeDescription()]) From a1257edc9530bba4bdc1bb541d1e0c83a5fa00a1 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:56:08 +0800 Subject: [PATCH 4/8] Revert CancellationError wrap --- Sources/LiveKit/Core/SignalClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 8eef1fe8d..6174811c3 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -170,7 +170,7 @@ actor SignalClient: Loggable { // Skip validation if user cancelled if error is CancellationError { await cleanUp(withError: error) - throw LiveKitError(.cancelled, internalError: error) + throw error } // Skip validation if reconnect mode From 666cbf3758e26c61591b0c95f122158895996c76 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 11 Sep 2025 23:26:26 +0800 Subject: [PATCH 5/8] Remove enums --- Sources/LiveKit/Core/SignalClient.swift | 17 +++++---- Sources/LiveKit/Support/HTTP.swift | 51 +++++++++---------------- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index 6174811c3..cfbbfdad0 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -188,14 +188,15 @@ actor SignalClient: Loggable { adaptiveStream: adaptiveStream, validate: true) log("Validating with url: \(validateUrl)...") - let validationResponse = await HTTP.requestValidation(from: validateUrl, token: token) - log("Validate response: \(validationResponse)") - - if case let .invalid(message) = validationResponse { - // Re-throw with validation response - throw LiveKitError(.validation, message: message) - } else { - // Re-throw original error + do { + try await HTTP.requestValidation(from: validateUrl, token: token) + // Re-throw original error since validation passed + throw LiveKitError(.network, internalError: error) + } catch let validationError as LiveKitError where validationError.type == .validation { + // Re-throw validation error + throw validationError + } catch { + // Re-throw original connection error for network issues during validation throw LiveKitError(.network, internalError: error) } } diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index 5fa33916b..720efc501 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -16,13 +16,6 @@ import Foundation -enum ServerValidationResponse { - case valid - case invalid(message: String) - // Network error etc. - case unknown(error: Error) -} - class HTTP: NSObject { static let statusCodeOK = 200 @@ -32,35 +25,27 @@ class HTTP: NSObject { delegate: nil, delegateQueue: operationQueue) - static func requestValidation(from url: URL, token: String) async -> ServerValidationResponse { - do { - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: .defaultHTTPConnect) - // Attach token to header - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - - // Make the data request - let (data, response) = try await session.data(for: request) + static func requestValidation(from url: URL, token: String) async throws { + var request = URLRequest(url: url, + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, + timeoutInterval: .defaultHTTPConnect) + // Attach token to header + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - // Print HTTP status code - guard let httpResponse = response as? HTTPURLResponse else { - throw URLError(.badServerResponse) - } + // Make the data request + let (data, response) = try await session.data(for: request) - // Valid if 200 - if httpResponse.statusCode == statusCodeOK { - return .valid - } - - guard let string = String(data: data, encoding: .utf8) else { - throw URLError(.badServerResponse) - } + guard let httpResponse = response as? HTTPURLResponse else { + throw URLError(.badServerResponse) + } - // Consider anything other than 200 invalid - return .invalid(message: string) - } catch { - return .unknown(error: error) + // Valid if 200 + if httpResponse.statusCode == statusCodeOK { + return } + + // For non-200 status codes, throw validation error with response body + let message = String(data: data, encoding: .utf8) + throw LiveKitError(.validation, message: message ?? "(No server message)") } } From b55107a3f86a915b41b054798793cbe7c2a14981 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:06:23 +0700 Subject: [PATCH 6/8] Fix shadowing --- Sources/LiveKit/Core/SignalClient.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/LiveKit/Core/SignalClient.swift b/Sources/LiveKit/Core/SignalClient.swift index aa41be192..408fe2435 100644 --- a/Sources/LiveKit/Core/SignalClient.swift +++ b/Sources/LiveKit/Core/SignalClient.swift @@ -162,20 +162,20 @@ actor SignalClient: Loggable { } return connectResponse - } catch { + } catch let connectionError { // Skip validation if user cancelled - if error is CancellationError { - await cleanUp(withError: error) - throw error + if connectionError is CancellationError { + await cleanUp(withError: connectionError) + throw connectionError } // Skip validation if reconnect mode if reconnectMode != nil { - await cleanUp(withError: error) - throw LiveKitError(.network, internalError: error) + await cleanUp(withError: connectionError) + throw LiveKitError(.network, internalError: connectionError) } - await cleanUp(withError: error) + await cleanUp(withError: connectionError) // Attempt to validate with server let validateUrl = try Utils.buildUrl(url, @@ -187,13 +187,13 @@ actor SignalClient: Loggable { do { try await HTTP.requestValidation(from: validateUrl, token: token) // Re-throw original error since validation passed - throw LiveKitError(.network, internalError: error) + throw LiveKitError(.network, internalError: connectionError) } catch let validationError as LiveKitError where validationError.type == .validation { // Re-throw validation error throw validationError } catch { // Re-throw original connection error for network issues during validation - throw LiveKitError(.network, internalError: error) + throw LiveKitError(.network, internalError: connectionError) } } } From c74abb8071e66af5efab8be6f450383605798f35 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:07:42 +0700 Subject: [PATCH 7/8] status code range --- Sources/LiveKit/Support/HTTP.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index 720efc501..d584e2972 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -17,8 +17,6 @@ import Foundation class HTTP: NSObject { - static let statusCodeOK = 200 - private static let operationQueue = OperationQueue() private static let session: URLSession = .init(configuration: .default, @@ -39,12 +37,12 @@ class HTTP: NSObject { throw URLError(.badServerResponse) } - // Valid if 200 - if httpResponse.statusCode == statusCodeOK { + // Valid if 2xx success status code + if (200 ..< 300).contains(httpResponse.statusCode) { return } - // For non-200 status codes, throw validation error with response body + // For non-2xx status codes, throw validation error with response body let message = String(data: data, encoding: .utf8) throw LiveKitError(.validation, message: message ?? "(No server message)") } From 6667f57cb88b58d34429a09cb245797c5f581f5b Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 19 Oct 2025 19:11:03 +0700 Subject: [PATCH 8/8] invert throw --- Sources/LiveKit/Support/HTTP.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/LiveKit/Support/HTTP.swift b/Sources/LiveKit/Support/HTTP.swift index d584e2972..1f1625ed1 100644 --- a/Sources/LiveKit/Support/HTTP.swift +++ b/Sources/LiveKit/Support/HTTP.swift @@ -37,13 +37,10 @@ class HTTP: NSObject { throw URLError(.badServerResponse) } - // Valid if 2xx success status code - if (200 ..< 300).contains(httpResponse.statusCode) { - return - } - // For non-2xx status codes, throw validation error with response body - let message = String(data: data, encoding: .utf8) - throw LiveKitError(.validation, message: message ?? "(No server message)") + if !(200 ..< 300).contains(httpResponse.statusCode) { + let message = String(data: data, encoding: .utf8) + throw LiveKitError(.validation, message: message ?? "(No server message)") + } } }