Files
streamyfin/modules/top-shelf-cache/ios/TopShelfCacheModule.swift
2026-05-21 08:47:45 +02:00

113 lines
3.0 KiB
Swift

import ExpoModulesCore
import Foundation
import Security
#if canImport(TVServices)
import TVServices
#endif
public class TopShelfCacheModule: Module {
private let appGroupInfoPlistKey = "StreamyfinAppGroupIdentifier"
private let keychainAccessGroupInfoPlistKey = "StreamyfinKeychainAccessGroupIdentifier"
private let cacheKey = "TopShelfCache"
private let apiKeyService = "StreamyfinTopShelf"
private let apiKeyAccount = "JellyfinApiKey"
private var appGroupIdentifier: String? {
if let appGroupIdentifier = Bundle.main.object(
forInfoDictionaryKey: appGroupInfoPlistKey
) as? String {
return appGroupIdentifier
}
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
return nil
}
return "group.\(bundleIdentifier)"
}
private var keychainAccessGroupIdentifier: String? {
Bundle.main.object(forInfoDictionaryKey: keychainAccessGroupInfoPlistKey) as? String
}
public func definition() -> ModuleDefinition {
Name("TopShelfCache")
Function("writeCache") { (json: String, apiKey: String?) -> Bool in
guard
let appGroupIdentifier = appGroupIdentifier,
let defaults = UserDefaults(suiteName: appGroupIdentifier)
else {
return false
}
defaults.set(json, forKey: cacheKey)
defaults.set(Date().timeIntervalSince1970, forKey: "\(cacheKey)UpdatedAt")
defaults.synchronize()
let didSaveAPIKey = saveAPIKey(apiKey)
#if canImport(TVServices)
TVTopShelfContentProvider.topShelfContentDidChange()
#endif
return didSaveAPIKey
}
Function("clearCache") { () -> Bool in
guard
let appGroupIdentifier = appGroupIdentifier,
let defaults = UserDefaults(suiteName: appGroupIdentifier)
else {
return false
}
defaults.removeObject(forKey: cacheKey)
defaults.removeObject(forKey: "\(cacheKey)UpdatedAt")
defaults.synchronize()
let didDeleteAPIKey = deleteAPIKey()
#if canImport(TVServices)
TVTopShelfContentProvider.topShelfContentDidChange()
#endif
return didDeleteAPIKey
}
}
private func baseAPIKeyQuery() -> [String: Any] {
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: apiKeyService,
kSecAttrAccount as String: apiKeyAccount
]
if let keychainAccessGroupIdentifier {
query[kSecAttrAccessGroup as String] = keychainAccessGroupIdentifier
}
return query
}
private func saveAPIKey(_ apiKey: String?) -> Bool {
guard deleteAPIKey() else {
return false
}
guard
let apiKey,
!apiKey.isEmpty,
let data = apiKey.data(using: .utf8)
else {
return true
}
var query = baseAPIKeyQuery()
query[kSecValueData as String] = data
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
private func deleteAPIKey() -> Bool {
let status = SecItemDelete(baseAPIKeyQuery() as CFDictionary)
return status == errSecSuccess || status == errSecItemNotFound
}
}