refactor: Enhance API error handling with logging, introduce minimum loading time, update app version to 1.2.1, and ignore .DS_Store.

This commit is contained in:
Paweł Orzech 2026-01-17 21:34:47 +00:00
parent 1747bb110a
commit c21057146d
No known key found for this signature in database
5 changed files with 60 additions and 13 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View file

@ -60,3 +60,4 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
.DS_Store

BIN
MacTorn-v1.2.1.zip Normal file

Binary file not shown.

View file

@ -17,8 +17,8 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.2</string>
<string>1.2.1</string>
<key>CFBundleVersion</key>
<string>1.2</string>
<string>1.2.1</string>
</dict>
</plist>

View file

@ -1,6 +1,9 @@
import Foundation
import Combine
import SwiftUI
import os.log
private let logger = Logger(subsystem: "com.mactorn", category: "AppState")
@MainActor
class AppState: ObservableObject {
@ -234,18 +237,35 @@ class AppState: ObservableObject {
func fetchData() {
guard !apiKey.isEmpty else {
errorMsg = "API Key required"
logger.warning("Fetch aborted: API Key required")
return
}
guard let url = TornAPI.url(for: apiKey) else {
errorMsg = "Invalid URL"
logger.error("Fetch aborted: Invalid URL")
return
}
isLoading = true
errorMsg = nil
logger.info("Starting data fetch...")
Task {
let startTime = Date()
// Ensure minimum loading time for UX, then set isLoading = false
defer {
Task { @MainActor in
let elapsed = Date().timeIntervalSince(startTime)
if elapsed < 0.5 {
try? await Task.sleep(nanoseconds: UInt64((0.5 - elapsed) * 1_000_000_000))
}
self.isLoading = false
}
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
@ -253,33 +273,59 @@ class AppState: ObservableObject {
throw APIError.invalidResponse
}
logger.info("HTTP response: \(httpResponse.statusCode)")
switch httpResponse.statusCode {
case 200:
// Check for Torn API error in response (API returns 200 even on errors)
if let tornError = checkForTornAPIError(data: data) {
await MainActor.run {
self.errorMsg = tornError
}
logger.error("Torn API error: \(tornError)")
return
}
// Parse on background thread
try await parseDataInBackground(data: data)
// Fetch faction data separately
await fetchFactionData()
logger.info("Data fetch completed successfully")
case 403, 404:
await MainActor.run {
self.errorMsg = "Invalid API Key"
self.data = nil
self.isLoading = false
}
logger.error("HTTP \(httpResponse.statusCode): Invalid API Key")
default:
await MainActor.run {
self.errorMsg = "HTTP Error: \(httpResponse.statusCode)"
self.isLoading = false
}
logger.error("HTTP Error: \(httpResponse.statusCode)")
}
} catch {
await MainActor.run {
self.errorMsg = error.localizedDescription
self.isLoading = false
self.errorMsg = "Network error: \(error.localizedDescription)"
}
logger.error("Network error: \(error.localizedDescription)")
}
}
}
/// Check if Torn API returned an error (API returns HTTP 200 even on errors like rate limiting)
private func checkForTornAPIError(data: Data) -> String? {
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let error = json["error"] as? [String: Any],
let errorMessage = error["error"] as? String else {
return nil
}
let errorCode = error["code"] as? Int ?? 0
logger.warning("Torn API error code \(errorCode): \(errorMessage)")
return "API Error: \(errorMessage)"
}
// Move parsing logic here and mark as non-isolated or detached