damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

DamusPurpleNotificationManagement.swift (3347B)


      1 //
      2 //  DamusPurpleNotificationManagement.swift
      3 //  damus
      4 //
      5 //  Created by Daniel D’Aquino on 2024-02-26.
      6 //
      7 
      8 import Foundation
      9 
     10 /// A definition of how many days in advance to notify the user of impending expiration. (e.g. 3 days before expiration AND 2 days before expiration AND 1 day before expiration)
     11 fileprivate let PURPLE_IMPENDING_EXPIRATION_NOTIFICATION_SCHEDULE: Set<Int> = [7, 3, 1]
     12 fileprivate let ONE_DAY: TimeInterval = 60 * 60 * 24
     13 
     14 extension DamusPurple {
     15     typealias NotificationHandlerFunction = (DamusAppNotification) async -> Void
     16     
     17     func check_and_send_app_notifications_if_needed(handler: NotificationHandlerFunction) async {
     18         await self.check_and_send_purple_expiration_notifications_if_needed(handler: handler)
     19     }
     20     
     21     /// Checks if we need to send a DamusPurple impending expiration notification to the user, and sends them if needed.
     22     ///
     23     /// **Note:** To keep things simple at this point, this function uses a "best effort" strategy, and silently fails if something is wrong, as it is not an essential component of the app — to avoid adding more error handling complexity to the app
     24     private func check_and_send_purple_expiration_notifications_if_needed(handler: NotificationHandlerFunction) async {
     25         if self.storekit_manager.recorded_purchased_products.count > 0 {
     26             // If user has a recurring IAP purchase, there no need to notify them of impending expiration
     27             return
     28         }
     29         guard let purple_expiration_date: Date = try? await self.get_maybe_cached_account(pubkey: self.keypair.pubkey)?.expiry else {
     30             return  // If there are no expiry dates (e.g. The user is not a Purple user) or we cannot get it for some reason (e.g. server is temporarily down and we have no cache), don't bother sending notifications
     31         }
     32         
     33         let days_to_expiry: Int = round_days_to_date(purple_expiration_date, from: Date.now)
     34         
     35         let applicable_impending_expiry_notification_schedule_items: [Int] = PURPLE_IMPENDING_EXPIRATION_NOTIFICATION_SCHEDULE.filter({ $0 >= days_to_expiry })
     36         
     37         for applicable_impending_expiry_notification_schedule_item in applicable_impending_expiry_notification_schedule_items {
     38             // Send notifications predicted by the schedule
     39             // Note: The `insert_app_notification` has built-in logic to prevent us from sending two identical notifications, so we need not worry about it here.
     40             await handler(.init(
     41                 content: .purple_impending_expiration(
     42                     days_remaining: applicable_impending_expiry_notification_schedule_item,
     43                     expiry_date: UInt64(purple_expiration_date.timeIntervalSince1970)
     44                 ),
     45                 timestamp: purple_expiration_date.addingTimeInterval(-Double(applicable_impending_expiry_notification_schedule_item) * ONE_DAY))
     46             )
     47         }
     48         
     49         if days_to_expiry < 0 {
     50             await handler(.init(
     51                 content: .purple_expired(expiry_date: UInt64(purple_expiration_date.timeIntervalSince1970)),
     52                 timestamp: purple_expiration_date)
     53             )
     54         }
     55     }
     56 }
     57 
     58 fileprivate func round_days_to_date(_ target_date: Date, from from_date: Date) -> Int {
     59     return Int(round(target_date.timeIntervalSince(from_date) / ONE_DAY))
     60 }