// Image URL validation utilities for the ImageInput component import { apiClient } from './api'; export interface ImageValidationResult { isValid: boolean; error?: string; detectedType?: string; filename?: string; isCorsBlocked?: boolean; base64Data?: string; } export interface ImageInputMode { type: 'file' | 'url' | null; value?: File | string; } // Supported image MIME types and extensions export const IMAGE_MIME_TYPES = [ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/svg+xml', 'image/tiff' ] as const; export const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'tiff'] as const; export type ImageExtension = typeof IMAGE_EXTENSIONS[number]; // Extract extension from filename or URL export function extractExtension(filenameOrUrl: string): string | null { // Remove query parameters and fragments const cleanUrl = filenameOrUrl.split('?')[0].split('#')[0]; // Get the last part after dot const extension = cleanUrl.split('.').pop()?.toLowerCase(); return extension || null; } // Check if URL contains image extension (more flexible than just checking the end) export function hasImageExtension(url: string): boolean { const extension = extractExtension(url); return extension ? isValidImageExtension(extension) : false; } // Check if extension is a valid image extension export function isValidImageExtension(extension: string): extension is ImageExtension { return IMAGE_EXTENSIONS.includes(extension as ImageExtension); } // Get MIME type from extension export function getMimeTypeFromExtension(extension: string): string { const extToMime: Record = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'webp': 'image/webp', 'bmp': 'image/bmp', 'svg': 'image/svg+xml', 'tiff': 'image/tiff' }; return extToMime[extension as ImageExtension] || 'image/unknown'; } // Validate file object export function validateFile(file: File): ImageValidationResult { // Check if file is empty if (!file || file.size === 0) { return { isValid: false, error: 'File is empty or invalid' }; } // Check file size (max 10MB) const maxSize = 10 * 1024 * 1024; // 10MB if (file.size > maxSize) { return { isValid: false, error: `File size (${(file.size / 1024 / 1024).toFixed(1)}MB) exceeds 10MB limit` }; } // Check MIME type if (!IMAGE_MIME_TYPES.includes(file.type as any)) { return { isValid: false, error: `File type '${file.type}' is not supported. Supported formats: ${IMAGE_EXTENSIONS.join(', ')}` }; } return { isValid: true, detectedType: file.type, filename: file.name }; } // Basic URL validation without network requests export function validateUrlFormat(url: string): ImageValidationResult { try { new URL(url); } catch { return { isValid: false, error: 'Invalid URL format' }; } // Check if it has a valid image extension if (!hasImageExtension(url)) { return { isValid: false, error: 'URL does not point to a supported image format. Supported formats: ' + IMAGE_EXTENSIONS.join(', ') }; } // Basic validation passed - return the extension-based result const extension = extractExtension(url); return { isValid: true, detectedType: extension ? getMimeTypeFromExtension(extension) : undefined, filename: url.split('/').pop() || url }; } // Validate image URL using server-side download endpoint export async function validateImageUrlWithBase64(url: string): Promise { // First validate URL format and extension const formatValidation = validateUrlFormat(url); if (!formatValidation.isValid) { return formatValidation; } try { // Use server-side download endpoint to avoid CORS issues const result = await apiClient.downloadImageFromUrl(url); return { isValid: true, detectedType: result.mimeType, filename: result.filename, base64Data: result.base64Data }; } catch (error) { return { isValid: false, error: `Failed to download image from URL: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } // Validate image input (file or URL) with base64 conversion export async function validateImageInput( input: File | string ): Promise { if (input instanceof File) { return validateFile(input); } else { return await validateImageUrlWithBase64(input); } } // Get display name for image input export function getImageDisplayName(input: File | string): string { if (input instanceof File) { return input.name; } else { const url = new URL(input); return url.pathname.split('/').pop() || input; } } // Convert file to data URL for preview export function fileToDataURL(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(file); }); }