krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v4.8.1: DomainDig/DomainAvailabilityService.swift · raw
1import Foundation
2
3struct DomainAvailabilityService {
4 private static let suggestionTLDs = ["net", "io", "dev", "app", "co", "org"]
5
6 static func check(domain: String) async -> DomainAvailabilityResult {
7 let normalizedDomain = normalize(domain)
8 guard !normalizedDomain.isEmpty else {
9 return DomainAvailabilityResult(domain: domain, status: .unknown)
10 }
11
12 if await checkViaRDAP(domain: normalizedDomain) == .registered {
13 return DomainAvailabilityResult(domain: normalizedDomain, status: .registered)
14 }
15
16 let fallbackStatus = await checkViaDNSFallback(domain: normalizedDomain)
17 if fallbackStatus == .registered {
18 return DomainAvailabilityResult(domain: normalizedDomain, status: .unknown)
19 }
20
21 return DomainAvailabilityResult(domain: normalizedDomain, status: fallbackStatus)
22 }
23
24 static func suggestions(for domain: String, limit: Int = 6) async -> [DomainSuggestionResult] {
25 let normalizedDomain = normalize(domain)
26 let candidates = suggestionCandidates(for: normalizedDomain, limit: limit)
27 guard !candidates.isEmpty else { return [] }
28
29 var results: [DomainSuggestionResult] = []
30 for candidate in candidates {
31 if Task.isCancelled { break }
32 let result = await check(domain: candidate)
33 results.append(DomainSuggestionResult(domain: result.domain, status: result.status))
34 }
35 return results
36 }
37
38 private static func checkViaRDAP(domain: String) async -> DomainAvailabilityStatus? {
39 let status = await RDAPService.registrationStatus(for: domain)
40 return status
41 }
42
43 private static func checkViaDNSFallback(domain: String) async -> DomainAvailabilityStatus {
44 do {
45 let aRecords = try await DNSLookupService.lookup(domain: domain, recordType: .A)
46 if !aRecords.isEmpty {
47 return .registered
48 }
49 } catch {
50 }
51
52 do {
53 let nsRecords = try await DNSLookupService.lookup(domain: domain, recordType: .NS)
54 if !nsRecords.isEmpty {
55 return .registered
56 }
57 return .unknown
58 } catch {
59 return .unknown
60 }
61 }
62
63 private static func suggestionCandidates(for domain: String, limit: Int) -> [String] {
64 let parts = domain.split(separator: ".")
65 guard parts.count >= 2 else { return [] }
66
67 let base = parts.dropLast().joined(separator: ".")
68 let tld = String(parts.last ?? "")
69
70 var candidates: [String] = []
71 for suggestionTLD in suggestionTLDs where suggestionTLD != tld {
72 candidates.append("\(base).\(suggestionTLD)")
73 if candidates.count == limit {
74 return candidates
75 }
76 }
77
78 if !base.contains("-"), base.count >= 6, candidates.count < limit {
79 let midpoint = base.index(base.startIndex, offsetBy: base.count / 2)
80 let hyphenated = "\(base[..<midpoint])-\(base[midpoint...]).\(tld)"
81 if hyphenated != domain {
82 candidates.append(hyphenated)
83 }
84 }
85
86 return Array(candidates.prefix(limit))
87 }
88
89 private static func normalize(_ domain: String) -> String {
90 domain
91 .trimmingCharacters(in: .whitespacesAndNewlines)
92 .lowercased()
93 }
94}