Merge pull request #305 from Lissy93/feat/small-improvments

Small improvments
This commit is contained in:
Alicia Sykes
2026-05-11 11:03:13 +01:00
committed by GitHub
19 changed files with 153 additions and 165 deletions

View File

@@ -7,25 +7,23 @@ const log = createLogger('carbon');
const TIMEOUT = 8000;
const MAX_BYTES = 10 * 1024 * 1024;
// Sustainable Web Design model v3 constants, matches websitecarbon.com formula
const KWH_PER_GB = 0.81;
// Sustainable Web Design model v4 constants, matches websitecarbon.com formula
const KWH_PER_GB = 0.3;
const FIRST_VISIT = 0.25;
const RETURN_VISIT = 0.75;
const RETURN_DATA_PCT = 0.02;
const GRID_INTENSITY = 442;
const GRID_INTENSITY = 494;
const RENEWABLE_INTENSITY = 50;
const LITRES_PER_GRAM = 0.5562;
// Reference median grams CO2 per visit, drawn from websitecarbon's published average.
// Used to estimate a percentile rank since we lack their measured-sites dataset
const REFERENCE_MEDIAN_GRAMS = 0.8;
// Median CO2 for an HTML-only fetch at HTTP Archive's ~30 KB median
const REFERENCE_MEDIAN_GRAMS = 0.001;
// Approximate percentile via log2 distance from the reference median.
// 1 doubling above median drops 25 points; clamp to [1, 99]
// Percentile rank via log2 distance from the median, clamped to [1, 95]
const estimateCleanerThan = (grams) => {
if (!grams || grams <= 0) return 0;
const pct = 50 - 25 * Math.log2(grams / REFERENCE_MEDIAN_GRAMS);
return Math.max(1, Math.min(99, Math.round(pct)));
const pct = 50 - 15 * Math.log2(grams / REFERENCE_MEDIAN_GRAMS);
return Math.max(1, Math.min(95, Math.round(pct)));
};
// Stream the response, cap at MAX_BYTES so huge pages can't blow memory or time

View File

@@ -4,19 +4,24 @@ import { parseTarget } from './_common/parse-target.js';
import { upstreamError } from './_common/upstream.js';
const rankHandler = async (url) => {
const { hostname: domain } = parseTarget(url);
const { hostname } = parseTarget(url);
const { TRANCO_USERNAME, TRANCO_API_KEY } = process.env;
const auth = TRANCO_API_KEY
? { auth: { username: TRANCO_USERNAME, password: TRANCO_API_KEY } }
: {};
const fallback = hostname.startsWith('www.') ? hostname.slice(4) : `www.${hostname}`;
// Tranco indexes only one variant per site, so try as-is, then toggle www
const lookup = (domain) => httpGet(`https://tranco-list.eu/api/ranks/domain/${domain}`, auth);
try {
const response = await httpGet(`https://tranco-list.eu/api/ranks/domain/${domain}`, auth);
if (!response.data?.ranks?.length) {
return {
skipped: `${domain} isn't ranked in the top 1 million sites yet`,
};
const first = await lookup(hostname);
if (first.data?.ranks?.length) return first.data;
try {
const second = await lookup(fallback);
if (second.data?.ranks?.length) return second.data;
} catch {
// Ignore fallback failures (e.g. rate limit) and accept the empty first result
}
return response.data;
return { skipped: `${hostname} isn't ranked in the top 1 million sites yet` };
} catch (error) {
return upstreamError(error, 'Tranco rank lookup');
}

View File

@@ -45,10 +45,15 @@ const subdomainsHandler = async (url) => {
params: { q: `%.${domain}`, output: 'json' },
headers: { Accept: 'application/json' },
});
const rows = Array.isArray(res.data) ? res.data : [];
const all = collectSubdomains(rows, domain);
if (!Array.isArray(res.data)) {
return { error: 'Certificate Transparency lookup returned unexpected data, please retry' };
}
const all = collectSubdomains(res.data, domain);
if (!all.length) {
return { skipped: `No subdomains found for ${domain} in Certificate Transparency logs` };
return {
skipped: `No subdomains found for ${domain} in Certificate Transparency logs`,
retryable: true,
};
}
return {
domain,

View File

@@ -17,7 +17,8 @@
"typecheck": "astro check",
"lint": "eslint --config .config/eslint.config.js .",
"format:check": "prettier --check --ignore-unknown '!yarn.lock' '!**/*.md' .",
"format:fix": "prettier --write --ignore-unknown '!yarn.lock' '!**/*.md' ."
"format:fix": "prettier --write --ignore-unknown '!yarn.lock' '!**/*.md' .",
"hold-my-beer": "yarn format:fix && yarn lint && yarn typecheck"
},
"dependencies": {
"@astrojs/check": "^0.9.9",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 B

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -1,20 +1,45 @@
{
"name": "Web Check",
"short_name": "Web Check",
"name": "Lissy93/Web-Check",
"description": "Web Check is the all-in-one OSINT and security tool, for revealing the inner workings of any website",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#d6fb41",
"background_color": "#111211",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
"src": "/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
},
{
"src": "apple-touch-icon.png",
"src": "/favicon-16x16.png",
"type": "image/png",
"sizes": "16x16"
},
{
"src": "/favicon-32x32.png",
"type": "image/png",
"sizes": "32x32"
},
{
"src": "/apple-touch-icon.png",
"type": "image/png",
"sizes": "180x180"
},
{
"src": "/android-chrome-192x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any"
},
{
"src": "/android-chrome-512x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#9fef00",
"background_color": "#141d2b"
]
}

View File

@@ -20,7 +20,7 @@ const Nav = (props: { children?: ReactNode }) => {
return (
<Header as="header">
<Heading color={colors.primary} size="large">
<img width="64" src="/web-check.png" alt="Web Check Icon" />
<img width="64" src="/favicon.svg" alt="Web Check Icon" />
<a href="/" target="_self">
Web Check
</a>

View File

@@ -315,12 +315,16 @@ const FancyBackground = (): JSX.Element => {
useEffect(() => {
App.setup();
App.draw();
var frame = function () {
App.evolve();
const frameInterval = 25;
let lastTime = 0;
const frame = (now: number) => {
if (now - lastTime >= frameInterval) {
App.evolve();
lastTime = now;
}
requestAnimationFrame(frame);
};
frame();
requestAnimationFrame(frame);
}, [App]);
return (

View File

@@ -51,7 +51,7 @@ const Footer = (props: { isFixed?: boolean }): JSX.Element => {
</span>
<span>
<Link to="/about">Web-Check</Link> is licensed under <ALink href={licenseUrl}>MIT</ALink> -
© <ALink href={authorUrl}>Alicia Sykes</ALink> 2023
© <ALink href={authorUrl}>Alicia Sykes</ALink> 2026
</span>
</StyledFooter>
);

View File

@@ -8,14 +8,15 @@ const LoaderContainer = styled(StyledCard)`
margin: 0 auto;
width: 95vw;
position: relative;
transition: all 0.2s ease-in-out;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 2rem;
height: 50vh;
transition: all 0.3s ease-in-out;
transition:
height 0.3s ease-in-out,
opacity 0.3s ease-in-out;
p.loadTimeInfo {
text-align: center;
margin: 0;
@@ -29,14 +30,9 @@ const LoaderContainer = styled(StyledCard)`
height: 0;
overflow: hidden;
opacity: 0;
margin: -1rem 0 0 0;
padding: 0;
svg {
width: 0;
}
h4 {
font-size: 0;
}
margin-top: -1rem;
padding-top: 0;
padding-bottom: 0;
}
&.hide {
display: none;

View File

@@ -102,11 +102,11 @@ const fetchAndPoll = (path: string) =>
}),
);
// Re-run on transient errors, returning the last error if all attempts fail
// Re-run on transient errors or when the server hints `retryable: true`
const fetchAndRetry = (path: string) =>
retrying(
path,
(r) => !!r?.error,
(r) => !!r?.error || !!r?.retryable,
3,
2000,
(last) => last,
@@ -207,7 +207,6 @@ export const jobs: JobSpec[] = [
{
id: 'tls-labs',
expectedAddressTypes: [...URL_ONLY],
noClientTimeout: true,
cards: [
card('tls-security-audit', 'TLS Security Audit', ['security'], TlsSecurityAuditCard),
card('tls-client-compat', 'TLS Client Compatibility', ['security'], TlsClientCompatCard),

View File

@@ -121,12 +121,11 @@ const SponsorshipContainer = styled.div`
}
`;
const makeAnchor = (title: string): string => {
return title
const makeAnchor = (title: string): string =>
title
.toLowerCase()
.replace(/[^\w\s]|_/g, '')
.replace(/\s+/g, '-');
};
const About = (): JSX.Element => {
const location = useLocation();

View File

@@ -43,42 +43,15 @@ const SponsorCard = styled.div`
box-shadow: 4px 4px 0px ${colors.bgShadowColor};
border-radius: 8px;
padding: 1rem;
z-index: 5;
margin: 1rem;
width: calc(100% - 2rem);
max-width: 60rem;
z-index: 2;
.inner {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 1rem;
p {
margin: 0.25rem 0;
}
p {
margin: 0.25rem 0;
}
a {
color: ${colors.textColor};
}
img {
border-radius: 0.25rem;
box-shadow: 2px 2px 0px ${colors.fgShadowColor};
transition: box-shadow 0.2s;
margin: 0 auto;
display: block;
width: 200px;
&:hover {
box-shadow: 4px 4px 0px ${colors.fgShadowColor};
}
&:active {
box-shadow: -2px -2px 0px ${colors.fgShadowColor};
}
}
.cta {
font-size: 0.78rem;
a {
color: ${colors.primary};
}
color: ${colors.primary};
}
`;
@@ -124,14 +97,23 @@ const SiteFeaturesWrapper = styled(StyledCard)`
font-size: 0.9rem;
color: ${colors.textColor};
li {
position: relative;
margin: 0.1rem 0;
text-indent: -1.2rem;
padding-left: 1.2rem;
break-inside: avoid-column;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
li:before {
content: '✓';
color: ${colors.primary};
margin-right: 0.5rem;
position: absolute;
left: 0;
}
li:not(:last-child) a {
color: inherit;
text-decoration: none;
}
}
a {
@@ -139,6 +121,13 @@ const SiteFeaturesWrapper = styled(StyledCard)`
}
`;
// Build a URL-safe anchor id from a section title (e.g. "IP Info" -> "ip-info")
const makeAnchor = (title: string): string =>
title
.toLowerCase()
.replace(/[^\w\s]|_/g, '')
.replace(/\s+/g, '-');
const Home = (): JSX.Element => {
const defaultPlaceholder = 'e.g. duck.com';
const [userInput, setUserInput] = useState('');
@@ -191,23 +180,6 @@ const Home = (): JSX.Element => {
submit();
};
// const findIpAddress = () => {
// setUserInput('');
// setPlaceholder('Looking up your IP...');
// setInputDisabled(true);
// fetch('https://ipapi.co/json/')
// .then(function(response) {
// response.json().then(jsonData => {
// setUserInput(jsonData.ip);
// setPlaceholder(defaultPlaceholder);
// setInputDisabled(true);
// });
// })
// .catch(function(error) {
// console.log('Failed to get IP address :\'(', error)
// });
// };
return (
<HomeContainer>
<FancyBackground />
@@ -238,43 +210,16 @@ const Home = (): JSX.Element => {
</UserInputMain>
<SponsorCard>
<Heading as="h2" size="small" color={colors.primary}>
Sponsored by
Enjoying Web Check?
</Heading>
<div className="inner">
<p>
<a
target="_blank"
rel="noreferrer"
href="https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh"
>
Terminal Trove
</a>{' '}
- The $HOME of all things in the terminal.
<br />
<span className="cta">
Get updates on the latest CLI/TUI tools via the{' '}
<a
target="_blank"
rel="noreferrer"
className="cta"
href="https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh"
>
Terminal Trove newsletter
</a>
</span>
</p>
<a
target="_blank"
rel="noreferrer"
href="https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh"
>
<img
width="120"
alt="Terminal Trove"
src="https://i.ibb.co/NKtYjJ1/terminal-trove-web-check.png"
/>
<p>
It's free, open source, and funded by the community. If it's been useful, you can keep it
going (and ad-free) by{' '}
<a target="_blank" rel="noreferrer" href="https://github.com/sponsors/Lissy93">
sponsoring me on GitHub
</a>
</div>
. Every bit genuinely helps, thank you
</p>
</SponsorCard>
<SiteFeaturesWrapper>
<div className="features">
@@ -283,7 +228,11 @@ const Home = (): JSX.Element => {
</Heading>
<ul>
{docs.map((doc, index) => (
<li key={index}>{doc.title}</li>
<li key={index}>
<Link to={`/check/about#${makeAnchor(doc.title)}`} title={doc.title}>
{doc.title}
</Link>
</li>
))}
<li>
<Link to="/check/about">+ more!</Link>

View File

@@ -16,7 +16,10 @@ const siteInfo = {
titleLong: 'Web Check - X-Ray Vision for any Website',
description:
'Web Check is the all-in-one OSINT and security tool, for revealing the inner workings of any website',
keywords: '',
keywords:
'web check, OSINT, website security, security audit, DNS lookup, SSL check, ' +
'tech stack detection, website analyzer, web tools, open source',
themeColor: '#c1fb41',
author: 'Alicia Sykes',
twitter: '@Lissy_Sykes',
site: import.meta.env.SITE_URL || 'https://web-check.xyz',
@@ -27,17 +30,16 @@ const siteInfo = {
},
};
// Set values for the meta tags, from props or defaults
const {
title = siteInfo.title,
description = siteInfo.description,
keywords = siteInfo.keywords,
breadcrumbs,
customSchemaJson,
} = Astro.props;
// Resolve per-page values, falling back to siteInfo defaults
const { title, description, keywords, breadcrumbs, customSchemaJson } = Astro.props;
const pageTitle = title ?? siteInfo.title;
const ogTitle = title ?? siteInfo.titleLong;
const pageDescription = description ?? siteInfo.description;
const pageKeywords = keywords ?? siteInfo.keywords;
// Set non-customizable values for meta tags, from the siteInfo
const { site, author, twitter, analytics, titleLong } = siteInfo;
const { site, author, twitter, analytics, themeColor } = siteInfo;
const canonical = new URL(Astro.url.pathname, site).href;
const ogImage = `${site}/banner.png`;
// Given a map of breadcrumbs, return the JSON-LD for the BreadcrumbList schema
const makeBreadcrumbs = () => {
@@ -56,10 +58,12 @@ const makeBreadcrumbs = () => {
---
<!-- Core info -->
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
{pageKeywords && <meta name="keywords" content={pageKeywords} />}
<meta name="author" content={author} />
<meta name="theme-color" content={themeColor} />
<link rel="canonical" href={canonical} />
<!-- Page info, viewport, Astro credit -->
<meta charset="UTF-8" />
@@ -69,23 +73,23 @@ const makeBreadcrumbs = () => {
<!-- Icons and colors -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="512x512" href="/web-check.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<!-- Social media meta tags (Open Graphh + Twitter) -->
<meta property="og:site_name" content={title} />
<meta property="og:site_name" content={siteInfo.title} />
<meta property="og:type" content="website" />
<meta property="og:url" content={site} />
<meta property="og:title" content={titleLong} />
<meta property="og:description" content={description} />
<meta property="og:image" content={`${site}/banner.png`} />
<meta name="twitter:card" content="summary" />
<meta property="og:url" content={canonical} />
<meta property="og:title" content={ogTitle} />
<meta property="og:description" content={pageDescription} />
<meta property="og:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content={site} />
<meta name="twitter:title" content={titleLong} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={`${site}/banner.png`} />
<link rel="twitter:image" sizes="180x180" href={`${site}/apple-touch-icon.png`} />
<meta name="twitter:url" content={canonical} />
<meta name="twitter:title" content={ogTitle} />
<meta name="twitter:description" content={pageDescription} />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:site" content={twitter} />
<meta name="twitter:creator" content={twitter} />

View File

@@ -14,9 +14,12 @@ if (searchUrl) {
const target = normalizeAddress(searchUrl);
if (target) Astro.redirect(`/check/${target}`);
}
const { target } = Astro.params;
const pageTitle = target ? `${target} | Web Check` : 'Web Check';
---
<BaseLayout preloadBodyFont={false}>
<BaseLayout preloadBodyFont={false} title={pageTitle}>
<link
slot="head"
href="/fonts/PTMono-Regular.woff2"