ef8eab4a07
Carries the rebrand from the backend (APPLE_CLIENT_ID, APNS_TOPIC) all the way through the iOS targets: - All target PRODUCT_BUNDLE_IDENTIFIERs: com.tt.honeyDue.* → com.myhoneydue.honeyDue.* - DEVELOPMENT_TEAM: V3PF3M6B6U → X86BR9WTLD (across every target) - APP_GROUP_IDENTIFIER: group.com.tt.honeyDue.* → group.com.myhoneydue.honeyDue.* - BGTaskSchedulerPermittedIdentifiers + BackgroundTaskManager constant - KeychainHelper service identifier - StoreKit fallback product IDs + Info.plist IAP product ID keys - ExportOptions.plist teamID - NSCamera / NSPhotoLibrary usage descriptions reworded - Onboarding suggestion strings reworked (new %lld%% match copy, dropped old "Great match" / "Good match" / "Generating suggestions" strings — replaced by relevance-percentage labels) - xctestplan + settings.local.json housekeeping App-group rename means UserDefaults / shared-container data written to the old group ID is abandoned. Acceptable since this is pre-launch.
65 lines
2.2 KiB
Swift
65 lines
2.2 KiB
Swift
import Foundation
|
|
import Security
|
|
import ComposeApp
|
|
|
|
/// Implements KeychainDelegate (Kotlin interface) to provide secure token storage
|
|
/// via the iOS Keychain. Injected into TokenManager.Companion before DataManager init.
|
|
final class KeychainHelper: NSObject, KeychainDelegate {
|
|
static let shared = KeychainHelper()
|
|
|
|
private let service = "com.myhoneydue.honeyDue"
|
|
|
|
func save(key: String, value: String) -> Bool {
|
|
guard let data = value.data(using: .utf8) else { return false }
|
|
|
|
// Delete existing item first (SecItemUpdate is fiddly; delete+add is reliable)
|
|
let deleteQuery: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key
|
|
]
|
|
SecItemDelete(deleteQuery as CFDictionary)
|
|
|
|
let addQuery: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key,
|
|
kSecValueData as String: data,
|
|
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
|
|
]
|
|
|
|
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
return status == errSecSuccess
|
|
}
|
|
|
|
func get(key: String) -> String? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
|
|
var item: CFTypeRef?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
|
|
guard status == errSecSuccess, let data = item as? Data else {
|
|
return nil
|
|
}
|
|
|
|
return String(data: data, encoding: .utf8)
|
|
}
|
|
|
|
func delete(key: String) -> Bool {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: key
|
|
]
|
|
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
return status == errSecSuccess || status == errSecItemNotFound
|
|
}
|
|
}
|