Skip to content

Commit 4c742d5

Browse files
committed
Add an error for lost connection
1 parent 58aab91 commit 4c742d5

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

FirebaseAI/Sources/Types/Internal/Live/AsyncWebSocket.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,30 @@ final class AsyncWebSocket: NSObject, @unchecked Sendable, URLSessionWebSocketDe
7171
let message = try await webSocketTask.receive()
7272
continuation.yield(message)
7373
} catch {
74-
close(code: webSocketTask.closeCode, reason: webSocketTask.closeReason)
74+
if let error = webSocketTask.error as? NSError {
75+
close(
76+
code: webSocketTask.closeCode,
77+
reason: webSocketTask.closeReason,
78+
underlyingError: error
79+
)
80+
} else {
81+
close(code: webSocketTask.closeCode, reason: webSocketTask.closeReason)
82+
}
7583
}
7684
}
7785
}
7886
}
7987

80-
private func close(code: URLSessionWebSocketTask.CloseCode, reason: Data?) {
81-
let error = WebSocketClosedError(closeCode: code, closeReason: reason)
88+
private func close(
89+
code: URLSessionWebSocketTask.CloseCode,
90+
reason: Data?,
91+
underlyingError: Error? = nil
92+
) {
93+
let error = WebSocketClosedError(
94+
closeCode: code,
95+
closeReason: reason,
96+
underlyingError: underlyingError
97+
)
8298
closeError.withLock {
8399
$0 = error
84100
}
@@ -110,21 +126,27 @@ private extension URLSessionWebSocketTask {
110126
///
111127
/// See the `closeReason` for why, or the `errorCode` for the corresponding
112128
/// `URLSessionWebSocketTask.CloseCode`.
129+
///
130+
/// In some cases, the `NSUnderlyingErrorKey` key may be populated with an
131+
/// error for additional context.
113132
struct WebSocketClosedError: Error, Sendable, CustomNSError {
114133
let closeCode: URLSessionWebSocketTask.CloseCode
115134
let closeReason: String
135+
let underlyingError: Error?
116136

117-
init(closeCode: URLSessionWebSocketTask.CloseCode, closeReason: Data?) {
137+
init(closeCode: URLSessionWebSocketTask.CloseCode, closeReason: Data?, underlyingError: Error? = nil) {
118138
self.closeCode = closeCode
119139
self.closeReason = closeReason
120140
.flatMap { String(data: $0, encoding: .utf8) } ?? "Unknown reason."
141+
self.underlyingError = underlyingError
121142
}
122143

123144
var errorCode: Int { closeCode.rawValue }
124145

125146
var errorUserInfo: [String: Any] {
126147
[
127148
NSLocalizedDescriptionKey: "WebSocket closed with code \(closeCode.rawValue). Reason: \(closeReason)",
149+
NSUnderlyingErrorKey: underlyingError as Any,
128150
]
129151
}
130152
}

FirebaseAI/Sources/Types/Internal/Live/LiveSessionService.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,14 @@ actor LiveSessionService {
244244
if let error = error as? WebSocketClosedError {
245245
// only raise an error if the session didn't close normally (ie; the user calling close)
246246
if error.closeCode != .goingAway {
247-
let error = LiveSessionUnexpectedClosureError(underlyingError: error)
247+
let closureError: Error
248+
if let error = error.underlyingError as? NSError, error.domain == NSURLErrorDomain, error.code == NSURLErrorNetworkConnectionLost {
249+
closureError = LiveSessionLostConnectionError(underlyingError: error)
250+
} else {
251+
closureError = LiveSessionUnexpectedClosureError(underlyingError: error)
252+
}
248253
close()
249-
responseContinuation.finish(throwing: error)
254+
responseContinuation.finish(throwing: closureError)
250255
}
251256
} else {
252257
// an error occurred outside the websocket, so it's likely not closed
@@ -365,10 +370,27 @@ public struct LiveSessionUnsupportedMessageError: Error, Sendable, CustomNSError
365370
}
366371
}
367372

373+
/// The live session was closed, because the network connection was lost.
374+
///
375+
/// Check the `NSUnderlyingErrorKey` entry in ``errorUserInfo`` for the error that caused this.
376+
public struct LiveSessionLostConnectionError: Error, Sendable, CustomNSError {
377+
let underlyingError: Error
378+
379+
init(underlyingError: Error) {
380+
self.underlyingError = underlyingError
381+
}
382+
383+
public var errorUserInfo: [String: Any] {
384+
[
385+
NSLocalizedDescriptionKey: "The live session lost connection to the server. Cause: \(underlyingError.localizedDescription)",
386+
NSUnderlyingErrorKey: underlyingError,
387+
]
388+
}
389+
}
390+
368391
/// The live session was closed, but not for a reason the SDK expected.
369392
///
370393
/// Check the `NSUnderlyingErrorKey` entry in ``errorUserInfo`` for the error that caused this.
371-
// TODO: two common causes I can think of are api limits and network issues. I wonder if we can catch these somehow, as they seem common enough to surface as actual errors.
372394
public struct LiveSessionUnexpectedClosureError: Error, Sendable, CustomNSError {
373395
let underlyingError: WebSocketClosedError
374396

0 commit comments

Comments
 (0)