krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

v4.5.0: DomainDig/FeatureAccessService.swift · raw

  1import Foundation
  2
  3enum FeatureTier: String, Codable, CaseIterable, Identifiable {
  4    case free
  5    case pro
  6    case proPlus = "dataPlus"
  7
  8    var id: String { rawValue }
  9
 10    var title: String {
 11        switch self {
 12        case .free:
 13            return "Free"
 14        case .pro:
 15            return "Pro"
 16        case .proPlus:
 17            return "Pro+"
 18        }
 19    }
 20}
 21
 22enum FeatureCapability: String, CaseIterable, Identifiable {
 23    case singleLookup
 24    case basicHistory
 25    case limitedTracking
 26    case workflows
 27    case batchOperations
 28    case automatedMonitoring
 29    case localAlerts
 30    case advancedExports
 31    case ownershipHistory
 32    case dnsHistory
 33    case extendedSubdomains
 34    case domainPricing
 35
 36    var id: String { rawValue }
 37
 38    var title: String {
 39        switch self {
 40        case .singleLookup:
 41            return "Single lookup"
 42        case .basicHistory:
 43            return "Basic history"
 44        case .limitedTracking:
 45            return "Limited tracking"
 46        case .workflows:
 47            return "Workflows"
 48        case .batchOperations:
 49            return "Batch operations"
 50        case .automatedMonitoring:
 51            return "Background monitoring"
 52        case .localAlerts:
 53            return "Local alerts"
 54        case .advancedExports:
 55            return "Advanced exports"
 56        case .ownershipHistory:
 57            return "Ownership history"
 58        case .dnsHistory:
 59            return "DNS history"
 60        case .extendedSubdomains:
 61            return "Extended subdomains"
 62        case .domainPricing:
 63            return "Domain pricing"
 64        }
 65    }
 66}
 67
 68struct FeatureEntitlements: Equatable {
 69    let tier: FeatureTier
 70    let capabilities: Set<FeatureCapability>
 71    let trackedDomainLimit: Int
 72    let workflowLimit: Int?
 73    let batchSizeLimit: Int?
 74}
 75
 76struct UpgradePromptContext: Identifiable, Equatable {
 77    let id = UUID()
 78    let title: String
 79    let message: String
 80    let capability: FeatureCapability
 81}
 82
 83enum FeatureAccessService {
 84    private static let effectivelyUnlimitedTrackedDomains = 5_000
 85
 86    static var currentTier: FeatureTier {
 87        PurchaseService.cachedTier
 88    }
 89
 90    static var entitlements: FeatureEntitlements {
 91        switch currentTier {
 92        case .free:
 93            return FeatureEntitlements(
 94                tier: .free,
 95                capabilities: [.singleLookup, .basicHistory, .limitedTracking, .workflows, .batchOperations],
 96                trackedDomainLimit: 5,
 97                workflowLimit: 1,
 98                batchSizeLimit: 10
 99            )
100        case .pro:
101            return FeatureEntitlements(
102                tier: .pro,
103                capabilities: [
104                    .singleLookup,
105                    .basicHistory,
106                    .limitedTracking,
107                    .workflows,
108                    .batchOperations,
109                    .automatedMonitoring,
110                    .localAlerts,
111                    .advancedExports
112                ],
113                trackedDomainLimit: effectivelyUnlimitedTrackedDomains,
114                workflowLimit: nil,
115                batchSizeLimit: nil
116            )
117        case .proPlus:
118            return FeatureEntitlements(
119                tier: .proPlus,
120                capabilities: Set(FeatureCapability.allCases),
121                trackedDomainLimit: effectivelyUnlimitedTrackedDomains,
122                workflowLimit: nil,
123                batchSizeLimit: nil
124            )
125        }
126    }
127
128    static func hasAccess(to capability: FeatureCapability) -> Bool {
129        entitlements.capabilities.contains(capability)
130    }
131
132    static func canAddTrackedDomain(currentCount: Int) -> Bool {
133        currentCount < entitlements.trackedDomainLimit
134    }
135
136    static func trackedDomainLimitMessage(currentCount: Int) -> String? {
137        guard currentTier == .free else { return nil }
138        if currentCount >= entitlements.trackedDomainLimit {
139            return "Free includes up to \(entitlements.trackedDomainLimit) tracked domains. Available in Pro."
140        }
141        return "Free includes up to \(entitlements.trackedDomainLimit) tracked domains."
142    }
143
144    static func canCreateWorkflow(currentCount: Int) -> Bool {
145        guard hasAccess(to: .workflows) else { return false }
146        guard let limit = entitlements.workflowLimit else { return true }
147        return currentCount < limit
148    }
149
150    static func canRunBatch(domainCount: Int) -> Bool {
151        guard hasAccess(to: .batchOperations) else { return false }
152        guard let limit = entitlements.batchSizeLimit else { return true }
153        return domainCount <= limit
154    }
155
156    static func upgradeMessage(for capability: FeatureCapability) -> String {
157        switch capability {
158        case .workflows, .batchOperations, .automatedMonitoring, .localAlerts, .advancedExports:
159            return "Available in Pro"
160        case .ownershipHistory, .dnsHistory, .extendedSubdomains, .domainPricing:
161            return "Available in Pro+"
162        case .limitedTracking:
163            return "Tracking is limited on Free"
164        case .singleLookup, .basicHistory:
165            return "Included in Free"
166        }
167    }
168
169    static func workflowLimitMessage(currentCount: Int) -> String? {
170        guard let limit = entitlements.workflowLimit, currentCount >= limit else { return nil }
171        return "Free includes up to \(limit) workflow."
172    }
173
174    static func batchLimitMessage(domainCount: Int) -> String? {
175        guard let limit = entitlements.batchSizeLimit, domainCount > limit else { return nil }
176        return "Free runs batches up to \(limit) domains."
177    }
178
179    static func workflowAllowanceSummary(currentCount: Int) -> String? {
180        guard currentTier == .free, let limit = entitlements.workflowLimit else { return nil }
181        if currentCount >= limit {
182            return "Free includes up to \(limit) workflow. Available in Pro."
183        }
184        return "Free includes up to \(limit) workflow."
185    }
186
187    static func batchAllowanceSummary() -> String? {
188        guard currentTier == .free, let limit = entitlements.batchSizeLimit else { return nil }
189        return "Free runs batches up to \(limit) domains."
190    }
191
192    static func upgradePromptForTrackedDomains(currentCount: Int) -> UpgradePromptContext? {
193        guard currentCount >= entitlements.trackedDomainLimit else { return nil }
194        return UpgradePromptContext(
195            title: "Available in Pro",
196            message: "Free includes up to \(entitlements.trackedDomainLimit) tracked domains. You can keep your current watchlist, but adding more requires Pro.",
197            capability: .limitedTracking
198        )
199    }
200
201    static func upgradePromptForWorkflows(currentCount: Int) -> UpgradePromptContext? {
202        guard let limit = entitlements.workflowLimit, currentCount >= limit else { return nil }
203        return UpgradePromptContext(
204            title: "Available in Pro",
205            message: "Free includes up to \(limit) workflow. You can keep your current workflows, but creating more requires Pro.",
206            capability: .workflows
207        )
208    }
209
210    static func upgradePromptForBatch(domainCount: Int) -> UpgradePromptContext? {
211        guard let limit = entitlements.batchSizeLimit, domainCount > limit else { return nil }
212        return UpgradePromptContext(
213            title: "Available in Pro",
214            message: "Free runs batches up to \(limit) domains at a time. You can continue with smaller batches or upgrade to Pro.",
215            capability: .batchOperations
216        )
217    }
218
219    static func upgradePrompt(for capability: FeatureCapability) -> UpgradePromptContext {
220        let title: String
221        switch capability {
222        case .ownershipHistory, .dnsHistory, .extendedSubdomains, .domainPricing:
223            title = "Available in Pro+"
224        default:
225            title = "Available in Pro"
226        }
227        return UpgradePromptContext(
228            title: title,
229            message: upgradeMessage(for: capability),
230            capability: capability
231        )
232    }
233
234    static func enabledFeatureLabels() -> [String] {
235        FeatureCapability.allCases
236            .filter { hasAccess(to: $0) }
237            .map(\.title)
238    }
239}