Files
aris-old/IrisCompanion/iris/Spotify/SpotifyAPIClient.swift

78 lines
2.5 KiB
Swift
Raw Permalink Normal View History

//
// 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)
}
}