Skip to content
1 change: 1 addition & 0 deletions .changes/validation-logic
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patch type="changed" "Minor validation logic improvements"
32 changes: 19 additions & 13 deletions Sources/LiveKit/Core/SignalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,33 +162,39 @@ 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 error
await cleanUp(withError: connectionError)
throw LiveKitError(.network, internalError: connectionError)
}

await cleanUp(withError: error)
await cleanUp(withError: connectionError)

// 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)
log("Validate response: \(validationResponse)")
// re-throw with validation response
throw LiveKitError(.network, message: "Validation response: \"\(validationResponse)\"")
do {
try await HTTP.requestValidation(from: validateUrl, token: token)
// Re-throw original error since validation passed
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: connectionError)
}
}
}

Expand Down
16 changes: 11 additions & 5 deletions Sources/LiveKit/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum LiveKitErrorType: Int, Sendable {
case webRTC = 201

case network // Network issue
case validation // Network issue
Copy link
Contributor

@pblazej pblazej Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd rename that to authorization (or better?), as this is very generic (not sure about other SDKs).

Otherwise LGTM^2

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we used to call this "validation", the endpoint (path is /validate)

pathSegments.append("validate")

So maybe validation is better ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that matches backend, then why not


// Server
case duplicateIdentity = 500
Expand Down Expand Up @@ -80,6 +81,8 @@ extension LiveKitErrorType: CustomStringConvertible {
"WebRTC error"
case .network:
"Network error"
case .validation:
"Validation error"
case .duplicateIdentity:
"Duplicate Participant identity"
case .serverShutdown:
Expand Down Expand Up @@ -121,26 +124,29 @@ extension LiveKitErrorType: CustomStringConvertible {
public class LiveKitError: NSError, @unchecked Sendable, Loggable {
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,
message: String? = nil,
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()])
Expand Down
18 changes: 11 additions & 7 deletions Sources/LiveKit/Support/HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@ 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)
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")

// 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")
let (data, response) = try await session.data(for: request)

guard let httpResponse = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}

return string
// For non-2xx status codes, throw validation error with response body
if !(200 ..< 300).contains(httpResponse.statusCode) {
let message = String(data: data, encoding: .utf8)
throw LiveKitError(.validation, message: message ?? "(No server message)")
}
}
}
Loading