xref: /expo/packages/expo-image/src/web/hooks.ts (revision cf6af4e9)
1import { useEffect, useMemo, useState } from 'react';
2
3import { OnErrorEvent } from './ImageWrapper.types';
4import { ImageNativeProps, ImageSource } from '../Image.types';
5import { useBlurhash } from '../utils/blurhash/useBlurhash';
6import { isThumbhashString } from '../utils/resolveSources';
7import { thumbHashStringToDataURL } from '../utils/thumbhash/thumbhash';
8
9export function useThumbhash(source: ImageSource | null | undefined) {
10  const isThumbhash = isThumbhashString(source?.uri || '');
11  const strippedThumbhashString = source?.uri?.replace(/thumbhash:\//, '') ?? '';
12  const thumbhashSource = useMemo(
13    () => (isThumbhash ? { uri: thumbHashStringToDataURL(strippedThumbhashString) } : null),
14    [strippedThumbhashString, isThumbhash]
15  );
16  return thumbhashSource;
17}
18
19export function useImageHashes(source: ImageSource | null | undefined) {
20  const thumbhash = useThumbhash(source);
21  const blurhash = useBlurhash(source);
22  return useMemo(
23    () => ({
24      resolvedSource: blurhash ?? thumbhash ?? source,
25      isImageHash: !!blurhash || !!thumbhash,
26    }),
27    [blurhash, thumbhash]
28  );
29}
30
31export function useHeaders(
32  source: ImageSource | null | undefined,
33  cachePolicy: ImageNativeProps['cachePolicy'],
34  onError?: OnErrorEvent[]
35): ImageSource | null | undefined {
36  const [objectURL, setObjectURL] = useState<string | null>(null);
37  useEffect(() => {
38    (async () => {
39      if (!source?.headers || !source.uri) {
40        return;
41      }
42      try {
43        const result = await fetch(source.uri, {
44          headers: source.headers,
45          cache: cachePolicy === 'none' ? 'no-cache' : 'default',
46          redirect: 'follow',
47        });
48        if (!result.ok) {
49          throw new Error(`Failed to fetch image: ${result.status} ${result.statusText}`);
50        }
51        const blob = await result.blob();
52        setObjectURL((prevObjURL) => {
53          if (prevObjURL) {
54            URL.revokeObjectURL(prevObjURL);
55          }
56          return URL.createObjectURL(blob);
57        });
58        // eslint-disable-next-line @typescript-eslint/no-unused-vars
59      } catch (error) {
60        onError?.forEach((e) => e?.({ source }));
61      }
62    })();
63  }, [source]);
64  if (!source?.headers) {
65    return source;
66  }
67  if (!objectURL) {
68    // Avoid fetching a URL without headers if we have headers
69    return null;
70  }
71  return { ...source, uri: objectURL };
72}
73