1import { Platform } from 'expo-modules-core';
2import { PixelRatio } from 'react-native';
3import { PackagerAsset } from 'react-native/Libraries/Image/AssetRegistry';
4
5export type ResolvedAssetSource = {
6  __packager_asset: boolean;
7  width?: number;
8  height?: number;
9  uri: string;
10  scale: number;
11};
12
13// Returns the Metro dev server-specific asset location.
14function getScaledAssetPath(asset): string {
15  const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
16  const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
17  const type = !asset.type ? '' : `.${asset.type}`;
18  if (__DEV__) {
19    return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;
20  } else {
21    return asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + scaleSuffix + type;
22  }
23}
24
25export default class AssetSourceResolver {
26  serverUrl: string;
27  // where the jsbundle is being run from
28  // NOTE(EvanBacon): Never defined on web.
29  jsbundleUrl?: string | null;
30  // the asset to resolve
31  asset: PackagerAsset;
32
33  constructor(
34    serverUrl: string | undefined | null,
35    jsbundleUrl: string | undefined | null,
36    asset: PackagerAsset
37  ) {
38    this.serverUrl = serverUrl || 'https://expo.dev';
39    this.jsbundleUrl = null;
40    this.asset = asset;
41  }
42
43  // Always true for web runtimes
44  isLoadedFromServer(): boolean {
45    return true;
46  }
47
48  // Always false for web runtimes
49  isLoadedFromFileSystem(): boolean {
50    return false;
51  }
52
53  defaultAsset(): ResolvedAssetSource {
54    return this.assetServerURL();
55  }
56
57  /**
58   * @returns absolute remote URL for the hosted asset.
59   */
60  assetServerURL(): ResolvedAssetSource {
61    const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl);
62    fromUrl.searchParams.set('platform', Platform.OS);
63    fromUrl.searchParams.set('hash', this.asset.hash);
64    return this.fromSource(
65      // Relative on web
66      fromUrl.toString().replace(fromUrl.origin, '')
67    );
68  }
69
70  fromSource(source: string): ResolvedAssetSource {
71    return {
72      __packager_asset: true,
73      width: this.asset.width ?? undefined,
74      height: this.asset.height ?? undefined,
75      uri: source,
76      scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
77    };
78  }
79
80  static pickScale(scales: number[], deviceScale: number): number {
81    for (let i = 0; i < scales.length; i++) {
82      if (scales[i] >= deviceScale) {
83        return scales[i];
84      }
85    }
86    return scales[scales.length - 1] || 1;
87  }
88}
89