refactor: CommandClient & Connections
This commit is contained in:
@@ -8,8 +8,10 @@ import SwiftUI
|
|||||||
public struct ConnectionListView: View {
|
public struct ConnectionListView: View {
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@StateObject private var viewModel = ConnectionListViewModel()
|
@StateObject private var viewModel = ConnectionListViewModel()
|
||||||
|
@StateObject private var commandClient = CommandClient([.connections])
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
@@ -59,19 +61,24 @@ public struct ConnectionListView: View {
|
|||||||
.alert($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.connect()
|
viewModel.connect()
|
||||||
|
commandClient.connect()
|
||||||
}
|
}
|
||||||
.onReceive(environments.commandClient.$connections) { connections in
|
.onDisappear {
|
||||||
|
viewModel.disconnect()
|
||||||
|
commandClient.disconnect()
|
||||||
|
}
|
||||||
|
.onReceive(commandClient.$connections) { connections in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
viewModel.setConnections(connections)
|
viewModel.setConnections(connections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChangeCompat(of: viewModel.connectionStateFilter) { filter in
|
.onChangeCompat(of: viewModel.connectionStateFilter) { filter in
|
||||||
environments.commandClient.connectionStateFilter = filter
|
commandClient.connectionStateFilter = filter
|
||||||
environments.commandClient.filterConnectionsNow()
|
commandClient.filterConnectionsNow()
|
||||||
}
|
}
|
||||||
.onChangeCompat(of: viewModel.connectionSort) { sort in
|
.onChangeCompat(of: viewModel.connectionSort) { sort in
|
||||||
environments.commandClient.connectionSort = sort
|
commandClient.connectionSort = sort
|
||||||
environments.commandClient.filterConnectionsNow()
|
commandClient.filterConnectionsNow()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public struct ButtonVisibilityState {
|
|||||||
groupsCount = Variant.screenshotMode && actualGroupsCount == 0
|
groupsCount = Variant.screenshotMode && actualGroupsCount == 0
|
||||||
? screenshotFallbackGroupsCount
|
? screenshotFallbackGroupsCount
|
||||||
: actualGroupsCount
|
: actualGroupsCount
|
||||||
connectionsCount = commandClient.connections?.count ?? 0
|
connectionsCount = Int(commandClient.status?.connectionsIn ?? 0)
|
||||||
|
|
||||||
let isConnected = Variant.screenshotMode || profile.status.isConnectedStrict
|
let isConnected = Variant.screenshotMode || profile.status.isConnectedStrict
|
||||||
|
|
||||||
|
|||||||
@@ -202,22 +202,14 @@ public class CommandClient: ObservableObject {
|
|||||||
}
|
}
|
||||||
clientOptions.statusInterval = Int64(NSEC_PER_SEC)
|
clientOptions.statusInterval = Int64(NSEC_PER_SEC)
|
||||||
let client = LibboxNewCommandClient(clientHandler(self), clientOptions)!
|
let client = LibboxNewCommandClient(clientHandler(self), clientOptions)!
|
||||||
do {
|
|
||||||
for i in 0 ..< 10 {
|
|
||||||
try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC)))
|
|
||||||
try Task.checkCancellation()
|
|
||||||
do {
|
do {
|
||||||
try client.connect()
|
try client.connect()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
commandClient = client
|
commandClient = client
|
||||||
}
|
}
|
||||||
return
|
|
||||||
} catch {}
|
|
||||||
try Task.checkCancellation()
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
try? client.disconnect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol {
|
||||||
|
|||||||
@@ -15,43 +15,64 @@
|
|||||||
let socketPath: String
|
let socketPath: String
|
||||||
var commandServer: LibboxCommandServer?
|
var commandServer: LibboxCommandServer?
|
||||||
|
|
||||||
|
private enum ServiceReadyState {
|
||||||
|
case pending
|
||||||
|
case ready
|
||||||
|
case failed(Error)
|
||||||
|
}
|
||||||
|
|
||||||
private let serviceReadyLock = NSLock()
|
private let serviceReadyLock = NSLock()
|
||||||
private var _serviceReady = false
|
private var serviceReadyState = ServiceReadyState.pending
|
||||||
private var serviceReadyContinuations: [CheckedContinuation<Void, Never>] = []
|
private var serviceReadyContinuations: [CheckedContinuation<Void, Error>] = []
|
||||||
|
private static let defaultNotReadyError = NSError(domain: "CommandXPC", code: -1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Command server not ready",
|
||||||
|
])
|
||||||
|
|
||||||
init(socketPath: String) {
|
init(socketPath: String) {
|
||||||
self.socketPath = socketPath
|
self.socketPath = socketPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForServiceReady() async {
|
func waitForServiceReady() async throws {
|
||||||
serviceReadyLock.lock()
|
serviceReadyLock.lock()
|
||||||
if _serviceReady {
|
switch serviceReadyState {
|
||||||
|
case .ready:
|
||||||
serviceReadyLock.unlock()
|
serviceReadyLock.unlock()
|
||||||
return
|
return
|
||||||
}
|
case let .failed(error):
|
||||||
await withCheckedContinuation { continuation in
|
serviceReadyLock.unlock()
|
||||||
|
throw error
|
||||||
|
case .pending:
|
||||||
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
serviceReadyContinuations.append(continuation)
|
serviceReadyContinuations.append(continuation)
|
||||||
serviceReadyLock.unlock()
|
serviceReadyLock.unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func markServiceReady() {
|
func markServiceReady() {
|
||||||
|
resolveServiceReady(.success(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func markServiceNotReady(_ error: Error? = nil) {
|
||||||
|
resolveServiceReady(.failure(error ?? Self.defaultNotReadyError))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveServiceReady(_ result: Result<Void, Error>) {
|
||||||
serviceReadyLock.lock()
|
serviceReadyLock.lock()
|
||||||
_serviceReady = true
|
switch result {
|
||||||
|
case .success:
|
||||||
|
serviceReadyState = .ready
|
||||||
|
case let .failure(error):
|
||||||
|
serviceReadyState = .failed(error)
|
||||||
|
}
|
||||||
let continuations = serviceReadyContinuations
|
let continuations = serviceReadyContinuations
|
||||||
serviceReadyContinuations.removeAll()
|
serviceReadyContinuations.removeAll()
|
||||||
serviceReadyLock.unlock()
|
serviceReadyLock.unlock()
|
||||||
for continuation in continuations {
|
for continuation in continuations {
|
||||||
continuation.resume()
|
continuation.resume(with: result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func markServiceNotReady() {
|
|
||||||
serviceReadyLock.lock()
|
|
||||||
_serviceReady = false
|
|
||||||
serviceReadyLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
let bundleID = AppConfiguration.packageName + ".standalone"
|
let bundleID = AppConfiguration.packageName + ".standalone"
|
||||||
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
||||||
@@ -98,7 +119,12 @@
|
|||||||
|
|
||||||
func extensionRequirements(reply: @escaping (Bool, Bool, NSError?) -> Void) {
|
func extensionRequirements(reply: @escaping (Bool, Bool, NSError?) -> Void) {
|
||||||
Task {
|
Task {
|
||||||
await service.waitForServiceReady()
|
do {
|
||||||
|
try await service.waitForServiceReady()
|
||||||
|
} catch {
|
||||||
|
reply(false, false, error as NSError)
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let commandServer = service.commandServer else {
|
guard let commandServer = service.commandServer else {
|
||||||
reply(false, false, NSError(domain: "CommandXPC", code: -1, userInfo: [
|
reply(false, false, NSError(domain: "CommandXPC", code: -1, userInfo: [
|
||||||
NSLocalizedDescriptionKey: "Command server not available",
|
NSLocalizedDescriptionKey: "Command server not available",
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public struct ImportRemoteProfileRequest: Hashable, Identifiable {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class ExtensionEnvironments: ObservableObject {
|
public class ExtensionEnvironments: ObservableObject {
|
||||||
@Published public var commandClient = CommandClient([.log, .status, .groups, .clashMode, .connections])
|
@Published public var commandClient = CommandClient([.log, .status, .groups, .clashMode])
|
||||||
@Published public var extensionProfileLoading = true
|
@Published public var extensionProfileLoading = true
|
||||||
@Published public var extensionProfile: ExtensionProfile?
|
@Published public var extensionProfile: ExtensionProfile?
|
||||||
@Published public var emptyProfiles = false
|
@Published public var emptyProfiles = false
|
||||||
|
|||||||
@@ -171,7 +171,16 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
writeMessage("(packet-tunnel): Here I stand")
|
writeMessage("(packet-tunnel): Here I stand")
|
||||||
|
do {
|
||||||
try await startService()
|
try await startService()
|
||||||
|
} catch {
|
||||||
|
#if os(macOS)
|
||||||
|
if Variant.useSystemExtension {
|
||||||
|
xpcService.markServiceNotReady(error)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
throw error
|
||||||
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if Variant.useSystemExtension {
|
if Variant.useSystemExtension {
|
||||||
xpcService.markServiceReady()
|
xpcService.markServiceReady()
|
||||||
@@ -251,6 +260,9 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if Variant.useSystemExtension {
|
if Variant.useSystemExtension {
|
||||||
|
xpcService.markServiceNotReady(NSError(domain: "CommandXPC", code: -1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Command server stopped",
|
||||||
|
]))
|
||||||
xpcListener.invalidate()
|
xpcListener.invalidate()
|
||||||
xpcListener = nil
|
xpcListener = nil
|
||||||
xpcService.commandServer = nil
|
xpcService.commandServer = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user