zitare-native/Sources/Features/WebShell/CookieBridge.swift
Till 75b5e7113f ζ-1: WebShellView + Universal-Link-Routing
- WebShellView (UIViewRepresentable + NSViewRepresentable) wrapt
  WKWebView, KVO-Observation für Loading/Progress/canGoBack/URL,
  Pull-to-Refresh via UIRefreshControl
- WebShellCoordinator (MainActor) hält WKNavigationDelegate +
  WKUIDelegate, externe Links via openURL aus dem Environment in
  System-Browser, Host-Whitelist auf zitare.com + .mana.how
- RootView refactored: Lesen-Tab lädt webBaseURL/, Erkunden-Tab
  /explore. Universal-Links zitare.com/q|a|c/<slug>, /search,
  /region/*, /thema/* etc. routen in den passenden Tab,
  reloadToken zwingt Re-Navigation auch bei selber URL
- AppConfig.webBaseURL = appBaseURL (zitare.mana.how) bis
  Cloudflare-Zone für zitare.com live ist; publicWebURL als
  Konstante schon eingetragen
- CookieBridge-Skeleton für mana.access auf .mana.how —
  scharfgeschaltet erst in ζ-3 nach Live-Auth-Smoke
- iPhone 16e Simulator: zitare.mana.how lädt, Carl-Spitteler-Quote
  rendert, Healthz weiter 200
- 16 Files swiftlint-grün, alle Tests grün

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

75 lines
3 KiB
Swift

import Foundation
import ManaCore
import WebKit
/// Reicht den mana-auth-JWT als Cookie an den `WKWebView` weiter, sodass
/// eingeloggte `(app)`-Routen auf `zitare.mana.how` ohne zweiten Login
/// erreichbar sind.
///
/// **Phase ζ-1: Skeleton.** Methoden existieren, werden aber heute
/// nicht aufgerufen bevor sie scharfgeschaltet werden, muss der
/// Cookie-SSO-Pfad auf der Web-Seite (`zitare/apps/api/src/auth/`
/// und `apps/zitare/src/lib/auth/token-helper.ts`) gegen einen
/// *echten* mana-auth-Token End-to-End getestet sein (Verifikations-
/// Lücke in `zitare/STATUS.md`).
///
/// **Phase ζ-3:** wird in `SubmitQuoteView` benutzt vor dem POST
/// gegen `zitare-api.mana.how` und vor dem Öffnen von
/// `zitare.mana.how/me` im WebView.
///
/// **Cookie-Schema** (gespiegelt zu mana-auth, siehe
/// `mana/services/mana-auth/src/auth/cookies.ts`):
/// - Name: `mana.access` (JWT) und optional `mana.refresh` (Opaque)
/// - Domain: `.mana.how` (App-Surface; **nicht** `.com`)
/// - Path: `/`
/// - Secure: true, HTTPOnly: false (WebView muss lesen können),
/// SameSite: Lax
enum CookieBridge {
/// Setzt den `mana.access`-Cookie im geteilten `WKHTTPCookieStore`,
/// wenn der `AuthClient` einen gültigen JWT hält. No-op sonst.
@MainActor
static func installManaAccess(from auth: AuthClient) async {
guard case .signedIn = auth.status, let token = currentAccessToken(from: auth) else {
Log.web.debug("CookieBridge: kein signedIn-Token, no-op")
return
}
guard let cookie = makeAccessCookie(token: token) else {
Log.web.warning("CookieBridge: konnte Cookie-Properties nicht bauen")
return
}
let store = WKWebsiteDataStore.default().httpCookieStore
await store.setCookie(cookie)
Log.web.info("CookieBridge: mana.access für .mana.how gesetzt")
}
/// Entfernt den `mana.access`-Cookie wieder etwa nach Logout.
@MainActor
static func removeManaAccess() async {
let store = WKWebsiteDataStore.default().httpCookieStore
let cookies = await store.allCookies()
for cookie in cookies where cookie.name == "mana.access" {
await store.deleteCookie(cookie)
}
Log.web.info("CookieBridge: mana.access entfernt")
}
private static func currentAccessToken(from auth: AuthClient) -> String? {
// ManaCore hält den JWT im Keychain. In ζ-3 ersetzt durch die
// tatsächliche `auth.currentAccessToken()`-API; heute nur
// Skelett-Hook, damit Cookie-Setup und API in Reichweite sind.
// Linter beruhigen ohne unused warning:
_ = auth
return nil
}
private static func makeAccessCookie(token: String) -> HTTPCookie? {
HTTPCookie(properties: [
.name: "mana.access",
.value: token,
.domain: ".mana.how",
.path: "/",
.secure: true,
.sameSitePolicy: HTTPCookieStringPolicy.sameSiteLax
])
}
}