commit 513adfa1c71a2344e7d09a278fc0449fb2cf91d9
parent a912de458b584f23297fa919b410c41a7e34505b
Author: Michael Camilleri <[email protected]>
Date: Thu, 4 Jun 2026 07:49:00 +0900
Add logging for cookie errors
Diffstat:
3 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/Crossmate/Services/AppServices.swift b/Crossmate/Services/AppServices.swift
@@ -179,8 +179,10 @@ final class AppServices {
let syncEngine = SyncEngine(container: self.ckContainer, persistence: persistence)
self.syncEngine = syncEngine
self.syncMonitor = SyncMonitor(log: eventLog)
- self.nytAuth = NYTAuthService()
self.driveMonitor = DriveMonitor()
+ self.nytAuth = NYTAuthService(log: { message in
+ eventLog.note(message)
+ })
self.nytFetcher = NYTPuzzleFetcher { NYTAuthService.currentCookie() }
self.inputMonitor = InputMonitor()
let identity = AuthorIdentity()
diff --git a/Crossmate/Services/KeychainHelper.swift b/Crossmate/Services/KeychainHelper.swift
@@ -24,6 +24,10 @@ enum KeychainHelper {
}
static func load(key: String) -> Data? {
+ loadWithStatus(key: key).data
+ }
+
+ static func loadWithStatus(key: String) -> (data: Data?, status: OSStatus) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
@@ -34,8 +38,8 @@ enum KeychainHelper {
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
- guard status == errSecSuccess else { return nil }
- return result as? Data
+ guard status == errSecSuccess else { return (nil, status) }
+ return (result as? Data, status)
}
static func delete(key: String) {
diff --git a/Crossmate/Services/NYTAuthService.swift b/Crossmate/Services/NYTAuthService.swift
@@ -1,4 +1,5 @@
import Foundation
+import Security
import UIKit
import WebKit
@@ -8,10 +9,15 @@ final class NYTAuthService {
private(set) var signedInEmail: String?
private(set) var isLoading = false
var errorMessage: String?
+ private let log: ((String) -> Void)?
nonisolated private static let emailKey = "nyt-email"
nonisolated private static let cookieKey = "nyt-cookie"
+ init(log: ((String) -> Void)? = nil) {
+ self.log = log
+ }
+
/// Thread-safe read of the stored NYT cookie for use from non-MainActor contexts.
nonisolated static func currentCookie() -> String? {
guard let data = KeychainHelper.load(key: cookieKey) else { return nil }
@@ -21,14 +27,17 @@ final class NYTAuthService {
static let loginURL = URL(string: "https://myaccount.nytimes.com/auth/login?response_type=cookie&client_id=games&redirect_uri=https%3A%2F%2Fwww.nytimes.com%2Fcrosswords")!
func loadStoredSession() {
- guard KeychainHelper.load(key: Self.cookieKey) != nil else {
+ let cookieResult = KeychainHelper.loadWithStatus(key: Self.cookieKey)
+ logCookieKeychainLoad(result: cookieResult)
+ guard cookieResult.data != nil else {
isSignedIn = false
signedInEmail = nil
return
}
isSignedIn = true
- if let emailData = KeychainHelper.load(key: Self.emailKey),
+ let emailResult = KeychainHelper.loadWithStatus(key: Self.emailKey)
+ if let emailData = emailResult.data,
let email = String(data: emailData, encoding: .utf8),
!email.isEmpty {
signedInEmail = email
@@ -95,6 +104,29 @@ final class NYTAuthService {
// MARK: - Private
+ private func logCookieKeychainLoad(
+ result: (data: Data?, status: OSStatus)
+ ) {
+ log?(
+ "NYT session cookie keychain load: status=\(Self.keychainStatusDescription(result.status)) " +
+ "hasData=\(result.data != nil)"
+ )
+ }
+
+ nonisolated private static func keychainStatusDescription(_ status: OSStatus) -> String {
+ if status == errSecSuccess {
+ return "success(0)"
+ }
+ if status == errSecItemNotFound {
+ return "itemNotFound(\(status))"
+ }
+ let message = SecCopyErrorMessageString(status, nil) as String?
+ if let message, !message.isEmpty {
+ return "\(message)(\(status))"
+ }
+ return "OSStatus(\(status))"
+ }
+
private func fetchUserEmail(cookies: [HTTPCookie]) async throws -> String? {
let cookieHeader = Self.cookieHeader(for: cookies)
guard let configuration = try await fetchAccountGraphQLConfiguration(