137 lines
4.2 KiB
Swift
137 lines
4.2 KiB
Swift
//
|
|
// 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<T: Codable>(
|
|
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)
|
|
}
|
|
}
|
|
}
|