Christopher Hotchkiss Christopher Hotchkiss
Login

Code

remindwall / RWPureSwift / Sources / TransitAPI / TransitKeyStore.swift

Back to remindwall — Current RemindWall

Download this file

import ConcurrencyExtras
import Dependencies
import DependenciesMacros
import Foundation
import Security

@DependencyClient
public struct TransitKeyStore: Sendable {
    public var read: @Sendable () -> String?
    public var write: @Sendable (String?) -> Void
}

extension TransitKeyStore: DependencyKey {
    public static let liveValue: Self = {
        let service = "io.hotchkiss.RemindWall.transit"
        let account = "oba_api_key"

        return Self(
            read: {
                var query: [String: Any] = [
                    kSecClass as String: kSecClassGenericPassword,
                    kSecAttrService as String: service,
                    kSecAttrAccount as String: account,
                    kSecAttrSynchronizable as String: kCFBooleanTrue!,
                    kSecReturnData as String: kCFBooleanTrue!,
                    kSecMatchLimit as String: kSecMatchLimitOne,
                ]
                var item: CFTypeRef?
                let status = SecItemCopyMatching(query as CFDictionary, &item)
                guard status == errSecSuccess,
                      let data = item as? Data,
                      let value = String(data: data, encoding: .utf8) else {
                    return nil
                }
                _ = query
                return value
            },
            write: { newValue in
                let baseQuery: [String: Any] = [
                    kSecClass as String: kSecClassGenericPassword,
                    kSecAttrService as String: service,
                    kSecAttrAccount as String: account,
                    kSecAttrSynchronizable as String: kCFBooleanTrue!,
                ]
                _ = SecItemDelete(baseQuery as CFDictionary)

                guard let value = newValue, !value.isEmpty,
                      let data = value.data(using: .utf8) else {
                    return
                }
                var addQuery = baseQuery
                addQuery[kSecValueData as String] = data
                _ = SecItemAdd(addQuery as CFDictionary, nil)
            }
        )
    }()

    public static var testValue: Self {
        let storage = LockIsolated<String?>(nil)
        return Self(
            read: { storage.value },
            write: { storage.setValue($0) }
        )
    }

    public static let previewValue = Self(
        read: { nil },
        write: { _ in }
    )
}

extension DependencyValues {
    public var transitKeyStore: TransitKeyStore {
        get { self[TransitKeyStore.self] }
        set { self[TransitKeyStore.self] = newValue }
    }
}