- 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
81 lines
3.2 KiB
Swift
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 }
|
|
}
|
|
}
|
|
}
|
|
}
|