diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift index 6d798fb..e8aed86 100644 --- a/Apple/App/BurrowApp.swift +++ b/Apple/App/BurrowApp.swift @@ -15,7 +15,7 @@ struct BurrowApp: App { var body: some Scene { WindowGroup { - TunnelView() + TunnelView(tunnel: Self.tunnel) } } } diff --git a/Apple/App/Menu/MenuView.swift b/Apple/App/Menu/MenuView.swift index 9d8fb31..56a7494 100644 --- a/Apple/App/Menu/MenuView.swift +++ b/Apple/App/Menu/MenuView.swift @@ -8,7 +8,7 @@ import SwiftUI struct MenuItemToggleView: View { - @ObservedObject var tunnel: Tunnel + var tunnel: Tunnel var body: some View { HStack { @@ -23,7 +23,6 @@ struct MenuItemToggleView: View { .padding(.horizontal, 4) .padding(10) .frame(minWidth: 300, minHeight: 32, maxHeight: 32) - .task { await tunnel.update() } } } diff --git a/Apple/App/NetworkExtension+Async.swift b/Apple/App/NetworkExtension+Async.swift index ba478f3..4833efb 100644 --- a/Apple/App/NetworkExtension+Async.swift +++ b/Apple/App/NetworkExtension+Async.swift @@ -2,13 +2,13 @@ import NetworkExtension extension NEVPNManager { func remove() async throws { - let _: Void = try await withUnsafeThrowingContinuation { continuation in + _ = try await withUnsafeThrowingContinuation { continuation in removeFromPreferences(completionHandler: completion(continuation)) } } func save() async throws { - let _: Void = try await withUnsafeThrowingContinuation { continuation in + _ = try await withUnsafeThrowingContinuation { continuation in saveToPreferences(completionHandler: completion(continuation)) } } @@ -18,13 +18,7 @@ extension NETunnelProviderManager { class var managers: [NETunnelProviderManager] { get async throws { try await withUnsafeThrowingContinuation { continuation in - loadAllFromPreferences { managers, error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: managers ?? []) - } - } + loadAllFromPreferences(completionHandler: completion(continuation)) } } } @@ -32,10 +26,20 @@ extension NETunnelProviderManager { private func completion(_ continuation: UnsafeContinuation) -> (Error?) -> Void { return { error in - if let error = error { + if let error { continuation.resume(throwing: error) } else { continuation.resume(returning: ()) } } } + +private func completion(_ continuation: UnsafeContinuation) -> (T?, Error?) -> Void { + return { value, error in + if let error { + continuation.resume(throwing: error) + } else if let value { + continuation.resume(returning: value) + } + } +} diff --git a/Apple/App/Tunnel.swift b/Apple/App/Tunnel.swift index e8bff22..0421a0c 100644 --- a/Apple/App/Tunnel.swift +++ b/Apple/App/Tunnel.swift @@ -2,15 +2,16 @@ import Combine import NetworkExtension import SwiftUI -@MainActor -class Tunnel: ObservableObject { - @Published private(set) var status: Status = .unknown - @Published private var error: NEVPNError? +@Observable class Tunnel { + private(set) var status: Status = .unknown + private var error: NEVPNError? private let bundleIdentifier: String private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void private var tasks: [Task] = [] + // Each manager corresponds to one entry in the Settings app. + // Our goal is to maintain a single manager, so we create one if none exist and delete extra if there are any. private var managers: [NEVPNManager]? { didSet { status = currentStatus } } @@ -48,24 +49,31 @@ class Tunnel: ObservableObject { self.bundleIdentifier = bundleIdentifier self.configure = configure + listenForUpdates() + Task { await update() } + } + + private func listenForUpdates() { + let center = NotificationCenter.default let statusTask = Task { - for try await _ in NotificationCenter.default.notifications(named: .NEVPNStatusDidChange) { + for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) { status = currentStatus } } let configurationTask = Task { - for try await _ in NotificationCenter.default.notifications(named: .NEVPNConfigurationChange) { + for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) { await update() } } tasks = [statusTask, configurationTask] } - func update() async { + private func update() async { do { - managers = try await NETunnelProviderManager.managers - } catch let error as NEVPNError { - self.error = error + let updated = try await NETunnelProviderManager.managers + await MainActor.run { managers = updated } + } catch let vpnError as NEVPNError { + error = vpnError } catch { print(error) } @@ -109,7 +117,9 @@ class Tunnel: ObservableObject { } deinit { - tasks.forEach { $0.cancel() } + for task in tasks { + task.cancel() + } } } diff --git a/Apple/App/TunnelView.swift b/Apple/App/TunnelView.swift index e3b9e28..dd91603 100644 --- a/Apple/App/TunnelView.swift +++ b/Apple/App/TunnelView.swift @@ -1,36 +1,34 @@ import SwiftUI struct TunnelView: View { -// @ObservedObject var tunnel: Tunnel + var tunnel: Tunnel var body: some View { - EmptyView() -// VStack { -// Text(verbatim: tunnel.status.description) -// switch tunnel.status { -// case .connected: -// Button("Disconnect", action: stop) -// case .permissionRequired: -// Button("Allow", action: configure) -// case .disconnected: -// Button("Start", action: start) -// default: -// EmptyView() -// } -// } -// .task { await tunnel.update() } -// .padding() + VStack { + Text(verbatim: tunnel.status.description) + switch tunnel.status { + case .connected: + Button("Disconnect", action: stop) + case .permissionRequired: + Button("Allow", action: configure) + case .disconnected: + Button("Start", action: start) + default: + EmptyView() + } + } + .padding() } -// private func start() { -// try? tunnel.start() -// } -// -// private func stop() { -// tunnel.stop() -// } -// -// private func configure() { -// Task { try await tunnel.configure() } -// } + private func start() { + try? tunnel.start() + } + + private func stop() { + tunnel.stop() + } + + private func configure() { + Task { try await tunnel.configure() } + } } diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index a8ff620..c0e4f09 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -196,7 +196,7 @@ buildRules = ( ); dependencies = ( - D0BCC6122A0B328800AD070D /* PBXTargetDependency */, + D08252712B5C3E2E005DA378 /* PBXTargetDependency */, ); name = NetworkExtension; productName = BurrowNetworkExtension; @@ -215,7 +215,7 @@ buildRules = ( ); dependencies = ( - D0BCC6142A0B329200AD070D /* PBXTargetDependency */, + D08252732B5C3E33005DA378 /* PBXTargetDependency */, D020F65C29E4A697002790F6 /* PBXTargetDependency */, ); name = App; @@ -231,7 +231,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; TargetAttributes = { D020F65229E4A697002790F6 = { CreatedOnToolsVersion = 14.3; @@ -251,6 +251,7 @@ ); mainGroup = D05B9F6929E39EEC008CB1F9; packageReferences = ( + D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */, ); productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */; projectDirPath = ""; @@ -337,13 +338,13 @@ target = D020F65229E4A697002790F6 /* NetworkExtension */; targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */; }; - D0BCC6122A0B328800AD070D /* PBXTargetDependency */ = { + D08252712B5C3E2E005DA378 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = D0BCC6112A0B328800AD070D /* SwiftLintPlugin */; + productRef = D08252702B5C3E2E005DA378 /* SwiftLintPlugin */; }; - D0BCC6142A0B329200AD070D /* PBXTargetDependency */ = { + D08252732B5C3E33005DA378 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = D0BCC6132A0B329200AD070D /* SwiftLintPlugin */; + productRef = D08252722B5C3E33005DA378 /* SwiftLintPlugin */; }; /* End PBXTargetDependency section */ @@ -423,25 +424,25 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */ = { + D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/SwiftLint.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.51.0; + minimumVersion = 0.54.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - D0BCC6112A0B328800AD070D /* SwiftLintPlugin */ = { + D08252702B5C3E2E005DA378 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */; + package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */; productName = "plugin:SwiftLintPlugin"; }; - D0BCC6132A0B329200AD070D /* SwiftLintPlugin */ = { + D08252722B5C3E33005DA378 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */; + package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */; productName = "plugin:SwiftLintPlugin"; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..7522840 --- /dev/null +++ b/Apple/Burrow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,86 @@ +{ + "pins" : [ + { + "identity" : "collectionconcurrencykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", + "state" : { + "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version" : "0.2.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" + } + }, + { + "identity" : "sourcekitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/SourceKitten.git", + "state" : { + "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", + "version" : "0.34.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a", + "version" : "1.2.2" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, + { + "identity" : "swiftlint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/SwiftLint.git", + "state" : { + "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", + "version" : "0.54.0" + } + }, + { + "identity" : "swiftytexttable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", + "state" : { + "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version" : "0.9.0" + } + }, + { + "identity" : "swxmlhash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/drmohundro/SWXMLHash.git", + "state" : { + "revision" : "4d0f62f561458cbe1f732171e625f03195151b60", + "version" : "7.0.1" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", + "version" : "5.0.6" + } + } + ], + "version" : 2 +} diff --git a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme index 7bb7808..c63f8e6 100644 --- a/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/Apple/Burrow.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -1,6 +1,6 @@