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