1import { Platform, UnavailabilityError } from 'expo-modules-core'; 2import invariant from 'invariant'; 3import { Dimensions } from 'react-native'; 4 5type PackagerAsset = { 6 __packager_asset: boolean; 7 fileSystemLocation: string; 8 httpServerLocation: string; 9 width?: number; 10 height?: number; 11 scales: number[]; 12 hash: string; 13 name: string; 14 type: string; 15}; 16 17function getBasePath({ httpServerLocation }: PackagerAsset): string { 18 if (httpServerLocation[0] === '/') { 19 return httpServerLocation.substr(1); 20 } 21 return httpServerLocation; 22} 23 24export type ResolvedAssetSource = { 25 __packager_asset: boolean; 26 width?: number; 27 height?: number; 28 uri: string; 29 scale: number; 30}; 31 32function getScale(): number { 33 return Dimensions.get('window').scale; 34} 35 36function getScaledAssetPath(asset): string { 37 const scale = AssetSourceResolver.pickScale(asset.scales, getScale()); 38 const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; 39 const assetDir = getBasePath(asset); 40 return assetDir + '/' + asset.name + scaleSuffix + (asset.type ? `.${asset.type}` : ''); 41} 42 43export default class AssetSourceResolver { 44 serverUrl?: string | null; 45 // where the jsbundle is being run from 46 jsbundleUrl?: string | null; 47 // the asset to resolve 48 asset: PackagerAsset; 49 50 constructor( 51 serverUrl: string | undefined | null, 52 jsbundleUrl: string | undefined | null, 53 asset: PackagerAsset 54 ) { 55 this.serverUrl = serverUrl; 56 this.jsbundleUrl = jsbundleUrl; 57 this.asset = asset; 58 } 59 isLoadedFromServer(): boolean { 60 return !!this.serverUrl; 61 } 62 isLoadedFromFileSystem(): boolean { 63 return !!(this.jsbundleUrl && this.jsbundleUrl.startsWith('file://')); 64 } 65 defaultAsset(): ResolvedAssetSource { 66 if (this.isLoadedFromServer()) { 67 return this.assetServerURL(); 68 } 69 70 return this.scaledAssetURLNearBundle(); 71 } 72 assetServerURL(): ResolvedAssetSource { 73 invariant(!!this.serverUrl, 'need server to load from'); 74 return this.fromSource( 75 this.serverUrl + 76 getScaledAssetPath(this.asset) + 77 '?platform=' + 78 Platform.OS + 79 '&hash=' + 80 this.asset.hash 81 ); 82 } 83 scaledAssetPath(): ResolvedAssetSource { 84 return this.fromSource(getScaledAssetPath(this.asset)); 85 } 86 scaledAssetURLNearBundle(): ResolvedAssetSource { 87 const path = this.jsbundleUrl || ''; 88 return this.fromSource(path + getScaledAssetPath(this.asset)); 89 } 90 resourceIdentifierWithoutScale(): ResolvedAssetSource { 91 throw new UnavailabilityError('react-native', 'resourceIdentifierWithoutScale()'); 92 } 93 drawableFolderInBundle(): ResolvedAssetSource { 94 throw new UnavailabilityError('react-native', 'drawableFolderInBundle()'); 95 } 96 fromSource(source: string): ResolvedAssetSource { 97 return { 98 __packager_asset: true, 99 width: this.asset.width, 100 height: this.asset.height, 101 uri: source, 102 scale: AssetSourceResolver.pickScale(this.asset.scales, getScale()), 103 }; 104 } 105 106 static pickScale(scales: number[], deviceScale: number): number { 107 for (let i = 0; i < scales.length; i++) { 108 if (scales[i] >= deviceScale) { 109 return scales[i]; 110 } 111 } 112 return scales[scales.length - 1] || 1; 113 } 114} 115