import { useState, useEffect, useCallback } from 'react'; import { useParams } from "react-router-dom"; import styled from 'styled-components'; import colors from 'styles/colors'; import Heading from 'components/Form/Heading'; import Card from 'components/Form/Card'; import ErrorBoundary from 'components/misc/ErrorBoundary'; import Footer from 'components/misc/Footer'; import ServerLocationCard from 'components/Results/ServerLocation'; import ServerInfoCard from 'components/Results/ServerInfo'; import HostNamesCard from 'components/Results/HostNames'; import WhoIsCard from 'components/Results/WhoIs'; import BuiltWithCard from 'components/Results/BuiltWith'; import LighthouseCard from 'components/Results/Lighthouse'; import ScreenshotCard from 'components/Results/Screenshot'; import SslCertCard from 'components/Results/SslCert'; import HeadersCard from 'components/Results/Headers'; import CookiesCard from 'components/Results/Cookies'; import RobotsTxtCard from 'components/Results/RobotsTxt'; import DnsRecordsCard from 'components/Results/DnsRecords'; import ProgressBar, { LoadingJob, LoadingState, initialJobs } from 'components/misc/ProgressBar'; import keys from 'utils/get-keys'; import { determineAddressType, AddressType } from 'utils/address-type-checker'; import { getLocation, ServerLocation, getServerInfo, ServerInfo, getHostNames, HostNames, makeTechnologies, TechnologyGroup, parseCookies, Cookie, parseRobotsTxt, Whois, } from 'utils/result-processor'; const ResultsOuter = styled.div` display: flex; flex-direction: column; `; const ResultsContent = styled.section` width: 95vw; display: grid; grid-auto-flow: dense; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 1rem; margin: auto; width: calc(100% - 2rem); padding-bottom: 1rem; `; const Header = styled(Card)` margin: 1rem; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; padding: 0.5rem 1rem; `; const Results = (): JSX.Element => { const startTime = new Date().getTime(); const [ serverInfo, setServerInfo ] = useState(); const [ hostNames, setHostNames ] = useState(); const [ locationResults, setLocationResults ] = useState(); const [ whoIsResults, setWhoIsResults ] = useState(); const [ technologyResults, setTechnologyResults ] = useState(); const [ lighthouseResults, setLighthouseResults ] = useState(); const [ sslResults, setSslResults ] = useState(); const [ headersResults, setHeadersResults ] = useState(); const [ dnsResults, setDnsResults ] = useState(); const [ robotsTxtResults, setRobotsTxtResults ] = useState(); const [ cookieResults, setCookieResults ] = useState(null); const [ screenshotResult, setScreenshotResult ] = useState(); const [ ipAddress, setIpAddress ] = useState(undefined); const [ addressType, setAddressType ] = useState('empt'); const { address } = useParams(); const [ loadingJobs, setLoadingJobs ] = useState(initialJobs); const updateLoadingJobs = useCallback((job: string, newState: LoadingState, error?: string) => { const timeTaken = new Date().getTime() - startTime; setLoadingJobs((prevJobs) => { const newJobs = prevJobs.map((loadingJob: LoadingJob) => { if (loadingJob.name === job) { return { ...loadingJob, error, state: newState, timeTaken }; } return loadingJob; }); if (newState === 'error') { console.warn(`Error in ${job}: ${error}`); } return newJobs; }); }, []); /* Cancel remaining jobs after 20 second timeout */ useEffect(() => { const checkJobs = () => { loadingJobs.forEach(job => { if (job.state === 'loading') { updateLoadingJobs(job.name, 'timed-out'); } }); }; const timeoutId = setTimeout(checkJobs, 10000); return () => { clearTimeout(timeoutId); }; }, [loadingJobs, updateLoadingJobs]); // dependencies for the effect useEffect(() => { setAddressType(determineAddressType(address || '')); if (addressType === 'ipV4') { setIpAddress(address); } }, []); /* Get IP address from URL */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('get-ip', 'skipped'); return; } const fetchIpAddress = () => { fetch(`/find-url-ip?address=${address}`) .then(function(response) { response.json().then(jsonData => { setIpAddress(jsonData.ip); updateLoadingJobs('get-ip', 'success'); }); }) .catch(function(error) { updateLoadingJobs('get-ip', 'error', error); }); }; if (!ipAddress) { fetchIpAddress(); } }, [address, addressType]); /* Get SSL info */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('ssl', 'skipped'); return; } fetch(`/ssl-check?url=${address}`) .then(response => response.json()) .then(response => { if (Object.keys(response).length > 0) { setSslResults(response); updateLoadingJobs('ssl', 'success'); } else { updateLoadingJobs('ssl', 'error', 'No SSL Cert found'); } }) .catch(err => updateLoadingJobs('ssl', 'error', err)); }, [address, addressType]) /* Get Cookies */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('cookies', 'skipped'); return; } fetch(`/get-cookies?url=${address}`) .then(response => response.json()) .then(response => { setCookieResults(parseCookies(response.cookies)); updateLoadingJobs('cookies', 'success'); }) .catch(err => updateLoadingJobs('cookies', 'error', err)); }, [address, addressType]) /* Get Robots.txt */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('robots-txt', 'skipped'); return; } fetch(`/read-robots-txt?url=${address}`) .then(response => response.text()) .then(response => { setRobotsTxtResults(parseRobotsTxt(response)); updateLoadingJobs('robots-txt', 'success'); }) .catch(err => updateLoadingJobs('robots-txt', 'error', err)); }, [address, addressType]) /* Get Headers */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('headers', 'skipped'); return; } fetch(`/get-headers?url=${address}`) .then(response => response.json()) .then(response => { setHeadersResults(response); updateLoadingJobs('headers', 'success'); }) .catch(err => updateLoadingJobs('headers', 'error', err)); }, [address, addressType]) /* Get DNS records */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('dns', 'skipped'); return; } fetch(`/get-dns?url=${address}`) .then(response => response.json()) .then(response => { setDnsResults(response); updateLoadingJobs('dns', 'success'); }) .catch(err => updateLoadingJobs('dns', 'error', err)); }, [address, addressType]) /* Get Lighthouse report */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('lighthouse', 'skipped'); return; } fetch(`/lighthouse-report?url=${address}`) .then(response => response.json()) .then(response => { setLighthouseResults(response.lighthouseResult); setScreenshotResult(response.lighthouseResult?.fullPageScreenshot?.screenshot?.data); updateLoadingJobs('lighthouse', 'success'); }) .catch(err => { // if (err.errorType === 'TimeoutError') { // Netlify limits to 10 seconds, we can try again client-side... const params = 'category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile'; const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${address}&${params}&key=${keys.googleCloud}`; fetch(endpoint) .then(response => response.json()) .then(response => { setLighthouseResults(response.lightHouseResult); setScreenshotResult(response?.lighthouseResult?.fullPageScreenshot?.screenshot?.data); updateLoadingJobs('lighthouse', 'success'); }) .catch(err => updateLoadingJobs('lighthouse', 'error', err)); }); }, [address, addressType]) /* Get IP address location info */ useEffect(() => { const fetchIpLocation = () => { fetch(`https://ipapi.co/${ipAddress}/json/`) .then(function(response) { response.json().then(jsonData => { setLocationResults(getLocation(jsonData)); updateLoadingJobs('location', 'success'); }); }) .catch(function(error) { updateLoadingJobs('location', 'error', error); }); }; if (ipAddress) { fetchIpLocation(); } }, [ipAddress]); /* Get hostnames and server info from Shodan */ useEffect(() => { const applyShodanResults = (response: any) => { setServerInfo(getServerInfo(response)); setHostNames(getHostNames(response)); } const fetchShodanData = () => { const apiKey = keys.shodan; fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${apiKey}`) .then(response => response.json()) .then(response => { if (!response.error) { applyShodanResults(response) updateLoadingJobs('server-info', 'success'); } }) .catch(err => updateLoadingJobs('server-info', 'error', err)); }; if (ipAddress) { fetchShodanData(); } }, [ipAddress]); /* Get BuiltWith tech stack */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('built-with', 'skipped'); return; } const apiKey = keys.builtWith; const endpoint = `https://api.builtwith.com/v21/api.json?KEY=${apiKey}&LOOKUP=${address}`; fetch(endpoint) .then(response => response.json()) .then(response => { setTechnologyResults(makeTechnologies(response)); updateLoadingJobs('built-with', 'success'); }) .catch(err => updateLoadingJobs('built-with', 'error', err)); }, [address, addressType]); /* Get WhoIs info for a given domain name */ useEffect(() => { if (addressType !== 'url') { updateLoadingJobs('whois', 'skipped'); return; } const applyWhoIsResults = (response: any) => { const whoIsResults: Whois = { created: response.date_created, expires: response.date_expires, updated: response.date_updated, nameservers: response.nameservers, }; setWhoIsResults(whoIsResults); } const fetchWhoIsData = () => { const apiKey = keys.whoApi; fetch(`https://api.whoapi.com/?domain=${address}&r=whois&apikey=${apiKey}`) .then(response => response.json()) .then(response => { if (!response.error) applyWhoIsResults(response) updateLoadingJobs('whois', 'success'); }) .catch(err => updateLoadingJobs('whois', 'error', err)); }; if (addressType === 'url') { fetchWhoIsData(); } }, [addressType, address]); const makeSiteName = (address: string): string => { try { return new URL(address).hostname.replace('www.', ''); } catch (error) { return address; } } return (
Web Check { address && { addressType === 'url' && } {makeSiteName(address)} }
{ locationResults && } { sslResults && } { headersResults && } { hostNames && } { whoIsResults && } { dnsResults && } { lighthouseResults && } { cookieResults && } { screenshotResult && } { technologyResults && } { robotsTxtResults && } { serverInfo && }