Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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