mobile_nebula/ios/Runner/Sites.swift

186 lines
6.1 KiB
Swift
Raw Normal View History

2020-07-27 20:43:58 +00:00
import MobileNebula
2025-02-13 22:33:07 +00:00
import NetworkExtension
2020-07-27 20:43:58 +00:00
class SiteContainer {
var site: Site
var updater: SiteUpdater
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
init(site: Site, updater: SiteUpdater) {
self.site = site
self.updater = updater
}
}
class Sites {
private var containers = [String: SiteContainer]()
2020-07-27 20:43:58 +00:00
private var messenger: FlutterBinaryMessenger?
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
init(messenger: FlutterBinaryMessenger?) {
self.messenger = messenger
}
2025-02-13 22:33:07 +00:00
func loadSites(completion: @escaping ([String: Site]?, Error?) -> Void) {
_ = SiteList { sites, err in
if err != nil {
2020-07-27 20:43:58 +00:00
return completion(nil, err)
}
2025-02-13 22:33:07 +00:00
sites?.values.forEach { site in
var updater = self.containers[site.id]?.updater
2025-02-13 22:33:07 +00:00
if updater != nil {
updater!.setSite(site: site)
} else {
updater = SiteUpdater(messenger: self.messenger!, site: site)
}
self.containers[site.id] = SiteContainer(site: site, updater: updater!)
2020-07-27 20:43:58 +00:00
}
2025-02-13 22:33:07 +00:00
let justSites = self.containers.mapValues {
2025-02-13 22:33:07 +00:00
$0.site
2020-07-27 20:43:58 +00:00
}
completion(justSites, nil)
}
}
2025-02-13 22:33:07 +00:00
func deleteSite(id: String, callback: @escaping (Error?) -> Void) {
if let site = containers.removeValue(forKey: id) {
_ = KeyChain.delete(key: "\(site.site.id).dnCredentials")
_ = KeyChain.delete(key: "\(site.site.id).key")
2025-02-13 22:33:07 +00:00
do {
let fileManager = FileManager.default
let siteDir = try SiteList.getSiteDir(id: site.site.id)
try fileManager.removeItem(at: siteDir)
} catch {
print("Failed to delete site from fs: \(error.localizedDescription)")
}
2025-02-13 22:33:07 +00:00
#if !targetEnvironment(simulator)
site.site.manager!.removeFromPreferences(completionHandler: callback)
return
#endif
2020-07-27 20:43:58 +00:00
}
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
// Nothing to remove
callback(nil)
}
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
func getSite(id: String) -> Site? {
2025-02-13 22:33:07 +00:00
return containers[id]?.site
2020-07-27 20:43:58 +00:00
}
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
func getUpdater(id: String) -> SiteUpdater? {
2025-02-13 22:33:07 +00:00
return containers[id]?.updater
2020-07-27 20:43:58 +00:00
}
2025-02-13 22:33:07 +00:00
2021-04-27 15:29:28 +00:00
func getContainer(id: String) -> SiteContainer? {
2025-02-13 22:33:07 +00:00
return containers[id]
2021-04-27 15:29:28 +00:00
}
2020-07-27 20:43:58 +00:00
}
class SiteUpdater: NSObject, FlutterStreamHandler {
2025-02-13 22:33:07 +00:00
private var eventSink: FlutterEventSink?
private var eventChannel: FlutterEventChannel
2020-07-27 20:43:58 +00:00
private var site: Site
private var notification: Any?
2021-04-27 15:29:28 +00:00
public var startFunc: (() -> Void)?
private var configFd: Int32? = nil
private var configObserver: DispatchSourceFileSystemObject? = nil
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
init(messenger: FlutterBinaryMessenger, site: Site) {
do {
let configPath = try SiteList.getSiteConfigFile(id: site.id, createDir: false)
2025-02-13 22:33:07 +00:00
configFd = open(configPath.path, O_EVTONLY)
configObserver = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: configFd!,
eventMask: .write
)
2025-02-13 22:33:07 +00:00
} catch {
// SiteList.getSiteConfigFile should never throw because we are not creating it here
2025-02-13 22:33:07 +00:00
configObserver = nil
}
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
self.site = site
super.init()
2025-02-13 22:33:07 +00:00
2020-07-27 20:43:58 +00:00
eventChannel.setStreamHandler(self)
2025-02-13 22:33:07 +00:00
configObserver?.setEventHandler(handler: configUpdated)
configObserver?.setCancelHandler {
if self.configFd != nil {
close(self.configFd!)
}
self.configObserver = nil
}
2025-02-13 22:33:07 +00:00
configObserver?.resume()
2020-07-27 20:43:58 +00:00
}
2025-02-13 22:33:07 +00:00
func setSite(site: Site) {
self.site = site
}
2021-04-27 15:29:28 +00:00
/// onListen is called when flutter code attaches an event listener
2025-02-13 22:33:07 +00:00
func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
#if !targetEnvironment(simulator)
if site.manager == nil {
// TODO: The dn updater path seems to race to build a site that lacks a manager. The UI does not display this error
// and a another listen should occur and succeed.
return FlutterError(code: "Internal Error", message: "Flutter manager was not present", details: nil)
2021-04-27 15:29:28 +00:00
}
2025-02-13 22:33:07 +00:00
notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection, queue: nil) { _ in
let oldConnected = self.site.connected
self.site.status = statusString[self.site.manager!.connection.status]
self.site.connected = statusMap[self.site.manager!.connection.status]
// Check to see if we just moved to connected and if we have a start function to call when that happens
if self.site.connected!, oldConnected != self.site.connected, self.startFunc != nil {
self.startFunc!()
self.startFunc = nil
}
self.update(connected: self.site.connected!)
}
#endif
2020-07-27 20:43:58 +00:00
return nil
}
2025-02-13 22:33:07 +00:00
2021-04-27 15:29:28 +00:00
/// onCancel is called when the flutter listener stops listening
2025-02-13 22:33:07 +00:00
func onCancel(withArguments _: Any?) -> FlutterError? {
if notification != nil {
NotificationCenter.default.removeObserver(notification!)
2020-07-27 20:43:58 +00:00
}
return nil
}
2025-02-13 22:33:07 +00:00
2021-04-27 15:29:28 +00:00
/// update is a way to send information to the flutter listener and generally should not be used directly
func update(connected: Bool, replaceSite: Site? = nil) {
2025-02-13 22:33:07 +00:00
if replaceSite != nil {
site = replaceSite!
}
site.connected = connected
site.status = connected ? "Connected" : "Disconnected"
2025-02-13 22:33:07 +00:00
let encoder = JSONEncoder()
let data = try! encoder.encode(site)
2025-02-13 22:33:07 +00:00
eventSink?(String(data: data, encoding: .utf8))
}
2025-02-13 22:33:07 +00:00
private func configUpdated() {
2025-02-13 22:33:07 +00:00
if site.connected != true {
return
}
2025-02-13 22:33:07 +00:00
guard let newSite = try? Site(manager: site.manager!) else {
return
}
2025-02-13 22:33:07 +00:00
update(connected: newSite.connected ?? false, replaceSite: newSite)
2020-07-27 20:43:58 +00:00
}
}