UNPKG

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