generated from polymech/site-template
114 lines
3.0 KiB
TypeScript
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);
|
|
}
|