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
This commit is contained in:
Trey t
2026-04-11 12:52:18 -05:00
parent c77e506db5
commit 148bc3887c
77 changed files with 6710 additions and 847 deletions

View File

@@ -7,6 +7,9 @@ struct SSLProxyingListView: View {
@State private var entries: [SSLProxyingEntry] = []
@State private var showAddInclude = false
@State private var showAddExclude = false
@State private var editingEntry: SSLProxyingEntry?
@State private var entryToDelete: SSLProxyingEntry?
@State private var showDeleteConfirmation = false
@State private var observation: AnyDatabaseCancellable?
private let rulesRepo = RulesRepository()
@@ -41,13 +44,17 @@ struct SSLProxyingListView: View {
.font(.subheadline)
} else {
ForEach(includeEntries) { entry in
Text(entry.domainPattern)
Button {
editingEntry = entry
} label: {
Text(entry.domainPattern)
.foregroundStyle(.primary)
}
}
.onDelete { indexSet in
for index in indexSet {
if let id = includeEntries[index].id {
try? rulesRepo.deleteSSLEntry(id: id)
}
if let index = indexSet.first {
entryToDelete = includeEntries[index]
showDeleteConfirmation = true
}
}
}
@@ -60,13 +67,17 @@ struct SSLProxyingListView: View {
.font(.subheadline)
} else {
ForEach(excludeEntries) { entry in
Text(entry.domainPattern)
Button {
editingEntry = entry
} label: {
Text(entry.domainPattern)
.foregroundStyle(.primary)
}
}
.onDelete { indexSet in
for index in indexSet {
if let id = excludeEntries[index].id {
try? rulesRepo.deleteSSLEntry(id: id)
}
if let index = indexSet.first {
entryToDelete = excludeEntries[index]
showDeleteConfirmation = true
}
}
}
@@ -81,6 +92,7 @@ struct SSLProxyingListView: View {
Divider()
Button("Clear All Rules", role: .destructive) {
try? rulesRepo.deleteAllSSLEntries()
IPCManager.shared.post(.configurationChanged)
}
} label: {
Image(systemName: "ellipsis.circle")
@@ -91,12 +103,37 @@ struct SSLProxyingListView: View {
DomainEntrySheet(title: "New Include Entry", isInclude: true) { pattern in
var entry = SSLProxyingEntry(domainPattern: pattern, isInclude: true)
try? rulesRepo.insertSSLEntry(&entry)
IPCManager.shared.isSSLProxyingEnabled = true
IPCManager.shared.post(.configurationChanged)
}
}
.sheet(isPresented: $showAddExclude) {
DomainEntrySheet(title: "New Exclude Entry", isInclude: false) { pattern in
var entry = SSLProxyingEntry(domainPattern: pattern, isInclude: false)
try? rulesRepo.insertSSLEntry(&entry)
IPCManager.shared.post(.configurationChanged)
}
}
.sheet(item: $editingEntry) { entry in
DomainEntrySheet(
title: entry.isInclude ? "Edit Include Entry" : "Edit Exclude Entry",
isInclude: entry.isInclude,
existingEntry: entry
) { pattern in
var updated = entry
updated.domainPattern = pattern
try? rulesRepo.updateSSLEntry(updated)
if updated.isInclude {
IPCManager.shared.isSSLProxyingEnabled = true
}
IPCManager.shared.post(.configurationChanged)
}
}
.confirmationDialog("Delete this rule?", isPresented: $showDeleteConfirmation, presenting: entryToDelete) { entry in
Button("Delete", role: .destructive) {
if let id = entry.id {
try? rulesRepo.deleteSSLEntry(id: id)
}
}
}
.task {
@@ -115,11 +152,19 @@ struct SSLProxyingListView: View {
struct DomainEntrySheet: View {
let title: String
let isInclude: Bool
let existingEntry: SSLProxyingEntry?
let onSave: (String) -> Void
@State private var domainPattern = ""
@Environment(\.dismiss) private var dismiss
init(title: String, isInclude: Bool, existingEntry: SSLProxyingEntry? = nil, onSave: @escaping (String) -> Void) {
self.title = title
self.isInclude = isInclude
self.existingEntry = existingEntry
self.onSave = onSave
}
var body: some View {
NavigationStack {
Form {
@@ -145,6 +190,11 @@ struct DomainEntrySheet: View {
.disabled(domainPattern.isEmpty)
}
}
.onAppear {
if let entry = existingEntry {
domainPattern = entry.domainPattern
}
}
}
}
}