From ede904a024b7c2880053b64246acbcc0b23864d1 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Fri, 9 Feb 2024 20:20:24 +0330 Subject: [PATCH] Add ios connection info --- ios/Runner.xcodeproj/project.pbxproj | 12 ++ ios/Runner/AppDelegate.swift | 3 +- .../Handlers/ActiveGroupsEventHandler.swift | 49 ++++++ ios/Runner/Handlers/GroupsEventHandler.swift | 81 ++------- ios/Runner/Handlers/StatsEventHandler.swift | 50 ++---- ios/Shared/CommandClient.swift | 166 ++++++++++++++++++ ios/Shared/Outbound.swift | 18 ++ lib/features/home/widget/home_page.dart | 3 +- 8 files changed, 287 insertions(+), 95 deletions(-) create mode 100644 ios/Runner/Handlers/ActiveGroupsEventHandler.swift create mode 100644 ios/Shared/CommandClient.swift create mode 100644 ios/Shared/Outbound.swift diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 225a73c1..13a0e531 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -42,6 +42,9 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + D703EE932B764EA3001D88B3 /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703EE922B764EA3001D88B3 /* CommandClient.swift */; }; + D703EE962B765176001D88B3 /* ActiveGroupsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */; }; + D7CC50862B768C50006BC140 /* Outbound.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CC50852B768C50006BC140 /* Outbound.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -164,6 +167,9 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-SingBoxPacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; sourceTree = ""; }; C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D703EE922B764EA3001D88B3 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; }; + D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveGroupsEventHandler.swift; sourceTree = ""; }; + D7CC50852B768C50006BC140 /* Outbound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Outbound.swift; sourceTree = ""; }; F3FFE1D9C2D5629FACC123EE /* Pods-Runner-SingBoxPacketTunnel.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -218,6 +224,7 @@ 03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */, 03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */, 0736958C2B3B79E0007249BE /* StatsEventHandler.swift */, + D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */, ); path = Handlers; sourceTree = ""; @@ -258,6 +265,8 @@ isa = PBXGroup; children = ( 03E392CE2ADDEFC8000ADF15 /* FilePath.swift */, + D703EE922B764EA3001D88B3 /* CommandClient.swift */, + D7CC50852B768C50006BC140 /* Outbound.swift */, ); path = Shared; sourceTree = ""; @@ -672,13 +681,16 @@ 03B5167B2AE79DB400EA47E2 /* FileMethodHandler.swift in Sources */, 03B516772AE7634400EA47E2 /* Logger.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + D703EE962B765176001D88B3 /* ActiveGroupsEventHandler.swift in Sources */, 03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */, 03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */, 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */, 0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */, 03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */, + D7CC50862B768C50006BC140 /* Outbound.swift in Sources */, 032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */, 0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */, + D703EE932B764EA3001D88B3 /* CommandClient.swift in Sources */, 03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */, 03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */, 03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */, diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 95848ad7..ed2afd47 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -4,7 +4,7 @@ import Libcore @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -28,6 +28,7 @@ import Libcore AlertsEventHandler.register(with: self.registrar(forPlugin: AlertsEventHandler.name)!) LogsEventHandler.register(with: self.registrar(forPlugin: LogsEventHandler.name)!) GroupsEventHandler.register(with: self.registrar(forPlugin: GroupsEventHandler.name)!) + ActiveGroupsEventHandler.register(with: self.registrar(forPlugin: ActiveGroupsEventHandler.name)!) StatsEventHandler.register(with: self.registrar(forPlugin: StatsEventHandler.name)!) } } diff --git a/ios/Runner/Handlers/ActiveGroupsEventHandler.swift b/ios/Runner/Handlers/ActiveGroupsEventHandler.swift new file mode 100644 index 00000000..431e1ebc --- /dev/null +++ b/ios/Runner/Handlers/ActiveGroupsEventHandler.swift @@ -0,0 +1,49 @@ +import Foundation +import Combine +import Libcore + +public class ActiveGroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { + + static let name = "\(Bundle.main.serviceIdentifier)/active-groups" + private var commandClient: CommandClient? + private var channel: FlutterEventChannel? + private var events: FlutterEventSink? + private var cancellable: AnyCancellable? + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = ActiveGroupsEventHandler() + instance.channel = FlutterEventChannel(name: Self.name, + binaryMessenger: registrar.messenger()) + instance.channel?.setStreamHandler(instance) + } + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) + self.events = events + commandClient = CommandClient(.groupsInfoOnly) + commandClient?.connect() + cancellable = commandClient?.$groups.sink{ [self] sbGroups in + self.writeGroups(sbGroups) + } + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + commandClient?.disconnect() + cancellable?.cancel() + events = nil + return nil + } + + func writeGroups(_ sbGroups: [SBGroup]?) { + guard let sbGroups else {return} + if + let groups = try? JSONEncoder().encode(sbGroups), + let groups = String(data: groups, encoding: .utf8) + { + DispatchQueue.main.async { [events = self.events, groups] () in + events?(groups) + } + } + } +} diff --git a/ios/Runner/Handlers/GroupsEventHandler.swift b/ios/Runner/Handlers/GroupsEventHandler.swift index c6179807..e0dcce2b 100644 --- a/ios/Runner/Handlers/GroupsEventHandler.swift +++ b/ios/Runner/Handlers/GroupsEventHandler.swift @@ -1,93 +1,50 @@ -// -// GroupsEventHandler.swift -// Runner -// -// Created by GFWFighter on 10/24/23. -// - import Foundation +import Combine import Libcore -struct SBItem: Codable { - let tag: String - let type: String - let urlTestDelay: Int - - enum CodingKeys: String, CodingKey { - case tag - case type - case urlTestDelay = "url-test-delay" - } -} - -struct SBGroup: Codable { - let tag: String - let type: String - let selected: String - let items: [SBItem] -} - -public class GroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, LibboxCommandClientHandlerProtocol { +public class GroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler{ static let name = "\(Bundle.main.serviceIdentifier)/groups" + private var commandClient: CommandClient? private var channel: FlutterEventChannel? - - private var commandClient: LibboxCommandClient? private var events: FlutterEventSink? + private var cancellable: AnyCancellable? public static func register(with registrar: FlutterPluginRegistrar) { let instance = GroupsEventHandler() - instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger()) + instance.channel = FlutterEventChannel(name: Self.name, + binaryMessenger: registrar.messenger()) instance.channel?.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) self.events = events - let opts = LibboxCommandClientOptions() - opts.command = LibboxCommandGroup - opts.statusInterval = 3000 - commandClient = LibboxCommandClient(self, options: opts) - try? commandClient?.connect() + commandClient = CommandClient(.groups) + commandClient?.connect() + cancellable = commandClient?.$groups.sink{ [self] groups in + self.writeGroups(groups) + } return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { - try? commandClient?.disconnect() + commandClient?.disconnect() + cancellable?.cancel() + events = nil return nil } - public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) { - guard let message else { return } - var groups = [SBGroup]() - while message.hasNext() { - let group = message.next()! - var items = [SBItem]() - var groupItems = group.getItems() - while groupItems?.hasNext() ?? false { - let item = groupItems?.next()! - items.append(SBItem(tag: item!.tag, type: item!.type, urlTestDelay: Int(item!.urlTestDelay))) - } - groups.append(.init(tag: group.tag, type: group.type, selected: group.selected, items: items)) - } - if - let groups = try? JSONEncoder().encode(groups), + func writeGroups(_ sbGroups: [SBGroup]?) { + guard let sbGroups else {return} + if + let groups = try? JSONEncoder().encode(sbGroups), let groups = String(data: groups, encoding: .utf8) { - DispatchQueue.main.async { [events = self.events, groups] () in + DispatchQueue.main.async { [events = self.events, groups] in events?(groups) } } } } - -extension GroupsEventHandler { - public func clearLog() {} - public func connected() {} - public func disconnected(_ message: String?) {} - public func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {} - public func updateClashMode(_ newMode: String?) {} - public func writeLog(_ message: String?) {} - public func writeStatus(_ message: LibboxStatusMessage?) {} -} diff --git a/ios/Runner/Handlers/StatsEventHandler.swift b/ios/Runner/Handlers/StatsEventHandler.swift index 6b91ec44..8884cdea 100644 --- a/ios/Runner/Handlers/StatsEventHandler.swift +++ b/ios/Runner/Handlers/StatsEventHandler.swift @@ -1,48 +1,46 @@ -// -// StatsEventHandler.swift -// Runner -// -// Created by Hiddify on 12/27/23. -// - import Foundation import Flutter +import Combine import Libcore -public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, LibboxCommandClientHandlerProtocol { +public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { + static let name = "\(Bundle.main.serviceIdentifier)/stats" + private var commandClient: CommandClient? private var channel: FlutterEventChannel? - - private var commandClient: LibboxCommandClient? private var events: FlutterEventSink? + private var cancellable: AnyCancellable? public static func register(with registrar: FlutterPluginRegistrar) { let instance = StatsEventHandler() - instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger(), codec: FlutterJSONMethodCodec()) + instance.channel = FlutterEventChannel(name: Self.name, + binaryMessenger: registrar.messenger(), + codec: FlutterJSONMethodCodec()) instance.channel?.setStreamHandler(instance) } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) self.events = events - let opts = LibboxCommandClientOptions() - opts.command = LibboxCommandStatus - opts.statusInterval = Int64(NSEC_PER_SEC) - commandClient = LibboxCommandClient(self, options: opts) - try? commandClient?.connect() + commandClient = CommandClient(.status) + commandClient?.connect() + cancellable = commandClient?.$status.sink{ [self] status in + self.writeStatus(status) + } return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { - try? commandClient?.disconnect() + commandClient?.disconnect() + cancellable?.cancel() + events = nil return nil } - public func writeStatus(_ message: LibboxStatusMessage?) { - guard - let message - else { return } + func writeStatus(_ message: LibboxStatusMessage?) { + guard let message else { return } + let data = [ "connections-in": message.connectionsIn, "connections-out": message.connectionsOut, @@ -54,13 +52,3 @@ public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, L events?(data) } } - -extension StatsEventHandler { - public func clearLog() {} - public func connected() {} - public func disconnected(_ message: String?) {} - public func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {} - public func updateClashMode(_ newMode: String?) {} - public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) {} - public func writeLog(_ message: String?) {} -} diff --git a/ios/Shared/CommandClient.swift b/ios/Shared/CommandClient.swift new file mode 100644 index 00000000..b98a1045 --- /dev/null +++ b/ios/Shared/CommandClient.swift @@ -0,0 +1,166 @@ +import Foundation +import Libcore + +public class CommandClient: ObservableObject { + public enum ConnectionType { + case status + case groups + case log + case groupsInfoOnly + } + + private let connectionType: ConnectionType + private let logMaxLines: Int + private var commandClient: LibboxCommandClient? + private var connectTask: Task? + + @Published private(set) var isConnected: Bool + @Published private(set) var status: LibboxStatusMessage? + @Published private(set) var groups: [SBGroup]? + @Published private(set) var logList: [String] + + public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) { + self.connectionType = connectionType + self.logMaxLines = logMaxLines + logList = [] + isConnected = false + } + + public func connect() { + if isConnected { + return + } + if let connectTask { + connectTask.cancel() + } + connectTask = Task { + await connect0() + } + } + + public func disconnect() { + if let connectTask { + connectTask.cancel() + self.connectTask = nil + } + if let commandClient { + try? commandClient.disconnect() + self.commandClient = nil + } + } + + private nonisolated func connect0() async { + let clientOptions = LibboxCommandClientOptions() + switch connectionType { + case .status: + clientOptions.command = LibboxCommandStatus + case .groups: + clientOptions.command = LibboxCommandGroup + case .log: + clientOptions.command = LibboxCommandLog + case .groupsInfoOnly: + clientOptions.command = LibboxCommandGroupInfoOnly + } + clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) + 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 { + try client.connect() + await MainActor.run { + commandClient = client + } + return + } catch {} + try Task.checkCancellation() + } + } catch { + try? client.disconnect() + } + } + + private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol { + private let commandClient: CommandClient + + init(_ commandClient: CommandClient) { + self.commandClient = commandClient + } + + func connected() { + DispatchQueue.main.async { [self] in + if commandClient.connectionType == .log { + commandClient.logList = [] + } + commandClient.isConnected = true + } + } + + func disconnected(_: String?) { + DispatchQueue.main.async { [self] in + commandClient.isConnected = false + } + } + + func clearLog() { + DispatchQueue.main.async { [self] in + commandClient.logList.removeAll() + } + } + + func writeLog(_ message: String?) { + guard let message else { + return + } + DispatchQueue.main.async { [self] in + if commandClient.logList.count > commandClient.logMaxLines { + commandClient.logList.removeFirst() + } + commandClient.logList.append(message) + } + } + + func writeStatus(_ message: LibboxStatusMessage?) { + DispatchQueue.main.async { [self] in + commandClient.status = message + } + } + + func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { + guard let groups else { + return + } + var sbGroups = [SBGroup]() + while groups.hasNext() { + let group = groups.next()! + var items = [SBItem]() + let groupItems = group.getItems() + while groupItems?.hasNext() ?? false { + let item = groupItems?.next()! + items.append(SBItem(tag: item!.tag, + type: item!.type, + urlTestDelay: Int(item!.urlTestDelay) + ) + ) + } + + sbGroups.append(.init(tag: group.tag, + type: group.type, + selected: group.selected, + items: items) + ) + + } + DispatchQueue.main.async { [self] in + commandClient.groups = sbGroups + } + } + + func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { + } + + func updateClashMode(_ newMode: String?) { + } + } +} diff --git a/ios/Shared/Outbound.swift b/ios/Shared/Outbound.swift new file mode 100644 index 00000000..e3abbcf2 --- /dev/null +++ b/ios/Shared/Outbound.swift @@ -0,0 +1,18 @@ +public struct SBItem: Codable { + let tag: String + let type: String + let urlTestDelay: Int + + enum CodingKeys: String, CodingKey { + case tag + case type + case urlTestDelay = "url-test-delay" + } +} + +public struct SBGroup: Codable { + let tag: String + let type: String + let selected: String + let items: [SBItem] +} diff --git a/lib/features/home/widget/home_page.dart b/lib/features/home/widget/home_page.dart index 38c07345..8118b70a 100644 --- a/lib/features/home/widget/home_page.dart +++ b/lib/features/home/widget/home_page.dart @@ -62,7 +62,8 @@ class HomePage extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Expanded(child: ConnectionButton()), - if (Platform.isAndroid) const ActiveProxyFooter(), + if (Platform.isAndroid || Platform.isIOS) + const ActiveProxyFooter(), ], ), ),