Decouple Constants from BundleConfiguration (#635)

Fixes #619
This commit is contained in:
Davide 2024-09-28 19:05:47 +02:00 committed by GitHub
parent 28a2017da2
commit a29495a69c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 105 additions and 108 deletions

View File

@ -4,8 +4,6 @@
<dict>
<key>AppConfig</key>
<dict>
<key>appId</key>
<string>$(CFG_APP_ID)</string>
<key>appStoreId</key>
<string>$(CFG_APP_STORE_ID)</string>
<key>groupId</key>
@ -16,8 +14,6 @@
<string>$(CFG_TEAM_ID).$(CFG_GROUP_ID)</string>
<key>profilesContainerName</key>
<string>$(CFG_PROFILES_CONTAINER_NAME)</string>
<key>teamId</key>
<string>$(CFG_TEAM_ID)</string>
<key>tunnelId</key>
<string>$(CFG_TUNNEL_ID)</string>
</dict>

View File

@ -35,6 +35,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
import AppKit
import AppUI
import CommonLibrary
import PassepartoutKit
import SwiftUI
final class AppDelegate: NSObject, NSApplicationDelegate {
@ -59,7 +60,7 @@ private extension AppDelegate {
func quitConfirmationAlert() -> NSApplication.TerminateReply {
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = Strings.Alerts.ConfirmQuit.title(Constants.shared.identifiers.displayName)
alert.messageText = Strings.Alerts.ConfirmQuit.title(BundleConfiguration.mainDisplayName)
alert.informativeText = Strings.Alerts.ConfirmQuit.message
alert.addButton(withTitle: Strings.Global.ok)
alert.addButton(withTitle: Strings.Global.cancel)

View File

@ -42,7 +42,7 @@ struct PassepartoutApp: App {
private let context: AppContext = .shared
// private let context: AppContext = .mock(withRegistry: .shared)
private let appName = Constants.shared.identifiers.displayName
private let appName = BundleConfiguration.mainDisplayName
@StateObject
private var theme = Theme()
@ -71,7 +71,7 @@ private extension PassepartoutApp {
)
.onLoad {
CommonLibrary.configureLogging(
to: Constants.shared.urlForAppLog,
to: BundleConfiguration.urlForAppLog,
parameters: Constants.shared.log
)
AppUI.configure(with: context)

View File

@ -28,7 +28,7 @@ import Foundation
import PassepartoutKit
extension Issue {
static func fromBundle(_ bundle: BundleConfiguration, purchasedProducts: Set<AppProduct>, tunnel: Tunnel) async -> Self {
static func with(versionString: String, purchasedProducts: Set<AppProduct>, tunnel: Tunnel) async -> Self {
let appLog = CommonLibrary.currentLog(parameters: Constants.shared.log)
.joined(separator: "\n")
.data(using: .utf8)
@ -42,7 +42,7 @@ extension Issue {
.data(using: .utf8)
}
// latest persisted tunnel log
else if let latestTunnelEntry = CommonLibrary.availableLogs(at: Constants.shared.urlForTunnelLog)
else if let latestTunnelEntry = CommonLibrary.availableLogs(at: BundleConfiguration.urlForTunnelLog)
.max(by: { $0.key < $1.key }) {
tunnelLog = try? Data(contentsOf: latestTunnelEntry.value)
@ -53,7 +53,7 @@ extension Issue {
}
return Issue(
appLine: "\(Strings.Unlocalized.appName) \(bundle.versionString)",
appLine: "\(Strings.Unlocalized.appName) \(versionString)",
purchasedProducts: purchasedProducts,
appLog: appLog,
tunnelLog: tunnelLog

View File

@ -43,7 +43,7 @@ public struct AppProduct: RawRepresentable, Hashable, Sendable {
extension AppProduct: InAppIdentifierProviding {
public var inAppIdentifier: String {
[
BundleConfiguration.main.string(for: .iapBundlePrefix),
BundleConfiguration.mainString(for: .iapBundlePrefix),
rawValue
].joined(separator: ".")
}

View File

@ -29,7 +29,6 @@ import SwiftUI
import UtilsLibrary
struct AboutView: View {
let identifiers: Constants.Identifiers
@Binding
var navigationRoute: AboutRouterView.NavigationRoute?
@ -66,7 +65,6 @@ private extension AboutView {
#Preview {
AboutView(
identifiers: Constants.shared.identifiers,
navigationRoute: .constant(nil)
)
}

View File

@ -24,6 +24,7 @@
//
import CommonLibrary
import PassepartoutKit
import SwiftUI
struct LinksView: View {
@ -42,10 +43,14 @@ private extension LinksView {
.shared
}
var appStoreId: String {
BundleConfiguration.mainString(for: .appStoreId)
}
var supportSection: some View {
Section {
Link(Strings.Views.About.Links.Rows.joinCommunity, destination: constants.websites.subreddit)
Link(Strings.Views.About.Links.Rows.writeReview, destination: constants.urlForReview)
Link(Strings.Views.About.Links.Rows.writeReview, destination: BundleConfiguration.urlForReview)
} header: {
Text(Strings.Views.About.Links.Sections.support)
}

View File

@ -32,7 +32,6 @@ extension AboutRouterView {
var body: some View {
NavigationStack {
AboutView(
identifiers: Constants.shared.identifiers,
navigationRoute: $navigationRoute
)
.toolbar {

View File

@ -25,6 +25,7 @@
#if os(iOS)
import PassepartoutKit
import SwiftUI
extension AboutView {
@ -41,7 +42,7 @@ extension AboutView {
Section {
diagnosticsLink
Text(Strings.Global.version)
.withTrailingText(identifiers.versionString)
.withTrailingText(BundleConfiguration.mainVersionString)
}
}
}

View File

@ -32,7 +32,6 @@ extension AboutRouterView {
var body: some View {
NavigationSplitView {
AboutView(
identifiers: Constants.shared.identifiers,
navigationRoute: $navigationRoute
)
} detail: {

View File

@ -23,10 +23,11 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import SwiftUI
#if os(macOS)
import PassepartoutKit
import SwiftUI
extension AboutView {
var listView: some View {
List(selection: $navigationRoute) {
@ -39,7 +40,7 @@ extension AboutView {
}
}
.safeAreaInset(edge: .bottom) {
Text(identifiers.versionString)
Text(BundleConfiguration.mainVersionString)
.padding(.bottom)
}
}

View File

@ -51,7 +51,7 @@ struct DiagnosticsView: View {
private var logsPrivateData = false
var availableTunnelLogs: () -> [LogEntry] = {
CommonLibrary.availableLogs(at: Constants.shared.urlForTunnelLog)
CommonLibrary.availableLogs(at: BundleConfiguration.urlForTunnelLog)
.sorted {
$0.key > $1.key
}

View File

@ -25,6 +25,7 @@
#if os(iOS)
import PassepartoutKit
import SwiftUI
import UIKit
import UtilsLibrary
@ -38,7 +39,11 @@ extension ReportIssueButton: View {
defer {
isPending = false
}
let issue = await Issue.fromBundle(.main, purchasedProducts: purchasedProducts, tunnel: tunnel)
let issue = await Issue.with(
versionString: BundleConfiguration.mainVersionString,
purchasedProducts: purchasedProducts,
tunnel: tunnel
)
guard MailComposerView.canSendMail() else {
openMailTo(with: issue)
return

View File

@ -25,6 +25,7 @@
#if os(macOS)
import PassepartoutKit
import SwiftUI
extension ReportIssueButton: View {
@ -35,7 +36,11 @@ extension ReportIssueButton: View {
return
}
Task {
let issue = await Issue.fromBundle(.main, purchasedProducts: purchasedProducts, tunnel: tunnel)
let issue = await Issue.with(
versionString: BundleConfiguration.mainVersionString,
purchasedProducts: purchasedProducts,
tunnel: tunnel
)
service.recipients = [issue.to]
service.subject = issue.subject
service.perform(withItems: issue.items)

View File

@ -28,8 +28,6 @@ import PassepartoutKit
extension BundleConfiguration {
public enum BundleKey: String {
case appId
case appStoreId
case customUserLevel
@ -42,31 +40,76 @@ extension BundleConfiguration {
case profilesContainerName
case teamId
case tunnelId
}
// WARNING: nil from package itself, e.g. in previews
static let failableMain: BundleConfiguration? = {
BundleConfiguration(.main, key: "AppConfig")
}()
public static let main: BundleConfiguration = {
guard let failableMain else {
fatalError("Unable to build BundleConfiguration")
public static var mainDisplayName: String {
if isPreview {
return "preview-display-name"
}
return failableMain
}()
return main.displayName
}
public func string(for key: BundleKey) -> String {
guard let value: String = value(forKey: key.rawValue) else {
fatalError("Key '\(key)' not found in bundle")
public static var mainVersionString: String {
if isPreview {
return "preview-1.2.3"
}
return main.versionString
}
public static func mainString(for key: BundleKey) -> String {
if isPreview {
return "preview-key(\(key.rawValue))"
}
guard let value: String = main.value(forKey: key.rawValue) else {
fatalError("Missing main bundle key: \(key.rawValue)")
}
return value
}
public func integerIfPresent(for key: BundleKey) -> Int? {
value(forKey: key.rawValue)
public static func mainIntegerIfPresent(for key: BundleKey) -> Int? {
if isPreview {
return nil
}
return main.value(forKey: key.rawValue)
}
public static var urlForReview: URL {
let appStoreId = mainString(for: .appStoreId)
guard let url = URL(string: "https://apps.apple.com/app/id\(appStoreId)?action=write-review") else {
fatalError("Unable to build urlForReview")
}
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 {
// WARNING: fails from package itself, e.g. in previews
static var main: BundleConfiguration {
guard let bundle = BundleConfiguration(.main, key: Constants.shared.bundle) else {
fatalError("Missing main bundle")
}
return bundle
}
static var isPreview: Bool {
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")
}
}

View File

@ -31,36 +31,6 @@ extension Constants {
}
public struct Constants: Decodable, Sendable {
private static var bundleConfiguration: BundleConfiguration? {
.failableMain
}
public struct Identifiers: Decodable, Sendable {
public var appId: String {
bundleConfiguration?.string(for: .appId) ?? "1234567890"
}
public var appStoreId: String {
bundleConfiguration?.string(for: .appStoreId) ?? "11223344"
}
public var groupId: String {
bundleConfiguration?.string(for: .groupId) ?? "fake-group-id"
}
public var iapBundlePrefix: String {
bundleConfiguration?.string(for: .iapBundlePrefix) ?? "fake-iap-prefix"
}
public var displayName: String {
bundleConfiguration?.displayName ?? "DisplayName"
}
public var versionString: String {
bundleConfiguration?.versionString ?? "1.0.0 (1000)"
}
}
public struct Websites: Decodable, Sendable {
public let home: URL
@ -158,7 +128,7 @@ public struct Constants: Decodable, Sendable {
public let maxAge: TimeInterval?
}
public let identifiers: Identifiers
public let bundle: String
public let websites: Websites
@ -170,24 +140,3 @@ public struct Constants: Decodable, Sendable {
public let log: Log
}
extension Constants {
public var urlForReview: URL {
URL(string: "https://apps.apple.com/app/id\(identifiers.appStoreId)?action=write-review")!
}
public var urlForAppLog: URL {
cachesURL.appending(path: log.appPath)
}
public var urlForTunnelLog: URL {
cachesURL.appending(path: log.tunnelPath)
}
private var cachesURL: URL {
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifiers.groupId) else {
fatalError("Unable to access App Group container")
}
return url.appending(components: "Library", "Caches")
}
}

View File

@ -1,6 +1,5 @@
{
"identifiers": {
},
"bundle": "AppConfig",
"websites": {
"home": "https://passepartoutvpn.app",
"subreddit": "https://www.reddit.com/r/passepartout/",

View File

@ -32,7 +32,7 @@ extension LoggerDestination {
extension UserDefaults {
public static let group: UserDefaults = {
let appGroup = BundleConfiguration.main.string(for: .groupId)
let appGroup = BundleConfiguration.mainString(for: .groupId)
guard let defaults = UserDefaults(suiteName: appGroup) else {
fatalError("No access to App Group: \(appGroup)")
}

View File

@ -37,7 +37,7 @@ extension ProfileManager {
let store = CoreDataPersistentStore(
logger: .default,
containerName: BundleConfiguration.main.string(for: .profilesContainerName),
containerName: BundleConfiguration.mainString(for: .profilesContainerName),
model: model,
cloudKit: false,
cloudKitIdentifier: nil,
@ -70,7 +70,7 @@ extension Tunnel {
extension Tunnel {
static let shared = Tunnel(
strategy: NETunnelStrategy(
bundleIdentifier: BundleConfiguration.main.string(for: .tunnelId),
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
encoder: .shared
)
)

View File

@ -53,7 +53,7 @@ extension IAPManager {
return testAppType
}
if let infoValue = BundleConfiguration.main.integerIfPresent(for: .customUserLevel),
if let infoValue = BundleConfiguration.mainIntegerIfPresent(for: .customUserLevel),
let testAppType = AppUserLevel(rawValue: infoValue) {
return testAppType

View File

@ -64,7 +64,7 @@ extension Registry {
extension TunnelEnvironment where Self == AppGroupEnvironment {
static var shared: Self {
AppGroupEnvironment(
appGroup: BundleConfiguration.main.string(for: .groupId),
appGroup: BundleConfiguration.mainString(for: .groupId),
prefix: "PassepartoutKit."
)
}
@ -84,9 +84,9 @@ extension NEProtocolDecoder where Self == KeychainNEProtocolCoder {
private var sharedProtocolCoder: KeychainNEProtocolCoder {
KeychainNEProtocolCoder(
tunnelBundleIdentifier: BundleConfiguration.main.string(for: .tunnelId),
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
registry: .shared,
coder: CodableProfileCoder(),
keychain: AppleKeychain(group: BundleConfiguration.main.string(for: .keychainGroupId))
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
)
}

View File

@ -32,7 +32,7 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
override func startTunnel(options: [String: NSObject]? = nil) async throws {
CommonLibrary.configureLogging(
to: Constants.shared.urlForTunnelLog,
to: BundleConfiguration.urlForTunnelLog,
parameters: Constants.shared.log
)
fwd = try await NEPTPForwarder(

View File

@ -4,14 +4,10 @@
<dict>
<key>AppConfig</key>
<dict>
<key>appId</key>
<string>$(CFG_APP_ID)</string>
<key>groupId</key>
<string>$(CFG_GROUP_ID)</string>
<key>keychainGroupId</key>
<string>$(CFG_TEAM_ID).$(CFG_GROUP_ID)</string>
<key>teamId</key>
<string>$(CFG_TEAM_ID)</string>
<key>tunnelId</key>
<string>$(CFG_TUNNEL_ID)</string>
</dict>