Features implemented: - Modular JavaScript architecture (theme.js, dns-tools.js, whois.js, punycode.js, ip-tools.js, main.js) - Responsive design with dark/light theme toggle - DNS Lookup and Reverse DNS Lookup tools - Whois Lookup functionality - IDN Punycode Converter with full Unicode support - Comprehensive IP Address Tools (validation, IPv4-to-IPv6 mapping, IPv6 compression/expansion) - Dynamic tab descriptions that change based on active tool - Mobile-responsive horizontal scrollable tabs - Copy-to-clipboard functionality for all results - Clean footer with dynamic year - IPv4-mapped IPv6 address explanation with clear warnings Technical improvements: - Separated concerns with modular JS files - Fixed browser compatibility issues with punycode library - Implemented proper error handling and user feedback - Added comprehensive input validation - Optimized for mobile devices with touch-friendly UI
263 lines
11 KiB
JavaScript
263 lines
11 KiB
JavaScript
// DNS Things - DNS Tools Module
|
|
|
|
// Initialize IP display functionality
|
|
async function initIPDisplay() {
|
|
const ipv4Element = document.getElementById('ipv4');
|
|
const ipv6Element = document.getElementById('ipv6');
|
|
|
|
if (!ipv4Element || !ipv6Element) return;
|
|
|
|
try {
|
|
// Fetch IPv4
|
|
const ipv4Response = await fetch('https://api.ipify.org?format=json');
|
|
const ipv4Data = await ipv4Response.json();
|
|
ipv4Element.textContent = ipv4Data.ip;
|
|
} catch (error) {
|
|
ipv4Element.textContent = 'Unable to fetch';
|
|
console.error('Error fetching IPv4:', error);
|
|
}
|
|
|
|
try {
|
|
// Fetch IPv6
|
|
const ipv6Response = await fetch('https://api64.ipify.org?format=json');
|
|
const ipv6Data = await ipv6Response.json();
|
|
ipv6Element.textContent = ipv6Data.ip;
|
|
} catch (error) {
|
|
ipv6Element.textContent = 'Not available';
|
|
console.error('Error fetching IPv6:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize DNS lookup form
|
|
function initDNSForm() {
|
|
const form = document.getElementById('dns-form');
|
|
const input = document.getElementById('domain-input');
|
|
const results = document.getElementById('dns-results-container');
|
|
const loader = document.getElementById('dns-loader');
|
|
|
|
if (!form || !input || !results || !loader) return;
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const domain = input.value.trim();
|
|
if (!domain) return;
|
|
|
|
// Show loader
|
|
loader.classList.remove('hidden');
|
|
results.innerHTML = '';
|
|
|
|
try {
|
|
// DNS record types to query
|
|
const recordTypes = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CNAME', 'SOA', 'PTR', 'SRV'];
|
|
|
|
// Create promises for all DNS queries
|
|
const queries = recordTypes.map(type =>
|
|
fetch(`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=${type}`)
|
|
.then(response => response.json())
|
|
.then(data => ({ type, data }))
|
|
.catch(error => ({ type, error }))
|
|
);
|
|
|
|
// Wait for all queries to complete
|
|
const responses = await Promise.all(queries);
|
|
|
|
// Group results by record type
|
|
const groupedResults = {};
|
|
|
|
responses.forEach(({ type, data, error }) => {
|
|
if (error) {
|
|
console.error(`Error fetching ${type} records:`, error);
|
|
return;
|
|
}
|
|
|
|
if (data.Answer && data.Answer.length > 0) {
|
|
groupedResults[type] = data.Answer.map(record => {
|
|
// Clean the record data
|
|
let cleanData = record.data;
|
|
if (typeof cleanData === 'string') {
|
|
cleanData = cleanData.replace(/^"(.*)"$/, '$1').replace(/\.$/, '');
|
|
}
|
|
return {
|
|
...record,
|
|
data: cleanData
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
// Display results
|
|
if (Object.keys(groupedResults).length === 0) {
|
|
results.innerHTML = `
|
|
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300 rounded-lg">
|
|
<h3 class="font-medium mb-1">No DNS records found</h3>
|
|
<p class="text-sm">The domain "${domain}" does not have any DNS records or does not exist.</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
let resultsHTML = '<div class="space-y-4">';
|
|
|
|
Object.entries(groupedResults).forEach(([type, records]) => {
|
|
resultsHTML += `
|
|
<div class="dns-result-item p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
<h4 class="font-semibold text-gray-800 dark:text-white mb-3">${type} Records (${records.length})</h4>
|
|
<ul class="space-y-2">
|
|
`;
|
|
|
|
records.forEach(record => {
|
|
resultsHTML += `
|
|
<li class="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
|
<span class="font-mono text-sm break-all">${record.data}</span>
|
|
<button onclick="copyToClipboard('${record.data}', this)" class="ml-2 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" title="Copy to clipboard">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</li>
|
|
`;
|
|
});
|
|
|
|
resultsHTML += '</ul></div>';
|
|
});
|
|
|
|
resultsHTML += '</div>';
|
|
results.innerHTML = resultsHTML;
|
|
}
|
|
} catch (error) {
|
|
console.error('DNS lookup error:', error);
|
|
results.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h3 class="font-medium mb-1">Error</h3>
|
|
<p class="text-sm">Failed to perform DNS lookup. Please try again.</p>
|
|
</div>
|
|
`;
|
|
} finally {
|
|
loader.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Auto-submit on paste
|
|
input.addEventListener('paste', () => {
|
|
setTimeout(() => {
|
|
if (input.value.trim()) {
|
|
form.dispatchEvent(new Event('submit'));
|
|
}
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
// Initialize reverse DNS lookup form
|
|
function initReverseDNSForm() {
|
|
const form = document.getElementById('reverse-dns-form');
|
|
const input = document.getElementById('ip-input');
|
|
const results = document.getElementById('reverse-dns-results');
|
|
const loader = document.getElementById('reverse-dns-loader');
|
|
|
|
if (!form || !input || !results || !loader) return;
|
|
|
|
// IPv6 reverse DNS helper
|
|
function ipv6ToReverseDNS(ipv6) {
|
|
// Expand IPv6 to full format
|
|
let expanded = ipv6;
|
|
if (ipv6.includes('::')) {
|
|
const parts = ipv6.split('::');
|
|
const left = parts[0] ? parts[0].split(':') : [];
|
|
const right = parts[1] ? parts[1].split(':') : [];
|
|
const middle = new Array(8 - left.length - right.length).fill('0000');
|
|
expanded = left.concat(middle).concat(right).join(':');
|
|
}
|
|
|
|
// Remove colons and pad each segment to 4 characters
|
|
const fullHex = expanded.split(':').map(segment =>
|
|
segment.padStart(4, '0')
|
|
).join('');
|
|
|
|
// Reverse and add dots
|
|
return fullHex.split('').reverse().join('.') + '.ip6.arpa';
|
|
}
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const ip = input.value.trim();
|
|
if (!ip) return;
|
|
|
|
// Show loader
|
|
loader.classList.remove('hidden');
|
|
results.innerHTML = '';
|
|
|
|
try {
|
|
let reverseDomain;
|
|
|
|
// Check if IPv4 or IPv6
|
|
if (ip.includes(':')) {
|
|
// IPv6
|
|
try {
|
|
reverseDomain = ipv6ToReverseDNS(ip);
|
|
} catch (error) {
|
|
throw new Error('Invalid IPv6 address format');
|
|
}
|
|
} else {
|
|
// IPv4
|
|
const parts = ip.split('.');
|
|
if (parts.length !== 4 || parts.some(part => isNaN(part) || part < 0 || part > 255)) {
|
|
throw new Error('Invalid IPv4 address format');
|
|
}
|
|
reverseDomain = parts.reverse().join('.') + '.in-addr.arpa';
|
|
}
|
|
|
|
// Perform reverse DNS lookup
|
|
const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(reverseDomain)}&type=PTR`);
|
|
const data = await response.json();
|
|
|
|
if (data.Answer && data.Answer.length > 0) {
|
|
let resultsHTML = '<div class="space-y-2">';
|
|
data.Answer.forEach(record => {
|
|
const hostname = record.data.replace(/\.$/, '');
|
|
resultsHTML += `
|
|
<div class="flex justify-between items-center p-3 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div>
|
|
<div class="font-medium text-green-800 dark:text-green-200">Hostname found:</div>
|
|
<div class="font-mono text-sm text-green-700 dark:text-green-300">${hostname}</div>
|
|
</div>
|
|
<button onclick="copyToClipboard('${hostname}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`;
|
|
});
|
|
resultsHTML += '</div>';
|
|
results.innerHTML = resultsHTML;
|
|
} else {
|
|
results.innerHTML = `
|
|
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300 rounded-lg">
|
|
<h3 class="font-medium mb-1">No reverse DNS record found</h3>
|
|
<p class="text-sm">The IP address "${ip}" does not have a reverse DNS (PTR) record.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
} catch (error) {
|
|
console.error('Reverse DNS lookup error:', error);
|
|
results.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h3 class="font-medium mb-1">Error</h3>
|
|
<p class="text-sm">${error.message || 'Failed to perform reverse DNS lookup. Please check the IP address format.'}</p>
|
|
</div>
|
|
`;
|
|
} finally {
|
|
loader.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Auto-submit on paste
|
|
input.addEventListener('paste', () => {
|
|
setTimeout(() => {
|
|
if (input.value.trim()) {
|
|
form.dispatchEvent(new Event('submit'));
|
|
}
|
|
}, 100);
|
|
});
|
|
}
|