// // Networking.swift // SolianWidgetExtensionExtension // // Created by LittleSheep on 2026/1/4. // import Foundation enum RemoteError: Error { case missingCredentials case invalidURL case invalidResponse case httpError(Int) case decodingError } extension RemoteError: LocalizedError { var errorDescription: String? { switch self { case .missingCredentials: return "Please open the app to sign in." case .invalidURL: return "Invalid server configuration." case .invalidResponse: return "Server returned an invalid response." case .httpError(let code): return "Server error (\(code))." case .decodingError: return "Failed to read server data." } } } struct TokenData: Codable { let token: String } class WidgetNetworkService { private let appGroup = "group.solsynth.solian" private let tokenKey = "flutter.dyn_user_tk" private let urlKey = "flutter.app_server_url" private lazy var session: URLSession = { let configuration = URLSessionConfiguration.ephemeral configuration.timeoutIntervalForRequest = 10.0 configuration.timeoutIntervalForResource = 10.0 configuration.waitsForConnectivity = false return URLSession(configuration: configuration) }() private var userDefaults: UserDefaults? { UserDefaults(suiteName: appGroup) } var token: String? { guard let tokenString = userDefaults?.string(forKey: tokenKey) else { return nil } guard let data = tokenString.data(using: .utf8) else { return nil } do { let tokenData = try JSONDecoder().decode(TokenData.self, from: data) return tokenData.token } catch { print("[WidgetKit] Failed to decode token: \(error)") return nil } } var baseURL: String { return userDefaults?.string(forKey: urlKey) ?? "https://api.solian.app" } func makeRequest( path: String, method: String = "GET", headers: [String: String] = [:] ) async throws -> T? { guard let token = token else { throw RemoteError.missingCredentials } guard let url = URL(string: "\(baseURL)\(path)") else { throw RemoteError.invalidURL } var request = URLRequest(url: url) request.httpMethod = method request.setValue("AtField \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Accept") for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } request.timeoutInterval = 10.0 print("[WidgetKit] [Network] Requesting: \(baseURL)\(path)") let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw RemoteError.invalidResponse } print("[WidgetKit] [Network] Status: \(httpResponse.statusCode), Data length: \(data.count)") if let jsonString = String(data: data, encoding: .utf8) { print("[WidgetKit] [Network] Response: \(jsonString.prefix(500))") } switch httpResponse.statusCode { case 200...299: let decoder = JSONDecoder() do { let result = try decoder.decode(T.self, from: data) print("[WidgetKit] [Network] Successfully decoded response") return result } catch { print("[WidgetKit] [Network] Decoding error: \(error.localizedDescription)") print("[WidgetKit] [Network] Expected type: \(String(describing: T.self))") throw RemoteError.decodingError } case 404: print("[WidgetKit] [Network] Resource not found (404)") return nil default: print("[WidgetKit] [Network] HTTP Error: \(httpResponse.statusCode)") throw RemoteError.httpError(httpResponse.statusCode) } } }