mana-swift-core/Sources/ManaCore/API/AuthenticatedTransport.swift
Till JS df6f67ee45 v1.0.0 — initiale Extraktion aus memoro-native
ManaCore + ManaTokens als Swift-Package für alle nativen
mana-e.V.-Apps. Phase α aus mana/docs/MANA_SWIFT.md durch.

ManaCore:
- AuthClient gegen mana-auth (Login, Refresh, Status-Maschine)
- AuthenticatedTransport (URLSession + 401-Retry)
- ManaAppConfig-Protocol für App-injizierbare Konfig
- KeychainStore mit optionaler Shared-Access-Group
- JWT-Parser für lokale Expiry-Prüfung
- AuthError, CoreLog (interne OSLog-Logger)

ManaTokens:
- 12 Vereins-Tokens als dynamic Light/Dark Colors
- 5 Brand-Literale (mana-yellow, spectrum-orange, ...)
- Spacing, Radius, Typography aus mana/docs/THEMING.md

Tests: 12 Unit-Tests grün via swift test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:13:31 +02:00

61 lines
2.3 KiB
Swift

import Foundation
/// Authentifizierter HTTP-Transport. Setzt automatisch den `Authorization: Bearer`-
/// Header, refreshed bei `401` einmal proaktiv und wiederholt den Request.
///
/// Eine Instanz pro App-Backend (z.B. memoro-api, cards-api). Beim Init
/// die Basis-URL des jeweiligen App-Servers übergeben, nicht die mana-auth-URL.
public actor AuthenticatedTransport {
private let baseURL: URL
private let session: URLSession
private let auth: AuthClient
public init(baseURL: URL, auth: AuthClient, session: URLSession = .shared) {
self.baseURL = baseURL
self.session = session
self.auth = auth
}
/// Führt einen authentifizierten Request aus. Bei `401` wird der
/// Access-Token einmal refreshed und der Call wiederholt.
public func request(
path: String,
method: String = "GET",
body: Data? = nil,
contentType: String = "application/json"
) async throws -> (Data, HTTPURLResponse) {
let token = try await auth.freshAccessToken()
let response = try await send(path: path, method: method, body: body, contentType: contentType, token: token)
if response.1.statusCode == 401 {
let refreshed = try await auth.refreshAccessToken()
return try await send(path: path, method: method, body: body, contentType: contentType, token: refreshed)
}
return response
}
private func send(
path: String,
method: String,
body: Data?,
contentType: String,
token: String
) async throws -> (Data, HTTPURLResponse) {
var request = URLRequest(url: baseURL.appending(path: path))
request.httpMethod = method
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if let body {
request.httpBody = body
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
}
do {
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw AuthError.networkFailure("Keine HTTP-Antwort")
}
return (data, http)
} catch let error as URLError {
throw AuthError.networkFailure(error.localizedDescription)
}
}
}