mirror of
https://github.com/yggdrasil-network/yggdrasil-ios.git
synced 2025-04-28 14:15:09 +03:00
SwiftUI is happening
This commit is contained in:
parent
291b12b785
commit
feb0417b12
71 changed files with 983 additions and 2180 deletions
|
@ -10,6 +10,7 @@ import UIKit
|
|||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
import SwiftUI
|
||||
import Yggdrasil
|
||||
import NetworkExtension
|
||||
|
||||
|
@ -28,12 +29,14 @@ class PlatformItemSource: NSObject {}
|
|||
#endif
|
||||
|
||||
class ConfigurationProxy: PlatformItemSource {
|
||||
|
||||
private var manager: NETunnelProviderManager?
|
||||
private var json: Data? = nil
|
||||
private var dict: [String: Any]? = nil
|
||||
|
||||
override init() {
|
||||
init(manager: NETunnelProviderManager? = nil) {
|
||||
self.manager = manager
|
||||
super.init()
|
||||
|
||||
self.json = MobileGenerateConfigJSON()
|
||||
do {
|
||||
try self.convertToDict()
|
||||
|
@ -48,8 +51,10 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
self.fix()
|
||||
}
|
||||
|
||||
init(json: Data) throws {
|
||||
init(json: Data, manager: NETunnelProviderManager? = nil) throws {
|
||||
self.manager = manager
|
||||
super.init()
|
||||
|
||||
self.json = json
|
||||
try self.convertToDict()
|
||||
self.fix()
|
||||
|
@ -59,9 +64,10 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
self.set("Listen", to: [] as [String])
|
||||
self.set("AdminListen", to: "none")
|
||||
self.set("IfName", to: "dummy")
|
||||
// self.set("Peers", to: ["tcp://172.22.97.1.5190", "tls://172.22.97.1:5191"])
|
||||
|
||||
if self.get("AutoStart") == nil {
|
||||
self.set("AutoStart", to: ["WiFi": false, "Mobile": false] as [String: Bool])
|
||||
self.set("AutoStart", to: ["Any": false, "WiFi": false, "Mobile": false, "Ethernet": false] as [String: Bool])
|
||||
}
|
||||
|
||||
let multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? []
|
||||
|
@ -88,6 +94,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
var multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? []
|
||||
multicastInterfaces[0]["Beacon"] = newValue
|
||||
self.set("MulticastInterfaces", to: multicastInterfaces)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,10 +110,61 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
var multicastInterfaces = self.get("MulticastInterfaces") as? [[String: Any]] ?? []
|
||||
multicastInterfaces[0]["Listen"] = newValue
|
||||
self.set("MulticastInterfaces", to: multicastInterfaces)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
public var autoStartAny: Bool {
|
||||
get {
|
||||
return self.get("Any", inSection: "AutoStart") as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
self.set("Any", inSection: "AutoStart", to: newValue)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
public var autoStartWiFi: Bool {
|
||||
get {
|
||||
return self.get("WiFi", inSection: "AutoStart") as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
self.set("WiFi", inSection: "AutoStart", to: newValue)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
func get(_ key: String) -> Any? {
|
||||
public var autoStartEthernet: Bool {
|
||||
get {
|
||||
return self.get("Ethernet", inSection: "AutoStart") as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
self.set("Ethernet", inSection: "AutoStart", to: newValue)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
public var autoStartMobile: Bool {
|
||||
get {
|
||||
return self.get("Mobile", inSection: "AutoStart") as? Bool ?? false
|
||||
}
|
||||
set {
|
||||
self.set("Mobile", inSection: "AutoStart", to: newValue)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
public var peers: [String] {
|
||||
get {
|
||||
return self.get("Peers") as? [String] ?? []
|
||||
}
|
||||
set {
|
||||
self.set("Peers", to: newValue)
|
||||
self.trySave()
|
||||
}
|
||||
}
|
||||
|
||||
private func get(_ key: String) -> Any? {
|
||||
if let dict = self.dict {
|
||||
if dict.keys.contains(key) {
|
||||
return dict[key]
|
||||
|
@ -115,7 +173,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
return nil
|
||||
}
|
||||
|
||||
func get(_ key: String, inSection section: String) -> Any? {
|
||||
private func get(_ key: String, inSection section: String) -> Any? {
|
||||
if let dict = self.get(section) as? [String: Any] {
|
||||
if dict.keys.contains(key) {
|
||||
return dict[key]
|
||||
|
@ -124,7 +182,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
return nil
|
||||
}
|
||||
|
||||
func add(_ value: Any, in key: String) {
|
||||
private func add(_ value: Any, in key: String) {
|
||||
if self.dict != nil {
|
||||
if self.dict![key] as? [Any] != nil {
|
||||
var temp = self.dict![key] as? [Any] ?? []
|
||||
|
@ -134,7 +192,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
}
|
||||
}
|
||||
|
||||
func remove(_ value: String, from key: String) {
|
||||
private func remove(_ value: String, from key: String) {
|
||||
if self.dict != nil {
|
||||
if self.dict![key] as? [String] != nil {
|
||||
var temp = self.dict![key] as? [String] ?? []
|
||||
|
@ -146,7 +204,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
}
|
||||
}
|
||||
|
||||
func remove(index: Int, from key: String) {
|
||||
private func remove(index: Int, from key: String) {
|
||||
if self.dict != nil {
|
||||
if self.dict![key] as? [Any] != nil {
|
||||
var temp = self.dict![key] as? [Any] ?? []
|
||||
|
@ -156,13 +214,13 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
}
|
||||
}
|
||||
|
||||
func set(_ key: String, to value: Any) {
|
||||
private func set(_ key: String, to value: Any) {
|
||||
if self.dict != nil {
|
||||
self.dict![key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func set(_ key: String, inSection section: String, to value: Any?) {
|
||||
private func set(_ key: String, inSection section: String, to value: Any?) {
|
||||
if self.dict != nil {
|
||||
if self.dict!.keys.contains(section), let value = value {
|
||||
var temp = self.dict![section] as? [String: Any] ?? [:]
|
||||
|
@ -181,6 +239,12 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
}
|
||||
}
|
||||
|
||||
private func trySave() {
|
||||
if var manager = self.manager {
|
||||
try? self.save(to: &manager)
|
||||
}
|
||||
}
|
||||
|
||||
func save(to manager: inout NETunnelProviderManager) throws {
|
||||
self.fix()
|
||||
if let data = self.data() {
|
||||
|
@ -192,6 +256,18 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
|
||||
let disconnectrule = NEOnDemandRuleDisconnect()
|
||||
var rules: [NEOnDemandRule] = [disconnectrule]
|
||||
if self.get("Any", inSection: "AutoStart") as? Bool ?? false {
|
||||
let wifirule = NEOnDemandRuleConnect()
|
||||
wifirule.interfaceTypeMatch = .any
|
||||
rules.insert(wifirule, at: 0)
|
||||
}
|
||||
#if os(macOS)
|
||||
if self.get("Ethernet", inSection: "AutoStart") as? Bool ?? false {
|
||||
let wifirule = NEOnDemandRuleConnect()
|
||||
wifirule.interfaceTypeMatch = .ethernet
|
||||
rules.insert(wifirule, at: 0)
|
||||
}
|
||||
#endif
|
||||
if self.get("WiFi", inSection: "AutoStart") as? Bool ?? false {
|
||||
let wifirule = NEOnDemandRuleConnect()
|
||||
wifirule.interfaceTypeMatch = .wiFi
|
||||
|
@ -214,8 +290,7 @@ class ConfigurationProxy: PlatformItemSource {
|
|||
if let error = error {
|
||||
print(error)
|
||||
} else {
|
||||
print("Save successfully")
|
||||
NotificationCenter.default.post(name: NSNotification.Name.YggdrasilSettingsUpdated, object: self)
|
||||
print("Saved successfully")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -22,46 +22,52 @@ typealias ApplicationDelegateAdaptor = NSApplicationDelegateAdaptor
|
|||
|
||||
class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||
var vpnManager: NETunnelProviderManager = NETunnelProviderManager()
|
||||
var yggdrasilConfig: ConfigurationProxy = ConfigurationProxy()
|
||||
let yggdrasilComponent = "eu.neilalexander.yggdrasil.extension"
|
||||
private var adminTimer: DispatchSourceTimer?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: nil, using: { notification in
|
||||
if let conn = notification.object as? NEVPNConnection {
|
||||
self.updateStatus(conn: conn)
|
||||
switch conn.status {
|
||||
case .connected:
|
||||
self.requestSummaryIPC()
|
||||
self.requestStatusIPC()
|
||||
case .disconnecting, .disconnected:
|
||||
self.clearStatus()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.vpnTunnelProviderManagerInit()
|
||||
self.makeIPCRequests()
|
||||
}
|
||||
|
||||
func toggleYggdrasil() {
|
||||
if !self.yggdrasilEnabled {
|
||||
print("Starting VPN tunnel")
|
||||
do {
|
||||
try self.vpnManager.connection.startVPNTunnel()
|
||||
} catch {
|
||||
print("Failed to start VPN tunnel: \(error.localizedDescription)")
|
||||
return
|
||||
@Published var yggdrasilEnabled: Bool = false {
|
||||
didSet {
|
||||
if yggdrasilEnabled {
|
||||
if vpnManager.connection.status != .connected && vpnManager.connection.status != .connecting {
|
||||
do {
|
||||
try self.vpnManager.connection.startVPNTunnel()
|
||||
} catch {
|
||||
print("Failed to start VPN tunnel: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if vpnManager.connection.status != .disconnected && vpnManager.connection.status != .disconnecting {
|
||||
self.vpnManager.connection.stopVPNTunnel()
|
||||
}
|
||||
}
|
||||
print("Started VPN tunnel")
|
||||
} else {
|
||||
print("Stopping VPN tunnel")
|
||||
self.vpnManager.connection.stopVPNTunnel()
|
||||
print("Stopped VPN tunnel")
|
||||
}
|
||||
self.yggdrasilEnabled = !self.yggdrasilEnabled
|
||||
}
|
||||
|
||||
var yggdrasilConfig: ConfigurationProxy? = nil
|
||||
|
||||
private var adminTimer: DispatchSourceTimer?
|
||||
|
||||
@Published var yggdrasilEnabled: Bool = false
|
||||
@Published var yggdrasilConnected: Bool = false
|
||||
|
||||
@Published var yggdrasilPublicKey: String = "N/A"
|
||||
@Published var yggdrasilIP: String = "N/A"
|
||||
@Published var yggdrasilSubnet: String = "N/A"
|
||||
@Published var yggdrasilCoords: String = "[]"
|
||||
|
@ -74,39 +80,42 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
|||
return Yggdrasil.MobileGetVersion()
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: PlatformApplication) {
|
||||
func becameActive() {
|
||||
print("Application became active")
|
||||
|
||||
if self.adminTimer == nil {
|
||||
self.adminTimer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue(label: "Admin Queue"))
|
||||
self.adminTimer = DispatchSource.makeTimerSource(flags: .strict, queue: .main)
|
||||
self.adminTimer!.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(2), leeway: DispatchTimeInterval.seconds(1))
|
||||
self.adminTimer!.setEventHandler {
|
||||
self.makeIPCRequests()
|
||||
self.updateStatus(conn: self.vpnManager.connection)
|
||||
}
|
||||
}
|
||||
if self.adminTimer != nil {
|
||||
self.adminTimer!.resume()
|
||||
}
|
||||
|
||||
self.requestSummaryIPC()
|
||||
self.updateStatus(conn: self.vpnManager.connection)
|
||||
}
|
||||
|
||||
func updateStatus(conn: NEVPNConnection) {
|
||||
if conn.status == .connected {
|
||||
self.makeIPCRequests()
|
||||
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
||||
self.clearStatus()
|
||||
}
|
||||
self.yggdrasilConnected = self.yggdrasilEnabled && self.yggdrasilPeers.count > 0 && self.yggdrasilDHT.count > 0
|
||||
print("Connection status: \(yggdrasilEnabled), \(yggdrasilConnected)")
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: PlatformApplication) {
|
||||
func becameInactive() {
|
||||
print("Application became inactive")
|
||||
|
||||
if self.adminTimer != nil {
|
||||
self.adminTimer!.suspend()
|
||||
}
|
||||
}
|
||||
|
||||
func becameBackground() {}
|
||||
|
||||
func updateStatus(conn: NEVPNConnection) {
|
||||
if conn.status == .connected {
|
||||
self.requestStatusIPC()
|
||||
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
||||
self.clearStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func vpnTunnelProviderManagerInit() {
|
||||
print("Loading saved managers...")
|
||||
|
||||
|
@ -138,25 +147,24 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
self.vpnManager.loadFromPreferences(completionHandler: { (error: Error?) in
|
||||
var loadedConfig = false
|
||||
if error == nil {
|
||||
if let vpnConfig = self.vpnManager.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let confJson = vpnConfig.providerConfiguration!["json"] as? Data {
|
||||
if let loaded = try? ConfigurationProxy(json: confJson) {
|
||||
if let loaded = try? ConfigurationProxy(json: confJson, manager: self.vpnManager) {
|
||||
print("Found existing protocol configuration")
|
||||
self.yggdrasilConfig = loaded
|
||||
loadedConfig = true
|
||||
} else {
|
||||
print("Existing protocol configuration is invalid, ignoring")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.yggdrasilConfig == nil {
|
||||
if !loadedConfig {
|
||||
print("Generating new protocol configuration")
|
||||
self.yggdrasilConfig = ConfigurationProxy()
|
||||
|
||||
if let config = self.yggdrasilConfig {
|
||||
try? config.save(to: &self.vpnManager)
|
||||
}
|
||||
self.yggdrasilConfig = ConfigurationProxy(manager: self.vpnManager)
|
||||
try? self.yggdrasilConfig.save(to: &self.vpnManager)
|
||||
}
|
||||
|
||||
self.vpnManager.localizedDescription = "Yggdrasil"
|
||||
|
@ -165,56 +173,48 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func makeIPCRequests() {
|
||||
func requestSummaryIPC() {
|
||||
if self.vpnManager.connection.status != .connected {
|
||||
return
|
||||
}
|
||||
if let session = self.vpnManager.connection as? NETunnelProviderSession {
|
||||
try? session.sendProviderMessage("address".data(using: .utf8)!) { (address) in
|
||||
if let address = address {
|
||||
self.yggdrasilIP = String(data: address, encoding: .utf8)!
|
||||
NotificationCenter.default.post(name: .YggdrasilSelfUpdated, object: nil)
|
||||
try? session.sendProviderMessage("summary".data(using: .utf8)!) { js in
|
||||
if let js = js, let summary = try? JSONDecoder().decode(YggdrasilSummary.self, from: js) {
|
||||
self.yggdrasilEnabled = true
|
||||
self.yggdrasilIP = summary.address
|
||||
self.yggdrasilSubnet = summary.subnet
|
||||
self.yggdrasilPublicKey = summary.publicKey
|
||||
}
|
||||
}
|
||||
try? session.sendProviderMessage("subnet".data(using: .utf8)!) { (subnet) in
|
||||
if let subnet = subnet {
|
||||
self.yggdrasilSubnet = String(data: subnet, encoding: .utf8)!
|
||||
NotificationCenter.default.post(name: .YggdrasilSelfUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
try? session.sendProviderMessage("coords".data(using: .utf8)!) { (coords) in
|
||||
if let coords = coords {
|
||||
self.yggdrasilCoords = String(data: coords, encoding: .utf8)!
|
||||
NotificationCenter.default.post(name: .YggdrasilSelfUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
try? session.sendProviderMessage("peers".data(using: .utf8)!) { (peers) in
|
||||
if let peers = peers {
|
||||
if let jsonResponse = try? JSONSerialization.jsonObject(with: peers, options: []) as? [[String: Any]] {
|
||||
}
|
||||
}
|
||||
|
||||
func requestStatusIPC() {
|
||||
if self.vpnManager.connection.status != .connected {
|
||||
return
|
||||
}
|
||||
if let session = self.vpnManager.connection as? NETunnelProviderSession {
|
||||
try? session.sendProviderMessage("status".data(using: .utf8)!) { js in
|
||||
if let js = js, let status = try? JSONDecoder().decode(YggdrasilStatus.self, from: js) {
|
||||
self.yggdrasilCoords = status.coords
|
||||
if let jsonResponse = try? JSONSerialization.jsonObject(with: status.peers, options: []) as? [[String: Any]] {
|
||||
self.yggdrasilPeers = jsonResponse
|
||||
NotificationCenter.default.post(name: .YggdrasilPeersUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
try? session.sendProviderMessage("dht".data(using: .utf8)!) { (peers) in
|
||||
if let peers = peers {
|
||||
if let jsonResponse = try? JSONSerialization.jsonObject(with: peers, options: []) as? [[String: Any]] {
|
||||
if let jsonResponse = try? JSONSerialization.jsonObject(with: status.dht, options: []) as? [[String: Any]] {
|
||||
self.yggdrasilDHT = jsonResponse
|
||||
NotificationCenter.default.post(name: .YggdrasilDHTUpdated, object: nil)
|
||||
}
|
||||
self.yggdrasilConnected = self.yggdrasilEnabled && self.yggdrasilPeers.count > 0 && self.yggdrasilDHT.count > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearStatus() {
|
||||
self.yggdrasilConnected = false
|
||||
self.yggdrasilIP = "N/A"
|
||||
self.yggdrasilSubnet = "N/A"
|
||||
self.yggdrasilCoords = "[]"
|
||||
self.yggdrasilPeers = []
|
||||
self.yggdrasilDHT = []
|
||||
NotificationCenter.default.post(name: .YggdrasilSelfUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .YggdrasilPeersUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .YggdrasilDHTUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
|
21
Yggdrasil Network Cross-Platform/IPCResponses.swift
Normal file
21
Yggdrasil Network Cross-Platform/IPCResponses.swift
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// IPCResponses.swift
|
||||
// YggdrasilNetwork
|
||||
//
|
||||
// Created by Neil Alexander on 20/02/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct YggdrasilSummary: Codable {
|
||||
var address: String
|
||||
var subnet: String
|
||||
var publicKey: String
|
||||
}
|
||||
|
||||
struct YggdrasilStatus: Codable {
|
||||
var enabled: Bool
|
||||
var coords: String
|
||||
var peers: Data
|
||||
var dht: Data
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// NSNotification.swift
|
||||
// YggdrasilNetwork
|
||||
//
|
||||
// Created by Neil Alexander on 20/02/2019.
|
||||
//
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
extension Notification.Name {
|
||||
static let YggdrasilSelfUpdated = Notification.Name("YggdrasilSelfUpdated")
|
||||
static let YggdrasilPeersUpdated = Notification.Name("YggdrasilPeersUpdated")
|
||||
static let YggdrasilSettingsUpdated = Notification.Name("YggdrasilSettingsUpdated")
|
||||
static let YggdrasilDHTUpdated = Notification.Name("YggdrasilDHTUpdated")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue