Fix UI issues and Update Version Number

This commit is contained in:
Stossy11
2025-10-03 10:54:04 +10:00
parent 47f553ed27
commit a8e60f27b2
3 changed files with 238 additions and 128 deletions

View File

@@ -1,7 +1,7 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
MARKETING_VERSION = 1.2
MARKETING_VERSION = 1.2.1
CURRENT_PROJECT_VERSION = 1
// Vars to be overwritten by `CodeSigning.xcconfig` if exists

View File

@@ -44,6 +44,7 @@ class TunnelManager: ObservableObject {
@Published var waitingOnSettings: Bool = false
@Published var vpnManager: NETunnelProviderManager?
private var vpnObserver: NSObjectProtocol?
private var isProcessingStatusChange = false
private var tunnelDeviceIp: String {
UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0"
@@ -105,8 +106,8 @@ class TunnelManager: ObservableObject {
}
private init() {
loadTunnelPreferences()
setupStatusObserver()
loadTunnelPreferences()
}
// MARK: - Private Methods
@@ -118,32 +119,29 @@ class TunnelManager: ObservableObject {
if let error = error {
VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)")
self.tunnelStatus = .error
self.waitingOnSettings = true
return
}
defer {
self.waitingOnSettings = true
}
self.hasLocalDeviceSupport = true
self.waitingOnSettings = true
if let managers = managers, !managers.isEmpty {
var stosManagers = [NETunnelProviderManager]()
for manager in managers {
if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol,
proto.providerBundleIdentifier == self.tunnelBundleId {
stosManagers.append(manager)
let stosManagers = managers.filter { manager in
guard let proto = manager.protocolConfiguration as? NETunnelProviderProtocol else {
return false
}
return proto.providerBundleIdentifier == self.tunnelBundleId
}
if !stosManagers.isEmpty {
if stosManagers.count > 1 {
self.cleanupDuplicateManagers(stosManagers)
} else {
self.vpnManager = stosManagers.first
self.updateTunnelStatus(from: stosManagers.first!.connection.status)
VPNLogger.shared.log("Loaded existing StosVPN tunnel configuration")
} else if let manager = stosManagers.first {
self.vpnManager = manager
let currentStatus = manager.connection.status
VPNLogger.shared.log("Loaded existing StosVPN tunnel configuration with status: \(currentStatus.rawValue)")
self.updateTunnelStatus(from: currentStatus)
}
} else {
VPNLogger.shared.log("No StosVPN tunnel configuration found")
@@ -163,19 +161,18 @@ class TunnelManager: ObservableObject {
}
let managerToKeep = activeManager ?? managers.first!
self.vpnManager = managerToKeep
self.updateTunnelStatus(from: managerToKeep.connection.status)
var removedCount = 0
for manager in managers {
if manager != managerToKeep {
manager.removeFromPreferences { error in
if let error = error {
VPNLogger.shared.log("Error removing duplicate VPN: \(error.localizedDescription)")
} else {
removedCount += 1
VPNLogger.shared.log("Successfully removed duplicate VPN configuration")
}
DispatchQueue.main.async { [weak self] in
self?.vpnManager = managerToKeep
self?.updateTunnelStatus(from: managerToKeep.connection.status)
}
for manager in managers where manager != managerToKeep {
manager.removeFromPreferences { error in
if let error = error {
VPNLogger.shared.log("Error removing duplicate VPN: \(error.localizedDescription)")
} else {
VPNLogger.shared.log("Successfully removed duplicate VPN configuration")
}
}
}
@@ -187,9 +184,14 @@ class TunnelManager: ObservableObject {
object: nil,
queue: .main
) { [weak self] notification in
guard let self = self,
let connection = notification.object as? NEVPNConnection else {
return
guard let self = self else { return }
guard let connection = notification.object as? NEVPNConnection else { return }
VPNLogger.shared.log("VPN Status notification received: \(connection.status.rawValue)")
// Update status immediately if it's our manager
if let manager = self.vpnManager, connection == manager.connection {
self.updateTunnelStatus(from: connection.status)
}
self.handleVPNStatusChange(notification: notification)
@@ -197,29 +199,37 @@ class TunnelManager: ObservableObject {
}
private func updateTunnelStatus(from connectionStatus: NEVPNStatus) {
DispatchQueue.main.async {
switch connectionStatus {
case .invalid, .disconnected:
self.tunnelStatus = .disconnected
case .connecting:
self.tunnelStatus = .connecting
case .connected:
self.tunnelStatus = .connected
case .disconnecting:
self.tunnelStatus = .disconnecting
case .reasserting:
self.tunnelStatus = .connecting
@unknown default:
self.tunnelStatus = .error
}
let newStatus: TunnelStatus
switch connectionStatus {
case .invalid, .disconnected:
newStatus = .disconnected
case .connecting:
newStatus = .connecting
case .connected:
newStatus = .connected
case .disconnecting:
newStatus = .disconnecting
case .reasserting:
newStatus = .connecting
@unknown default:
newStatus = .error
}
VPNLogger.shared.log("StosVPN status updated: \(self.tunnelStatus)")
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if self.tunnelStatus != newStatus {
VPNLogger.shared.log("StosVPN status updated from \(self.tunnelStatus) to \(newStatus)")
}
self.tunnelStatus = newStatus
}
}
private func createStosVPNConfiguration(completion: @escaping (NETunnelProviderManager?) -> Void) {
NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in
guard let self = self else { return }
guard let self = self else {
completion(nil)
return
}
if let error = error {
VPNLogger.shared.log("Error checking existing VPN configurations: \(error.localizedDescription)")
@@ -229,16 +239,15 @@ class TunnelManager: ObservableObject {
if let managers = managers {
let stosManagers = managers.filter { manager in
if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol {
return proto.providerBundleIdentifier == self.tunnelBundleId
guard let proto = manager.protocolConfiguration as? NETunnelProviderProtocol else {
return false
}
return false
return proto.providerBundleIdentifier == self.tunnelBundleId
}
if !stosManagers.isEmpty {
let manager = stosManagers.first!
if let existingManager = stosManagers.first {
VPNLogger.shared.log("Found existing StosVPN configuration, using it instead of creating new one")
completion(manager)
completion(existingManager)
return
}
}
@@ -291,15 +300,13 @@ class TunnelManager: ObservableObject {
}
let activeManager = managers.first { manager in
return manager.connection.status == .connected ||
manager.connection.status == .connecting
manager.connection.status == .connected || manager.connection.status == .connecting
}
completion(activeManager)
}
}
// MARK: - Public Methods
func toggleVPNConnection() {
@@ -311,6 +318,27 @@ class TunnelManager: ObservableObject {
}
func startVPN() {
if let manager = vpnManager {
let currentStatus = manager.connection.status
VPNLogger.shared.log("Current manager status: \(currentStatus.rawValue)")
if currentStatus == .connected {
VPNLogger.shared.log("VPN already connected, updating UI")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connected
}
return
}
if currentStatus == .connecting {
VPNLogger.shared.log("VPN already connecting, updating UI")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connecting
}
return
}
}
getActiveVPNManager { [weak self] activeManager in
guard let self = self else { return }
@@ -329,71 +357,123 @@ class TunnelManager: ObservableObject {
private func initializeAndStartStosVPN() {
if let manager = vpnManager {
startExistingVPN(manager: manager)
} else {
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
manager.loadFromPreferences { [weak self] error in
guard let self = self else { return }
if let error = error {
VPNLogger.shared.log("Error reloading VPN configurations: \(error.localizedDescription)")
self.createStosVPNConfiguration { manager in
guard let manager = manager else { return }
self.vpnManager = manager
VPNLogger.shared.log("Error reloading manager: \(error.localizedDescription)")
self.createAndStartVPN()
return
}
self.startExistingVPN(manager: manager)
}
} else {
createAndStartVPN()
}
}
private func createAndStartVPN() {
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
guard let self = self else { return }
if let error = error {
VPNLogger.shared.log("Error reloading VPN configurations: \(error.localizedDescription)")
}
if let managers = managers {
let stosManagers = managers.filter { manager in
guard let proto = manager.protocolConfiguration as? NETunnelProviderProtocol else {
return false
}
return proto.providerBundleIdentifier == self.tunnelBundleId
}
if !stosManagers.isEmpty {
DispatchQueue.main.async { [weak self] in
self?.vpnManager = stosManagers.first
}
if stosManagers.count > 1 {
self.cleanupDuplicateManagers(stosManagers)
}
if let manager = stosManagers.first {
self.startExistingVPN(manager: manager)
}
return
}
}
if let managers = managers {
let stosManagers = managers.filter { manager in
if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol {
return proto.providerBundleIdentifier == self.tunnelBundleId
}
return false
}
if !stosManagers.isEmpty {
self.vpnManager = stosManagers.first
if stosManagers.count > 1 {
self.cleanupDuplicateManagers(stosManagers)
}
self.startExistingVPN(manager: stosManagers.first!)
return
}
}
self.createStosVPNConfiguration { manager in
guard let manager = manager else { return }
self.vpnManager = manager
self.startExistingVPN(manager: manager)
self.createStosVPNConfiguration { [weak self] manager in
guard let self = self, let manager = manager else { return }
DispatchQueue.main.async { [weak self] in
self?.vpnManager = manager
}
self.startExistingVPN(manager: manager)
}
}
}
private func startExistingVPN(manager: NETunnelProviderManager) {
guard tunnelStatus != .connected else {
// First check the actual current status
let currentStatus = manager.connection.status
VPNLogger.shared.log("Current VPN status before start attempt: \(currentStatus.rawValue)")
if currentStatus == .connected {
VPNLogger.shared.log("StosVPN tunnel is already connected")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connected
}
return
}
if currentStatus == .connecting {
VPNLogger.shared.log("StosVPN tunnel is already connecting")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connecting
}
return
}
manager.isEnabled = true
manager.saveToPreferences { error in
manager.saveToPreferences { [weak self] error in
guard let self = self else { return }
if let error = error {
VPNLogger.shared.log(error.localizedDescription)
VPNLogger.shared.log("Error saving preferences: \(error.localizedDescription)")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .error
}
return
}
// Reload it to apply
manager.loadFromPreferences { error in
manager.loadFromPreferences { [weak self] error in
guard let self = self else { return }
if let error = error {
VPNLogger.shared.log(error.localizedDescription)
VPNLogger.shared.log("Error reloading preferences: \(error.localizedDescription)")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .error
}
return
}
self.tunnelStatus = .connecting
// Check status again after reload
let statusAfterReload = manager.connection.status
VPNLogger.shared.log("VPN status after reload: \(statusAfterReload.rawValue)")
if statusAfterReload == .connected {
VPNLogger.shared.log("VPN is already connected after reload")
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connected
}
return
}
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .connecting
}
let options: [String: NSObject] = [
"TunnelDeviceIP": self.tunnelDeviceIp as NSObject,
@@ -405,7 +485,9 @@ class TunnelManager: ObservableObject {
try manager.connection.startVPNTunnel(options: options)
VPNLogger.shared.log("StosVPN tunnel start initiated")
} catch {
self.tunnelStatus = .error
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .error
}
VPNLogger.shared.log("Failed to start StosVPN tunnel: \(error.localizedDescription)")
}
}
@@ -413,9 +495,15 @@ class TunnelManager: ObservableObject {
}
func stopVPN() {
guard let manager = vpnManager else { return }
guard let manager = vpnManager else {
VPNLogger.shared.log("No VPN manager available to stop")
return
}
DispatchQueue.main.async { [weak self] in
self?.tunnelStatus = .disconnecting
}
tunnelStatus = .disconnecting
manager.connection.stopVPNTunnel()
VPNLogger.shared.log("StosVPN tunnel stop initiated")
@@ -425,9 +513,12 @@ class TunnelManager: ObservableObject {
func handleVPNStatusChange(notification: Notification) {
guard let connection = notification.object as? NEVPNConnection else { return }
VPNLogger.shared.log("Handling VPN status change: \(connection.status.rawValue)")
// Always update status if it's our manager's connection
if let manager = vpnManager, connection == manager.connection {
VPNLogger.shared.log("Status change is for our StosVPN manager")
updateTunnelStatus(from: connection.status)
return
}
if connection.status == .disconnected &&
@@ -437,23 +528,40 @@ class TunnelManager: ObservableObject {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.initializeAndStartStosVPN()
}
return
}
// Check if this is a different StosVPN manager (perhaps a duplicate)
// This helps discover duplicates created by other means
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
// Prevent recursive calls when checking for duplicates
guard !isProcessingStatusChange else { return }
isProcessingStatusChange = true
// Check for duplicates asynchronously without blocking
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self = self else { return }
if let managers = managers, !managers.isEmpty {
let stosManagers = managers.filter { manager in
if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol {
return proto.providerBundleIdentifier == self.tunnelBundleId
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
guard let self = self, let managers = managers, !managers.isEmpty else {
DispatchQueue.main.async { [weak self] in
self?.isProcessingStatusChange = false
}
return false
return
}
let stosManagers = managers.filter { manager in
guard let proto = manager.protocolConfiguration as? NETunnelProviderProtocol else {
return false
}
return proto.providerBundleIdentifier == self.tunnelBundleId
}
if stosManagers.count > 1 {
self.cleanupDuplicateManagers(stosManagers)
DispatchQueue.main.async { [weak self] in
self?.cleanupDuplicateManagers(stosManagers)
}
}
DispatchQueue.main.async { [weak self] in
self?.isProcessingStatusChange = false
}
}
}
@@ -462,7 +570,9 @@ class TunnelManager: ObservableObject {
// MARK: - Cleanup Utilities
func cleanupAllVPNConfigurations() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
guard let self = self else { return }
if let error = error {
VPNLogger.shared.log("Error loading VPN configurations for cleanup: \(error.localizedDescription)")
return
@@ -471,28 +581,28 @@ class TunnelManager: ObservableObject {
guard let managers = managers else { return }
for manager in managers {
if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol,
proto.providerBundleIdentifier == self.tunnelBundleId {
guard let proto = manager.protocolConfiguration as? NETunnelProviderProtocol,
proto.providerBundleIdentifier == self.tunnelBundleId else {
continue
}
// If connected, disconnect first
if manager.connection.status == .connected ||
manager.connection.status == .connecting {
manager.connection.stopVPNTunnel()
}
if manager.connection.status == .connected || manager.connection.status == .connecting {
manager.connection.stopVPNTunnel()
}
manager.removeFromPreferences { error in
if let error = error {
VPNLogger.shared.log("Error removing VPN configuration: \(error.localizedDescription)")
} else {
VPNLogger.shared.log("Successfully removed VPN configuration")
}
manager.removeFromPreferences { error in
if let error = error {
VPNLogger.shared.log("Error removing VPN configuration: \(error.localizedDescription)")
} else {
VPNLogger.shared.log("Successfully removed VPN configuration")
}
}
}
self.vpnManager = nil
self.tunnelStatus = .disconnected
DispatchQueue.main.async { [weak self] in
self?.vpnManager = nil
self?.tunnelStatus = .disconnected
}
}
}