#if os(macOS) import CryptoKit import Foundation import WebKit /// Per-account persistent `WKWebsiteDataStore` for the OpenAI dashboard scrape. /// /// Why: `WKWebsiteDataStore.default()` is a single shared cookie jar. If the user switches Codex accounts, /// we want to keep multiple signed-in dashboard sessions around (one per email) without clearing cookies. /// /// Implementation detail: macOS 14+ supports `WKWebsiteDataStore.dataStore(forIdentifier:)`, which creates /// persistent isolated stores keyed by an identifier. We derive a stable UUID from the email so the same /// account always maps to the same cookie store. @MainActor public enum OpenAIDashboardWebsiteDataStore { public static func store(forAccountEmail email: String?) -> WKWebsiteDataStore { guard let normalized = normalizeEmail(email) else { return .default() } let id = Self.identifier(forNormalizedEmail: normalized) return WKWebsiteDataStore(forIdentifier: id) } /// Clears the persistent cookie store for a single account email. /// /// Note: this does *not* impact other accounts, and is safe to use when the stored session is "stuck" /// or signed in to a different account than expected. public static func clearStore(forAccountEmail email: String?) async { // Clear only ChatGPT/OpenAI domain data for the per-account store. // Avoid deleting the entire persistent store (WebKit requires all WKWebViews using it to be released). let store = self.store(forAccountEmail: email) await withCheckedContinuation { cont in store.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in let filtered = records.filter { record in let name = record.displayName.lowercased() return name.contains("chatgpt.com") || name.contains("openai.com") } store.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: filtered) { cont.resume() } } } } // MARK: - Private private static func normalizeEmail(_ email: String?) -> String? { guard let raw = email?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil } return raw.lowercased() } private static func identifier(forNormalizedEmail email: String) -> UUID { let digest = SHA256.hash(data: Data(email.utf8)) var bytes = Array(digest.prefix(16)) // Make it a well-formed UUID (v4 + RFC4122 variant) while staying deterministic. bytes[6] = (bytes[6] & 0x0F) | 0x40 bytes[8] = (bytes[8] & 0x3F) | 0x80 let uuidBytes: uuid_t = ( bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]) return UUID(uuid: uuidBytes) } } #endif