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  return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;
19}
20
21export default class AssetSourceResolver {
22  serverUrl: string;
23  // where the jsbundle is being run from
24  // NOTE(EvanBacon): Never defined on web.
25  jsbundleUrl?: string | null;
26  // the asset to resolve
27  asset: PackagerAsset;
28
29  constructor(
30    serverUrl: string | undefined | null,
31    jsbundleUrl: string | undefined | null,
32    asset: PackagerAsset
33  ) {
34    if (!serverUrl) {
35      throw new Error('Web assets require a server URL');
36    }
37
38    this.serverUrl = serverUrl;
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(fromUrl.toString());
65  }
66
67  fromSource(source: string): ResolvedAssetSource {
68    return {
69      __packager_asset: true,
70      width: this.asset.width ?? undefined,
71      height: this.asset.height ?? undefined,
72      uri: source,
73      scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
74    };
75  }
76
77  static pickScale(scales: number[], deviceScale: number): number {
78    for (let i = 0; i < scales.length; i++) {
79      if (scales[i] >= deviceScale) {
80        return scales[i];
81      }
82    }
83    return scales[scales.length - 1] || 1;
84  }
85}
86