// // SpotifyAPIClient.swift // iris // // Spotify Web API client for fetching currently playing track. // import Foundation import os @available(iOS 16.0, *) @MainActor final class SpotifyAPIClient { private let authManager: SpotifyAuthManager private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "iris", category: "SpotifyAPI") private let baseUrl = "https://api.spotify.com/v1" init(authManager: SpotifyAuthManager) { self.authManager = authManager } func getCurrentPlayback() async throws -> SpotifyPlaybackState? { try await getCurrentPlayback(isRetry: false) } private func getCurrentPlayback(isRetry: Bool) async throws -> SpotifyPlaybackState? { let refreshed = await authManager.refreshTokenIfNeeded() guard refreshed else { throw SpotifyAPIError.notAuthenticated } guard let token = authManager.accessToken else { throw SpotifyAPIError.notAuthenticated } var request = URLRequest(url: URL(string: "\(baseUrl)/me/player/currently-playing")!) request.httpMethod = "GET" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw SpotifyAPIError.invalidResponse } switch httpResponse.statusCode { case 200: return try JSONDecoder().decode(SpotifyPlaybackState.self, from: data) case 204: return nil case 401: guard !isRetry else { throw SpotifyAPIError.tokenExpired } let refreshed = await authManager.refreshTokenIfNeeded() if refreshed { return try await getCurrentPlayback(isRetry: true) } throw SpotifyAPIError.tokenExpired case 429: let retryAfter = httpResponse.value(forHTTPHeaderField: "Retry-After") logger.warning("Rate limited, retry after: \(retryAfter ?? "unknown")") throw SpotifyAPIError.rateLimited default: logger.error("API error: \(httpResponse.statusCode)") throw SpotifyAPIError.apiError(httpResponse.statusCode) } } enum SpotifyAPIError: Error { case notAuthenticated case tokenExpired case invalidResponse case rateLimited case apiError(Int) } }