Update Apple client with changes from demoable build (#1809)
Brings in the changes from the Demoable build so I can start getting feedback from users on. --------- Co-authored-by: Roopesh Chander <roop@roopc.net>
@@ -16,10 +16,13 @@ extension SwiftConnlibError: @unchecked Sendable {}
|
||||
extension SwiftConnlibError: Error {}
|
||||
|
||||
public protocol CallbackHandlerDelegate: AnyObject {
|
||||
func onConnect(tunnelAddressIPv4: String, tunnelAddressIPv6: String)
|
||||
func onSetInterfaceConfig(tunnelAddressIPv4: String, tunnelAddressIPv6: String, dnsAddress: String)
|
||||
func onTunnelReady()
|
||||
func onAddRoute(_: String)
|
||||
func onRemoveRoute(_: String)
|
||||
func onUpdateResources(resourceList: String)
|
||||
func onDisconnect()
|
||||
func onError(error: Error, isRecoverable: Bool)
|
||||
func onDisconnect(error: Error)
|
||||
func onError(error: Error)
|
||||
}
|
||||
|
||||
public class CallbackHandler {
|
||||
@@ -28,22 +31,26 @@ public class CallbackHandler {
|
||||
|
||||
func onSetInterfaceConfig(tunnelAddresses: TunnelAddresses, dnsAddress: RustString) {
|
||||
logger.debug("CallbackHandler.onSetInterfaceConfig: IPv4: \(tunnelAddresses.address4.toString(), privacy: .public), IPv6: \(tunnelAddresses.address6.toString(), privacy: .public), DNS: \(dnsAddress.toString(), privacy: .public)")
|
||||
// Unimplemented
|
||||
delegate?.onSetInterfaceConfig(
|
||||
tunnelAddressIPv4: tunnelAddresses.address4.toString(),
|
||||
tunnelAddressIPv6: tunnelAddresses.address6.toString(),
|
||||
dnsAddress: dnsAddress.toString()
|
||||
)
|
||||
}
|
||||
|
||||
func onTunnelReady() {
|
||||
logger.debug("CallbackHandler.onTunnelReady")
|
||||
// Unimplemented
|
||||
delegate?.onTunnelReady()
|
||||
}
|
||||
|
||||
func onAddRoute(route: RustString) {
|
||||
logger.debug("CallbackHandler.onAddRoute: \(route.toString(), privacy: .public)")
|
||||
// Unimplemented
|
||||
delegate?.onAddRoute(route.toString())
|
||||
}
|
||||
|
||||
func onRemoveRoute(route: RustString) {
|
||||
logger.debug("CallbackHandler.onRemoveRoute: \(route.toString(), privacy: .public)")
|
||||
// Unimplemented
|
||||
delegate?.onRemoveRoute(route.toString())
|
||||
}
|
||||
|
||||
func onUpdateResources(resourceList: ResourceList) {
|
||||
@@ -54,11 +61,11 @@ public class CallbackHandler {
|
||||
func onDisconnect(error: SwiftConnlibError) {
|
||||
logger.debug("CallbackHandler.onDisconnect: \(error, privacy: .public)")
|
||||
// TODO: convert `error` to `Optional` by checking for `None` case
|
||||
delegate?.onDisconnect()
|
||||
delegate?.onDisconnect(error: error)
|
||||
}
|
||||
|
||||
func onError(error: SwiftConnlibError) {
|
||||
logger.debug("CallbackHandler.onError: \(error, privacy: .public)")
|
||||
delegate?.onError(error: error, isRecoverable: true)
|
||||
delegate?.onError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */; };
|
||||
05D3BB2128FDE9C000BC3727 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; };
|
||||
6F68E44C2A588522003C7D08 /* AllConfigs.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 6F68E44B2A588522003C7D08 /* AllConfigs.xcconfig */; };
|
||||
6FA39A042A6A7248000F0157 /* NetworkResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA39A032A6A7248000F0157 /* NetworkResource.swift */; };
|
||||
6FA39A052A6A7248000F0157 /* NetworkResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA39A032A6A7248000F0157 /* NetworkResource.swift */; };
|
||||
6FE454F62A5BFB93006549B1 /* Adapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE454EA2A5BFABA006549B1 /* Adapter.swift */; };
|
||||
6FE454F72A5BFB93006549B1 /* Adapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE454EA2A5BFABA006549B1 /* Adapter.swift */; };
|
||||
6FE455092A5D110D006549B1 /* CallbackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE455082A5D110D006549B1 /* CallbackHandler.swift */; };
|
||||
@@ -97,6 +99,7 @@
|
||||
05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FirezoneNetworkExtensionmacOS.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
6F68E44B2A588522003C7D08 /* AllConfigs.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = AllConfigs.xcconfig; path = xcconfig/AllConfigs.xcconfig; sourceTree = "<group>"; };
|
||||
6FA39A032A6A7248000F0157 /* NetworkResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResource.swift; sourceTree = "<group>"; };
|
||||
6FE454EA2A5BFABA006549B1 /* Adapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Adapter.swift; sourceTree = "<group>"; };
|
||||
6FE455082A5D110D006549B1 /* CallbackHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallbackHandler.swift; path = Connlib/CallbackHandler.swift; sourceTree = "<group>"; };
|
||||
6FE4550B2A5D111D006549B1 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Connlib/Generated/SwiftBridgeCore.swift; sourceTree = "<group>"; };
|
||||
@@ -156,6 +159,7 @@
|
||||
05CF1CDE290B1A9000CF4755 /* FirezoneNetworkExtension_macOS.entitlements */,
|
||||
05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */,
|
||||
6FE454EA2A5BFABA006549B1 /* Adapter.swift */,
|
||||
6FA39A032A6A7248000F0157 /* NetworkResource.swift */,
|
||||
6FE455082A5D110D006549B1 /* CallbackHandler.swift */,
|
||||
6FE4550B2A5D111D006549B1 /* SwiftBridgeCore.swift */,
|
||||
6FE4550E2A5D112C006549B1 /* connlib-apple.swift */,
|
||||
@@ -450,6 +454,7 @@
|
||||
6FE4550F2A5D112C006549B1 /* connlib-apple.swift in Sources */,
|
||||
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
|
||||
6FE454F62A5BFB93006549B1 /* Adapter.swift in Sources */,
|
||||
6FA39A042A6A7248000F0157 /* NetworkResource.swift in Sources */,
|
||||
6FE4550C2A5D111E006549B1 /* SwiftBridgeCore.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -462,6 +467,7 @@
|
||||
6FE455102A5D112C006549B1 /* connlib-apple.swift in Sources */,
|
||||
05CF1D16290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
|
||||
6FE454F72A5BFB93006549B1 /* Adapter.swift in Sources */,
|
||||
6FA39A052A6A7248000F0157 /* NetworkResource.swift in Sources */,
|
||||
6FE4550D2A5D111E006549B1 /* SwiftBridgeCore.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo-1024.png",
|
||||
"filename" : "appicon-ios-1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-16.png",
|
||||
"filename" : "appicon-mac-16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-32.png",
|
||||
"filename" : "appicon-mac-16-2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-32 1.png",
|
||||
"filename" : "appicon-mac-32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-64.png",
|
||||
"filename" : "appicon-mac-32-2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-128.png",
|
||||
"filename" : "appicon-mac-128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-256.png",
|
||||
"filename" : "appicon-mac-128-2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-256 1.png",
|
||||
"filename" : "appicon-mac-256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-512.png",
|
||||
"filename" : "appicon-mac-256-2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-512 1.png",
|
||||
"filename" : "appicon-mac-512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "logo-1024 1.png",
|
||||
"filename" : "appicon-mac-512-2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 958 B |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1006 B |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
25
swift/apple/Firezone/Assets.xcassets/MenuBarIconConnected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo-main-connected-light.svg",
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "logo-main-connected-dark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1324 2C18.5021 5.47053 13.9784 13.1622 15.5748 16.2679C12.2943 11.7793 16.7638 7.92182 14.1324 2Z" fill="#E6E6E6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5555 8.67994C19.4118 10.4496 15.9831 14.5961 17.8084 15.6575C19.6568 16.7325 19.4477 12.265 22 13.4111C19.3425 12.5489 20.4449 17.8678 17.1522 17.16C13.3839 16.3499 18.0702 10.6345 16.5555 8.67994Z" fill="#E6E6E6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 14.7397C5.43996 10.6068 12.8498 17.239 16.3465 17.6534C20.8434 18.1863 19.6455 13.2284 21.8963 13.4131C19.8223 13.6516 21.0672 18.8367 16.4304 18.9969C11.3806 19.1715 6.34256 11.83 0 14.7397Z" fill="#E6E6E6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 803 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1324 2C18.5021 5.47053 13.9784 13.1622 15.5748 16.2679C12.2943 11.7793 16.7638 7.92182 14.1324 2Z" fill="#262626"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5555 8.67994C19.4118 10.4496 15.9831 14.5961 17.8084 15.6575C19.6568 16.7325 19.4477 12.265 22 13.4111C19.3425 12.5489 20.4449 17.8678 17.1522 17.16C13.3839 16.3499 18.0702 10.6345 16.5555 8.67994Z" fill="#262626"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 14.7397C5.43996 10.6068 12.8498 17.239 16.3465 17.6534C20.8434 18.1863 19.6455 13.2284 21.8963 13.4131C19.8223 13.6516 21.0672 18.8367 16.4304 18.9969C11.3806 19.1715 6.34256 11.83 0 14.7397Z" fill="#262626"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 803 B |
22
swift/apple/Firezone/Assets.xcassets/MenuBarIconDisconnected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo-main-disconnected-light.svg",
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "logo-main-disconnected-dark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1324 2C18.5021 5.47053 13.9784 13.1622 15.5748 16.2679C12.2943 11.7793 16.7638 7.92182 14.1324 2Z" fill="#5C5C5C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5555 8.67994C19.4118 10.4496 15.9831 14.5961 17.8084 15.6575C19.6568 16.7325 19.4477 12.265 22 13.4111C19.3425 12.5489 20.4449 17.8678 17.1522 17.16C13.3839 16.3499 18.0702 10.6345 16.5555 8.67994Z" fill="#5C5C5C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 14.7397C5.43996 10.6068 12.8498 17.239 16.3465 17.6534C20.8434 18.1863 19.6455 13.2284 21.8963 13.4131C19.8223 13.6516 21.0672 18.8367 16.4304 18.9969C11.3806 19.1715 6.34256 11.83 0 14.7397Z" fill="#5C5C5C"/>
|
||||
<line x1="3.35355" y1="3.64645" x2="19.3536" y2="19.6464" stroke="#E6E6E6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 880 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1324 2C18.5021 5.47053 13.9784 13.1622 15.5748 16.2679C12.2943 11.7793 16.7638 7.92182 14.1324 2Z" fill="#A9A9A9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5555 8.67994C19.4118 10.4496 15.9831 14.5961 17.8084 15.6575C19.6568 16.7325 19.4477 12.265 22 13.4111C19.3425 12.5489 20.4449 17.8678 17.1522 17.16C13.3839 16.3499 18.0702 10.6345 16.5555 8.67994Z" fill="#A9A9A9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 14.7397C5.43996 10.6068 12.8498 17.239 16.3465 17.6534C20.8434 18.1863 19.6455 13.2284 21.8963 13.4131C19.8223 13.6516 21.0672 18.8367 16.4304 18.9969C11.3806 19.1715 6.34256 11.83 0 14.7397Z" fill="#A9A9A9"/>
|
||||
<line x1="3.33218" y1="3.6263" x2="21.3322" y2="19.6263" stroke="#262626"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 879 B |
@@ -2,14 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.multipath</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -23,7 +23,7 @@ public final class SettingsViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func saveButtonTapped() {
|
||||
func save() {
|
||||
settingsClient.saveSettings(settings)
|
||||
onSettingsSaved()
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public final class SettingsViewModel: ObservableObject {
|
||||
|
||||
public struct SettingsView: View {
|
||||
@ObservedObject var model: SettingsViewModel
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
public init(model: SettingsViewModel) {
|
||||
self.model = model
|
||||
@@ -76,12 +77,19 @@ public struct SettingsView: View {
|
||||
.navigationTitle("Settings")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("Save") {
|
||||
model.saveButtonTapped()
|
||||
#if os(macOS)
|
||||
Button("Done") {
|
||||
self.doneButtonTapped()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doneButtonTapped() {
|
||||
model.save()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
struct FormTextField: View {
|
||||
@@ -104,10 +112,15 @@ struct FormTextField: View {
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
#else
|
||||
TextField(title, text: text, prompt: Text(placeholder))
|
||||
.autocorrectionDisabled()
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundColor(.secondary)
|
||||
HStack(spacing: 30) {
|
||||
Spacer()
|
||||
TextField(title, text: text, prompt: Text(placeholder))
|
||||
.autocorrectionDisabled()
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: 360)
|
||||
Spacer()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// DisplayableResources.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
// This models resources that are displayed in the UI
|
||||
|
||||
import Foundation
|
||||
|
||||
public class DisplayableResources {
|
||||
public typealias Resource = (name: String, location: String)
|
||||
public private(set) var version: UInt64
|
||||
public private(set) var versionString: String
|
||||
public private(set) var orderedResources: [Resource]
|
||||
|
||||
public init(version: UInt64, resources: [Resource]) {
|
||||
self.version = version
|
||||
self.versionString = "\(version)"
|
||||
self.orderedResources = resources.sorted { $0.name < $1.name }
|
||||
}
|
||||
|
||||
public convenience init() {
|
||||
self.init(version: 0, resources: [])
|
||||
}
|
||||
|
||||
public func update(resources: [Resource]) {
|
||||
self.version = self.version &+ 1 // Overflow is ok
|
||||
self.versionString = "\(version)"
|
||||
self.orderedResources = resources.sorted { $0.name < $1.name }
|
||||
}
|
||||
}
|
||||
|
||||
extension DisplayableResources {
|
||||
public func toData() -> Data? {
|
||||
(
|
||||
"\(versionString)," +
|
||||
(orderedResources.flatMap { [$0.name, $0.location] })
|
||||
.map { $0.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) }.compactMap { $0 }
|
||||
.joined(separator: ",")
|
||||
).data(using: .utf8)
|
||||
}
|
||||
|
||||
public convenience init?(from data: Data) {
|
||||
guard let components = String(data: data, encoding: .utf8)?.split(separator: ",") else {
|
||||
return nil
|
||||
}
|
||||
guard let versionString = components.first, let version = UInt64(versionString) else {
|
||||
return nil
|
||||
}
|
||||
var resources: [Resource] = []
|
||||
for index in stride(from: 2, to: components.count, by: 2) {
|
||||
guard let name = components[index - 1].removingPercentEncoding,
|
||||
let location = components[index].removingPercentEncoding else {
|
||||
continue
|
||||
}
|
||||
resources.append((name: name, location: location))
|
||||
}
|
||||
self.init(version: version, resources: resources)
|
||||
}
|
||||
|
||||
public func versionStringToData() -> Data {
|
||||
versionString.data(using: .utf8)!
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import OSLog
|
||||
|
||||
// TODO: Can this file be removed since we're managing the tunnel in connlib?
|
||||
|
||||
@MainActor
|
||||
final class TunnelStore: ObservableObject {
|
||||
private static let logger = Logger.make(for: TunnelStore.self)
|
||||
|
||||
@@ -27,6 +26,12 @@ final class TunnelStore: ObservableObject {
|
||||
didSet { TunnelStore.logger.info("isEnabled changed: \(self.isEnabled.description)") }
|
||||
}
|
||||
|
||||
@Published private(set) var resources = DisplayableResources()
|
||||
|
||||
private var resourcesTimer: Timer? {
|
||||
didSet(oldValue) { oldValue?.invalidate() }
|
||||
}
|
||||
|
||||
private var tunnelObservingTasks: [Task<Void, Never>] = []
|
||||
|
||||
init(tunnel: NETunnelProviderManager) {
|
||||
@@ -71,6 +76,37 @@ final class TunnelStore: ObservableObject {
|
||||
session.stopTunnel()
|
||||
}
|
||||
|
||||
func beginUpdatingResources() {
|
||||
self.updateResources()
|
||||
let timer = Timer(timeInterval: 1 /*second*/, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard self.status == .connected else { return }
|
||||
self.updateResources()
|
||||
}
|
||||
RunLoop.main.add(timer, forMode: .common)
|
||||
self.resourcesTimer = timer
|
||||
}
|
||||
|
||||
func endUpdatingResources() {
|
||||
self.resourcesTimer = nil
|
||||
}
|
||||
|
||||
private func updateResources() {
|
||||
let session = tunnel.connection as! NETunnelProviderSession
|
||||
let resourcesQuery = resources.versionStringToData()
|
||||
do {
|
||||
try session.sendProviderMessage(resourcesQuery) { [weak self] reply in
|
||||
if let reply = reply { // If reply is nil, then the resources have not changed
|
||||
if let updatedResources = DisplayableResources(from: reply) {
|
||||
self?.resources = updatedResources
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
TunnelStore.logger.error("Error: sendProviderMessage: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private static func makeManager() -> NETunnelProviderManager {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class MenuBar {
|
||||
public final class MenuBar: NSObject {
|
||||
let logger = Logger.make(for: MenuBar.self)
|
||||
@Dependency(\.mainQueue) private var mainQueue
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
private var statusItem: NSStatusItem
|
||||
private var orderedResources: [DisplayableResources.Resource] = []
|
||||
private var isMenuVisible = false {
|
||||
didSet { handleMenuVisibilityOrStatusChanged() }
|
||||
}
|
||||
private lazy var disconnectedIcon = NSImage(named: "MenuBarIconDisconnected")
|
||||
private lazy var connectedIcon = NSImage(named: "MenuBarIconConnected")
|
||||
|
||||
let settingsViewModel: SettingsViewModel
|
||||
|
||||
@@ -37,16 +43,13 @@
|
||||
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
if let button = statusItem.button {
|
||||
button.image = NSImage(
|
||||
// TODO: Replace with AppIcon when it exists
|
||||
systemSymbolName: "circle",
|
||||
accessibilityDescription: "Firezone icon"
|
||||
)
|
||||
}
|
||||
|
||||
super.init()
|
||||
createMenu()
|
||||
|
||||
if let button = statusItem.button {
|
||||
button.image = disconnectedIcon
|
||||
}
|
||||
|
||||
Task {
|
||||
let tunnel = try await TunnelStore.loadOrCreate()
|
||||
self.appStore = AppStore(tunnelStore: TunnelStore(tunnel: tunnel))
|
||||
@@ -70,9 +73,23 @@
|
||||
.sink { [weak self] status in
|
||||
if status == .connected {
|
||||
self?.connectionMenuItem.title = "Disconnect"
|
||||
self?.statusItem.button?.image = self?.connectedIcon
|
||||
} else {
|
||||
self?.connectionMenuItem.title = "Connect"
|
||||
self?.statusItem.button?.image = self?.disconnectedIcon
|
||||
}
|
||||
self?.handleMenuVisibilityOrStatusChanged()
|
||||
if status != .connected {
|
||||
self?.setOrderedResources([])
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appStore?.tunnel.$resources
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] resources in
|
||||
guard let self = self else { return }
|
||||
self.setOrderedResources(resources.orderedResources)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
@@ -83,6 +100,7 @@
|
||||
menu,
|
||||
title: "Connect",
|
||||
action: #selector(connectButtonTapped),
|
||||
isHidden: true,
|
||||
target: self
|
||||
)
|
||||
|
||||
@@ -99,35 +117,62 @@
|
||||
isHidden: true,
|
||||
target: self
|
||||
)
|
||||
private lazy var resourcesTitleMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "No Resources",
|
||||
action: nil,
|
||||
isHidden: false,
|
||||
target: self
|
||||
)
|
||||
private lazy var resourcesSeparatorMenuItem = NSMenuItem.separator()
|
||||
private lazy var aboutMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "About",
|
||||
action: #selector(aboutButtonTapped),
|
||||
target: self
|
||||
)
|
||||
private lazy var settingsMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Settings",
|
||||
action: #selector(settingsButtonTapped),
|
||||
target: self
|
||||
)
|
||||
private lazy var quitMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Quit",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
key: "q",
|
||||
target: nil
|
||||
)
|
||||
private lazy var quitMenuItem: NSMenuItem = {
|
||||
let menuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Quit",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
key: "q",
|
||||
target: nil
|
||||
)
|
||||
if let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String {
|
||||
menuItem.title = "Quit \(appName)"
|
||||
}
|
||||
return menuItem
|
||||
}()
|
||||
|
||||
private func createMenu() {
|
||||
menu.addItem(connectionMenuItem)
|
||||
menu.addItem(loginMenuItem)
|
||||
menu.addItem(logoutMenuItem)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
menu.addItem(resourcesTitleMenuItem)
|
||||
menu.addItem(resourcesSeparatorMenuItem)
|
||||
|
||||
menu.addItem(aboutMenuItem)
|
||||
menu.addItem(settingsMenuItem)
|
||||
menu.addItem(quitMenuItem)
|
||||
|
||||
menu.delegate = self
|
||||
|
||||
statusItem.menu = menu
|
||||
}
|
||||
|
||||
private func createMenuItem(
|
||||
_: NSMenu,
|
||||
title: String,
|
||||
action: Selector,
|
||||
action: Selector?,
|
||||
isHidden: Bool = false,
|
||||
key: String = "",
|
||||
target: AnyObject?
|
||||
@@ -136,6 +181,7 @@
|
||||
|
||||
item.isHidden = isHidden
|
||||
item.target = target
|
||||
item.isEnabled = (action != nil)
|
||||
|
||||
return item
|
||||
}
|
||||
@@ -148,7 +194,6 @@
|
||||
}
|
||||
loginMenuItem.target = nil
|
||||
logoutMenuItem.isHidden = false
|
||||
connectionMenuItem.isHidden = false
|
||||
}
|
||||
|
||||
private func showLoggedOut() {
|
||||
@@ -156,7 +201,6 @@
|
||||
loginMenuItem.target = self
|
||||
|
||||
logoutMenuItem.isHidden = true
|
||||
connectionMenuItem.isHidden = true
|
||||
}
|
||||
|
||||
@objc private func connectButtonTapped() {
|
||||
@@ -195,8 +239,81 @@
|
||||
openSettingsWindow()
|
||||
}
|
||||
|
||||
@objc private func aboutButtonTapped() {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
NSApp.orderFrontStandardAboutPanel(self)
|
||||
}
|
||||
|
||||
private func openSettingsWindow() {
|
||||
NSWorkspace.shared.open(URL(string: "firezone://settings")!)
|
||||
}
|
||||
|
||||
private func handleMenuVisibilityOrStatusChanged() {
|
||||
guard let appStore = appStore else { return }
|
||||
let status = appStore.tunnel.status
|
||||
if isMenuVisible && status == .connected {
|
||||
appStore.tunnel.beginUpdatingResources()
|
||||
} else {
|
||||
appStore.tunnel.endUpdatingResources()
|
||||
}
|
||||
resourcesTitleMenuItem.isHidden = (status != .connected)
|
||||
resourcesSeparatorMenuItem.isHidden = (status != .connected)
|
||||
}
|
||||
|
||||
private func setOrderedResources(_ newOrderedResources: [DisplayableResources.Resource]) {
|
||||
let diff = newOrderedResources.difference(
|
||||
from: self.orderedResources,
|
||||
by: { $0.name == $1.name && $0.location == $1.location }
|
||||
)
|
||||
let baseIndex = menu.index(of: resourcesTitleMenuItem) + 1
|
||||
for change in diff {
|
||||
switch change {
|
||||
case .insert(offset: let offset, element: let element, associatedWith: _):
|
||||
let menuItem = createResourceMenuItem(title: element.name, submenuTitle: element.location)
|
||||
menu.insertItem(menuItem, at: baseIndex + offset)
|
||||
orderedResources.insert(element, at: offset)
|
||||
case .remove(offset: let offset, element: _, associatedWith: _):
|
||||
menu.removeItem(at: baseIndex + offset)
|
||||
orderedResources.remove(at: offset)
|
||||
}
|
||||
}
|
||||
resourcesTitleMenuItem.title = orderedResources.isEmpty ? "No Resources" : "Resources"
|
||||
}
|
||||
|
||||
private func createResourceMenuItem(title: String, submenuTitle: String) -> NSMenuItem {
|
||||
let item = NSMenuItem(title: title, action: nil, keyEquivalent: "")
|
||||
|
||||
let subMenu = NSMenu()
|
||||
let subMenuItem = NSMenuItem(title: submenuTitle, action: #selector(resourceValueTapped(_:)), keyEquivalent: "")
|
||||
subMenuItem.isEnabled = true
|
||||
subMenuItem.target = self
|
||||
subMenu.addItem(subMenuItem)
|
||||
|
||||
item.isHidden = false
|
||||
item.submenu = subMenu
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
@objc private func resourceValueTapped(_ sender: AnyObject?) {
|
||||
if let value = (sender as? NSMenuItem)?.title {
|
||||
copyToClipboard(value)
|
||||
}
|
||||
}
|
||||
|
||||
private func copyToClipboard(_ string: String) {
|
||||
let pasteBoard = NSPasteboard.general
|
||||
pasteBoard.clearContents()
|
||||
pasteBoard.writeObjects([string as NSString])
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuBar: NSMenuDelegate {
|
||||
public func menuNeedsUpdate(_ menu: NSMenu) {
|
||||
isMenuVisible = true
|
||||
}
|
||||
public func menuDidClose(_ menu: NSMenu) {
|
||||
isMenuVisible = false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import FirezoneKit
|
||||
import os.log
|
||||
|
||||
public enum AdapterError: Error {
|
||||
@@ -31,11 +32,7 @@ private enum State {
|
||||
public class Adapter {
|
||||
private let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
|
||||
|
||||
// Maintain a handle to the currently instantiated tunnel adapter 🤮
|
||||
public static var currentAdapter: Adapter?
|
||||
|
||||
// Maintain a reference to the initialized callback handler
|
||||
public static var callbackHandler: CallbackHandler?
|
||||
private var callbackHandler: CallbackHandler
|
||||
|
||||
// Latest applied NETunnelProviderNetworkSettings
|
||||
public var lastNetworkSettings: NEPacketTunnelNetworkSettings?
|
||||
@@ -52,19 +49,16 @@ public class Adapter {
|
||||
/// Adapter state.
|
||||
private var state: State = .stopped
|
||||
|
||||
/// Keep track of resources
|
||||
private var displayableResources = DisplayableResources()
|
||||
|
||||
public init(with packetTunnelProvider: NEPacketTunnelProvider) {
|
||||
self.packetTunnelProvider = packetTunnelProvider
|
||||
|
||||
// There must be a better way than making this a static class var...
|
||||
Self.currentAdapter = self
|
||||
Self.callbackHandler = CallbackHandler()
|
||||
Self.callbackHandler?.delegate = self
|
||||
self.callbackHandler = CallbackHandler()
|
||||
self.callbackHandler.delegate = self
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Remove static var reference
|
||||
Self.currentAdapter = nil
|
||||
|
||||
// Cancel network monitor
|
||||
networkMonitor?.cancel()
|
||||
|
||||
@@ -94,7 +88,7 @@ public class Adapter {
|
||||
do {
|
||||
try self.setNetworkSettings(self.generateNetworkSettings(ipv4Routes: [], ipv6Routes: []))
|
||||
self.state = .started(
|
||||
try WrappedSession.connect("http://localhost:4568", "test-token", Self.callbackHandler!)
|
||||
try WrappedSession.connect("http://localhost:4568", "test-token", self.callbackHandler)
|
||||
)
|
||||
self.networkMonitor = networkMonitor
|
||||
completionHandler(nil)
|
||||
@@ -129,6 +123,17 @@ public class Adapter {
|
||||
}
|
||||
}
|
||||
|
||||
public func getDisplayableResourcesIfVersionDifferentFrom(
|
||||
referenceVersionString: String, completionHandler: @escaping (DisplayableResources?) -> Void) {
|
||||
workQueue.async {
|
||||
if referenceVersionString == self.displayableResources.versionString {
|
||||
completionHandler(nil)
|
||||
} else {
|
||||
completionHandler(self.displayableResources)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func generateNetworkSettings(
|
||||
addresses4: [String] = ["100.100.111.2"], addresses6: [String] = ["fd00:0222:2011:1111::2"],
|
||||
ipv4Routes: [NEIPv4Route], ipv6Routes: [NEIPv6Route]
|
||||
@@ -254,14 +259,16 @@ public class Adapter {
|
||||
private func didReceivePathUpdate(path: Network.NWPath) {
|
||||
#if os(macOS)
|
||||
if case .started(let wrappedSession) = self.state {
|
||||
wrappedSession.bumpSockets()
|
||||
self.logger.log(level: .debug, "Suppressing call to bumpSockets()")
|
||||
// wrappedSession.bumpSockets()
|
||||
}
|
||||
#elseif os(iOS)
|
||||
switch self.state {
|
||||
case .started(let wrappedSession):
|
||||
if path.status == .satisfied {
|
||||
wrappedSession.disableSomeRoamingForBrokenMobileSemantics()
|
||||
wrappedSession.bumpSockets()
|
||||
self.logger.log(level: .debug, "Suppressing calls to disableSomeRoamingForBrokenMobileSemantics() and bumpSockets()")
|
||||
// wrappedSession.disableSomeRoamingForBrokenMobileSemantics()
|
||||
// wrappedSession.bumpSockets()
|
||||
} else {
|
||||
//self.logger.log(.debug, "Connectivity offline, pausing backend.")
|
||||
self.state = .temporaryShutdown
|
||||
@@ -277,7 +284,7 @@ public class Adapter {
|
||||
try self.setNetworkSettings(self.lastNetworkSettings!)
|
||||
|
||||
self.state = .started(
|
||||
try WrappedSession.connect("http://localhost:4568", "test-token", Self.callbackHandler!)
|
||||
try WrappedSession.connect("http://localhost:4568", "test-token", self.callbackHandler)
|
||||
)
|
||||
} catch {
|
||||
self.logger.log(level: .debug, "Failed to restart backend: \(error.localizedDescription)")
|
||||
@@ -294,72 +301,41 @@ public class Adapter {
|
||||
}
|
||||
|
||||
extension Adapter: CallbackHandlerDelegate {
|
||||
public func onConnect(tunnelAddressIPv4: String, tunnelAddressIPv6: String) {
|
||||
let addresses4 = [tunnelAddressIPv4]
|
||||
let addresses6 = [tunnelAddressIPv6]
|
||||
let ipv4Routes =
|
||||
Adapter.currentAdapter?.lastNetworkSettings?.ipv4Settings?.includedRoutes ?? []
|
||||
let ipv6Routes =
|
||||
Adapter.currentAdapter?.lastNetworkSettings?.ipv6Settings?.includedRoutes ?? []
|
||||
|
||||
_ = setTunnelSettingsKeepingSomeExisting(
|
||||
addresses4: addresses4, addresses6: addresses6, ipv4Routes: ipv4Routes, ipv6Routes: ipv6Routes
|
||||
)
|
||||
}
|
||||
|
||||
public func onUpdateResources(resourceList: String) {
|
||||
let addresses4 =
|
||||
self.lastNetworkSettings?.ipv4Settings?.addresses ?? ["100.100.111.2"]
|
||||
let addresses6 =
|
||||
self.lastNetworkSettings?.ipv6Settings?.addresses ?? [
|
||||
"fd00:0222:2021:1111::2"
|
||||
]
|
||||
|
||||
// TODO: Use actual passed in resources to achieve split tunnel
|
||||
let ipv4Routes = [NEIPv4Route(destinationAddress: "100.64.0.0", subnetMask: "255.192.0.0")]
|
||||
let ipv6Routes = [
|
||||
NEIPv6Route(destinationAddress: "fd00:0222:2021:1111::0", networkPrefixLength: 64)
|
||||
]
|
||||
|
||||
_ = setTunnelSettingsKeepingSomeExisting(
|
||||
addresses4: addresses4, addresses6: addresses6, ipv4Routes: ipv4Routes, ipv6Routes: ipv6Routes
|
||||
)
|
||||
}
|
||||
|
||||
public func onDisconnect() {
|
||||
public func onSetInterfaceConfig(tunnelAddressIPv4: String, tunnelAddressIPv6: String, dnsAddress: String) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
public func onError(error: Error, isRecoverable: Bool) {
|
||||
let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
|
||||
logger.log(level: .error, "Internal connlib error: \(String(describing: error), privacy: .public)")
|
||||
public func onTunnelReady() {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
private func setTunnelSettingsKeepingSomeExisting(
|
||||
addresses4: [String], addresses6: [String], ipv4Routes: [NEIPv4Route], ipv6Routes: [NEIPv6Route]
|
||||
) -> Bool {
|
||||
let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
|
||||
public func onAddRoute(_: String) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
do {
|
||||
/* If the tunnel interface addresses are being updated, it's impossible for the tunnel to
|
||||
stay up due to the way WireGuard works. Still, we try not to change the tunnel's routes
|
||||
here Just In Case™.
|
||||
*/
|
||||
try self.setNetworkSettings(
|
||||
self.generateNetworkSettings(
|
||||
addresses4: addresses4,
|
||||
addresses6: addresses6,
|
||||
ipv4Routes: ipv4Routes,
|
||||
ipv6Routes: ipv6Routes
|
||||
)
|
||||
)
|
||||
public func onRemoveRoute(_: String) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
return true
|
||||
} catch let error {
|
||||
logger.log(level: .debug, "Error setting adapter settings: \(String(describing: error))")
|
||||
|
||||
return false
|
||||
public func onUpdateResources(resourceList: String) {
|
||||
workQueue.async {
|
||||
let jsonString = "[\(resourceList)]"
|
||||
guard let jsonData = jsonString.data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
guard let networkResources = try? JSONDecoder().decode([NetworkResource].self, from: jsonData) else {
|
||||
return
|
||||
}
|
||||
self.displayableResources.update(resources: networkResources.map { $0.displayableResource })
|
||||
}
|
||||
}
|
||||
|
||||
public func onDisconnect(error: Error) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
public func onError(error: Error) {
|
||||
let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
|
||||
logger.log(level: .error, "Internal connlib error: \(String(describing: error), privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,5 @@
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,11 +8,7 @@
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
79
swift/apple/FirezoneNetworkExtension/NetworkResource.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// NetworkResource.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct NetworkResource: Decodable {
|
||||
enum ResourceLocation {
|
||||
case dns(domain: String, ipv4: String, ipv6: String)
|
||||
case cidr(cidrAddress: String)
|
||||
|
||||
func toString() -> String {
|
||||
switch self {
|
||||
case .dns(let domain, ipv4: _, ipv6: _): return domain
|
||||
case .cidr(let cidrAddress): return cidrAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name: String
|
||||
let resourceLocation: ResourceLocation
|
||||
|
||||
var displayableResource: (name: String, location: String) {
|
||||
(name: name, location: resourceLocation.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// A DNS resource example:
|
||||
// {
|
||||
// "type": "dns",
|
||||
// "address": "app.posthog.com",
|
||||
// "name": "PostHog",
|
||||
// "ipv4": "100.64.0.1",
|
||||
// "ipv6": "fd00:2021:11111::1"
|
||||
// }
|
||||
//
|
||||
// A CIDR resource example:
|
||||
// {
|
||||
// "type": "cidr",
|
||||
// "address": "10.0.0.0/24",
|
||||
// "name": "AWS SJC VPC1",
|
||||
// }
|
||||
|
||||
extension NetworkResource {
|
||||
enum ResourceKeys: String, CodingKey {
|
||||
case type
|
||||
case address
|
||||
case name
|
||||
case ipv4
|
||||
case ipv6
|
||||
}
|
||||
|
||||
enum DecodeError: Error {
|
||||
case invalidType(String)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: ResourceKeys.self)
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
let type = try container.decode(String.self, forKey: .type)
|
||||
let resourceLocation: ResourceLocation = try {
|
||||
switch type {
|
||||
case "dns":
|
||||
let domain = try container.decode(String.self, forKey: .address)
|
||||
let ipv4 = try container.decode(String.self, forKey: .ipv4)
|
||||
let ipv6 = try container.decode(String.self, forKey: .ipv6)
|
||||
return .dns(domain: domain, ipv4: ipv4, ipv6: ipv6)
|
||||
case "cidr":
|
||||
let address = try container.decode(String.self, forKey: .address)
|
||||
return .cidr(cidrAddress: address)
|
||||
default:
|
||||
throw DecodeError.invalidType(type)
|
||||
}
|
||||
}()
|
||||
self.init(name: name, resourceLocation: resourceLocation)
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
let query = String(data: messageData, encoding: .utf8) ?? ""
|
||||
adapter.getDisplayableResourcesIfVersionDifferentFrom(referenceVersionString: query) { displayableResources in
|
||||
completionHandler?(displayableResources?.toData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||