Update Tunnel on the main thread
Also updated it to use the new Swift Observable macro
This commit is contained in:
parent
b008762a5b
commit
2b9ecb7b6a
10 changed files with 167 additions and 69 deletions
|
|
@ -15,7 +15,7 @@ struct BurrowApp: App {
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
TunnelView()
|
TunnelView(tunnel: Self.tunnel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MenuItemToggleView: View {
|
struct MenuItemToggleView: View {
|
||||||
@ObservedObject var tunnel: Tunnel
|
var tunnel: Tunnel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -23,7 +23,6 @@ struct MenuItemToggleView: View {
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
||||||
.task { await tunnel.update() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ import NetworkExtension
|
||||||
|
|
||||||
extension NEVPNManager {
|
extension NEVPNManager {
|
||||||
func remove() async throws {
|
func remove() async throws {
|
||||||
let _: Void = try await withUnsafeThrowingContinuation { continuation in
|
_ = try await withUnsafeThrowingContinuation { continuation in
|
||||||
removeFromPreferences(completionHandler: completion(continuation))
|
removeFromPreferences(completionHandler: completion(continuation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func save() async throws {
|
func save() async throws {
|
||||||
let _: Void = try await withUnsafeThrowingContinuation { continuation in
|
_ = try await withUnsafeThrowingContinuation { continuation in
|
||||||
saveToPreferences(completionHandler: completion(continuation))
|
saveToPreferences(completionHandler: completion(continuation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,13 +18,7 @@ extension NETunnelProviderManager {
|
||||||
class var managers: [NETunnelProviderManager] {
|
class var managers: [NETunnelProviderManager] {
|
||||||
get async throws {
|
get async throws {
|
||||||
try await withUnsafeThrowingContinuation { continuation in
|
try await withUnsafeThrowingContinuation { continuation in
|
||||||
loadAllFromPreferences { managers, error in
|
loadAllFromPreferences(completionHandler: completion(continuation))
|
||||||
if let error = error {
|
|
||||||
continuation.resume(throwing: error)
|
|
||||||
} else {
|
|
||||||
continuation.resume(returning: managers ?? [])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,10 +26,20 @@ extension NETunnelProviderManager {
|
||||||
|
|
||||||
private func completion(_ continuation: UnsafeContinuation<Void, Error>) -> (Error?) -> Void {
|
private func completion(_ continuation: UnsafeContinuation<Void, Error>) -> (Error?) -> Void {
|
||||||
return { error in
|
return { error in
|
||||||
if let error = error {
|
if let error {
|
||||||
continuation.resume(throwing: error)
|
continuation.resume(throwing: error)
|
||||||
} else {
|
} else {
|
||||||
continuation.resume(returning: ())
|
continuation.resume(returning: ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func completion<T>(_ continuation: UnsafeContinuation<T, Error>) -> (T?, Error?) -> Void {
|
||||||
|
return { value, error in
|
||||||
|
if let error {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
} else if let value {
|
||||||
|
continuation.resume(returning: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@ import Combine
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@Observable class Tunnel {
|
||||||
class Tunnel: ObservableObject {
|
private(set) var status: Status = .unknown
|
||||||
@Published private(set) var status: Status = .unknown
|
private var error: NEVPNError?
|
||||||
@Published private var error: NEVPNError?
|
|
||||||
|
|
||||||
private let bundleIdentifier: String
|
private let bundleIdentifier: String
|
||||||
private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void
|
private let configure: (NETunnelProviderManager, NETunnelProviderProtocol) -> Void
|
||||||
private var tasks: [Task<Void, Error>] = []
|
private var tasks: [Task<Void, Error>] = []
|
||||||
|
|
||||||
|
// 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]? {
|
private var managers: [NEVPNManager]? {
|
||||||
didSet { status = currentStatus }
|
didSet { status = currentStatus }
|
||||||
}
|
}
|
||||||
|
|
@ -48,24 +49,31 @@ class Tunnel: ObservableObject {
|
||||||
self.bundleIdentifier = bundleIdentifier
|
self.bundleIdentifier = bundleIdentifier
|
||||||
self.configure = configure
|
self.configure = configure
|
||||||
|
|
||||||
|
listenForUpdates()
|
||||||
|
Task { await update() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func listenForUpdates() {
|
||||||
|
let center = NotificationCenter.default
|
||||||
let statusTask = Task {
|
let statusTask = Task {
|
||||||
for try await _ in NotificationCenter.default.notifications(named: .NEVPNStatusDidChange) {
|
for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
|
||||||
status = currentStatus
|
status = currentStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let configurationTask = Task {
|
let configurationTask = Task {
|
||||||
for try await _ in NotificationCenter.default.notifications(named: .NEVPNConfigurationChange) {
|
for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
|
||||||
await update()
|
await update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tasks = [statusTask, configurationTask]
|
tasks = [statusTask, configurationTask]
|
||||||
}
|
}
|
||||||
|
|
||||||
func update() async {
|
private func update() async {
|
||||||
do {
|
do {
|
||||||
managers = try await NETunnelProviderManager.managers
|
let updated = try await NETunnelProviderManager.managers
|
||||||
} catch let error as NEVPNError {
|
await MainActor.run { managers = updated }
|
||||||
self.error = error
|
} catch let vpnError as NEVPNError {
|
||||||
|
error = vpnError
|
||||||
} catch {
|
} catch {
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +117,9 @@ class Tunnel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
tasks.forEach { $0.cancel() }
|
for task in tasks {
|
||||||
|
task.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,34 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TunnelView: View {
|
struct TunnelView: View {
|
||||||
// @ObservedObject var tunnel: Tunnel
|
var tunnel: Tunnel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
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()
|
EmptyView()
|
||||||
// VStack {
|
}
|
||||||
// Text(verbatim: tunnel.status.description)
|
}
|
||||||
// switch tunnel.status {
|
.padding()
|
||||||
// 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private func start() {
|
private func start() {
|
||||||
// try? tunnel.start()
|
try? tunnel.start()
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func stop() {
|
private func stop() {
|
||||||
// tunnel.stop()
|
tunnel.stop()
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func configure() {
|
private func configure() {
|
||||||
// Task { try await tunnel.configure() }
|
Task { try await tunnel.configure() }
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D0BCC6122A0B328800AD070D /* PBXTargetDependency */,
|
D08252712B5C3E2E005DA378 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = NetworkExtension;
|
name = NetworkExtension;
|
||||||
productName = BurrowNetworkExtension;
|
productName = BurrowNetworkExtension;
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D0BCC6142A0B329200AD070D /* PBXTargetDependency */,
|
D08252732B5C3E33005DA378 /* PBXTargetDependency */,
|
||||||
D020F65C29E4A697002790F6 /* PBXTargetDependency */,
|
D020F65C29E4A697002790F6 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = App;
|
name = App;
|
||||||
|
|
@ -231,7 +231,7 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1430;
|
LastSwiftUpdateCheck = 1430;
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
D020F65229E4A697002790F6 = {
|
D020F65229E4A697002790F6 = {
|
||||||
CreatedOnToolsVersion = 14.3;
|
CreatedOnToolsVersion = 14.3;
|
||||||
|
|
@ -251,6 +251,7 @@
|
||||||
);
|
);
|
||||||
mainGroup = D05B9F6929E39EEC008CB1F9;
|
mainGroup = D05B9F6929E39EEC008CB1F9;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */,
|
||||||
);
|
);
|
||||||
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
|
productRefGroup = D05B9F7329E39EEC008CB1F9 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
|
@ -337,13 +338,13 @@
|
||||||
target = D020F65229E4A697002790F6 /* NetworkExtension */;
|
target = D020F65229E4A697002790F6 /* NetworkExtension */;
|
||||||
targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */;
|
targetProxy = D020F65B29E4A697002790F6 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
D0BCC6122A0B328800AD070D /* PBXTargetDependency */ = {
|
D08252712B5C3E2E005DA378 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
productRef = D0BCC6112A0B328800AD070D /* SwiftLintPlugin */;
|
productRef = D08252702B5C3E2E005DA378 /* SwiftLintPlugin */;
|
||||||
};
|
};
|
||||||
D0BCC6142A0B329200AD070D /* PBXTargetDependency */ = {
|
D08252732B5C3E33005DA378 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
productRef = D0BCC6132A0B329200AD070D /* SwiftLintPlugin */;
|
productRef = D08252722B5C3E33005DA378 /* SwiftLintPlugin */;
|
||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
|
@ -423,25 +424,25 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
|
D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/realm/SwiftLint.git";
|
repositoryURL = "https://github.com/realm/SwiftLint.git";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 0.51.0;
|
minimumVersion = 0.54.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
D0BCC6112A0B328800AD070D /* SwiftLintPlugin */ = {
|
D08252702B5C3E2E005DA378 /* SwiftLintPlugin */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */;
|
package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
|
||||||
productName = "plugin:SwiftLintPlugin";
|
productName = "plugin:SwiftLintPlugin";
|
||||||
};
|
};
|
||||||
D0BCC6132A0B329200AD070D /* SwiftLintPlugin */ = {
|
D08252722B5C3E33005DA378 /* SwiftLintPlugin */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D0BCC6102A0B327700AD070D /* XCRemoteSwiftPackageReference "SwiftLint" */;
|
package = D082526F2B5C3E23005DA378 /* XCRemoteSwiftPackageReference "SwiftLint" */;
|
||||||
productName = "plugin:SwiftLintPlugin";
|
productName = "plugin:SwiftLintPlugin";
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ SDKROOT = auto
|
||||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES
|
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES
|
||||||
|
|
||||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
||||||
MACOSX_DEPLOYMENT_TARGET = 12.0
|
MACOSX_DEPLOYMENT_TARGET = 14.0
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO
|
||||||
SUPPORTS_MACCATALYST = NO
|
SUPPORTS_MACCATALYST = NO
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue