diff --git a/azooKeyMac/AppDelegate.swift b/azooKeyMac/AppDelegate.swift index 4b7133f2..8f6ed9e2 100644 --- a/azooKeyMac/AppDelegate.swift +++ b/azooKeyMac/AppDelegate.swift @@ -84,6 +84,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { self.server = IMKServer(name: Bundle.main.infoDictionary?["InputMethodConnectionName"] as? String, bundleIdentifier: Bundle.main.bundleIdentifier) NSLog("tried connection") + // Keychainから設定値を非同期で読み込み + Task { + await Config.OpenAiApiKey.loadFromKeychain() + } + // Check if mainMenu exists, or create it if NSApp.mainMenu == nil { NSApp.mainMenu = NSMenu() diff --git a/azooKeyMac/Configs/StringConfigItem.swift b/azooKeyMac/Configs/StringConfigItem.swift index 37ecd3f8..2df19373 100644 --- a/azooKeyMac/Configs/StringConfigItem.swift +++ b/azooKeyMac/Configs/StringConfigItem.swift @@ -24,15 +24,33 @@ extension Config { struct OpenAiApiKey: StringConfigItem { static var key: String = "dev.ensan.inputmethod.azooKeyMac.preference.OpenAiApiKey" + private static var cachedValue: String = "" + private static var isLoaded: Bool = false + // keychainで保存 var value: String { get { - KeychainHelper.read(key: Self.key) ?? "" + if !Self.isLoaded { + Task { + Self.cachedValue = await KeychainHelper.read(key: Self.key) ?? "" + Self.isLoaded = true + } + } + return Self.cachedValue } nonmutating set { - KeychainHelper.save(key: Self.key, value: newValue) + Self.cachedValue = newValue + Task { + await KeychainHelper.save(key: Self.key, value: newValue) + } } } + + // 初期化時にKeychainから値を読み込む + static func loadFromKeychain() async { + cachedValue = await KeychainHelper.read(key: key) ?? "" + isLoaded = true + } } } diff --git a/azooKeyMac/Extensions/KeychainHelper.swift b/azooKeyMac/Extensions/KeychainHelper.swift index dbac537b..4d5e1c77 100644 --- a/azooKeyMac/Extensions/KeychainHelper.swift +++ b/azooKeyMac/Extensions/KeychainHelper.swift @@ -2,49 +2,64 @@ import Foundation import Security enum KeychainHelper { - static func save(key: String, value: String) { - guard let data = value.data(using: .utf8) else { - print("StringをDataに変換する際にエラーが発生しました") - return - } + static func save(key: String, value: String) async { + await withCheckedContinuation { continuation in + Task.detached { + guard let data = value.data(using: .utf8) else { + print("StringをDataに変換する際にエラーが発生しました") + continuation.resume() + return + } - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecValueData as String: data - ] + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] - // 既存のデータがあれば削除 - SecItemDelete(query as CFDictionary) + // 既存のデータがあれば削除 + SecItemDelete(query as CFDictionary) - // 新しいデータを追加 - SecItemAdd(query as CFDictionary, nil) + // 新しいデータを追加 + SecItemAdd(query as CFDictionary, nil) + continuation.resume() + } + } } - static func read(key: String) -> String? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecReturnData as String: kCFBooleanTrue!, - kSecMatchLimit as String: kSecMatchLimitOne - ] + static func read(key: String) async -> String? { + await withCheckedContinuation { continuation in + Task.detached { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: kCFBooleanTrue!, + kSecMatchLimit as String: kSecMatchLimitOne + ] - var dataTypeRef: AnyObject? - let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) - if status == errSecSuccess, let data = dataTypeRef as? Data { - return String(bytes: data, encoding: .utf8) + if status == errSecSuccess, let data = dataTypeRef as? Data { + continuation.resume(returning: String(bytes: data, encoding: .utf8)) + } else { + continuation.resume(returning: nil) + } + } } - - return nil } - static func delete(key: String) { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key - ] + static func delete(key: String) async { + await withCheckedContinuation { continuation in + Task.detached { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key + ] - SecItemDelete(query as CFDictionary) + SecItemDelete(query as CFDictionary) + continuation.resume() + } + } } }