@@ -19,7 +19,7 @@ import {
1919 UndiciCauseTypeError ,
2020} from './errors' ;
2121import { RetryableJobError } from '../queue/errors' ;
22- import { normalizeImageUri , processImageCache } from '../images/image-cache' ;
22+ import { processImageCache } from '../images/image-cache' ;
2323import {
2424 RawMetadataLocale ,
2525 RawMetadataLocalizationCType ,
@@ -42,6 +42,12 @@ const METADATA_FETCH_HTTP_AGENT = new Agent({
4242
4343const PUBLIC_GATEWAY_IPFS_REPLACED = ENV . PUBLIC_GATEWAY_IPFS_REPLACED . split ( ',' ) ;
4444
45+ export type FetchableMetadataUrl = {
46+ url : URL ;
47+ gateway : 'ipfs' | 'arweave' | null ;
48+ fetchHeaders ?: Record < string , string > ;
49+ } ;
50+
4551/**
4652 * Fetches all the localized metadata JSONs for a token. First, it downloads the default metadata
4753 * JSON and parses it looking for other localizations. If those are found, each of them is then
@@ -174,9 +180,8 @@ async function parseMetadataForInsertion(
174180 let cachedImage : string | undefined ;
175181 let cachedThumbnailImage : string | undefined ;
176182 if ( image && typeof image === 'string' && ENV . IMAGE_CACHE_PROCESSOR_ENABLED ) {
177- const normalizedUrl = normalizeImageUri ( image ) ;
178183 [ cachedImage , cachedThumbnailImage ] = await processImageCache (
179- normalizedUrl ,
184+ image ,
180185 contract . principal ,
181186 token . token_number
182187 ) ;
@@ -245,14 +250,16 @@ async function parseMetadataForInsertion(
245250export async function fetchMetadata (
246251 httpUrl : URL ,
247252 contract_principal : string ,
248- token_number : bigint
253+ token_number : bigint ,
254+ headers ?: Record < string , string >
249255) : Promise < string | undefined > {
250256 const url = httpUrl . toString ( ) ;
251257 try {
252258 logger . info ( `MetadataFetch for ${ contract_principal } #${ token_number } from ${ url } ` ) ;
253259 const result = await request ( url , {
254260 method : 'GET' ,
255261 throwOnError : true ,
262+ headers,
256263 dispatcher :
257264 // Disable during tests so we can inject a global mock agent.
258265 process . env . NODE_ENV === 'test' ? undefined : METADATA_FETCH_HTTP_AGENT ,
@@ -306,10 +313,13 @@ export async function getMetadataFromUri(
306313 return parseJsonMetadata ( token_uri , content ) ;
307314 }
308315
309- // Support HTTP/S URLs otherwise
310- const httpUrl = getFetchableDecentralizedStorageUrl ( token_uri ) ;
316+ // Support HTTP/S URLs otherwise.
317+ // Transform the URL to use a public gateway if necessary.
318+ const { url : httpUrl , fetchHeaders } = getFetchableMetadataUrl ( token_uri ) ;
311319 const urlStr = httpUrl . toString ( ) ;
312- const content = await fetchMetadata ( httpUrl , contract_principal , token_number ) ;
320+
321+ // Fetch the metadata.
322+ const content = await fetchMetadata ( httpUrl , contract_principal , token_number , fetchHeaders ) ;
313323 return parseJsonMetadata ( urlStr , content ) ;
314324}
315325
@@ -342,30 +352,47 @@ function parseJsonMetadata(url: string, content?: string): RawMetadata {
342352 * @param uri - URL to convert
343353 * @returns Fetchable URL
344354 */
345- export function getFetchableDecentralizedStorageUrl ( uri : string ) : URL {
355+ export function getFetchableMetadataUrl ( uri : string ) : FetchableMetadataUrl {
346356 try {
347357 const parsedUri = new URL ( uri ) ;
358+ const result : FetchableMetadataUrl = {
359+ url : parsedUri ,
360+ gateway : null ,
361+ fetchHeaders : { } ,
362+ } ;
348363 if ( parsedUri . protocol === 'http:' || parsedUri . protocol === 'https:' ) {
349364 // If this is a known public IPFS gateway, replace it with `ENV.PUBLIC_GATEWAY_IPFS`.
350365 if ( PUBLIC_GATEWAY_IPFS_REPLACED . includes ( parsedUri . hostname ) ) {
351- return new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } ${ parsedUri . pathname } ` ) ;
366+ result . url = new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } ${ parsedUri . pathname } ` ) ;
367+ result . gateway = 'ipfs' ;
368+ } else {
369+ result . url = parsedUri ;
352370 }
353- return parsedUri ;
354- }
355- if ( parsedUri . protocol === 'ipfs:' ) {
371+ } else if ( parsedUri . protocol === 'ipfs:' ) {
356372 const host = parsedUri . host === 'ipfs' ? 'ipfs' : `ipfs/${ parsedUri . host } ` ;
357- return new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } /${ host } ${ parsedUri . pathname } ` ) ;
358- }
359- if ( parsedUri . protocol === 'ipns:' ) {
360- return new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } /${ parsedUri . host } ${ parsedUri . pathname } ` ) ;
373+ result . url = new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } /${ host } ${ parsedUri . pathname } ` ) ;
374+ result . gateway = 'ipfs' ;
375+ } else if ( parsedUri . protocol === 'ipns:' ) {
376+ result . url = new URL ( `${ ENV . PUBLIC_GATEWAY_IPFS } /${ parsedUri . host } ${ parsedUri . pathname } ` ) ;
377+ result . gateway = 'ipfs' ;
378+ } else if ( parsedUri . protocol === 'ar:' ) {
379+ result . url = new URL ( `${ ENV . PUBLIC_GATEWAY_ARWEAVE } /${ parsedUri . host } ${ parsedUri . pathname } ` ) ;
380+ result . gateway = 'arweave' ;
381+ } else {
382+ throw new MetadataParseError ( `Unsupported uri protocol: ${ uri } ` ) ;
361383 }
362- if ( parsedUri . protocol === 'ar:' ) {
363- return new URL ( `${ ENV . PUBLIC_GATEWAY_ARWEAVE } /${ parsedUri . host } ${ parsedUri . pathname } ` ) ;
384+
385+ if ( result . gateway === 'ipfs' && ENV . PUBLIC_GATEWAY_IPFS_EXTRA_HEADER ) {
386+ const [ key , value ] = ENV . PUBLIC_GATEWAY_IPFS_EXTRA_HEADER . split ( ':' ) ;
387+ result . fetchHeaders = {
388+ [ key . trim ( ) ] : value . trim ( ) ,
389+ } ;
364390 }
391+
392+ return result ;
365393 } catch ( error ) {
366394 throw new MetadataParseError ( `Invalid uri: ${ uri } ` ) ;
367395 }
368- throw new MetadataParseError ( `Unsupported uri protocol: ${ uri } ` ) ;
369396}
370397
371398export function parseDataUrl (
0 commit comments