feat(ios): W4 app wiring — DI graph, event fan-out, lifecycle, privacy shade
T-iOS-15: AppEnvironment production DI (real Keychain/probe/transports), EventFanOut (engine.events → TerminalVM + GateVM + activity bridge), scenePhase lifecycle (.background→close, suspend→rebuild+reopen reclaims size), privacy shade on != .active, spike screen deleted, cold-start routing Walkthrough proxies: hosted live-server smoke over the production DI graph (probe→store→ attach→echo→close) + real-Keychain kSecAttrAccessible assertion (closes T-iOS-7 deferral) Deviation closed: TerminalViewModel.lastSentDims (valid-only) + alive-engine .active → notifyForegrounded(dims:) (orchestrator fix on behalf of T-iOS-11 owner) Env finding: repo under ~/Documents → TCC blocks sim-spawned node; SimServerHarness dual-mode (self-bootstrap / WEBTERM_SERVER_URL via simctl launchctl setenv) Verified: 214 pkg + 76 app + 10 integration tests green; verify agent 8/8 PASS
This commit is contained in:
56
ios/App/WebTerm/Wiring/AppEnvironment.swift
Normal file
56
ios/App/WebTerm/Wiring/AppEnvironment.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
import APIClient
|
||||
import Foundation
|
||||
import HostRegistry
|
||||
import SessionCore
|
||||
import WireProtocol
|
||||
|
||||
/// T-iOS-15 · Production dependency graph (composition root). One immutable
|
||||
/// value assembled at launch; every screen/VM receives its dependencies from
|
||||
/// here — no ad-hoc URLSession/keychain access anywhere else in the App layer.
|
||||
///
|
||||
/// Assembly security audit (task 安全注, verified at this single point):
|
||||
/// - All `G` (state-changing) HTTP goes through `APIClient` (kill via
|
||||
/// SessionListViewModel's client, probe's kill round-trip inside
|
||||
/// `runPairingProbe`); `URLSessionHTTPTransport` adds no headers of its own.
|
||||
/// - Origin is derived solely by `HostEndpoint` (WS: URLSessionTermTransport;
|
||||
/// HTTP: APIClient's route builder) — nothing here hand-assembles one.
|
||||
/// - Secrets: hosts in `KeychainHostStore`
|
||||
/// (AfterFirstUnlockThisDeviceOnly, hosted keychain test asserts it);
|
||||
/// UserDefaults carries only the non-secret per-host lastSessionId.
|
||||
/// - No debug ATS overrides exist (project.yml declares NO
|
||||
/// NSAllowsArbitraryLoads in any configuration — only the five §5.2 CIDR
|
||||
/// exceptions), and no isSecureTextEntry-style screenshot hacks are used;
|
||||
/// `UIScreen.isCaptured` detection is SKIPPED per plan (accepted residual
|
||||
/// risk, local trust model).
|
||||
struct AppEnvironment: Sendable {
|
||||
let hostStore: any HostStore
|
||||
let lastSessionStore: any LastSessionStore
|
||||
let http: any HTTPTransport
|
||||
let termTransport: any TermTransport
|
||||
/// Injected into `PairingViewModel` — production is `runPairingProbe`
|
||||
/// over the real transports (two-step: RO GET, then WS attach + guarded
|
||||
/// kill; only runs after the user's explicit confirm, T-iOS-12).
|
||||
let probe: PairingViewModel.Probe
|
||||
|
||||
static func production() -> AppEnvironment {
|
||||
let http = URLSessionHTTPTransport()
|
||||
let termTransport = URLSessionTermTransport()
|
||||
return AppEnvironment(
|
||||
hostStore: KeychainHostStore(),
|
||||
lastSessionStore: UserDefaultsLastSessionStore(),
|
||||
http: http,
|
||||
termTransport: termTransport,
|
||||
probe: { endpoint in
|
||||
await runPairingProbe(endpoint: endpoint, http: http, ws: termTransport)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Away-digest source for a session engine: wraps `APIClient.events` per
|
||||
/// host (the engine never holds an HTTP client — plan §3.2).
|
||||
func makeEventsSource(endpoint: HostEndpoint)
|
||||
-> @Sendable (UUID) async throws -> [TimelineEvent] {
|
||||
let client = APIClient(endpoint: endpoint, http: http)
|
||||
return { id in try await client.events(id: id) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user