1import { Platform } from 'expo-modules-core'; 2import { getAssetByID } from 'react-native/Libraries/Image/AssetRegistry'; 3import { selectAssetSource } from './AssetSources'; 4import * as AssetUris from './AssetUris'; 5import * as ImageAssets from './ImageAssets'; 6import { getLocalAssetUri } from './LocalAssets'; 7import { downloadAsync, IS_ENV_WITH_UPDATES_ENABLED } from './PlatformUtils'; 8import resolveAssetSource from './resolveAssetSource'; 9// @needsAudit 10/** 11 * The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its 12 * name and type) and provides facilities to load the asset data. 13 */ 14export class Asset { 15 /** 16 * @private 17 */ 18 static byHash = {}; 19 /** 20 * @private 21 */ 22 static byUri = {}; 23 /** 24 * The name of the asset file without the extension. Also without the part from `@` onward in the 25 * filename (used to specify scale factor for images). 26 */ 27 name; 28 /** 29 * The extension of the asset filename. 30 */ 31 type; 32 /** 33 * The MD5 hash of the asset's data. 34 */ 35 hash = null; 36 /** 37 * A URI that points to the asset's data on the remote server. When running the published version 38 * of your app, this refers to the location on Expo's asset server where Expo has stored your 39 * asset. When running the app from Expo CLI during development, this URI points to Expo CLI's 40 * server running on your computer and the asset is served directly from your computer. If you 41 * are not using Classic Updates (legacy), this field should be ignored as we ensure your assets 42 * are on device before before running your application logic. 43 */ 44 uri; 45 /** 46 * If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the 47 * `file://` URI pointing to the local file on the device that contains the asset data. 48 */ 49 localUri = null; 50 /** 51 * If the asset is an image, the width of the image data divided by the scale factor. The scale 52 * factor is the number after `@` in the filename, or `1` if not present. 53 */ 54 width = null; 55 /** 56 * If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present. 57 */ 58 height = null; 59 // @docsMissing 60 downloading = false; 61 // @docsMissing 62 downloaded = false; 63 /** 64 * @private 65 */ 66 _downloadCallbacks = []; 67 constructor({ name, type, hash = null, uri, width, height }) { 68 this.name = name; 69 this.type = type; 70 this.hash = hash; 71 this.uri = uri; 72 if (typeof width === 'number') { 73 this.width = width; 74 } 75 if (typeof height === 'number') { 76 this.height = height; 77 } 78 if (hash) { 79 this.localUri = getLocalAssetUri(hash, type); 80 if (this.localUri) { 81 this.downloaded = true; 82 } 83 } 84 if (Platform.OS === 'web') { 85 if (!name) { 86 this.name = AssetUris.getFilename(uri); 87 } 88 if (!type) { 89 this.type = AssetUris.getFileExtension(uri); 90 } 91 } 92 } 93 // @needsAudit 94 /** 95 * A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience. 96 * @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be 97 * just one module or URL without an Array. 98 * @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been 99 * saved to disk. 100 * @example 101 * ```ts 102 * const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png')); 103 * ``` 104 */ 105 static loadAsync(moduleId) { 106 const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId]; 107 return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync())); 108 } 109 // @needsAudit 110 /** 111 * Returns the [`Asset`](#asset) instance representing an asset given its module or URL. 112 * @param virtualAssetModule The value of `require('path/to/file')` for the asset or external 113 * network URL 114 * @return The [`Asset`](#asset) instance for the asset. 115 */ 116 static fromModule(virtualAssetModule) { 117 if (typeof virtualAssetModule === 'string') { 118 return Asset.fromURI(virtualAssetModule); 119 } 120 const meta = getAssetByID(virtualAssetModule); 121 if (!meta) { 122 throw new Error(`Module "${virtualAssetModule}" is missing from the asset registry`); 123 } 124 // Outside of the managed env we need the moduleId to initialize the asset 125 // because resolveAssetSource depends on it 126 if (!IS_ENV_WITH_UPDATES_ENABLED) { 127 const { uri } = resolveAssetSource(virtualAssetModule); 128 const asset = new Asset({ 129 name: meta.name, 130 type: meta.type, 131 hash: meta.hash, 132 uri, 133 width: meta.width, 134 height: meta.height, 135 }); 136 // TODO: FileSystem should probably support 'downloading' from drawable 137 // resources But for now it doesn't (it only supports raw resources) and 138 // React Native's Image works fine with drawable resource names for 139 // images. 140 if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) { 141 asset.localUri = asset.uri; 142 asset.downloaded = true; 143 } 144 Asset.byHash[meta.hash] = asset; 145 return asset; 146 } 147 return Asset.fromMetadata(meta); 148 } 149 // @docsMissing 150 static fromMetadata(meta) { 151 // The hash of the whole asset, not to be confused with the hash of a specific file returned 152 // from `selectAssetSource` 153 const metaHash = meta.hash; 154 if (Asset.byHash[metaHash]) { 155 return Asset.byHash[metaHash]; 156 } 157 const { uri, hash } = selectAssetSource(meta); 158 const asset = new Asset({ 159 name: meta.name, 160 type: meta.type, 161 hash, 162 uri, 163 width: meta.width, 164 height: meta.height, 165 }); 166 Asset.byHash[metaHash] = asset; 167 return asset; 168 } 169 // @docsMissing 170 static fromURI(uri) { 171 if (Asset.byUri[uri]) { 172 return Asset.byUri[uri]; 173 } 174 // Possibly a Base64-encoded URI 175 let type = ''; 176 if (uri.indexOf(';base64') > -1) { 177 type = uri.split(';')[0].split('/')[1]; 178 } 179 else { 180 const extension = AssetUris.getFileExtension(uri); 181 type = extension.startsWith('.') ? extension.substring(1) : extension; 182 } 183 const asset = new Asset({ 184 name: '', 185 type, 186 hash: null, 187 uri, 188 }); 189 Asset.byUri[uri] = asset; 190 return asset; 191 } 192 // @needsAudit 193 /** 194 * Downloads the asset data to a local file in the device's cache directory. Once the returned 195 * promise is fulfilled without error, the [`localUri`](#assetlocaluri) field of this asset points 196 * to a local file containing the asset data. The asset is only downloaded if an up-to-date local 197 * file for the asset isn't already present due to an earlier download. The downloaded `Asset` 198 * will be returned when the promise is resolved. 199 * @return Returns a Promise which fulfills with an `Asset` instance. 200 */ 201 async downloadAsync() { 202 if (this.downloaded) { 203 return this; 204 } 205 if (this.downloading) { 206 await new Promise((resolve, reject) => { 207 this._downloadCallbacks.push({ resolve, reject }); 208 }); 209 return this; 210 } 211 this.downloading = true; 212 try { 213 if (Platform.OS === 'web') { 214 if (ImageAssets.isImageType(this.type)) { 215 const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri); 216 this.width = width; 217 this.height = height; 218 this.name = name; 219 } 220 else { 221 this.name = AssetUris.getFilename(this.uri); 222 } 223 } 224 this.localUri = await downloadAsync(this.uri, this.hash, this.type, this.name); 225 this.downloaded = true; 226 this._downloadCallbacks.forEach(({ resolve }) => resolve()); 227 } 228 catch (e) { 229 this._downloadCallbacks.forEach(({ reject }) => reject(e)); 230 throw e; 231 } 232 finally { 233 this.downloading = false; 234 this._downloadCallbacks = []; 235 } 236 return this; 237 } 238} 239//# sourceMappingURL=Asset.js.map