diff --git a/README.md b/README.md index 1a69ad5..d02ff7f 100644 --- a/README.md +++ b/README.md @@ -74,37 +74,40 @@ The useNft() hook requires two arguments: the NFT `contract` address, and its to The returned value is an object containing information about the loading state: ```tsx -const result = useNft("0xd07dc4262bcdbf85190c01c996b4c06a461d2430", "90473") +const { status, loading, error, reload, nft } = useNft("0xd07dc4262bcdbf85190c01c996b4c06a461d2430", "90473") // one of "error", "loading" and "done" -result.status +status // same as status === "loading" -result.loading +loading // undefined or Error instance when status === "error" -result.error +error // call this function to retry in case of error -result.reload +reload // nft is undefined when status !== "done" -result.nft +nft // name of the NFT (or empty string) -result.nft.name +nft.name // description of the NFT (or empty string) -result.nft.description +nft.description // image / media URL of the NFT (or empty string) -result.nft.image +nft.image + +// the type of media: "image", "video" or "unknown" +nft.imageType // current owner of the NFT (or empty string) -result.nft.owner +nft.owner // url of the json containing the NFT's metadata -result.nft.metadataUrl +nft.metadataUrl ``` As TypeScript type: @@ -118,6 +121,7 @@ type NftResult = { nft?: { description: string image: string + imageType: "image" | "video" | "unknown" name: string owner: string metadataUrl?: string @@ -185,7 +189,7 @@ Allows to proxy the image URL. This is useful to optimize (compress / resize) th Default value: ```js -function imageProxy(url) { +function imageProxy(url, metadata) { return url } ``` diff --git a/examples/ethereum/src/App.tsx b/examples/ethereum/src/App.tsx index 4c8eeaa..a6d83cb 100644 --- a/examples/ethereum/src/App.tsx +++ b/examples/ethereum/src/App.tsx @@ -7,8 +7,6 @@ import NoEthereum from "./NoEthereum" import nfts from "../../nfts" import useEthereum from "./use-ethereum" -const imageProxy = (url: string) => - `https://ik.imagekit.io/p/${encodeURIComponent(url)}?tr=n-card` const jsonProxy = (url: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}` @@ -16,11 +14,7 @@ function App() { const ethereum = useEthereum() const connected = ethereum?.isConnected() return ( - + {connected ? : } ) diff --git a/examples/ethers/src/App.tsx b/examples/ethers/src/App.tsx index baf70fd..ac61f02 100644 --- a/examples/ethers/src/App.tsx +++ b/examples/ethers/src/App.tsx @@ -11,18 +11,12 @@ const ethersConfig = { provider: getDefaultProvider("homestead"), } -const imageProxy = (url: string) => - `https://ik.imagekit.io/p/${encodeURIComponent(url)}?tr=n-card` const jsonProxy = (url: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}` function App() { return ( - + diff --git a/examples/fetchWrapper/yarn.lock b/examples/fetchWrapper/yarn.lock index afc20f4..57bff97 100644 --- a/examples/fetchWrapper/yarn.lock +++ b/examples/fetchWrapper/yarn.lock @@ -9287,14 +9287,14 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard -"swr@npm:^1.0.0-beta.6": - version: 1.0.0-beta.6 - resolution: "swr@npm:1.0.0-beta.6" +"swr@npm:^1.0.0-beta.10": + version: 1.0.0-beta.10 + resolution: "swr@npm:1.0.0-beta.10" dependencies: dequal: 2.0.2 peerDependencies: react: ^16.11.0 || ^17.0.0 - checksum: b67113b40001be62389195341b0eb7ad10fdcb4d724166228781ba22a99f26f1d2d0a9f2b2252f7bc8695e500376390b1e552b0e3581d8ba1e9328a580318cfc + checksum: 6895e0bc2d849cfb805857fa1f8dbb792136b9bb5177e401f21b0e96e41b2e427de289ee820d4d75850b500e50d8695d89985cf8ea164a5db0e38f45e57bffb8 languageName: node linkType: hard @@ -9740,7 +9740,7 @@ resolve@^2.0.0-next.3: version: 0.0.0-use.local resolution: "use-nft@portal:../..::locator=fetchwrapper%40workspace%3A." dependencies: - swr: ^1.0.0-beta.6 + swr: ^1.0.0-beta.10 peerDependencies: react: ">=17" languageName: node diff --git a/src/fetchers/ethereum/index.tsx b/src/fetchers/ethereum/index.tsx index 722cc77..802132e 100644 --- a/src/fetchers/ethereum/index.tsx +++ b/src/fetchers/ethereum/index.tsx @@ -64,7 +64,7 @@ function addProxyImage( imageProxy: ImageProxyFn ): NftMetadata { return metadata.image.startsWith("http") - ? { ...metadata, image: imageProxy(metadata.image) } + ? { ...metadata, image: imageProxy(metadata.image, metadata) } : metadata } diff --git a/src/fetchers/ethereum/standard-nft.tsx b/src/fetchers/ethereum/standard-nft.tsx index d856ad2..3849836 100644 --- a/src/fetchers/ethereum/standard-nft.tsx +++ b/src/fetchers/ethereum/standard-nft.tsx @@ -2,7 +2,12 @@ import type { Address, FetchContext, NftMetadata } from "../../types" import type { EthereumFetcherConfig, EthereumProviderEip1193 } from "./types" import { fetchMetadata } from "../shared/fetch-metadata" -import { MultipleErrors, normalizeTokenUrl, promiseAny } from "../../utils" +import { + MultipleErrors, + normalizeTokenUrl, + promiseAny, + urlExtensionType, +} from "../../utils" import { decodeAddress, decodeString, @@ -50,10 +55,12 @@ export async function fetchStandardNftContractData( ]) const metadata = await fetchMetadata(metadataUrl, fetchContext) + const imageType = urlExtensionType(metadata.image) return { ...metadata, - owner, + imageType, metadataUrl, + owner, } } diff --git a/src/fetchers/ethers/index.tsx b/src/fetchers/ethers/index.tsx index 4256726..f146eb3 100644 --- a/src/fetchers/ethers/index.tsx +++ b/src/fetchers/ethers/index.tsx @@ -60,7 +60,7 @@ function addProxyImage( imageProxy: ImageProxyFn ): NftMetadata { return metadata.image.startsWith("http") - ? { ...metadata, image: imageProxy(metadata.image) } + ? { ...metadata, image: imageProxy(metadata.image, metadata) } : metadata } diff --git a/src/fetchers/ethers/standard-nft.tsx b/src/fetchers/ethers/standard-nft.tsx index 79b1bbd..85b3f75 100644 --- a/src/fetchers/ethers/standard-nft.tsx +++ b/src/fetchers/ethers/standard-nft.tsx @@ -3,7 +3,12 @@ import type { Address, FetchContext, NftMetadata } from "../../types" import type { EthersFetcherConfig } from "./types" import { fetchMetadata } from "../shared/fetch-metadata" -import { MultipleErrors, normalizeTokenUrl, promiseAny } from "../../utils" +import { + MultipleErrors, + normalizeTokenUrl, + promiseAny, + urlExtensionType, +} from "../../utils" const ABI = [ // ERC-721 @@ -55,10 +60,12 @@ export async function fetchStandardNftContractData( ]) const metadata = await fetchMetadata(metadataUrl, fetchContext) + const imageType = urlExtensionType(metadata.image) return { ...metadata, - owner, + imageType, metadataUrl, + owner, } } diff --git a/src/fetchers/shared/cryptokitties.ts b/src/fetchers/shared/cryptokitties.ts index f467445..c07756f 100644 --- a/src/fetchers/shared/cryptokitties.ts +++ b/src/fetchers/shared/cryptokitties.ts @@ -14,12 +14,14 @@ export async function cryptoKittiesMetadata( bio: string image_url: string } + const image = data?.image_url ?? "" return { description: data?.bio ?? "−", - image: data?.image_url ?? "", + image, + imageType: image ? "image" : "unknown", + metadataUrl, name: data?.name ?? "Unknown", owner: "", - metadataUrl, } } diff --git a/src/fetchers/shared/cryptopunks.ts b/src/fetchers/shared/cryptopunks.ts index 5e2891f..8965a76 100644 --- a/src/fetchers/shared/cryptopunks.ts +++ b/src/fetchers/shared/cryptopunks.ts @@ -14,6 +14,7 @@ export function cryptoPunksMetadata(index: string): NftMetadata { return { description: CRYPTOPUNKS_DESCRIPTION, image: `https://www.larvalabs.com/cryptopunks/cryptopunk${index}.png`, + imageType: "image", metadataUrl: "", name: `CryptoPunk ${index}`, owner: "", diff --git a/src/fetchers/shared/decentraland-estate.tsx b/src/fetchers/shared/decentraland-estate.tsx index caf7deb..a9f29ce 100644 --- a/src/fetchers/shared/decentraland-estate.tsx +++ b/src/fetchers/shared/decentraland-estate.tsx @@ -60,10 +60,12 @@ export async function decentralandEstateMetadata( } const nft = data?.nfts?.[0] + const image = nft?.image ?? "" return { description: nft?.estate?.data?.description ?? "−", - image: nft?.image ?? "", + image, + imageType: image ? "image" : "unknown", metadataUrl: "", name: nft?.name ?? "Unknown", owner: nft?.owner?.address ?? "", diff --git a/src/fetchers/shared/decentraland-parcel.tsx b/src/fetchers/shared/decentraland-parcel.tsx index e1a3337..1029163 100644 --- a/src/fetchers/shared/decentraland-parcel.tsx +++ b/src/fetchers/shared/decentraland-parcel.tsx @@ -65,11 +65,13 @@ export async function decentralandParcelMetadata( const nft = data?.nfts?.[0] const parcel = nft?.parcel + const image = nft?.image ?? "" return { description: parcel?.data?.description ?? "-", - image: nft?.image ?? "", - metadataUrl: '', + image, + imageType: image ? "image" : "unknown", + metadataUrl: "", name: nft?.name ?? `Parcel ${parcel?.x},${parcel?.y}`, owner: nft?.owner?.address ?? "", } diff --git a/src/fetchers/shared/mooncats.ts b/src/fetchers/shared/mooncats.ts index 9addbb4..3459c25 100644 --- a/src/fetchers/shared/mooncats.ts +++ b/src/fetchers/shared/mooncats.ts @@ -65,6 +65,7 @@ export async function moonCatsMetadata( `The (unofficial) wrapped version of MoonCats Rescue. ` + `Original cat ID: ${catId}.`, image, + imageType: image ? "image" : "unknown", metadataUrl: "", name: `Wrapped MoonCat #${tokenId}`, owner: "", diff --git a/src/types.ts b/src/types.ts index 9875f38..f812ba4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,9 +32,10 @@ export type NftResult = NftResultLoading | NftResultError | NftResultDone export type NftMetadata = { description: string image: string + imageType: "image" | "video" | "unknown" + metadataUrl: string name: string owner: Address - metadataUrl: string } export type NftJsonMetadata = { @@ -70,7 +71,7 @@ export type FetcherDeclaration = export type FetcherProp = Fetcher | FetcherDeclaration -export type ImageProxyFn = (url: string) => string +export type ImageProxyFn = (url: string, metadata: NftMetadata) => string export type JsonProxyFn = (url: string) => string export type IpfsUrlFn = (cid: string, path?: string) => string diff --git a/src/utils.tsx b/src/utils.tsx index 486693e..cc2d524 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -261,3 +261,13 @@ export class MultipleErrors extends Error { this.errors = errors } } + +const IMAGE_EXT_RE = /\.(?:png|svg|jpg|jepg|gif|webp|jxl|avif)$/ +const VIDEO_EXT_RE = /\.(?:mp4|mov|webm|ogv)$/ + +// Guess a file type from the extension used in a URL +export function urlExtensionType(url: string): NftMetadata["imageType"] { + if (IMAGE_EXT_RE.test(url)) return "image" + if (VIDEO_EXT_RE.test(url)) return "video" + return "unknown" +} diff --git a/test/utils.test.tsx b/test/utils.test.tsx index 8dc9b3e..fa0de30 100644 --- a/test/utils.test.tsx +++ b/test/utils.test.tsx @@ -1,15 +1,16 @@ import { fetchImage, - ipfsUrlFromString, + identity, ipfsUrlDefault, + ipfsUrlFromString, isAddress, - identity, normalizeImageUrl, normalizeNftMetadata, normalizeNiftyGatewayUrl, normalizeOpenSeaUrl, normalizeTokenUrl, parseNftUrl, + urlExtensionType, } from "../src/utils" const IPFS_HASH_1 = @@ -187,3 +188,11 @@ describe("normalizeNftMetadata()", () => { }) }) }) + +describe("urlExtensionType()", () => { + it("guesses the asset type from the URL", () => { + expect(urlExtensionType("something.jpg")).toBe("image") + expect(urlExtensionType("something.mov")).toBe("video") + expect(urlExtensionType("something.xyz")).toBe("unknown") + }) +}) diff --git a/website/src/App.tsx b/website/src/App.tsx index f4f63c7..ff4655c 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -18,8 +18,8 @@ const fetcher = [ }, ] -const imageProxy = (url: string) => - url.endsWith(".mp4") +const imageProxy = (url: string, metadata: NftMetadata) => + metadata.imageType === "video" ? url : `https://ik.imagekit.io/p/${encodeURIComponent(url)}?tr=n-card`