UNPKG

4.04 kBJavaScriptView Raw
1'use strict';
2
3import {
4 NativeModules,
5 PixelRatio,
6 Platform,
7} from 'react-native';
8
9import AssetRegistry from 'react-native/Libraries/Image/AssetRegistry';
10import AssetSourceResolver from 'react-native/Libraries/Image/AssetSourceResolver';
11import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
12
13import { manifest } from './Constants';
14
15// Return { uri, hash } for an asset's file, picking the correct scale, based on
16// its React Native metadata. If the asset isn't an image just picks the first
17// file.
18const pickScale = (meta) => {
19 // This logic is based on that in AssetSourceResolver.js, we just do it with
20 // our own tweaks for Exponent
21
22 const scale = meta.scales.length > 1 ?
23 AssetSourceResolver.pickScale(meta.scales, PixelRatio.get()) :
24 1;
25 const index = meta.scales.findIndex(s => s === scale);
26 const hash = meta.fileHashes[index] || meta.fileHashes[0];
27
28 if (manifest.xde) {
29 // Development server URI is pieced together
30 const suffix = scale === 1 ? '' : '@' + scale + 'x';
31 return {
32 uri: manifest.bundleUrl.match(/^https?:\/\/.*?\//)[0] +
33 meta.httpServerLocation.replace(/^\/?/, '') +
34 '/' + meta.name + suffix + '.' + meta.type +
35 '?platform=' + Platform.OS + '&hash=' + meta.hash,
36 hash,
37 };
38 }
39
40 // CDN URI is based directly on the hash
41 return {
42 uri: 'https://d1wp6m56sqw74a.cloudfront.net/~assets/' + hash,
43 hash,
44 };
45};
46
47export default class Asset {
48 static byModule = {};
49
50 constructor({ name, type, hash, uri, width, height }) {
51 this.name = name;
52 this.type = type;
53 this.hash = hash;
54 this.uri = uri;
55 if (typeof width === 'number') {
56 this.width = width;
57 }
58 if (typeof height === 'number') {
59 this.height = height;
60 }
61
62 this.downloading = false;
63 this.downloaded = false;
64 this.downloadCallbacks = [];
65 }
66
67 static fromModule(moduleId) {
68 if (Asset.byModule[moduleId]) {
69 return Asset.byModule[moduleId];
70 }
71
72 // TODO(nikki): Make React Native's AssetRegistry save moduleId so we don't
73 // have to do this here.
74 const meta = AssetRegistry.getAssetByID(moduleId);
75 meta.moduleId = moduleId;
76 const { uri, hash } = pickScale(meta);
77
78 const asset = new Asset({
79 name: meta.name,
80 type: meta.type,
81 hash,
82 uri,
83 width: meta.width,
84 height: meta.height,
85 });
86 Asset.byModule[moduleId] = asset;
87 return asset;
88 }
89
90 async downloadAsync() {
91 if (this.downloaded) {
92 return;
93 }
94 if (this.downloading) {
95 await new Promise((resolve, reject) =>
96 this.downloadCallbacks.push({ resolve, reject }));
97 return;
98 }
99 this.downloading = true;
100
101 try {
102 const path = `ExponentAsset-${this.hash}.${this.type}`;
103 let exists, md5, uri;
104 ({ exists, md5, uri } = await NativeModules.ExponentFileSystem.getInfoAsync(
105 path, { cache: true, md5: true }));
106 if (!exists || md5 !== this.hash) {
107 ({ md5, uri } = await NativeModules.ExponentFileSystem.downloadAsync(
108 this.uri, path, { cache: true, md5: true }));
109 if (md5 !== this.hash) {
110 throw new Error(`Downloaded file for asset '${this.name}.${this.type}' ` +
111 `failed MD5 integrity check`);
112 }
113 }
114 this.localUri = uri;
115 this.downloaded = true;
116 this.downloadCallbacks.forEach(({ resolve }) => resolve());
117 } catch (e) {
118 this.downloadCallbacks.forEach(({ reject }) => reject(e));
119 throw e;
120 } finally {
121 this.downloading = false;
122 this.downloadCallbacks = [];
123 }
124 }
125}
126
127// Override React Native's asset resolution for `Image` components
128resolveAssetSource.setCustomSourceTransformer((resolver) => {
129 if (!resolver.asset.moduleId) {
130 return resolver.fromSource(pickScale(resolver.asset).uri);
131 }
132 const asset = Asset.fromModule(resolver.asset.moduleId);
133 return resolver.fromSource(asset.downloaded ? asset.localUri : asset.uri);
134});
135