Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions azooKeyMac/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Comment on lines +87 to +90
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、初回の利用時ってどういうことになる?キーチェーンに値が入っていない場合はアラート出ない?


// Check if mainMenu exists, or create it
if NSApp.mainMenu == nil {
NSApp.mainMenu = NSMenu()
Expand Down
22 changes: 20 additions & 2 deletions azooKeyMac/Configs/StringConfigItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
81 changes: 48 additions & 33 deletions azooKeyMac/Extensions/KeychainHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
}