// Copyright 2017-2023 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 import '@polkadot/x-textencoder/shim'; import '@polkadot/x-textdecoder/shim'; import type { CallOptions } from './types'; import * as isIPFS from 'is-ipfs'; import { useEffect, useMemo, useState } from 'react'; import { useIsMountedRef } from './useIsMountedRef'; interface Options extends CallOptions { transform?: (value: any) => T } function isCid (cid: string): boolean { return !!cid && (isIPFS.cid(cid) || isIPFS.base32cid(cid.toLowerCase())); } const cache = new Map(); async function fetchIpfsData (ipfsHashes: string[]): Promise> { const result = new Map(); const promises = ipfsHashes.map((ipfsHash) => { if (cache.has(ipfsHash)) { result.set(ipfsHash, cache.get(ipfsHash)); return Promise.resolve(); } return fetch(`https://ipfs.io/ipfs/${ipfsHash}`) .then(async (res) => { const ipfsResponse = res.status >= 200 && res.status < 300 ? await res.text() : null; cache.set(ipfsHash, ipfsResponse); result.set(ipfsHash, ipfsResponse); }); }); await Promise.allSettled(promises); return result; } function postProcessData (ipfsData: Map, { transform }: Options = {}) { if (!transform) { return ipfsData; } for (const [key, value] of ipfsData.entries()) { ipfsData.set(key, transform(value)); } return ipfsData; } // FIXME This is generic, we cannot really use createNamedHook export function useIpfsFetch (hashes: string[] | undefined, options?: Options): Map | undefined { const mountedRef = useIsMountedRef(); const [value, setValue] = useState | undefined>(); const ipfsHashes = useMemo(() => { if (!hashes) { return undefined; } return hashes .map((hash) => isCid(hash) ? hash : '') .filter((hash) => !!hash); }, [hashes]); useEffect((): void => { if (mountedRef.current && ipfsHashes) { fetchIpfsData(ipfsHashes) .then((ipfsData) => setValue(postProcessData(ipfsData, options))) // eslint-disable-next-line @typescript-eslint/no-empty-function .catch(() => { }); } }, [ipfsHashes, options, mountedRef]); return value; }