mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-02-17 13:22:09 +00:00
Refactor static functions/entities in Library (#679)
Reduce the impact of hidden dependencies on BundleConfiguration and Constants.shared Fixes #656
This commit is contained in:
parent
4b0bc7f064
commit
5fb6f4f4d2
@ -32,8 +32,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "2e61214462dcf6ad9e211d8fdbd611c6755845c4",
|
"revision" : "779910e268e79f1004a95285ac2485255d88bb21"
|
||||||
"version" : "0.8.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,9 +70,10 @@ private extension PassepartoutApp {
|
|||||||
registry: context.registry
|
registry: context.registry
|
||||||
)
|
)
|
||||||
.onLoad {
|
.onLoad {
|
||||||
CommonLibrary.configureLogging(
|
PassepartoutConfiguration.shared.configureLogging(
|
||||||
to: BundleConfiguration.urlForAppLog,
|
to: BundleConfiguration.urlForAppLog,
|
||||||
parameters: Constants.shared.log
|
parameters: Constants.shared.log,
|
||||||
|
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||||
)
|
)
|
||||||
AppUI.configure(with: context)
|
AppUI.configure(with: context)
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ 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: "2e61214462dcf6ad9e211d8fdbd611c6755845c4"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "779910e268e79f1004a95285ac2485255d88bb21"),
|
||||||
// .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"),
|
||||||
|
@ -30,8 +30,6 @@ import PassepartoutKit
|
|||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension AppData {
|
extension AppData {
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
|
||||||
public static func cdProfileRepositoryV3(
|
public static func cdProfileRepositoryV3(
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
coder: ProfileCoder,
|
coder: ProfileCoder,
|
||||||
|
@ -26,11 +26,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
public enum AppUI {
|
||||||
public struct AppUI {
|
|
||||||
private init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func configure(with context: AppContext) {
|
public static func configure(with context: AppContext) {
|
||||||
assertMissingModuleImplementations()
|
assertMissingModuleImplementations()
|
||||||
}
|
}
|
||||||
@ -40,11 +36,11 @@ private extension AppUI {
|
|||||||
static func assertMissingModuleImplementations() {
|
static func assertMissingModuleImplementations() {
|
||||||
ModuleType.allCases.forEach { moduleType in
|
ModuleType.allCases.forEach { moduleType in
|
||||||
let module = moduleType.newModule()
|
let module = moduleType.newModule()
|
||||||
guard module as? ModuleTypeProviding != nil else {
|
guard module is ModuleTypeProviding else {
|
||||||
fatalError("\(moduleType): does not implement ModuleTypeProviding")
|
fatalError("\(moduleType): is not ModuleTypeProviding")
|
||||||
}
|
}
|
||||||
guard module as? any ModuleViewProviding != nil else {
|
guard module is any ModuleViewProviding else {
|
||||||
fatalError("\(moduleType): does not implement ModuleViewProviding")
|
fatalError("\(moduleType): is not ModuleViewProviding")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Issue+App.swift
|
// Issue+Metadata.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 9/18/24.
|
// Created by Davide De Rosa on 9/18/24.
|
||||||
@ -28,23 +28,35 @@ import Foundation
|
|||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension Issue {
|
extension Issue {
|
||||||
|
struct Metadata {
|
||||||
|
let configuration: PassepartoutConfiguration
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
let versionString: String
|
||||||
static func with(versionString: String, purchasedProducts: Set<AppProduct>, tunnel: Tunnel) async -> Self {
|
|
||||||
let appLog = CommonLibrary.currentLog(parameters: Constants.shared.log)
|
let purchasedProducts: Set<AppProduct>
|
||||||
|
|
||||||
|
let tunnel: Tunnel
|
||||||
|
|
||||||
|
let urlForTunnelLog: URL
|
||||||
|
|
||||||
|
let parameters: Constants.Log
|
||||||
|
}
|
||||||
|
|
||||||
|
static func withMetadata(_ metadata: Metadata) async -> Issue {
|
||||||
|
let appLog = metadata.configuration.currentLog(parameters: metadata.parameters)
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
.data(using: .utf8)
|
.data(using: .utf8)
|
||||||
|
|
||||||
let tunnelLog: Data?
|
let tunnelLog: Data?
|
||||||
|
|
||||||
// live tunnel log
|
// live tunnel log
|
||||||
if await tunnel.status != .inactive {
|
if await metadata.tunnel.status != .inactive {
|
||||||
tunnelLog = await tunnel.currentLog(parameters: Constants.shared.log)
|
tunnelLog = await metadata.tunnel.currentLog(parameters: metadata.parameters)
|
||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
.data(using: .utf8)
|
.data(using: .utf8)
|
||||||
}
|
}
|
||||||
// latest persisted tunnel log
|
// latest persisted tunnel log
|
||||||
else if let latestTunnelEntry = CommonLibrary.availableLogs(at: BundleConfiguration.urlForTunnelLog)
|
else if let latestTunnelEntry = metadata.configuration.availableLogs(at: metadata.urlForTunnelLog)
|
||||||
.max(by: { $0.key < $1.key }) {
|
.max(by: { $0.key < $1.key }) {
|
||||||
|
|
||||||
tunnelLog = try? Data(contentsOf: latestTunnelEntry.value)
|
tunnelLog = try? Data(contentsOf: latestTunnelEntry.value)
|
||||||
@ -55,13 +67,15 @@ extension Issue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Issue(
|
return Issue(
|
||||||
appLine: "\(Strings.Unlocalized.appName) \(versionString)",
|
appLine: "\(Strings.Unlocalized.appName) \(metadata.versionString)",
|
||||||
purchasedProducts: purchasedProducts,
|
purchasedProducts: metadata.purchasedProducts,
|
||||||
appLog: appLog,
|
appLog: appLog,
|
||||||
tunnelLog: tunnelLog
|
tunnelLog: tunnelLog
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Issue {
|
||||||
var to: String {
|
var to: String {
|
||||||
Constants.shared.emails.issues
|
Constants.shared.emails.issues
|
||||||
}
|
}
|
@ -29,22 +29,18 @@ import SwiftUI
|
|||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension DebugLogView {
|
extension DebugLogView {
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
|
||||||
static func withApp(parameters: Constants.Log) -> DebugLogView {
|
static func withApp(parameters: Constants.Log) -> DebugLogView {
|
||||||
DebugLogView {
|
DebugLogView {
|
||||||
CommonLibrary.currentLog(parameters: parameters)
|
PassepartoutConfiguration.shared.currentLog(parameters: parameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
|
||||||
static func withTunnel(_ tunnel: Tunnel, parameters: Constants.Log) -> DebugLogView {
|
static func withTunnel(_ tunnel: Tunnel, parameters: Constants.Log) -> DebugLogView {
|
||||||
DebugLogView {
|
DebugLogView {
|
||||||
await tunnel.currentLog(parameters: parameters)
|
await tunnel.currentLog(parameters: parameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
|
||||||
static func withURL(_ url: URL) -> DebugLogView {
|
static func withURL(_ url: URL) -> DebugLogView {
|
||||||
DebugLogView {
|
DebugLogView {
|
||||||
do {
|
do {
|
||||||
|
@ -47,11 +47,11 @@ struct DiagnosticsView: View {
|
|||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var iapManager: IAPManager
|
var iapManager: IAPManager
|
||||||
|
|
||||||
@AppStorage(AppPreference.logsPrivateData.key, store: .group)
|
@AppStorage(AppPreference.logsPrivateData.key, store: .appGroup)
|
||||||
private var logsPrivateData = false
|
private var logsPrivateData = false
|
||||||
|
|
||||||
var availableTunnelLogs: () -> [LogEntry] = {
|
var availableTunnelLogs: () -> [LogEntry] = {
|
||||||
CommonLibrary.availableLogs(at: BundleConfiguration.urlForTunnelLog)
|
PassepartoutConfiguration.shared.availableLogs(at: BundleConfiguration.urlForTunnelLog)
|
||||||
.sorted {
|
.sorted {
|
||||||
$0.key > $1.key
|
$0.key > $1.key
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
@ -33,24 +34,7 @@ import UtilsLibrary
|
|||||||
extension ReportIssueButton: View {
|
extension ReportIssueButton: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Button(title) {
|
Button(title, action: sendEmail)
|
||||||
Task {
|
|
||||||
isPending = true
|
|
||||||
defer {
|
|
||||||
isPending = false
|
|
||||||
}
|
|
||||||
let issue = await Issue.with(
|
|
||||||
versionString: BundleConfiguration.mainVersionString,
|
|
||||||
purchasedProducts: purchasedProducts,
|
|
||||||
tunnel: tunnel
|
|
||||||
)
|
|
||||||
guard MailComposerView.canSendMail() else {
|
|
||||||
openMailTo(with: issue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
issueBeingReported = issue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isPending {
|
if isPending {
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView()
|
ProgressView()
|
||||||
@ -75,6 +59,41 @@ extension ReportIssueButton: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension ReportIssueButton {
|
||||||
|
func sendEmail() {
|
||||||
|
Task {
|
||||||
|
isPending = true
|
||||||
|
defer {
|
||||||
|
isPending = false
|
||||||
|
}
|
||||||
|
let issue = await Issue.withMetadata(.init(
|
||||||
|
configuration: .shared,
|
||||||
|
versionString: BundleConfiguration.mainVersionString,
|
||||||
|
purchasedProducts: purchasedProducts,
|
||||||
|
tunnel: tunnel,
|
||||||
|
urlForTunnelLog: BundleConfiguration.urlForTunnelLog,
|
||||||
|
parameters: Constants.shared.log
|
||||||
|
))
|
||||||
|
guard MailComposerView.canSendMail() else {
|
||||||
|
openMailTo(with: issue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issueBeingReported = issue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openMailTo(with issue: Issue) {
|
||||||
|
guard let url = URL.mailto(to: issue.to, subject: issue.subject, body: issue.body) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard UIApplication.shared.canOpenURL(url) else {
|
||||||
|
isUnableToEmail = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension Issue {
|
private extension Issue {
|
||||||
var attachments: [MailComposerView.Attachment] {
|
var attachments: [MailComposerView.Attachment] {
|
||||||
var list: [MailComposerView.Attachment] = []
|
var list: [MailComposerView.Attachment] = []
|
||||||
@ -89,17 +108,4 @@ private extension Issue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ReportIssueButton {
|
|
||||||
func openMailTo(with issue: Issue) {
|
|
||||||
guard let url = URL.mailto(to: issue.to, subject: issue.subject, body: issue.body) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard UIApplication.shared.canOpenURL(url) else {
|
|
||||||
isUnableToEmail = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UIApplication.shared.open(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -25,26 +25,39 @@
|
|||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ReportIssueButton: View {
|
extension ReportIssueButton: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button(title) {
|
Button(title, action: sendEmail)
|
||||||
|
.disabled(isPending)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ReportIssueButton {
|
||||||
|
func sendEmail() {
|
||||||
|
Task {
|
||||||
guard let service = NSSharingService(named: .composeEmail) else {
|
guard let service = NSSharingService(named: .composeEmail) else {
|
||||||
isUnableToEmail = true
|
isUnableToEmail = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Task {
|
isPending = true
|
||||||
let issue = await Issue.with(
|
defer {
|
||||||
versionString: BundleConfiguration.mainVersionString,
|
isPending = false
|
||||||
purchasedProducts: purchasedProducts,
|
|
||||||
tunnel: tunnel
|
|
||||||
)
|
|
||||||
service.recipients = [issue.to]
|
|
||||||
service.subject = issue.subject
|
|
||||||
service.perform(withItems: issue.items)
|
|
||||||
}
|
}
|
||||||
|
let issue = await Issue.withMetadata(.init(
|
||||||
|
configuration: .shared,
|
||||||
|
versionString: BundleConfiguration.mainVersionString,
|
||||||
|
purchasedProducts: purchasedProducts,
|
||||||
|
tunnel: tunnel,
|
||||||
|
urlForTunnelLog: BundleConfiguration.urlForTunnelLog,
|
||||||
|
parameters: Constants.shared.log
|
||||||
|
))
|
||||||
|
service.recipients = [issue.to]
|
||||||
|
service.subject = issue.subject
|
||||||
|
service.perform(withItems: issue.items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
//
|
//
|
||||||
// CommonLibrary.swift
|
// PassepartoutConfiguration+Extensions.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 8/31/24.
|
// Created by Davide De Rosa on 10/4/24.
|
||||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
//
|
//
|
||||||
// https://github.com/passepartoutvpn
|
// https://github.com/passepartoutvpn
|
||||||
@ -26,24 +26,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
extension PassepartoutConfiguration {
|
||||||
public struct CommonLibrary {
|
public func configureLogging(to url: URL, parameters: Constants.Log, logsPrivateData: Bool) {
|
||||||
private init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func configureLogging(to url: URL, parameters: Constants.Log) {
|
|
||||||
pp_log(.common, .debug, "Log to: \(url)")
|
pp_log(.common, .debug, "Log to: \(url)")
|
||||||
|
|
||||||
PassepartoutConfiguration.shared.setLocalLogger(options: .init(
|
setLocalLogger(options: .init(
|
||||||
url: url,
|
url: url,
|
||||||
maxNumberOfLines: parameters.maxNumberOfLines,
|
maxNumberOfLines: parameters.maxNumberOfLines,
|
||||||
maxLevel: parameters.maxLevel,
|
maxLevel: parameters.maxLevel,
|
||||||
mapper: parameters.formatter.formattedLine
|
mapper: parameters.formatter.formattedLine
|
||||||
))
|
))
|
||||||
|
|
||||||
if UserDefaults.group.bool(forKey: AppPreference.logsPrivateData.key) {
|
if logsPrivateData {
|
||||||
PassepartoutConfiguration.shared.logsAddresses = true
|
logsAddresses = true
|
||||||
PassepartoutConfiguration.shared.logsModules = true
|
logsModules = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if let maxAge = parameters.maxAge {
|
if let maxAge = parameters.maxAge {
|
||||||
@ -51,15 +47,15 @@ public struct CommonLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func currentLog(parameters: Constants.Log) -> [String] {
|
public func currentLog(parameters: Constants.Log) -> [String] {
|
||||||
PassepartoutConfiguration.shared.currentLogLines(
|
currentLogLines(
|
||||||
sinceLast: parameters.sinceLast,
|
sinceLast: parameters.sinceLast,
|
||||||
maxLevel: parameters.maxLevel
|
maxLevel: parameters.maxLevel
|
||||||
)
|
)
|
||||||
.map(parameters.formatter.formattedLine)
|
.map(parameters.formatter.formattedLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func availableLogs(at url: URL) -> [Date: URL] {
|
public func availableLogs(at url: URL) -> [Date: URL] {
|
||||||
let parent = url.deletingLastPathComponent()
|
let parent = url.deletingLastPathComponent()
|
||||||
let prefix = url.lastPathComponent
|
let prefix = url.lastPathComponent
|
||||||
do {
|
do {
|
||||||
@ -81,13 +77,13 @@ public struct CommonLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func flushLog() {
|
public func flushLog() {
|
||||||
try? PassepartoutConfiguration.shared.saveLog()
|
try? saveLog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CommonLibrary {
|
private extension PassepartoutConfiguration {
|
||||||
static func purgeLogs(at url: URL, beyond maxAge: TimeInterval) {
|
func purgeLogs(at url: URL, beyond maxAge: TimeInterval) {
|
||||||
let logs = availableLogs(at: url)
|
let logs = availableLogs(at: url)
|
||||||
let minDate = Date().addingTimeInterval(-maxAge)
|
let minDate = Date().addingTimeInterval(-maxAge)
|
||||||
logs.forEach { date, url in
|
logs.forEach { date, url in
|
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// BundleConfiguration+AppGroup.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 10/4/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
|
||||||
|
import PassepartoutKit
|
||||||
|
|
||||||
|
// WARNING: beware of Constants.shared dependency
|
||||||
|
|
||||||
|
extension BundleConfiguration {
|
||||||
|
public static var urlForAppLog: URL {
|
||||||
|
cachesURL.appending(path: Constants.shared.log.appPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var urlForTunnelLog: URL {
|
||||||
|
cachesURL.appending(path: Constants.shared.log.tunnelPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension BundleConfiguration {
|
||||||
|
static var cachesURL: URL {
|
||||||
|
let groupId = mainString(for: .groupId)
|
||||||
|
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupId) else {
|
||||||
|
fatalError("Unable to access App Group container")
|
||||||
|
}
|
||||||
|
return url.appending(components: "Library", "Caches")
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
// TODO: #656, make non-static
|
// WARNING: beware of Constants.shared dependency
|
||||||
|
|
||||||
extension BundleConfiguration {
|
extension BundleConfiguration {
|
||||||
public enum BundleKey: String {
|
public enum BundleKey: String {
|
||||||
case appStoreId
|
case appStoreId
|
||||||
@ -92,21 +93,13 @@ extension BundleConfiguration {
|
|||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var urlForAppLog: URL {
|
|
||||||
cachesURL.appending(path: Constants.shared.log.appPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var urlForTunnelLog: URL {
|
|
||||||
cachesURL.appending(path: Constants.shared.log.tunnelPath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BundleConfiguration {
|
private extension BundleConfiguration {
|
||||||
|
|
||||||
// WARNING: fails from package itself, e.g. in previews
|
// WARNING: fails from package itself, e.g. in previews
|
||||||
static var main: BundleConfiguration {
|
static var main: BundleConfiguration {
|
||||||
guard let bundle = BundleConfiguration(.main, key: Constants.shared.bundle) else {
|
guard let bundle = BundleConfiguration(.main, key: Constants.shared.bundleKey) else {
|
||||||
fatalError("Missing main bundle")
|
fatalError("Missing main bundle")
|
||||||
}
|
}
|
||||||
return bundle
|
return bundle
|
||||||
@ -115,12 +108,4 @@ private extension BundleConfiguration {
|
|||||||
static var isPreview: Bool {
|
static var isPreview: Bool {
|
||||||
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
static var cachesURL: URL {
|
|
||||||
let groupId = mainString(for: .groupId)
|
|
||||||
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupId) else {
|
|
||||||
fatalError("Unable to access App Group container")
|
|
||||||
}
|
|
||||||
return url.appending(components: "Library", "Caches")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ public struct Constants: Decodable, Sendable {
|
|||||||
public let maxAge: TimeInterval?
|
public let maxAge: TimeInterval?
|
||||||
}
|
}
|
||||||
|
|
||||||
public let bundle: String
|
public let bundleKey: String
|
||||||
|
|
||||||
public let websites: Websites
|
public let websites: Websites
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"bundle": "AppConfig",
|
"bundleKey": "AppConfig",
|
||||||
"websites": {
|
"websites": {
|
||||||
"home": "https://passepartoutvpn.app",
|
"home": "https://passepartoutvpn.app",
|
||||||
"subreddit": "https://www.reddit.com/r/passepartout/",
|
"subreddit": "https://www.reddit.com/r/passepartout/",
|
||||||
|
@ -31,7 +31,7 @@ extension LoggerDestination {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
public static let group: UserDefaults = {
|
public static let appGroup: UserDefaults = {
|
||||||
let appGroup = BundleConfiguration.mainString(for: .groupId)
|
let appGroup = BundleConfiguration.mainString(for: .groupId)
|
||||||
guard let defaults = UserDefaults(suiteName: appGroup) else {
|
guard let defaults = UserDefaults(suiteName: appGroup) else {
|
||||||
fatalError("No access to App Group: \(appGroup)")
|
fatalError("No access to App Group: \(appGroup)")
|
||||||
|
@ -219,7 +219,7 @@ private extension GenericCreditsView.LicenseView {
|
|||||||
do {
|
do {
|
||||||
let session = URLSession(configuration: .ephemeral)
|
let session = URLSession(configuration: .ephemeral)
|
||||||
let response = try await session.data(from: url)
|
let response = try await session.data(from: url)
|
||||||
let string = String(decoding: response.0, as: UTF8.self)
|
let string = String(data: response.0, encoding: .utf8)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
content = string
|
content = string
|
||||||
}
|
}
|
||||||
|
@ -188,70 +188,8 @@ extension IAPManagerTests {
|
|||||||
|
|
||||||
// MARK: Purchasable
|
// MARK: Purchasable
|
||||||
|
|
||||||
// func test_givenNoPurchase_thenCanBuyFullAndPlatformVersion() {
|
// TODO: #570, test app library
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
//
|
|
||||||
//#if targetEnvironment(macCatalyst)
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion, .fullVersion_macOS])
|
|
||||||
//#else
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion, .fullVersion_iOS])
|
|
||||||
//#endif
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func test_givenFullVersion_thenCannotPurchase() {
|
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.fullVersion])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
//
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func test_givenPlatformVersion_thenCannotPurchaseSamePlatform() {
|
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
//
|
|
||||||
//#if targetEnvironment(macCatalyst)
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.fullVersion_macOS])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
|
|
||||||
//#else
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.fullVersion_iOS])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
|
|
||||||
//#endif
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func test_givenOtherPlatformVersion_thenCanOnlyPurchaseMissingPlatform() {
|
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
//
|
|
||||||
//#if targetEnvironment(macCatalyst)
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.fullVersion_iOS])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion_macOS])
|
|
||||||
//#else
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.fullVersion_macOS])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion_iOS])
|
|
||||||
//#endif
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func test_givenAppleTV_whenDidNotPurchase_thenCanPurchase() {
|
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
//
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: .appleTV), [.appleTV])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func test_givenAppleTV_whenDidPurchase_thenCannotPurchase() {
|
|
||||||
// let reader = MockReceiptReader()
|
|
||||||
// reader.setReceipt(withBuild: defaultBuildNumber, products: [.appleTV])
|
|
||||||
// let sut = IAPManager(receiptReader: reader)
|
|
||||||
//
|
|
||||||
// XCTAssertEqual(sut.purchasableProducts(withFeature: .appleTV), [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// MARK: App level
|
// MARK: App level
|
||||||
|
|
||||||
func test_givenBetaApp_thenIsRestricted() async {
|
func test_givenBetaApp_thenIsRestricted() async {
|
||||||
|
@ -31,9 +31,10 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
|||||||
private var fwd: NEPTPForwarder?
|
private var fwd: NEPTPForwarder?
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
||||||
CommonLibrary.configureLogging(
|
PassepartoutConfiguration.shared.configureLogging(
|
||||||
to: BundleConfiguration.urlForTunnelLog,
|
to: BundleConfiguration.urlForTunnelLog,
|
||||||
parameters: Constants.shared.log
|
parameters: Constants.shared.log,
|
||||||
|
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||||
)
|
)
|
||||||
fwd = try await NEPTPForwarder(
|
fwd = try await NEPTPForwarder(
|
||||||
provider: self,
|
provider: self,
|
||||||
@ -44,7 +45,7 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
|||||||
do {
|
do {
|
||||||
try await fwd?.startTunnel(options: options)
|
try await fwd?.startTunnel(options: options)
|
||||||
} catch {
|
} catch {
|
||||||
CommonLibrary.flushLog()
|
PassepartoutConfiguration.shared.flushLog()
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,11 +53,11 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
|||||||
override func stopTunnel(with reason: NEProviderStopReason) async {
|
override func stopTunnel(with reason: NEProviderStopReason) async {
|
||||||
await fwd?.stopTunnel(with: reason)
|
await fwd?.stopTunnel(with: reason)
|
||||||
fwd = nil
|
fwd = nil
|
||||||
CommonLibrary.flushLog()
|
PassepartoutConfiguration.shared.flushLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func cancelTunnelWithError(_ error: (any Error)?) {
|
override func cancelTunnelWithError(_ error: (any Error)?) {
|
||||||
CommonLibrary.flushLog()
|
PassepartoutConfiguration.shared.flushLog()
|
||||||
super.cancelTunnelWithError(error)
|
super.cancelTunnelWithError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user