NetworkClient.swift 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import Foundation
  2. enum NetworkClient {
  3. static let session: URLSession = {
  4. let configuration = URLSessionConfiguration.default
  5. configuration.waitsForConnectivity = true
  6. configuration.timeoutIntervalForRequest = 60
  7. configuration.timeoutIntervalForResource = 120
  8. configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
  9. return URLSession(configuration: configuration)
  10. }()
  11. /// Low-latency session for AI API calls — does not block waiting for connectivity.
  12. static let aiSession: URLSession = {
  13. let configuration = URLSessionConfiguration.default
  14. configuration.waitsForConnectivity = false
  15. configuration.timeoutIntervalForRequest = 45
  16. configuration.timeoutIntervalForResource = 60
  17. configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
  18. configuration.httpMaximumConnectionsPerHost = 4
  19. return URLSession(configuration: configuration)
  20. }()
  21. static func data(
  22. for request: URLRequest,
  23. session: URLSession = session,
  24. retries: Int = 2
  25. ) async throws -> (Data, URLResponse) {
  26. var lastError: Error?
  27. for attempt in 0...retries {
  28. if attempt > 0 {
  29. try await Task.sleep(nanoseconds: UInt64(attempt) * 400_000_000)
  30. }
  31. do {
  32. return try await session.data(for: request)
  33. } catch let error as URLError where error.isTransientNetworkFailure {
  34. lastError = error
  35. } catch {
  36. throw error
  37. }
  38. }
  39. throw lastError ?? URLError(.unknown)
  40. }
  41. static func aiData(for request: URLRequest) async throws -> (Data, URLResponse) {
  42. try await data(for: request, session: aiSession, retries: 0)
  43. }
  44. static func prepare() async {
  45. guard let url = URL(string: "https://api.openai.com") else { return }
  46. var request = URLRequest(url: url)
  47. request.httpMethod = "HEAD"
  48. request.timeoutInterval = 10
  49. _ = try? await aiSession.data(for: request)
  50. }
  51. }
  52. private extension URLError {
  53. var isTransientNetworkFailure: Bool {
  54. switch code {
  55. case .networkConnectionLost,
  56. .notConnectedToInternet,
  57. .timedOut,
  58. .cannotConnectToHost,
  59. .dnsLookupFailed,
  60. .cannotFindHost,
  61. .internationalRoamingOff,
  62. .callIsActive:
  63. return true
  64. default:
  65. return false
  66. }
  67. }
  68. }