export const ALLOWED_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] as const; export type AllowedImageMimeType = (typeof ALLOWED_IMAGE_MIME_TYPES)[number]; const EXT_BY_MIME: Record = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp', 'image/gif': 'gif', }; export function normalizeImageMime(value: string): string { if (value === 'image/jpg' || value === 'image/pjpeg') return 'image/jpeg'; return value; } export function isAllowedImageType(value: string): value is AllowedImageMimeType { return (ALLOWED_IMAGE_MIME_TYPES as readonly string[]).includes(value); } export function detectImageMime(buffer: Uint8Array): AllowedImageMimeType | null { // JPEG if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xd8) { return 'image/jpeg'; } // PNG if ( buffer.length >= 8 && buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47 && buffer[4] === 0x0d && buffer[5] === 0x0a && buffer[6] === 0x1a && buffer[7] === 0x0a ) { return 'image/png'; } // GIF87a/GIF89a if ( buffer.length >= 6 && buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38 && (buffer[4] === 0x37 || buffer[4] === 0x39) && buffer[5] === 0x61 ) { return 'image/gif'; } // WEBP: "RIFF"...."WEBP" if ( buffer.length >= 12 && buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 && buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50 ) { return 'image/webp'; } return null; } export function getImageExtension(mime: AllowedImageMimeType): string { return EXT_BY_MIME[mime]; } export function firstBytesHex(buffer: Uint8Array, length = 16): string { return Array.from(buffer.slice(0, length)) .map((byte) => byte.toString(16).padStart(2, '0')) .join(' '); }