First working version

This commit is contained in:
GFWFighter
2023-10-24 18:29:53 +03:30
parent fe7cdc276e
commit b83fcce310
19 changed files with 734 additions and 32 deletions

View File

@@ -0,0 +1,45 @@
//
// AlertEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
public class AlertsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(FilePath.packageName)/service.alerts"
private var channel: FlutterEventChannel?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = AlertsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
cancellable = VPNManager.shared.$alert.sink { [events] alert in
var data = [
"status": "Stopped",
"alert": alert.alert?.rawValue,
"message": alert.message,
]
for key in data.keys {
if data[key] == nil {
data.removeValue(forKey: key)
}
}
events(data)
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
cancellable?.cancel()
return nil
}
}

View File

@@ -0,0 +1,35 @@
//
// FileMethodHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
public class FileMethodHandler: NSObject, FlutterPlugin {
public static let name = "\(FilePath.packageName)/files.method"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: Self.name, binaryMessenger: registrar.messenger())
let instance = FileMethodHandler()
registrar.addMethodCallDelegate(instance, channel: channel)
instance.channel = channel
}
private var channel: FlutterMethodChannel?
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "get_paths":
result([
"working": FilePath.workingDirectory.path,
"temp": FilePath.cacheDirectory.path,
"base": FilePath.sharedDirectory.path
])
default:
result(FlutterMethodNotImplemented)
}
}
}

View File

@@ -0,0 +1,93 @@
//
// GroupsEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
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 {
static let name = "\(FilePath.packageName)/groups"
private var channel: FlutterEventChannel?
var commandClient: LibboxCommandClient?
var events: FlutterEventSink?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = GroupsEventHandler()
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()
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
try? commandClient?.disconnect()
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),
let groups = String(data: groups, encoding: .utf8)
{
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?) {}
}

View File

@@ -0,0 +1,28 @@
//
// LogsEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
public class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(FilePath.packageName)/service.logs"
private var channel: FlutterEventChannel?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = LogsEventHandler()
instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
return nil
}
}

View File

@@ -0,0 +1,180 @@
//
// MethodHandler.swift
// Runner
//
// Created by GFWFighter on 10/23/23.
//
import Flutter
import Combine
import Libcore
public class MethodHandler: NSObject, FlutterPlugin {
private var cancelBag: Set<AnyCancellable> = []
public static let name = "\(FilePath.packageName)/method"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: Self.name, binaryMessenger: registrar.messenger())
let instance = MethodHandler()
registrar.addMethodCallDelegate(instance, channel: channel)
instance.channel = channel
}
private var channel: FlutterMethodChannel?
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "parse_config":
result(parseConfig(args: call.arguments))
case "change_config_options":
result(changeConfigOptions(args: call.arguments))
case "start":
Task { [unowned self] in
let res = await start(args: call.arguments)
await MainActor.run {
result(res)
}
}
case "restart":
Task { [unowned self] in
let res = await restart(args: call.arguments)
await MainActor.run {
result(res)
}
}
case "stop":
result(stop())
case "url_test":
result(urlTest(args: call.arguments))
case "select_outbound":
result(selectOutbound(args: call.arguments))
default:
result(FlutterMethodNotImplemented)
}
}
public func parseConfig(args: Any?) -> String {
var error: NSError?
guard
let args = args as? [String:Any?],
let path = args["path"] as? String,
let tempPath = args["tempPath"] as? String,
let debug = (args["debug"] as? NSNumber)?.boolValue
else {
return "bad method format"
}
let res = MobileParse(path, tempPath, debug, &error)
if let error {
return error.localizedDescription
}
return ""
}
public func changeConfigOptions(args: Any?) -> Bool {
guard let options = args as? String else {
return false
}
VPNConfig.shared.configOptions = options
return true
}
public func start(args: Any?) async -> Bool {
guard
let args = args as? [String:Any?],
let path = args["path"] as? String
else {
return false
}
VPNConfig.shared.activeConfigPath = path
var error: NSError?
let config = MobileBuildConfig(path, VPNConfig.shared.configOptions, &error)
if let error {
return false
}
do {
try await VPNManager.shared.setup()
try await VPNManager.shared.connect(with: config, disableMemoryLimit: VPNConfig.shared.disableMemoryLimit)
} catch {
return false
}
return true
}
public func stop() -> Bool {
VPNManager.shared.disconnect()
return true
}
private func waitForStop() -> Future<Void, Never> {
return Future { promise in
var cancellable: AnyCancellable? = nil
cancellable = VPNManager.shared.$state
.filter { $0 == .disconnected }
.first()
.delay(for: 0.5, scheduler: RunLoop.current)
.sink(receiveValue: { _ in
promise(.success(()))
cancellable?.cancel()
})
}
}
public func restart(args: Any?) async -> Bool {
guard
let args = args as? [String:Any?],
let path = args["path"] as? String
else {
return false
}
VPNConfig.shared.activeConfigPath = path
VPNManager.shared.disconnect()
await waitForStop().value
var error: NSError?
let config = MobileBuildConfig(path, VPNConfig.shared.configOptions, &error)
if let error {
return false
}
do {
try await VPNManager.shared.setup()
try await VPNManager.shared.connect(with: config, disableMemoryLimit: VPNConfig.shared.disableMemoryLimit)
} catch {
return false
}
return true
}
public func selectOutbound(args: Any?) -> Bool {
guard
let args = args as? [String:Any?],
let group = args["groupTag"] as? String,
let outbound = args["outboundTag"] as? String
else {
return false
}
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
do {
try LibboxNewStandaloneCommandClient()?.selectOutbound(group, outboundTag: outbound)
} catch {
return false
}
return true
}
public func urlTest(args: Any?) -> Bool {
guard
let args = args as? [String:Any?]
else {
return false
}
let group = args["groupTag"] as? String
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
do {
try LibboxNewStandaloneCommandClient()?.urlTest(group)
} catch {
return false
}
return true
}
}

View File

@@ -0,0 +1,46 @@
//
// StatusEventHandler.swift
// Runner
//
// Created by GFWFighter on 10/24/23.
//
import Foundation
import Combine
public class StatusEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler {
static let name = "\(FilePath.packageName)/service.status"
private var channel: FlutterEventChannel?
private var cancellable: AnyCancellable?
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = StatusEventHandler()
instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger())
instance.channel?.setStreamHandler(instance)
}
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
cancellable = VPNManager.shared.$state.sink { [events] status in
switch status {
case .reasserting, .connecting:
events(["status": "Starting"])
case .connected:
events(["status": "Started"])
case .disconnecting:
events(["status": "Stopping"])
case .disconnected, .invalid:
events(["status": "Stopped"])
@unknown default:
events(["status": "Stopped"])
}
}
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
cancellable?.cancel()
return nil
}
}