@bifrostwallet/assets 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/index.d.ts +9 -1
- package/index.js +1 -1
- package/package.json +1 -4
- package/providers/eip155-14.json +18 -18
- package/providers/eip155-19.json +1 -1
- package/verify-logo-hash.js +92 -23
- package/fetch-logos.js +0 -193
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ yarn add @bifrostwallet/assets --exact
|
|
|
22
22
|
|
|
23
23
|
`<version>` is the package version and the git tag. That tag is written into the package at build time. Returned `logoURL` values are absolute. A missing record is still `null`.
|
|
24
24
|
|
|
25
|
-
An exact version tag is immutable on jsDelivr. Cache headers last one year, and the file is stored permanently, so this pinned URL does not go stale ([Caching](https://www.jsdelivr.com/documentation#id-caching)). Self-host when you want a copy you control
|
|
25
|
+
An exact version tag is immutable on jsDelivr. Cache headers last one year, and the file is stored permanently, so this pinned URL does not go stale ([Caching](https://www.jsdelivr.com/documentation#id-caching)). Self-host when you want a copy you control. The steps are in [Self-hosting logos](#self-hosting-logos).
|
|
26
26
|
|
|
27
27
|
Don't load logos from `raw.githubusercontent.com`. GitHub's raw file host is not a CDN: it rate-limits requests per IP and returns HTTP 429 under load. jsDelivr is built for this traffic.
|
|
28
28
|
|
|
@@ -43,23 +43,35 @@ Pass `baseURL` when you host the files yourself. A trailing slash is optional. T
|
|
|
43
43
|
|
|
44
44
|
### Optional: check a downloaded logo
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
`verifyLogoHash(bytes, logoHash)` is true when the SHA-256 of the decoded file bytes equals `logoHash`: 64 lowercase hex characters, no `0x` prefix. `fetchLogo(entry)` downloads `logoURL` and runs that check in one step. It does not retry unless you pass `retries` (the delay before the first retry is `retryDelay`, 500 ms by default, and it doubles each time).
|
|
47
47
|
|
|
48
48
|
### Self-hosting logos
|
|
49
49
|
|
|
50
|
-
Logo files are not in the npm package.
|
|
50
|
+
Logo files are not in the npm package. Clone this repository and check out the same version as your installed `@bifrostwallet/assets`. Tags have no `v` prefix.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
```sh
|
|
53
|
+
git clone https://github.com/bifrostwallet/assets
|
|
54
|
+
cd assets
|
|
55
|
+
git checkout <version>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`<version>` is the installed package version, for example `1.2.2`.
|
|
59
|
+
|
|
60
|
+
Upload only the `logo.webp` files under `data/`. Keep those relative paths (`tokens/…`, `providers/…`, `contracts/…`, `dapps/…`) so `baseURL` can join them.
|
|
61
|
+
|
|
62
|
+
Google Cloud:
|
|
53
63
|
|
|
54
64
|
```sh
|
|
55
|
-
|
|
65
|
+
gcloud storage rsync data gs://<your-bucket> --recursive --exclude='^(?!.*logo\.webp$).*'
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
Amazon S3:
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
```sh
|
|
71
|
+
aws s3 sync data s3://<your-bucket> --exclude '*' --include '*logo.webp'
|
|
72
|
+
```
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
Point the package at the public URL of that directory:
|
|
63
75
|
|
|
64
76
|
```typescript
|
|
65
77
|
import { createAssets } from "@bifrostwallet/assets";
|
|
@@ -69,7 +81,7 @@ const assets = createAssets({
|
|
|
69
81
|
});
|
|
70
82
|
```
|
|
71
83
|
|
|
72
|
-
|
|
84
|
+
When you bump `@bifrostwallet/assets`, check out that version and upload again before you ship. The default jsDelivr URL changes with the version. A copy you host does not.
|
|
73
85
|
|
|
74
86
|
### Listed providers
|
|
75
87
|
|
package/index.d.ts
CHANGED
|
@@ -186,6 +186,14 @@ export interface FetchLogoOptions {
|
|
|
186
186
|
/** Defaults to global fetch. Tests pass a substitute. */
|
|
187
187
|
fetch?: typeof fetch;
|
|
188
188
|
signal?: AbortSignal;
|
|
189
|
+
/**
|
|
190
|
+
* Extra attempts after a network error or HTTP 403, 408, 429, or 5xx.
|
|
191
|
+
* Defaults to 0.
|
|
192
|
+
* A hash mismatch or a response over 256KB is never retried.
|
|
193
|
+
*/
|
|
194
|
+
retries?: number;
|
|
195
|
+
/** Milliseconds before the first retry. Doubles on each later retry. Defaults to 500. */
|
|
196
|
+
retryDelay?: number;
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
/**
|
|
@@ -201,7 +209,7 @@ export declare function fetchLogo(
|
|
|
201
209
|
/**
|
|
202
210
|
* True when SHA-256 of the decoded logo bytes equals expectedHash.
|
|
203
211
|
* expectedHash is 64 lowercase hex characters with no 0x prefix (catalog logoHash).
|
|
204
|
-
* For bytes you already have
|
|
212
|
+
* For bytes you already have.
|
|
205
213
|
* fetchLogo calls this after a download.
|
|
206
214
|
*/
|
|
207
215
|
export declare function verifyLogoHash(
|
package/index.js
CHANGED
|
@@ -50,7 +50,7 @@ const CONTRACT_CATEGORIES = ["dex","aggregator","bridge","lending","staking","yi
|
|
|
50
50
|
const CONTRACT_CATEGORY_LABELS = {"dex":"DEX","aggregator":"Aggregator","bridge":"Bridge","lending":"Lending","staking":"Staking","yield":"Yield","perpetuals":"Perpetuals","predictions":"Predictions","nft-marketplace":"NFT Marketplace","governance":"Governance","fassets":"FAssets","rewards":"Rewards","other":"Other"};
|
|
51
51
|
const DAPP_CATEGORIES = ["featured","dex","lending","yield","staking","bridges","marketplaces","social","games","other"];
|
|
52
52
|
const DAPP_CATEGORY_LABELS = {"featured":"Featured","dex":"DEX","lending":"Lending","yield":"Yield","staking":"Staking","bridges":"Bridges","marketplaces":"Marketplaces","social":"Social","games":"Games","other":"Other"};
|
|
53
|
-
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.
|
|
53
|
+
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.3.1/data/";
|
|
54
54
|
|
|
55
55
|
const EIP712_DOMAIN_SALT = "0xffc5336f20c2026b0f53d269def3cdffa3ca99b07e238af05f263a89517104f7";
|
|
56
56
|
const PROVIDER_LISTING_PRIMARY_TYPE = "ProviderListing";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bifrostwallet/assets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Curated catalog of data providers, validators, tokens, contracts and dapps for Bifrost Wallet.",
|
|
5
5
|
"repository": "https://github.com/bifrostwallet/assets.git",
|
|
6
6
|
"author": "Bifrost Software Ltd",
|
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
"default": "./signatures.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
"bin": {
|
|
21
|
-
"fetch-logos": "./fetch-logos.js"
|
|
22
|
-
},
|
|
23
20
|
"sideEffects": false,
|
|
24
21
|
"dependencies": {}
|
|
25
22
|
}
|
package/providers/eip155-14.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"NodeID-6cnsMcUNTJwCcDuAyPwJqEaVa6yMrk8eq"
|
|
53
53
|
],
|
|
54
54
|
"listed": true,
|
|
55
|
-
"managementGroup":
|
|
55
|
+
"managementGroup": false,
|
|
56
56
|
"domainLinked": false,
|
|
57
57
|
"dualNetwork": true,
|
|
58
58
|
"firstTransaction": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"NodeID-NbTToANYcdsreLC5PBcE9gsMBpBY7N7s1"
|
|
134
134
|
],
|
|
135
135
|
"listed": true,
|
|
136
|
-
"managementGroup":
|
|
136
|
+
"managementGroup": false,
|
|
137
137
|
"domainLinked": false,
|
|
138
138
|
"dualNetwork": true,
|
|
139
139
|
"firstTransaction": {
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
"NodeID-J8Zbi3b3AjTEUmHdE4KSApEU1U36iaeDS"
|
|
246
246
|
],
|
|
247
247
|
"listed": true,
|
|
248
|
-
"managementGroup":
|
|
248
|
+
"managementGroup": false,
|
|
249
249
|
"domainLinked": false,
|
|
250
250
|
"dualNetwork": true,
|
|
251
251
|
"firstTransaction": {
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
"NodeID-HGNPxSh8dZqCNCmHfMMNWYC8CNQiUwDWK"
|
|
327
327
|
],
|
|
328
328
|
"listed": true,
|
|
329
|
-
"managementGroup":
|
|
329
|
+
"managementGroup": false,
|
|
330
330
|
"domainLinked": false,
|
|
331
331
|
"dualNetwork": true,
|
|
332
332
|
"firstTransaction": {
|
|
@@ -576,7 +576,7 @@
|
|
|
576
576
|
"NodeID-C3FRhsMBh3KNLKiVbc8xLY7gnVzmJ7EY9"
|
|
577
577
|
],
|
|
578
578
|
"listed": true,
|
|
579
|
-
"managementGroup":
|
|
579
|
+
"managementGroup": false,
|
|
580
580
|
"domainLinked": false,
|
|
581
581
|
"dualNetwork": true,
|
|
582
582
|
"firstTransaction": {
|
|
@@ -655,7 +655,7 @@
|
|
|
655
655
|
"NodeID-9WF8MvjNhmR45AdNjVfAWp27KKq7KCGpm"
|
|
656
656
|
],
|
|
657
657
|
"listed": true,
|
|
658
|
-
"managementGroup":
|
|
658
|
+
"managementGroup": false,
|
|
659
659
|
"domainLinked": false,
|
|
660
660
|
"dualNetwork": true,
|
|
661
661
|
"firstTransaction": {
|
|
@@ -924,7 +924,7 @@
|
|
|
924
924
|
"NodeID-8aGBxZssVWRvEr57YGRsWjZTHbnoWrfcc"
|
|
925
925
|
],
|
|
926
926
|
"listed": true,
|
|
927
|
-
"managementGroup":
|
|
927
|
+
"managementGroup": false,
|
|
928
928
|
"domainLinked": false,
|
|
929
929
|
"dualNetwork": true,
|
|
930
930
|
"firstTransaction": {
|
|
@@ -1029,7 +1029,7 @@
|
|
|
1029
1029
|
"NodeID-JF4XWnC9PjZVXQuqLcpQvxztdMez3CpkR"
|
|
1030
1030
|
],
|
|
1031
1031
|
"listed": true,
|
|
1032
|
-
"managementGroup":
|
|
1032
|
+
"managementGroup": false,
|
|
1033
1033
|
"domainLinked": false,
|
|
1034
1034
|
"dualNetwork": true,
|
|
1035
1035
|
"firstTransaction": {
|
|
@@ -1058,7 +1058,7 @@
|
|
|
1058
1058
|
"NodeID-FDR3CXdwP3H3Zz9QKfL61fk56zkPEgdFV"
|
|
1059
1059
|
],
|
|
1060
1060
|
"listed": true,
|
|
1061
|
-
"managementGroup":
|
|
1061
|
+
"managementGroup": false,
|
|
1062
1062
|
"domainLinked": false,
|
|
1063
1063
|
"dualNetwork": true,
|
|
1064
1064
|
"firstTransaction": {
|
|
@@ -1218,7 +1218,7 @@
|
|
|
1218
1218
|
"NodeID-MsF6Xc1PsMWtcxVudCepWb5kUeF1EF8RX"
|
|
1219
1219
|
],
|
|
1220
1220
|
"listed": true,
|
|
1221
|
-
"managementGroup":
|
|
1221
|
+
"managementGroup": false,
|
|
1222
1222
|
"domainLinked": false,
|
|
1223
1223
|
"dualNetwork": true,
|
|
1224
1224
|
"firstTransaction": {
|
|
@@ -1323,7 +1323,7 @@
|
|
|
1323
1323
|
"NodeID-4mKZ7GYeKtdHMxhmHajDh1WVrVx1xWJxJ"
|
|
1324
1324
|
],
|
|
1325
1325
|
"listed": true,
|
|
1326
|
-
"managementGroup":
|
|
1326
|
+
"managementGroup": false,
|
|
1327
1327
|
"domainLinked": false,
|
|
1328
1328
|
"dualNetwork": true,
|
|
1329
1329
|
"firstTransaction": {
|
|
@@ -1349,7 +1349,7 @@
|
|
|
1349
1349
|
"NodeID-Ckyh9cpfkkXSo5PYNNFactcrKkY2XhkXR"
|
|
1350
1350
|
],
|
|
1351
1351
|
"listed": true,
|
|
1352
|
-
"managementGroup":
|
|
1352
|
+
"managementGroup": false,
|
|
1353
1353
|
"domainLinked": false,
|
|
1354
1354
|
"dualNetwork": true,
|
|
1355
1355
|
"firstTransaction": {
|
|
@@ -1783,7 +1783,7 @@
|
|
|
1783
1783
|
"NodeID-KiAUisCU8UvXGnMRiUvoeyWneSdUm987"
|
|
1784
1784
|
],
|
|
1785
1785
|
"listed": true,
|
|
1786
|
-
"managementGroup":
|
|
1786
|
+
"managementGroup": false,
|
|
1787
1787
|
"domainLinked": false,
|
|
1788
1788
|
"dualNetwork": true,
|
|
1789
1789
|
"firstTransaction": {
|
|
@@ -2001,7 +2001,7 @@
|
|
|
2001
2001
|
"NodeID-Mhm29H5a3Xq1jG9UpVoqo5mFXqqL3WpUV"
|
|
2002
2002
|
],
|
|
2003
2003
|
"listed": true,
|
|
2004
|
-
"managementGroup":
|
|
2004
|
+
"managementGroup": false,
|
|
2005
2005
|
"domainLinked": false,
|
|
2006
2006
|
"dualNetwork": false,
|
|
2007
2007
|
"firstTransaction": {
|
|
@@ -2219,7 +2219,7 @@
|
|
|
2219
2219
|
"NodeID-NEBeLpZHjxiSB9fbqDeqsxU529e41hQYE"
|
|
2220
2220
|
],
|
|
2221
2221
|
"listed": true,
|
|
2222
|
-
"managementGroup":
|
|
2222
|
+
"managementGroup": true,
|
|
2223
2223
|
"domainLinked": false,
|
|
2224
2224
|
"dualNetwork": false,
|
|
2225
2225
|
"firstTransaction": {
|
|
@@ -2432,7 +2432,7 @@
|
|
|
2432
2432
|
"NodeID-KiaPr2n8VH16oA7mGzveYgk2hceo2WYst"
|
|
2433
2433
|
],
|
|
2434
2434
|
"listed": false,
|
|
2435
|
-
"managementGroup":
|
|
2435
|
+
"managementGroup": true,
|
|
2436
2436
|
"domainLinked": false,
|
|
2437
2437
|
"dualNetwork": false,
|
|
2438
2438
|
"firstTransaction": {
|
|
@@ -2491,7 +2491,7 @@
|
|
|
2491
2491
|
"NodeID-KWuQ1CaxMUvDRRh7pzgcGMXpv38SFmb3r"
|
|
2492
2492
|
],
|
|
2493
2493
|
"listed": true,
|
|
2494
|
-
"managementGroup":
|
|
2494
|
+
"managementGroup": true,
|
|
2495
2495
|
"domainLinked": false,
|
|
2496
2496
|
"dualNetwork": true,
|
|
2497
2497
|
"firstTransaction": {
|
|
@@ -2517,7 +2517,7 @@
|
|
|
2517
2517
|
"NodeID-5amxeiA5AKHPPy6v1C7S1YgmkoXQDedtE"
|
|
2518
2518
|
],
|
|
2519
2519
|
"listed": false,
|
|
2520
|
-
"managementGroup":
|
|
2520
|
+
"managementGroup": true,
|
|
2521
2521
|
"domainLinked": false,
|
|
2522
2522
|
"dualNetwork": false,
|
|
2523
2523
|
"firstTransaction": {
|
package/providers/eip155-19.json
CHANGED
|
@@ -1502,7 +1502,7 @@
|
|
|
1502
1502
|
"publicKey": "0x2c7d487f812bdd5fe1657fac089232d801eeed16500efe5f0e6b89517ddd7af82242f718255d2f647c7204f55f5c410eca0988a8bbd242835a6565521bb59b07",
|
|
1503
1503
|
"validators": [],
|
|
1504
1504
|
"listed": true,
|
|
1505
|
-
"managementGroup":
|
|
1505
|
+
"managementGroup": true,
|
|
1506
1506
|
"domainLinked": false,
|
|
1507
1507
|
"dualNetwork": true,
|
|
1508
1508
|
"firstTransaction": {
|
package/verify-logo-hash.js
CHANGED
|
@@ -13,6 +13,42 @@ function verifyLogoHash(bytes, expectedHash) {
|
|
|
13
13
|
return crypto.timingSafeEqual(digest, expected);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function delay(ms) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
setTimeout(resolve, ms);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readRetryCount(value) {
|
|
23
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
|
24
|
+
const count = Math.floor(value);
|
|
25
|
+
return count > 0 ? count : 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readRetryDelay(value) {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return 500;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isRetryableStatus(status) {
|
|
34
|
+
return status === 403 || status === 408 || status === 429 || (status >= 500 && status <= 599);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function callerAborted(options) {
|
|
38
|
+
return !!(options && options.signal && options.signal.aborted);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function backoff(options, attempt, status) {
|
|
42
|
+
const retries = readRetryCount(options && options.retries);
|
|
43
|
+
const retryDelay = readRetryDelay(options && options.retryDelay);
|
|
44
|
+
const wait = retryDelay * 2 ** (attempt - 1);
|
|
45
|
+
const onRetry = options && options.onRetry;
|
|
46
|
+
if (typeof onRetry === "function") {
|
|
47
|
+
await onRetry({ attempt, retries, status, delay: wait });
|
|
48
|
+
}
|
|
49
|
+
if (wait > 0) await delay(wait);
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
async function fetchLogo(entry, options) {
|
|
17
53
|
if (!entry || typeof entry.logoURL !== "string" || entry.logoURL.trim() === "") {
|
|
18
54
|
throw new Error("logoURL is required");
|
|
@@ -21,29 +57,62 @@ async function fetchLogo(entry, options) {
|
|
|
21
57
|
throw new Error("logoHash must be a lowercase SHA-256 hex digest");
|
|
22
58
|
}
|
|
23
59
|
const fetchImpl = (options && options.fetch) || fetch;
|
|
24
|
-
const
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
const retries = readRetryCount(options && options.retries);
|
|
61
|
+
let attempt = 0;
|
|
62
|
+
for (;;) {
|
|
63
|
+
const signal = options && options.signal ? options.signal : AbortSignal.timeout(30000);
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetchImpl(entry.logoURL, {
|
|
67
|
+
redirect: "follow",
|
|
68
|
+
headers: { accept: "application/octet-stream" },
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (!callerAborted(options) && attempt < retries) {
|
|
73
|
+
attempt += 1;
|
|
74
|
+
await backoff(options, attempt, "network");
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const message = error && error.message ? error.message : String(error);
|
|
78
|
+
throw new Error("Could not fetch logo: " + message);
|
|
79
|
+
}
|
|
80
|
+
if (!response || !response.ok) {
|
|
81
|
+
const status = response && typeof response.status === "number" ? response.status : 0;
|
|
82
|
+
if (response && typeof response.arrayBuffer === "function") {
|
|
83
|
+
try {
|
|
84
|
+
await response.arrayBuffer();
|
|
85
|
+
} catch (drainError) {
|
|
86
|
+
void drainError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (isRetryableStatus(status) && attempt < retries && !callerAborted(options)) {
|
|
90
|
+
attempt += 1;
|
|
91
|
+
await backoff(options, attempt, status);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const label = status ? status : "request failed";
|
|
95
|
+
throw new Error("Could not fetch logo: HTTP " + label);
|
|
96
|
+
}
|
|
97
|
+
let bytes;
|
|
98
|
+
try {
|
|
99
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!callerAborted(options) && attempt < retries) {
|
|
102
|
+
attempt += 1;
|
|
103
|
+
await backoff(options, attempt, "network");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const message = error && error.message ? error.message : String(error);
|
|
107
|
+
throw new Error("Could not fetch logo: " + message);
|
|
108
|
+
}
|
|
109
|
+
if (bytes.byteLength > 256 * 1024) {
|
|
110
|
+
throw new Error("Could not fetch logo: response exceeds 256KB");
|
|
111
|
+
}
|
|
112
|
+
if (!verifyLogoHash(bytes, entry.logoHash)) {
|
|
113
|
+
throw new Error("logo hash mismatch");
|
|
114
|
+
}
|
|
115
|
+
return bytes;
|
|
46
116
|
}
|
|
47
|
-
return bytes;
|
|
48
117
|
}
|
|
49
118
|
module.exports = { verifyLogoHash, fetchLogo };
|
package/fetch-logos.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
// @bifrostwallet/assets fetch-logos — generated file, do not edit.
|
|
4
|
-
// Downloads logo files for this package version. Each catalog entry's logoHash
|
|
5
|
-
// is the trust anchor. Bytes are the decoded response body (content-encoding removed).
|
|
6
|
-
// Accept is application/octet-stream so the CDN is not asked to convert the image.
|
|
7
|
-
|
|
8
|
-
const fs = require("node:fs");
|
|
9
|
-
const path = require("node:path");
|
|
10
|
-
const { fetchLogo, verifyLogoHash } = require("./verify-logo-hash.js");
|
|
11
|
-
|
|
12
|
-
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.2.2/data/";
|
|
13
|
-
const USAGE = [
|
|
14
|
-
"Usage: fetch-logos <dir>",
|
|
15
|
-
"",
|
|
16
|
-
"Download this package version's logos into <dir>.",
|
|
17
|
-
"Paths stay relative to data/ (providers/, tokens/, contracts/, dapps/).",
|
|
18
|
-
"Each file is checked against the logoHash on its catalog entry.",
|
|
19
|
-
"A missing hash or a hash mismatch does not write that file, and the command exits non-zero.",
|
|
20
|
-
"Do not upload the directory when the command fails.",
|
|
21
|
-
"",
|
|
22
|
-
"Upload <dir> to any static host. createAssets baseURL is optional.",
|
|
23
|
-
"Omit it to use jsDelivr at this release tag:",
|
|
24
|
-
"https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.2.2/data/",
|
|
25
|
-
"Or set baseURL to the public URL where you uploaded that directory.",
|
|
26
|
-
"Example: https://assets.example.com/",
|
|
27
|
-
"Run fetch-logos again when you bump the package version.",
|
|
28
|
-
].join("\n");
|
|
29
|
-
|
|
30
|
-
function readJson(filePath) {
|
|
31
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function walkJsonArrays(dir, visit) {
|
|
35
|
-
if (!fs.existsSync(dir)) return;
|
|
36
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
-
const fullPath = path.join(dir, entry.name);
|
|
38
|
-
if (entry.isDirectory()) {
|
|
39
|
-
walkJsonArrays(fullPath, visit);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (!entry.name.endsWith(".json")) continue;
|
|
43
|
-
if (entry.name === "package.json" || entry.name === "signatures.json") {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const parsed = readJson(fullPath);
|
|
47
|
-
if (!Array.isArray(parsed)) continue;
|
|
48
|
-
for (const item of parsed) {
|
|
49
|
-
if (item && typeof item === "object" && typeof item.logoURL === "string") {
|
|
50
|
-
visit(item, path.relative(dir, fullPath));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isSafeLogoPath(value) {
|
|
57
|
-
if (!value || value.startsWith("/") || value.includes("..") || value.includes("\\")) return false;
|
|
58
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return false;
|
|
59
|
-
return /^(providers|tokens|contracts|dapps)\/[A-Za-z0-9._@/-]+$/.test(value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function loadAnchors(packageRoot) {
|
|
63
|
-
const entries = new Map();
|
|
64
|
-
const failures = [];
|
|
65
|
-
walkJsonArrays(packageRoot, (item) => {
|
|
66
|
-
const logoPath = item.logoURL;
|
|
67
|
-
const logoHash = item.logoHash;
|
|
68
|
-
if (!isSafeLogoPath(logoPath)) {
|
|
69
|
-
failures.push(String(logoPath) + ": invalid logo path");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (typeof logoHash !== "string" || !/^[0-9a-f]{64}$/.test(logoHash)) {
|
|
73
|
-
failures.push(logoPath + ": missing logoHash");
|
|
74
|
-
entries.set(logoPath, null);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const existing = entries.get(logoPath);
|
|
78
|
-
if (existing !== undefined && existing !== logoHash) {
|
|
79
|
-
failures.push(logoPath + ": logoHash conflict");
|
|
80
|
-
entries.set(logoPath, null);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (existing === undefined) entries.set(logoPath, logoHash);
|
|
84
|
-
});
|
|
85
|
-
return { entries, failures };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function destinationFor(destDir, logoPath) {
|
|
89
|
-
const root = path.resolve(destDir);
|
|
90
|
-
const target = path.resolve(root, logoPath);
|
|
91
|
-
const relative = path.relative(root, target);
|
|
92
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
93
|
-
throw new Error("logo path escapes destination");
|
|
94
|
-
}
|
|
95
|
-
return target;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function mapPool(items, limit, fn) {
|
|
99
|
-
const queue = items.slice();
|
|
100
|
-
const workers = [];
|
|
101
|
-
const count = Math.min(limit, queue.length);
|
|
102
|
-
for (let i = 0; i < count; i += 1) {
|
|
103
|
-
workers.push(
|
|
104
|
-
(async () => {
|
|
105
|
-
while (queue.length > 0) {
|
|
106
|
-
const item = queue.shift();
|
|
107
|
-
await fn(item);
|
|
108
|
-
}
|
|
109
|
-
})(),
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
await Promise.all(workers);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function fetchLogos(options) {
|
|
116
|
-
if (!options || typeof options.destDir !== "string" || options.destDir.trim() === "") {
|
|
117
|
-
throw new Error("destDir is required");
|
|
118
|
-
}
|
|
119
|
-
const packageRoot = options.packageRoot || __dirname;
|
|
120
|
-
const supplied = typeof options.baseURL === "string" ? options.baseURL.trim() : "";
|
|
121
|
-
const baseURL = (supplied || DEFAULT_LOGO_BASE_URL).replace(/\/?$/, "/");
|
|
122
|
-
const fetchImpl = options.fetch || fetch;
|
|
123
|
-
const loaded = loadAnchors(packageRoot);
|
|
124
|
-
const failures = loaded.failures.slice();
|
|
125
|
-
const written = [];
|
|
126
|
-
const jobs = [];
|
|
127
|
-
for (const [logoPath, logoHash] of loaded.entries) {
|
|
128
|
-
if (typeof logoHash !== "string") continue;
|
|
129
|
-
jobs.push({ logoPath, logoHash });
|
|
130
|
-
}
|
|
131
|
-
await mapPool(jobs, 8, async (job) => {
|
|
132
|
-
try {
|
|
133
|
-
const bytes = await fetchLogo(
|
|
134
|
-
{ logoURL: baseURL + job.logoPath, logoHash: job.logoHash },
|
|
135
|
-
{ fetch: fetchImpl },
|
|
136
|
-
);
|
|
137
|
-
const target = destinationFor(options.destDir, job.logoPath);
|
|
138
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
139
|
-
const partial = target + ".partial";
|
|
140
|
-
fs.writeFileSync(partial, bytes);
|
|
141
|
-
fs.renameSync(partial, target);
|
|
142
|
-
written.push(job.logoPath);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
const message = error && error.message ? error.message : String(error);
|
|
145
|
-
failures.push(job.logoPath + ": " + message);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
return { written, failures };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function runCli(argv, deps) {
|
|
152
|
-
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
153
|
-
const extra = deps || {};
|
|
154
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
155
|
-
console.info(USAGE);
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
if (args[0] === "fetch-logos") args.shift();
|
|
159
|
-
if (args.length !== 1) {
|
|
160
|
-
console.error(USAGE);
|
|
161
|
-
return 1;
|
|
162
|
-
}
|
|
163
|
-
const result = await fetchLogos({
|
|
164
|
-
destDir: args[0],
|
|
165
|
-
packageRoot: extra.packageRoot,
|
|
166
|
-
baseURL: extra.baseURL,
|
|
167
|
-
fetch: extra.fetch,
|
|
168
|
-
});
|
|
169
|
-
for (const failure of result.failures) console.error(failure);
|
|
170
|
-
if (result.failures.length > 0) {
|
|
171
|
-
console.error(
|
|
172
|
-
"fetch-logos failed for " +
|
|
173
|
-
result.failures.length +
|
|
174
|
-
" logo(s). Those files were not written. Do not upload this directory.",
|
|
175
|
-
);
|
|
176
|
-
return 1;
|
|
177
|
-
}
|
|
178
|
-
console.info("Wrote " + result.written.length + " logo(s) to " + args[0]);
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (require.main === module) {
|
|
183
|
-
runCli(process.argv.slice(2))
|
|
184
|
-
.then((code) => {
|
|
185
|
-
process.exitCode = code;
|
|
186
|
-
})
|
|
187
|
-
.catch((error) => {
|
|
188
|
-
console.error(error && error.message ? error.message : error);
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
module.exports = { fetchLogos, fetchLogo, runCli, verifyLogoHash };
|