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