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:
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user