Files
ProxyIOS/UI/More/PinnedDomainsView.swift
Trey t 148bc3887c Add iPad support, auto-pinning, and comprehensive logging
- 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
2026-04-11 12:52:18 -05:00

81 lines
3.2 KiB
Swift

import SwiftUI
import ProxyCore
import GRDB
struct PinnedDomainsView: View {
@State private var domains: [PinnedDomain] = []
@State private var observation: AnyDatabaseCancellable?
@State private var showClearConfirmation = false
private let repo = PinnedDomainRepository()
var body: some View {
List {
Section {
VStack(alignment: .leading, spacing: 8) {
Text("SSL Pinning Detection")
.font(.headline)
Text("Domains listed here were automatically detected as using SSL pinning. MITM interception is skipped for these domains — they use passthrough instead so the app still works.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
}
Section("Detected Domains (\(domains.count))") {
if domains.isEmpty {
Text("No pinned domains detected yet")
.foregroundStyle(.secondary)
.font(.subheadline)
} else {
ForEach(domains) { domain in
VStack(alignment: .leading, spacing: 4) {
Text(domain.domain)
.font(.subheadline.weight(.medium))
if let reason = domain.reason {
Text(reason)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(2)
}
Text("Detected \(Date(timeIntervalSince1970: domain.detectedAt).formatted(.relative(presentation: .named)))")
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
.onDelete { offsets in
for index in offsets {
try? repo.unpin(domain: domains[index].domain)
}
}
}
}
}
.navigationTitle("Pinned Domains")
.toolbar {
if !domains.isEmpty {
ToolbarItem(placement: .topBarTrailing) {
Button("Clear All", role: .destructive) {
showClearConfirmation = true
}
}
}
}
.confirmationDialog("Clear All Pinned Domains?", isPresented: $showClearConfirmation) {
Button("Clear All", role: .destructive) {
try? repo.deleteAll()
}
} message: {
Text("This will allow MITM interception to be attempted again for all domains. Pinned domains will be re-detected automatically if they still use SSL pinning.")
}
.task {
observation = repo.observeAll()
.start(in: DatabaseManager.shared.dbPool) { error in
print("Pinned domains observation error: \(error)")
} onChange: { newDomains in
withAnimation { domains = newDomains }
}
}
}
}