1import { Platform } from 'expo-modules-core'; 2import { PixelRatio } from 'react-native'; 3import { PackagerAsset } from 'react-native/Libraries/Image/AssetRegistry'; 4 5export type ResolvedAssetSource = { 6 __packager_asset: boolean; 7 width?: number; 8 height?: number; 9 uri: string; 10 scale: number; 11}; 12 13// Returns the Metro dev server-specific asset location. 14function getScaledAssetPath(asset): string { 15 const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get()); 16 const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; 17 const type = !asset.type ? '' : `.${asset.type}`; 18 return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type; 19} 20 21export default class AssetSourceResolver { 22 serverUrl: string; 23 // where the jsbundle is being run from 24 // NOTE(EvanBacon): Never defined on web. 25 jsbundleUrl?: string | null; 26 // the asset to resolve 27 asset: PackagerAsset; 28 29 constructor( 30 serverUrl: string | undefined | null, 31 jsbundleUrl: string | undefined | null, 32 asset: PackagerAsset 33 ) { 34 if (!serverUrl) { 35 throw new Error('Web assets require a server URL'); 36 } 37 38 this.serverUrl = serverUrl; 39 this.jsbundleUrl = null; 40 this.asset = asset; 41 } 42 43 // Always true for web runtimes 44 isLoadedFromServer(): boolean { 45 return true; 46 } 47 48 // Always false for web runtimes 49 isLoadedFromFileSystem(): boolean { 50 return false; 51 } 52 53 defaultAsset(): ResolvedAssetSource { 54 return this.assetServerURL(); 55 } 56 57 /** 58 * @returns absolute remote URL for the hosted asset. 59 */ 60 assetServerURL(): ResolvedAssetSource { 61 const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl); 62 fromUrl.searchParams.set('platform', Platform.OS); 63 fromUrl.searchParams.set('hash', this.asset.hash); 64 return this.fromSource(fromUrl.toString()); 65 } 66 67 fromSource(source: string): ResolvedAssetSource { 68 return { 69 __packager_asset: true, 70 width: this.asset.width ?? undefined, 71 height: this.asset.height ?? undefined, 72 uri: source, 73 scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()), 74 }; 75 } 76 77 static pickScale(scales: number[], deviceScale: number): number { 78 for (let i = 0; i < scales.length; i++) { 79 if (scales[i] >= deviceScale) { 80 return scales[i]; 81 } 82 } 83 return scales[scales.length - 1] || 1; 84 } 85} 86