ReadKeep/readeck/Data/KeychainHelper.swift
Ilyas Hallak 1763dd6fa1 feat: Complete Share Extension implementation with Keychain integration
UI/UX Improvements:
- Replace SLComposeServiceViewController with custom UIViewController
- Add beautiful green-themed UI with Readeck branding and logo
- Implement modern card-based layout with shadows and rounded corners
- Add custom cancel button and proper navigation styling
- Include loading states and comprehensive user feedback

Backend Integration:
- Add KeychainHelper integration for secure token/endpoint storage
- Implement proper API integration with async/await
- Add comprehensive error handling and status messages
- Include DTOs for API communication

Security & Configuration:
- Add keychain access groups to entitlements for both main app and extension
- Update TokenProvider to save tokens to keychain
- Modify LogoutUseCase to clear keychain data
- Update SaveServerSettingsUseCase to persist endpoint in keychain
- Configure proper build settings and file sharing between targets

Extension Lifecycle:
- Implement proper URL extraction from various sources
- Add automatic extension dismissal on success
- Ensure proper extension context handling
2025-07-04 00:00:35 +02:00

59 lines
1.9 KiB
Swift

import Foundation
import Security
class KeychainHelper {
static let shared = KeychainHelper()
private init() {}
private static let accessGroup = "8J69P655GN.de.ilyashallak.readeck2"
@discardableResult
func saveToken(_ token: String) -> Bool {
saveString(token, forKey: "readeck_token")
}
func loadToken() -> String? {
loadString(forKey: "readeck_token")
}
@discardableResult
func saveEndpoint(_ endpoint: String) -> Bool {
saveString(endpoint, forKey: "readeck_endpoint")
}
func loadEndpoint() -> String? {
loadString(forKey: "readeck_endpoint")
}
// MARK: - Private generic helpers
@discardableResult
private func saveString(_ value: String, forKey key: String) -> Bool {
guard let data = value.data(using: .utf8) else { return false }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessGroup as String: KeychainHelper.accessGroup
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
private func loadString(forKey key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecAttrAccessGroup as String: KeychainHelper.accessGroup,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess, let data = result as? Data {
return String(data: data, encoding: .utf8)
}
return nil
}
}