domainlooker
Version:
A fast CLI and MCP server for inspecting domains: WHOIS/RDAP, DNS, SSL, ports, subdomains, with CSV/JSON export.
124 lines • 4.83 kB
JavaScript
import { WhoisService } from '../services/whois.js';
import { DNSService } from '../services/dns.js';
import { SSLService } from '../services/ssl.js';
import { NetworkService } from '../services/network.js';
import { SubdomainService } from '../services/subdomain.js';
import { PropagationService } from '../services/propagation.js';
export function hasWhoisData(whois) {
// A parser can leave a key set to `undefined` (a matched label with no value),
// so require at least one field with an actual value — not merely a key.
return !!whois && Object.values(whois).some(v => v != null && (!Array.isArray(v) || v.length > 0));
}
export function hasDnsData(dns) {
return !!dns && Object.values(dns).some(v => v != null && (!Array.isArray(v) || v.length > 0));
}
/** Lightweight, factual checks surfaced at the end of a report and in exports. */
export function collectAdvisories(info) {
const advisories = [];
if (!info.ssl) {
advisories.push('No SSL certificate detected.');
}
else if (info.ssl.daysUntilExpiry !== undefined && info.ssl.daysUntilExpiry < 30) {
advisories.push(`SSL certificate expires in ${info.ssl.daysUntilExpiry} days.`);
}
if (info.whois?.registrationDate) {
const days = (Date.now() - new Date(info.whois.registrationDate).getTime()) / 86400000;
if (days >= 0 && days < 30) {
advisories.push(`Domain was registered ${Math.round(days)} days ago.`);
}
}
return advisories;
}
/**
* Gathers domain intelligence with no side effects (no console output, no
* spinners). Every method resolves to its data or `null` and never rejects, so
* callers can run them concurrently and compose the results freely. This is the
* shared engine behind both the CLI and the MCP server.
*/
export class DomainCollector {
constructor() {
this.whoisService = new WhoisService();
this.dnsService = new DNSService();
this.sslService = new SSLService();
this.networkService = new NetworkService();
this.subdomainService = new SubdomainService();
this.propagationService = new PropagationService();
}
/** Run every enabled lookup in parallel; total time is bounded by the slowest one. */
async collect(domain, options = {}) {
const info = { domain };
const tasks = [
this.whois(domain, options).then(r => { info.whois = r ?? undefined; }),
this.dns(domain, options).then(r => { info.dns = r ?? undefined; }),
this.ssl(domain, options).then(r => { info.ssl = r ?? undefined; }),
];
if (!options.quick) {
tasks.push(this.ports(domain, options).then(r => { info.network = r ?? undefined; }));
}
if (options.subdomains) {
tasks.push(this.subdomains(domain, options).then(r => { info.subdomains = r ?? undefined; }));
}
await Promise.all(tasks);
return info;
}
async whois(domain, options = {}) {
try {
const result = await this.whoisService.lookup(domain, {
timeout: options.whoisTimeoutMs,
rdapTimeout: options.rdapTimeoutMs,
});
// A lookup can succeed while yielding nothing usable; report that as "no data".
return hasWhoisData(result) ? result : null;
}
catch (error) {
options.onError?.('whois', error);
return null;
}
}
async dns(domain, options = {}) {
try {
return await this.dnsService.lookup(domain);
}
catch (error) {
options.onError?.('dns', error);
return null;
}
}
async ssl(domain, options = {}) {
try {
return await this.sslService.getCertificate(domain, 443, options.sslTimeoutMs);
}
catch (error) {
options.onError?.('ssl', error);
return null;
}
}
async ports(domain, options = {}) {
try {
return await this.networkService.getNetworkInfo(domain, { timeoutMs: options.portTimeoutMs });
}
catch (error) {
options.onError?.('ports', error);
return null;
}
}
async subdomains(domain, options = {}) {
try {
return await this.subdomainService.discoverSubdomains(domain);
}
catch (error) {
options.onError?.('subdomains', error);
return null;
}
}
async propagation(domain, recordType = 'A', options = {}) {
try {
return await this.propagationService.check(domain, recordType);
}
catch (error) {
options.onError?.('propagation', error);
return null;
}
}
}
//# sourceMappingURL=collector.js.map