Add ios connection info

This commit is contained in:
problematicconsumer
2024-02-09 20:20:24 +03:30
parent 22cf3b5f7f
commit ede904a024
8 changed files with 287 additions and 95 deletions

View File

@@ -42,6 +42,9 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -164,6 +167,9 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
D703EE922B764EA3001D88B3 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = "<group>"; };
D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveGroupsEventHandler.swift; sourceTree = "<group>"; };
D7CC50852B768C50006BC140 /* Outbound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Outbound.swift; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -218,6 +224,7 @@
03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */, 03B5166C2AE7325500EA47E2 /* LogsEventHandler.swift */,
03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */, 03B5167C2AE7AC6200EA47E2 /* GroupsEventHandler.swift */,
0736958C2B3B79E0007249BE /* StatsEventHandler.swift */, 0736958C2B3B79E0007249BE /* StatsEventHandler.swift */,
D703EE952B765176001D88B3 /* ActiveGroupsEventHandler.swift */,
); );
path = Handlers; path = Handlers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -258,6 +265,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
03E392CE2ADDEFC8000ADF15 /* FilePath.swift */, 03E392CE2ADDEFC8000ADF15 /* FilePath.swift */,
D703EE922B764EA3001D88B3 /* CommandClient.swift */,
D7CC50852B768C50006BC140 /* Outbound.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -672,13 +681,16 @@
03B5167B2AE79DB400EA47E2 /* FileMethodHandler.swift in Sources */, 03B5167B2AE79DB400EA47E2 /* FileMethodHandler.swift in Sources */,
03B516772AE7634400EA47E2 /* Logger.swift in Sources */, 03B516772AE7634400EA47E2 /* Logger.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
D703EE962B765176001D88B3 /* ActiveGroupsEventHandler.swift in Sources */,
03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */, 03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */,
03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */, 03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */,
0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */, 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */,
0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */, 0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */,
03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */, 03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */,
D7CC50862B768C50006BC140 /* Outbound.swift in Sources */,
032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */, 032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */,
0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */, 0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */,
D703EE932B764EA3001D88B3 /* CommandClient.swift in Sources */,
03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */, 03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */,
03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */, 03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */,
03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */, 03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */,

View File

@@ -28,6 +28,7 @@ import Libcore
AlertsEventHandler.register(with: self.registrar(forPlugin: AlertsEventHandler.name)!) AlertsEventHandler.register(with: self.registrar(forPlugin: AlertsEventHandler.name)!)
LogsEventHandler.register(with: self.registrar(forPlugin: LogsEventHandler.name)!) LogsEventHandler.register(with: self.registrar(forPlugin: LogsEventHandler.name)!)
GroupsEventHandler.register(with: self.registrar(forPlugin: GroupsEventHandler.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)!) StatsEventHandler.register(with: self.registrar(forPlugin: StatsEventHandler.name)!)
} }
} }

View File

@@ -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)
}
}
}
}

View File

@@ -1,93 +1,50 @@
//
// GroupsEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation import Foundation
import Combine
import Libcore import Libcore
struct SBItem: Codable { public class GroupsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler{
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 {
static let name = "\(Bundle.main.serviceIdentifier)/groups" static let name = "\(Bundle.main.serviceIdentifier)/groups"
private var commandClient: CommandClient?
private var channel: FlutterEventChannel? private var channel: FlutterEventChannel?
private var commandClient: LibboxCommandClient?
private var events: FlutterEventSink? private var events: FlutterEventSink?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let instance = GroupsEventHandler() 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) instance.channel?.setStreamHandler(instance)
} }
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events self.events = events
let opts = LibboxCommandClientOptions() commandClient = CommandClient(.groups)
opts.command = LibboxCommandGroup commandClient?.connect()
opts.statusInterval = 3000 cancellable = commandClient?.$groups.sink{ [self] groups in
commandClient = LibboxCommandClient(self, options: opts) self.writeGroups(groups)
try? commandClient?.connect() }
return nil return nil
} }
public func onCancel(withArguments arguments: Any?) -> FlutterError? { public func onCancel(withArguments arguments: Any?) -> FlutterError? {
try? commandClient?.disconnect() commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil return nil
} }
public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) { func writeGroups(_ sbGroups: [SBGroup]?) {
guard let message else { return } guard let sbGroups 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 if
let groups = try? JSONEncoder().encode(groups), let groups = try? JSONEncoder().encode(sbGroups),
let groups = String(data: groups, encoding: .utf8) 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) 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?) {}
}

View File

@@ -1,48 +1,46 @@
//
// StatsEventHandler.swift
// Runner
//
// Created by Hiddify on 12/27/23.
//
import Foundation import Foundation
import Flutter import Flutter
import Combine
import Libcore import Libcore
public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, LibboxCommandClientHandlerProtocol { public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(Bundle.main.serviceIdentifier)/stats" static let name = "\(Bundle.main.serviceIdentifier)/stats"
private var commandClient: CommandClient?
private var channel: FlutterEventChannel? private var channel: FlutterEventChannel?
private var commandClient: LibboxCommandClient?
private var events: FlutterEventSink? private var events: FlutterEventSink?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let instance = StatsEventHandler() 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) instance.channel?.setStreamHandler(instance)
} }
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
self.events = events self.events = events
let opts = LibboxCommandClientOptions() commandClient = CommandClient(.status)
opts.command = LibboxCommandStatus commandClient?.connect()
opts.statusInterval = Int64(NSEC_PER_SEC) cancellable = commandClient?.$status.sink{ [self] status in
commandClient = LibboxCommandClient(self, options: opts) self.writeStatus(status)
try? commandClient?.connect() }
return nil return nil
} }
public func onCancel(withArguments arguments: Any?) -> FlutterError? { public func onCancel(withArguments arguments: Any?) -> FlutterError? {
try? commandClient?.disconnect() commandClient?.disconnect()
cancellable?.cancel()
events = nil
return nil return nil
} }
public func writeStatus(_ message: LibboxStatusMessage?) { func writeStatus(_ message: LibboxStatusMessage?) {
guard guard let message else { return }
let message
else { return }
let data = [ let data = [
"connections-in": message.connectionsIn, "connections-in": message.connectionsIn,
"connections-out": message.connectionsOut, "connections-out": message.connectionsOut,
@@ -54,13 +52,3 @@ public class StatsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, L
events?(data) 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?) {}
}

View File

@@ -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<Void, Error>?
@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?) {
}
}
}

18
ios/Shared/Outbound.swift Normal file
View File

@@ -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]
}

View File

@@ -62,7 +62,8 @@ class HomePage extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Expanded(child: ConnectionButton()), const Expanded(child: ConnectionButton()),
if (Platform.isAndroid) const ActiveProxyFooter(), if (Platform.isAndroid || Platform.isIOS)
const ActiveProxyFooter(),
], ],
), ),
), ),