Files
honeyDueKMP/iosApp/iosApp/Shared/Utilities/KeychainHelper.swift
T
Trey t ef8eab4a07 iOS: complete bundle ID + team ID migration to com.myhoneydue.*
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.
2026-05-01 18:30:52 -07:00

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
}
}