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

@@ -1,6 +1,6 @@
import SwiftUI
struct FilterChip: Identifiable {
struct FilterChip: Identifiable, Equatable {
let id = UUID()
let label: String
var isSelected: Bool = false
@@ -14,21 +14,37 @@ struct FilterChipsView: View {
HStack(spacing: 8) {
ForEach($chips) { $chip in
Button {
chip.isSelected.toggle()
withAnimation(.spring(response: 0.24, dampingFraction: 0.9)) {
chip.isSelected.toggle()
}
} label: {
Text(chip.label)
.font(.caption.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
chip.isSelected ? Color.accentColor : Color(.systemGray5),
in: Capsule()
)
.foregroundStyle(chip.isSelected ? .white : .primary)
HStack(spacing: 6) {
if chip.isSelected {
Image(systemName: "checkmark")
.font(.caption2.weight(.bold))
}
Text(chip.label)
.font(.caption.weight(.semibold))
}
.padding(.horizontal, 14)
.padding(.vertical, 9)
.background(
chip.isSelected ? Color.accentColor.opacity(0.16) : Color(.systemBackground),
in: Capsule()
)
.overlay(
Capsule()
.stroke(
chip.isSelected ? Color.accentColor.opacity(0.35) : Color.primary.opacity(0.06),
lineWidth: 1
)
)
.foregroundStyle(chip.isSelected ? Color.accentColor : .primary)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
.padding(.vertical, 2)
}
}
}

View File

@@ -0,0 +1,98 @@
import SwiftUI
struct HexView: View {
let data: Data
@State private var showAll = false
private var displayData: Data {
if showAll || data.count <= 1024 {
return data
}
return data.prefix(1024)
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
// Header
Text("Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ASCII")
.font(.system(.caption2, design: .monospaced))
.foregroundStyle(.secondary)
.padding(.bottom, 4)
// Hex rows
let bytes = [UInt8](displayData)
let rowCount = (bytes.count + 15) / 16
ForEach(0..<rowCount, id: \.self) { row in
hexRow(bytes: bytes, row: row)
}
// Show more
if !showAll && data.count > 1024 {
Button {
showAll = true
} label: {
Text("Show All (\(formatBytes(data.count)) total)")
.font(.system(.caption, design: .monospaced))
}
.padding(.top, 8)
}
}
.textSelection(.enabled)
}
private func hexRow(bytes: [UInt8], row: Int) -> some View {
let offset = row * 16
let end = min(offset + 16, bytes.count)
let rowBytes = Array(bytes[offset..<end])
return HStack(spacing: 0) {
// Offset
Text(String(format: "%08X ", offset))
.foregroundStyle(.secondary)
// Hex bytes
Text(hexString(rowBytes))
// ASCII
Text(" ")
Text(asciiString(rowBytes))
.foregroundStyle(.orange)
}
.font(.system(.caption2, design: .monospaced))
}
private func hexString(_ bytes: [UInt8]) -> String {
var result = ""
for i in 0..<16 {
if i < bytes.count {
result += String(format: "%02X ", bytes[i])
} else {
result += " "
}
if i == 7 {
result += " "
}
}
return result
}
private func asciiString(_ bytes: [UInt8]) -> String {
var result = ""
for byte in bytes {
if byte >= 0x20, byte <= 0x7E {
result += String(UnicodeScalar(byte))
} else {
result += "."
}
}
return result
}
private func formatBytes(_ count: Int) -> String {
if count < 1024 { return "\(count) B" }
if count < 1_048_576 { return String(format: "%.1f KB", Double(count) / 1024) }
return String(format: "%.1f MB", Double(count) / 1_048_576)
}
}

View File

@@ -0,0 +1,136 @@
import SwiftUI
struct JSONTreeView: View {
let data: Data
@State private var root: JSONNode?
var body: some View {
Group {
if let root {
JSONNodeExpandableView(
node: root,
collapseByDefault: false,
childCollapseThreshold: 50
)
} else {
Text("Invalid JSON")
.font(.system(.caption, design: .monospaced))
.foregroundStyle(.secondary)
}
}
.task {
root = parseJSON(data)
}
}
private func parseJSON(_ data: Data) -> JSONNode? {
guard let obj = try? JSONSerialization.jsonObject(with: data) else { return nil }
return buildNode(key: nil, value: obj)
}
private func buildNode(key: String?, value: Any) -> JSONNode {
if let dict = value as? [String: Any] {
let children = dict.sorted(by: { $0.key < $1.key }).map { buildNode(key: $0.key, value: $0.value) }
return JSONNode(key: key, kind: .object, displayValue: "{\(dict.count)}", children: children)
} else if let arr = value as? [Any] {
let children = arr.enumerated().map { buildNode(key: "[\($0.offset)]", value: $0.element) }
return JSONNode(key: key, kind: .array, displayValue: "[\(arr.count)]", children: children)
} else if let str = value as? String {
return JSONNode(key: key, kind: .string, displayValue: "\"\(str)\"", children: [])
} else if let num = value as? NSNumber {
if CFBooleanGetTypeID() == CFGetTypeID(num) {
let boolVal = num.boolValue
return JSONNode(key: key, kind: .bool, displayValue: boolVal ? "true" : "false", children: [])
}
return JSONNode(key: key, kind: .number, displayValue: "\(num)", children: [])
} else if value is NSNull {
return JSONNode(key: key, kind: .null, displayValue: "null", children: [])
} else {
return JSONNode(key: key, kind: .string, displayValue: "\(value)", children: [])
}
}
}
// MARK: - Model
private struct JSONNode: Identifiable {
let id = UUID()
let key: String?
let kind: JSONValueKind
let displayValue: String
let children: [JSONNode]
var childCount: Int {
children.count + children.reduce(0) { $0 + $1.childCount }
}
}
private enum JSONValueKind {
case object, array, string, number, bool, null
}
// MARK: - Expandable wrapper (allows per-node expand state)
private struct JSONNodeExpandableView: View {
let node: JSONNode
let collapseByDefault: Bool
let childCollapseThreshold: Int
@State private var isExpanded: Bool
init(node: JSONNode, collapseByDefault: Bool, childCollapseThreshold: Int) {
self.node = node
self.collapseByDefault = collapseByDefault
self.childCollapseThreshold = childCollapseThreshold
_isExpanded = State(initialValue: !collapseByDefault)
}
var body: some View {
if node.children.isEmpty {
leafRow
} else {
DisclosureGroup(isExpanded: $isExpanded) {
ForEach(node.children) { child in
JSONNodeExpandableView(
node: child,
collapseByDefault: child.childCount > childCollapseThreshold,
childCollapseThreshold: childCollapseThreshold
)
}
} label: {
labelRow(valueColor: .secondary)
}
}
}
private var leafRow: some View {
labelRow(valueColor: valueColor)
}
private func labelRow(valueColor: Color) -> some View {
HStack(alignment: .firstTextBaseline, spacing: 4) {
if let key = node.key {
Text(key + ":")
.font(.system(.caption, design: .monospaced))
.bold()
.foregroundStyle(.primary)
}
Text(node.displayValue)
.font(.system(.caption, design: .monospaced))
.foregroundStyle(valueColor)
.textSelection(.enabled)
}
}
private var valueColor: Color {
switch node.kind {
case .string: return .green
case .number: return .blue
case .bool: return .orange
case .null: return .gray
case .object, .array: return .secondary
}
}
}

View File

@@ -0,0 +1,34 @@
import SwiftUI
import UIKit
struct SelectableTextView: UIViewRepresentable {
let text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.isEditable = false
textView.isSelectable = true
textView.isScrollEnabled = false
textView.backgroundColor = .clear
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.adjustsFontForContentSizeCategory = true
textView.font = .monospacedSystemFont(ofSize: 12, weight: .regular)
textView.textColor = .label
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
if uiView.text != text {
uiView.text = text
}
}
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
let width = proposal.width ?? UIScreen.main.bounds.width
let fittingSize = CGSize(width: width, height: .greatestFiniteMagnitude)
let size = uiView.sizeThatFits(fittingSize)
return CGSize(width: width, height: ceil(size.height))
}
}