Skip to content

Commit a6c0245

Browse files
authored
fix(icons): resolve icon paths relative to JSON config file (#123)
- Add resolveRelativePath() and resolveIconPaths() in chain-config service - Resolve icon and tokenIconBase paths relative to JSON file URL when loading - Add isSameOrigin() check in token-icon to distinguish local vs CDN resources - Update default-chains.json to use relative paths (../icons/...) This ensures icons work correctly regardless of deployment subpath.
1 parent d484e58 commit a6c0245

File tree

3 files changed

+88
-29
lines changed

3 files changed

+88
-29
lines changed

public/configs/default-chains.json

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
"type": "bioforest",
66
"name": "BFMeta",
77
"symbol": "BFM",
8-
"icon": "/icons/bfmeta/chain.svg",
8+
"icon": "../icons/bfmeta/chain.svg",
99
"tokenIconBase": [
10-
"/icons/bfmeta/tokens",
10+
"../icons/bfmeta/tokens",
1111
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/bfm",
1212
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/bfm"
1313
],
@@ -27,9 +27,9 @@
2727
"type": "bioforest",
2828
"name": "CCChain",
2929
"symbol": "CCC",
30-
"icon": "/icons/ccchain/chain.svg",
30+
"icon": "../icons/ccchain/chain.svg",
3131
"tokenIconBase": [
32-
"/icons/ccchain/tokens",
32+
"../icons/ccchain/tokens",
3333
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/ccc",
3434
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/ccc"
3535
],
@@ -43,9 +43,9 @@
4343
"type": "bioforest",
4444
"name": "PMChain",
4545
"symbol": "PMC",
46-
"icon": "/icons/pmchain/chain.svg",
46+
"icon": "../icons/pmchain/chain.svg",
4747
"tokenIconBase": [
48-
"/icons/pmchain/tokens",
48+
"../icons/pmchain/tokens",
4949
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/pmc",
5050
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/pmc"
5151
],
@@ -59,9 +59,9 @@
5959
"type": "bioforest",
6060
"name": "BFChain V2",
6161
"symbol": "BFT",
62-
"icon": "/icons/bfchainv2/chain.svg",
62+
"icon": "../icons/bfchainv2/chain.svg",
6363
"tokenIconBase": [
64-
"/icons/bfchainv2/tokens",
64+
"../icons/bfchainv2/tokens",
6565
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/bftv2",
6666
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/bftv2"
6767
],
@@ -75,9 +75,9 @@
7575
"type": "bioforest",
7676
"name": "BTGMeta",
7777
"symbol": "BTGM",
78-
"icon": "/icons/btgmeta/chain.svg",
78+
"icon": "../icons/btgmeta/chain.svg",
7979
"tokenIconBase": [
80-
"/icons/btgmeta/tokens",
80+
"../icons/btgmeta/tokens",
8181
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/btgm",
8282
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/btgm"
8383
],
@@ -105,9 +105,9 @@
105105
"type": "bioforest",
106106
"name": "ETHMeta",
107107
"symbol": "ETHM",
108-
"icon": "/icons/ethmeta/chain.svg",
108+
"icon": "../icons/ethmeta/chain.svg",
109109
"tokenIconBase": [
110-
"/icons/ethmeta/tokens",
110+
"../icons/ethmeta/tokens",
111111
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/ethm",
112112
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/ethm"
113113
],
@@ -135,9 +135,9 @@
135135
"type": "evm",
136136
"name": "Ethereum",
137137
"symbol": "ETH",
138-
"icon": "/icons/ethereum/chain.svg",
138+
"icon": "../icons/ethereum/chain.svg",
139139
"tokenIconBase": [
140-
"/icons/ethereum/tokens",
140+
"../icons/ethereum/tokens",
141141
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/eth",
142142
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/eth"
143143
],
@@ -151,9 +151,9 @@
151151
"type": "evm",
152152
"name": "BNB Smart Chain",
153153
"symbol": "BNB",
154-
"icon": "/icons/binance/chain.svg",
154+
"icon": "../icons/binance/chain.svg",
155155
"tokenIconBase": [
156-
"/icons/binance/tokens",
156+
"../icons/binance/tokens",
157157
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/bsc",
158158
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/bsc"
159159
],
@@ -167,9 +167,9 @@
167167
"type": "bip39",
168168
"name": "Tron",
169169
"symbol": "TRX",
170-
"icon": "/icons/tron/chain.svg",
170+
"icon": "../icons/tron/chain.svg",
171171
"tokenIconBase": [
172-
"/icons/tron/tokens",
172+
"../icons/tron/tokens",
173173
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/tron",
174174
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/tron"
175175
],
@@ -183,9 +183,9 @@
183183
"type": "bip39",
184184
"name": "Bitcoin",
185185
"symbol": "BTC",
186-
"icon": "/icons/bitcoin/chain.svg",
186+
"icon": "../icons/bitcoin/chain.svg",
187187
"tokenIconBase": [
188-
"/icons/bitcoin/tokens",
188+
"../icons/bitcoin/tokens",
189189
"https://bfm-fonts-cdn.oss-cn-hongkong.aliyuncs.com/meta-icon/btcm",
190190
"https://raw.githubusercontent.com/BFChainMeta/fonts-cdn/main/src/meta-icon/btcm"
191191
],

src/components/wallet/token-icon.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,31 @@ export interface TokenIconProps {
4848
className?: string | undefined;
4949
}
5050

51+
/**
52+
* 检查 URL 是否是同源的(本地资源)
53+
*/
54+
function isSameOrigin(url: string): boolean {
55+
// 相对路径视为同源
56+
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {
57+
return true
58+
}
59+
// 检查是否与当前页面同源
60+
try {
61+
const urlObj = new URL(url)
62+
return urlObj.origin === window.location.origin
63+
} catch {
64+
return true // 解析失败视为相对路径
65+
}
66+
}
67+
5168
/**
5269
* 根据 base 路径和 symbol 生成图标 URL
70+
* - 本地资源(同源):使用 {symbol}.svg 格式
71+
* - CDN 资源(跨域):使用 icon-{symbol}.png 格式
5372
*/
5473
function buildIconUrl(base: string, symbol: string): string {
5574
const lowerSymbol = symbol.toLowerCase();
56-
if (base.startsWith('/') || base.startsWith('./')) {
75+
if (isSameOrigin(base)) {
5776
return `${base}/${lowerSymbol}.svg`;
5877
}
5978
return `${base}/icon-${lowerSymbol}.png`;

src/services/chain-config/index.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ async function loadDefaultChainConfigs(): Promise<ChainConfig[]> {
8080
throw new Error('fetch is not available in this environment')
8181
}
8282

83-
const response = await fetch(getDefaultChainsUrl(), {
83+
const jsonUrl = getDefaultChainsUrl()
84+
const response = await fetch(jsonUrl, {
8485
method: 'GET',
8586
headers: { Accept: 'application/json' },
8687
})
@@ -90,7 +91,8 @@ async function loadDefaultChainConfigs(): Promise<ChainConfig[]> {
9091
}
9192

9293
const json: unknown = await response.json()
93-
const parsed = parseConfigs(json, 'default')
94+
// 传入 JSON 文件 URL,用于解析相对路径
95+
const parsed = parseConfigs(json, 'default', jsonUrl)
9496
defaultChainsCache = parsed
9597
return parsed
9698
})()
@@ -110,18 +112,56 @@ function parseJsonString(input: string): unknown {
110112
}
111113
}
112114

113-
function parseConfigs(input: unknown, source: ChainConfigSource): ChainConfig[] {
115+
/**
116+
* 解析相对路径为绝对 URL(相对于 JSON 文件位置)
117+
*/
118+
function resolveRelativePath(path: string, jsonFileUrl: string): string {
119+
// 已经是绝对 URL,直接返回
120+
if (path.startsWith('http://') || path.startsWith('https://')) {
121+
return path
122+
}
123+
// 解析相对路径
124+
return new URL(path, jsonFileUrl).toString()
125+
}
126+
127+
/**
128+
* 解析配置中的 icon 和 tokenIconBase 相对路径
129+
*/
130+
function resolveIconPaths(
131+
config: { icon?: string | undefined; tokenIconBase?: string[] | undefined },
132+
jsonFileUrl: string
133+
): { icon?: string; tokenIconBase?: string[] } {
134+
const result: { icon?: string; tokenIconBase?: string[] } = {}
135+
136+
if (config.icon !== undefined) {
137+
result.icon = resolveRelativePath(config.icon, jsonFileUrl)
138+
}
139+
140+
if (config.tokenIconBase !== undefined) {
141+
result.tokenIconBase = config.tokenIconBase.map((base) =>
142+
resolveRelativePath(base, jsonFileUrl)
143+
)
144+
}
145+
146+
return result
147+
}
148+
149+
function parseConfigs(input: unknown, source: ChainConfigSource, jsonFileUrl?: string): ChainConfig[] {
114150
const normalized: unknown = Array.isArray(input) ? input.map(normalizeUnknownType) : normalizeUnknownType(input)
115151

116152
const parsed = Array.isArray(normalized)
117153
? ChainConfigListSchema.parse(normalized)
118154
: [ChainConfigSchema.parse(normalized)]
119155

120-
return parsed.map((config) => ({
121-
...config,
122-
source,
123-
enabled: true,
124-
}))
156+
return parsed.map((config) => {
157+
const resolvedPaths = jsonFileUrl ? resolveIconPaths(config, jsonFileUrl) : {}
158+
return {
159+
...config,
160+
...resolvedPaths,
161+
source,
162+
enabled: true,
163+
}
164+
})
125165
}
126166

127167
function mergeBySource(options: {

0 commit comments

Comments
 (0)