Rework ios ipc (#28)

This commit is contained in:
Nathan Brown 2021-04-27 10:29:28 -05:00 committed by GitHub
parent 4cad646a7c
commit 4c28cc196e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 259 additions and 290 deletions

View File

@ -1,7 +1,7 @@
import NetworkExtension import NetworkExtension
import MobileNebula import MobileNebula
import os.log import os.log
import MMWormhole import SwiftyJSON
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
private var networkMonitor: NWPathMonitor? private var networkMonitor: NWPathMonitor?
@ -9,16 +9,32 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var site: Site? private var site: Site?
private var _log = OSLog(subsystem: "net.defined.mobileNebula", category: "PacketTunnelProvider") private var _log = OSLog(subsystem: "net.defined.mobileNebula", category: "PacketTunnelProvider")
private var wormhole = MMWormhole(applicationGroupIdentifier: "group.net.defined.mobileNebula", optionalDirectory: "ipc")
private var nebula: MobileNebulaNebula? private var nebula: MobileNebulaNebula?
private var didSleep = false private var didSleep = false
private var cachedRouteDescription: String?
private func log(_ message: StaticString, _ args: CVarArg...) { // This is the system completionHandler, only set when we expect the UI to ask us to actually start so that errors can flow back to the UI
private var startCompleter: ((Error?) -> Void)?
private func log(_ message: StaticString, _ args: Any...) {
os_log(message, log: _log, args) os_log(message, log: _log, args)
} }
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
NSKeyedUnarchiver.setClass(IPCRequest.classForKeyedUnarchiver(), forClassName: "Runner.IPCRequest") // There is currently no way to get initialization errors back to the UI via completionHandler here
// `expectStart` is sent only via the UI which means we should wait for the real start command which has another completion handler the UI can intercept
// In the end we need to call this completionHandler to inform the system of our state
if options?["expectStart"] != nil {
startCompleter = completionHandler
return
}
// VPN is being booted out of band of the UI. Use the system completion handler as there will be nothing to route initialization errors to but we still need to report
// success/fail by the presence of an error or nil
start(completionHandler: completionHandler)
}
private func start(completionHandler: @escaping (Error?) -> Void) {
let proto = self.protocolConfiguration as! NETunnelProviderProtocol let proto = self.protocolConfiguration as! NETunnelProviderProtocol
var config: Data var config: Data
var key: String var key: String
@ -38,17 +54,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
do { do {
key = try _site.getKey() key = try _site.getKey()
} catch { } catch {
wormhole.passMessageObject(IPCMessage(id: _site.id, type: "error", message: error.localizedDescription), identifier: "nebula")
return completionHandler(error) return completionHandler(error)
} }
startNetworkMonitor()
let fileDescriptor = (self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32) ?? -1 let fileDescriptor = (self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32) ?? -1
if fileDescriptor < 0 { if fileDescriptor < 0 {
let msg = IPCMessage(id: _site.id, type: "error", message: "Starting tunnel failed: Could not determine file descriptor") return completionHandler("Starting tunnel failed: Could not determine file descriptor")
wormhole.passMessageObject(msg, identifier: "nebula")
return completionHandler(NSError())
} }
var ifnameSize = socklen_t(IFNAMSIZ) var ifnameSize = socklen_t(IFNAMSIZ)
@ -66,9 +77,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var err: NSError? var err: NSError?
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err) let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
if (err != nil) { if (err != nil) {
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - certificate") return completionHandler(err!)
self.wormhole.passMessageObject(msg, identifier: "nebula")
return completionHandler(err)
} }
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]) tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)] var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
@ -77,9 +86,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
_site.unsafeRoutes.forEach { unsafeRoute in _site.unsafeRoutes.forEach { unsafeRoute in
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err) let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
if (err != nil) { if (err != nil) {
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - unsafe routes") return completionHandler(err!)
self.wormhole.passMessageObject(msg, identifier: "nebula")
return completionHandler(err)
} }
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)) routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
} }
@ -87,20 +94,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
tunnelNetworkSettings.mtu = _site.mtu as NSNumber tunnelNetworkSettings.mtu = _site.mtu as NSNumber
wormhole.listenForMessage(withIdentifier: "app", listener: self.wormholeListener)
self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in
if (error != nil) { if (error != nil) {
let msg = IPCMessage(id: _site.id, type: "error", message: error?.localizedDescription ?? "Unknown setTunnelNetworkSettings error") return completionHandler(error!)
self.wormhole.passMessageObject(msg, identifier: "nebula")
return completionHandler(error)
} }
var err: NSError? var err: NSError?
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, Int(fileDescriptor), &err) self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, Int(fileDescriptor), &err)
self.startNetworkMonitor()
if err != nil { if err != nil {
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.Main") return completionHandler(err!)
self.wormhole.passMessageObject(msg, identifier: "nebula")
return completionHandler(err)
} }
self.nebula!.start() self.nebula!.start()
@ -132,21 +136,80 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
private func pathUpdate(path: Network.NWPath) { private func pathUpdate(path: Network.NWPath) {
//TODO: we can likely be smarter here and enumerate all the interfaces and their current addresses, only rebind if things changed let routeDescription = collectAddresses(endpoints: path.gateways)
nebula?.rebind("network change") if routeDescription != cachedRouteDescription {
// Don't bother to rebind if we don't have any gateways
if routeDescription != "" {
nebula?.rebind("network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")")
}
cachedRouteDescription = routeDescription
}
} }
private func wormholeListener(msg: Any?) { private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
guard let call = msg as? IPCRequest else { var str: [String] = []
endpoints.forEach{ endpoint in
switch endpoint {
case let .hostPort(.ipv6(host), port):
str.append("[\(host)]:\(port)")
case let .hostPort(.ipv4(host), port):
str.append("\(host):\(port)")
default:
return
}
}
return str.sorted().joined(separator: ", ")
}
override func handleAppMessage(_ data: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let call = try? JSONDecoder().decode(IPCRequest.self, from: data) else {
log("Failed to decode IPCRequest from network extension") log("Failed to decode IPCRequest from network extension")
return return
} }
var error: Error? var error: Error?
var data: Any? var data: JSON?
// start command has special treatment due to needing to call two completers
if call.command == "start" {
self.start() { error in
// Notify the system of our start result
if self.startCompleter != nil {
if error == nil {
// Clean boot, no errors
self.startCompleter!(nil)
} else {
// We encountered an error, we can just pass NSError() here since ios throws it away
// But we will provide it in the event we can intercept the error without doing this workaround sometime in the future
self.startCompleter!(error!.localizedDescription)
}
}
// Notify the UI if we have a completionHandler
if completionHandler != nil {
if error == nil {
// No response data, this is expected on a clean start
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)))
} else {
// Error response has
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error!.localizedDescription))))
}
}
}
return
}
if nebula == nil {
// Respond with an empty success message in the event a command comes in before we've truly started
log("Received command but do not have a nebula instance")
return completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)))
}
//TODO: try catch over all this //TODO: try catch over all this
switch call.type { switch call.command {
case "listHostmap": (data, error) = listHostmap(pending: false) case "listHostmap": (data, error) = listHostmap(pending: false)
case "listPendingHostmap": (data, error) = listHostmap(pending: true) case "listPendingHostmap": (data, error) = listHostmap(pending: true)
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!) case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
@ -154,37 +217,37 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!) case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
default: default:
error = "Unknown IPC message type \(call.type)" error = "Unknown IPC message type \(call.command)"
} }
if (error != nil) { if (error != nil) {
self.wormhole.passMessageObject(IPCMessage(id: "", type: "error", message: error!.localizedDescription), identifier: call.callbackId) completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error"))))
} else { } else {
self.wormhole.passMessageObject(IPCMessage(id: "", type: "success", message: data), identifier: call.callbackId) completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data)))
} }
} }
private func listHostmap(pending: Bool) -> (String?, Error?) { private func listHostmap(pending: Bool) -> (JSON?, Error?) {
var err: NSError? var err: NSError?
let res = nebula!.listHostmap(pending, error: &err) let res = nebula!.listHostmap(pending, error: &err)
return (res, err) return (JSON(res), err)
} }
private func getHostInfo(args: Dictionary<String, Any>) -> (String?, Error?) { private func getHostInfo(args: JSON) -> (JSON?, Error?) {
var err: NSError? var err: NSError?
let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"] as? String, pending: args["pending"] as! Bool, error: &err) let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
return (res, err) return (JSON(res), err)
} }
private func setRemoteForTunnel(args: Dictionary<String, Any>) -> (String?, Error?) { private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) {
var err: NSError? var err: NSError?
let res = nebula!.setRemoteForTunnel(args["vpnIp"] as? String, addr: args["addr"] as? String, error: &err) let res = nebula!.setRemoteForTunnel(args["vpnIp"].string, addr: args["addr"].string, error: &err)
return (res, err) return (JSON(res), err)
} }
private func closeTunnel(args: Dictionary<String, Any>) -> (Bool?, Error?) { private func closeTunnel(args: JSON) -> (JSON?, Error?) {
let res = nebula!.closeTunnel(args["vpnIp"] as? String) let res = nebula!.closeTunnel(args["vpnIp"].string)
return (res, nil) return (JSON(res), nil)
} }
} }

View File

@ -1,58 +1,36 @@
import NetworkExtension import NetworkExtension
import MobileNebula import MobileNebula
import SwiftyJSON
extension String: Error {} extension String: Error {}
class IPCMessage: NSObject, NSCoding { enum IPCResponseType: String, Codable {
var id: String case error = "error"
var type: String case success = "success"
var message: Any?
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(type, forKey: "type")
aCoder.encode(message, forKey: "message")
} }
required init(coder aDecoder: NSCoder) { class IPCResponse: Codable {
id = aDecoder.decodeObject(forKey: "id") as! String var type: IPCResponseType
type = aDecoder.decodeObject(forKey: "type") as! String //TODO: change message to data?
message = aDecoder.decodeObject(forKey: "message") as Any? var message: JSON?
}
init(id: String, type: String, message: Any) { init(type: IPCResponseType, message: JSON?) {
self.id = id
self.type = type self.type = type
self.message = message self.message = message
} }
} }
class IPCRequest: NSObject, NSCoding { class IPCRequest: Codable {
var type: String var command: String
var callbackId: String var arguments: JSON?
var arguments: Dictionary<String, Any>?
func encode(with aCoder: NSCoder) { init(command: String, arguments: JSON?) {
aCoder.encode(type, forKey: "type") self.command = command
aCoder.encode(arguments, forKey: "arguments")
aCoder.encode(callbackId, forKey: "callbackId")
}
required init(coder aDecoder: NSCoder) {
callbackId = aDecoder.decodeObject(forKey: "callbackId") as! String
type = aDecoder.decodeObject(forKey: "type") as! String
arguments = aDecoder.decodeObject(forKey: "arguments") as? Dictionary<String, Any>
}
init(callbackId: String, type: String, arguments: Dictionary<String, Any>?) {
self.callbackId = callbackId
self.type = type
self.arguments = arguments self.arguments = arguments
} }
init(callbackId: String, type: String) { init(command: String) {
self.callbackId = callbackId self.command = command
self.type = type
} }
} }
@ -135,7 +113,7 @@ let statusString: Dictionary<NEVPNStatus, String> = [
] ]
// Represents a site that was pulled out of the system configuration // Represents a site that was pulled out of the system configuration
struct Site: Codable { class Site: Codable {
// Stored in manager // Stored in manager
var name: String var name: String
var id: String var id: String
@ -151,18 +129,17 @@ struct Site: Codable {
var cipher: String var cipher: String
var sortKey: Int var sortKey: Int
var logVerbosity: String var logVerbosity: String
var connected: Bool? var connected: Bool? //TODO: active is a better name
var status: String? var status: String?
var logFile: String? var logFile: String?
// A list of error encountered when trying to rehydrate a site from config // A list of error encountered when trying to rehydrate a site from config
var errors: [String] var errors: [String]
// We initialize to avoid an error with Codable, there is probably a better way since manager must be present for a Site but is not codable var manager: NETunnelProviderManager?
var manager: NETunnelProviderManager = NETunnelProviderManager()
// Creates a new site from a vpn manager instance // Creates a new site from a vpn manager instance
init(manager: NETunnelProviderManager) throws { convenience init(manager: NETunnelProviderManager) throws {
//TODO: Throw an error and have Sites delete the site, notify the user instead of using ! //TODO: Throw an error and have Sites delete the site, notify the user instead of using !
let proto = manager.protocolConfiguration as! NETunnelProviderProtocol let proto = manager.protocolConfiguration as! NETunnelProviderProtocol
try self.init(proto: proto) try self.init(proto: proto)
@ -171,7 +148,7 @@ struct Site: Codable {
self.status = statusString[manager.connection.status] self.status = statusString[manager.connection.status]
} }
init(proto: NETunnelProviderProtocol) throws { convenience init(proto: NETunnelProviderProtocol) throws {
let dict = proto.providerConfiguration let dict = proto.providerConfiguration
let config = dict?["config"] as? Data ?? Data() let config = dict?["config"] as? Data ?? Data()
let decoder = JSONDecoder() let decoder = JSONDecoder()

View File

@ -32,12 +32,12 @@ target 'Runner' do
use_modular_headers! use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
pod 'MMWormhole', '~> 2.0.0' pod 'SwiftyJSON', '~> 5.0'
end end
target 'NebulaNetworkExtension' do target 'NebulaNetworkExtension' do
use_frameworks! use_frameworks!
pod 'MMWormhole', '~> 2.0.0' pod 'SwiftyJSON', '~> 5.0'
end end
post_install do |installer| post_install do |installer|

View File

@ -39,9 +39,6 @@ PODS:
- Flutter - Flutter
- FLAnimatedImage (1.0.12) - FLAnimatedImage (1.0.12)
- Flutter (1.0.0) - Flutter (1.0.0)
- MMWormhole (2.0.0):
- MMWormhole/Core (= 2.0.0)
- MMWormhole/Core (2.0.0)
- MTBBarcodeScanner (5.0.11) - MTBBarcodeScanner (5.0.11)
- package_info (0.0.1): - package_info (0.0.1):
- Flutter - Flutter
@ -54,6 +51,7 @@ PODS:
- FLAnimatedImage (>= 1.0.11) - FLAnimatedImage (>= 1.0.11)
- SDWebImage/Core (~> 5.6) - SDWebImage/Core (~> 5.6)
- SwiftProtobuf (1.8.0) - SwiftProtobuf (1.8.0)
- SwiftyJSON (5.0.1)
- url_launcher (0.0.1): - url_launcher (0.0.1):
- Flutter - Flutter
@ -61,9 +59,9 @@ DEPENDENCIES:
- barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- MMWormhole (~> 2.0.0)
- package_info (from `.symlinks/plugins/package_info/ios`) - package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- SwiftyJSON (~> 5.0)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
SPEC REPOS: SPEC REPOS:
@ -71,11 +69,11 @@ SPEC REPOS:
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- FLAnimatedImage - FLAnimatedImage
- MMWormhole
- MTBBarcodeScanner - MTBBarcodeScanner
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- SwiftProtobuf - SwiftProtobuf
- SwiftyJSON
EXTERNAL SOURCES: EXTERNAL SOURCES:
barcode_scan: barcode_scan:
@ -98,15 +96,15 @@ SPEC CHECKSUMS:
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
MMWormhole: 0cd3fd35a9118b2e2d762b499f54eeaace0be791
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70 SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
PODFILE CHECKSUM: e8d4fb1ed5b0713de2623a28dfae2585e15c0d00 PODFILE CHECKSUM: 87c61886589bcc4c3c709db9ee22a607d81c4861
COCOAPODS: 1.10.1 COCOAPODS: 1.10.1

View File

@ -23,10 +23,10 @@
43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; }; 43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; };
4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; }; 4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
78E28476711DF3A9D186C429 /* Pods_NebulaNetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */; };
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 */; };
E91B9DAD4A83866D0AF1DAE1 /* Pods_NebulaNetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -64,11 +64,11 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.debug.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
384887B4785D38431E800D3A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 384887B4785D38431E800D3A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.debug.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.debug.xcconfig"; sourceTree = "<group>"; };
437F72582469AAC500A0C4B9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = "<group>"; }; 437F72582469AAC500A0C4B9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = "<group>"; };
437F725C2469AC5700A0C4B9 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; }; 437F725C2469AC5700A0C4B9 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
43871C9A2444DD39004F9075 /* MobileNebula.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MobileNebula.framework; sourceTree = "<group>"; }; 43871C9A2444DD39004F9075 /* MobileNebula.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MobileNebula.framework; sourceTree = "<group>"; };
@ -84,12 +84,14 @@
43B66ECC245A146300B18C36 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43B66ECC245A146300B18C36 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
43B828DA249C08DC00CA229C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43B828DA249C08DC00CA229C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; };
43E9BBD0251450C5000BFB8C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43E9BBD0251450C5000BFB8C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; };
53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.profile.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.profile.xcconfig"; sourceTree = "<group>"; };
5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NebulaNetworkExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; 6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8E4961BE2F06B97C8C693530 /* 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>"; }; 8E4961BE2F06B97C8C693530 /* 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>"; };
8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NebulaNetworkExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.release.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -98,8 +100,6 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
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>"; };
C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.release.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.release.xcconfig"; sourceTree = "<group>"; };
FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.profile.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -109,7 +109,7 @@
files = ( files = (
43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */, 43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */,
43871C9B2444DD39004F9075 /* MobileNebula.framework in Frameworks */, 43871C9B2444DD39004F9075 /* MobileNebula.framework in Frameworks */,
78E28476711DF3A9D186C429 /* Pods_NebulaNetworkExtension.framework in Frameworks */, E91B9DAD4A83866D0AF1DAE1 /* Pods_NebulaNetworkExtension.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -135,7 +135,7 @@
43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */, 43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */,
43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */, 43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */,
384887B4785D38431E800D3A /* Pods_Runner.framework */, 384887B4785D38431E800D3A /* Pods_Runner.framework */,
8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */, 5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -217,9 +217,9 @@
C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */, C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */,
6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */, 6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */,
8E4961BE2F06B97C8C693530 /* Pods-Runner.profile.xcconfig */, 8E4961BE2F06B97C8C693530 /* Pods-Runner.profile.xcconfig */,
137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */, 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */,
E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */, 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */,
FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */, 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@ -231,7 +231,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 43AA895D2444DA6500EDC39C /* Build configuration list for PBXNativeTarget "NebulaNetworkExtension" */; buildConfigurationList = 43AA895D2444DA6500EDC39C /* Build configuration list for PBXNativeTarget "NebulaNetworkExtension" */;
buildPhases = ( buildPhases = (
D39D78EE128AD494ACEF8DC0 /* [CP] Check Pods Manifest.lock */, 2C0A52E24BC9F327251CBAD2 /* [CP] Check Pods Manifest.lock */,
43AA89632444DAD100EDC39C /* ShellScript */, 43AA89632444DAD100EDC39C /* ShellScript */,
43AA89502444DA6500EDC39C /* Sources */, 43AA89502444DA6500EDC39C /* Sources */,
43AA89512444DA6500EDC39C /* Frameworks */, 43AA89512444DA6500EDC39C /* Frameworks */,
@ -343,11 +343,11 @@
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework", "${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework",
"${BUILT_PRODUCTS_DIR}/MMWormhole/MMWormhole.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework", "${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework",
"${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework",
"${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework", "${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
@ -359,11 +359,11 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MMWormhole.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
@ -375,6 +375,28 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
2C0A52E24BC9F327251CBAD2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-NebulaNetworkExtension-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -421,28 +443,6 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
}; };
D39D78EE128AD494ACEF8DC0 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-NebulaNetworkExtension-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FF0E0EB9A684F086443A8FBA /* [CP] Check Pods Manifest.lock */ = { FF0E0EB9A684F086443A8FBA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -579,7 +579,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -605,7 +605,7 @@
}; };
43AA895E2444DA6500EDC39C /* Debug */ = { 43AA895E2444DA6500EDC39C /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */; baseConfigurationReference = 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */;
buildSettings = { buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -614,7 +614,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -641,7 +641,7 @@
}; };
43AA895F2444DA6500EDC39C /* Release */ = { 43AA895F2444DA6500EDC39C /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */; baseConfigurationReference = 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */;
buildSettings = { buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -650,7 +650,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -674,7 +674,7 @@
}; };
43AA89602444DA6500EDC39C /* Profile */ = { 43AA89602444DA6500EDC39C /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */; baseConfigurationReference = 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */;
buildSettings = { buildSettings = {
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@ -683,7 +683,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -819,7 +819,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -852,7 +852,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_TEAM = 576H3XS7FP; DEVELOPMENT_TEAM = 576H3XS7FP;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (

View File

@ -2,7 +2,7 @@ import UIKit
import Flutter import Flutter
import MobileNebula import MobileNebula
import NetworkExtension import NetworkExtension
import MMWormhole import SwiftyJSON
enum ChannelName { enum ChannelName {
static let vpn = "net.defined.mobileNebula/NebulaVpnService" static let vpn = "net.defined.mobileNebula/NebulaVpnService"
@ -15,7 +15,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
private var sites: Sites? private var sites: Sites?
private var wormhole = MMWormhole(applicationGroupIdentifier: "group.net.defined.mobileNebula", optionalDirectory: "ipc")
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
@ -30,9 +29,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
sites = Sites(messenger: controller.binaryMessenger) sites = Sites(messenger: controller.binaryMessenger)
let channel = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger) let channel = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger)
NSKeyedUnarchiver.setClass(IPCMessage.classForKeyedUnarchiver(), forClassName: "NebulaNetworkExtension.IPCMessage")
wormhole.listenForMessage(withIdentifier: "nebula", listener: self.wormholeListener)
channel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in channel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method { switch call.method {
case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result) case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result)
@ -45,11 +41,11 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
case "startSite": return self.startSite(call: call, result: result) case "startSite": return self.startSite(call: call, result: result)
case "stopSite": return self.stopSite(call: call, result: result) case "stopSite": return self.stopSite(call: call, result: result)
case "active.listHostmap": self.activeListHostmap(call: call, result: result) case "active.listHostmap": self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
case "active.listPendingHostmap": self.activeListPendingHostmap(call: call, result: result) case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
case "active.getHostInfo": self.activeGetHostInfo(call: call, result: result) case "active.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
case "active.setRemoteForTunnel": self.activeSetRemoteForTunnel(call: call, result: result) case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
case "active.closeTunnel": self.activeCloseTunnel(call: call, result: result) case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
case "share": Share.share(call: call, result: result) case "share": Share.share(call: call, result: result)
case "shareFile": Share.shareFile(call: call, result: result) case "shareFile": Share.shareFile(call: call, result: result)
@ -143,12 +139,14 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) { func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) } guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
let updater = self.sites?.getUpdater(id: id) let updater = self.sites?.getUpdater(id: id)
updater?.update(connected: true) updater?.update(connected: true)
#else #else
let manager = self.sites?.getSite(id: id)?.manager let container = self.sites?.getContainer(id: id)
let manager = container?.site.manager
manager?.loadFromPreferences{ error in manager?.loadFromPreferences{ error in
//TODO: Handle load error //TODO: Handle load error
// This is silly but we need to enable the site each time to avoid situations where folks have multiple sites // This is silly but we need to enable the site each time to avoid situations where folks have multiple sites
@ -158,12 +156,13 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
manager?.loadFromPreferences{ error in manager?.loadFromPreferences{ error in
//TODO: Handle load error //TODO: Handle load error
do { do {
try manager?.connection.startVPNTunnel() container?.updater.startFunc = {() -> Void in
return self.vpnRequest(command: "start", arguments: args, result: result)
}
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
} catch { } catch {
return result(CallFailedError(message: "Could not start site", details: error.localizedDescription)) return result(CallFailedError(message: "Could not start site", details: error.localizedDescription))
} }
return result(nil)
} }
} }
} }
@ -188,120 +187,46 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
#endif #endif
} }
func activeListHostmap(call: FlutterMethodCall, result: @escaping FlutterResult) { func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) } guard let args = arguments as? Dictionary<String, Any> else { return result(NoArgumentsError()) }
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
//TODO: match id for safety?
wormholeRequestWithCallback(type: "listHostmap", arguments: nil) { (data, err) -> () in
if (err != nil) {
return result(CallFailedError(message: err!.localizedDescription))
}
result(data)
}
}
func activeListPendingHostmap(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
//TODO: match id for safety?
wormholeRequestWithCallback(type: "listPendingHostmap", arguments: nil) { (data, err) -> () in
if (err != nil) {
return result(CallFailedError(message: err!.localizedDescription))
}
result(data)
}
}
func activeGetHostInfo(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, Any> else { return result(NoArgumentsError()) }
guard let id = args["id"] as? String else { return result(MissingArgumentError(message: "id is a required argument")) } guard let id = args["id"] as? String else { return result(MissingArgumentError(message: "id is a required argument")) }
guard let vpnIp = args["vpnIp"] as? String else { return result(MissingArgumentError(message: "vpnIp is a required argument")) } let container = sites?.getContainer(id: id)
let pending = args["pending"] as? Bool ?? false
//TODO: match id for safety? if container == nil {
wormholeRequestWithCallback(type: "getHostInfo", arguments: ["vpnIp": vpnIp, "pending": pending]) { (data, err) -> () in // No site for this id
if (err != nil) { return result(nil)
return result(CallFailedError(message: err!.localizedDescription))
} }
result(data) if !(container!.site.connected ?? false) {
} // Site isn't connected, no point in sending a command
return result(nil)
} }
func activeSetRemoteForTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) { if let session = container!.site.manager?.connection as? NETunnelProviderSession {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) } do {
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } try session.sendProviderMessage(try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in
guard let vpnIp = args["vpnIp"] else { return result(MissingArgumentError(message: "vpnIp is a required argument")) } if data == nil {
guard let addr = args["addr"] else { return result(MissingArgumentError(message: "addr is a required argument")) } return result(nil)
//TODO: match id for safety?
wormholeRequestWithCallback(type: "setRemoteForTunnel", arguments: ["vpnIp": vpnIp, "addr": addr]) { (data, err) -> () in
if (err != nil) {
return result(CallFailedError(message: err!.localizedDescription))
} }
result(data) //print(String(decoding: data!, as: UTF8.self))
} guard let res = try? JSONDecoder().decode(IPCResponse.self, from: data!) else {
return result(CallFailedError(message: "Failed to decode response"))
} }
func activeCloseTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) { if res.type == .success {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) } return result(res.message?.object)
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
guard let vpnIp = args["vpnIp"] else { return result(MissingArgumentError(message: "vpnIp is a required argument")) }
//TODO: match id for safety?
wormholeRequestWithCallback(type: "closeTunnel", arguments: ["vpnIp": vpnIp]) { (data, err) -> () in
if (err != nil) {
return result(CallFailedError(message: err!.localizedDescription))
} }
result(data as? Bool ?? false) return result(CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error"))
} }
} catch {
return result(CallFailedError(message: error.localizedDescription))
} }
} else {
func wormholeListener(msg: Any?) { //TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen?
guard let call = msg as? IPCMessage else { result(nil)
print("Failed to decode IPCMessage from network extension")
return
} }
switch call.type {
case "error":
guard let updater = self.sites?.getUpdater(id: call.id) else {
return print("Could not find site to deliver error to \(call.id): \(String(describing: call.message))")
}
updater.setError(err: call.message as! String)
default:
print("Unknown IPC message type \(call.type)")
}
}
func wormholeRequestWithCallback(type: String, arguments: Dictionary<String, Any>?, completion: @escaping (Any?, Error?) -> ()) {
let uuid = UUID().uuidString
wormhole.listenForMessage(withIdentifier: uuid) { msg -> () in
self.wormhole.stopListeningForMessage(withIdentifier: uuid)
guard let call = msg as? IPCMessage else {
completion("", "Failed to decode IPCMessage callback from network extension")
return
}
switch call.type {
case "error":
completion("", call.message as? String ?? "Failed to convert error")
case "success":
completion(call.message, nil)
default:
completion("", "Unknown IPC message type \(call.type)")
}
}
wormhole.passMessageObject(IPCRequest(callbackId: uuid, type: type, arguments: arguments), identifier: "app")
} }
} }

View File

@ -90,7 +90,7 @@ class Sites {
try? fileManager.removeItem(at: sitePath) try? fileManager.removeItem(at: sitePath)
#else #else
_ = KeyChain.delete(key: site.site.id) _ = KeyChain.delete(key: site.site.id)
site.site.manager.removeFromPreferences(completionHandler: callback) site.site.manager!.removeFromPreferences(completionHandler: callback)
#endif #endif
} }
@ -105,6 +105,10 @@ class Sites {
func getUpdater(id: String) -> SiteUpdater? { func getUpdater(id: String) -> SiteUpdater? {
return self.sites[id]?.updater return self.sites[id]?.updater
} }
func getContainer(id: String) -> SiteContainer? {
return self.sites[id]
}
} }
class SiteUpdater: NSObject, FlutterStreamHandler { class SiteUpdater: NSObject, FlutterStreamHandler {
@ -112,6 +116,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
private var eventChannel: FlutterEventChannel; private var eventChannel: FlutterEventChannel;
private var site: Site private var site: Site
private var notification: Any? private var notification: Any?
public var startFunc: (() -> Void)?
init(messenger: FlutterBinaryMessenger, site: Site) { init(messenger: FlutterBinaryMessenger, site: Site) {
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger) eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
@ -120,13 +125,20 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
eventChannel.setStreamHandler(self) eventChannel.setStreamHandler(self)
} }
/// onListen is called when flutter code attaches an event listener
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events; eventSink = events;
self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager.connection , queue: nil) { _ in self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection , queue: nil) { n in
let connected = self.site.connected
self.site.status = statusString[self.site.manager!.connection.status]
self.site.connected = statusMap[self.site.manager!.connection.status]
self.site.status = statusString[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
self.site.connected = statusMap[self.site.manager.connection.status] if self.site.connected! && connected != self.site.connected && self.startFunc != nil {
self.startFunc!()
self.startFunc = nil
}
let d: Dictionary<String, Any> = [ let d: Dictionary<String, Any> = [
"connected": self.site.connected!, "connected": self.site.connected!,
@ -138,14 +150,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
return nil return nil
} }
func setError(err: String) { /// onCancel is called when the flutter listener stops listening
let d: Dictionary<String, Any> = [
"connected": self.site.connected!,
"status": self.site.status!,
]
self.eventSink?(FlutterError(code: "", message: err, details: d))
}
func onCancel(withArguments arguments: Any?) -> FlutterError? { func onCancel(withArguments arguments: Any?) -> FlutterError? {
if (self.notification != nil) { if (self.notification != nil) {
NotificationCenter.default.removeObserver(self.notification!) NotificationCenter.default.removeObserver(self.notification!)
@ -153,6 +158,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
return nil return nil
} }
/// update is a way to send information to the flutter listener and generally should not be used directly
func update(connected: Bool) { func update(connected: Bool) {
let d: Dictionary<String, Any> = [ let d: Dictionary<String, Any> = [
"connected": connected, "connected": connected,

View File

@ -185,8 +185,7 @@ class Site {
try { try {
await platform.invokeMethod("startSite", <String, String>{"id": id}); await platform.invokeMethod("startSite", <String, String>{"id": id});
} on PlatformException catch (err) { } on PlatformException catch (err) {
//TODO: fix this message throw err.message ?? err.toString();
throw err.details ?? err.message ?? err.toString();
} catch (err) { } catch (err) {
throw err.toString(); throw err.toString();
} }

View File

@ -50,7 +50,6 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
} }
onChange = site.onChange().listen((_) { onChange = site.onChange().listen((_) {
setState(() {});
if (lastState != site.connected) { if (lastState != site.connected) {
//TODO: connected is set before the nebula object exists leading to a crash race, waiting for "Connected" status is a gross hack but keeps it alive //TODO: connected is set before the nebula object exists leading to a crash race, waiting for "Connected" status is a gross hack but keeps it alive
if (site.status == 'Connected') { if (site.status == 'Connected') {
@ -62,6 +61,8 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
pendingHosts = null; pendingHosts = null;
} }
} }
setState(() {});
}, onError: (err) { }, onError: (err) {
setState(() {}); setState(() {});
Utils.popError(context, "Error", err); Utils.popError(context, "Error", err);
@ -88,7 +89,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
}), }),
refreshController: refreshController, refreshController: refreshController,
onRefresh: () async { onRefresh: () async {
if (site.connected) { if (site.connected && site.status == "Connected") {
await _listHostmap(); await _listHostmap();
} }
refreshController.refreshCompleted(); refreshController.refreshCompleted();