Make VPN public methods async
- With Swift Concurrency - Raise targets to iOS 13 / macOS 10.15
This commit is contained in:
parent
990a0b85a6
commit
e12e0b3051
|
@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Manager package completely rewritten.
|
- Manager package completely rewritten with Swift Concurrency.
|
||||||
- WireGuard: Use entities from WireGuardKit directly.
|
- WireGuard: Use entities from WireGuardKit directly.
|
||||||
- Only enable on-demand if at least one rule is provided.
|
- Only enable on-demand if at least one rule is provided.
|
||||||
- Dropped incomplete support for IPSec/IKEv2.
|
- Dropped incomplete support for IPSec/IKEv2.
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.0.0</string>
|
<string>5.0.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.0.0</string>
|
<string>5.0.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
|
@ -125,17 +125,18 @@ class OpenVPNViewController: UIViewController {
|
||||||
|
|
||||||
var extra = NetworkExtensionExtra()
|
var extra = NetworkExtensionExtra()
|
||||||
extra.passwordReference = passwordReference
|
extra.passwordReference = passwordReference
|
||||||
|
|
||||||
vpn.reconnect(
|
vpn.reconnect(
|
||||||
tunnelIdentifier,
|
tunnelIdentifier,
|
||||||
configuration: cfg!,
|
configuration: cfg!,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
delay: nil
|
after: .seconds(2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect() {
|
func disconnect() {
|
||||||
vpn.disconnect()
|
Task {
|
||||||
|
await vpn.disconnect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func displayLog() {
|
@IBAction func displayLog() {
|
||||||
|
|
|
@ -72,7 +72,9 @@ class WireGuardViewController: UIViewController {
|
||||||
object: nil
|
object: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
vpn.prepare()
|
Task {
|
||||||
|
await vpn.prepare()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func connectionClicked(_ sender: Any) {
|
@IBAction func connectionClicked(_ sender: Any) {
|
||||||
|
@ -105,16 +107,20 @@ class WireGuardViewController: UIViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vpn.reconnect(
|
Task {
|
||||||
tunnelIdentifier,
|
try await vpn.reconnect(
|
||||||
configuration: cfg,
|
tunnelIdentifier,
|
||||||
extra: nil,
|
configuration: cfg,
|
||||||
delay: nil
|
extra: nil,
|
||||||
)
|
after: .seconds(2)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect() {
|
func disconnect() {
|
||||||
vpn.disconnect()
|
Task {
|
||||||
|
await vpn.disconnect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateButton() {
|
func updateButton() {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.0.0</string>
|
<string>5.0.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
|
|
|
@ -116,12 +116,11 @@ class OpenVPNViewController: NSViewController {
|
||||||
|
|
||||||
var extra = NetworkExtensionExtra()
|
var extra = NetworkExtensionExtra()
|
||||||
extra.passwordReference = passwordReference
|
extra.passwordReference = passwordReference
|
||||||
|
|
||||||
vpn.reconnect(
|
vpn.reconnect(
|
||||||
tunnelIdentifier,
|
tunnelIdentifier,
|
||||||
configuration: cfg!,
|
configuration: cfg!,
|
||||||
extra: extra,
|
extra: extra,
|
||||||
delay: nil
|
after: .seconds(2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ class WireGuardViewController: NSViewController {
|
||||||
tunnelIdentifier,
|
tunnelIdentifier,
|
||||||
configuration: cfg,
|
configuration: cfg,
|
||||||
extra: nil,
|
extra: nil,
|
||||||
delay: nil
|
after: .seconds(2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.0.0</string>
|
<string>5.0.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
|
@ -1116,8 +1116,8 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PATH = "${PATH}:/opt/homebrew/bin";
|
PATH = "${PATH}:/opt/homebrew/bin";
|
||||||
|
@ -1177,8 +1177,8 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
PATH = "${PATH}:/opt/homebrew/bin";
|
PATH = "${PATH}:/opt/homebrew/bin";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "TunnelKit",
|
name: "TunnelKit",
|
||||||
platforms: [
|
platforms: [
|
||||||
.iOS(.v12), .macOS(.v10_14)
|
.iOS(.v13), .macOS(.v10_15)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
|
|
|
@ -38,13 +38,21 @@ public class MockVPN: VPN {
|
||||||
notifyStatus(.disconnected)
|
notifyStatus(.disconnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func install(_ tunnelBundleIdentifier: String, configuration: NetworkExtensionConfiguration, extra: Data?, completionHandler: ((Result<Void, Error>) -> Void)?) {
|
public func install(
|
||||||
|
_ tunnelBundleIdentifier: String,
|
||||||
|
configuration: NetworkExtensionConfiguration,
|
||||||
|
extra: Data?
|
||||||
|
) {
|
||||||
notifyReinstall(true)
|
notifyReinstall(true)
|
||||||
notifyStatus(.disconnected)
|
notifyStatus(.disconnected)
|
||||||
completionHandler?(.success(()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reconnect(_ tunnelBundleIdentifier: String, configuration: NetworkExtensionConfiguration, extra: Data?, delay: Double?) {
|
public func reconnect(
|
||||||
|
_ tunnelBundleIdentifier: String,
|
||||||
|
configuration: NetworkExtensionConfiguration,
|
||||||
|
extra: Data?,
|
||||||
|
after: DispatchTimeInterval
|
||||||
|
) {
|
||||||
notifyReinstall(true)
|
notifyReinstall(true)
|
||||||
notifyStatus(.connected)
|
notifyStatus(.connected)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ private let log = SwiftyBeaver.self
|
||||||
|
|
||||||
/// `VPN` based on the NetworkExtension framework.
|
/// `VPN` based on the NetworkExtension framework.
|
||||||
public class NetworkExtensionVPN: VPN {
|
public class NetworkExtensionVPN: VPN {
|
||||||
|
private let semaphore = DispatchSemaphore(value: 1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializes a provider.
|
Initializes a provider.
|
||||||
|
@ -45,168 +46,200 @@ public class NetworkExtensionVPN: VPN {
|
||||||
NotificationCenter.default.removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: VPN
|
// MARK: Public
|
||||||
|
|
||||||
public func prepare() {
|
public func prepare() async {
|
||||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
|
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func install(
|
public func install(
|
||||||
_ tunnelBundleIdentifier: String,
|
_ tunnelBundleIdentifier: String,
|
||||||
configuration: NetworkExtensionConfiguration,
|
configuration: NetworkExtensionConfiguration,
|
||||||
extra: NetworkExtensionExtra?,
|
extra: NetworkExtensionExtra?
|
||||||
completionHandler: ((Result<NETunnelProviderManager, Error>) -> Void)?
|
) async throws {
|
||||||
) {
|
_ = try await installReturningManager(
|
||||||
let proto: NETunnelProviderProtocol
|
tunnelBundleIdentifier,
|
||||||
do {
|
configuration: configuration,
|
||||||
proto = try configuration.asTunnelProtocol(
|
extra: extra
|
||||||
withBundleIdentifier: tunnelBundleIdentifier,
|
)
|
||||||
extra: extra
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
completionHandler?(.failure(error))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lookupAll { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let managers):
|
|
||||||
|
|
||||||
// install (new or existing) then callback
|
|
||||||
let targetManager = managers.first {
|
|
||||||
$0.isTunnel(withIdentifier: tunnelBundleIdentifier)
|
|
||||||
} ?? NETunnelProviderManager()
|
|
||||||
|
|
||||||
self.install(
|
|
||||||
targetManager,
|
|
||||||
title: configuration.title,
|
|
||||||
protocolConfiguration: proto,
|
|
||||||
onDemandRules: extra?.onDemandRules ?? [],
|
|
||||||
completionHandler: completionHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
// remove others afterwards (to avoid permission request)
|
|
||||||
managers.filter {
|
|
||||||
!$0.isTunnel(withIdentifier: tunnelBundleIdentifier)
|
|
||||||
}.forEach {
|
|
||||||
$0.removeFromPreferences(completionHandler: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
completionHandler?(.failure(error))
|
|
||||||
self.notifyError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reconnect(
|
public func reconnect(
|
||||||
_ tunnelBundleIdentifier: String,
|
_ tunnelBundleIdentifier: String,
|
||||||
configuration: NetworkExtensionConfiguration,
|
configuration: NetworkExtensionConfiguration,
|
||||||
extra: Extra?,
|
extra: NetworkExtensionExtra?,
|
||||||
delay: Double?
|
after: DispatchTimeInterval
|
||||||
) {
|
) async throws {
|
||||||
let delay = delay ?? 2.0
|
do {
|
||||||
install(
|
let manager = try await installReturningManager(
|
||||||
tunnelBundleIdentifier,
|
tunnelBundleIdentifier,
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
extra: extra
|
extra: extra
|
||||||
) { result in
|
)
|
||||||
switch result {
|
if manager.connection.status != .disconnected {
|
||||||
case .success(let manager):
|
manager.connection.stopVPNTunnel()
|
||||||
if manager.connection.status != .disconnected {
|
try await Task.sleep(nanoseconds: after.nanoseconds)
|
||||||
manager.connection.stopVPNTunnel()
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
||||||
self.connect(manager)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.connect(manager)
|
|
||||||
}
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
self.notifyError(error)
|
|
||||||
}
|
}
|
||||||
|
try manager.connection.startVPNTunnel()
|
||||||
|
} catch {
|
||||||
|
notifyError(error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func disconnect() {
|
public func disconnect() async {
|
||||||
lookupAll {
|
do {
|
||||||
if case .success(let managers) = $0 {
|
let managers = try await lookupAll()
|
||||||
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
|
guard !managers.isEmpty else {
|
||||||
|
continuation.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
managers.forEach {
|
managers.forEach {
|
||||||
|
let isLast = ($0 == managers.last)
|
||||||
$0.connection.stopVPNTunnel()
|
$0.connection.stopVPNTunnel()
|
||||||
$0.isOnDemandEnabled = false
|
$0.isOnDemandEnabled = false
|
||||||
$0.isEnabled = false
|
$0.isEnabled = false
|
||||||
$0.saveToPreferences(completionHandler: nil)
|
$0.saveToPreferences { _ in
|
||||||
|
if isLast {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func uninstall() {
|
public func uninstall() async {
|
||||||
lookupAll {
|
do {
|
||||||
if case .success(let managers) = $0 {
|
let managers = try await lookupAll()
|
||||||
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
|
guard !managers.isEmpty else {
|
||||||
|
continuation.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
managers.forEach {
|
managers.forEach {
|
||||||
|
let isLast = ($0 == managers.last)
|
||||||
$0.connection.stopVPNTunnel()
|
$0.connection.stopVPNTunnel()
|
||||||
$0.removeFromPreferences(completionHandler: nil)
|
$0.removeFromPreferences { _ in
|
||||||
|
if isLast {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Helpers
|
// MARK: Helpers
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
private func installReturningManager(
|
||||||
|
_ tunnelBundleIdentifier: String,
|
||||||
|
configuration: NetworkExtensionConfiguration,
|
||||||
|
extra: NetworkExtensionExtra?
|
||||||
|
) async throws -> NETunnelProviderManager {
|
||||||
|
let proto = try configuration.asTunnelProtocol(
|
||||||
|
withBundleIdentifier: tunnelBundleIdentifier,
|
||||||
|
extra: extra
|
||||||
|
)
|
||||||
|
let managers = try await lookupAll()
|
||||||
|
|
||||||
|
// install (new or existing) then callback
|
||||||
|
let targetManager = managers.first {
|
||||||
|
$0.isTunnel(withIdentifier: tunnelBundleIdentifier)
|
||||||
|
} ?? NETunnelProviderManager()
|
||||||
|
|
||||||
|
_ = try await install(
|
||||||
|
targetManager,
|
||||||
|
title: configuration.title,
|
||||||
|
protocolConfiguration: proto,
|
||||||
|
onDemandRules: extra?.onDemandRules ?? []
|
||||||
|
)
|
||||||
|
|
||||||
|
// remove others afterwards (to avoid permission request)
|
||||||
|
await retainManagers(managers) {
|
||||||
|
$0.isTunnel(withIdentifier: tunnelBundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetManager
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
private func install(
|
private func install(
|
||||||
_ manager: NETunnelProviderManager,
|
_ manager: NETunnelProviderManager,
|
||||||
title: String,
|
title: String,
|
||||||
protocolConfiguration: NETunnelProviderProtocol,
|
protocolConfiguration: NETunnelProviderProtocol,
|
||||||
onDemandRules: [NEOnDemandRule],
|
onDemandRules: [NEOnDemandRule]
|
||||||
completionHandler: ((Result<NETunnelProviderManager, Error>) -> Void)?
|
) async throws -> NETunnelProviderManager {
|
||||||
) {
|
try await withCheckedThrowingContinuation { continuation in
|
||||||
manager.localizedDescription = title
|
manager.localizedDescription = title
|
||||||
manager.protocolConfiguration = protocolConfiguration
|
manager.protocolConfiguration = protocolConfiguration
|
||||||
|
|
||||||
if !onDemandRules.isEmpty {
|
if !onDemandRules.isEmpty {
|
||||||
manager.onDemandRules = onDemandRules
|
manager.onDemandRules = onDemandRules
|
||||||
manager.isOnDemandEnabled = true
|
manager.isOnDemandEnabled = true
|
||||||
} else {
|
} else {
|
||||||
manager.isOnDemandEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.isEnabled = true
|
|
||||||
manager.saveToPreferences { error in
|
|
||||||
if let error = error {
|
|
||||||
manager.isOnDemandEnabled = false
|
manager.isOnDemandEnabled = false
|
||||||
manager.isEnabled = false
|
|
||||||
completionHandler?(.failure(error))
|
|
||||||
self.notifyError(error)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
manager.loadFromPreferences { error in
|
|
||||||
|
manager.isEnabled = true
|
||||||
|
manager.saveToPreferences { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completionHandler?(.failure(error))
|
manager.isOnDemandEnabled = false
|
||||||
|
manager.isEnabled = false
|
||||||
|
continuation.resume(throwing: error)
|
||||||
self.notifyError(error)
|
self.notifyError(error)
|
||||||
return
|
} else {
|
||||||
|
manager.loadFromPreferences { error in
|
||||||
|
if let error = error {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
self.notifyError(error)
|
||||||
|
} else {
|
||||||
|
continuation.resume(returning: manager)
|
||||||
|
self.notifyReinstall(manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
completionHandler?(.success(manager))
|
|
||||||
self.notifyReinstall(manager)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func connect(_ manager: NETunnelProviderManager) {
|
private func retainManagers(_ managers: [NETunnelProviderManager], isIncluded: (NETunnelProviderManager) -> Bool) async {
|
||||||
do {
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
try manager.connection.startVPNTunnel()
|
let others = managers.filter {
|
||||||
} catch {
|
!isIncluded($0)
|
||||||
notifyError(error)
|
}
|
||||||
}
|
guard !others.isEmpty else {
|
||||||
}
|
continuation.resume()
|
||||||
|
|
||||||
public func lookupAll(completionHandler: @escaping (Result<[NETunnelProviderManager], Error>) -> Void) {
|
|
||||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
|
||||||
if let error = error {
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completionHandler(.success(managers ?? []))
|
others.forEach {
|
||||||
|
let isLast = ($0 == others.last)
|
||||||
|
$0.removeFromPreferences { _ in
|
||||||
|
if isLast {
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func lookupAll() async throws -> [NETunnelProviderManager] {
|
||||||
|
try await withCheckedThrowingContinuation { continuation in
|
||||||
|
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
||||||
|
if let error = error {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
} else {
|
||||||
|
continuation.resume(returning: managers ?? [])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ import Foundation
|
||||||
|
|
||||||
/// Helps controlling a VPN without messing with underlying implementations.
|
/// Helps controlling a VPN without messing with underlying implementations.
|
||||||
public protocol VPN {
|
public protocol VPN {
|
||||||
associatedtype Manager
|
|
||||||
|
|
||||||
associatedtype Configuration
|
associatedtype Configuration
|
||||||
|
|
||||||
associatedtype Extra
|
associatedtype Extra
|
||||||
|
@ -36,7 +34,7 @@ public protocol VPN {
|
||||||
/**
|
/**
|
||||||
Synchronizes with the current VPN state.
|
Synchronizes with the current VPN state.
|
||||||
*/
|
*/
|
||||||
func prepare()
|
func prepare() async
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Installs the VPN profile.
|
Installs the VPN profile.
|
||||||
|
@ -44,14 +42,12 @@ public protocol VPN {
|
||||||
- Parameter tunnelBundleIdentifier: The bundle identifier of the tunnel extension.
|
- Parameter tunnelBundleIdentifier: The bundle identifier of the tunnel extension.
|
||||||
- Parameter configuration: The configuration to install.
|
- Parameter configuration: The configuration to install.
|
||||||
- Parameter extra: Optional extra arguments.
|
- Parameter extra: Optional extra arguments.
|
||||||
- Parameter completionHandler: The completion handler.
|
|
||||||
*/
|
*/
|
||||||
func install(
|
func install(
|
||||||
_ tunnelBundleIdentifier: String,
|
_ tunnelBundleIdentifier: String,
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
extra: Extra?,
|
extra: Extra?
|
||||||
completionHandler: ((Result<Manager, Error>) -> Void)?
|
) async throws
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reconnects to the VPN.
|
Reconnects to the VPN.
|
||||||
|
@ -59,22 +55,46 @@ public protocol VPN {
|
||||||
- Parameter tunnelBundleIdentifier: The bundle identifier of the tunnel extension.
|
- Parameter tunnelBundleIdentifier: The bundle identifier of the tunnel extension.
|
||||||
- Parameter configuration: The configuration to install.
|
- Parameter configuration: The configuration to install.
|
||||||
- Parameter extra: Optional extra arguments.
|
- Parameter extra: Optional extra arguments.
|
||||||
- Parameter delay: The reconnection delay in seconds.
|
- Parameter after: The reconnection delay.
|
||||||
*/
|
*/
|
||||||
func reconnect(
|
func reconnect(
|
||||||
_ tunnelBundleIdentifier: String,
|
_ tunnelBundleIdentifier: String,
|
||||||
configuration: Configuration,
|
configuration: Configuration,
|
||||||
extra: Extra?,
|
extra: Extra?,
|
||||||
delay: Double?
|
after: DispatchTimeInterval
|
||||||
)
|
) async throws
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Disconnects from the VPN.
|
Disconnects from the VPN.
|
||||||
*/
|
*/
|
||||||
func disconnect()
|
func disconnect() async
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Uninstalls the VPN profile.
|
Uninstalls the VPN profile.
|
||||||
*/
|
*/
|
||||||
func uninstall()
|
func uninstall() async
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DispatchTimeInterval {
|
||||||
|
public var nanoseconds: UInt64 {
|
||||||
|
switch self {
|
||||||
|
case .seconds(let sec):
|
||||||
|
return UInt64(sec) * NSEC_PER_SEC
|
||||||
|
|
||||||
|
case .milliseconds(let msec):
|
||||||
|
return UInt64(msec) * NSEC_PER_MSEC
|
||||||
|
|
||||||
|
case .microseconds(let usec):
|
||||||
|
return UInt64(usec) * NSEC_PER_USEC
|
||||||
|
|
||||||
|
case .nanoseconds(let nsec):
|
||||||
|
return UInt64(nsec)
|
||||||
|
|
||||||
|
case .never:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue