64 lines
2.6 KiB
Swift
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()
|
|
}
|
|
}
|