Skip to content

Commit bc5b543

Browse files
committed
Fix all resolveScheme unhandled errors (#5096)
Fixes: DASH-333
1 parent 1a45f21 commit bc5b543

File tree

12 files changed

+141
-101
lines changed

12 files changed

+141
-101
lines changed

apps/dashboard/.eslintrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ module.exports = {
3434
message:
3535
"Use useV5DashboardChain instead if you are using it inside a component",
3636
},
37+
{
38+
selector: "CallExpression[callee.name='resolveScheme']",
39+
message:
40+
"resolveScheme can throw error if resolution fails. Either catch the error and ignore the lint warning or Use `resolveSchemeWithErrorHandler` / `replaceIpfsUrl` utility in dashboard instead",
41+
},
3742
],
3843
"no-restricted-imports": [
3944
"error",

apps/dashboard/src/@/components/blocks/wallet-address.tsx

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client";
2-
32
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
43
import {
54
HoverCard,
65
HoverCardContent,
76
HoverCardTrigger,
87
} from "@/components/ui/hover-card";
98
import { useThirdwebClient } from "@/constants/thirdweb.client";
9+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
1010
import { useClipboard } from "hooks/useClipboard";
1111
import { Check, Copy, ExternalLinkIcon } from "lucide-react";
1212
import { useMemo } from "react";
@@ -18,7 +18,6 @@ import {
1818
type SocialProfile,
1919
useSocialProfiles,
2020
} from "thirdweb/react";
21-
import { resolveScheme } from "thirdweb/storage";
2221
import { cn } from "../../lib/utils";
2322
import { Badge } from "../ui/badge";
2423
import { Button } from "../ui/button";
@@ -113,42 +112,41 @@ export function WalletAddress(props: {
113112
) : !profiles.data?.length ? (
114113
<p className="text-muted-foreground text-sm">No profiles found</p>
115114
) : (
116-
profiles.data?.map((profile) => (
117-
<div
118-
className="flex flex-row items-center gap-2"
119-
key={profile.type + profile.name}
120-
>
121-
{profile.avatar &&
122-
(profile.avatar.startsWith("http") ||
123-
profile.avatar?.startsWith("ipfs")) && (
115+
profiles.data?.map((profile) => {
116+
const walletAvatarLink = resolveSchemeWithErrorHandler({
117+
client: thirdwebClient,
118+
uri: profile.avatar,
119+
});
120+
121+
return (
122+
<div
123+
className="flex flex-row items-center gap-2"
124+
key={profile.type + profile.name}
125+
>
126+
{walletAvatarLink && (
124127
<Avatar>
125-
<AvatarImage
126-
src={resolveScheme({
127-
client: thirdwebClient,
128-
uri: profile.avatar,
129-
})}
130-
alt={profile.name}
131-
/>
128+
<AvatarImage src={walletAvatarLink} alt={profile.name} />
132129
{profile.name && (
133130
<AvatarFallback>
134131
{profile.name.slice(0, 2)}
135132
</AvatarFallback>
136133
)}
137134
</Avatar>
138135
)}
139-
<div className="flex w-full flex-col gap-1">
140-
<div className="flex w-full flex-row items-center justify-between gap-4">
141-
<h4 className="font-semibold text-md">{profile.name}</h4>
142-
<Badge variant="outline">{profile.type}</Badge>
136+
<div className="flex w-full flex-col gap-1">
137+
<div className="flex w-full flex-row items-center justify-between gap-4">
138+
<h4 className="font-semibold text-md">{profile.name}</h4>
139+
<Badge variant="outline">{profile.type}</Badge>
140+
</div>
141+
{profile.bio && (
142+
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
143+
{profile.bio}
144+
</p>
145+
)}
143146
</div>
144-
{profile.bio && (
145-
<p className="line-clamp-1 whitespace-normal text-muted-foreground text-sm">
146-
{profile.bio}
147-
</p>
148-
)}
149147
</div>
150-
</div>
151-
))
148+
);
149+
})
152150
)}
153151
<Button
154152
asChild
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ThirdwebClient } from "thirdweb";
2+
import { resolveScheme } from "thirdweb/storage";
3+
4+
export function resolveSchemeWithErrorHandler(options: {
5+
uri: string | undefined;
6+
client: ThirdwebClient;
7+
}) {
8+
if (!options.uri) {
9+
return undefined;
10+
}
11+
try {
12+
// eslint-disable-next-line no-restricted-syntax
13+
return resolveScheme({
14+
uri: options.uri,
15+
client: options.client,
16+
});
17+
} catch (err) {
18+
console.error("error resolving ipfs url", options.uri, err);
19+
return undefined;
20+
}
21+
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
66
import { Spinner } from "@/components/ui/Spinner/Spinner";
77
import { useThirdwebClient } from "@/constants/thirdweb.client";
88
import { useDashboardRouter } from "@/lib/DashboardRouter";
9+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
910
import {
1011
Box,
1112
ButtonGroup,
@@ -25,7 +26,6 @@ import type { ThirdwebContract } from "thirdweb";
2526
import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721";
2627
import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155";
2728
import { useReadContract } from "thirdweb/react";
28-
import { resolveScheme } from "thirdweb/storage";
2929
import { Badge, Button, Card, CodeBlock, Heading, Text } from "tw-components";
3030
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
3131
import { shortenString } from "utils/usedapp-external";
@@ -83,6 +83,15 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
8383
},
8484
);
8585

86+
const tokenURIHttpLink = resolveSchemeWithErrorHandler({
87+
client,
88+
uri: nft?.tokenURI,
89+
});
90+
const nftImageLink = resolveSchemeWithErrorHandler({
91+
client,
92+
uri: nft?.metadata.image,
93+
});
94+
8695
if (isPending) {
8796
return (
8897
<div className="flex h-[400px] items-center justify-center">
@@ -263,14 +272,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
263272
tooltip="The URI of this NFT"
264273
copyIconPosition="right"
265274
/>
266-
<Button variant="ghost" size="sm">
267-
<Link
268-
href={resolveScheme({ client, uri: nft.tokenURI })}
269-
target="_blank"
270-
>
271-
<ExternalLinkIcon className="size-4" />
272-
</Link>
273-
</Button>
275+
{tokenURIHttpLink && (
276+
<Button variant="ghost" size="sm">
277+
<Link href={tokenURIHttpLink} target="_blank">
278+
<ExternalLinkIcon className="size-4" />
279+
</Link>
280+
</Button>
281+
)}
274282
</GridItem>
275283
{nft.metadata.image && (
276284
<>
@@ -287,17 +295,13 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
287295
tooltip="The media URI of this NFT"
288296
copyIconPosition="right"
289297
/>
290-
<Button variant="ghost" size="sm">
291-
<Link
292-
href={resolveScheme({
293-
client,
294-
uri: nft.metadata.image,
295-
})}
296-
target="_blank"
297-
>
298-
<ExternalLinkIcon className="size-4" />
299-
</Link>
300-
</Button>
298+
{nftImageLink && (
299+
<Button variant="ghost" size="sm">
300+
<Link href={nftImageLink} target="_blank">
301+
<ExternalLinkIcon className="size-4" />
302+
</Link>
303+
</Button>
304+
)}
301305
</GridItem>
302306
</>
303307
)}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export const SettingsMetadata = ({
8989
let image: string | undefined = metadata.data?.image;
9090
try {
9191
image = image
92-
? resolveScheme({
92+
? // eslint-disable-next-line no-restricted-syntax
93+
resolveScheme({
9394
client: contract.client,
9495
uri: image,
9596
})

apps/dashboard/src/app/(dashboard)/(chain)/components/server/chain-icon.tsx

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import "server-only";
33
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
44
import { getThirdwebClient } from "@/constants/thirdweb.server";
5+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
56
import { cn } from "@/lib/utils";
6-
import { resolveScheme } from "thirdweb/storage";
77

88
const fallbackChainIcon =
99
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIHZpZXdCb3g9IjAgMCA5NiA5NiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTY4LjE1MTkgNzUuNzM3MkM2Mi4yOTQzIDc5Ljk5MyA1NS4yMzk3IDgyLjI4NTIgNDcuOTk5MyA4Mi4yODUyQzQwLjc1ODkgODIuMjg1MiAzMy43MDQzIDc5Ljk5MyAyNy44NDY2IDc1LjczNzJNNjMuMDI5MSAxNy4xODM3QzY5LjUzNjggMjAuMzU3NyA3NC44NzI2IDI1LjUxMDQgNzguMjcxOCAzMS45MDMzQzgxLjY3MDkgMzguMjk2MiA4Mi45NTkgNDUuNjAxMiA4MS45NTEzIDUyLjc3MTFNMTQuMDQ3NiA1Mi43NzA4QzEzLjAzOTkgNDUuNjAwOCAxNC4zMjggMzguMjk1OSAxNy43MjcxIDMxLjkwM0MyMS4xMjYzIDI1LjUxMDEgMjYuNDYyMSAyMC4zNTczIDMyLjk2OTggMTcuMTgzM000Ni4wNTk4IDI5LjM2NzVMMjkuMzY3MyA0Ni4wNkMyOC42ODg1IDQ2LjczODkgMjguMzQ5IDQ3LjA3ODMgMjguMjIxOCA0Ny40Njk3QzI4LjExIDQ3LjgxNCAyOC4xMSA0OC4xODQ5IDI4LjIyMTggNDguNTI5MkMyOC4zNDkgNDguOTIwNiAyOC42ODg1IDQ5LjI2MDEgMjkuMzY3MyA0OS45MzlMNDYuMDU5OCA2Ni42MzE0QzQ2LjczODcgNjcuMzEwMyA0Ny4wNzgxIDY3LjY0OTcgNDcuNDY5NSA2Ny43NzY5QzQ3LjgxMzggNjcuODg4OCA0OC4xODQ3IDY3Ljg4ODggNDguNTI5IDY3Ljc3NjlDNDguOTIwNCA2Ny42NDk3IDQ5LjI1OTkgNjcuMzEwMyA0OS45Mzg4IDY2LjYzMTRMNjYuNjMxMiA0OS45MzlDNjcuMzEwMSA0OS4yNjAxIDY3LjY0OTUgNDguOTIwNiA2Ny43NzY3IDQ4LjUyOTJDNjcuODg4NiA0OC4xODQ5IDY3Ljg4ODYgNDcuODE0IDY3Ljc3NjcgNDcuNDY5N0M2Ny42NDk1IDQ3LjA3ODMgNjcuMzEwMSA0Ni43Mzg5IDY2LjYzMTIgNDYuMDZMNDkuOTM4OCAyOS4zNjc1QzQ5LjI1OTkgMjguNjg4NyA0OC45MjA0IDI4LjM0OTIgNDguNTI5IDI4LjIyMkM0OC4xODQ3IDI4LjExMDIgNDcuODEzOCAyOC4xMTAyIDQ3LjQ2OTUgMjguMjIyQzQ3LjA3ODEgMjguMzQ5MiA0Ni43Mzg3IDI4LjY4ODcgNDYuMDU5OCAyOS4zNjc1WiIgc3Ryb2tlPSIjNDA0MDQwIiBzdHJva2Utd2lkdGg9IjYuODU3MTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K";
@@ -13,24 +13,35 @@ export async function ChainIcon(props: {
1313
className?: string;
1414
}) {
1515
if (props.iconUrl) {
16-
const resolved = resolveScheme({
16+
let imageLink = fallbackChainIcon;
17+
18+
const resolved = resolveSchemeWithErrorHandler({
1719
client: getThirdwebClient(),
1820
uri: props.iconUrl,
1921
});
20-
const res = await fetch(resolved, {
21-
// revalidate every hour
22-
next: { revalidate: 60 * 60 },
23-
method: "HEAD",
24-
headers: DASHBOARD_THIRDWEB_SECRET_KEY
25-
? {
26-
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
27-
}
28-
: {},
29-
}).catch(() => null);
22+
23+
if (resolved) {
24+
// check if it loads or not
25+
const res = await fetch(resolved, {
26+
// revalidate every hour
27+
next: { revalidate: 60 * 60 },
28+
method: "HEAD",
29+
headers: DASHBOARD_THIRDWEB_SECRET_KEY
30+
? {
31+
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
32+
}
33+
: {},
34+
}).catch(() => null);
35+
36+
if (res?.status === 200) {
37+
imageLink = resolved;
38+
}
39+
}
40+
3041
return (
3142
<img
3243
alt=""
33-
src={res?.status === 200 ? resolved : fallbackChainIcon}
44+
src={imageLink}
3445
className={cn("object-contain", props.className)}
3546
/>
3647
);

apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/ecosystem-header.client.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { Skeleton } from "@/components/ui/skeleton";
1515
import { TabLinks } from "@/components/ui/tabs";
1616
import { useThirdwebClient } from "@/constants/thirdweb.client";
17+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
1718
import {
1819
AlertTriangleIcon,
1920
CheckIcon,
@@ -23,7 +24,6 @@ import {
2324
import Image from "next/image";
2425
import Link from "next/link";
2526
import { usePathname } from "next/navigation";
26-
import { resolveScheme } from "thirdweb/storage";
2727
import { useEcosystemList } from "../../../../hooks/use-ecosystem-list";
2828
import type { Ecosystem } from "../../../../types";
2929
import { useEcosystem } from "../../hooks/use-ecosystem";
@@ -127,6 +127,11 @@ export function EcosystemHeader(props: {
127127

128128
const ecosystem = fetchedEcosystem ?? props.ecosystem;
129129

130+
const ecosystemImageLink = resolveSchemeWithErrorHandler({
131+
uri: ecosystem.imageUrl,
132+
client,
133+
});
134+
130135
return (
131136
<div className="flex flex-col gap-8">
132137
<EcosystemAlertBanner ecosystem={ecosystem} />
@@ -136,13 +141,10 @@ export function EcosystemHeader(props: {
136141
{!ecosystem.imageUrl ? (
137142
<Skeleton className="size-24" />
138143
) : (
139-
ecosystem.imageUrl && (
144+
ecosystemImageLink && (
140145
<div className="relative size-24 overflow-hidden rounded-md">
141146
<Image
142-
src={resolveScheme({
143-
uri: ecosystem.imageUrl,
144-
client,
145-
})}
147+
src={ecosystemImageLink}
146148
sizes="100px"
147149
alt={ecosystem.name}
148150
fill

apps/dashboard/src/app/(dashboard)/published-contract/components/contract-info.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getThirdwebClient } from "@/constants/thirdweb.server";
2-
import { resolveScheme } from "thirdweb/storage";
2+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
33

44
export function DeployContractInfo(props: {
55
name: string;
@@ -9,20 +9,18 @@ export function DeployContractInfo(props: {
99
}) {
1010
const contractNameDisplay = props.displayName || props.name;
1111

12+
const contractImageLink = resolveSchemeWithErrorHandler({
13+
client: getThirdwebClient(),
14+
uri: props.logo,
15+
});
16+
1217
return (
1318
<div className="flex flex-col gap-2">
1419
<div className="flex flex-1 items-center gap-4">
15-
{props.logo && (
20+
{contractImageLink && (
1621
<div className="hidden shrink-0 items-center justify-center rounded-xl border border-border p-2 md:flex">
1722
{/*eslint-disable-next-line @next/next/no-img-element*/}
18-
<img
19-
className="size-12"
20-
alt={props.name}
21-
src={resolveScheme({
22-
client: getThirdwebClient(),
23-
uri: props.logo,
24-
})}
25-
/>
23+
<img className="size-12" alt={props.name} src={contractImageLink} />
2624
</div>
2725
)}
2826

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { CopyTextButton } from "@/components/ui/CopyTextButton";
77
import { Input } from "@/components/ui/input";
88
import { useThirdwebClient } from "@/constants/thirdweb.client";
99
import { useDashboardRouter } from "@/lib/DashboardRouter";
10+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
1011
import { useMutation } from "@tanstack/react-query";
1112
import { FileInput } from "components/shared/FileInput";
1213
import { useState } from "react";
1314
import { toast } from "sonner";
14-
import { resolveScheme } from "thirdweb/storage";
1515

1616
type UpdateTeamField = (team: Partial<Team>) => Promise<void>;
1717

@@ -152,12 +152,10 @@ function TeamAvatarFormControl(props: {
152152
avatar: string | undefined;
153153
}) {
154154
const client = useThirdwebClient();
155-
const teamUrl = props.avatar
156-
? resolveScheme({
157-
client: client,
158-
uri: props.avatar,
159-
})
160-
: undefined;
155+
const teamAvatarUrl = resolveSchemeWithErrorHandler({
156+
client: client,
157+
uri: props.avatar,
158+
});
161159

162160
const [teamAvatar, setTeamAvatar] = useState<File | undefined>();
163161

@@ -200,7 +198,7 @@ function TeamAvatarFormControl(props: {
200198
setValue={setTeamAvatar}
201199
className="w-20 rounded-full lg:w-28"
202200
disableHelperText
203-
fileUrl={teamUrl}
201+
fileUrl={teamAvatarUrl}
204202
/>
205203
</div>
206204
</SettingsCard>

0 commit comments

Comments
 (0)