Files
Felitendo b383c8e948 chore: drop two retired names from prose, and write down what admin means
The old server name and the account the repositories live under were still
scattered through comments, doc prose and one line of dashboard copy. None of
those readings told anyone anything: neither name is what anything is called
now, and a comment that explains a value in terms of a name nobody uses
explains nothing.

Values are untouched, and that distinction is the whole rule. The legacy backup
type string, the pre-rename UserDefaults key, the pre-rename host in the
User-Agent allowlist and the registry namespaces are all things somebody else
already stored; rewriting one breaks an install rather than tidying it. So the
sentences changed and the strings did not, and AGENTS.md now lists exactly
which hits a sweep is supposed to keep finding.

The feed's blocklist grows the second name, so a future message cannot put it
back on a page anyone can load. Its fixtures name both on purpose: that test is
the proof the feed never publishes them, and it cannot show that without
saying them once.

Also states the thing that had been left implicit: FTStore Admin is the
dashboard and the console app together, so an operator-facing control that
lands in only one of them is half-built.
2026-08-05 14:56:07 +02:00

92 lines
2.6 KiB
Swift

//
// FetchService.swift
// Loader
//
// Created by samara on 14.03.2025.
//
import Foundation
// MARK: - Class
public class NBFetchService {
public enum NBFetchServiceError: Error, LocalizedError {
case invalidURL
case networkError(Error)
case noData
case parsingError(Error)
public var errorDescription: String? {
switch self {
case .invalidURL: "The URL is invalid."
case .networkError(let error): "Network error: \(error.localizedDescription)"
case .noData: "No data received."
case .parsingError(let error): "Failed to parse data: \(error.localizedDescription)"
}
}
}
public init() {}
/// Identifies the app to its repo backend. The server gates its feed on this, so it
/// must be deterministic and start with `FTStore`/`FTStore-Manager`. Derived from
/// the running app's bundle so the same code yields the right value in both apps.
public static let userAgent: String = {
let info = Bundle.main.infoDictionary
let version = (info?["CFBundleShortVersionString"] as? String) ?? "1.0"
let bundleId = Bundle.main.bundleIdentifier ?? "app.ftstore"
let product = bundleId == "app.ftstore.manager" ? "FTStore-Manager" : "FTStore"
return "\(product)/\(version) (\(bundleId))"
}()
}
// MARK: - Class extension: fetch
extension NBFetchService {
public func fetch<T: Decodable>(
from urlString: String,
completion: @escaping (Result<T, Error>) -> Void
) {
guard let url = URL(string: urlString) else {
completion(.failure(NBFetchServiceError.invalidURL))
return
}
fetch(from: url, completion: completion)
}
public func fetch<T: Decodable>(
from url: URL,
completion: @escaping (Result<T, Error>) -> Void
) {
DispatchQueue.global(qos: .userInitiated).async {
// Always pull a fresh copy: source feeds change frequently, and the
// default protocol cache policy would otherwise serve a stale JSON
// (showing versions that no longer exist in the live manifest).
var request = URLRequest(url: url)
request.cachePolicy = .reloadIgnoringLocalCacheData
request.setValue(Self.userAgent, forHTTPHeaderField: "User-Agent")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(NBFetchServiceError.networkError(error)))
return
}
guard let data = data else {
completion(.failure(NBFetchServiceError.noData))
return
}
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(T.self, from: data)
completion(.success(decodedData))
} catch {
completion(.failure(NBFetchServiceError.parsingError(error)))
}
}
task.resume()
}
}
}