const fs = require('fs').promises; const dns = require('dns').promises; const state = require('../../../infrastructure/state'); const { logError, logWarn, logInfo } = require('../../../infrastructure/logger'); const { saveSelectorCache } = require('../cache'); const { broadcast } = require('../websocket'); const localDnsFile = process.env.LOCAL_DNS_FILE || 'cache/local_dns.json'; async function handleLocalDnsRoutes(req, res) { const urlPath = req.urlPath || new URL(req.url, `https://${req.headers.host}`).pathname; const method = req.method; if (method === 'GET' && urlPath === '/api/local-dns') { try { let records = state.localDnsRecords || []; let conflicts = []; const domains = new Set([...state.domainsWithBoth, ...state.versionPreferences.keys()]); for (const domain of domains) { let publicIP = state.publicIpForDomain[domain]; if (!publicIP && state.versionPreferences.has(domain)) { try { const ips = await dns.resolve4(domain); publicIP = ips[0] || 'N/A'; state.publicIpForDomain[domain] = publicIP; } catch (err) { logWarn('Admin', `Failed to resolve public IP for ${domain}: ${err.message}`); publicIP = 'N/A'; } } conflicts.push({ domain, version: state.versionPreferences.get(domain) || 'p2p', publicIP: publicIP || 'N/A' }); } records = records.map((rec, index) => ({ ...rec, index })); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ records, conflicts })); } catch (err) { logError('Admin', `Failed to fetch local DNS and conflicts: ${err.message}`); res.writeHead(500); res.end(JSON.stringify({ error: 'Failed to fetch local DNS and conflicts' })); } return true; } if (method === 'GET' && urlPath === '/api/selector-cache') { try { const preferences = Object.fromEntries(state.versionPreferences); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(preferences)); } catch (err) { logError('Admin', `Failed to fetch selector cache: ${err.message}`); res.writeHead(500); res.end(JSON.stringify({ error: 'Failed to fetch selector cache' })); } return true; } if (method === 'POST' && urlPath === '/api/add-local-dns') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { try { const record = JSON.parse(body); if (!record.name || !record.type || !record.ttl || isNaN(record.ttl)) { throw new Error('Missing or invalid required fields: name, type, ttl'); } record.class = record.class || 'IN'; let records = state.localDnsRecords || []; records.push(record); await fs.writeFile(localDnsFile, JSON.stringify(records, null, 2)); state.localDnsRecords = records; broadcast({ type: 'update-local-dns' }); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('OK'); } catch (err) { logError('Admin', `Failed to add local DNS record: ${err.message}`); res.writeHead(500); res.end(err.message); } }); return true; } if (method === 'POST' && urlPath === '/api/update-local-dns') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { try { const { index, record } = JSON.parse(body); if (!record.name || !record.type || !record.ttl || isNaN(record.ttl)) { throw new Error('Missing or invalid required fields: name, type, ttl'); } let records = state.localDnsRecords || []; if (index >= 0 && index < records.length) { record.class = record.class || 'IN'; records[index] = record; await fs.writeFile(localDnsFile, JSON.stringify(records, null, 2)); state.localDnsRecords = records; broadcast({ type: 'update-local-dns' }); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('OK'); } else { res.writeHead(400); res.end('Invalid index'); } } catch (err) { logError('Admin', `Failed to update local DNS record: ${err.message}`); res.writeHead(500); res.end(err.message); } }); return true; } if (method === 'POST' && urlPath === '/api/delete-local-dns') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { try { const { index } = JSON.parse(body); let records = state.localDnsRecords || []; if (index >= 0 && index < records.length) { records.splice(index, 1); await fs.writeFile(localDnsFile, JSON.stringify(records, null, 2)); state.localDnsRecords = records; broadcast({ type: 'update-local-dns' }); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('OK'); } else { res.writeHead(400); res.end('Invalid index'); } } catch (err) { logError('Admin', `Failed to delete local DNS record: ${err.message}`); res.writeHead(500); res.end(err.message); } }); return true; } if (method === 'POST' && urlPath === '/api/update-version-preference') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { try { const { domain, version } = JSON.parse(body); if (version !== 'p2p' && version !== 'public') { res.writeHead(400); res.end('Invalid version, must be "p2p" or "public"'); return; } state.versionPreferences.set(domain, version); await saveSelectorCache(); broadcast({ type: 'update-local-dns' }); logInfo('Admin', `Updated version preference for ${domain} to ${version}`); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('OK'); } catch (err) { logError('Admin', `Failed to update version preference: ${err.message}`); res.writeHead(500); res.end(err.message); } }); return true; } return false; } module.exports = { handleLocalDnsRoutes };