mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(apple): permit resources to be disabled (#6215)
Work for #6074 equivalent to #6166 for MacOS MacOs view: <img width="547" alt="image" src="https://github.com/user-attachments/assets/f465183e-247b-49b5-a916-3ecc5f0a02f4"> iOS(ipad) view:  Other than implementing the resource disabling, this PR also refactor the IPC between the network extension and the app so that it's some form of structured IPC instead of relying on it being deserializable to string to match the message. One big difference with Android is that we don't introduce the concept of a `ResourceView` for swift, the main reason for this is that on iOS the resources are bound to the view instead of just being a parameter for creating the view. So if we modify the `disabled` property it'd update the UI unnecessarily, also it'd update the `Store` value for the resource and then we need to copy that over again to the view. Making it easier to go out of sync.
This commit is contained in:
@@ -60,6 +60,9 @@ mod ffi {
|
||||
// <https://github.com/firezone/firezone/issues/4350>
|
||||
#[swift_bridge(swift_name = "setDns")]
|
||||
fn set_dns(&mut self, dns_servers: String);
|
||||
|
||||
#[swift_bridge(swift_name = "setDisabledResources")]
|
||||
fn set_disabled_resources(&mut self, disabled_resources: String);
|
||||
fn disconnect(self);
|
||||
}
|
||||
|
||||
@@ -234,6 +237,11 @@ impl WrappedSession {
|
||||
.set_dns(serde_json::from_str(&dns_servers).unwrap())
|
||||
}
|
||||
|
||||
fn set_disabled_resources(&mut self, disabled_resources: String) {
|
||||
self.inner
|
||||
.set_disabled_resources(serde_json::from_str(&disabled_resources).unwrap())
|
||||
}
|
||||
|
||||
fn disconnect(self) {
|
||||
self.inner.disconnect()
|
||||
}
|
||||
|
||||
@@ -20,9 +20,55 @@ public enum TunnelManagerKeys {
|
||||
static let authBaseURL = "authBaseURL"
|
||||
static let apiURL = "apiURL"
|
||||
public static let logFilter = "logFilter"
|
||||
public static let disabledResources = "disabledResources"
|
||||
}
|
||||
|
||||
class TunnelManager {
|
||||
public enum TunnelMessage: Codable {
|
||||
case getResourceList(Data)
|
||||
case signOut
|
||||
case setDisabledResources(Set<String>)
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case value
|
||||
}
|
||||
|
||||
enum MessageType: String, Codable {
|
||||
case getResourceList
|
||||
case signOut
|
||||
case setDisabledResources
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(MessageType.self, forKey: .type)
|
||||
switch type {
|
||||
case .setDisabledResources:
|
||||
let value = try container.decode(Set<String>.self, forKey: .value)
|
||||
self = .setDisabledResources(value)
|
||||
case .getResourceList:
|
||||
let value = try container.decode(Data.self, forKey: .value)
|
||||
self = .getResourceList(value)
|
||||
case .signOut:
|
||||
self = .signOut
|
||||
}
|
||||
}
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .setDisabledResources(let value):
|
||||
try container.encode(MessageType.setDisabledResources, forKey: .type)
|
||||
try container.encode(value, forKey: .value)
|
||||
case .getResourceList(let value):
|
||||
try container.encode(MessageType.getResourceList, forKey: .type)
|
||||
try container.encode(value, forKey: .value)
|
||||
case .signOut:
|
||||
try container.encode(MessageType.signOut, forKey: .type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TunnelManager {
|
||||
// Expose closures that someone else can use to respond to events
|
||||
// for this manager.
|
||||
var statusChangeHandler: ((NEVPNStatus) async -> Void)?
|
||||
@@ -36,11 +82,17 @@ class TunnelManager {
|
||||
|
||||
// Cache resources on this side of the IPC barrier so we can
|
||||
// return them to callers when they haven't changed.
|
||||
private var resourcesListCache = Data()
|
||||
private var resourcesListCache: [Resource] = []
|
||||
|
||||
// Persists our tunnel settings
|
||||
private var manager: NETunnelProviderManager?
|
||||
|
||||
// Resources that are currently disabled and will not be used
|
||||
public var disabledResources: Set<String> = []
|
||||
|
||||
// Encoder used to send messages to the tunnel
|
||||
private let encoder = PropertyListEncoder()
|
||||
|
||||
// Use separate bundle IDs for release and debug.
|
||||
// Helps with testing releases and dev builds on the same Mac.
|
||||
#if DEBUG
|
||||
@@ -69,6 +121,7 @@ class TunnelManager {
|
||||
protocolConfiguration.serverAddress = settings.apiURL
|
||||
manager.localizedDescription = bundleDescription
|
||||
manager.protocolConfiguration = protocolConfiguration
|
||||
encoder.outputFormat = .binary
|
||||
|
||||
// Save the new VPN profile to System Preferences and reload it,
|
||||
// which should update our status from invalid -> disconnected.
|
||||
@@ -101,6 +154,10 @@ class TunnelManager {
|
||||
// Found it
|
||||
let settings = Settings.fromProviderConfiguration(providerConfiguration)
|
||||
let actorName = providerConfiguration[TunnelManagerKeys.actorName]
|
||||
if let disabledResourcesData = providerConfiguration[TunnelManagerKeys.disabledResources]?.data(using: .utf8) {
|
||||
self.disabledResources = (try? JSONDecoder().decode(Set<String>.self, from: disabledResourcesData)) ?? Set()
|
||||
|
||||
}
|
||||
let status = manager.connection.status
|
||||
|
||||
// Share what we found with our caller
|
||||
@@ -188,7 +245,7 @@ class TunnelManager {
|
||||
func stop(clearToken: Bool = false) {
|
||||
if clearToken {
|
||||
do {
|
||||
try session().sendProviderMessage("signOut".data(using: .utf8)!) { _ in
|
||||
try session().sendProviderMessage(encoder.encode(TunnelMessage.signOut)) { _ in
|
||||
self.session().stopTunnel()
|
||||
}
|
||||
} catch {
|
||||
@@ -199,14 +256,32 @@ class TunnelManager {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchResources(callback: @escaping (Data) -> Void) {
|
||||
func updateDisabledResources() {
|
||||
guard session().status == .connected else { return }
|
||||
|
||||
try? session().sendProviderMessage(encoder.encode(TunnelMessage.setDisabledResources(disabledResources))) { _ in }
|
||||
}
|
||||
|
||||
func toggleResourceDisabled(resource: String, enabled: Bool) {
|
||||
if enabled {
|
||||
disabledResources.remove(resource)
|
||||
} else {
|
||||
disabledResources.insert(resource)
|
||||
}
|
||||
|
||||
updateDisabledResources()
|
||||
}
|
||||
|
||||
func fetchResources(callback: @escaping ([Resource]) -> Void) {
|
||||
guard session().status == .connected else { return }
|
||||
|
||||
do {
|
||||
try session().sendProviderMessage(resourceListHash) { data in
|
||||
try session().sendProviderMessage(encoder.encode(TunnelMessage.getResourceList(resourceListHash))) { data in
|
||||
if let data = data {
|
||||
self.resourceListHash = Data(SHA256.hash(data: data))
|
||||
self.resourcesListCache = data
|
||||
var decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
self.resourcesListCache = (try? decoder.decode([Resource].self, from: data)) ?? []
|
||||
}
|
||||
|
||||
callback(self.resourcesListCache)
|
||||
@@ -248,7 +323,7 @@ class TunnelManager {
|
||||
if session.status == .disconnected {
|
||||
// Reset resource list on disconnect
|
||||
resourceListHash = Data()
|
||||
resourcesListCache = Data()
|
||||
resourcesListCache = []
|
||||
}
|
||||
|
||||
await statusChangeHandler?(session.status)
|
||||
|
||||
@@ -16,8 +16,9 @@ public struct Resource: Decodable, Identifiable, Equatable {
|
||||
public var status: ResourceStatus
|
||||
public var sites: [Site]
|
||||
public var type: ResourceType
|
||||
public var canToggle: Bool
|
||||
|
||||
public init(id: String, name: String, address: String, addressDescription: String?, status: ResourceStatus, sites: [Site], type: ResourceType) {
|
||||
public init(id: String, name: String, address: String, addressDescription: String?, status: ResourceStatus, sites: [Site], type: ResourceType, canToggle: Bool) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.address = address
|
||||
@@ -25,6 +26,7 @@ public struct Resource: Decodable, Identifiable, Equatable {
|
||||
self.status = status
|
||||
self.sites = sites
|
||||
self.type = type
|
||||
self.canToggle = canToggle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ struct Settings: Equatable {
|
||||
var authBaseURL: String
|
||||
var apiURL: String
|
||||
var logFilter: String
|
||||
var disabledResources: Set<String>
|
||||
|
||||
var isValid: Bool {
|
||||
let authBaseURL = URL(string: authBaseURL)
|
||||
@@ -26,6 +27,7 @@ struct Settings: Equatable {
|
||||
&& !logFilter.isEmpty
|
||||
}
|
||||
|
||||
|
||||
// Convert provider configuration (which may have empty fields if it was tampered with) to Settings
|
||||
static func fromProviderConfiguration(_ providerConfiguration: [String: Any]?) -> Settings {
|
||||
if let providerConfiguration = providerConfiguration as? [String: String] {
|
||||
@@ -35,19 +37,29 @@ struct Settings: Equatable {
|
||||
apiURL: providerConfiguration[TunnelManagerKeys.apiURL]
|
||||
?? Settings.defaultValue.apiURL,
|
||||
logFilter: providerConfiguration[TunnelManagerKeys.logFilter]
|
||||
?? Settings.defaultValue.logFilter
|
||||
?? Settings.defaultValue.logFilter,
|
||||
disabledResources: getDisabledResources(disabledResources: providerConfiguration[TunnelManagerKeys.disabledResources])
|
||||
)
|
||||
} else {
|
||||
return Settings.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
static private func getDisabledResources(disabledResources: String?) -> Set<String> {
|
||||
guard let disabledResourcesJSON = disabledResources, let disabledResourcesData = disabledResourcesJSON.data(using: .utf8) else{
|
||||
return Set()
|
||||
}
|
||||
return (try? JSONDecoder().decode(Set<String>.self, from: disabledResourcesData))
|
||||
?? Settings.defaultValue.disabledResources
|
||||
}
|
||||
|
||||
// Used for initializing a new providerConfiguration from Settings
|
||||
func toProviderConfiguration() -> [String: String] {
|
||||
return [
|
||||
"authBaseURL": authBaseURL,
|
||||
"apiURL": apiURL,
|
||||
"logFilter": logFilter,
|
||||
TunnelManagerKeys.authBaseURL: authBaseURL,
|
||||
TunnelManagerKeys.apiURL: apiURL,
|
||||
TunnelManagerKeys.logFilter: logFilter,
|
||||
TunnelManagerKeys.disabledResources: String(data: try! JSONEncoder().encode(disabledResources), encoding: .utf8) ?? "",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -59,13 +71,15 @@ struct Settings: Equatable {
|
||||
authBaseURL: "https://app.firez.one",
|
||||
apiURL: "wss://api.firez.one",
|
||||
logFilter:
|
||||
"firezone_tunnel=debug,phoenix_channel=debug,connlib_shared=debug,connlib_client_shared=debug,snownet=debug,str0m=info,warn"
|
||||
"firezone_tunnel=debug,phoenix_channel=debug,connlib_shared=debug,connlib_client_shared=debug,snownet=debug,str0m=info,warn",
|
||||
disabledResources: Set()
|
||||
)
|
||||
#else
|
||||
Settings(
|
||||
authBaseURL: "https://app.firezone.dev",
|
||||
apiURL: "wss://api.firezone.dev",
|
||||
logFilter: "str0m=warn,info"
|
||||
logFilter: "str0m=warn,info",
|
||||
disabledResources: Set()
|
||||
)
|
||||
#endif
|
||||
}()
|
||||
|
||||
@@ -45,6 +45,10 @@ public final class Store: ObservableObject {
|
||||
initTunnelManager()
|
||||
}
|
||||
|
||||
public func isResourceEnabled(_ id: String) -> Bool {
|
||||
!tunnelManager.disabledResources.contains(id)
|
||||
}
|
||||
|
||||
private func initNotifications() {
|
||||
// Finish initializing notification binding
|
||||
sessionNotification.signInHandler = {
|
||||
@@ -138,7 +142,7 @@ public final class Store: ObservableObject {
|
||||
|
||||
// Network Extensions don't have a 2-way binding up to the GUI process,
|
||||
// so we need to periodically ask the tunnel process for them.
|
||||
func beginUpdatingResources(callback: @escaping (Data) -> Void) {
|
||||
func beginUpdatingResources(callback: @escaping ([Resource]) -> Void) {
|
||||
Log.app.log("\(#function)")
|
||||
|
||||
tunnelManager.fetchResources(callback: callback)
|
||||
@@ -167,6 +171,15 @@ public final class Store: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func toggleResourceDisabled(resource: String, enabled: Bool) {
|
||||
tunnelManager.toggleResourceDisabled(resource: resource, enabled: enabled)
|
||||
var newSettings = settings
|
||||
newSettings.disabledResources = tunnelManager.disabledResources
|
||||
Task {
|
||||
try await save(newSettings)
|
||||
}
|
||||
}
|
||||
|
||||
// Handles the frequent VPN state changes during sign in, sign out, etc.
|
||||
private func handleVPNStatusChange(status: NEVPNStatus) async {
|
||||
self.status = status
|
||||
|
||||
@@ -59,15 +59,11 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
guard let self = self else { return }
|
||||
|
||||
if status == .connected {
|
||||
model.store.beginUpdatingResources { data in
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
if let newResources = try? decoder.decode([Resource].self, from: data) {
|
||||
model.store.beginUpdatingResources { newResources in
|
||||
// Handle resource changes
|
||||
self.populateResourceMenu(newResources)
|
||||
self.handleTunnelStatusOrResourcesChanged(status: status, resources: newResources)
|
||||
self.resources = newResources
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.store.endUpdatingResources()
|
||||
@@ -424,6 +420,10 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
return item
|
||||
}
|
||||
|
||||
private func resourceTitle(_ id: String) -> String {
|
||||
model.isResourceEnabled(id) ? "Disable this resource" : "Enable this resource"
|
||||
}
|
||||
|
||||
private func createSubMenu(resource: Resource) -> NSMenu {
|
||||
let subMenu = NSMenu()
|
||||
let resourceAddressDescriptionItem = NSMenuItem()
|
||||
@@ -433,6 +433,7 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
let siteSectionItem = NSMenuItem()
|
||||
let siteNameItem = NSMenuItem()
|
||||
let siteStatusItem = NSMenuItem()
|
||||
let enableToggle = NSMenuItem()
|
||||
|
||||
|
||||
// AddressDescription first -- will be most common action
|
||||
@@ -484,6 +485,18 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
resourceAddressItem.target = self
|
||||
subMenu.addItem(resourceAddressItem)
|
||||
|
||||
// Resource toggle
|
||||
if resource.canToggle {
|
||||
subMenu.addItem(NSMenuItem.separator())
|
||||
enableToggle.action = #selector(resourceToggle(_:))
|
||||
enableToggle.title = resourceTitle(resource.id)
|
||||
enableToggle.toolTip = "Enable or disable resource"
|
||||
enableToggle.isEnabled = true
|
||||
enableToggle.target = self
|
||||
enableToggle.representedObject = resource.id
|
||||
subMenu.addItem(enableToggle)
|
||||
}
|
||||
|
||||
// Site details
|
||||
if let site = resource.sites.first {
|
||||
subMenu.addItem(NSMenuItem.separator())
|
||||
@@ -526,6 +539,13 @@ public final class MenuBar: NSObject, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func resourceToggle(_ sender: NSMenuItem) {
|
||||
let id = sender.representedObject as! String
|
||||
|
||||
self.model.store.toggleResourceDisabled(resource: id, enabled: !model.isResourceEnabled(id))
|
||||
sender.title = resourceTitle(id)
|
||||
}
|
||||
|
||||
@objc private func resourceURLTapped(_ sender: AnyObject?) {
|
||||
if let value = (sender as? NSMenuItem)?.title {
|
||||
// URL has already been validated
|
||||
|
||||
@@ -15,6 +15,7 @@ public final class SessionViewModel: ObservableObject {
|
||||
@Published private(set) var resources: [Resource]? = nil
|
||||
@Published private(set) var status: NEVPNStatus? = nil
|
||||
|
||||
|
||||
let store: Store
|
||||
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
@@ -40,10 +41,8 @@ public final class SessionViewModel: ObservableObject {
|
||||
self.status = status
|
||||
|
||||
if status == .connected {
|
||||
store.beginUpdatingResources() { data in
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
self.resources = try? decoder.decode([Resource].self, from: data)
|
||||
store.beginUpdatingResources() { resources in
|
||||
self.resources = resources
|
||||
}
|
||||
} else {
|
||||
store.endUpdatingResources()
|
||||
@@ -54,6 +53,10 @@ public final class SessionViewModel: ObservableObject {
|
||||
#endif
|
||||
}
|
||||
|
||||
public func isResourceEnabled(_ resource: String) -> Bool {
|
||||
store.isResourceEnabled(resource)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@@ -69,9 +72,26 @@ struct SessionView: View {
|
||||
Text("No Resources. Contact your admin to be granted access.")
|
||||
} else {
|
||||
List(resources) { resource in
|
||||
NavigationLink(resource.name, destination: ResourceView(resource: resource))
|
||||
.navigationTitle("All Resources")
|
||||
HStack {
|
||||
NavigationLink { ResourceView(resource: resource) }
|
||||
label: {
|
||||
HStack {
|
||||
Text(resource.name)
|
||||
if resource.canToggle {
|
||||
Spacer()
|
||||
Toggle("Enabled", isOn: Binding<Bool>(
|
||||
get: { model.isResourceEnabled(resource.id) },
|
||||
set: { newValue in
|
||||
model.store.toggleResourceDisabled(resource: resource.id, enabled: newValue)
|
||||
}
|
||||
)).labelsHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("All Resources")
|
||||
}
|
||||
}
|
||||
|
||||
.listStyle(GroupedListStyle())
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -59,6 +59,12 @@ class Adapter {
|
||||
/// This is the primary async primitive used in this class.
|
||||
private let workQueue = DispatchQueue(label: "FirezoneAdapterWorkQueue")
|
||||
|
||||
/// Currently disabled resources
|
||||
private var disabledResources: Set<String> = []
|
||||
|
||||
/// Cache of resources that can be disabled
|
||||
private var canBeDisabled: Set<String> = []
|
||||
|
||||
/// Adapter state.
|
||||
private var state: AdapterState {
|
||||
didSet {
|
||||
@@ -79,6 +85,7 @@ class Adapter {
|
||||
apiURL: String,
|
||||
token: String,
|
||||
logFilter: String,
|
||||
disabledResources: Set<String>,
|
||||
packetTunnelProvider: PacketTunnelProvider
|
||||
) {
|
||||
self.apiURL = apiURL
|
||||
@@ -89,6 +96,7 @@ class Adapter {
|
||||
self.logFilter = logFilter
|
||||
self.connlibLogFolderPath = SharedAccess.connlibLogFolderURL?.path ?? ""
|
||||
self.networkSettings = nil
|
||||
self.disabledResources = disabledResources
|
||||
}
|
||||
|
||||
// Could happen abruptly if the process is killed.
|
||||
@@ -187,6 +195,35 @@ class Adapter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resources() -> [Resource] {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
guard let resourceList = resourceListJSON else { return [] }
|
||||
return (try? decoder.decode([Resource].self, from: resourceList.data(using: .utf8)!)) ?? []
|
||||
}
|
||||
|
||||
public func setDisabledResources(newDisabledResources: Set<String>) {
|
||||
workQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.disabledResources = newDisabledResources
|
||||
self.resourcesUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
public func resourcesUpdated() {
|
||||
guard case .tunnelStarted(let session) = self.state else { return }
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
canBeDisabled = Set(resources().filter({ $0.canToggle }).map({ $0.id }))
|
||||
|
||||
let disablingResources = disabledResources.filter({ canBeDisabled.contains($0) })
|
||||
|
||||
let currentlyDisabled = try! JSONEncoder().encode(disablingResources)
|
||||
session.setDisabledResources(String(data: currentlyDisabled, encoding: .utf8)!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Responding to path updates
|
||||
@@ -370,6 +407,8 @@ extension Adapter: CallbackHandlerDelegate {
|
||||
|
||||
// Update resource List. We don't care what's inside.
|
||||
resourceListJSON = resourceList
|
||||
|
||||
self.resourcesUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
return
|
||||
}
|
||||
|
||||
let disabledResources: Set<String> = if let disabledResourcesJSON = providerConfiguration[TunnelManagerKeys.disabledResources]?.data(using: .utf8) {
|
||||
(try? JSONDecoder().decode(Set<String>.self, from: disabledResourcesJSON )) ?? Set()
|
||||
} else {
|
||||
Set()
|
||||
}
|
||||
|
||||
let adapter = Adapter(
|
||||
apiURL: apiURL, token: token, logFilter: logFilter, packetTunnelProvider: self)
|
||||
apiURL: apiURL, token: token, logFilter: logFilter, disabledResources: disabledResources, packetTunnelProvider: self)
|
||||
self.adapter = adapter
|
||||
|
||||
|
||||
try adapter.start()
|
||||
|
||||
// Tell the system the tunnel is up, moving the tunnelManager status to
|
||||
@@ -128,37 +135,38 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
cancelTunnelWithError(nil)
|
||||
}
|
||||
|
||||
// TODO: Use a message format to allow requesting different types of data.
|
||||
// This currently assumes we're requesting resources.
|
||||
override func handleAppMessage(_ message: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
let string = String(data: message, encoding: .utf8)
|
||||
guard let tunnelMessage = try? PropertyListDecoder().decode(TunnelMessage.self, from: message) else { return }
|
||||
|
||||
switch string {
|
||||
case "signOut":
|
||||
switch tunnelMessage {
|
||||
case .setDisabledResources(let value):
|
||||
adapter?.setDisabledResources(newDisabledResources: value)
|
||||
case .signOut:
|
||||
Task {
|
||||
do {
|
||||
try await clearToken()
|
||||
} catch {
|
||||
Log.tunnel.error("\(#function): Error: \(error)")
|
||||
}
|
||||
await clearToken()
|
||||
}
|
||||
default:
|
||||
adapter?.getResourcesIfVersionDifferentFrom(hash: message) {
|
||||
case .getResourceList(let value):
|
||||
adapter?.getResourcesIfVersionDifferentFrom(hash: value) {
|
||||
resourceListJSON in
|
||||
completionHandler?(resourceListJSON?.data(using: .utf8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func clearToken() async throws {
|
||||
let keychain = Keychain()
|
||||
guard let ref = await keychain.search()
|
||||
else {
|
||||
Log.tunnel.error("\(#function): Error: token not found!")
|
||||
return
|
||||
}
|
||||
enum TokenError: Error {
|
||||
case TokenNotFound
|
||||
}
|
||||
|
||||
try await keychain.delete(persistentRef: ref)
|
||||
private func clearToken() async {
|
||||
do {
|
||||
let keychain = Keychain()
|
||||
guard let ref = await keychain.search() else {
|
||||
throw TokenError.TokenNotFound
|
||||
}
|
||||
try await keychain.delete(persistentRef: ref)
|
||||
} catch {
|
||||
Log.tunnel.error("\(#function): Error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user