site-library/src/base/url.ts
2025-03-28 17:12:22 +01:00

114 lines
3.0 KiB
TypeScript

import puppeteer from 'puppeteer';
export interface UrlCheckResult {
valid: boolean;
error?: string;
}
export interface UrlChecker {
check(url: string, timeout?: number): Promise<UrlCheckResult>;
}
export class PuppeteerUrlChecker implements UrlChecker {
private readonly defaultTimeout: number = 10000;
private readonly userAgent: string = 'Mozilla/5.0 (compatible; PolymechBot/1.0; +http://polymech.org)';
async check(url: string, timeout: number = this.defaultTimeout): Promise<UrlCheckResult> {
let browser;
try {
browser = await puppeteer.launch({
headless: 'new' as any,
args: ['--ignore-certificate-errors', '--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setUserAgent(this.userAgent);
await page.setDefaultNavigationTimeout(timeout);
const response = await page.goto(url, {
waitUntil: 'networkidle0',
timeout: timeout
});
if (!response) {
return { valid: false, error: 'No response received' };
}
const status = response.status();
if (status >= 200 && status < 400) {
return { valid: true };
}
return {
valid: false,
error: `HTTP ${status}: ${response.statusText()}`
};
} catch (error) {
if (error instanceof Error) {
return {
valid: false,
error: error.message
};
}
return {
valid: false,
error: 'Unknown error occurred'
};
} finally {
if (browser) {
await browser.close();
}
}
}
}
export class FetchUrlChecker implements UrlChecker {
private readonly defaultTimeout: number = 10000;
private readonly userAgent: string = 'Mozilla/5.0 (compatible; PolymechBot/1.0; +http://polymech.org)';
async check(url: string, timeout: number = this.defaultTimeout): Promise<UrlCheckResult> {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
signal: controller.signal,
redirect: 'follow',
headers: {
'User-Agent': this.userAgent
}
});
clearTimeout(timeoutId);
if (!response.ok) {
return {
valid: false,
error: `HTTP ${response.status}: ${response.statusText}`
};
}
return { valid: true };
} catch (error) {
if (error instanceof Error) {
return {
valid: false,
error: error.message
};
}
return {
valid: false,
error: 'Unknown error occurred'
};
}
}
}
// Default checker instance
export const defaultChecker: UrlChecker = new PuppeteerUrlChecker();
// Export a convenience function
export async function checkUrl(url: string, timeout?: number): Promise<UrlCheckResult> {
return defaultChecker.check(url, timeout);
}