krz/domain-dig

an ios app for DNS & SSL analysis

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

v5.0.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            // No A record (or the lookup failed); fall through to the NS probe.
51        }
52
53        do {
54            let nsRecords = try await DNSLookupService.lookup(domain: domain, recordType: .NS)
55            if !nsRecords.isEmpty {
56                return .registered
57            }
58            return .unknown
59        } catch {
60            return .unknown
61        }
62    }
63
64    private static func suggestionCandidates(for domain: String, limit: Int) -> [String] {
65        let parts = domain.split(separator: ".")
66        guard parts.count >= 2 else { return [] }
67
68        let base = parts.dropLast().joined(separator: ".")
69        let tld = String(parts.last ?? "")
70
71        var candidates: [String] = []
72        for suggestionTLD in suggestionTLDs where suggestionTLD != tld {
73            candidates.append("\(base).\(suggestionTLD)")
74            if candidates.count == limit {
75                return candidates
76            }
77        }
78
79        if !base.contains("-"), base.count >= 6, candidates.count < limit {
80            let midpoint = base.index(base.startIndex, offsetBy: base.count / 2)
81            let hyphenated = "\(base[..<midpoint])-\(base[midpoint...]).\(tld)"
82            if hyphenated != domain {
83                candidates.append(hyphenated)
84            }
85        }
86
87        return Array(candidates.prefix(limit))
88    }
89
90    private static func normalize(_ domain: String) -> String {
91        domain
92            .trimmingCharacters(in: .whitespacesAndNewlines)
93            .lowercased()
94    }
95}