summaryrefslogtreecommitdiff
path: root/DomainDig/SubdomainDiscoveryService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-21 23:21:19 -0500
committerChristian Cleberg <[email protected]>2026-04-21 23:21:19 -0500
commit3846dc2f5dee69fc4ffbc052009a60e17d60e36b (patch)
tree867dca9d45b87f318c1b368cd535c9c9fb53e29d /DomainDig/SubdomainDiscoveryService.swift
parente73d58ee5dd43eaeaaa717f7781cc22256df30f0 (diff)
downloaddomain-dig-3846dc2f5dee69fc4ffbc052009a60e17d60e36b.tar.gz
domain-dig-3846dc2f5dee69fc4ffbc052009a60e17d60e36b.tar.bz2
domain-dig-3846dc2f5dee69fc4ffbc052009a60e17d60e36b.zip
feat(v2.5.0): add caching, request deduplication, and performance improvements
Diffstat (limited to 'DomainDig/SubdomainDiscoveryService.swift')
-rw-r--r--DomainDig/SubdomainDiscoveryService.swift45
1 files changed, 3 insertions, 42 deletions
diff --git a/DomainDig/SubdomainDiscoveryService.swift b/DomainDig/SubdomainDiscoveryService.swift
index 57db25d..4e25cf0 100644
--- a/DomainDig/SubdomainDiscoveryService.swift
+++ b/DomainDig/SubdomainDiscoveryService.swift
@@ -7,55 +7,16 @@ enum SubdomainDiscoveryService {
return .empty("No passive subdomains found")
}
- return await cache.subdomains(for: normalizedDomain, limit: limit)
+ return await fetchSubdomains(for: normalizedDomain, limit: limit)
}
- private static let cache = SubdomainDiscoveryCache()
-
private static func normalize(_ domain: String) -> String {
domain
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
}
-}
-
-private actor SubdomainDiscoveryCache {
- private var cachedResults: [String: ServiceResult<[DiscoveredSubdomain]>] = [:]
- private var inFlightTasks: [String: Task<ServiceResult<[DiscoveredSubdomain]>, Never>] = [:]
- private var lastRequestAt: Date?
-
- func subdomains(for domain: String, limit: Int) async -> ServiceResult<[DiscoveredSubdomain]> {
- if let cachedResult = cachedResults[domain] {
- return cachedResult
- }
-
- if let inFlightTask = inFlightTasks[domain] {
- return await inFlightTask.value
- }
-
- let task = Task<ServiceResult<[DiscoveredSubdomain]>, Never> {
- await enforceRateLimit()
- return await fetchSubdomains(for: domain, limit: limit)
- }
- inFlightTasks[domain] = task
-
- let result = await task.value
- cachedResults[domain] = result
- inFlightTasks[domain] = nil
- return result
- }
-
- private func enforceRateLimit() async {
- if let lastRequestAt {
- let delay = max(0, 0.75 - Date().timeIntervalSince(lastRequestAt))
- if delay > 0 {
- try? await Task.sleep(for: .seconds(delay))
- }
- }
- lastRequestAt = Date()
- }
- private func fetchSubdomains(for domain: String, limit: Int) async -> ServiceResult<[DiscoveredSubdomain]> {
+ private static func fetchSubdomains(for domain: String, limit: Int) async -> ServiceResult<[DiscoveredSubdomain]> {
var components = URLComponents(string: "https://crt.sh/")!
components.queryItems = [
URLQueryItem(name: "q", value: "%.\(domain)"),
@@ -81,7 +42,7 @@ private actor SubdomainDiscoveryCache {
}
}
- private func parseSubdomains(from entries: [CRTShEntry], domain: String, limit: Int) -> [DiscoveredSubdomain] {
+ private static func parseSubdomains(from entries: [CRTShEntry], domain: String, limit: Int) -> [DiscoveredSubdomain] {
var seen = Set<String>()
var results: [DiscoveredSubdomain] = []