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

@@ -14,6 +14,10 @@ public final class ComposeRepository: Sendable {
}
}
public func fetch(id: Int64) throws -> ComposeRequest? {
try db.dbPool.read { db in try ComposeRequest.fetchOne(db, id: id) }
}
public func insert(_ request: inout ComposeRequest) throws {
try db.dbPool.write { db in try request.insert(db) }
}

View File

@@ -0,0 +1,41 @@
import Foundation
import GRDB
public final class ConfigurationRepository: Sendable {
private let db: DatabaseManager
public init(db: DatabaseManager = .shared) {
self.db = db
}
public func observeConfiguration() -> ValueObservation<ValueReducers.Fetch<ProxyConfiguration>> {
ValueObservation.tracking { db in
try ProxyConfiguration.fetchOne(db, key: 1) ?? ProxyConfiguration()
}
}
public func current() throws -> ProxyConfiguration {
try db.dbPool.read { db in
try ProxyConfiguration.fetchOne(db, key: 1) ?? ProxyConfiguration()
}
}
public func update(_ mutate: (inout ProxyConfiguration) -> Void) throws {
try db.dbPool.write { db in
var configuration = try fetchOrCreate(in: db)
mutate(&configuration)
configuration.updatedAt = Date().timeIntervalSince1970
try configuration.save(db)
}
}
private func fetchOrCreate(in db: Database) throws -> ProxyConfiguration {
if let configuration = try ProxyConfiguration.fetchOne(db, key: 1) {
return configuration
}
var configuration = ProxyConfiguration()
try configuration.insert(db)
return configuration
}
}

View File

@@ -0,0 +1,63 @@
import Foundation
import GRDB
public final class PinnedDomainRepository: Sendable {
private let db: DatabaseManager
public init(db: DatabaseManager = .shared) {
self.db = db
}
/// Check if a domain (or any parent wildcard) is pinned.
public func isPinned(domain: String) -> Bool {
do {
return try db.dbPool.read { db in
try PinnedDomain.filter(Column("domain") == domain).fetchCount(db) > 0
}
} catch {
return false
}
}
/// Record a domain as pinned after a TLS handshake failure.
public func markPinned(domain: String, reason: String) {
do {
try db.dbPool.write { db in
// Use INSERT OR IGNORE since domain has UNIQUE constraint
try db.execute(
sql: "INSERT OR IGNORE INTO pinned_domains (domain, reason, detectedAt) VALUES (?, ?, ?)",
arguments: [domain, reason, Date().timeIntervalSince1970]
)
}
ProxyLogger.mitm.info("Marked domain as PINNED: \(domain) reason=\(reason)")
} catch {
ProxyLogger.mitm.error("Failed to mark pinned domain: \(error.localizedDescription)")
}
}
/// Remove a domain from the pinned list (user override).
public func unpin(domain: String) throws {
try db.dbPool.write { db in
_ = try PinnedDomain.filter(Column("domain") == domain).deleteAll(db)
}
}
/// Get all pinned domains.
public func fetchAll() throws -> [PinnedDomain] {
try db.dbPool.read { db in
try PinnedDomain.order(Column("detectedAt").desc).fetchAll(db)
}
}
public func observeAll() -> ValueObservation<ValueReducers.Fetch<[PinnedDomain]>> {
ValueObservation.tracking { db in
try PinnedDomain.order(Column("detectedAt").desc).fetchAll(db)
}
}
public func deleteAll() throws {
try db.dbPool.write { db in
_ = try PinnedDomain.deleteAll(db)
}
}
}

View File

@@ -26,6 +26,10 @@ public final class RulesRepository: Sendable {
try db.dbPool.write { db in try entry.insert(db) }
}
public func updateSSLEntry(_ entry: SSLProxyingEntry) throws {
try db.dbPool.write { db in try entry.update(db) }
}
public func deleteSSLEntry(id: Int64) throws {
try db.dbPool.write { db in _ = try SSLProxyingEntry.deleteOne(db, id: id) }
}
@@ -34,6 +38,12 @@ public final class RulesRepository: Sendable {
try db.dbPool.write { db in _ = try SSLProxyingEntry.deleteAll(db) }
}
public func fetchEnabledBlockEntries() throws -> [BlockListEntry] {
try db.dbPool.read { db in
try BlockListEntry.filter(Column("isEnabled") == true).fetchAll(db)
}
}
// MARK: - Block List
public func observeBlockListEntries() -> ValueObservation<ValueReducers.Fetch<[BlockListEntry]>> {
@@ -82,6 +92,12 @@ public final class RulesRepository: Sendable {
try db.dbPool.write { db in _ = try BreakpointRule.deleteAll(db) }
}
public func fetchEnabledMapLocalRules() throws -> [MapLocalRule] {
try db.dbPool.read { db in
try MapLocalRule.filter(Column("isEnabled") == true).fetchAll(db)
}
}
// MARK: - Map Local Rules
public func observeMapLocalRules() -> ValueObservation<ValueReducers.Fetch<[MapLocalRule]>> {
@@ -106,6 +122,12 @@ public final class RulesRepository: Sendable {
try db.dbPool.write { db in _ = try MapLocalRule.deleteAll(db) }
}
public func fetchEnabledDNSSpoofRules() throws -> [DNSSpoofRule] {
try db.dbPool.read { db in
try DNSSpoofRule.filter(Column("isEnabled") == true).fetchAll(db)
}
}
// MARK: - DNS Spoof Rules
public func observeDNSSpoofRules() -> ValueObservation<ValueReducers.Fetch<[DNSSpoofRule]>> {
@@ -118,6 +140,10 @@ public final class RulesRepository: Sendable {
try db.dbPool.write { db in try rule.insert(db) }
}
public func updateDNSSpoofRule(_ rule: DNSSpoofRule) throws {
try db.dbPool.write { db in try rule.update(db) }
}
public func deleteDNSSpoofRule(id: Int64) throws {
try db.dbPool.write { db in _ = try DNSSpoofRule.deleteOne(db, id: id) }
}

View File

@@ -0,0 +1,45 @@
import Foundation
import GRDB
public final class RuntimeStatusRepository: Sendable {
private let db: DatabaseManager
public init(db: DatabaseManager = .shared) {
self.db = db
}
public func observeStatus() -> ValueObservation<ValueReducers.Fetch<ProxyRuntimeStatus>> {
ValueObservation.tracking { db in
try ProxyRuntimeStatus.fetchOne(db, key: 1) ?? ProxyRuntimeStatus()
}
}
public func current() throws -> ProxyRuntimeStatus {
try db.dbPool.read { db in
try ProxyRuntimeStatus.fetchOne(db, key: 1) ?? ProxyRuntimeStatus()
}
}
public func update(_ mutate: (inout ProxyRuntimeStatus) -> Void) {
do {
try db.dbPool.write { db in
var status = try fetchOrCreate(in: db)
mutate(&status)
status.updatedAt = Date().timeIntervalSince1970
try status.save(db)
}
} catch {
ProxyLogger.db.error("RuntimeStatusRepository update failed: \(error.localizedDescription)")
}
}
private func fetchOrCreate(in db: Database) throws -> ProxyRuntimeStatus {
if let status = try ProxyRuntimeStatus.fetchOne(db, key: 1) {
return status
}
var status = ProxyRuntimeStatus()
try status.insert(db)
return status
}
}