Initial project setup - Phases 1-3 complete
This commit is contained in:
103
App/AppState.swift
Normal file
103
App/AppState.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
import SwiftUI
|
||||
import NetworkExtension
|
||||
import ProxyCore
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
final class AppState {
|
||||
var vpnStatus: NEVPNStatus = .disconnected
|
||||
var isCertificateInstalled: Bool = false
|
||||
var isCertificateTrusted: Bool = false
|
||||
|
||||
private var vpnManager: NETunnelProviderManager?
|
||||
private var statusObservation: NSObjectProtocol?
|
||||
|
||||
init() {
|
||||
Task {
|
||||
await loadVPNManager()
|
||||
}
|
||||
}
|
||||
|
||||
func loadVPNManager() async {
|
||||
do {
|
||||
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
||||
if let existing = managers.first {
|
||||
vpnManager = existing
|
||||
} else {
|
||||
let manager = NETunnelProviderManager()
|
||||
let proto = NETunnelProviderProtocol()
|
||||
proto.providerBundleIdentifier = ProxyConstants.extensionBundleIdentifier
|
||||
proto.serverAddress = ProxyConstants.proxyHost
|
||||
manager.protocolConfiguration = proto
|
||||
manager.localizedDescription = "Proxy"
|
||||
manager.isEnabled = true
|
||||
try await manager.saveToPreferences()
|
||||
try await manager.loadFromPreferences()
|
||||
vpnManager = manager
|
||||
}
|
||||
observeVPNStatus()
|
||||
vpnStatus = vpnManager?.connection.status ?? .disconnected
|
||||
} catch {
|
||||
print("[AppState] Failed to load VPN manager: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func toggleVPN() async {
|
||||
guard let manager = vpnManager else {
|
||||
await loadVPNManager()
|
||||
return
|
||||
}
|
||||
|
||||
switch manager.connection.status {
|
||||
case .connected, .connecting:
|
||||
manager.connection.stopVPNTunnel()
|
||||
case .disconnected, .invalid:
|
||||
do {
|
||||
// Ensure saved and fresh before starting
|
||||
manager.isEnabled = true
|
||||
try await manager.saveToPreferences()
|
||||
try await manager.loadFromPreferences()
|
||||
try manager.connection.startVPNTunnel()
|
||||
} catch {
|
||||
print("[AppState] Failed to start VPN: \(error)")
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var isVPNConnected: Bool {
|
||||
vpnStatus == .connected
|
||||
}
|
||||
|
||||
var vpnStatusText: String {
|
||||
switch vpnStatus {
|
||||
case .connected: "Connected"
|
||||
case .connecting: "Connecting..."
|
||||
case .disconnecting: "Disconnecting..."
|
||||
case .disconnected: "Disconnected"
|
||||
case .invalid: "Not Configured"
|
||||
case .reasserting: "Reconnecting..."
|
||||
@unknown default: "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private func observeVPNStatus() {
|
||||
guard let manager = vpnManager else { return }
|
||||
|
||||
// Remove existing observer
|
||||
if let existing = statusObservation {
|
||||
NotificationCenter.default.removeObserver(existing)
|
||||
}
|
||||
|
||||
statusObservation = NotificationCenter.default.addObserver(
|
||||
forName: .NEVPNStatusDidChange,
|
||||
object: manager.connection,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.vpnStatus = manager.connection.status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
App/ContentView.swift
Normal file
47
App/ContentView.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(AppState.self) private var appState
|
||||
|
||||
enum Tab: Hashable {
|
||||
case home, pin, compose, more
|
||||
}
|
||||
|
||||
@State private var selectedTab: Tab = .home
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
NavigationStack {
|
||||
HomeView()
|
||||
}
|
||||
.tabItem {
|
||||
Label("Home", systemImage: "house.fill")
|
||||
}
|
||||
.tag(Tab.home)
|
||||
|
||||
NavigationStack {
|
||||
PinView()
|
||||
}
|
||||
.tabItem {
|
||||
Label("Pin", systemImage: "pin.fill")
|
||||
}
|
||||
.tag(Tab.pin)
|
||||
|
||||
NavigationStack {
|
||||
ComposeListView()
|
||||
}
|
||||
.tabItem {
|
||||
Label("Compose", systemImage: "square.and.pencil")
|
||||
}
|
||||
.tag(Tab.compose)
|
||||
|
||||
NavigationStack {
|
||||
MoreView()
|
||||
}
|
||||
.tabItem {
|
||||
Label("More", systemImage: "ellipsis.circle.fill")
|
||||
}
|
||||
.tag(Tab.more)
|
||||
}
|
||||
}
|
||||
}
|
||||
14
App/Entitlements/ProxyApp.entitlements
Normal file
14
App/Entitlements/ProxyApp.entitlements
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.treyt.proxyapp</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
35
App/Info.plist
Normal file
35
App/Info.plist
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Proxy</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
13
App/ProxyApp.swift
Normal file
13
App/ProxyApp.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct ProxyApp: App {
|
||||
@State private var appState = AppState()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(appState)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user