In-place NetworkExtension profiles (#715)
Profiles are being maintained in two places: - Core Data - NetworkExtension Core Data is redundant for local profiles, so make NetworkExtension the only source of truth.
This commit is contained in:
parent
6d479a7059
commit
0aac8cd9f3
|
@ -9,6 +9,15 @@
|
||||||
"version" : "1.7.18"
|
"version" : "1.7.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "generic-json-swift",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zoul/generic-json-swift",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "0a06575f4038b504e78ac330913d920f1630f510",
|
||||||
|
"version" : "2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "kvitto",
|
"identity" : "kvitto",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
@ -32,7 +41,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "7efa18eb75b7a102781be3c62cd31a08607f03c8"
|
"revision" : "90267688fa16be83e7f75f26d5eb5b3094b309ec"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,7 @@ let package = Package(
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "7efa18eb75b7a102781be3c62cd31a08607f03c8"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "90267688fa16be83e7f75f26d5eb5b3094b309ec"),
|
||||||
// .package(path: "../../../passepartoutkit-source"),
|
// .package(path: "../../../passepartoutkit-source"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
||||||
|
@ -47,6 +47,7 @@ let package = Package(
|
||||||
.target(
|
.target(
|
||||||
name: "AppData",
|
name: "AppData",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"AppLibrary",
|
||||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -64,7 +65,6 @@ let package = Package(
|
||||||
.target(
|
.target(
|
||||||
name: "AppLibrary",
|
name: "AppLibrary",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"AppData",
|
|
||||||
"CommonLibrary",
|
"CommonLibrary",
|
||||||
"Kvitto",
|
"Kvitto",
|
||||||
"LegacyV2",
|
"LegacyV2",
|
||||||
|
|
|
@ -28,10 +28,10 @@ import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension AppData {
|
extension AppData {
|
||||||
public static var cdProfilesModel: NSManagedObjectModel {
|
public static let cdProfilesModel: NSManagedObjectModel = {
|
||||||
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
|
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
|
||||||
fatalError("Unable to build Core Data model (Profiles v3)")
|
fatalError("Unable to build Core Data model (Profiles v3)")
|
||||||
}
|
}
|
||||||
return model
|
return model
|
||||||
}
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppData
|
import AppData
|
||||||
|
import AppLibrary
|
||||||
import CoreData
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
@ -34,9 +35,13 @@ extension AppData {
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
coder: ProfileCoder,
|
coder: ProfileCoder,
|
||||||
context: NSManagedObjectContext,
|
context: NSManagedObjectContext,
|
||||||
|
observingResults: Bool,
|
||||||
onResultError: ((Error) -> CoreDataResultAction)?
|
onResultError: ((Error) -> CoreDataResultAction)?
|
||||||
) -> any ProfileRepository {
|
) -> any ProfileRepository {
|
||||||
let repository = CoreDataRepository<CDProfileV3, Profile>(context: context) {
|
let repository = CoreDataRepository<CDProfileV3, Profile>(
|
||||||
|
context: context,
|
||||||
|
observingResults: observingResults
|
||||||
|
) {
|
||||||
$0.sortDescriptors = [
|
$0.sortDescriptors = [
|
||||||
.init(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare)),
|
.init(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare)),
|
||||||
.init(key: "lastUpdate", ascending: true)
|
.init(key: "lastUpdate", ascending: true)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// MockProfileRepository.swift
|
// InMemoryProfileRepository.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/11/24.
|
// Created by Davide De Rosa on 8/11/24.
|
||||||
|
@ -28,7 +28,7 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
public final class MockProfileRepository: ProfileRepository {
|
public final class InMemoryProfileRepository: ProfileRepository {
|
||||||
private var profiles: [Profile] {
|
private var profiles: [Profile] {
|
||||||
didSet {
|
didSet {
|
||||||
profilesSubject.send(EntitiesResult(profiles, isFiltering: false))
|
profilesSubject.send(EntitiesResult(profiles, isFiltering: false))
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// NEProfileRepository.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 10/10/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 Combine
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
import UtilsLibrary
|
||||||
|
|
||||||
|
public final class NEProfileRepository: ProfileRepository {
|
||||||
|
private let repository: NETunnelManagerRepository
|
||||||
|
|
||||||
|
private let profilesSubject: CurrentValueSubject<[Profile], Never>
|
||||||
|
|
||||||
|
private var subscription: AnyCancellable?
|
||||||
|
|
||||||
|
public init(repository: NETunnelManagerRepository) {
|
||||||
|
self.repository = repository
|
||||||
|
profilesSubject = CurrentValueSubject([])
|
||||||
|
|
||||||
|
subscription = repository
|
||||||
|
.managersPublisher
|
||||||
|
.sink { [weak self] allManagers in
|
||||||
|
let profiles = allManagers.values.compactMap {
|
||||||
|
try? repository.profile(from: $0)
|
||||||
|
}
|
||||||
|
self?.profilesSubject.send(profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
try await repository.load()
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .fault, "Unable to load NE profiles: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var entitiesPublisher: AnyPublisher<EntitiesResult<Profile>, Never> {
|
||||||
|
profilesSubject
|
||||||
|
.map {
|
||||||
|
EntitiesResult($0, isFiltering: false)
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func filter(byFormat format: String, arguments: [Any]?) async throws {
|
||||||
|
assertionFailure("Unused by ProfileManager")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func resetFilter() async throws {
|
||||||
|
assertionFailure("Unused by ProfileManager")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func saveEntities(_ entities: [Profile]) async throws {
|
||||||
|
for profile in entities {
|
||||||
|
try await repository.save(profile, connect: false, title: \.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeEntities(withIds ids: [UUID]) async throws {
|
||||||
|
for profileId in ids {
|
||||||
|
try await repository.remove(profileId: profileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppData
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
@ -37,10 +36,6 @@ public final class ProfileManager: ObservableObject {
|
||||||
case remove([Profile.ID])
|
case remove([Profile.ID])
|
||||||
}
|
}
|
||||||
|
|
||||||
public var beforeSave: ((Profile) async throws -> Void)?
|
|
||||||
|
|
||||||
public var afterRemove: (([Profile.ID]) async -> Void)?
|
|
||||||
|
|
||||||
private let repository: any ProfileRepository
|
private let repository: any ProfileRepository
|
||||||
|
|
||||||
private let remoteRepository: (any ProfileRepository)?
|
private let remoteRepository: (any ProfileRepository)?
|
||||||
|
@ -64,7 +59,7 @@ public final class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
// for testing/previews
|
// for testing/previews
|
||||||
public init(profiles: [Profile]) {
|
public init(profiles: [Profile]) {
|
||||||
repository = MockProfileRepository(profiles: profiles)
|
repository = InMemoryProfileRepository(profiles: profiles)
|
||||||
remoteRepository = nil
|
remoteRepository = nil
|
||||||
self.profiles = []
|
self.profiles = []
|
||||||
allProfiles = profiles.reduce(into: [:]) {
|
allProfiles = profiles.reduce(into: [:]) {
|
||||||
|
@ -122,7 +117,6 @@ extension ProfileManager {
|
||||||
do {
|
do {
|
||||||
let existingProfile = allProfiles[profile.id]
|
let existingProfile = allProfiles[profile.id]
|
||||||
if existingProfile == nil || profile != existingProfile {
|
if existingProfile == nil || profile != existingProfile {
|
||||||
try await beforeSave?(profile)
|
|
||||||
try await repository.saveEntities([profile])
|
try await repository.saveEntities([profile])
|
||||||
|
|
||||||
allProfiles[profile.id] = profile
|
allProfiles[profile.id] = profile
|
||||||
|
@ -160,7 +154,6 @@ extension ProfileManager {
|
||||||
// remove local profiles
|
// remove local profiles
|
||||||
var newAllProfiles = allProfiles
|
var newAllProfiles = allProfiles
|
||||||
try await repository.removeEntities(withIds: profileIds)
|
try await repository.removeEntities(withIds: profileIds)
|
||||||
await afterRemove?(profileIds)
|
|
||||||
profileIds.forEach {
|
profileIds.forEach {
|
||||||
newAllProfiles.removeValue(forKey: $0)
|
newAllProfiles.removeValue(forKey: $0)
|
||||||
}
|
}
|
||||||
|
@ -245,7 +238,6 @@ extension ProfileManager {
|
||||||
public func observeObjects(searchDebounce: Int = 200) {
|
public func observeObjects(searchDebounce: Int = 200) {
|
||||||
repository
|
repository
|
||||||
.entitiesPublisher
|
.entitiesPublisher
|
||||||
.first()
|
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
self?.reloadLocalProfiles($0)
|
self?.reloadLocalProfiles($0)
|
||||||
|
|
|
@ -69,13 +69,6 @@ public final class AppContext: ObservableObject {
|
||||||
self.constants = constants
|
self.constants = constants
|
||||||
subscriptions = []
|
subscriptions = []
|
||||||
|
|
||||||
profileManager.beforeSave = { [weak self] in
|
|
||||||
try await self?.installSavedProfile($0)
|
|
||||||
}
|
|
||||||
profileManager.afterRemove = { [weak self] in
|
|
||||||
self?.uninstallRemovedProfiles(withIds: $0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
try await tunnel.prepare()
|
try await tunnel.prepare()
|
||||||
await iapManager.reloadReceipt()
|
await iapManager.reloadReceipt()
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppData
|
|
||||||
import AppLibrary
|
import AppLibrary
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -48,6 +48,8 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
||||||
|
|
||||||
private let context: NSManagedObjectContext
|
private let context: NSManagedObjectContext
|
||||||
|
|
||||||
|
private let observingResults: Bool
|
||||||
|
|
||||||
private let fromMapper: (CD) throws -> T?
|
private let fromMapper: (CD) throws -> T?
|
||||||
|
|
||||||
private let toMapper: (T, NSManagedObjectContext) throws -> CD
|
private let toMapper: (T, NSManagedObjectContext) throws -> CD
|
||||||
|
@ -61,6 +63,7 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: NSManagedObjectContext,
|
context: NSManagedObjectContext,
|
||||||
|
observingResults: Bool,
|
||||||
beforeFetch: ((NSFetchRequest<CD>) -> Void)? = nil,
|
beforeFetch: ((NSFetchRequest<CD>) -> Void)? = nil,
|
||||||
fromMapper: @escaping (CD) throws -> T?,
|
fromMapper: @escaping (CD) throws -> T?,
|
||||||
toMapper: @escaping (T, NSManagedObjectContext) throws -> CD,
|
toMapper: @escaping (T, NSManagedObjectContext) throws -> CD,
|
||||||
|
@ -72,6 +75,7 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
||||||
|
|
||||||
self.entityName = entityName
|
self.entityName = entityName
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.observingResults = observingResults
|
||||||
self.fromMapper = fromMapper
|
self.fromMapper = fromMapper
|
||||||
self.toMapper = toMapper
|
self.toMapper = toMapper
|
||||||
self.onResultError = onResultError
|
self.onResultError = onResultError
|
||||||
|
@ -161,6 +165,9 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
||||||
|
|
||||||
// XXX: triggers on entity insert/update/delete and reloads/remaps ALL into entitiesSubject
|
// XXX: triggers on entity insert/update/delete and reloads/remaps ALL into entitiesSubject
|
||||||
public nonisolated func controllerDidChangeContent(_ controller: NSFetchedResultsController<any NSFetchRequestResult>) {
|
public nonisolated func controllerDidChangeContent(_ controller: NSFetchedResultsController<any NSFetchRequestResult>) {
|
||||||
|
guard observingResults else {
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let cdController = controller as? NSFetchedResultsController<CD> else {
|
guard let cdController = controller as? NSFetchedResultsController<CD> else {
|
||||||
fatalError("Unable to upcast results to \(CD.self)")
|
fatalError("Unable to upcast results to \(CD.self)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,41 +27,27 @@ import AppData
|
||||||
import AppDataProfiles
|
import AppDataProfiles
|
||||||
import AppLibrary
|
import AppLibrary
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension ProfileManager {
|
extension ProfileManager {
|
||||||
static let shared: ProfileManager = {
|
static let shared: ProfileManager = {
|
||||||
let model = AppData.cdProfilesModel
|
let repository = localProfileRepository
|
||||||
|
|
||||||
let store = CoreDataPersistentStore(
|
|
||||||
logger: .default,
|
|
||||||
containerName: BundleConfiguration.mainString(for: .profilesContainerName),
|
|
||||||
model: model,
|
|
||||||
cloudKitIdentifier: nil,
|
|
||||||
author: nil
|
|
||||||
)
|
|
||||||
let repository = AppData.cdProfileRepositoryV3(
|
|
||||||
registry: .shared,
|
|
||||||
coder: CodableProfileCoder(),
|
|
||||||
context: store.context
|
|
||||||
) { error in
|
|
||||||
pp_log(.app, .error, "Unable to decode local result: \(error)")
|
|
||||||
return .ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
let remoteStore = CoreDataPersistentStore(
|
let remoteStore = CoreDataPersistentStore(
|
||||||
logger: .default,
|
logger: .default,
|
||||||
containerName: BundleConfiguration.mainString(for: .remoteProfilesContainerName),
|
containerName: BundleConfiguration.mainString(for: .remoteProfilesContainerName),
|
||||||
model: model,
|
model: coreDataModel,
|
||||||
cloudKitIdentifier: BundleConfiguration.mainString(for: .cloudKitId),
|
cloudKitIdentifier: BundleConfiguration.mainString(for: .cloudKitId),
|
||||||
author: nil
|
author: nil
|
||||||
)
|
)
|
||||||
let remoteRepository = AppData.cdProfileRepositoryV3(
|
let remoteRepository = AppData.cdProfileRepositoryV3(
|
||||||
registry: .shared,
|
registry: .shared,
|
||||||
coder: CodableProfileCoder(),
|
coder: CodableProfileCoder(),
|
||||||
context: remoteStore.context
|
context: remoteStore.context,
|
||||||
|
observingResults: true
|
||||||
) { error in
|
) { error in
|
||||||
pp_log(.app, .error, "Unable to decode remote result: \(error)")
|
pp_log(.app, .error, "Unable to decode remote result: \(error)")
|
||||||
return .ignore
|
return .ignore
|
||||||
|
@ -71,6 +57,10 @@ extension ProfileManager {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var coreDataModel: NSManagedObjectModel {
|
||||||
|
AppData.cdProfilesModel
|
||||||
|
}
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
|
|
||||||
extension Tunnel {
|
extension Tunnel {
|
||||||
|
@ -79,15 +69,42 @@ extension Tunnel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var localProfileRepository: any ProfileRepository {
|
||||||
|
let store = CoreDataPersistentStore(
|
||||||
|
logger: .default,
|
||||||
|
containerName: BundleConfiguration.mainString(for: .profilesContainerName),
|
||||||
|
model: coreDataModel,
|
||||||
|
cloudKitIdentifier: nil,
|
||||||
|
author: nil
|
||||||
|
)
|
||||||
|
return AppData.cdProfileRepositoryV3(
|
||||||
|
registry: .shared,
|
||||||
|
coder: CodableProfileCoder(),
|
||||||
|
context: store.context,
|
||||||
|
observingResults: false
|
||||||
|
) { error in
|
||||||
|
pp_log(.app, .error, "Unable to decode local result: \(error)")
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
extension Tunnel {
|
extension Tunnel {
|
||||||
static let shared = Tunnel(
|
static let shared = Tunnel(
|
||||||
strategy: NETunnelStrategy(
|
strategy: NETunnelStrategy(repository: neRepository)
|
||||||
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
|
||||||
encoder: .shared,
|
|
||||||
environment: .shared
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var localProfileRepository: any ProfileRepository {
|
||||||
|
NEProfileRepository(repository: neRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var neRepository: NETunnelManagerRepository {
|
||||||
|
NETunnelManagerRepository(
|
||||||
|
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
||||||
|
coder: Registry.sharedProtocolCoder,
|
||||||
|
environment: .shared
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,15 @@ extension Registry {
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
static var sharedProtocolCoder: KeychainNEProtocolCoder {
|
||||||
|
KeychainNEProtocolCoder(
|
||||||
|
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
||||||
|
registry: .shared,
|
||||||
|
coder: CodableProfileCoder(),
|
||||||
|
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelEnvironment where Self == AppGroupEnvironment {
|
extension TunnelEnvironment where Self == AppGroupEnvironment {
|
||||||
|
@ -69,24 +78,3 @@ extension TunnelEnvironment where Self == AppGroupEnvironment {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NEProtocolEncoder where Self == KeychainNEProtocolCoder {
|
|
||||||
static var shared: Self {
|
|
||||||
sharedProtocolCoder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NEProtocolDecoder where Self == KeychainNEProtocolCoder {
|
|
||||||
static var shared: Self {
|
|
||||||
sharedProtocolCoder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var sharedProtocolCoder: KeychainNEProtocolCoder {
|
|
||||||
KeychainNEProtocolCoder(
|
|
||||||
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
|
||||||
registry: .shared,
|
|
||||||
coder: CodableProfileCoder(),
|
|
||||||
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
)
|
)
|
||||||
fwd = try await NEPTPForwarder(
|
fwd = try await NEPTPForwarder(
|
||||||
provider: self,
|
provider: self,
|
||||||
decoder: .shared,
|
decoder: Registry.sharedProtocolCoder,
|
||||||
registry: .shared,
|
registry: .shared,
|
||||||
environment: .shared
|
environment: .shared
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue