mirror of
https://github.com/Lissy93/web-check.git
synced 2026-05-13 16:01:23 -04:00
chore: Upgrades all deps, migrate to new astro, ts, etc. removes non-essential deps
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Specify the Node.js version to use
|
||||
ARG NODE_VERSION=20
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
# Specify the Debian version to use, the default is "bullseye"
|
||||
ARG DEBIAN_VERSION=bullseye
|
||||
|
||||
108
api/_common/http.js
Normal file
108
api/_common/http.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Thin fetch wrapper matching the axios shape used across the api: opts.params,
|
||||
// opts.headers, opts.auth, opts.timeout, opts.validateStatus; returns
|
||||
// { data, status, statusText, headers }; throws errors with response/code
|
||||
|
||||
const DEFAULT_TIMEOUT = 60000;
|
||||
|
||||
const buildAuth = (auth) => {
|
||||
if (!auth?.username) return null;
|
||||
return 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
|
||||
};
|
||||
|
||||
const appendParams = (url, params) => {
|
||||
if (!params) return url;
|
||||
const u = new URL(url);
|
||||
for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
|
||||
return u.href;
|
||||
};
|
||||
|
||||
const headersToObject = (headers) => {
|
||||
const out = {};
|
||||
for (const [k, v] of headers.entries()) {
|
||||
const key = k.toLowerCase();
|
||||
if (key === 'set-cookie') {
|
||||
out[key] = headers.getSetCookie ? headers.getSetCookie() : v.split(/, (?=[^;]+=)/);
|
||||
} else {
|
||||
out[key] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Auto-parse JSON when the response advertises it, fall back to raw text
|
||||
const parseBody = async (response) => {
|
||||
const ct = (response.headers.get('content-type') || '').toLowerCase();
|
||||
const text = await response.text();
|
||||
if (!text) return ct.includes('json') ? null : '';
|
||||
if (ct.includes('json')) {
|
||||
try { return JSON.parse(text); } catch { return text; }
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const isOk = (status, validate) =>
|
||||
validate ? validate(status) : (status >= 200 && status < 300);
|
||||
|
||||
const wrapNetworkError = (error) => {
|
||||
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
||||
const e = new Error(error.message || 'Request timed out');
|
||||
e.code = 'ECONNABORTED';
|
||||
return e;
|
||||
}
|
||||
const code = error.cause?.code;
|
||||
if (code) {
|
||||
const e = new Error(error.message);
|
||||
e.code = code;
|
||||
return e;
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
const send = async (method, url, body, opts = {}) => {
|
||||
const finalUrl = appendParams(url, opts.params);
|
||||
const headers = { ...opts.headers };
|
||||
const authHeader = buildAuth(opts.auth);
|
||||
if (authHeader) headers.authorization = authHeader;
|
||||
|
||||
const init = {
|
||||
method,
|
||||
headers,
|
||||
signal: AbortSignal.timeout(opts.timeout || DEFAULT_TIMEOUT),
|
||||
};
|
||||
|
||||
if (body !== undefined && body !== null) {
|
||||
if (typeof body === 'object') {
|
||||
init.body = JSON.stringify(body);
|
||||
const hasCt = Object.keys(headers).some(k => k.toLowerCase() === 'content-type');
|
||||
if (!hasCt) init.headers['content-type'] = 'application/json';
|
||||
} else {
|
||||
init.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(finalUrl, init);
|
||||
} catch (error) {
|
||||
throw wrapNetworkError(error);
|
||||
}
|
||||
|
||||
const data = await parseBody(response);
|
||||
const result = {
|
||||
data,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: headersToObject(response.headers),
|
||||
};
|
||||
|
||||
if (!isOk(response.status, opts.validateStatus)) {
|
||||
const err = new Error(`Request failed with status code ${response.status}`);
|
||||
err.response = result;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const httpGet = (url, opts) => send('GET', url, null, opts);
|
||||
export const httpPost = (url, body, opts) => send('POST', url, body, opts);
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
|
||||
const convertTimestampToDate = (timestamp) => {
|
||||
const [year, month, day, hour, minute, second] = [
|
||||
@@ -50,7 +50,7 @@ const wayBackHandler = async (url) => {
|
||||
const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${url}&output=json&fl=timestamp,statuscode,digest,length,offset`;
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(cdxUrl);
|
||||
const { data } = await httpGet(cdxUrl);
|
||||
|
||||
// Check there's data
|
||||
if (!data || !Array.isArray(data) || data.length <= 1) {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import axios from 'axios';
|
||||
import puppeteer from 'puppeteer';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
|
||||
const getPuppeteerCookies = async (url) => {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new',
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
const navigationPromise = page.goto(url, { waitUntil: 'networkidle2' });
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Puppeteer took too long!')), 3000)
|
||||
);
|
||||
await Promise.race([navigationPromise, timeoutPromise]);
|
||||
return await page.cookies();
|
||||
return await browser.cookies();
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
@@ -26,19 +26,13 @@ const cookieHandler = async (url) => {
|
||||
let clientCookies = null;
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
withCredentials: true,
|
||||
maxRedirects: 5,
|
||||
});
|
||||
const response = await httpGet(url);
|
||||
headerCookies = response.headers['set-cookie'];
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
return { error: `Request failed with status ${error.response.status}: ${error.message}` };
|
||||
} else if (error.request) {
|
||||
return { error: `No response received: ${error.message}` };
|
||||
} else {
|
||||
return { error: `Error setting up request: ${error.message}` };
|
||||
}
|
||||
return { error: `No response received: ${error.message}` };
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { promises as dnsPromises } from 'dns';
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -14,8 +14,7 @@ const dnsHandler = async (url) => {
|
||||
}
|
||||
const results = await Promise.all(addresses.map(async (address) => {
|
||||
const hostname = await dnsPromises.reverse(address).catch(() => null);
|
||||
const dohDirectSupports = await axios
|
||||
.get(`https://${address}/dns-query`)
|
||||
const dohDirectSupports = await httpGet(`https://${address}/dns-query`)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
return { address, hostname, dohDirectSupports };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ const hasWaf = (waf) => ({ hasWaf: true, waf });
|
||||
const firewallHandler = async (url) => {
|
||||
const { href } = parseTarget(url);
|
||||
try {
|
||||
const response = await axios.get(href);
|
||||
const response = await httpGet(href);
|
||||
const headers = response.headers;
|
||||
|
||||
if (headers['server'] && headers['server'].includes('cloudflare')) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const headersHandler = async (url) => {
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
const response = await httpGet(url, {
|
||||
validateStatus: (status) => status >= 200 && status < 600,
|
||||
});
|
||||
return response.headers;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const httpsSecHandler = async (url) => {
|
||||
try {
|
||||
const { headers } = await axios.get(url);
|
||||
const { headers } = await httpGet(url);
|
||||
return {
|
||||
strictTransportPolicy: !!headers['strict-transport-security'],
|
||||
xFrameOptions: !!headers['x-frame-options'],
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import unzipper from 'unzipper';
|
||||
import csv from 'csv-parser';
|
||||
import fs from 'fs';
|
||||
import middleware from './_common/middleware.js';
|
||||
|
||||
// Should also work with the following sources:
|
||||
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
|
||||
// https://tranco-list.eu/top-1m.csv.zip
|
||||
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
|
||||
// https://radar.cloudflare.com/charts/LargerTopDomainsTable/attachment?id=525&top=1000000
|
||||
// https://statvoo.com/dl/top-1million-sites.csv.zip
|
||||
|
||||
const FILE_URL = 'https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip';
|
||||
const TEMP_FILE_PATH = '/tmp/top-1m.csv';
|
||||
|
||||
const rankHandler = async (url) => {
|
||||
let domain = null;
|
||||
|
||||
try {
|
||||
domain = new URL(url).hostname;
|
||||
} catch (e) {
|
||||
throw new Error('Invalid URL');
|
||||
}
|
||||
|
||||
// Download and unzip the file if not in cache
|
||||
if (!fs.existsSync(TEMP_FILE_PATH)) {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: FILE_URL,
|
||||
responseType: 'stream'
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
response.data
|
||||
.pipe(unzipper.Extract({ path: '/tmp' }))
|
||||
.on('close', resolve)
|
||||
.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Parse the CSV and find the rank
|
||||
return new Promise((resolve, reject) => {
|
||||
const csvStream = fs.createReadStream(TEMP_FILE_PATH)
|
||||
.pipe(csv({
|
||||
headers: ['rank', 'domain'],
|
||||
}))
|
||||
.on('data', (row) => {
|
||||
if (row.domain === domain) {
|
||||
csvStream.destroy();
|
||||
resolve({
|
||||
domain: domain,
|
||||
rank: row.rank,
|
||||
isFound: true,
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
resolve({
|
||||
skipped: `Skipping, as ${domain} is not present in the Umbrella top 1M list.`,
|
||||
domain: domain,
|
||||
isFound: false,
|
||||
});
|
||||
})
|
||||
.on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const handler = middleware(rankHandler);
|
||||
export default handler;
|
||||
@@ -1,13 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import urlLib from 'url';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const linkedPagesHandler = async (url) => {
|
||||
let response;
|
||||
try {
|
||||
response = await axios.get(url);
|
||||
response = await httpGet(url);
|
||||
} catch (error) {
|
||||
return upstreamError(error, 'Linked pages fetch');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { upstreamError } from './_common/upstream.js';
|
||||
const locationHandler = async (url) => {
|
||||
const { hostname } = parseTarget(url);
|
||||
try {
|
||||
const res = await axios.get(`https://ipapi.co/${hostname}/json/`, { timeout: 5000 });
|
||||
const res = await httpGet(`https://ipapi.co/${hostname}/json/`, { timeout: 5000 });
|
||||
if (res.data?.error) return { skipped: res.data.reason || 'Lookup unavailable' };
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { requireEnv, upstreamError } from './_common/upstream.js';
|
||||
|
||||
const qualityHandler = async (url) => {
|
||||
@@ -12,7 +12,7 @@ const qualityHandler = async (url) => {
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = (await axios.get(endpoint)).data;
|
||||
data = (await httpGet(endpoint)).data;
|
||||
} catch (error) {
|
||||
return upstreamError(error, 'Quality check');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -10,7 +10,7 @@ const rankHandler = async (url) => {
|
||||
? { auth: { username: TRANCO_USERNAME, password: TRANCO_API_KEY } }
|
||||
: {};
|
||||
try {
|
||||
const response = await axios.get(
|
||||
const response = await httpGet(
|
||||
`https://tranco-list.eu/api/ranks/domain/${domain}`,
|
||||
{ timeout: 5000, ...auth },
|
||||
);
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import got from 'got';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const MAX_REDIRECTS = 12;
|
||||
const TIMEOUT_MS = 10000;
|
||||
const USER_AGENT = 'Mozilla/5.0 (compatible; WebCheck/2.0; +https://web-check.xyz)';
|
||||
|
||||
// Walks the redirect chain manually, recording each Location header as got did
|
||||
const redirectsHandler = async (url) => {
|
||||
const redirects = [url];
|
||||
let current = url;
|
||||
try {
|
||||
await got(url, {
|
||||
followRedirect: true,
|
||||
maxRedirects: 12,
|
||||
hooks: {
|
||||
beforeRedirect: [
|
||||
(_options, response) => { redirects.push(response.headers.location); },
|
||||
],
|
||||
},
|
||||
});
|
||||
for (let i = 0; i < MAX_REDIRECTS; i++) {
|
||||
const response = await fetch(current, {
|
||||
redirect: 'manual',
|
||||
signal: AbortSignal.timeout(TIMEOUT_MS),
|
||||
headers: { 'user-agent': USER_AGENT },
|
||||
});
|
||||
if (response.status < 300 || response.status >= 400) {
|
||||
if (response.status >= 400) {
|
||||
const err = new Error(`HTTP ${response.status}`);
|
||||
err.response = { status: response.status };
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const location = response.headers.get('location');
|
||||
if (!location) break;
|
||||
redirects.push(location);
|
||||
current = new URL(location, current).href;
|
||||
}
|
||||
return { redirects };
|
||||
} catch (error) {
|
||||
if (error.cause?.code) error.code = error.cause.code;
|
||||
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
||||
error.code = 'ECONNABORTED';
|
||||
}
|
||||
return upstreamError(error, 'Redirect lookup');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -17,7 +17,7 @@ const parseRobotsTxt = (content) => {
|
||||
const robotsHandler = async (url) => {
|
||||
const { protocol, hostname } = parseTarget(url);
|
||||
try {
|
||||
const res = await axios.get(`${protocol}//${hostname}/robots.txt`);
|
||||
const res = await httpGet(`${protocol}//${hostname}/robots.txt`);
|
||||
const parsed = parseRobotsTxt(res.data || '');
|
||||
return parsed.robots.length
|
||||
? parsed
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import puppeteer from 'puppeteer-core';
|
||||
import chromium from 'chrome-aws-lambda';
|
||||
import chromium from '@sparticuz/chromium';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { execFile } from 'child_process';
|
||||
import { promises as fs } from 'fs';
|
||||
@@ -46,9 +46,9 @@ const puppeteerScreenshot = async (targetUrl) => {
|
||||
browser = await puppeteer.launch({
|
||||
args: [...chromium.args, '--no-sandbox'],
|
||||
defaultViewport: { width: 800, height: 600 },
|
||||
executablePath: process.env.CHROME_PATH || '/usr/bin/chromium',
|
||||
executablePath: process.env.CHROME_PATH || await chromium.executablePath(),
|
||||
headless: true,
|
||||
ignoreHTTPSErrors: true,
|
||||
acceptInsecureCerts: true,
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { URL } from 'url';
|
||||
import followRedirects from 'follow-redirects';
|
||||
import middleware from './_common/middleware.js';
|
||||
|
||||
const { https } = followRedirects;
|
||||
import { httpGet } from './_common/http.js';
|
||||
|
||||
const SECURITY_TXT_PATHS = [
|
||||
'/security.txt',
|
||||
@@ -71,26 +69,15 @@ const securityTxtHandler = async (urlParam) => {
|
||||
return { isPresent: false };
|
||||
};
|
||||
|
||||
async function fetchSecurityTxt(baseURL, path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(path, baseURL);
|
||||
https.get(url.toString(), { headers: { 'User-Agent': 'curl/8.0.0' } }, (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
// Returns the file body when the path 200s, else null so the next path is tried
|
||||
const fetchSecurityTxt = async (baseURL, path) => {
|
||||
const url = new URL(path, baseURL);
|
||||
const res = await httpGet(url.toString(), {
|
||||
headers: { 'User-Agent': 'curl/8.0.0' },
|
||||
validateStatus: () => true,
|
||||
});
|
||||
}
|
||||
return res.status === 200 ? res.data : null;
|
||||
};
|
||||
|
||||
export const handler = middleware(securityTxtHandler);
|
||||
export default handler;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { requireEnv, upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -9,7 +9,7 @@ const shodanHandler = async (url) => {
|
||||
if (auth.skipped) return auth;
|
||||
const { hostname } = parseTarget(url);
|
||||
try {
|
||||
const res = await axios.get(
|
||||
const res = await httpGet(
|
||||
`https://api.shodan.io/shodan/host/${hostname}?key=${auth.value}`,
|
||||
{ timeout: 8000 },
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import xml2js from 'xml2js';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const HARD_TIMEOUT = 5000;
|
||||
@@ -9,13 +9,13 @@ const MAX_CHILD_SITEMAPS = 25;
|
||||
|
||||
// Fetch and parse a sitemap XML
|
||||
const fetchSitemap = async (sitemapUrl) => {
|
||||
const res = await axios.get(sitemapUrl, { timeout: HARD_TIMEOUT });
|
||||
const res = await httpGet(sitemapUrl, { timeout: HARD_TIMEOUT });
|
||||
return new xml2js.Parser().parseStringPromise(res.data);
|
||||
};
|
||||
|
||||
// Pull a Sitemap: line out of robots.txt
|
||||
const findSitemapInRobots = async (baseUrl) => {
|
||||
const robots = await axios.get(`${baseUrl}/robots.txt`, { timeout: HARD_TIMEOUT });
|
||||
const robots = await httpGet(`${baseUrl}/robots.txt`, { timeout: HARD_TIMEOUT });
|
||||
for (const line of robots.data.split('\n')) {
|
||||
if (line.toLowerCase().startsWith('sitemap:')) {
|
||||
return line.split(/\s+/)[1]?.trim() || null;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
const socialTagsHandler = async (url) => {
|
||||
let response;
|
||||
try {
|
||||
response = await axios.get(url);
|
||||
response = await httpGet(url);
|
||||
} catch (error) {
|
||||
return upstreamError(error, 'Social tags fetch');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import xml2js from 'xml2js';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpPost } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { requireEnv, upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ const safeBrowsing = async (url) => {
|
||||
const auth = requireEnv('GOOGLE_CLOUD_API_KEY', 'Google Safe Browsing');
|
||||
if (auth.skipped) return auth;
|
||||
try {
|
||||
const res = await axios.post(
|
||||
const res = await httpPost(
|
||||
`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${auth.value}`,
|
||||
{
|
||||
threatInfo: {
|
||||
@@ -33,7 +33,7 @@ const safeBrowsing = async (url) => {
|
||||
const urlHaus = async (url) => {
|
||||
const { hostname } = parseTarget(url);
|
||||
try {
|
||||
const res = await axios.post(
|
||||
const res = await httpPost(
|
||||
'https://urlhaus-api.abuse.ch/v1/host/',
|
||||
`host=${hostname}`,
|
||||
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
|
||||
@@ -47,7 +47,7 @@ const urlHaus = async (url) => {
|
||||
const phishTank = async (url) => {
|
||||
try {
|
||||
const encoded = Buffer.from(url).toString('base64');
|
||||
const res = await axios.post(
|
||||
const res = await httpPost(
|
||||
`https://checkurl.phishtank.com/checkurl/?url=${encoded}`,
|
||||
null,
|
||||
{ headers: { 'User-Agent': 'phishtank/web-check' }, timeout: 3000 },
|
||||
@@ -63,7 +63,7 @@ const cloudmersive = async (url) => {
|
||||
const auth = requireEnv('CLOUDMERSIVE_API_KEY', 'Cloudmersive');
|
||||
if (auth.skipped) return auth;
|
||||
try {
|
||||
const res = await axios.post(
|
||||
const res = await httpPost(
|
||||
'https://api.cloudmersive.com/virus/scan/website',
|
||||
`Url=${encodeURIComponent(url)}`,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -9,7 +9,7 @@ const SSL_LABS = 'https://api.ssllabs.com/api/v3/analyze';
|
||||
const tlsLabsHandler = async (url) => {
|
||||
const { hostname } = parseTarget(url);
|
||||
try {
|
||||
const res = await axios.get(SSL_LABS, {
|
||||
const res = await httpGet(SSL_LABS, {
|
||||
params: { host: hostname, fromCache: 'on', maxAge: 24, all: 'done' },
|
||||
timeout: 8000,
|
||||
headers: { 'User-Agent': 'web-check (https://web-check.xyz)' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpGet } from './_common/http.js';
|
||||
import { parseTarget } from './_common/parse-target.js';
|
||||
import { requireEnv, upstreamError } from './_common/upstream.js';
|
||||
|
||||
@@ -9,7 +9,7 @@ const whoisProHandler = async (url) => {
|
||||
const { hostname } = parseTarget(url);
|
||||
let data;
|
||||
try {
|
||||
const res = await axios.get(
|
||||
const res = await httpGet(
|
||||
`https://api.whoapi.com/?domain=${hostname}&r=whois&apikey=${auth.value}`,
|
||||
{ timeout: 8000 },
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import net from 'net';
|
||||
import psl from 'psl';
|
||||
import axios from 'axios';
|
||||
import middleware from './_common/middleware.js';
|
||||
import { httpPost } from './_common/http.js';
|
||||
import { createLogger } from './_common/logger.js';
|
||||
|
||||
const log = createLogger('whois');
|
||||
@@ -71,8 +71,8 @@ const fetchFromInternic = async (hostname) => new Promise((resolve, reject) => {
|
||||
|
||||
const fetchFromMyAPI = async (hostname) => {
|
||||
try {
|
||||
const response = await axios.post('https://whois-api-zeta.vercel.app/', {
|
||||
domain: hostname
|
||||
const response = await httpPost('https://whois-api-zeta.vercel.app/', {
|
||||
domain: hostname,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import partytown from '@astrojs/partytown';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
// Adapters
|
||||
import vercelAdapter from '@astrojs/vercel/serverless';
|
||||
import vercelAdapter from '@astrojs/vercel';
|
||||
import netlifyAdapter from '@astrojs/netlify';
|
||||
import nodeAdapter from '@astrojs/node';
|
||||
import cloudflareAdapter from '@astrojs/cloudflare';
|
||||
@@ -22,8 +22,8 @@ const unwrapEnvVar = (varName, fallbackValue) => {
|
||||
// Determine the deploy target (vercel, netlify, cloudflare, node)
|
||||
const deployTarget = unwrapEnvVar('PLATFORM', 'node').toLowerCase();
|
||||
|
||||
// Determine the output mode (server, hybrid or static)
|
||||
const output = unwrapEnvVar('OUTPUT', 'hybrid');
|
||||
// Determine the output mode (static or server). Mixed prerender supported in static mode
|
||||
const output = unwrapEnvVar('OUTPUT', 'static');
|
||||
|
||||
// The FQDN of where the site is hosted (used for sitemaps & canonical URLs)
|
||||
const site = unwrapEnvVar('SITE_URL', 'https://web-check.xyz');
|
||||
|
||||
86
package.json
86
package.json
@@ -4,11 +4,10 @@
|
||||
"version": "2.0.2",
|
||||
"homepage": "https://web-check.xyz",
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server",
|
||||
"start-pm": "pm2 start server.js -i max",
|
||||
"build": "astro check && astro build",
|
||||
"dev:vercel": "PLATFORM='vercel' npx vercel dev",
|
||||
"dev:netlify": "PLATFORM='netlify' npx netlify dev",
|
||||
@@ -17,58 +16,63 @@
|
||||
"dev": "concurrently -c magenta,cyan -n backend,frontend 'yarn dev:api' 'yarn dev:astro'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.10",
|
||||
"@astrojs/react": "^3.6.3",
|
||||
"@astrojs/check": "^0.9.9",
|
||||
"@astrojs/react": "^5.0.4",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@fortawesome/svelte-fontawesome": "^0.2.4",
|
||||
"@types/react": "^18.3.28",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"astro": "^4.16.19",
|
||||
"axios": "^1.16.0",
|
||||
"@sparticuz/chromium": "^148.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"astro": "^6.2.2",
|
||||
"cheerio": "^1.2.0",
|
||||
"chrome-aws-lambda": "^10.1.0",
|
||||
"chromium": "^3.0.3",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"csv-parser": "^3.2.0",
|
||||
"dotenv": "^16.6.1",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^7.5.1",
|
||||
"framer-motion": "^11.18.2",
|
||||
"got": "^14.6.6",
|
||||
"pm2": "^5.4.3",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.5.0",
|
||||
"framer-motion": "^12.38.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"psl": "^1.15.0",
|
||||
"puppeteer": "^22.15.0",
|
||||
"puppeteer-core": "^22.15.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"puppeteer": "^24.42.0",
|
||||
"puppeteer-core": "^24.42.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-router-dom": "^6.30.3",
|
||||
"react-router-dom": "^7.14.2",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"react-toastify": "^10.0.6",
|
||||
"recharts": "^2.15.4",
|
||||
"svelte": "^4.2.20",
|
||||
"typescript": "^5.9.3",
|
||||
"unzipper": "^0.11.6",
|
||||
"url-parse": "^1.5.10",
|
||||
"react-toastify": "^11.1.0",
|
||||
"recharts": "^3.8.1",
|
||||
"svelte": "^5.55.5",
|
||||
"typescript": "^6.0.3",
|
||||
"wappalyzer": "^6.10.66",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"tar-fs": "^2.1.4",
|
||||
"basic-ftp": "^5.2.0",
|
||||
"picomatch": "^2.3.2",
|
||||
"systeminformation": "^5.31.0",
|
||||
"d3-color": "^3.1.0",
|
||||
"braces": "^3.0.3",
|
||||
"wappalyzer/**/ws": "^8.17.1",
|
||||
"@vercel/routing-utils/path-to-regexp": "^6.3.0",
|
||||
"uuid": "^14.0.0",
|
||||
"yaml": "^2.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/cloudflare": "^10.4.2",
|
||||
"@astrojs/netlify": "^5.5.4",
|
||||
"@astrojs/node": "^8.3.4",
|
||||
"@astrojs/cloudflare": "^13.3.1",
|
||||
"@astrojs/netlify": "^7.0.8",
|
||||
"@astrojs/node": "^10.0.6",
|
||||
"@astrojs/partytown": "^2.1.7",
|
||||
"@astrojs/sitemap": "~3.4.1",
|
||||
"@astrojs/svelte": "^5.7.3",
|
||||
"@astrojs/sitemap": "^3.7.2",
|
||||
"@astrojs/svelte": "^8.1.0",
|
||||
"@astrojs/ts-plugin": "^1.10.7",
|
||||
"@astrojs/vercel": "^7.8.2",
|
||||
"concurrently": "^8.2.2",
|
||||
"@astrojs/vercel": "^10.0.6",
|
||||
"concurrently": "^9.2.1",
|
||||
"nodemon": "^3.1.14",
|
||||
"sass": "^1.99.0"
|
||||
},
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#9fef00" />
|
||||
<meta
|
||||
name="description"
|
||||
content="All-in-one OSINT tool, for quickly checking a websites data"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Web Check</title>
|
||||
<script defer data-domain="web-check.as93.net" src="https://no-track.as93.net/js/script.js"></script>
|
||||
|
||||
<!-- OpenGraph Social Tags -->
|
||||
<meta property="og:title" content="Web Check">
|
||||
<meta property="og:description" content="All-in-one Website OSINT Scanner">
|
||||
<meta property="og:image" content="/banner.png">
|
||||
<meta property="og:url" content="https://web-check.xyz">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- DarkReader -->
|
||||
<meta name="darkreader-lock">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<b>Welcome to Web-Check, the free and open source tool for viewing all available information about a website.</b><br />
|
||||
Get started by entering a URL, and clicking the "Scan" button, or view the code and docs
|
||||
on <a href="https://github.com/lissy93/web-check">GitHub</a>.<br />
|
||||
<small>Licensed under MIT, ©️ <a href="https://aliciasykes.com">Alicia Sykes</a> 2023.</small>
|
||||
<br /><br />
|
||||
JavaScript is required to continue, please enable it in your browser.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
11
server.js
11
server.js
@@ -5,7 +5,6 @@ import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import historyApiFallback from 'connect-history-api-fallback';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
@@ -56,7 +55,7 @@ const makeLimiterResponseMsg = (retryAfter) => {
|
||||
// Create rate limiters for each time frame
|
||||
const limiters = limits.map(limit => rateLimit({
|
||||
windowMs: limit.timeFrame * 1000,
|
||||
max: limit.max,
|
||||
limit: limit.max,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: makeLimiterResponseMsg(limit.messageTime) }
|
||||
@@ -181,14 +180,6 @@ if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle SPA routing
|
||||
app.use(historyApiFallback({
|
||||
rewrites: [
|
||||
{ from: new RegExp(`^${API_DIR}/.*$`), to: (context) => context.parsedUrl.path },
|
||||
{ from: /^.*$/, to: '/index.html' }
|
||||
]
|
||||
}));
|
||||
|
||||
// Anything left unhandled (which isn't an API endpoint), return a 404
|
||||
app.use((req, res, next) => {
|
||||
if (!req.path.startsWith(`${API_DIR}/`)) {
|
||||
|
||||
@@ -107,8 +107,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/typography.scss';
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/typography.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
|
||||
.input-container {
|
||||
position: relative;
|
||||
|
||||
@@ -21,7 +21,7 @@ const { links } = Astro.props;
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.button-wrap {
|
||||
margin: 3rem auto;
|
||||
display: flex;
|
||||
|
||||
@@ -63,7 +63,7 @@ import Screenshots from "./Screenshots.astro"
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
@@ -13,8 +13,8 @@ const durationVariance = 1; // Variance for randomization to append to travel in
|
||||
const delayBase = 500; // Base delay for meteor to respawn in milliseconds
|
||||
const delayVariance = 1500; // Variance for randomization to append to respawn in milliseconds
|
||||
const tailDuration = 0.25; // Duration for meteor tail to retract in seconds
|
||||
const headEasing = [0.8, 0.6, 1, 1]; // Easing for meteor head
|
||||
const tailEasing = [0.5, 0.6, 0.6, 1]; // Easing for meteor tail
|
||||
const headEasing = [0.8, 0.6, 1, 1] as const; // Easing for meteor head
|
||||
const tailEasing = [0.5, 0.6, 0.6, 1] as const; // Easing for meteor tail
|
||||
|
||||
const MeteorContainer = styled(motion.div)`
|
||||
position: absolute;
|
||||
@@ -135,7 +135,7 @@ const WebCheckHomeBackground = () => {
|
||||
<StyledRect fill="url(#dot-pattern)" />
|
||||
</StyledSvg>
|
||||
|
||||
{meteors.map(({ id, column, startRow, endRow, duration, tailVisible, animationStage, opacity }) => {
|
||||
{meteors.map(({ id, column, startRow, endRow, duration, tailVisible, animationStage }) => {
|
||||
return (
|
||||
<MeteorContainer
|
||||
key={id}
|
||||
|
||||
@@ -54,7 +54,7 @@ const screenshots = [
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@@ -23,7 +23,7 @@ const ctaImageSrc = 'https://i.ibb.co/5jJ4bzZ/terminal-trove-cta.png';
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.sponsored-but-dont-block {
|
||||
background: var(--text-color);
|
||||
color: var(--background);
|
||||
|
||||
@@ -27,7 +27,7 @@ const currentYear = new Date().getFullYear();
|
||||
</footer>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
footer {
|
||||
margin-top: 1rem;
|
||||
background: var(--primary);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { ViewTransitions } from 'astro:transitions'
|
||||
import { ClientRouter } from 'astro:transitions'
|
||||
import MetaTags from '@layouts/MetaTags.astro';
|
||||
|
||||
import '@styles/typography.scss';
|
||||
@@ -23,7 +23,7 @@ interface Props {
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<ViewTransitions />
|
||||
<ClientRouter />
|
||||
<MetaTags {...Astro.props } />
|
||||
<slot name="head" />
|
||||
<link href="/fonts/Hubot-Sans/Hubot-Sans.woff2"
|
||||
|
||||
@@ -92,13 +92,13 @@ const makeBreadcrumbs = () => {
|
||||
|
||||
<!-- Non-tracking hit counter -->
|
||||
{analytics.enable && (
|
||||
<script defer data-domain={analytics.domain} src={analytics.script}></script>
|
||||
<script is:inline defer data-domain={analytics.domain} src={analytics.script}></script>
|
||||
)}
|
||||
|
||||
<!-- Schema.org markup for Google -->
|
||||
{breadcrumbs && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(makeBreadcrumbs())} />
|
||||
<script is:inline type="application/ld+json" set:html={JSON.stringify(makeBreadcrumbs())} />
|
||||
)}
|
||||
{customSchemaJson && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(customSchemaJson)} />
|
||||
<script is:inline type="application/ld+json" set:html={JSON.stringify(customSchemaJson)} />
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import BaseLayout from '@layouts/Base.astro';
|
||||
import NavBar from '@components/scafold/Nav.astro';
|
||||
import Footer from '@components/scafold/Footer.astro';
|
||||
import Icon from '@components/molecules/Icon.svelte';
|
||||
|
||||
---
|
||||
|
||||
@@ -33,7 +32,7 @@ import Icon from '@components/molecules/Icon.svelte';
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.web-check-page {
|
||||
padding-top: 2rem;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -10,18 +10,11 @@ const isBossServer = import.meta.env.BOSS_SERVER === true;
|
||||
|
||||
const disableEverything = import.meta.env.VITE_DISABLE_EVERYTHING === true;
|
||||
|
||||
// Redirect strait to /check or /check/:url if running as self-hosted instance
|
||||
if (!isBossServer) {
|
||||
const searchUrl = new URLSearchParams(new URL(Astro.request.url).search).get('url');
|
||||
const redirectUrl = searchUrl ? `/check/${encodeURIComponent(searchUrl)}` : '/check';
|
||||
Astro.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<Fragment slot="head">
|
||||
{!isBossServer && (<meta http-equiv="refresh" content="0; url=/check" />)}
|
||||
<!-- {!isBossServer && (<meta http-equiv="refresh" content="0; url=/check" />)} -->
|
||||
</Fragment>
|
||||
{ disableEverything && <TempDisabled />}
|
||||
<main>
|
||||
@@ -33,7 +26,7 @@ if (!isBossServer) {
|
||||
</BaseLayout>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
main {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
|
||||
@@ -91,7 +91,7 @@ const cardData = [
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.web-check-page {
|
||||
padding-top: 2rem;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import BaseLayout from '@layouts/Base.astro';
|
||||
import NavBar from '@components/scafold/Nav.astro';
|
||||
import Footer from '@components/scafold/Footer.astro';
|
||||
import Icon from '@components/molecules/Icon.svelte';
|
||||
|
||||
---
|
||||
|
||||
@@ -90,7 +89,7 @@ import Icon from '@components/molecules/Icon.svelte';
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
.web-check-page {
|
||||
padding-top: 2rem;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -41,7 +41,7 @@ import Footer from '@components/scafold/Footer.astro';
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@styles/global.scss';
|
||||
@use '@styles/global.scss' as *;
|
||||
main {
|
||||
height: 100vh;
|
||||
padding-top: 2rem;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Global Stylesheet */
|
||||
@import './colors.scss';
|
||||
@import './media-queries.scss';
|
||||
@import './typography.scss';
|
||||
@forward './colors';
|
||||
@forward './media-queries';
|
||||
@forward './typography';
|
||||
|
||||
/* CSS Reset - Normalize dimensions and spacing across browsers */
|
||||
*,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { StyledCard } from 'web-check-live/components/Form/Card';
|
||||
import Heading from 'web-check-live/components/Form/Heading';
|
||||
|
||||
@@ -156,7 +156,7 @@ export const ExpandableRow = (props: RowProps) => {
|
||||
</span>
|
||||
{ row.plaintext && <PlainText>{row.plaintext}</PlainText> }
|
||||
{ row.listResults && (<List>
|
||||
{row.listResults.map((listItem: string, listIndex: number) => (
|
||||
{row.listResults.map((listItem: string) => (
|
||||
<li key={listItem}>{snip(listItem)}</li>
|
||||
))}
|
||||
</List>)}
|
||||
|
||||
@@ -64,7 +64,7 @@ const MalwareCard = (props: {data: any, title: string, actionButtons: any }): JS
|
||||
{urlHaus.urls && (
|
||||
<Expandable>
|
||||
<summary>Expand Results</summary>
|
||||
{ urlHaus.urls.map((urlResult: any, index: number) => {
|
||||
{ urlHaus.urls.map((urlResult: any) => {
|
||||
const rows = [
|
||||
{ lbl: 'ID', val: urlResult.id },
|
||||
{ lbl: 'Status', val: urlResult.url_status },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component, type ErrorInfo, type ReactNode } from "react";
|
||||
import { Component, type ErrorInfo, type ReactNode } from "react";
|
||||
import styled from '@emotion/styled';
|
||||
import Card from 'web-check-live/components/Form/Card';
|
||||
import Heading from 'web-check-live/components/Form/Heading';
|
||||
|
||||
@@ -3,18 +3,11 @@ interface Props {
|
||||
width: number,
|
||||
};
|
||||
|
||||
const Flag = ({ countryCode, width }: Props): JSX.Element => {
|
||||
|
||||
const getFlagUrl = (code: string, w: number = 64) => {
|
||||
const protocol = 'https';
|
||||
const cdn = 'flagcdn.com';
|
||||
const dimensions = `${width}x${width * 0.75}`;
|
||||
const country = countryCode.toLowerCase();
|
||||
const ext = 'png';
|
||||
return `${protocol}://${cdn}/${dimensions}/${country}.${ext}`;
|
||||
};
|
||||
|
||||
return (<img src={getFlagUrl(countryCode, width)} alt={countryCode} />);
|
||||
const Flag = ({ countryCode, width }: Props) => {
|
||||
const dimensions = `${width}x${width * 0.75}`;
|
||||
const country = countryCode.toLowerCase();
|
||||
const src = `https://flagcdn.com/${dimensions}/${country}.png`;
|
||||
return (<img src={src} alt={countryCode} />);
|
||||
}
|
||||
|
||||
export default Flag;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import colors from 'web-check-live/styles/colors';
|
||||
import { Card } from 'web-check-live/components/Form/Card';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { StaticRouter } from "react-router-dom/server";
|
||||
import { BrowserRouter, StaticRouter } from "react-router-dom";
|
||||
import App from "./App.tsx";
|
||||
|
||||
export default ({ pathname }: { pathname: string }) => (
|
||||
|
||||
9
src/web-check-live/typings/jsx.d.ts
vendored
Normal file
9
src/web-check-live/typings/jsx.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { JSX as ReactJSX } from 'react'
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
type Element = ReactJSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
@@ -303,7 +303,7 @@ const About = (): JSX.Element => {
|
||||
|
||||
<Heading as="h2" size="medium" color={colors.primary}>Support Us</Heading>
|
||||
<Section>
|
||||
{supportUs.map((para, index: number) => (<p dangerouslySetInnerHTML={{__html: para}} />))}
|
||||
{supportUs.map((para) => (<p dangerouslySetInnerHTML={{__html: para}} />))}
|
||||
</Section>
|
||||
|
||||
<Heading as="h2" size="medium" color={colors.primary}>Terms & Info</Heading>
|
||||
@@ -319,7 +319,7 @@ const About = (): JSX.Element => {
|
||||
<hr />
|
||||
<Heading as="h3" size="small" color={colors.primary}>Fair Use</Heading>
|
||||
<ul>
|
||||
{fairUse.map((para, index: number) => (<li>{para}</li>))}
|
||||
{fairUse.map((para) => (<li>{para}</li>))}
|
||||
</ul>
|
||||
<hr />
|
||||
<Heading as="h3" size="small" color={colors.primary}>Privacy</Heading>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { type ChangeEvent, type FormEvent, useState, useEffect } from 'react';
|
||||
import { type ChangeEvent, type SyntheticEvent, useState, useEffect } from 'react';
|
||||
import { Link, useNavigate, useLocation, type NavigateOptions } from 'react-router-dom';
|
||||
|
||||
import Heading from 'web-check-live/components/Form/Heading';
|
||||
@@ -190,7 +190,7 @@ const Home = (): JSX.Element => {
|
||||
}
|
||||
};
|
||||
|
||||
const formSubmitEvent = (event: FormEvent<HTMLFormElement>) => {
|
||||
const formSubmitEvent = (event: SyntheticEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user