From 81d68273342bb1b9f02e8ed8b7131fbccc34758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 Jan 2026 02:46:52 +0800 Subject: [PATCH] Refactor to eliminate hardcoded identifiers Read package identifier and app group ID from Info.plist at runtime via AppConfiguration. This enables SFM.System to use TeamID-prefixed app group format required by macOS Sequoia, fixing the privacy prompt when accessing app group data. --- .../Service/UIProfileUpdateTask.swift | 2 +- .../Views/Setting/CoreView.swift | 2 +- Extension/Info.plist | 4 +++ .../FileProviderExtension.swift | 9 ++++++- FileProviderExtension/Info.plist | 6 ++++- IntentsExtension/Info.plist | 4 +++ Library/Database/Profile+Transferable.swift | 2 +- Library/Network/ExtensionProfile.swift | 6 ++--- Library/Shared/AppConfiguration.swift | 25 +++++++++++++++++ Library/Shared/FilePath.swift | 27 +++++++++---------- SFI/ApplicationDelegate.swift | 2 +- SFI/Info.plist | 14 ++++++---- SFM.System/Info.plist | 12 ++++++--- SFM.System/SFM.entitlements | 2 +- SFM/Info.plist | 12 ++++++--- SFT/Info.plist | 6 ++++- SystemExtension/Info.plist | 6 ++++- SystemExtension/SystemExtension.entitlements | 2 +- TVExtension/Info.plist | 4 +++ WidgetExtension/Info.plist | 4 +++ sing-box.xcodeproj/project.pbxproj | 8 ++++++ 21 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 Library/Shared/AppConfiguration.swift diff --git a/ApplicationLibrary/Service/UIProfileUpdateTask.swift b/ApplicationLibrary/Service/UIProfileUpdateTask.swift index 4fde2a7..387a342 100644 --- a/ApplicationLibrary/Service/UIProfileUpdateTask.swift +++ b/ApplicationLibrary/Service/UIProfileUpdateTask.swift @@ -7,7 +7,7 @@ import Library #if os(iOS) || os(tvOS) public class UIProfileUpdateTask: BGAppRefreshTask { - private static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles" + private static let taskSchedulerPermittedIdentifier = AppConfiguration.backgroundTaskID private static var registered = false public static func configure() throws { diff --git a/ApplicationLibrary/Views/Setting/CoreView.swift b/ApplicationLibrary/Views/Setting/CoreView.swift index 9c2bfb0..1c54601 100644 --- a/ApplicationLibrary/Views/Setting/CoreView.swift +++ b/ApplicationLibrary/Views/Setting/CoreView.swift @@ -135,7 +135,7 @@ public struct CoreView: View { private nonisolated func openInFilesApp() async { do { let domains = try await NSFileProviderManager.domains() - guard let domain = domains.first(where: { $0.identifier.rawValue == "io.nekohasekai.sfavt.workingdir" }) else { + guard let domain = domains.first(where: { $0.identifier.rawValue == AppConfiguration.fileProviderDomainID }) else { throw NSError(domain: "CoreView", code: 0, userInfo: [NSLocalizedDescriptionKey: "File provider domain not found"]) } guard let manager = NSFileProviderManager(for: domain) else { diff --git a/Extension/Info.plist b/Extension/Info.plist index 3059459..81f2b5b 100644 --- a/Extension/Info.plist +++ b/Extension/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) NSExtension NSExtensionPointIdentifier diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift index dc8dd6c..77b818a 100644 --- a/FileProviderExtension/FileProviderExtension.swift +++ b/FileProviderExtension/FileProviderExtension.swift @@ -4,8 +4,15 @@ import UniformTypeIdentifiers class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension { let domain: NSFileProviderDomain + private static let appGroupID: String = { + guard let value = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String else { + fatalError("Missing AppGroupIdentifier in Info.plist") + } + return value + }() + private var workingDirectory: URL { - let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.io.nekohasekai.sfavt")! + let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroupID)! return groupURL .appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Caches", isDirectory: true) diff --git a/FileProviderExtension/Info.plist b/FileProviderExtension/Info.plist index 6f67c56..953209c 100644 --- a/FileProviderExtension/Info.plist +++ b/FileProviderExtension/Info.plist @@ -2,10 +2,14 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) NSExtension NSExtensionFileProviderDocumentGroup - group.io.nekohasekai.sfavt + $(APP_GROUP_IDENTIFIER) NSExtensionFileProviderSupportsEnumeration NSExtensionPointIdentifier diff --git a/IntentsExtension/Info.plist b/IntentsExtension/Info.plist index 8d15acb..63e5faf 100644 --- a/IntentsExtension/Info.plist +++ b/IntentsExtension/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) EXAppExtensionAttributes EXExtensionPointIdentifier diff --git a/Library/Database/Profile+Transferable.swift b/Library/Database/Profile+Transferable.swift index 621f4b7..7e6c140 100644 --- a/Library/Database/Profile+Transferable.swift +++ b/Library/Database/Profile+Transferable.swift @@ -115,7 +115,7 @@ public struct TypedProfile: Transferable, Codable { } public extension UTType { - static var profile: UTType { .init(exportedAs: "io.nekohasekai.sfavt.profile") } + static var profile: UTType { .init(exportedAs: AppConfiguration.profileUTType) } } #if !os(tvOS) diff --git a/Library/Network/ExtensionProfile.swift b/Library/Network/ExtensionProfile.swift index 14e1a75..aa5cf2e 100644 --- a/Library/Network/ExtensionProfile.swift +++ b/Library/Network/ExtensionProfile.swift @@ -4,7 +4,7 @@ import NetworkExtension @MainActor public class ExtensionProfile: ObservableObject { - public static let controlKind = "io.nekohasekai.sfavt.widget.ServiceToggle" + public static let controlKind = AppConfiguration.widgetControlKind private let manager: NEVPNManager private var connection: NEVPNConnection @@ -162,9 +162,9 @@ public class ExtensionProfile: ObservableObject { manager.localizedDescription = Variant.applicationName let tunnelProtocol = NETunnelProviderProtocol() if Variant.useSystemExtension { - tunnelProtocol.providerBundleIdentifier = "\(FilePath.packageName).system" + tunnelProtocol.providerBundleIdentifier = AppConfiguration.systemExtensionBundleID } else { - tunnelProtocol.providerBundleIdentifier = "\(FilePath.packageName).extension" + tunnelProtocol.providerBundleIdentifier = AppConfiguration.extensionBundleID } tunnelProtocol.serverAddress = "sing-box" manager.protocolConfiguration = tunnelProtocol diff --git a/Library/Shared/AppConfiguration.swift b/Library/Shared/AppConfiguration.swift new file mode 100644 index 0000000..7e37bf2 --- /dev/null +++ b/Library/Shared/AppConfiguration.swift @@ -0,0 +1,25 @@ +import Foundation + +public enum AppConfiguration { + public static let packageName: String = { + guard let value = Bundle.main.object(forInfoDictionaryKey: "BasePackageIdentifier") as? String else { + fatalError("Missing BasePackageIdentifier in Info.plist") + } + return value + }() + + public static let appGroupID: String = { + guard let value = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String else { + fatalError("Missing AppGroupIdentifier in Info.plist") + } + return value + }() + + public static var extensionBundleID: String { "\(packageName).extension" } + public static var systemExtensionBundleID: String { "\(packageName).system" } + public static var fileProviderDomainID: String { "\(packageName).workingdir" } + public static var widgetControlKind: String { "\(packageName).widget.ServiceToggle" } + public static var profileUTType: String { "\(packageName).profile" } + public static var backgroundTaskID: String { "\(packageName).update_profiles" } + public static var iCloudContainerID: String { "iCloud.\(packageName)" } +} diff --git a/Library/Shared/FilePath.swift b/Library/Shared/FilePath.swift index d0ee7a4..fbd9e3e 100644 --- a/Library/Shared/FilePath.swift +++ b/Library/Shared/FilePath.swift @@ -1,32 +1,29 @@ import Foundation public enum FilePath { - public static let packageName = "io.nekohasekai.sfavt" -} + public static let packageName = AppConfiguration.packageName + public static let groupName = AppConfiguration.appGroupID -public extension FilePath { - static let groupName = "group.\(packageName)" - - private static let defaultSharedDirectory: URL! = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: FilePath.groupName) + private static let defaultSharedDirectory: URL! = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupName) #if os(iOS) - static let sharedDirectory = defaultSharedDirectory! + public static let sharedDirectory = defaultSharedDirectory! #elseif os(tvOS) - static let sharedDirectory = defaultSharedDirectory + public static let sharedDirectory = defaultSharedDirectory .appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Caches", isDirectory: true) #elseif os(macOS) - static var sharedDirectory: URL! = defaultSharedDirectory + public static var sharedDirectory: URL! = defaultSharedDirectory #endif #if os(iOS) - static let cacheDirectory = sharedDirectory + public static let cacheDirectory = sharedDirectory .appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Caches", isDirectory: true) #elseif os(tvOS) - static let cacheDirectory = sharedDirectory + public static let cacheDirectory = sharedDirectory #elseif os(macOS) - static var cacheDirectory: URL { + public static var cacheDirectory: URL { sharedDirectory .appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Caches", isDirectory: true) @@ -34,15 +31,15 @@ public extension FilePath { #endif #if os(macOS) - static var workingDirectory: URL { + public static var workingDirectory: URL { cacheDirectory.appendingPathComponent("Working", isDirectory: true) } #else - static let workingDirectory = cacheDirectory.appendingPathComponent("Working", isDirectory: true) + public static let workingDirectory = cacheDirectory.appendingPathComponent("Working", isDirectory: true) #endif - static var iCloudDirectory = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true) ?? URL(string: "stub")! + public static var iCloudDirectory = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true) ?? URL(string: "stub")! } public extension URL { diff --git a/SFI/ApplicationDelegate.swift b/SFI/ApplicationDelegate.swift index 8af1fd7..633a5b7 100644 --- a/SFI/ApplicationDelegate.swift +++ b/SFI/ApplicationDelegate.swift @@ -85,7 +85,7 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCe @available(iOS 16.0, *) private nonisolated func registerFileProviderDomain() { let domain = NSFileProviderDomain( - identifier: NSFileProviderDomainIdentifier("io.nekohasekai.sfavt.workingdir"), + identifier: NSFileProviderDomainIdentifier(AppConfiguration.fileProviderDomainID), displayName: "sing-box" ) NSFileProviderManager.add(domain) { error in diff --git a/SFI/Info.plist b/SFI/Info.plist index c043f02..da29cac 100644 --- a/SFI/Info.plist +++ b/SFI/Info.plist @@ -2,9 +2,13 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) BGTaskSchedulerPermittedIdentifiers - io.nekohasekai.sfavt.update_profiles + $(BASE_PACKAGE_IDENTIFIER).update_profiles CFBundleDocumentTypes @@ -19,7 +23,7 @@ Owner LSItemContentTypes - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile @@ -55,7 +59,7 @@ NSUbiquitousContainers - iCloud.io.nekohasekai.sfavt + iCloud.$(BASE_PACKAGE_IDENTIFIER) NSUbiquitousContainerIsDocumentScopePublic @@ -84,7 +88,7 @@ UTTypeIconFiles UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension @@ -110,7 +114,7 @@ AppIcon.icns UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension diff --git a/SFM.System/Info.plist b/SFM.System/Info.plist index 8c21c62..4902489 100644 --- a/SFM.System/Info.plist +++ b/SFM.System/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) CFBundleDocumentTypes @@ -15,7 +19,7 @@ Owner LSItemContentTypes - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile @@ -38,7 +42,7 @@ NSUbiquitousContainers - iCloud.io.nekohasekai.sfavt + iCloud.$(BASE_PACKAGE_IDENTIFIER) NSUbiquitousContainerIsDocumentScopePublic @@ -62,7 +66,7 @@ UTTypeIconFiles UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension @@ -88,7 +92,7 @@ AppIcon.icns UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension diff --git a/SFM.System/SFM.entitlements b/SFM.System/SFM.entitlements index f22ea4f..d12ff84 100644 --- a/SFM.System/SFM.entitlements +++ b/SFM.System/SFM.entitlements @@ -24,7 +24,7 @@ com.apple.security.application-groups - group.io.nekohasekai.sfavt + $(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER) com.apple.security.files.user-selected.read-write diff --git a/SFM/Info.plist b/SFM/Info.plist index e7c2f49..58ef8ab 100644 --- a/SFM/Info.plist +++ b/SFM/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) CFBundleDocumentTypes @@ -15,7 +19,7 @@ Owner LSItemContentTypes - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile @@ -38,7 +42,7 @@ NSUbiquitousContainers - iCloud.io.nekohasekai.sfavt + iCloud.$(BASE_PACKAGE_IDENTIFIER) NSUbiquitousContainerIsDocumentScopePublic @@ -62,7 +66,7 @@ UTTypeIconFiles UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension @@ -88,7 +92,7 @@ AppIcon.icns UTTypeIdentifier - io.nekohasekai.sfavt.profile + $(BASE_PACKAGE_IDENTIFIER).profile UTTypeTagSpecification public.filename-extension diff --git a/SFT/Info.plist b/SFT/Info.plist index ea9f8e4..458b680 100644 --- a/SFT/Info.plist +++ b/SFT/Info.plist @@ -2,9 +2,13 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) BGTaskSchedulerPermittedIdentifiers - io.nekohasekai.sfavt.update_profiles + $(BASE_PACKAGE_IDENTIFIER).update_profiles CFBundleURLTypes diff --git a/SystemExtension/Info.plist b/SystemExtension/Info.plist index ea41763..203aeb4 100644 --- a/SystemExtension/Info.plist +++ b/SystemExtension/Info.plist @@ -2,10 +2,14 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) NetworkExtension NEMachServiceName - group.io.nekohasekai.sfavt.system + $(APP_GROUP_IDENTIFIER).system NEProviderClasses com.apple.networkextension.packet-tunnel diff --git a/SystemExtension/SystemExtension.entitlements b/SystemExtension/SystemExtension.entitlements index 58ddbdc..6d43459 100644 --- a/SystemExtension/SystemExtension.entitlements +++ b/SystemExtension/SystemExtension.entitlements @@ -10,7 +10,7 @@ com.apple.security.application-groups - group.io.nekohasekai.sfavt + $(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER) com.apple.security.app-sandbox diff --git a/TVExtension/Info.plist b/TVExtension/Info.plist index 3059459..81f2b5b 100644 --- a/TVExtension/Info.plist +++ b/TVExtension/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) NSExtension NSExtensionPointIdentifier diff --git a/WidgetExtension/Info.plist b/WidgetExtension/Info.plist index 0f118fb..160325a 100644 --- a/WidgetExtension/Info.plist +++ b/WidgetExtension/Info.plist @@ -2,6 +2,10 @@ + BasePackageIdentifier + $(BASE_PACKAGE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) NSExtension NSExtensionPointIdentifier diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 86785d8..c42dec6 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -1984,7 +1984,9 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_GROUP_IDENTIFIER = "group.$(BASE_PACKAGE_IDENTIFIER)"; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BASE_PACKAGE_IDENTIFIER = io.nekohasekai.sfavt; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2052,7 +2054,9 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_GROUP_IDENTIFIER = "group.$(BASE_PACKAGE_IDENTIFIER)"; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BASE_PACKAGE_IDENTIFIER = io.nekohasekai.sfavt; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2367,6 +2371,7 @@ 3AEECBFD2A6DF40A006A0E0C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APP_GROUP_IDENTIFIER = "$(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER)"; CODE_SIGN_ENTITLEMENTS = SystemExtension/SystemExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; @@ -2403,6 +2408,7 @@ 3AEECBFE2A6DF40A006A0E0C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APP_GROUP_IDENTIFIER = "$(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER)"; CODE_SIGN_ENTITLEMENTS = SystemExtension/SystemExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; @@ -2439,6 +2445,7 @@ 3AEECC112A6DF9CA006A0E0C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APP_GROUP_IDENTIFIER = "$(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; @@ -2480,6 +2487,7 @@ 3AEECC122A6DF9CA006A0E0C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APP_GROUP_IDENTIFIER = "$(TeamIdentifierPrefix)$(BASE_PACKAGE_IDENTIFIER)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;