UNPKG

domain-info-fetcher

Version:

A powerful TypeScript/JavaScript tool for comprehensive domain analysis, featuring detailed WHOIS data with registration dates, registrars, and domain status. Offers SSL certificate extraction (with PEM support), DNS records, and server details. Includes

469 lines (468 loc) 18.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.dateToTimestamp = exports.checkDomain = exports.getRootDomain = exports.extractSubdomain = exports.formatDomain = void 0; exports.fetchDomainInfo = fetchDomainInfo; const https = __importStar(require("https")); const dns = __importStar(require("dns")); const utils_1 = require("./src/utils"); // Default request options const DEFAULT_OPTIONS = { timeout: 10000, // 10 seconds followRedirects: true, maxRedirects: 5, }; /** * Formats a given domain to `example.com` format. * @param domain The domain to format. * @returns The formatted domain. */ // formatDomain moved to ./src/utils and imported above /** * Extracts the subdomain from a given domain. * @param domain The domain to extract the subdomain from. * @returns The subdomain or null if no subdomain is present. */ // extractSubdomain moved to ./src/utils and imported above /** * Gets the root domain (e.g., example.com) from a domain that may include a subdomain. * @param domain The domain to extract the root domain from. * @returns The root domain. */ // getRootDomain moved to ./src/utils and imported above /** * Checks if the given domain is valid. * @param domain The domain to check. * @returns True if the domain is valid, false otherwise. */ // checkDomain moved to ./src/utils and imported above /** * converts a date string to a timestamp * @param dateString * @returns timestamp */ // dateToTimestamp moved to ./src/utils and imported above /** * Extracts SSL data from the given certificate. * @param cert The certificate object * @returns An object containing the SSL data. */ function extractSslData(cert) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const validToTimestamp = (0, utils_1.dateToTimestamp)(cert.valid_to); const validFromTimestamp = (0, utils_1.dateToTimestamp)(cert.valid_from); // Extract human-readable subject and issuer information const subjectCN = typeof ((_a = cert.subject) === null || _a === void 0 ? void 0 : _a.CN) === "string" ? cert.subject.CN : typeof ((_b = cert.subject) === null || _b === void 0 ? void 0 : _b.commonName) === "string" ? cert.subject.commonName : Array.isArray((_c = cert.subject) === null || _c === void 0 ? void 0 : _c.CN) ? cert.subject.CN.join(", ") : Object.values(cert.subject || {}) .map((v) => (Array.isArray(v) ? v.join(", ") : v)) .join(", "); // Prioritize Organization (O) for issuer information, falling back to CN if not available const issuerCN = typeof ((_d = cert.issuer) === null || _d === void 0 ? void 0 : _d.O) === "string" ? cert.issuer.O : typeof ((_e = cert.issuer) === null || _e === void 0 ? void 0 : _e.organizationName) === "string" ? cert.issuer.organizationName : typeof ((_f = cert.issuer) === null || _f === void 0 ? void 0 : _f.CN) === "string" ? cert.issuer.CN : typeof ((_g = cert.issuer) === null || _g === void 0 ? void 0 : _g.commonName) === "string" ? cert.issuer.commonName : Array.isArray((_h = cert.issuer) === null || _h === void 0 ? void 0 : _h.O) ? cert.issuer.O.join(", ") : Array.isArray((_j = cert.issuer) === null || _j === void 0 ? void 0 : _j.CN) ? cert.issuer.CN.join(", ") : Object.values(cert.issuer || {}) .map((v) => (Array.isArray(v) ? v.join(", ") : v)) .join(", "); // Extract certificates in PEM format if available let certificate = undefined; let intermediateCertificate = undefined; let rootCertificate = undefined; try { // The main certificate PEM if (cert.raw) { certificate = `-----BEGIN CERTIFICATE-----\n${(_k = cert.raw .toString("base64") .match(/.{1,64}/g)) === null || _k === void 0 ? void 0 : _k.join("\n")}\n-----END CERTIFICATE-----`; } // Try to extract intermediate and root certificates if available if (cert.issuerCertificate) { const intermediate = cert.issuerCertificate; if (intermediate.raw) { intermediateCertificate = `-----BEGIN CERTIFICATE-----\n${(_l = intermediate.raw .toString("base64") .match(/.{1,64}/g)) === null || _l === void 0 ? void 0 : _l.join("\n")}\n-----END CERTIFICATE-----`; } // Root certificate (if chain available) if (intermediate.issuerCertificate && intermediate.issuerCertificate.raw) { rootCertificate = `-----BEGIN CERTIFICATE-----\n${(_m = intermediate.issuerCertificate.raw .toString("base64") .match(/.{1,64}/g)) === null || _m === void 0 ? void 0 : _m.join("\n")}\n-----END CERTIFICATE-----`; } } } catch { // Silently handle certificate extraction errors // This ensures backward compatibility - if certificate extraction fails, // we'll still return the existing fields } return { // Original fields (for backward compatibility) subject: cert.subject, issuer: cert.issuer, valid: validToTimestamp > Date.now(), validFrom: validFromTimestamp, validTo: validToTimestamp, // New certificate fields certificate, intermediateCertificate, rootCertificate, // Human-readable details details: { subject: subjectCN, issuer: issuerCN, validFrom: new Date(validFromTimestamp), validTo: new Date(validToTimestamp), }, }; } /** * Fetches SSL, server, and DNS data for the given domain. * @param domain The domain to fetch the information for. * @param options Optional request configuration * @returns A Promise that resolves to an object containing the SSL, server, and DNS data. */ async function fetchDomainInfo(domain, options) { if (!domain) { throw new Error("Domain name cannot be empty"); } if (!(0, utils_1.checkDomain)(domain)) { throw new Error("Invalid domain name"); } const mergedOptions = { ...DEFAULT_OPTIONS, ...options }; const formattedDomain = (0, utils_1.formatDomain)(domain); // Include WHOIS data in the Promise.all array const [sslData, serverData, dnsData, httpStatus, whoisData,] = await Promise.all([ getSslData(formattedDomain, mergedOptions).catch((error) => { // Enhance error message with more specific details let errorMessage = "Could not fetch SSL data for domain " + domain; if (error.code) { errorMessage += ". Error code: " + error.code; } if (error.message) { errorMessage += ". Details: " + error.message; } throw new Error(errorMessage); }), getServerData(formattedDomain, mergedOptions).catch((error) => { // Enhance error message with more specific details let errorMessage = "Could not fetch server data for domain " + domain; if (error.code) { errorMessage += ". Error code: " + error.code; } if (error.message) { errorMessage += ". Details: " + error.message; } throw new Error(errorMessage); }), getDnsData(formattedDomain).catch((error) => { // Enhance error message with more specific details let errorMessage = "Could not fetch DNS data for domain " + domain; if (error.code) { errorMessage += ". Error code: " + error.code; } if (error.message) { errorMessage += ". Details: " + error.message; } throw new Error(errorMessage); }), getHttpStatus(formattedDomain, mergedOptions), // Add WHOIS data fetch, but make it optional Promise.resolve().then(() => __importStar(require("./src/whois"))).then((whoisModule) => whoisModule.getWhoisData(formattedDomain).catch((error) => { // Log the error but don't fail the whole request console.warn(`WHOIS data fetch failed: ${error.message}`); return undefined; })) .catch(() => undefined), // Make WHOIS data optional ]); if (!sslData) { throw new Error("Could not fetch SSL data for domain " + domain + ". The SSL certificate may be invalid or the domain may not support HTTPS."); } return { sslData, serverData, dnsData, httpStatus, whoisData }; } /** * Retrieves SSL data for the given domain. * @param domain The domain to fetch the SSL data for. * @param options Request configuration options * @returns A Promise that resolves to an object containing the SSL data. */ async function getSslData(domain, options = DEFAULT_OPTIONS) { return new Promise((resolve, reject) => { const req = https .request(`https://${domain}`, { method: "HEAD", timeout: options.timeout, headers: options.headers || {}, }, (res) => { const socket = res.socket; const cert = socket.getPeerCertificate(true); resolve(extractSslData(cert)); socket.destroy(); }) .on("error", (error) => { reject(error); }); req.end(); }); } /** * Retrieves server data for the given domain. * @param domain The domain to fetch the server data for. * @param options Request configuration options * @returns A Promise that resolves to a string containing the server data. */ async function getServerData(domain, options = DEFAULT_OPTIONS) { return new Promise((resolve, reject) => { const req = https .request(`https://${domain}`, { method: "HEAD", timeout: options.timeout, headers: options.headers || {}, agent: false, // Disable connection pooling }, (res) => { const serverHeaderValue = res.headers["server"]; const result = Array.isArray(serverHeaderValue) ? serverHeaderValue[0] : serverHeaderValue; // Ensure socket is destroyed if (res.socket) { res.socket.destroy(); } resolve(result); }) .on("error", (error) => { reject(error); }); req.end(); }); } /** * Retrieves DNS data for the given domain. * @param domain The domain to fetch the DNS data for. * @returns A Promise that resolves to an object containing the DNS data. */ async function getDnsData(domain) { const subdomain = (0, utils_1.extractSubdomain)(domain); const rootDomain = (0, utils_1.getRootDomain)(domain); // For a subdomain, we primarily want A and CNAME records of the subdomain itself if (subdomain) { try { const A = await getARecords(domain); const CNAME = await getCNameRecord(domain); // For other DNS records, we typically want the root domain information const TXT = await getTxtRecords(rootDomain); const MX = await getMxRecords(rootDomain); const NS = await getNsRecords(rootDomain); const SOA = await getSoaRecord(rootDomain); return { A, CNAME, TXT, MX, NS, SOA }; } catch (error) { // If looking up the subdomain fails, fall back to the root domain console.error(`Error fetching subdomain DNS data: ${error instanceof Error ? error.message : String(error)}`); console.log(`Falling back to root domain ${rootDomain}`); } } // Standard lookup for root domain or fallback const A = await getARecords(domain); const CNAME = await getCNameRecord(domain); const TXT = await getTxtRecords(domain); const MX = await getMxRecords(domain); const NS = await getNsRecords(domain); const SOA = await getSoaRecord(domain); return { A, CNAME, TXT, MX, NS, SOA }; } /** * Retrieves A records for the given domain. * @param domain The domain to fetch the A records for. * @returns A Promise that resolves to an array of strings containing the A records. */ function getARecords(domain) { return new Promise((resolve, reject) => { dns.resolve4(domain, (error, addresses) => { if (error) { reject(error); } else { resolve(addresses); } }); }); } /** * Retrieves CNAME record for the given domain. * @param domain The domain to fetch the CNAME record for. * @returns A Promise that resolves to a string containing the CNAME record. */ function getCNameRecord(domain) { return new Promise((resolve, reject) => { dns.resolveCname(domain, (error, addresses) => { if (error) { if (error.code === "ENODATA") { resolve(null); } else { reject(error); } } else { resolve(addresses[0]); } }); }); } /** * Retrieves TXT records for the given domain. * @param domain The domain to fetch the TXT records for. * @returns A Promise that resolves to an array of strings containing the TXT records. */ function getTxtRecords(domain) { return new Promise((resolve, reject) => { dns.resolveTxt(domain, (error, records) => { if (error) { reject(error); } else { const flattenedRecords = records.flat(); resolve(flattenedRecords); } }); }); } /** * Retrieves MX records for the given domain. * @param domain The domain to fetch the MX records for. * @returns A Promise that resolves to an array of objects containing the MX records. */ function getMxRecords(domain) { return new Promise((resolve, reject) => { dns.resolveMx(domain, (error, records) => { if (error) { reject(error); } else { resolve(records); } }); }); } /** * Retrieves NS records for the given domain. * @param domain The domain to fetch the NS records for. * @returns A Promise that resolves to an array of strings containing the NS records. */ function getNsRecords(domain) { return new Promise((resolve, reject) => { dns.resolveNs(domain, (error, records) => { if (error) { reject(error); } else { resolve(records); } }); }); } /** * Retrieves SOA record for the given domain. * @param domain The domain to fetch the SOA record for. * @returns A Promise that resolves to an object containing the SOA record. */ function getSoaRecord(domain) { return new Promise((resolve, reject) => { dns.resolveSoa(domain, (error, record) => { if (error) { if (error.code === "ENODATA") { resolve(null); } else { reject(error); } } else { resolve(record); } }); }); } /** * Retrieves HTTP status for the given domain. * @param domain The domain to fetch the HTTP status for. * @param options Request configuration options * @returns A Promise that resolves to a number containing the HTTP status. */ async function getHttpStatus(domain, options = DEFAULT_OPTIONS) { return new Promise((resolve, reject) => { const req = https .request(`https://${domain}`, { method: "HEAD", timeout: options.timeout, headers: options.headers || {}, agent: false, // Disable connection pooling }, (res) => { const statusCode = res.statusCode || 0; // Ensure socket is destroyed if (res.socket) { res.socket.destroy(); } resolve(statusCode); }) .on("error", (error) => { reject(error); }); req.end(); }); } var utils_2 = require("./src/utils"); Object.defineProperty(exports, "formatDomain", { enumerable: true, get: function () { return utils_2.formatDomain; } }); Object.defineProperty(exports, "extractSubdomain", { enumerable: true, get: function () { return utils_2.extractSubdomain; } }); Object.defineProperty(exports, "getRootDomain", { enumerable: true, get: function () { return utils_2.getRootDomain; } }); Object.defineProperty(exports, "checkDomain", { enumerable: true, get: function () { return utils_2.checkDomain; } }); Object.defineProperty(exports, "dateToTimestamp", { enumerable: true, get: function () { return utils_2.dateToTimestamp; } });