Files
steipete--codexbar/Sources/CodexBar/CodexResetCreditExpiryNotifier.swift
2026-07-13 12:22:33 +08:00

64 lines
2.6 KiB
Swift

import CodexBarCore
import CryptoKit
import Foundation
@MainActor
struct CodexResetCreditExpiryNotifier {
static let expiryWindow: TimeInterval = 3 * 24 * 60 * 60
static let notificationPrefix = "codex-reset-credit-expiry"
static let summaryFingerprintsKey = "codexResetCreditExpirySummaryFingerprints"
static let maximumRememberedSummaries = 64
var userDefaults: UserDefaults = .standard
var notificationPoster: (String, String, String) -> Void = { prefix, title, body in
AppNotifications.shared.post(idPrefix: prefix, title: title, body: body)
}
func postExpiringCreditsIfNeeded(
snapshot: CodexRateLimitResetCreditsSnapshot,
resetStyle: ResetTimeDisplayStyle,
now: Date = Date())
{
let expiringCredits = snapshot.availableInventory(at: now).credits.filter { credit in
guard let expiresAt = credit.expiresAt else { return false }
return expiresAt.timeIntervalSince(now) <= Self.expiryWindow
}
guard !expiringCredits.isEmpty else { return }
let fingerprint = Self.summaryFingerprint(expiringCredits)
// Account-scoped refreshes can alternate inventories, so remember more than the latest summary.
var notifiedFingerprints = self.userDefaults.stringArray(forKey: Self.summaryFingerprintsKey) ?? []
guard !notifiedFingerprints.contains(fingerprint) else { return }
notifiedFingerprints.append(fingerprint)
if notifiedFingerprints.count > Self.maximumRememberedSummaries {
notifiedFingerprints.removeFirst(notifiedFingerprints.count - Self.maximumRememberedSummaries)
}
self.userDefaults.set(notifiedFingerprints, forKey: Self.summaryFingerprintsKey)
let expiringSnapshot = CodexRateLimitResetCreditsSnapshot(
credits: expiringCredits,
availableCount: expiringCredits.count,
updatedAt: now)
guard let presentation = CodexResetCreditsPresentation.make(
snapshot: expiringSnapshot,
resetStyle: resetStyle,
now: now)
else {
return
}
self.notificationPoster(
Self.notificationPrefix,
L("Limit Reset Credits"),
presentation.helpText)
}
private static func summaryFingerprint(_ credits: [CodexRateLimitResetCredit]) -> String {
let material = credits.map { credit in
"\(credit.id)\u{1f}\(credit.expiresAt?.timeIntervalSince1970 ?? 0)"
}.joined(separator: "\u{1e}")
return SHA256.hash(data: Data(material.utf8))
.map { String(format: "%02x", $0) }
.joined()
}
}