/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { getErrorMessage, isNodeError } from './errors.js'; import { URL } from 'url'; const PRIVATE_IP_RANGES = [ /^10\./, /^127\./, /^172\.(1[6-9]|2[0-9]|3[0-1])\./, /^192\.168\./, /^::1$/, /^fc00:/, /^fe80:/, ]; export class FetchError extends Error { constructor( message: string, public code?: string, ) { super(message); this.name = 'FetchError'; } } export function isPrivateIp(url: string): boolean { try { const hostname = new URL(url).hostname; return PRIVATE_IP_RANGES.some((range) => range.test(hostname)); } catch (_e) { return false; } } export async function fetchWithTimeout( url: string, timeout: number, ): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { signal: controller.signal }); return response; } catch (error) { if (isNodeError(error) && error.code === 'ABORT_ERR') { throw new FetchError(`Request timed out after ${timeout}ms`, 'ETIMEDOUT'); } throw new FetchError(getErrorMessage(error)); } finally { clearTimeout(timeoutId); } }