From a29495a69cd30fa907877ddf9e35c12a0546bb0a Mon Sep 17 00:00:00 2001 From: Davide Date: Sat, 28 Sep 2024 19:05:47 +0200 Subject: [PATCH] Decouple Constants from BundleConfiguration (#635) Fixes #619 --- Passepartout/App/App.plist | 4 - Passepartout/App/AppDelegate.swift | 3 +- Passepartout/App/PassepartoutApp.swift | 4 +- .../Sources/AppUI/Domain/Issue+App.swift | 6 +- .../Sources/AppUI/IAP/AppProduct.swift | 2 +- .../Sources/AppUI/Views/About/AboutView.swift | 2 - .../Sources/AppUI/Views/About/LinksView.swift | 7 +- .../Views/About/iOS/AboutRouterView+iOS.swift | 1 - .../AppUI/Views/About/iOS/AboutView+iOS.swift | 3 +- .../About/macOS/AboutRouterView+macOS.swift | 1 - .../Views/About/macOS/AboutView+macOS.swift | 7 +- .../Views/Diagnostics/DiagnosticsView.swift | 2 +- .../iOS/ReportIssueButton+iOS.swift | 7 +- .../macOS/ReportIssueButton+macOS.swift | 7 +- .../Domain/BundleConfiguration+Main.swift | 81 ++++++++++++++----- .../CommonLibrary/Domain/Constants.swift | 53 +----------- .../CommonLibrary/Resources/Constants.json | 3 +- .../Sources/CommonLibrary/Shared.swift | 2 +- Passepartout/Shared/Shared+AppLibrary.swift | 4 +- Passepartout/Shared/Shared+AppUI.swift | 2 +- Passepartout/Shared/Shared.swift | 6 +- .../Tunnel/PacketTunnelProvider.swift | 2 +- Passepartout/Tunnel/Tunnel.plist | 4 - 23 files changed, 105 insertions(+), 108 deletions(-) diff --git a/Passepartout/App/App.plist b/Passepartout/App/App.plist index 54af2e0a..f0f50eca 100644 --- a/Passepartout/App/App.plist +++ b/Passepartout/App/App.plist @@ -4,8 +4,6 @@ AppConfig - appId - $(CFG_APP_ID) appStoreId $(CFG_APP_STORE_ID) groupId @@ -16,8 +14,6 @@ $(CFG_TEAM_ID).$(CFG_GROUP_ID) profilesContainerName $(CFG_PROFILES_CONTAINER_NAME) - teamId - $(CFG_TEAM_ID) tunnelId $(CFG_TUNNEL_ID) diff --git a/Passepartout/App/AppDelegate.swift b/Passepartout/App/AppDelegate.swift index 0d8e9d57..e441af5c 100644 --- a/Passepartout/App/AppDelegate.swift +++ b/Passepartout/App/AppDelegate.swift @@ -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) diff --git a/Passepartout/App/PassepartoutApp.swift b/Passepartout/App/PassepartoutApp.swift index 288d76b1..156a89a3 100644 --- a/Passepartout/App/PassepartoutApp.swift +++ b/Passepartout/App/PassepartoutApp.swift @@ -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) diff --git a/Passepartout/Library/Sources/AppUI/Domain/Issue+App.swift b/Passepartout/Library/Sources/AppUI/Domain/Issue+App.swift index e539f3b4..e0f185e0 100644 --- a/Passepartout/Library/Sources/AppUI/Domain/Issue+App.swift +++ b/Passepartout/Library/Sources/AppUI/Domain/Issue+App.swift @@ -28,7 +28,7 @@ import Foundation import PassepartoutKit extension Issue { - static func fromBundle(_ bundle: BundleConfiguration, purchasedProducts: Set, tunnel: Tunnel) async -> Self { + static func with(versionString: String, purchasedProducts: Set, 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 diff --git a/Passepartout/Library/Sources/AppUI/IAP/AppProduct.swift b/Passepartout/Library/Sources/AppUI/IAP/AppProduct.swift index b9b64d7a..23416b7d 100644 --- a/Passepartout/Library/Sources/AppUI/IAP/AppProduct.swift +++ b/Passepartout/Library/Sources/AppUI/IAP/AppProduct.swift @@ -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: ".") } diff --git a/Passepartout/Library/Sources/AppUI/Views/About/AboutView.swift b/Passepartout/Library/Sources/AppUI/Views/About/AboutView.swift index 834c002a..87093491 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/AboutView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/AboutView.swift @@ -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) ) } diff --git a/Passepartout/Library/Sources/AppUI/Views/About/LinksView.swift b/Passepartout/Library/Sources/AppUI/Views/About/LinksView.swift index 3f3a0980..e5f05328 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/LinksView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/LinksView.swift @@ -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) } diff --git a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutRouterView+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutRouterView+iOS.swift index 413ef044..ed8368b0 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutRouterView+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutRouterView+iOS.swift @@ -32,7 +32,6 @@ extension AboutRouterView { var body: some View { NavigationStack { AboutView( - identifiers: Constants.shared.identifiers, navigationRoute: $navigationRoute ) .toolbar { diff --git a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift index e9a33a0e..3b0e119c 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift @@ -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) } } } diff --git a/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutRouterView+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutRouterView+macOS.swift index 67f1bad8..b40feb67 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutRouterView+macOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutRouterView+macOS.swift @@ -32,7 +32,6 @@ extension AboutRouterView { var body: some View { NavigationSplitView { AboutView( - identifiers: Constants.shared.identifiers, navigationRoute: $navigationRoute ) } detail: { diff --git a/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutView+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutView+macOS.swift index d27f2ca6..8aa237bc 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutView+macOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/macOS/AboutView+macOS.swift @@ -23,10 +23,11 @@ // along with Passepartout. If not, see . // -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) } } diff --git a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/DiagnosticsView.swift b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/DiagnosticsView.swift index 5fc2051e..b99292b6 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/DiagnosticsView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/DiagnosticsView.swift @@ -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 } diff --git a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/iOS/ReportIssueButton+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/iOS/ReportIssueButton+iOS.swift index 37c18f7f..77d50682 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/iOS/ReportIssueButton+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/iOS/ReportIssueButton+iOS.swift @@ -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 diff --git a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/macOS/ReportIssueButton+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/macOS/ReportIssueButton+macOS.swift index e1241481..e393ffc2 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Diagnostics/macOS/ReportIssueButton+macOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Diagnostics/macOS/ReportIssueButton+macOS.swift @@ -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) diff --git a/Passepartout/Library/Sources/CommonLibrary/Domain/BundleConfiguration+Main.swift b/Passepartout/Library/Sources/CommonLibrary/Domain/BundleConfiguration+Main.swift index 50c34354..3307beff 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Domain/BundleConfiguration+Main.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Domain/BundleConfiguration+Main.swift @@ -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") } } diff --git a/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift b/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift index c730e320..975bc101 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift @@ -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") - } -} diff --git a/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json b/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json index d5a9a80a..2bbcff3a 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json +++ b/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json @@ -1,6 +1,5 @@ { - "identifiers": { - }, + "bundle": "AppConfig", "websites": { "home": "https://passepartoutvpn.app", "subreddit": "https://www.reddit.com/r/passepartout/", diff --git a/Passepartout/Library/Sources/CommonLibrary/Shared.swift b/Passepartout/Library/Sources/CommonLibrary/Shared.swift index b237d378..00b665d5 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Shared.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Shared.swift @@ -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)") } diff --git a/Passepartout/Shared/Shared+AppLibrary.swift b/Passepartout/Shared/Shared+AppLibrary.swift index 4b67f0f3..a58bf0ba 100644 --- a/Passepartout/Shared/Shared+AppLibrary.swift +++ b/Passepartout/Shared/Shared+AppLibrary.swift @@ -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 ) ) diff --git a/Passepartout/Shared/Shared+AppUI.swift b/Passepartout/Shared/Shared+AppUI.swift index 6002d005..7dd986b9 100644 --- a/Passepartout/Shared/Shared+AppUI.swift +++ b/Passepartout/Shared/Shared+AppUI.swift @@ -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 diff --git a/Passepartout/Shared/Shared.swift b/Passepartout/Shared/Shared.swift index 92f69b0d..69a6892a 100644 --- a/Passepartout/Shared/Shared.swift +++ b/Passepartout/Shared/Shared.swift @@ -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)) ) } diff --git a/Passepartout/Tunnel/PacketTunnelProvider.swift b/Passepartout/Tunnel/PacketTunnelProvider.swift index accce123..3906e84a 100644 --- a/Passepartout/Tunnel/PacketTunnelProvider.swift +++ b/Passepartout/Tunnel/PacketTunnelProvider.swift @@ -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( diff --git a/Passepartout/Tunnel/Tunnel.plist b/Passepartout/Tunnel/Tunnel.plist index c1c16db6..77958724 100644 --- a/Passepartout/Tunnel/Tunnel.plist +++ b/Passepartout/Tunnel/Tunnel.plist @@ -4,14 +4,10 @@ AppConfig - appId - $(CFG_APP_ID) groupId $(CFG_GROUP_ID) keychainGroupId $(CFG_TEAM_ID).$(CFG_GROUP_ID) - teamId - $(CFG_TEAM_ID) tunnelId $(CFG_TUNNEL_ID)