diff --git a/Passepartout/App/Platforms/App+macOS.swift b/Passepartout/App/Platforms/App+macOS.swift
index 0657eac6..bc2044ce 100644
--- a/Passepartout/App/Platforms/App+macOS.swift
+++ b/Passepartout/App/Platforms/App+macOS.swift
@@ -83,10 +83,14 @@ extension PassepartoutApp {
.withEnvironment(from: context, theme: theme)
}
MenuBarExtra {
- AppMenu()
- .withEnvironment(from: context, theme: theme)
+ AppMenu(
+ profileManager: context.profileManager,
+ profileProcessor: context.profileProcessor,
+ tunnel: context.tunnel
+ )
+ .withEnvironment(from: context, theme: theme)
} label: {
- AppMenuImage(connectionObserver: context.connectionObserver)
+ AppMenuImage(tunnel: context.tunnel)
.environmentObject(theme)
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Business/AppContext.swift b/Passepartout/Library/Sources/AppUI/Business/AppContext.swift
index 735e1fb6..75f858fb 100644
--- a/Passepartout/Library/Sources/AppUI/Business/AppContext.swift
+++ b/Passepartout/Library/Sources/AppUI/Business/AppContext.swift
@@ -38,12 +38,10 @@ public final class AppContext: ObservableObject {
public let profileProcessor: ProfileProcessor
- public let tunnel: Tunnel
+ public let tunnel: ExtendedTunnel
public let tunnelEnvironment: TunnelEnvironment
- public let connectionObserver: ConnectionObserver
-
public let registry: Registry
public let providerManager: ProviderManager
@@ -65,9 +63,8 @@ public final class AppContext: ObservableObject {
self.iapManager = iapManager
self.profileManager = profileManager
self.profileProcessor = profileProcessor
- self.tunnel = tunnel
self.tunnelEnvironment = tunnelEnvironment
- connectionObserver = ConnectionObserver(
+ self.tunnel = ExtendedTunnel(
tunnel: tunnel,
environment: tunnelEnvironment,
interval: constants.tunnel.refreshInterval
@@ -79,7 +76,7 @@ public final class AppContext: ObservableObject {
Task {
await iapManager.reloadReceipt()
- connectionObserver.observeObjects()
+ self.tunnel.observeObjects()
profileManager.observeObjects()
observeObjects()
}
diff --git a/Passepartout/Library/Sources/AppUI/Business/ConnectionObserver.swift b/Passepartout/Library/Sources/AppUI/Business/ExtendedTunnel.swift
similarity index 59%
rename from Passepartout/Library/Sources/AppUI/Business/ConnectionObserver.swift
rename to Passepartout/Library/Sources/AppUI/Business/ExtendedTunnel.swift
index 32c02cc3..8f3051f5 100644
--- a/Passepartout/Library/Sources/AppUI/Business/ConnectionObserver.swift
+++ b/Passepartout/Library/Sources/AppUI/Business/ExtendedTunnel.swift
@@ -1,5 +1,5 @@
//
-// ConnectionObserver.swift
+// ExtendedTunnel.swift
// Passepartout
//
// Created by Davide De Rosa on 9/7/24.
@@ -29,8 +29,8 @@ import Foundation
import PassepartoutKit
@MainActor
-public final class ConnectionObserver: ObservableObject {
- public let tunnel: Tunnel
+public final class ExtendedTunnel: ObservableObject {
+ private let tunnel: Tunnel
private let environment: TunnelEnvironment
@@ -40,14 +40,10 @@ public final class ConnectionObserver: ObservableObject {
environment.environmentValue(forKey: key)
}
- public var connectionStatus: ConnectionStatus? {
- value(forKey: TunnelEnvironmentKeys.connectionStatus)
- }
-
@Published
public private(set) var lastErrorCode: PassepartoutError.Code? {
didSet {
- pp_log(.app, .info, "ConnectionObserver.lastErrorCode -> \(lastErrorCode?.rawValue ?? "nil")")
+ pp_log(.app, .info, "ExtendedTunnel.lastErrorCode -> \(lastErrorCode?.rawValue ?? "nil")")
}
}
@@ -103,3 +99,63 @@ public final class ConnectionObserver: ObservableObject {
.store(in: &subscriptions)
}
}
+
+extension ExtendedTunnel {
+ public var status: TunnelStatus {
+ tunnel.status
+ }
+
+ public var connectionStatus: TunnelStatus {
+ var status = tunnel.status
+ if status == .active, let environmentConnectionStatus {
+ if environmentConnectionStatus == .connected {
+ status = .active
+ } else {
+ status = .activating
+ }
+ }
+ return status
+ }
+
+ private var environmentConnectionStatus: ConnectionStatus? {
+ value(forKey: TunnelEnvironmentKeys.connectionStatus)
+ }
+}
+
+extension ExtendedTunnel {
+ public var currentProfile: TunnelCurrentProfile? {
+ tunnel.currentProfile
+ }
+
+ public func prepare(purge: Bool) async throws {
+ try await tunnel.prepare(purge: purge)
+ }
+
+ public func install(_ profile: Profile, processor: ProfileProcessor) async throws {
+ let newProfile = try processor.processed(profile)
+ try await tunnel.install(newProfile, connect: false, title: processor.title)
+ }
+
+ public func connect(with profile: Profile, processor: ProfileProcessor) async throws {
+ let newProfile = try processor.processed(profile)
+ try await tunnel.install(newProfile, connect: true, title: processor.title)
+ }
+
+ public func disconnect() async throws {
+ try await tunnel.disconnect()
+ }
+
+ public func currentLog(parameters: Constants.Log) async -> [String] {
+ let output = try? await tunnel.sendMessage(.localLog(
+ sinceLast: parameters.sinceLast,
+ maxLevel: parameters.maxLevel
+ ))
+ switch output {
+ case .debugLog(let log):
+ return log.lines.map(parameters.formatter.formattedLine)
+
+ default:
+ return []
+ }
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUIMain/Business/InteractiveManager.swift b/Passepartout/Library/Sources/AppUI/Business/InteractiveManager.swift
similarity index 77%
rename from Passepartout/Library/Sources/AppUIMain/Business/InteractiveManager.swift
rename to Passepartout/Library/Sources/AppUI/Business/InteractiveManager.swift
index 481d12c1..b8ab279e 100644
--- a/Passepartout/Library/Sources/AppUIMain/Business/InteractiveManager.swift
+++ b/Passepartout/Library/Sources/AppUI/Business/InteractiveManager.swift
@@ -27,23 +27,26 @@ import Foundation
import PassepartoutKit
@MainActor
-final class InteractiveManager: ObservableObject {
- typealias CompletionBlock = (Profile) async throws -> Void
+public final class InteractiveManager: ObservableObject {
+ public typealias CompletionBlock = (Profile) async throws -> Void
@Published
- var isPresented = false
+ public var isPresented = false
- private(set) var editor = ProfileEditor()
+ public private(set) var editor = ProfileEditor()
private var onComplete: CompletionBlock?
- func present(with profile: Profile, onComplete: CompletionBlock?) {
+ public init() {
+ }
+
+ public func present(with profile: Profile, onComplete: CompletionBlock?) {
editor = ProfileEditor(profile: profile)
self.onComplete = onComplete
isPresented = true
}
- func complete() async throws {
+ public func complete() async throws {
isPresented = false
let newProfile = try editor.build()
try await onComplete?(newProfile)
diff --git a/Passepartout/Library/Sources/AppUI/Extensions/Tunnel+Extensions.swift b/Passepartout/Library/Sources/AppUI/Extensions/Tunnel+Extensions.swift
deleted file mode 100644
index 732bc20c..00000000
--- a/Passepartout/Library/Sources/AppUI/Extensions/Tunnel+Extensions.swift
+++ /dev/null
@@ -1,55 +0,0 @@
-//
-// Tunnel+Extensions.swift
-// Passepartout
-//
-// Created by Davide De Rosa on 8/11/24.
-// Copyright (c) 2024 Davide De Rosa. All rights reserved.
-//
-// https://github.com/passepartoutvpn
-//
-// This file is part of Passepartout.
-//
-// Passepartout is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Passepartout is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Passepartout. If not, see .
-//
-
-import CommonLibrary
-import Foundation
-import PassepartoutKit
-
-@MainActor
-extension Tunnel {
- public func install(_ profile: Profile, processor: ProfileProcessor) async throws {
- let newProfile = try processor.processed(profile)
- try await install(newProfile, connect: false, title: processor.title)
- }
-
- public func connect(with profile: Profile, processor: ProfileProcessor) async throws {
- let newProfile = try processor.processed(profile)
- try await install(newProfile, connect: true, title: processor.title)
- }
-
- public func currentLog(parameters: Constants.Log) async -> [String] {
- let output = try? await sendMessage(.localLog(
- sinceLast: parameters.sinceLast,
- maxLevel: parameters.maxLevel
- ))
- switch output {
- case .debugLog(let log):
- return log.lines.map(parameters.formatter.formattedLine)
-
- default:
- return []
- }
- }
-}
diff --git a/Passepartout/Library/Sources/AppUI/Extensions/View+Environment.swift b/Passepartout/Library/Sources/AppUI/Extensions/View+Environment.swift
index dd3ce916..240cb12f 100644
--- a/Passepartout/Library/Sources/AppUI/Extensions/View+Environment.swift
+++ b/Passepartout/Library/Sources/AppUI/Extensions/View+Environment.swift
@@ -30,12 +30,9 @@ import SwiftUI
extension View {
public func withEnvironment(from context: AppContext, theme: Theme) -> some View {
environmentObject(theme)
- .environmentObject(context.connectionObserver)
.environmentObject(context.iapManager)
- .environmentObject(context.profileManager)
.environmentObject(context.profileProcessor)
.environmentObject(context.providerManager)
- .environmentObject(context.tunnel)
}
public func withMockEnvironment() -> some View {
diff --git a/Passepartout/Library/Sources/AppUI/Mock/Mock.swift b/Passepartout/Library/Sources/AppUI/Mock/Mock.swift
index 1cfb58c4..f0033b12 100644
--- a/Passepartout/Library/Sources/AppUI/Mock/Mock.swift
+++ b/Passepartout/Library/Sources/AppUI/Mock/Mock.swift
@@ -88,18 +88,12 @@ extension ProfileProcessor {
}
}
-extension Tunnel {
- public static var mock: Tunnel {
+extension ExtendedTunnel {
+ public static var mock: ExtendedTunnel {
AppContext.mock.tunnel
}
}
-extension ConnectionObserver {
- public static var mock: ConnectionObserver {
- AppContext.mock.connectionObserver
- }
-}
-
extension ProviderManager {
public static var mock: ProviderManager {
AppContext.mock.providerManager
diff --git a/Passepartout/Library/Sources/AppUI/Protocols/AppCoordinatorConforming.swift b/Passepartout/Library/Sources/AppUI/Protocols/AppCoordinatorConforming.swift
index b4629ba0..05b8201e 100644
--- a/Passepartout/Library/Sources/AppUI/Protocols/AppCoordinatorConforming.swift
+++ b/Passepartout/Library/Sources/AppUI/Protocols/AppCoordinatorConforming.swift
@@ -30,7 +30,7 @@ import PassepartoutKit
public protocol AppCoordinatorConforming {
init(
profileManager: ProfileManager,
- tunnel: Tunnel,
+ tunnel: ExtendedTunnel,
registry: Registry
)
}
diff --git a/Passepartout/Library/Sources/AppUI/Protocols/TunnelContextProviding.swift b/Passepartout/Library/Sources/AppUI/Protocols/TunnelContextProviding.swift
deleted file mode 100644
index cffe0f16..00000000
--- a/Passepartout/Library/Sources/AppUI/Protocols/TunnelContextProviding.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// TunnelContextProviding.swift
-// Passepartout
-//
-// Created by Davide De Rosa on 9/5/24.
-// Copyright (c) 2024 Davide De Rosa. All rights reserved.
-//
-// https://github.com/passepartoutvpn
-//
-// This file is part of Passepartout.
-//
-// Passepartout is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Passepartout is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Passepartout. If not, see .
-//
-
-import Foundation
-import PassepartoutKit
-
-public protocol TunnelContextProviding {
- var connectionObserver: ConnectionObserver { get }
-}
-
-@MainActor
-extension TunnelContextProviding {
- public var tunnelConnectionStatus: TunnelStatus {
- var status = connectionObserver.tunnel.status
- if status == .active, let connectionStatus = connectionObserver.connectionStatus {
- if connectionStatus == .connected {
- status = .active
- } else {
- status = .activating
- }
- }
- return status
- }
-}
diff --git a/Passepartout/Library/Sources/AppUI/Protocols/TunnelInstallationProviding.swift b/Passepartout/Library/Sources/AppUI/Protocols/TunnelInstallationProviding.swift
index 7fde139e..746a7328 100644
--- a/Passepartout/Library/Sources/AppUI/Protocols/TunnelInstallationProviding.swift
+++ b/Passepartout/Library/Sources/AppUI/Protocols/TunnelInstallationProviding.swift
@@ -30,7 +30,7 @@ import PassepartoutKit
public protocol TunnelInstallationProviding {
var profileManager: ProfileManager { get }
- var tunnel: Tunnel { get }
+ var tunnel: ExtendedTunnel { get }
}
@MainActor
diff --git a/Passepartout/Library/Sources/AppUIMain/UI/ConnectionStatusView.swift b/Passepartout/Library/Sources/AppUI/UI/ConnectionStatusView.swift
similarity index 78%
rename from Passepartout/Library/Sources/AppUIMain/UI/ConnectionStatusView.swift
rename to Passepartout/Library/Sources/AppUI/UI/ConnectionStatusView.swift
index 095c5cbe..d163f444 100644
--- a/Passepartout/Library/Sources/AppUIMain/UI/ConnectionStatusView.swift
+++ b/Passepartout/Library/Sources/AppUI/UI/ConnectionStatusView.swift
@@ -27,32 +27,34 @@ import Foundation
import PassepartoutKit
import SwiftUI
-struct ConnectionStatusView: View, TunnelContextProviding, ThemeProviding {
+public struct ConnectionStatusView: View, ThemeProviding {
@EnvironmentObject
- var theme: Theme
-
- @EnvironmentObject
- var connectionObserver: ConnectionObserver
+ public var theme: Theme
@ObservedObject
- var tunnel: Tunnel
+ private var tunnel: ExtendedTunnel
- var body: some View {
+ public init(tunnel: ExtendedTunnel) {
+ self.tunnel = tunnel
+ }
+
+ public var body: some View {
Text(statusDescription)
- .foregroundStyle(tunnelStatusColor)
+ .font(.headline)
+ .foregroundStyle(tunnel.statusColor(theme))
}
}
private extension ConnectionStatusView {
var statusDescription: String {
- if let lastErrorCode = connectionObserver.lastErrorCode {
+ if let lastErrorCode = tunnel.lastErrorCode {
return lastErrorCode.localizedDescription
}
- let status = tunnelConnectionStatus
+ let status = tunnel.connectionStatus
switch status {
case .active:
- if let dataCount = connectionObserver.dataCount {
+ if let dataCount = tunnel.dataCount {
let down = dataCount.received.descriptionAsDataUnit
let up = dataCount.sent.descriptionAsDataUnit
return "↓\(down) ↑\(up)"
@@ -75,7 +77,7 @@ private extension ConnectionStatusView {
#Preview("Connected") {
ConnectionStatusView(tunnel: .mock)
.task {
- try? await Tunnel.mock.connect(with: .mock, processor: .mock)
+ try? await ExtendedTunnel.mock.connect(with: .mock, processor: .mock)
}
.frame(width: 100, height: 100)
.withMockEnvironment()
@@ -94,7 +96,7 @@ private extension ConnectionStatusView {
}
return ConnectionStatusView(tunnel: .mock)
.task {
- try? await Tunnel.mock.connect(with: profile, processor: .mock)
+ try? await ExtendedTunnel.mock.connect(with: profile, processor: .mock)
}
.frame(width: 100, height: 100)
.withMockEnvironment()
diff --git a/Passepartout/Library/Sources/AppUIMain/UI/TunnelContextProviding+Theme.swift b/Passepartout/Library/Sources/AppUI/UI/ExtendedTunnel+Theme.swift
similarity index 81%
rename from Passepartout/Library/Sources/AppUIMain/UI/TunnelContextProviding+Theme.swift
rename to Passepartout/Library/Sources/AppUI/UI/ExtendedTunnel+Theme.swift
index d6a5535c..dd565155 100644
--- a/Passepartout/Library/Sources/AppUIMain/UI/TunnelContextProviding+Theme.swift
+++ b/Passepartout/Library/Sources/AppUI/UI/ExtendedTunnel+Theme.swift
@@ -1,5 +1,5 @@
//
-// TunnelContextProviding+Theme.swift
+// ExtendedTunnel+Theme.swift
// Passepartout
//
// Created by Davide De Rosa on 9/6/24.
@@ -26,11 +26,12 @@
import PassepartoutKit
import SwiftUI
-@MainActor
-extension TunnelContextProviding where Self: ThemeProviding {
- var tunnelStatusColor: Color {
- if connectionObserver.lastErrorCode != nil {
- switch connectionObserver.tunnel.status {
+extension ExtendedTunnel {
+
+ @MainActor
+ public func statusColor(_ theme: Theme) -> Color {
+ if lastErrorCode != nil {
+ switch status {
case .inactive:
return theme.inactiveColor
@@ -38,7 +39,7 @@ extension TunnelContextProviding where Self: ThemeProviding {
return theme.errorColor
}
}
- switch tunnelConnectionStatus {
+ switch connectionStatus {
case .active:
return theme.activeColor
diff --git a/Passepartout/Library/Sources/AppUIMain/UI/TunnelToggleButton.swift b/Passepartout/Library/Sources/AppUI/UI/TunnelToggleButton.swift
similarity index 76%
rename from Passepartout/Library/Sources/AppUIMain/UI/TunnelToggleButton.swift
rename to Passepartout/Library/Sources/AppUI/UI/TunnelToggleButton.swift
index 64c2333d..93df8505 100644
--- a/Passepartout/Library/Sources/AppUIMain/UI/TunnelToggleButton.swift
+++ b/Passepartout/Library/Sources/AppUI/UI/TunnelToggleButton.swift
@@ -27,18 +27,15 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct TunnelToggleButton