- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
129 lines
5.0 KiB
Swift
129 lines
5.0 KiB
Swift
import SwiftUI
|
|
import ProxyCore
|
|
|
|
struct SetupGuideView: View {
|
|
@Environment(AppState.self) private var appState
|
|
|
|
var isReady: Bool {
|
|
appState.isVPNConnected && appState.hasSharedCertificate
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(spacing: 24) {
|
|
// Status Banner
|
|
HStack {
|
|
Image(systemName: isReady ? "checkmark.circle.fill" : "exclamationmark.triangle.fill")
|
|
.font(.title2)
|
|
VStack(alignment: .leading) {
|
|
Text(isReady ? "Ready to Capture" : "Setup Required")
|
|
.font(.headline)
|
|
Text(isReady ? "The tunnel and shared certificate are configured" : "Complete the steps below to start")
|
|
.font(.caption)
|
|
.foregroundStyle(.white.opacity(0.8))
|
|
}
|
|
Spacer()
|
|
}
|
|
.foregroundStyle(.white)
|
|
.padding()
|
|
.background(isReady ? Color.green : Color.orange, in: RoundedRectangle(cornerRadius: 12))
|
|
|
|
Text("Follow these two steps to start capturing network traffic on your device.")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
|
|
// Step 1: VPN
|
|
stepRow(
|
|
title: "VPN Extension Enabled",
|
|
subtitle: appState.isVPNConnected
|
|
? "VPN is running and capturing traffic"
|
|
: "Tap to enable VPN",
|
|
isComplete: appState.isVPNConnected,
|
|
action: {
|
|
Task { await appState.toggleVPN() }
|
|
}
|
|
)
|
|
|
|
// Step 2: Certificate
|
|
NavigationLink {
|
|
CertificateView()
|
|
} label: {
|
|
HStack(spacing: 12) {
|
|
Image(systemName: appState.hasSharedCertificate ? "checkmark.circle.fill" : "circle")
|
|
.font(.title2)
|
|
.foregroundStyle(appState.hasSharedCertificate ? .green : .secondary)
|
|
|
|
VStack(alignment: .leading) {
|
|
Text("Shared Certificate Available")
|
|
.font(.subheadline.weight(.medium))
|
|
.foregroundStyle(appState.hasSharedCertificate ? .green : .primary)
|
|
Text(certificateSubtitle)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
|
|
if let lastError = appState.lastRuntimeError {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Label("Latest Runtime Error", systemImage: "exclamationmark.triangle.fill")
|
|
.font(.subheadline.weight(.medium))
|
|
.foregroundStyle(.orange)
|
|
Text(lastError)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.navigationTitle("Setup Guide")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
private var certificateSubtitle: String {
|
|
if !appState.isCertificateInstalled {
|
|
return "Generate and install the Proxy CA certificate."
|
|
}
|
|
if !appState.hasSharedCertificate {
|
|
return "The app has a CA, but the extension has not loaded the same one yet."
|
|
}
|
|
if appState.isHTTPSInspectionVerified {
|
|
return "HTTPS inspection has been verified on live traffic."
|
|
}
|
|
return "Include a domain in SSL Proxying, trust the CA in Settings, then retry the request."
|
|
}
|
|
|
|
private func stepRow(title: String, subtitle: String, isComplete: Bool, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
HStack(spacing: 12) {
|
|
Image(systemName: isComplete ? "checkmark.circle.fill" : "circle")
|
|
.font(.title2)
|
|
.foregroundStyle(isComplete ? .green : .secondary)
|
|
|
|
VStack(alignment: .leading) {
|
|
Text(title)
|
|
.font(.subheadline.weight(.medium))
|
|
.foregroundStyle(isComplete ? .green : .primary)
|
|
Text(subtitle)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|