Decouple library from PassepartoutKit implementations (#834)
Move the following dependencies: - OpenVPN/OpenSSL - WireGuard/Go up the chain until the main App/Tunnel targets, so that UILibrary and CommonLibrary can abstract from these unnecessary details. Instead, give module views access to generic implementations via Registry. Incidentally, this fixes an issue preventing TV previews from working due to OpenSSL linkage.
This commit is contained in:
parent
fed3a9b7d1
commit
7c27125dd7
|
@ -41,7 +41,7 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||
"state" : {
|
||||
"revision" : "e7bd9636ac31d6111b0bc7c171398e68eeb384b5"
|
||||
"revision" : "fe192115ca6f8e49447717dbe0a64347bd722aec"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ final class AppDelegate: NSObject {
|
|||
|
||||
func configure(with uiConfiguring: UILibraryConfiguring) {
|
||||
UILibrary(uiConfiguring)
|
||||
.configure()
|
||||
.configure(with: context)
|
||||
|
||||
Task {
|
||||
pp_log(.app, .notice, "Fetch providers index...")
|
||||
|
|
|
@ -15,7 +15,10 @@ let package = Package(
|
|||
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||
.library(
|
||||
name: "AppUI",
|
||||
targets: ["AppUI"]
|
||||
targets: [
|
||||
"AppUI",
|
||||
"PassepartoutImplementations"
|
||||
]
|
||||
),
|
||||
.library(
|
||||
name: "AppUIMain",
|
||||
|
@ -31,7 +34,10 @@ let package = Package(
|
|||
),
|
||||
.library(
|
||||
name: "TunnelLibrary",
|
||||
targets: ["CommonLibrary"]
|
||||
targets: [
|
||||
"CommonLibrary",
|
||||
"PassepartoutImplementations"
|
||||
]
|
||||
),
|
||||
.library(
|
||||
name: "UILibrary",
|
||||
|
@ -40,7 +46,7 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"),
|
||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "e7bd9636ac31d6111b0bc7c171398e68eeb384b5"),
|
||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "fe192115ca6f8e49447717dbe0a64347bd722aec"),
|
||||
// .package(path: "../../../passepartoutkit-source"),
|
||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.9.1"),
|
||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
||||
|
@ -109,9 +115,7 @@ let package = Package(
|
|||
name: "CommonLibrary",
|
||||
dependencies: [
|
||||
"CommonUtils",
|
||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source"),
|
||||
.product(name: "PassepartoutOpenVPNOpenSSL", package: "passepartoutkit-source-openvpn-openssl"),
|
||||
.product(name: "PassepartoutWireGuardGo", package: "passepartoutkit-source-wireguard-go")
|
||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
||||
],
|
||||
resources: [
|
||||
.process("Resources")
|
||||
|
@ -131,6 +135,13 @@ let package = Package(
|
|||
.process("Profiles.xcdatamodeld")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "PassepartoutImplementations",
|
||||
dependencies: [
|
||||
.product(name: "PassepartoutOpenVPNOpenSSL", package: "passepartoutkit-source-openvpn-openssl"),
|
||||
.product(name: "PassepartoutWireGuardGo", package: "passepartoutkit-source-wireguard-go")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "UILibrary",
|
||||
dependencies: [
|
||||
|
|
|
@ -24,24 +24,25 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
@_exported import UILibrary
|
||||
|
||||
public final class AppUIMain: UILibraryConfiguring {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public func configure() {
|
||||
assertMissingImplementations()
|
||||
public func configure(with context: AppContext) {
|
||||
assertMissingImplementations(with: context.registry)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppUIMain {
|
||||
func assertMissingImplementations() {
|
||||
func assertMissingImplementations(with registry: Registry) {
|
||||
let providerModuleTypes: Set<ModuleType> = [
|
||||
.openVPN
|
||||
]
|
||||
ModuleType.allCases.forEach { moduleType in
|
||||
let builder = moduleType.newModule()
|
||||
let builder = moduleType.newModule(with: registry)
|
||||
guard builder is any ModuleViewProviding else {
|
||||
fatalError("\(moduleType): is not ModuleViewProviding")
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ extension ModuleBuilder where Self: ModuleViewProviding {
|
|||
@MainActor
|
||||
func preview(title: String = "") -> some View {
|
||||
NavigationStack {
|
||||
moduleView(with: ProfileEditor(modules: [self]))
|
||||
moduleView(with: ProfileEditor(modules: [self]), impl: nil)
|
||||
.navigationTitle(title)
|
||||
}
|
||||
.withMockEnvironment()
|
||||
|
|
|
@ -28,19 +28,24 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
final class DefaultModuleViewFactory: ModuleViewFactory {
|
||||
private let registry: Registry
|
||||
|
||||
init(registry: Registry) {
|
||||
self.registry = registry
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func view(with editor: ProfileEditor, moduleId: UUID) -> some View {
|
||||
let result = editor.moduleViewProvider(withId: moduleId)
|
||||
let result = editor.moduleViewProvider(withId: moduleId, registry: registry)
|
||||
if let result {
|
||||
AnyView(result.provider.moduleView(with: editor))
|
||||
AnyView(result.provider.moduleView(with: editor, impl: result.impl))
|
||||
.navigationTitle(result.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ProfileEditor {
|
||||
func moduleViewProvider(withId moduleId: UUID) -> (provider: any ModuleViewProviding, title: String)? {
|
||||
func moduleViewProvider(withId moduleId: UUID, registry: Registry) -> ModuleViewProviderResult? {
|
||||
guard let module = module(withId: moduleId) else {
|
||||
// assertionFailure("No module with ID \(moduleId)")
|
||||
return nil
|
||||
|
@ -49,6 +54,18 @@ private extension ProfileEditor {
|
|||
assertionFailure("\(type(of: module)) does not provide a default view")
|
||||
return nil
|
||||
}
|
||||
return (provider, module.moduleType.localizedDescription)
|
||||
return ModuleViewProviderResult(
|
||||
title: module.moduleType.localizedDescription,
|
||||
provider: provider,
|
||||
impl: registry.implementation(for: module)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ModuleViewProviderResult {
|
||||
let title: String
|
||||
|
||||
let provider: any ModuleViewProviding
|
||||
|
||||
let impl: ModuleImplementation?
|
||||
}
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
protocol ModuleViewProviding {
|
||||
associatedtype Content: View
|
||||
|
||||
@MainActor
|
||||
func moduleView(with editor: ProfileEditor) -> Content
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> Content
|
||||
}
|
||||
|
|
|
@ -157,7 +157,8 @@ extension AppCoordinator {
|
|||
ProfileCoordinator(
|
||||
profileManager: profileManager,
|
||||
profileEditor: profileEditor,
|
||||
moduleViewFactory: DefaultModuleViewFactory(),
|
||||
registry: registry,
|
||||
moduleViewFactory: DefaultModuleViewFactory(registry: registry),
|
||||
modally: true,
|
||||
path: $profilePath,
|
||||
onDismiss: {
|
||||
|
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension DNSModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
DNSView(editor: editor, module: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension HTTPProxyModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
HTTPProxyView(editor: editor, module: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension IPModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
IPView(editor: editor, module: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension OnDemandModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
OnDemandView(editor: editor, module: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension OpenVPNModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
OpenVPNView(editor: editor, module: self)
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
OpenVPNView(editor: editor, module: self, impl: impl as? OpenVPNModule.Implementation)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension WireGuardModule.Builder: ModuleViewProviding {
|
||||
func moduleView(with editor: ProfileEditor) -> some View {
|
||||
WireGuardView(editor: editor, module: self)
|
||||
func moduleView(with editor: ProfileEditor, impl: ModuleImplementation?) -> some View {
|
||||
WireGuardView(editor: editor, module: self, impl: impl as? WireGuardModule.Implementation)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
import CommonLibrary
|
||||
import CommonUtils
|
||||
import CPassepartoutOpenVPNOpenSSL
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
|
@ -39,6 +38,8 @@ struct OpenVPNView: View, ModuleDraftEditing {
|
|||
|
||||
let module: OpenVPNModule.Builder
|
||||
|
||||
let impl: OpenVPNModule.Implementation?
|
||||
|
||||
private let isServerPushed: Bool
|
||||
|
||||
@State
|
||||
|
@ -66,12 +67,14 @@ struct OpenVPNView: View, ModuleDraftEditing {
|
|||
|
||||
self.editor = editor
|
||||
self.module = module
|
||||
impl = nil
|
||||
isServerPushed = true
|
||||
}
|
||||
|
||||
init(editor: ProfileEditor, module: OpenVPNModule.Builder) {
|
||||
init(editor: ProfileEditor, module: OpenVPNModule.Builder, impl: OpenVPNModule.Implementation?) {
|
||||
self.editor = editor
|
||||
self.module = module
|
||||
self.impl = impl
|
||||
isServerPushed = false
|
||||
}
|
||||
|
||||
|
@ -179,8 +182,13 @@ private extension OpenVPNView {
|
|||
}
|
||||
importURL = url
|
||||
|
||||
let parsed = try StandardOpenVPNParser(decrypter: OSSLTLSBox())
|
||||
.parsed(fromURL: url, passphrase: importPassphrase)
|
||||
guard let impl else {
|
||||
fatalError("Requires OpenVPNModule implementation")
|
||||
}
|
||||
guard let parser = impl.importer as? StandardOpenVPNParser else {
|
||||
fatalError("OpenVPNModule importer should be StandardOpenVPNParser")
|
||||
}
|
||||
let parsed = try parser.parsed(fromURL: url, passphrase: importPassphrase)
|
||||
|
||||
draft.wrappedValue.configurationBuilder = parsed.configuration.builder()
|
||||
} catch StandardOpenVPNParserError.encryptionPassphrase,
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import PassepartoutWireGuardGo
|
||||
import SwiftUI
|
||||
|
||||
struct WireGuardView: View, ModuleDraftEditing {
|
||||
|
@ -35,6 +34,8 @@ struct WireGuardView: View, ModuleDraftEditing {
|
|||
|
||||
let module: WireGuardModule.Builder
|
||||
|
||||
let impl: WireGuardModule.Implementation?
|
||||
|
||||
var body: some View {
|
||||
contentView
|
||||
.moduleView(editor: editor, draft: draft.wrappedValue)
|
||||
|
@ -45,7 +46,10 @@ struct WireGuardView: View, ModuleDraftEditing {
|
|||
|
||||
private extension WireGuardView {
|
||||
var configuration: WireGuard.Configuration.Builder {
|
||||
draft.wrappedValue.configurationBuilder ?? .default
|
||||
guard let impl else {
|
||||
fatalError("Requires WireGuardModule implementation")
|
||||
}
|
||||
return draft.wrappedValue.configurationBuilder ?? .init(keyGenerator: impl.keyGenerator)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -143,7 +147,7 @@ private extension WireGuardView {
|
|||
|
||||
// swiftlint: disable force_try
|
||||
#Preview {
|
||||
let gen = StandardWireGuardKeyGenerator()
|
||||
let gen = MockGenerator()
|
||||
|
||||
var builder = WireGuard.Configuration.Builder(keyGenerator: gen)
|
||||
builder.interface.addresses = ["1.1.1.1", "2.2.2.2"]
|
||||
|
@ -166,3 +170,21 @@ private extension WireGuardView {
|
|||
return module.preview()
|
||||
}
|
||||
// swiftlint: enable force_try
|
||||
|
||||
private final class MockGenerator: WireGuardKeyGenerator {
|
||||
func newPrivateKey() -> String {
|
||||
"private-key"
|
||||
}
|
||||
|
||||
func privateKey(from string: String) throws -> String {
|
||||
"private-key"
|
||||
}
|
||||
|
||||
func publicKey(from string: String) throws -> String {
|
||||
"public-key"
|
||||
}
|
||||
|
||||
func publicKey(for privateKey: String) throws -> String {
|
||||
"public-key"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ private extension ModuleDetailView {
|
|||
ModuleDetailView(
|
||||
profileEditor: ProfileEditor(profile: .mock),
|
||||
moduleId: Profile.mock.modules.first?.id,
|
||||
moduleViewFactory: DefaultModuleViewFactory()
|
||||
moduleViewFactory: DefaultModuleViewFactory(registry: Registry())
|
||||
)
|
||||
.withMockEnvironment()
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ struct ProfileCoordinator: View {
|
|||
|
||||
let profileEditor: ProfileEditor
|
||||
|
||||
let registry: Registry
|
||||
|
||||
let moduleViewFactory: any ModuleViewFactory
|
||||
|
||||
let modally: Bool
|
||||
|
@ -125,7 +127,7 @@ private extension ProfileCoordinator {
|
|||
return
|
||||
}
|
||||
|
||||
let module = moduleType.newModule()
|
||||
let module = moduleType.newModule(with: registry)
|
||||
withAnimation(theme.animation(for: .modules)) {
|
||||
profileEditor.saveModule(module, activating: true)
|
||||
}
|
||||
|
@ -152,7 +154,8 @@ private extension ProfileCoordinator {
|
|||
ProfileCoordinator(
|
||||
profileManager: .mock,
|
||||
profileEditor: ProfileEditor(profile: .newMockProfile()),
|
||||
moduleViewFactory: DefaultModuleViewFactory(),
|
||||
registry: Registry(),
|
||||
moduleViewFactory: DefaultModuleViewFactory(registry: Registry()),
|
||||
modally: false,
|
||||
path: .constant(NavigationPath()),
|
||||
onDismiss: {}
|
||||
|
|
|
@ -123,7 +123,7 @@ private extension ProfileSplitView {
|
|||
#Preview {
|
||||
ProfileSplitView(
|
||||
profileEditor: ProfileEditor(profile: .newMockProfile()),
|
||||
moduleViewFactory: DefaultModuleViewFactory()
|
||||
moduleViewFactory: DefaultModuleViewFactory(registry: Registry())
|
||||
)
|
||||
.withMockEnvironment()
|
||||
}
|
||||
|
|
|
@ -30,6 +30,6 @@ public final class AppUITV: UILibraryConfiguring {
|
|||
public init() {
|
||||
}
|
||||
|
||||
public func configure() {
|
||||
public func configure(with context: AppContext) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import PassepartoutWireGuardGo
|
||||
|
||||
extension LoggerDestination {
|
||||
public static let app = LoggerDestination(category: "app")
|
||||
|
@ -37,12 +36,6 @@ extension LoggerDestination {
|
|||
}
|
||||
}
|
||||
|
||||
extension WireGuard.Configuration.Builder {
|
||||
public static var `default`: Self {
|
||||
.init(keyGenerator: StandardWireGuardKeyGenerator())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: #716, move to Environment
|
||||
extension Constants {
|
||||
public static let shared = Bundle.module.unsafeDecode(Constants.self, filename: "Constants")
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Dummy.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 11/8/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
|
@ -27,13 +27,17 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
|
||||
extension ModuleType {
|
||||
public func newModule() -> any ModuleBuilder {
|
||||
public func newModule(with registry: Registry) -> any ModuleBuilder {
|
||||
switch self {
|
||||
case .openVPN:
|
||||
return OpenVPNModule.Builder()
|
||||
|
||||
case .wireGuard:
|
||||
return WireGuardModule.Builder(configurationBuilder: .default)
|
||||
let impl = registry.implementation(for: WireGuardModule.moduleHandler.id)
|
||||
guard let wireGuard = impl as? WireGuardModule.Implementation else {
|
||||
fatalError("Missing WireGuardModule implementation from Registry?")
|
||||
}
|
||||
return WireGuardModule.Builder(configurationBuilder: .init(keyGenerator: wireGuard.keyGenerator))
|
||||
|
||||
case .dns:
|
||||
return DNSModule.Builder()
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import PassepartoutWireGuardGo
|
||||
|
||||
public struct ModuleType: RawRepresentable, Hashable {
|
||||
public let rawValue: String
|
||||
|
|
|
@ -30,7 +30,7 @@ import PassepartoutKit
|
|||
|
||||
@MainActor
|
||||
public protocol UILibraryConfiguring {
|
||||
func configure()
|
||||
func configure(with context: AppContext)
|
||||
}
|
||||
|
||||
public final class UILibrary: UILibraryConfiguring {
|
||||
|
@ -40,12 +40,12 @@ public final class UILibrary: UILibraryConfiguring {
|
|||
self.uiConfiguring = uiConfiguring
|
||||
}
|
||||
|
||||
public func configure() {
|
||||
public func configure(with context: AppContext) {
|
||||
PassepartoutConfiguration.shared.configureLogging(
|
||||
to: BundleConfiguration.urlForAppLog,
|
||||
parameters: Constants.shared.log,
|
||||
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||
)
|
||||
uiConfiguring?.configure()
|
||||
uiConfiguring?.configure(with: context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -255,3 +255,9 @@ extension ProfileEditorTests {
|
|||
await fulfillment(of: [exp])
|
||||
}
|
||||
}
|
||||
|
||||
private extension WireGuard.Configuration.Builder {
|
||||
static var `default`: WireGuard.Configuration.Builder {
|
||||
WireGuard.Configuration.Builder(privateKey: "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ extension Registry {
|
|||
}
|
||||
),
|
||||
WireGuardModule.Implementation(
|
||||
keyGenerator: StandardWireGuardKeyGenerator(),
|
||||
importer: StandardWireGuardParser(),
|
||||
connectionBlock: { parameters, module in
|
||||
try GoWireGuardConnection(parameters: parameters, module: module)
|
||||
|
|
Loading…
Reference in New Issue