forked from core/mobile_nebula
Rework ios ipc (#28)
This commit is contained in:
parent
4cad646a7c
commit
4c28cc196e
|
@ -1,7 +1,7 @@
|
|||
import NetworkExtension
|
||||
import MobileNebula
|
||||
import os.log
|
||||
import MMWormhole
|
||||
import SwiftyJSON
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private var networkMonitor: NWPathMonitor?
|
||||
|
@ -9,16 +9,32 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
|
||||
private var site: Site?
|
||||
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 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)
|
||||
}
|
||||
|
||||
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
|
||||
var config: Data
|
||||
var key: String
|
||||
|
@ -38,17 +54,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
do {
|
||||
key = try _site.getKey()
|
||||
} catch {
|
||||
wormhole.passMessageObject(IPCMessage(id: _site.id, type: "error", message: error.localizedDescription), identifier: "nebula")
|
||||
return completionHandler(error)
|
||||
}
|
||||
|
||||
startNetworkMonitor()
|
||||
|
||||
let fileDescriptor = (self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32) ?? -1
|
||||
if fileDescriptor < 0 {
|
||||
let msg = IPCMessage(id: _site.id, type: "error", message: "Starting tunnel failed: Could not determine file descriptor")
|
||||
wormhole.passMessageObject(msg, identifier: "nebula")
|
||||
return completionHandler(NSError())
|
||||
return completionHandler("Starting tunnel failed: Could not determine file descriptor")
|
||||
}
|
||||
|
||||
var ifnameSize = socklen_t(IFNAMSIZ)
|
||||
|
@ -66,9 +77,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
var err: NSError?
|
||||
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
||||
if (err != nil) {
|
||||
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - certificate")
|
||||
self.wormhole.passMessageObject(msg, identifier: "nebula")
|
||||
return completionHandler(err)
|
||||
return completionHandler(err!)
|
||||
}
|
||||
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
||||
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
|
||||
|
@ -77,9 +86,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
_site.unsafeRoutes.forEach { unsafeRoute in
|
||||
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
||||
if (err != nil) {
|
||||
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - unsafe routes")
|
||||
self.wormhole.passMessageObject(msg, identifier: "nebula")
|
||||
return completionHandler(err)
|
||||
return completionHandler(err!)
|
||||
}
|
||||
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
||||
}
|
||||
|
@ -87,20 +94,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
|
||||
tunnelNetworkSettings.mtu = _site.mtu as NSNumber
|
||||
|
||||
wormhole.listenForMessage(withIdentifier: "app", listener: self.wormholeListener)
|
||||
self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in
|
||||
if (error != nil) {
|
||||
let msg = IPCMessage(id: _site.id, type: "error", message: error?.localizedDescription ?? "Unknown setTunnelNetworkSettings error")
|
||||
self.wormhole.passMessageObject(msg, identifier: "nebula")
|
||||
return completionHandler(error)
|
||||
return completionHandler(error!)
|
||||
}
|
||||
|
||||
var err: NSError?
|
||||
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, Int(fileDescriptor), &err)
|
||||
self.startNetworkMonitor()
|
||||
|
||||
if err != nil {
|
||||
let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.Main")
|
||||
self.wormhole.passMessageObject(msg, identifier: "nebula")
|
||||
return completionHandler(err)
|
||||
return completionHandler(err!)
|
||||
}
|
||||
|
||||
self.nebula!.start()
|
||||
|
@ -132,21 +136,80 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
}
|
||||
|
||||
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
|
||||
nebula?.rebind("network change")
|
||||
let routeDescription = collectAddresses(endpoints: path.gateways)
|
||||
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?) {
|
||||
guard let call = msg as? IPCRequest else {
|
||||
private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
switch call.type {
|
||||
switch call.command {
|
||||
case "listHostmap": (data, error) = listHostmap(pending: false)
|
||||
case "listPendingHostmap": (data, error) = listHostmap(pending: true)
|
||||
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
|
||||
|
@ -154,37 +217,37 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
|
||||
|
||||
default:
|
||||
error = "Unknown IPC message type \(call.type)"
|
||||
error = "Unknown IPC message type \(call.command)"
|
||||
}
|
||||
|
||||
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 {
|
||||
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?
|
||||
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?
|
||||
let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"] as? String, pending: args["pending"] as! Bool, error: &err)
|
||||
return (res, err)
|
||||
let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
|
||||
return (JSON(res), err)
|
||||
}
|
||||
|
||||
private func setRemoteForTunnel(args: Dictionary<String, Any>) -> (String?, Error?) {
|
||||
private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) {
|
||||
var err: NSError?
|
||||
let res = nebula!.setRemoteForTunnel(args["vpnIp"] as? String, addr: args["addr"] as? String, error: &err)
|
||||
return (res, err)
|
||||
let res = nebula!.setRemoteForTunnel(args["vpnIp"].string, addr: args["addr"].string, error: &err)
|
||||
return (JSON(res), err)
|
||||
}
|
||||
|
||||
private func closeTunnel(args: Dictionary<String, Any>) -> (Bool?, Error?) {
|
||||
let res = nebula!.closeTunnel(args["vpnIp"] as? String)
|
||||
return (res, nil)
|
||||
private func closeTunnel(args: JSON) -> (JSON?, Error?) {
|
||||
let res = nebula!.closeTunnel(args["vpnIp"].string)
|
||||
return (JSON(res), nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,58 +1,36 @@
|
|||
import NetworkExtension
|
||||
import MobileNebula
|
||||
import SwiftyJSON
|
||||
|
||||
extension String: Error {}
|
||||
|
||||
class IPCMessage: NSObject, NSCoding {
|
||||
var id: String
|
||||
var type: String
|
||||
var message: Any?
|
||||
enum IPCResponseType: String, Codable {
|
||||
case error = "error"
|
||||
case success = "success"
|
||||
}
|
||||
|
||||
func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(id, forKey: "id")
|
||||
aCoder.encode(type, forKey: "type")
|
||||
aCoder.encode(message, forKey: "message")
|
||||
}
|
||||
class IPCResponse: Codable {
|
||||
var type: IPCResponseType
|
||||
//TODO: change message to data?
|
||||
var message: JSON?
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
id = aDecoder.decodeObject(forKey: "id") as! String
|
||||
type = aDecoder.decodeObject(forKey: "type") as! String
|
||||
message = aDecoder.decodeObject(forKey: "message") as Any?
|
||||
}
|
||||
|
||||
init(id: String, type: String, message: Any) {
|
||||
self.id = id
|
||||
init(type: IPCResponseType, message: JSON?) {
|
||||
self.type = type
|
||||
self.message = message
|
||||
}
|
||||
}
|
||||
|
||||
class IPCRequest: NSObject, NSCoding {
|
||||
var type: String
|
||||
var callbackId: String
|
||||
var arguments: Dictionary<String, Any>?
|
||||
class IPCRequest: Codable {
|
||||
var command: String
|
||||
var arguments: JSON?
|
||||
|
||||
func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(type, forKey: "type")
|
||||
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
|
||||
init(command: String, arguments: JSON?) {
|
||||
self.command = command
|
||||
self.arguments = arguments
|
||||
}
|
||||
|
||||
init(callbackId: String, type: String) {
|
||||
self.callbackId = callbackId
|
||||
self.type = type
|
||||
init(command: String) {
|
||||
self.command = command
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +113,7 @@ let statusString: Dictionary<NEVPNStatus, String> = [
|
|||
]
|
||||
|
||||
// Represents a site that was pulled out of the system configuration
|
||||
struct Site: Codable {
|
||||
class Site: Codable {
|
||||
// Stored in manager
|
||||
var name: String
|
||||
var id: String
|
||||
|
@ -151,18 +129,17 @@ struct Site: Codable {
|
|||
var cipher: String
|
||||
var sortKey: Int
|
||||
var logVerbosity: String
|
||||
var connected: Bool?
|
||||
var connected: Bool? //TODO: active is a better name
|
||||
var status: String?
|
||||
var logFile: String?
|
||||
|
||||
// A list of error encountered when trying to rehydrate a site from config
|
||||
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 = NETunnelProviderManager()
|
||||
var manager: NETunnelProviderManager?
|
||||
|
||||
// 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 !
|
||||
let proto = manager.protocolConfiguration as! NETunnelProviderProtocol
|
||||
try self.init(proto: proto)
|
||||
|
@ -171,7 +148,7 @@ struct Site: Codable {
|
|||
self.status = statusString[manager.connection.status]
|
||||
}
|
||||
|
||||
init(proto: NETunnelProviderProtocol) throws {
|
||||
convenience init(proto: NETunnelProviderProtocol) throws {
|
||||
let dict = proto.providerConfiguration
|
||||
let config = dict?["config"] as? Data ?? Data()
|
||||
let decoder = JSONDecoder()
|
||||
|
|
|
@ -32,12 +32,12 @@ target 'Runner' do
|
|||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
pod 'MMWormhole', '~> 2.0.0'
|
||||
pod 'SwiftyJSON', '~> 5.0'
|
||||
end
|
||||
|
||||
target 'NebulaNetworkExtension' do
|
||||
use_frameworks!
|
||||
pod 'MMWormhole', '~> 2.0.0'
|
||||
pod 'SwiftyJSON', '~> 5.0'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
|
|
@ -39,9 +39,6 @@ PODS:
|
|||
- Flutter
|
||||
- FLAnimatedImage (1.0.12)
|
||||
- Flutter (1.0.0)
|
||||
- MMWormhole (2.0.0):
|
||||
- MMWormhole/Core (= 2.0.0)
|
||||
- MMWormhole/Core (2.0.0)
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- package_info (0.0.1):
|
||||
- Flutter
|
||||
|
@ -54,6 +51,7 @@ PODS:
|
|||
- FLAnimatedImage (>= 1.0.11)
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- SwiftProtobuf (1.8.0)
|
||||
- SwiftyJSON (5.0.1)
|
||||
- url_launcher (0.0.1):
|
||||
- Flutter
|
||||
|
||||
|
@ -61,9 +59,9 @@ DEPENDENCIES:
|
|||
- barcode_scan (from `.symlinks/plugins/barcode_scan/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- MMWormhole (~> 2.0.0)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- SwiftyJSON (~> 5.0)
|
||||
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
|
@ -71,11 +69,11 @@ SPEC REPOS:
|
|||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FLAnimatedImage
|
||||
- MMWormhole
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage
|
||||
- SDWebImageFLPlugin
|
||||
- SwiftProtobuf
|
||||
- SwiftyJSON
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
barcode_scan:
|
||||
|
@ -98,15 +96,15 @@ SPEC CHECKSUMS:
|
|||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||
MMWormhole: 0cd3fd35a9118b2e2d762b499f54eeaace0be791
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
|
||||
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
|
||||
SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70
|
||||
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
|
||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||
|
||||
PODFILE CHECKSUM: e8d4fb1ed5b0713de2623a28dfae2585e15c0d00
|
||||
PODFILE CHECKSUM: 87c61886589bcc4c3c709db9ee22a607d81c4861
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; };
|
||||
4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; };
|
||||
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 */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -64,11 +64,11 @@
|
|||
/* End PBXCopyFilesBuildPhase 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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -84,12 +84,14 @@
|
|||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -98,8 +100,6 @@
|
|||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -109,7 +109,7 @@
|
|||
files = (
|
||||
43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */,
|
||||
43871C9B2444DD39004F9075 /* MobileNebula.framework in Frameworks */,
|
||||
78E28476711DF3A9D186C429 /* Pods_NebulaNetworkExtension.framework in Frameworks */,
|
||||
E91B9DAD4A83866D0AF1DAE1 /* Pods_NebulaNetworkExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -135,7 +135,7 @@
|
|||
43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */,
|
||||
43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */,
|
||||
384887B4785D38431E800D3A /* Pods_Runner.framework */,
|
||||
8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */,
|
||||
5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -217,9 +217,9 @@
|
|||
C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */,
|
||||
6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */,
|
||||
8E4961BE2F06B97C8C693530 /* Pods-Runner.profile.xcconfig */,
|
||||
137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */,
|
||||
E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */,
|
||||
FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */,
|
||||
41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */,
|
||||
9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */,
|
||||
53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
@ -231,7 +231,7 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 43AA895D2444DA6500EDC39C /* Build configuration list for PBXNativeTarget "NebulaNetworkExtension" */;
|
||||
buildPhases = (
|
||||
D39D78EE128AD494ACEF8DC0 /* [CP] Check Pods Manifest.lock */,
|
||||
2C0A52E24BC9F327251CBAD2 /* [CP] Check Pods Manifest.lock */,
|
||||
43AA89632444DAD100EDC39C /* ShellScript */,
|
||||
43AA89502444DA6500EDC39C /* Sources */,
|
||||
43AA89512444DA6500EDC39C /* Frameworks */,
|
||||
|
@ -343,11 +343,11 @@
|
|||
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/MMWormhole/MMWormhole.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.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}/DKPhotoGallery.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}/SDWebImage.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.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}/file_picker.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";
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -421,28 +443,6 @@
|
|||
shellPath = /bin/sh;
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -579,7 +579,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -605,7 +605,7 @@
|
|||
};
|
||||
43AA895E2444DA6500EDC39C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */;
|
||||
baseConfigurationReference = 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
|
@ -614,7 +614,7 @@
|
|||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -641,7 +641,7 @@
|
|||
};
|
||||
43AA895F2444DA6500EDC39C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */;
|
||||
baseConfigurationReference = 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
|
@ -650,7 +650,7 @@
|
|||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -674,7 +674,7 @@
|
|||
};
|
||||
43AA89602444DA6500EDC39C /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */;
|
||||
baseConfigurationReference = 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
|
@ -683,7 +683,7 @@
|
|||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -819,7 +819,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
@ -852,7 +852,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_TEAM = 576H3XS7FP;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
|
|
@ -2,7 +2,7 @@ import UIKit
|
|||
import Flutter
|
||||
import MobileNebula
|
||||
import NetworkExtension
|
||||
import MMWormhole
|
||||
import SwiftyJSON
|
||||
|
||||
enum ChannelName {
|
||||
static let vpn = "net.defined.mobileNebula/NebulaVpnService"
|
||||
|
@ -15,7 +15,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
|||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
private var sites: Sites?
|
||||
private var wormhole = MMWormhole(applicationGroupIdentifier: "group.net.defined.mobileNebula", optionalDirectory: "ipc")
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -30,9 +29,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
|||
sites = Sites(messenger: 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
|
||||
switch call.method {
|
||||
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 "stopSite": return self.stopSite(call: call, result: result)
|
||||
|
||||
case "active.listHostmap": self.activeListHostmap(call: call, result: result)
|
||||
case "active.listPendingHostmap": self.activeListPendingHostmap(call: call, result: result)
|
||||
case "active.getHostInfo": self.activeGetHostInfo(call: call, result: result)
|
||||
case "active.setRemoteForTunnel": self.activeSetRemoteForTunnel(call: call, result: result)
|
||||
case "active.closeTunnel": self.activeCloseTunnel(call: call, result: result)
|
||||
case "active.listHostmap": self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
|
||||
case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
|
||||
case "active.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
|
||||
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
|
||||
case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
|
||||
|
||||
case "share": Share.share(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) {
|
||||
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")) }
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
let updater = self.sites?.getUpdater(id: id)
|
||||
updater?.update(connected: true)
|
||||
|
||||
#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
|
||||
//TODO: Handle load error
|
||||
// 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
|
||||
//TODO: Handle load error
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func activeListHostmap(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: "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()) }
|
||||
func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
|
||||
guard let args = 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 vpnIp = args["vpnIp"] as? String else { return result(MissingArgumentError(message: "vpnIp is a required argument")) }
|
||||
let pending = args["pending"] as? Bool ?? false
|
||||
let container = sites?.getContainer(id: id)
|
||||
|
||||
//TODO: match id for safety?
|
||||
wormholeRequestWithCallback(type: "getHostInfo", arguments: ["vpnIp": vpnIp, "pending": pending]) { (data, err) -> () in
|
||||
if (err != nil) {
|
||||
return result(CallFailedError(message: err!.localizedDescription))
|
||||
if container == nil {
|
||||
// No site for this id
|
||||
return result(nil)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 vpnIp = args["vpnIp"] else { return result(MissingArgumentError(message: "vpnIp is a required argument")) }
|
||||
guard let addr = args["addr"] else { return result(MissingArgumentError(message: "addr is a required argument")) }
|
||||
|
||||
//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))
|
||||
if let session = container!.site.manager?.connection as? NETunnelProviderSession {
|
||||
do {
|
||||
try session.sendProviderMessage(try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in
|
||||
if data == nil {
|
||||
return result(nil)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 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))
|
||||
if res.type == .success {
|
||||
return result(res.message?.object)
|
||||
}
|
||||
|
||||
result(data as? Bool ?? false)
|
||||
return result(CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error"))
|
||||
}
|
||||
} catch {
|
||||
return result(CallFailedError(message: error.localizedDescription))
|
||||
}
|
||||
|
||||
func wormholeListener(msg: Any?) {
|
||||
guard let call = msg as? IPCMessage else {
|
||||
print("Failed to decode IPCMessage from network extension")
|
||||
return
|
||||
} else {
|
||||
//TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen?
|
||||
result(nil)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class Sites {
|
|||
try? fileManager.removeItem(at: sitePath)
|
||||
#else
|
||||
_ = KeyChain.delete(key: site.site.id)
|
||||
site.site.manager.removeFromPreferences(completionHandler: callback)
|
||||
site.site.manager!.removeFromPreferences(completionHandler: callback)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,10 @@ class Sites {
|
|||
func getUpdater(id: String) -> SiteUpdater? {
|
||||
return self.sites[id]?.updater
|
||||
}
|
||||
|
||||
func getContainer(id: String) -> SiteContainer? {
|
||||
return self.sites[id]
|
||||
}
|
||||
}
|
||||
|
||||
class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||
|
@ -112,6 +116,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
|||
private var eventChannel: FlutterEventChannel;
|
||||
private var site: Site
|
||||
private var notification: Any?
|
||||
public var startFunc: (() -> Void)?
|
||||
|
||||
init(messenger: FlutterBinaryMessenger, site: Site) {
|
||||
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
|
||||
|
@ -120,13 +125,20 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
|||
eventChannel.setStreamHandler(self)
|
||||
}
|
||||
|
||||
/// onListen is called when flutter code attaches an event listener
|
||||
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||
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]
|
||||
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! && connected != self.site.connected && self.startFunc != nil {
|
||||
self.startFunc!()
|
||||
self.startFunc = nil
|
||||
}
|
||||
|
||||
let d: Dictionary<String, Any> = [
|
||||
"connected": self.site.connected!,
|
||||
|
@ -138,14 +150,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setError(err: String) {
|
||||
let d: Dictionary<String, Any> = [
|
||||
"connected": self.site.connected!,
|
||||
"status": self.site.status!,
|
||||
]
|
||||
self.eventSink?(FlutterError(code: "", message: err, details: d))
|
||||
}
|
||||
|
||||
/// onCancel is called when the flutter listener stops listening
|
||||
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||
if (self.notification != nil) {
|
||||
NotificationCenter.default.removeObserver(self.notification!)
|
||||
|
@ -153,6 +158,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
|||
return nil
|
||||
}
|
||||
|
||||
/// update is a way to send information to the flutter listener and generally should not be used directly
|
||||
func update(connected: Bool) {
|
||||
let d: Dictionary<String, Any> = [
|
||||
"connected": connected,
|
||||
|
|
|
@ -185,8 +185,7 @@ class Site {
|
|||
try {
|
||||
await platform.invokeMethod("startSite", <String, String>{"id": id});
|
||||
} on PlatformException catch (err) {
|
||||
//TODO: fix this message
|
||||
throw err.details ?? err.message ?? err.toString();
|
||||
throw err.message ?? err.toString();
|
||||
} catch (err) {
|
||||
throw err.toString();
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
|||
}
|
||||
|
||||
onChange = site.onChange().listen((_) {
|
||||
setState(() {});
|
||||
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
|
||||
if (site.status == 'Connected') {
|
||||
|
@ -62,6 +61,8 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
|||
pendingHosts = null;
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
|
||||
}, onError: (err) {
|
||||
setState(() {});
|
||||
Utils.popError(context, "Error", err);
|
||||
|
@ -88,7 +89,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
|||
}),
|
||||
refreshController: refreshController,
|
||||
onRefresh: () async {
|
||||
if (site.connected) {
|
||||
if (site.connected && site.status == "Connected") {
|
||||
await _listHostmap();
|
||||
}
|
||||
refreshController.refreshCompleted();
|
||||
|
|
Loading…
Reference in New Issue