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}