expo
Version:
The Expo SDK
166 lines (147 loc) • 4.45 kB
JavaScript
;
import { NativeModules, PixelRatio, Platform } from 'react-native';
import AssetRegistry from 'react-native/Libraries/Image/AssetRegistry';
import AssetSourceResolver
from 'react-native/Libraries/Image/AssetSourceResolver';
import resolveAssetSource
from 'react-native/Libraries/Image/resolveAssetSource';
import { manifest } from './Constants';
// Return { uri, hash } for an asset's file, picking the correct scale, based on
// its React Native metadata. If the asset isn't an image just picks the first
// file.
const pickScale = meta => {
// This logic is based on that in AssetSourceResolver.js, we just do it with
// our own tweaks for Exponent
const scale = meta.scales.length > 1
? AssetSourceResolver.pickScale(meta.scales, PixelRatio.get())
: 1;
const index = meta.scales.findIndex(s => s === scale);
const hash = meta.fileHashes[index] || meta.fileHashes[0];
const suffix =
'/' +
meta.name +
(scale === 1 ? '' : '@' + scale + 'x') +
'.' +
meta.type +
'?platform=' +
Platform.OS +
'&hash=' +
meta.hash;
if (/^https?:/.test(meta.httpServerLocation)) {
// This is a full URL, so we avoid prepending bundle URL/cloudfront
// This usually means Asset is on a different server, and the URL is present in the bundle
return {
uri: meta.httpServerLocation + suffix,
hash,
};
}
if (manifest.xde) {
// Development server URI is pieced together
return {
uri: manifest.bundleUrl.match(/^https?:\/\/.*?\//)[0] +
meta.httpServerLocation.replace(/^\/?/, '') +
suffix,
hash,
};
}
// CDN URI is based directly on the hash
return {
uri: 'https://d1wp6m56sqw74a.cloudfront.net/~assets/' + hash,
hash,
};
};
export default class Asset {
static byModule = {};
constructor({ name, type, hash, uri, width, height }) {
this.name = name;
this.type = type;
this.hash = hash;
this.uri = uri;
if (typeof width === 'number') {
this.width = width;
}
if (typeof height === 'number') {
this.height = height;
}
this.downloading = false;
this.downloaded = false;
this.downloadCallbacks = [];
}
static fromModule(moduleId) {
if (Asset.byModule[moduleId]) {
return Asset.byModule[moduleId];
}
// TODO(nikki): Make React Native's AssetRegistry save moduleId so we don't
// have to do this here.
const meta = AssetRegistry.getAssetByID(moduleId);
meta.moduleId = moduleId;
const { uri, hash } = pickScale(meta);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash,
uri,
width: meta.width,
height: meta.height,
});
Asset.byModule[moduleId] = asset;
return asset;
}
async downloadAsync() {
if (this.downloaded) {
return;
}
if (this.downloading) {
await new Promise((resolve, reject) =>
this.downloadCallbacks.push({ resolve, reject })
);
return;
}
this.downloading = true;
try {
const path = `ExponentAsset-${this.hash}.${this.type}`;
let exists, md5, uri;
({
exists,
md5,
uri,
} = await NativeModules.ExponentFileSystem.getInfoAsync(path, {
cache: true,
md5: true,
}));
if (!exists || md5 !== this.hash) {
({
md5,
uri,
} = await NativeModules.ExponentFileSystem.downloadAsync(
this.uri,
path,
{ cache: true, md5: true }
));
if (md5 !== this.hash) {
throw new Error(
`Downloaded file for asset '${this.name}.${this.type}' ` +
`failed MD5 integrity check`
);
}
}
this.localUri = uri;
this.downloaded = true;
this.downloadCallbacks.forEach(({ resolve }) => resolve());
} catch (e) {
this.downloadCallbacks.forEach(({ reject }) => reject(e));
throw e;
} finally {
this.downloading = false;
this.downloadCallbacks = [];
}
}
}
// Override React Native's asset resolution for `Image` components
resolveAssetSource.setCustomSourceTransformer(resolver => {
if (!resolver.asset.moduleId) {
return resolver.fromSource(pickScale(resolver.asset).uri);
}
const asset = Asset.fromModule(resolver.asset.moduleId);
return resolver.fromSource(asset.downloaded ? asset.localUri : asset.uri);
});