import { useEffect, useState } from 'react'; import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js'; const gifFrameDb = localForageLru.createInstance({ name: '5chanGifFrames', size: 500 }); const failedUrls = new Set(); const getCachedGifFrame = async (url: string): Promise => { return await gifFrameDb.getItem(url); }; const setCachedGifFrame = async (url: string, frameUrl: string): Promise => { await gifFrameDb.setItem(url, frameUrl); }; export const fetchImage = (url: string): Promise => { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open('GET', url, true); request.responseType = 'arraybuffer'; request.onloadend = () => { if (request.response !== undefined && (request.status === 200 || request.status === 304)) { resolve(request.response); } else { reject(new Error(`XMLHttpRequest, ${request.statusText}`)); } }; request.send(); }); }; export const readImage = (file: File): Promise => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => { resolve(reader.result as ArrayBuffer); }; reader.readAsArrayBuffer(file); }); }; const parseGif = async (buf: ArrayBuffer): Promise => { const image = new Image(); await new Promise((resolve) => { image.src = URL.createObjectURL(new Blob([buf])); image.onload = resolve; }); const canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; const ctx = canvas.getContext('2d'); if (ctx === null) throw new Error('Canvas Context null'); ctx.drawImage(image, 0, 0, image.width, image.height); return await new Promise((resolve, reject) => canvas.toBlob((blob) => { if (blob === null) { reject('Canvas Blob null'); } else { resolve(blob); } }), ); }; const useFetchGifFirstFrame = (url: string | undefined) => { const [frameUrl, setFrameUrl] = useState(null); useEffect(() => { if (!url) { setFrameUrl(null); return; } let isActive = true; const fetchFrame = async () => { if (failedUrls.has(url)) { if (isActive) setFrameUrl(null); return; } try { const cachedFrame = await getCachedGifFrame(url); if (cachedFrame) { try { const response = await fetch(cachedFrame); if (response.ok) { if (isActive) setFrameUrl(cachedFrame); return; } } catch {} } const blob = typeof url === 'string' ? await parseGif(await fetchImage(url)) : await parseGif(await readImage(url as File)); const objectUrl = URL.createObjectURL(blob); if (isActive) { setFrameUrl(objectUrl); await setCachedGifFrame(url, objectUrl); } } catch (error) { failedUrls.add(url); console.error('Failed to load GIF frame:', error); if (isActive) setFrameUrl(null); } }; fetchFrame(); return () => { isActive = false; }; }, [url]); return frameUrl; }; export default useFetchGifFirstFrame;