feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

View File

@@ -0,0 +1,277 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 10.0.10 — 2024-06-20
### 🐛 Bug fixes
- Fixed `PlatformUtils.ts` to have the correct export placeholders for react-native-web ([#29791](https://github.com/expo/expo/pull/29791) by [@Bram-dc](https://github.com/Bram-dc))
## 10.0.9 — 2024-06-13
### 💡 Others
- Removed @react-native/assets-registry dependency. ([#29541](https://github.com/expo/expo/pull/29541) by [@kudo](https://github.com/kudo))
## 10.0.8 — 2024-06-06
_This version does not introduce any user-facing changes._
## 10.0.7 — 2024-06-05
### 💡 Others
- Pin @react-native subpackage versions to 0.74.83. ([#29441](https://github.com/expo/expo/pull/29441) by [@kudo](https://github.com/kudo))
## 10.0.6 — 2024-05-03
### 🐛 Bug fixes
- Fixed `downloadAsync()` does not support Android resources from release builds. ([#28604](https://github.com/expo/expo/pull/28604) by [@kudo](https://github.com/kudo))
## 10.0.5 — 2024-05-02
_This version does not introduce any user-facing changes._
## 10.0.4 — 2024-05-01
_This version does not introduce any user-facing changes._
## 10.0.3 — 2024-04-24
### 🐛 Bug fixes
- Fix `TypeError: (0, _ExpoAsset.downloadAsync) is not a function` when loading assets using Expo Web. ([#28405](https://github.com/expo/expo/pull/28405) by [@jamiees2](https://github.com/jamiees2))
### 💡 Others
- Update mocks for SDK51. ([#28424](https://github.com/expo/expo/pull/28424) by [@aleqsio](https://github.com/aleqsio))
## 10.0.2 — 2024-04-23
_This version does not introduce any user-facing changes._
## 10.0.1 — 2024-04-22
_This version does not introduce any user-facing changes._
## 10.0.0 — 2024-04-18
### 🎉 New features
- Added config plugin to allow assets to be linked at build time. ([#27052](https://github.com/expo/expo/pull/27052) by [@alanjhughes](https://github.com/alanjhughes))
- Add Apple TV support to the new iOS native module. ([#27823](https://github.com/expo/expo/pull/27823) by [@douglowder](https://github.com/douglowder))
### 🐛 Bug fixes
- Fixed "Tried to resolve a promise more than once" crash on iOS. ([#27672](https://github.com/expo/expo/pull/27672) by [@kudo](https://github.com/kudo))
### 💡 Others
- Create native module for iOS and Android. Migrate `downloadAsync` to a native implementation. ([#27369](https://github.com/expo/expo/pull/27369) by [@aleqsio](https://github.com/aleqsio))
- Remove MD5 checksum verification for remote assets. This does not change method signatures nor require changes to your application code, and should not affect any apps in practice since this behavior was used only for apps that used Classic Updates, support for which ended with SDK 50. ([#25614](https://github.com/expo/expo/pull/25614) by [@ide](https://github.com/ide))
- [expo-updates] Migrate to requireNativeModule/requireOptionalNativeModule. ([#25648](https://github.com/expo/expo/pull/25648) by [@wschurman](https://github.com/wschurman))
- Clean up some asset stuff. ([#26310](https://github.com/expo/expo/pull/26310) by [@wschurman](https://github.com/wschurman))
- Remove most of Constants.appOwnership. ([#26313](https://github.com/expo/expo/pull/26313) by [@wschurman](https://github.com/wschurman))
- Remove assetUrlOverride and assetMapOverride. ([#26314](https://github.com/expo/expo/pull/26314) by [@wschurman](https://github.com/wschurman))
- Improve updates types and clarity in expo-asset. ([#26337](https://github.com/expo/expo/pull/26337) by [@wschurman](https://github.com/wschurman))
- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
## 9.0.2 - 2024-01-05
### 🐛 Bug fixes
- Fix relative URLs and support for `unstable_path` in development. ([#26084](https://github.com/expo/expo/pull/26084) by [@EvanBacon](https://github.com/EvanBacon))
## 9.0.1 — 2023-12-13
_This version does not introduce any user-facing changes._
## 9.0.0 — 2023-12-12
### 🛠 Breaking changes
- Removed support for the Classic Updates service for SDK 50 ([announcement](https://blog.expo.dev/sunsetting-expo-publish-and-classic-updates-6cb9cd295378)). Specifically, references to the Classic Updates CDN were removed. [Migrate](https://docs.expo.dev/eas-update/migrate-from-classic-updates/) to EAS or other service that conforms to the modern [Expo Updates protocol](https://docs.expo.dev/technical-specs/expo-updates-1/). ([#25613](https://github.com/expo/expo/pull/25613) by [@ide](https://github.com/ide))
### 🎉 New features
- Added support for React Native 0.73.0. ([#24971](https://github.com/expo/expo/pull/24971), [#25453](https://github.com/expo/expo/pull/25453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
### 💡 Others
- Removed references to legacy `bundledAssets` constant from `expo-file-system` that was used only in standalone apps. ([#25484](https://github.com/expo/expo/pull/25484) by [@tsapeta](https://github.com/tsapeta))
## 8.14.0 — 2023-11-14
### 🐛 Bug fixes
- fix URLs in development. ([#25202](https://github.com/expo/expo/pull/25202) by [@EvanBacon](https://github.com/EvanBacon))
### 💡 Others
- Collapse re-export of `react-native/Libraries/Image/AssetRegistry` to `@react-native/assets-registry/registry`. ([#25265](https://github.com/expo/expo/pull/25265) by [@EvanBacon](https://github.com/EvanBacon))
- Migrate to new standard `URL` support on native. ([#24941](https://github.com/expo/expo/pull/24941) by [@EvanBacon](https://github.com/EvanBacon))
- Renamed `unimodule.json` to `expo-module.config.json`. ([#25100](https://github.com/expo/expo/pull/25100) by [@reichhartd](https://github.com/reichhartd))
## 8.13.0 — 2023-10-17
### 🐛 Bug fixes
- URL encode asset paths defined as query parameter. ([#24562](https://github.com/expo/expo/pull/24562) by [@byCedric](https://github.com/byCedric))
## 8.12.1 — 2023-09-16
_This version does not introduce any user-facing changes._
## 8.12.0 — 2023-09-04
### 🛠 Breaking changes
- Convert `../` to `_` for the property `httpServerLocation` in `hashAssetFiles` (Metro asset pre-processor) to support assets in monorepos the same everywhere. ([#24090](https://github.com/expo/expo/pull/24090) by [@EvanBacon](https://github.com/EvanBacon))
## 8.11.0 — 2023-08-02
_This version does not introduce any user-facing changes._
## 8.10.1 — 2023-06-24
_This version does not introduce any user-facing changes._
## 8.10.0 — 2023-06-13
_This version does not introduce any user-facing changes._
## 8.9.2 — 2023-05-08
### 🐛 Bug fixes
- Fixed monorepo asset resolution in production for Metro web. ([#22094](https://github.com/expo/expo/pull/22094) by [@EvanBacon](https://github.com/EvanBacon))
## 8.9.1 - 2023-03-08
### 🐛 Bug fixes
- Fixed `@react-native/assets-registry` module not found issue on Web. ([#21469](https://github.com/expo/expo/pull/21469) by [@kudo](https://github.com/kudo))
## 8.9.0 — 2023-02-09
_This version does not introduce any user-facing changes._
## 8.8.0 — 2023-02-03
### 🐛 Bug fixes
- Fix loading Metro web assets from origins other than `/`. ([#20258](https://github.com/expo/expo/pull/20258) by [@EvanBacon](https://github.com/EvanBacon))
### 💡 Others
- Remove unused web features. ([#20258](https://github.com/expo/expo/pull/20258) by [@EvanBacon](https://github.com/EvanBacon))
## 8.6.2 — 2022-10-25
_This version does not introduce any user-facing changes._
## 8.6.1 — 2022-07-19
_This version does not introduce any user-facing changes._
## 8.6.0 — 2022-07-07
_This version does not introduce any user-facing changes._
## 8.5.0 — 2022-04-18
### 💡 Others
- Swap out Cloudfront CDN for `classic-assets.eascdn.net`. ([#15781](https://github.com/expo/expo/pull/15781)) by [@quinlanj](https://github.com/quinlanj)
## 8.4.6 - 2022-01-13
### 🐛 Bug fixes
- Fix missing `getManifest2()` function on web. ([#15891](https://github.com/expo/expo/pull/15891)) by [@jonsamp](https://github.com/jonsamp) ([#15891](https://github.com/expo/expo/pull/15891) by [@jonsamp](https://github.com/jonsamp))
## 8.4.5 — 2021-12-21
### 🐛 Bug fixes
- Fix an issue preventing the loading of assets using expo-updates manifests during local development. ([#15667](https://github.com/expo/expo/pull/15667)) by [@jonsamp](https://github.com/jonsamp)
## 8.4.4 — 2021-11-17
### 🐛 Bug fixes
- Fix `fromModule` on restrictive (Snack) web environments. ([#14435](https://github.com/expo/expo/pull/14435) by [@IjzerenHein](https://github.com/IjzerenHein))
## 8.4.1 — 2021-10-01
### 💡 Others
- Updated `@testing-library/react-hooks` to version `7.0.1`. ([#14552](https://github.com/expo/expo/pull/14552)) by [@Simek](https://github.com/Simek))
## 8.4.0 — 2021-09-08
### 🎉 New features
- Reapply [#12624](https://github.com/expo/expo/pull/12624) ([#13789](https://github.com/expo/expo/pull/13789) by [@jkhales](https://github.com/jkhales))
## 8.3.2 — 2021-04-21
### 🎉 New features
- Find local assets without extensions. ([#12624](https://github.com/expo/expo/pull/12624) by [@jkhales](https://github.com/jkhales))
## 8.3.1 — 2021-03-23
### 🐛 Bug fixes
- Removed annoying yellowbox warning message in bare workflow when there's no manifest available. ([#12237](https://github.com/expo/expo/pull/12237) by [@bbarthec](https://github.com/bbarthec))
## 8.3.0 — 2021-03-10
### 🐛 Bug fixes
- Remove peerDependencies and unimodulePeerDependencies from Expo modules. ([#11980](https://github.com/expo/expo/pull/11980) by [@brentvatne](https://github.com/brentvatne))
## 8.2.2 — 2021-01-15
_This version does not introduce any user-facing changes._
## 8.2.1 — 2020-11-17
_This version does not introduce any user-facing changes._
## 8.2.0 — 2020-08-18
### 🎉 New features
- Add `useAssets` hook to simplify assets handling. ([#8928](https://github.com/expo/expo/pull/8928) by [@bycedric](https://github.com/bycedric))
### 🐛 Bug fixes
- Fixed `Asset.loadAsync()` TypeScript signature to match `Asset.fromModule()` types. ([#9246](https://github.com/expo/expo/pull/9246) by [@barthap](https://github.com/barthap))
## 8.1.7 — 2020-05-29
_This version does not introduce any user-facing changes._
## 8.1.6 — 2020-05-27
_This version does not introduce any user-facing changes._
## 8.1.5
### 🎉 New features
- `asset.downloadAsync()` returns the resolved `Asset` when it resolves. ([#8646](https://github.com/expo/expo/pull/8646) by [@EvanBacon](https://github.com/EvanBacon))
- `Asset.loadAsync()` returns an array of resolved `Asset`s when it finishes loading the resources. ([#8646](https://github.com/expo/expo/pull/8646) by [@EvanBacon](https://github.com/EvanBacon))
- Added support for the `expo-updates` **no-publish workflow**. ([#8003](https://github.com/expo/expo/pull/8003) by [@esamelson](https://github.com/esamelson))

View File

@@ -0,0 +1,26 @@
# expo-asset
An Expo universal module to download assets and pass them into other APIs
# API documentation
- [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/asset.mdx)
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/asset/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/asset/). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npx expo install expo-asset
```
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

View File

@@ -0,0 +1,19 @@
apply plugin: 'com.android.library'
group = 'expo.modules.asset'
version = '10.0.10'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useDefaultAndroidSdkVersions()
useExpoPublishing()
android {
namespace "expo.modules.asset"
defaultConfig {
versionCode 1
versionName "10.0.10"
}
}

View File

@@ -0,0 +1,2 @@
<manifest>
</manifest>

View File

@@ -0,0 +1,98 @@
package expo.modules.asset
import android.content.Context
import android.net.Uri
import expo.modules.interfaces.filesystem.Permission
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.exception.CodedException
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.net.URI
import java.security.DigestInputStream
import java.security.MessageDigest
internal class UnableToDownloadAssetException(url: String) :
CodedException("Unable to download asset from url: $url")
class AssetModule : Module() {
private val context: Context
get() = appContext.reactContext ?: throw Exceptions.AppContextLost()
private fun getMD5HashOfFilePath(uri: URI): String {
val md = MessageDigest.getInstance("MD5")
return md.digest(uri.toString().toByteArray()).joinToString("") { "%02x".format(it) }
}
private fun getMD5HashOfFileContent(file: File): String? {
return try {
FileInputStream(file).use { inputStream ->
DigestInputStream(
inputStream,
MessageDigest.getInstance("MD5")
).use { digestInputStream ->
digestInputStream.messageDigest.digest().joinToString(separator = "") { "%02x".format(it) }
}
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private suspend fun downloadAsset(appContext: AppContext, uri: URI, localUrl: File): Uri {
if (localUrl.parentFile?.exists() != true) {
localUrl.mkdirs()
}
if (appContext.filePermission?.getPathPermissions(appContext.reactContext, localUrl.parent)?.contains(Permission.WRITE) != true) {
throw UnableToDownloadAssetException(uri.toString())
}
return withContext(appContext.backgroundCoroutineScope.coroutineContext) {
try {
val inputStream = when {
uri.toString().contains(":").not() -> openAssetResourceStream(context, uri.toString())
else -> uri.toURL().openStream()
}
inputStream.use { input ->
localUrl.outputStream().use { output ->
input.copyTo(output)
}
}
Uri.fromFile(localUrl)
} catch (e: Exception) {
throw UnableToDownloadAssetException(uri.toString())
}
}
}
override fun definition() = ModuleDefinition {
Name("ExpoAsset")
AsyncFunction("downloadAsync") Coroutine { uri: URI, md5Hash: String?, type: String ->
if (uri.scheme === "file") {
return@Coroutine uri
}
val cacheFileId = md5Hash ?: getMD5HashOfFilePath(uri)
val cacheDirectory = appContext.cacheDirectory
val localUrl = File("$cacheDirectory/ExponentAsset-$cacheFileId.$type")
if (!localUrl.exists()) {
return@Coroutine downloadAsset(appContext, uri, localUrl)
}
if (md5Hash == null || md5Hash == getMD5HashOfFileContent(localUrl)) {
return@Coroutine Uri.fromFile(localUrl)
}
return@Coroutine downloadAsset(appContext, uri, localUrl)
}
}
}

View File

@@ -0,0 +1,24 @@
package expo.modules.asset
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import java.io.InputStream
/**
* Opens an Android resource as stream.
*/
internal fun openAssetResourceStream(context: Context, assetName: String): InputStream {
val resources = context.resources
val resId = findResourceId(context, assetName) ?: throw Resources.NotFoundException(assetName)
return resources.openRawResource(resId)
}
@SuppressLint("DiscouragedApi")
private fun findResourceId(context: Context, assetName: String): Int? {
val resources = context.resources
val packageName = context.packageName
// react-native core and expo-assets plugin will put resource in `res/raw` or `res/drawable`
return resources.getIdentifier(assetName, "raw", packageName).takeIf { it != 0 }
?: resources.getIdentifier(assetName, "drawable", packageName).takeIf { it != 0 }
}

View File

@@ -0,0 +1 @@
module.exports = require('./plugin/build/withAssets');

View File

@@ -0,0 +1,92 @@
import { AssetMetadata } from './AssetSources';
export type AssetDescriptor = {
name: string;
type: string;
hash?: string | null;
uri: string;
width?: number | null;
height?: number | null;
};
export { AssetMetadata };
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
*/
export declare class Asset {
private static byHash;
private static byUri;
/**
* The name of the asset file without the extension. Also without the part from `@` onward in the
* filename (used to specify scale factor for images).
*/
name: string;
/**
* The extension of the asset filename.
*/
readonly type: string;
/**
* The MD5 hash of the asset's data.
*/
readonly hash: string | null;
/**
* A URI that points to the asset's data on the remote server. When running the published version
* of your app, this refers to the location on Expo's asset server where Expo has stored your
* asset. When running the app from Expo CLI during development, this URI points to Expo CLI's
* server running on your computer and the asset is served directly from your computer. If you
* are not using Classic Updates (legacy), this field should be ignored as we ensure your assets
* are on device before before running your application logic.
*/
readonly uri: string;
/**
* If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the
* `file://` URI pointing to the local file on the device that contains the asset data.
*/
localUri: string | null;
/**
* If the asset is an image, the width of the image data divided by the scale factor. The scale
* factor is the number after `@` in the filename, or `1` if not present.
*/
width: number | null;
/**
* If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.
*/
height: number | null;
private downloading;
/**
* Whether the asset has finished downloading from a call to [`downloadAsync()`](#downloadasync).
*/
downloaded: boolean;
private _downloadCallbacks;
constructor({ name, type, hash, uri, width, height }: AssetDescriptor);
/**
* A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.
* @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be
* just one module or URL without an Array.
* @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been
* saved to disk.
* @example
* ```ts
* const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));
* ```
*/
static loadAsync(moduleId: number | number[] | string | string[]): Promise<Asset[]>;
/**
* Returns the [`Asset`](#asset) instance representing an asset given its module or URL.
* @param virtualAssetModule The value of `require('path/to/file')` for the asset or external
* network URL
* @return The [`Asset`](#asset) instance for the asset.
*/
static fromModule(virtualAssetModule: number | string): Asset;
static fromMetadata(meta: AssetMetadata): Asset;
static fromURI(uri: string): Asset;
/**
* Downloads the asset data to a local file in the device's cache directory. Once the returned
* promise is fulfilled without error, the [`localUri`](#localuri) field of this asset points
* to a local file containing the asset data. The asset is only downloaded if an up-to-date local
* file for the asset isn't already present due to an earlier download. The downloaded `Asset`
* will be returned when the promise is resolved.
* @return Returns a Promise which fulfills with an `Asset` instance.
*/
downloadAsync(): Promise<this>;
}
//# sourceMappingURL=Asset.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Asset.d.ts","sourceRoot":"","sources":["../src/Asset.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAqB,MAAM,gBAAgB,CAAC;AASlE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAOF,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;GAGG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAM;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAM;IAE1B;;;OAGG;IACI,IAAI,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C;;;;;;;OAOG;IACH,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACI,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC;;;OAGG;IACI,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC;;OAEG;IACI,MAAM,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC,OAAO,CAAC,WAAW,CAAkB;IAErC;;OAEG;IACI,UAAU,EAAE,OAAO,CAAS;IAEnC,OAAO,CAAC,kBAAkB,CAAkC;gBAEhD,EAAE,IAAI,EAAE,IAAI,EAAE,IAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,eAAe;IA+B5E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMnF;;;;;OAKG;IACH,MAAM,CAAC,UAAU,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK;IAyC7D,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,KAAK;IAsB/C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IA2BlC;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAoCrC"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=Asset.fx.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Asset.fx.d.ts","sourceRoot":"","sources":["../src/Asset.fx.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,22 @@
import { Asset } from './Asset';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import { setCustomSourceTransformer } from './resolveAssetSource';
// Override React Native's asset resolution for `Image` components in contexts where it matters
if (IS_ENV_WITH_LOCAL_ASSETS) {
setCustomSourceTransformer((resolver) => {
try {
// Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists
if (resolver.asset.fileHashes) {
const asset = Asset.fromMetadata(resolver.asset);
return resolver.fromSource(asset.downloaded ? asset.localUri : asset.uri);
}
else {
return resolver.defaultAsset();
}
}
catch {
return resolver.defaultAsset();
}
});
}
//# sourceMappingURL=Asset.fx.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Asset.fx.js","sourceRoot":"","sources":["../src/Asset.fx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE,+FAA+F;AAC/F,IAAI,wBAAwB,EAAE;IAC5B,0BAA0B,CAAC,CAAC,QAAQ,EAAE,EAAE;QACtC,IAAI;YACF,2FAA2F;YAC3F,IAAI,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE;gBAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACjD,OAAO,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,QAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;aAC5E;iBAAM;gBACL,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;aAChC;SACF;QAAC,MAAM;YACN,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;SAChC;IACH,CAAC,CAAC,CAAC;CACJ","sourcesContent":["import { Asset } from './Asset';\nimport { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';\nimport { setCustomSourceTransformer } from './resolveAssetSource';\n\n// Override React Native's asset resolution for `Image` components in contexts where it matters\nif (IS_ENV_WITH_LOCAL_ASSETS) {\n setCustomSourceTransformer((resolver) => {\n try {\n // Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists\n if (resolver.asset.fileHashes) {\n const asset = Asset.fromMetadata(resolver.asset);\n return resolver.fromSource(asset.downloaded ? asset.localUri! : asset.uri);\n } else {\n return resolver.defaultAsset();\n }\n } catch {\n return resolver.defaultAsset();\n }\n });\n}\n"]}

View File

@@ -0,0 +1,231 @@
import { getAssetByID } from '@react-native/assets-registry/registry';
import { Platform } from 'expo-modules-core';
import { selectAssetSource } from './AssetSources';
import * as AssetUris from './AssetUris';
import { downloadAsync } from './ExpoAsset';
import * as ImageAssets from './ImageAssets';
import { getLocalAssetUri } from './LocalAssets';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import resolveAssetSource from './resolveAssetSource';
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
*/
export class Asset {
static byHash = {};
static byUri = {};
/**
* The name of the asset file without the extension. Also without the part from `@` onward in the
* filename (used to specify scale factor for images).
*/
name;
/**
* The extension of the asset filename.
*/
type;
/**
* The MD5 hash of the asset's data.
*/
hash = null;
/**
* A URI that points to the asset's data on the remote server. When running the published version
* of your app, this refers to the location on Expo's asset server where Expo has stored your
* asset. When running the app from Expo CLI during development, this URI points to Expo CLI's
* server running on your computer and the asset is served directly from your computer. If you
* are not using Classic Updates (legacy), this field should be ignored as we ensure your assets
* are on device before before running your application logic.
*/
uri;
/**
* If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the
* `file://` URI pointing to the local file on the device that contains the asset data.
*/
localUri = null;
/**
* If the asset is an image, the width of the image data divided by the scale factor. The scale
* factor is the number after `@` in the filename, or `1` if not present.
*/
width = null;
/**
* If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.
*/
height = null;
downloading = false;
/**
* Whether the asset has finished downloading from a call to [`downloadAsync()`](#downloadasync).
*/
downloaded = false;
_downloadCallbacks = [];
constructor({ name, type, hash = null, 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;
}
if (hash) {
this.localUri = getLocalAssetUri(hash, type);
if (this.localUri) {
this.downloaded = true;
}
}
if (Platform.OS === 'web') {
if (!name) {
this.name = AssetUris.getFilename(uri);
}
if (!type) {
this.type = AssetUris.getFileExtension(uri);
}
}
}
// @needsAudit
/**
* A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.
* @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be
* just one module or URL without an Array.
* @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been
* saved to disk.
* @example
* ```ts
* const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));
* ```
*/
static loadAsync(moduleId) {
const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId];
return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync()));
}
// @needsAudit
/**
* Returns the [`Asset`](#asset) instance representing an asset given its module or URL.
* @param virtualAssetModule The value of `require('path/to/file')` for the asset or external
* network URL
* @return The [`Asset`](#asset) instance for the asset.
*/
static fromModule(virtualAssetModule) {
if (typeof virtualAssetModule === 'string') {
return Asset.fromURI(virtualAssetModule);
}
const meta = getAssetByID(virtualAssetModule);
if (!meta) {
throw new Error(`Module "${virtualAssetModule}" is missing from the asset registry`);
}
// Outside of the managed env we need the moduleId to initialize the asset
// because resolveAssetSource depends on it
if (!IS_ENV_WITH_LOCAL_ASSETS) {
// null-check is performed above with `getAssetByID`.
const { uri } = resolveAssetSource(virtualAssetModule);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash: meta.hash,
uri,
width: meta.width,
height: meta.height,
});
// For images backward compatibility,
// keeps localUri the same as uri for React Native's Image that
// works fine with drawable resource names.
if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {
asset.localUri = asset.uri;
asset.downloaded = true;
}
Asset.byHash[meta.hash] = asset;
return asset;
}
return Asset.fromMetadata(meta);
}
// @docsMissing
static fromMetadata(meta) {
// The hash of the whole asset, not to be confused with the hash of a specific file returned
// from `selectAssetSource`
const metaHash = meta.hash;
if (Asset.byHash[metaHash]) {
return Asset.byHash[metaHash];
}
const { uri, hash } = selectAssetSource(meta);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash,
uri,
width: meta.width,
height: meta.height,
});
Asset.byHash[metaHash] = asset;
return asset;
}
// @docsMissing
static fromURI(uri) {
if (Asset.byUri[uri]) {
return Asset.byUri[uri];
}
// Possibly a Base64-encoded URI
let type = '';
if (uri.indexOf(';base64') > -1) {
type = uri.split(';')[0].split('/')[1];
}
else {
const extension = AssetUris.getFileExtension(uri);
type = extension.startsWith('.') ? extension.substring(1) : extension;
}
const asset = new Asset({
name: '',
type,
hash: null,
uri,
});
Asset.byUri[uri] = asset;
return asset;
}
// @needsAudit
/**
* Downloads the asset data to a local file in the device's cache directory. Once the returned
* promise is fulfilled without error, the [`localUri`](#localuri) field of this asset points
* to a local file containing the asset data. The asset is only downloaded if an up-to-date local
* file for the asset isn't already present due to an earlier download. The downloaded `Asset`
* will be returned when the promise is resolved.
* @return Returns a Promise which fulfills with an `Asset` instance.
*/
async downloadAsync() {
if (this.downloaded) {
return this;
}
if (this.downloading) {
await new Promise((resolve, reject) => {
this._downloadCallbacks.push({ resolve, reject });
});
return this;
}
this.downloading = true;
try {
if (Platform.OS === 'web') {
if (ImageAssets.isImageType(this.type)) {
const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri);
this.width = width;
this.height = height;
this.name = name;
}
else {
this.name = AssetUris.getFilename(this.uri);
}
}
this.localUri = await downloadAsync(this.uri, this.hash, this.type);
this.downloaded = true;
this._downloadCallbacks.forEach(({ resolve }) => resolve());
}
catch (e) {
this._downloadCallbacks.forEach(({ reject }) => reject(e));
throw e;
}
finally {
this.downloading = false;
this._downloadCallbacks = [];
}
return this;
}
}
//# sourceMappingURL=Asset.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
import { Asset } from './Asset';
/**
* Downloads and stores one or more assets locally.
* After the assets are loaded, this hook returns a list of asset instances.
* If something went wrong when loading the assets, an error is returned.
*
* > Note, the assets are not "reloaded" when you dynamically change the asset list.
*
* @return Returns an array containing:
* - on the first position, a list of all loaded assets. If they aren't loaded yet, this value is
* `undefined`.
* - on the second position, an error which encountered when loading the assets. If there was no
* error, this value is `undefined`.
*
* @example
* ```tsx
* const [assets, error] = useAssets([require('path/to/asset.jpg'), require('path/to/other.png')]);
*
* return assets ? <Image source={assets[0]} /> : null;
* ```
*/
export declare function useAssets(moduleIds: number | number[]): [Asset[] | undefined, Error | undefined];
//# sourceMappingURL=AssetHooks.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetHooks.d.ts","sourceRoot":"","sources":["../src/AssetHooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,SAAS,EAAE,KAAK,GAAG,SAAS,CAAC,CAShG"}

View File

@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { Asset } from './Asset';
// @needsAudit
/**
* Downloads and stores one or more assets locally.
* After the assets are loaded, this hook returns a list of asset instances.
* If something went wrong when loading the assets, an error is returned.
*
* > Note, the assets are not "reloaded" when you dynamically change the asset list.
*
* @return Returns an array containing:
* - on the first position, a list of all loaded assets. If they aren't loaded yet, this value is
* `undefined`.
* - on the second position, an error which encountered when loading the assets. If there was no
* error, this value is `undefined`.
*
* @example
* ```tsx
* const [assets, error] = useAssets([require('path/to/asset.jpg'), require('path/to/other.png')]);
*
* return assets ? <Image source={assets[0]} /> : null;
* ```
*/
export function useAssets(moduleIds) {
const [assets, setAssets] = useState();
const [error, setError] = useState();
useEffect(() => {
Asset.loadAsync(moduleIds).then(setAssets).catch(setError);
}, []);
return [assets, error];
}
//# sourceMappingURL=AssetHooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetHooks.js","sourceRoot":"","sources":["../src/AssetHooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,cAAc;AACd;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAC,SAA4B;IACpD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,EAAW,CAAC;IAChD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAS,CAAC;IAE5C,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACzB,CAAC","sourcesContent":["import { useEffect, useState } from 'react';\n\nimport { Asset } from './Asset';\n\n// @needsAudit\n/**\n * Downloads and stores one or more assets locally.\n * After the assets are loaded, this hook returns a list of asset instances.\n * If something went wrong when loading the assets, an error is returned.\n *\n * > Note, the assets are not \"reloaded\" when you dynamically change the asset list.\n *\n * @return Returns an array containing:\n * - on the first position, a list of all loaded assets. If they aren't loaded yet, this value is\n * `undefined`.\n * - on the second position, an error which encountered when loading the assets. If there was no\n * error, this value is `undefined`.\n *\n * @example\n * ```tsx\n * const [assets, error] = useAssets([require('path/to/asset.jpg'), require('path/to/other.png')]);\n *\n * return assets ? <Image source={assets[0]} /> : null;\n * ```\n */\nexport function useAssets(moduleIds: number | number[]): [Asset[] | undefined, Error | undefined] {\n const [assets, setAssets] = useState<Asset[]>();\n const [error, setError] = useState<Error>();\n\n useEffect(() => {\n Asset.loadAsync(moduleIds).then(setAssets).catch(setError);\n }, []);\n\n return [assets, error];\n}\n"]}

View File

@@ -0,0 +1,24 @@
import type { PackagerAsset } from '@react-native/assets-registry/registry';
export type ResolvedAssetSource = {
__packager_asset: boolean;
width?: number;
height?: number;
uri: string;
scale: number;
};
export default class AssetSourceResolver {
private readonly serverUrl;
private readonly jsbundleUrl;
readonly asset: PackagerAsset;
constructor(serverUrl: string | undefined | null, jsbundleUrl: string | undefined | null, asset: PackagerAsset);
isLoadedFromServer(): boolean;
isLoadedFromFileSystem(): boolean;
defaultAsset(): ResolvedAssetSource;
/**
* @returns absolute remote URL for the hosted asset.
*/
assetServerURL(): ResolvedAssetSource;
fromSource(source: string): ResolvedAssetSource;
static pickScale(scales: number[], deviceScale: number): number;
}
//# sourceMappingURL=AssetSourceResolver.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetSourceResolver.d.ts","sourceRoot":"","sources":["../src/AssetSourceResolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAI5E,MAAM,MAAM,mBAAmB,GAAG;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAcF,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAGnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IAExD,SAAgB,KAAK,EAAE,aAAa,CAAC;gBAGnC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACpC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACtC,KAAK,EAAE,aAAa;IAQtB,kBAAkB,IAAI,OAAO;IAK7B,sBAAsB,IAAI,OAAO;IAIjC,YAAY,IAAI,mBAAmB;IAInC;;OAEG;IACH,cAAc,IAAI,mBAAmB;IAUrC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB;IAU/C,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;CAQhE"}

View File

@@ -0,0 +1,67 @@
import { Platform } from 'expo-modules-core';
import { PixelRatio } from 'react-native';
// Returns the Metro dev server-specific asset location.
function getScaledAssetPath(asset) {
const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
const type = !asset.type ? '' : `.${asset.type}`;
if (__DEV__) {
return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;
}
else {
return asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + scaleSuffix + type;
}
}
export default class AssetSourceResolver {
serverUrl;
// where the jsbundle is being run from
// NOTE(EvanBacon): Never defined on web.
jsbundleUrl;
// the asset to resolve
asset;
constructor(serverUrl, jsbundleUrl, asset) {
this.serverUrl = serverUrl || 'https://expo.dev';
this.jsbundleUrl = null;
this.asset = asset;
}
// Always true for web runtimes
isLoadedFromServer() {
return true;
}
// Always false for web runtimes
isLoadedFromFileSystem() {
return false;
}
defaultAsset() {
return this.assetServerURL();
}
/**
* @returns absolute remote URL for the hosted asset.
*/
assetServerURL() {
const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl);
fromUrl.searchParams.set('platform', Platform.OS);
fromUrl.searchParams.set('hash', this.asset.hash);
return this.fromSource(
// Relative on web
fromUrl.toString().replace(fromUrl.origin, ''));
}
fromSource(source) {
return {
__packager_asset: true,
width: this.asset.width ?? undefined,
height: this.asset.height ?? undefined,
uri: source,
scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
};
}
static pickScale(scales, deviceScale) {
for (let i = 0; i < scales.length; i++) {
if (scales[i] >= deviceScale) {
return scales[i];
}
}
return scales[scales.length - 1] || 1;
}
}
//# sourceMappingURL=AssetSourceResolver.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetSourceResolver.js","sourceRoot":"","sources":["../src/AssetSourceResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAU1C,wDAAwD;AACxD,SAAS,kBAAkB,CAAC,KAAK;IAC/B,MAAM,KAAK,GAAG,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC;IACzD,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IACjD,IAAI,OAAO,EAAE;QACX,OAAO,KAAK,CAAC,kBAAkB,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,WAAW,GAAG,IAAI,CAAC;KACzE;SAAM;QACL,OAAO,KAAK,CAAC,kBAAkB,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,GAAG,WAAW,GAAG,IAAI,CAAC;KACjG;AACH,CAAC;AAED,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACrB,SAAS,CAAS;IACnC,uCAAuC;IACvC,yCAAyC;IACxB,WAAW,CAA4B;IACxD,uBAAuB;IACP,KAAK,CAAgB;IAErC,YACE,SAAoC,EACpC,WAAsC,EACtC,KAAoB;QAEpB,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,kBAAkB,CAAC;QACjD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,+BAA+B;IAC/B,kBAAkB;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gCAAgC;IAChC,sBAAsB;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACxE,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,UAAU;QACpB,kBAAkB;QAClB,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAC/C,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,MAAc;QACvB,OAAO;YACL,gBAAgB,EAAE,IAAI;YACtB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,SAAS;YACpC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS;YACtC,GAAG,EAAE,MAAM;YACX,KAAK,EAAE,mBAAmB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC;SAC1E,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,MAAgB,EAAE,WAAmB;QACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE;gBAC5B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;aAClB;SACF;QACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;CACF","sourcesContent":["import type { PackagerAsset } from '@react-native/assets-registry/registry';\nimport { Platform } from 'expo-modules-core';\nimport { PixelRatio } from 'react-native';\n\nexport type ResolvedAssetSource = {\n __packager_asset: boolean;\n width?: number;\n height?: number;\n uri: string;\n scale: number;\n};\n\n// Returns the Metro dev server-specific asset location.\nfunction getScaledAssetPath(asset): string {\n const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());\n const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';\n const type = !asset.type ? '' : `.${asset.type}`;\n if (__DEV__) {\n return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;\n } else {\n return asset.httpServerLocation.replace(/\\.\\.\\//g, '_') + '/' + asset.name + scaleSuffix + type;\n }\n}\n\nexport default class AssetSourceResolver {\n private readonly serverUrl: string;\n // where the jsbundle is being run from\n // NOTE(EvanBacon): Never defined on web.\n private readonly jsbundleUrl: string | undefined | null;\n // the asset to resolve\n public readonly asset: PackagerAsset;\n\n constructor(\n serverUrl: string | undefined | null,\n jsbundleUrl: string | undefined | null,\n asset: PackagerAsset\n ) {\n this.serverUrl = serverUrl || 'https://expo.dev';\n this.jsbundleUrl = null;\n this.asset = asset;\n }\n\n // Always true for web runtimes\n isLoadedFromServer(): boolean {\n return true;\n }\n\n // Always false for web runtimes\n isLoadedFromFileSystem(): boolean {\n return false;\n }\n\n defaultAsset(): ResolvedAssetSource {\n return this.assetServerURL();\n }\n\n /**\n * @returns absolute remote URL for the hosted asset.\n */\n assetServerURL(): ResolvedAssetSource {\n const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl);\n fromUrl.searchParams.set('platform', Platform.OS);\n fromUrl.searchParams.set('hash', this.asset.hash);\n return this.fromSource(\n // Relative on web\n fromUrl.toString().replace(fromUrl.origin, '')\n );\n }\n\n fromSource(source: string): ResolvedAssetSource {\n return {\n __packager_asset: true,\n width: this.asset.width ?? undefined,\n height: this.asset.height ?? undefined,\n uri: source,\n scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),\n };\n }\n\n static pickScale(scales: number[], deviceScale: number): number {\n for (let i = 0; i < scales.length; i++) {\n if (scales[i] >= deviceScale) {\n return scales[i];\n }\n }\n return scales[scales.length - 1] || 1;\n }\n}\n"]}

View File

@@ -0,0 +1,4 @@
import AssetSourceResolver from 'react-native/Libraries/Image/AssetSourceResolver';
export default AssetSourceResolver;
export * from 'react-native/Libraries/Image/AssetSourceResolver';
//# sourceMappingURL=AssetSourceResolver.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetSourceResolver.native.d.ts","sourceRoot":"","sources":["../src/AssetSourceResolver.native.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,kDAAkD,CAAC;AACnF,eAAe,mBAAmB,CAAC;AACnC,cAAc,kDAAkD,CAAC"}

View File

@@ -0,0 +1,4 @@
import AssetSourceResolver from 'react-native/Libraries/Image/AssetSourceResolver';
export default AssetSourceResolver;
export * from 'react-native/Libraries/Image/AssetSourceResolver';
//# sourceMappingURL=AssetSourceResolver.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetSourceResolver.native.js","sourceRoot":"","sources":["../src/AssetSourceResolver.native.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,kDAAkD,CAAC;AACnF,eAAe,mBAAmB,CAAC;AACnC,cAAc,kDAAkD,CAAC","sourcesContent":["import AssetSourceResolver from 'react-native/Libraries/Image/AssetSourceResolver';\nexport default AssetSourceResolver;\nexport * from 'react-native/Libraries/Image/AssetSourceResolver';\n"]}

View File

@@ -0,0 +1,25 @@
import type { PackagerAsset } from '@react-native/assets-registry/registry';
export type AssetMetadata = Pick<PackagerAsset, 'httpServerLocation' | 'name' | 'hash' | 'type' | 'scales' | 'width' | 'height'> & {
uri?: string;
fileHashes?: string[];
fileUris?: string[];
};
export type AssetSource = {
uri: string;
hash: string;
};
/**
* Selects the best file for the given asset (ex: choosing the best scale for images) and returns
* a { uri, hash } pair for the specific asset file.
*
* If the asset isn't an image with multiple scales, the first file is selected.
*/
export declare function selectAssetSource(meta: AssetMetadata): AssetSource;
/**
* Resolves the given URI to an absolute URI. If the given URI is already an absolute URI, it is
* simply returned. Otherwise, if it is a relative URI, it is resolved relative to the manifest's
* base URI.
*/
export declare function resolveUri(uri: string): string;
export declare function pathJoin(...paths: string[]): string;
//# sourceMappingURL=AssetSources.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetSources.d.ts","sourceRoot":"","sources":["../src/AssetSources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAQ5E,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,aAAa,EACb,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAChF,GAAG;IACF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,WAAW,CAuDlE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C;AAGD,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAwBnD"}

View File

@@ -0,0 +1,93 @@
import { Platform } from 'expo-modules-core';
import { PixelRatio, NativeModules } from 'react-native';
import AssetSourceResolver from './AssetSourceResolver';
import { getManifest2, manifestBaseUrl } from './PlatformUtils';
/**
* Selects the best file for the given asset (ex: choosing the best scale for images) and returns
* a { uri, hash } pair for the specific asset file.
*
* If the asset isn't an image with multiple scales, the first file is selected.
*/
export function selectAssetSource(meta) {
// This logic is based on that of AssetSourceResolver, with additional support for file hashes and
// explicitly provided URIs
const scale = AssetSourceResolver.pickScale(meta.scales, PixelRatio.get());
const index = meta.scales.findIndex((s) => s === scale);
const hash = meta.fileHashes ? meta.fileHashes[index] ?? meta.fileHashes[0] : meta.hash;
// Allow asset processors to directly provide the URL to load
const uri = meta.fileUris ? meta.fileUris[index] ?? meta.fileUris[0] : meta.uri;
if (uri) {
return { uri: resolveUri(uri), hash };
}
const fileScale = scale === 1 ? '' : `@${scale}x`;
const fileExtension = meta.type ? `.${encodeURIComponent(meta.type)}` : '';
const suffix = `/${encodeURIComponent(meta.name)}${fileScale}${fileExtension}`;
const params = new URLSearchParams({
platform: Platform.OS,
hash: meta.hash,
});
// For assets with a specified absolute URL, we use the existing origin instead of prepending the
// development server or production CDN URL origin
if (/^https?:\/\//.test(meta.httpServerLocation)) {
const uri = meta.httpServerLocation + suffix + '?' + params;
return { uri, hash };
}
// For assets during development using manifest2, we use the development server's URL origin
const manifest2 = getManifest2();
const devServerUrl = manifest2?.extra?.expoGo?.developer
? 'http://' + manifest2.extra.expoGo.debuggerHost
: null;
if (devServerUrl) {
const baseUrl = new URL(meta.httpServerLocation + suffix, devServerUrl);
baseUrl.searchParams.set('platform', Platform.OS);
baseUrl.searchParams.set('hash', meta.hash);
return {
uri: baseUrl.href,
hash,
};
}
// Temporary fallback for loading assets in Expo Go home
if (NativeModules.ExponentKernel) {
return { uri: `https://classic-assets.eascdn.net/~assets/${encodeURIComponent(hash)}`, hash };
}
// In correctly configured apps, we arrive here if the asset is locally available on disk due to
// being managed by expo-updates, and `getLocalAssetUri(hash)` must return a local URI for this
// hash. Since the asset is local, we don't have a remote URL and specify an invalid URL (an empty
// string) as a placeholder.
return { uri: '', hash };
}
/**
* Resolves the given URI to an absolute URI. If the given URI is already an absolute URI, it is
* simply returned. Otherwise, if it is a relative URI, it is resolved relative to the manifest's
* base URI.
*/
export function resolveUri(uri) {
// `manifestBaseUrl` is always an absolute URL or `null`.
return manifestBaseUrl ? new URL(uri, manifestBaseUrl).href : uri;
}
// A very cheap path canonicalization like path.join but without depending on a `path` polyfill.
export function pathJoin(...paths) {
// Start by simply combining paths, without worrying about ".." or "."
const combined = paths
.map((part, index) => {
if (index === 0) {
return part.trim().replace(/\/*$/, '');
}
return part.trim().replace(/(^\/*|\/*$)/g, '');
})
.filter((part) => part.length > 0)
.join('/')
.split('/');
// Handle ".." and "." in paths
const resolved = [];
for (const part of combined) {
if (part === '..') {
resolved.pop(); // Remove the last element from the result
}
else if (part !== '.') {
resolved.push(part);
}
}
return resolved.join('/');
}
//# sourceMappingURL=AssetSources.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
export declare function getFilename(url: string): string;
export declare function getFileExtension(url: string): string;
/**
* Returns the base URL from a manifest's URL. For example, given a manifest hosted at
* https://example.com/app/manifest.json, the base URL would be https://example.com/app/. Query
* parameters and fragments also are removed.
*
* For an Expo-hosted project with a manifest hosted at https://exp.host/@user/project/index.exp, the
* base URL would be https://exp.host/@user/project.
*
* We also normalize the "exp" protocol to "http" to handle internal URLs with the Expo schemes used
* to tell the OS to open the URLs in the the Expo client.
*/
export declare function getManifestBaseUrl(manifestUrl: string): string;
//# sourceMappingURL=AssetUris.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetUris.d.ts","sourceRoot":"","sources":["../src/AssetUris.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAe/C;AAMD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAuB9D"}

View File

@@ -0,0 +1,57 @@
export function getFilename(url) {
const { pathname, searchParams } = new URL(url, 'https://e');
// When attached to a dev server, we use `unstable_path` to represent the file path. This ensures
// the file name is not canonicalized by the browser.
// NOTE(EvanBacon): This is technically not tied to `__DEV__` as it's possible to use this while bundling in production
// mode.
if (__DEV__) {
if (searchParams.has('unstable_path')) {
const encodedFilePath = decodeURIComponent(searchParams.get('unstable_path'));
return getBasename(encodedFilePath);
}
}
return getBasename(pathname);
}
function getBasename(pathname) {
return pathname.substring(pathname.lastIndexOf('/') + 1);
}
export function getFileExtension(url) {
const filename = getFilename(url);
const dotIndex = filename.lastIndexOf('.');
// Ignore leading dots for hidden files
return dotIndex > 0 ? filename.substring(dotIndex) : '';
}
/**
* Returns the base URL from a manifest's URL. For example, given a manifest hosted at
* https://example.com/app/manifest.json, the base URL would be https://example.com/app/. Query
* parameters and fragments also are removed.
*
* For an Expo-hosted project with a manifest hosted at https://exp.host/@user/project/index.exp, the
* base URL would be https://exp.host/@user/project.
*
* We also normalize the "exp" protocol to "http" to handle internal URLs with the Expo schemes used
* to tell the OS to open the URLs in the the Expo client.
*/
export function getManifestBaseUrl(manifestUrl) {
const urlObject = new URL(manifestUrl);
let nextProtocol = urlObject.protocol;
// Change the scheme to http(s) if it is exp(s)
if (nextProtocol === 'exp:') {
nextProtocol = 'http:';
}
else if (nextProtocol === 'exps:') {
nextProtocol = 'https:';
}
urlObject.protocol = nextProtocol;
// Trim filename, query parameters, and fragment, if any
const directory = urlObject.pathname.substring(0, urlObject.pathname.lastIndexOf('/') + 1);
urlObject.pathname = directory;
urlObject.search = '';
urlObject.hash = '';
// The URL spec doesn't allow for changing the protocol to `http` or `https`
// without a port set so instead, we'll just swap the protocol manually.
return urlObject.protocol !== nextProtocol
? urlObject.href.replace(urlObject.protocol, nextProtocol)
: urlObject.href;
}
//# sourceMappingURL=AssetUris.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AssetUris.js","sourceRoot":"","sources":["../src/AssetUris.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAE7D,iGAAiG;IACjG,qDAAqD;IACrD,uHAAuH;IACvH,QAAQ;IACR,IAAI,OAAO,EAAE;QACX,IAAI,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE;YACrC,MAAM,eAAe,GAAG,kBAAkB,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAE,CAAC,CAAC;YAC/E,OAAO,WAAW,CAAC,eAAe,CAAC,CAAC;SACrC;KACF;IAED,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,OAAO,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,uCAAuC;IACvC,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAI,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC;IACtC,+CAA+C;IAC/C,IAAI,YAAY,KAAK,MAAM,EAAE;QAC3B,YAAY,GAAG,OAAO,CAAC;KACxB;SAAM,IAAI,YAAY,KAAK,OAAO,EAAE;QACnC,YAAY,GAAG,QAAQ,CAAC;KACzB;IACD,SAAS,CAAC,QAAQ,GAAG,YAAY,CAAC;IAElC,wDAAwD;IACxD,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3F,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC;IACtB,SAAS,CAAC,IAAI,GAAG,EAAE,CAAC;IAEpB,4EAA4E;IAC5E,wEAAwE;IACxE,OAAO,SAAS,CAAC,QAAQ,KAAK,YAAY;QACxC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC;QAC1D,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;AACrB,CAAC","sourcesContent":["export function getFilename(url: string): string {\n const { pathname, searchParams } = new URL(url, 'https://e');\n\n // When attached to a dev server, we use `unstable_path` to represent the file path. This ensures\n // the file name is not canonicalized by the browser.\n // NOTE(EvanBacon): This is technically not tied to `__DEV__` as it's possible to use this while bundling in production\n // mode.\n if (__DEV__) {\n if (searchParams.has('unstable_path')) {\n const encodedFilePath = decodeURIComponent(searchParams.get('unstable_path')!);\n return getBasename(encodedFilePath);\n }\n }\n\n return getBasename(pathname);\n}\n\nfunction getBasename(pathname: string): string {\n return pathname.substring(pathname.lastIndexOf('/') + 1);\n}\n\nexport function getFileExtension(url: string): string {\n const filename = getFilename(url);\n const dotIndex = filename.lastIndexOf('.');\n // Ignore leading dots for hidden files\n return dotIndex > 0 ? filename.substring(dotIndex) : '';\n}\n\n/**\n * Returns the base URL from a manifest's URL. For example, given a manifest hosted at\n * https://example.com/app/manifest.json, the base URL would be https://example.com/app/. Query\n * parameters and fragments also are removed.\n *\n * For an Expo-hosted project with a manifest hosted at https://exp.host/@user/project/index.exp, the\n * base URL would be https://exp.host/@user/project.\n *\n * We also normalize the \"exp\" protocol to \"http\" to handle internal URLs with the Expo schemes used\n * to tell the OS to open the URLs in the the Expo client.\n */\nexport function getManifestBaseUrl(manifestUrl: string): string {\n const urlObject = new URL(manifestUrl);\n\n let nextProtocol = urlObject.protocol;\n // Change the scheme to http(s) if it is exp(s)\n if (nextProtocol === 'exp:') {\n nextProtocol = 'http:';\n } else if (nextProtocol === 'exps:') {\n nextProtocol = 'https:';\n }\n urlObject.protocol = nextProtocol;\n\n // Trim filename, query parameters, and fragment, if any\n const directory = urlObject.pathname.substring(0, urlObject.pathname.lastIndexOf('/') + 1);\n urlObject.pathname = directory;\n urlObject.search = '';\n urlObject.hash = '';\n\n // The URL spec doesn't allow for changing the protocol to `http` or `https`\n // without a port set so instead, we'll just swap the protocol manually.\n return urlObject.protocol !== nextProtocol\n ? urlObject.href.replace(urlObject.protocol, nextProtocol)\n : urlObject.href;\n}\n"]}

View File

@@ -0,0 +1,11 @@
/**
* Downloads the asset from the given URL to a local cache and returns the local URL of the cached
* file.
*
* If there is already a locally cached file and its MD5 hash matches the given `md5Hash` parameter,
* if present, the remote asset is not downloaded. The `hash` property is included in Metro's asset
* metadata objects when this module's `hashAssetFiles` plugin is used, which is the typical way the
* `md5Hash` parameter of this function is provided.
*/
export declare function downloadAsync(url: string, md5Hash: string | null, type: string): Promise<string>;
//# sourceMappingURL=ExpoAsset.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoAsset.d.ts","sourceRoot":"","sources":["../src/ExpoAsset.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAEjB"}

View File

@@ -0,0 +1,15 @@
import { requireNativeModule } from 'expo-modules-core';
const AssetModule = requireNativeModule('ExpoAsset');
/**
* Downloads the asset from the given URL to a local cache and returns the local URL of the cached
* file.
*
* If there is already a locally cached file and its MD5 hash matches the given `md5Hash` parameter,
* if present, the remote asset is not downloaded. The `hash` property is included in Metro's asset
* metadata objects when this module's `hashAssetFiles` plugin is used, which is the typical way the
* `md5Hash` parameter of this function is provided.
*/
export async function downloadAsync(url, md5Hash, type) {
return AssetModule.downloadAsync(url, md5Hash, type);
}
//# sourceMappingURL=ExpoAsset.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoAsset.js","sourceRoot":"","sources":["../src/ExpoAsset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAErD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,OAAsB,EACtB,IAAY;IAEZ,OAAO,WAAW,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AACvD,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nconst AssetModule = requireNativeModule('ExpoAsset');\n\n/**\n * Downloads the asset from the given URL to a local cache and returns the local URL of the cached\n * file.\n *\n * If there is already a locally cached file and its MD5 hash matches the given `md5Hash` parameter,\n * if present, the remote asset is not downloaded. The `hash` property is included in Metro's asset\n * metadata objects when this module's `hashAssetFiles` plugin is used, which is the typical way the\n * `md5Hash` parameter of this function is provided.\n */\nexport async function downloadAsync(\n url: string,\n md5Hash: string | null,\n type: string\n): Promise<string> {\n return AssetModule.downloadAsync(url, md5Hash, type);\n}\n"]}

View File

@@ -0,0 +1,2 @@
export declare function downloadAsync(url: string, _hash: string | null, _type: string): Promise<string>;
//# sourceMappingURL=ExpoAsset.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoAsset.web.d.ts","sourceRoot":"","sources":["../src/ExpoAsset.web.ts"],"names":[],"mappings":"AAAA,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAEjB"}

View File

@@ -0,0 +1,4 @@
export async function downloadAsync(url, _hash, _type) {
return url;
}
//# sourceMappingURL=ExpoAsset.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoAsset.web.js","sourceRoot":"","sources":["../src/ExpoAsset.web.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,KAAoB,EACpB,KAAa;IAEb,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["export async function downloadAsync(\n url: string,\n _hash: string | null,\n _type: string\n): Promise<string> {\n return url;\n}\n"]}

View File

@@ -0,0 +1,9 @@
type ImageInfo = {
name: string;
width: number;
height: number;
};
export declare function isImageType(type: string): boolean;
export declare function getImageInfoAsync(url: string): Promise<ImageInfo>;
export {};
//# sourceMappingURL=ImageAssets.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ImageAssets.d.ts","sourceRoot":"","sources":["../src/ImageAssets.ts"],"names":[],"mappings":"AAKA,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAgBjE"}

View File

@@ -0,0 +1,24 @@
/* eslint-env browser */
import { Platform } from 'expo-modules-core';
import { getFilename } from './AssetUris';
export function isImageType(type) {
return /^(jpeg|jpg|gif|png|bmp|webp|heic)$/i.test(type);
}
export function getImageInfoAsync(url) {
if (!Platform.isDOMAvailable) {
return Promise.resolve({ name: getFilename(url), width: 0, height: 0 });
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onerror = reject;
img.onload = () => {
resolve({
name: getFilename(url),
width: img.naturalWidth,
height: img.naturalHeight,
});
};
img.src = url;
});
}
//# sourceMappingURL=ImageAssets.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ImageAssets.js","sourceRoot":"","sources":["../src/ImageAssets.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE;QAC5B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;KACzE;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC;QACrB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE;YAChB,OAAO,CAAC;gBACN,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC;gBACtB,KAAK,EAAE,GAAG,CAAC,YAAY;gBACvB,MAAM,EAAE,GAAG,CAAC,aAAa;aAC1B,CAAC,CAAC;QACL,CAAC,CAAC;QACF,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/* eslint-env browser */\nimport { Platform } from 'expo-modules-core';\n\nimport { getFilename } from './AssetUris';\n\ntype ImageInfo = {\n name: string;\n width: number;\n height: number;\n};\n\nexport function isImageType(type: string): boolean {\n return /^(jpeg|jpg|gif|png|bmp|webp|heic)$/i.test(type);\n}\n\nexport function getImageInfoAsync(url: string): Promise<ImageInfo> {\n if (!Platform.isDOMAvailable) {\n return Promise.resolve({ name: getFilename(url), width: 0, height: 0 });\n }\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onerror = reject;\n img.onload = () => {\n resolve({\n name: getFilename(url),\n width: img.naturalWidth,\n height: img.naturalHeight,\n });\n };\n img.src = url;\n });\n}\n"]}

View File

@@ -0,0 +1,5 @@
/**
* Returns the URI of a local asset from its hash, or null if the asset is not available locally
*/
export declare function getLocalAssetUri(hash: string, type: string | null): string | null;
//# sourceMappingURL=LocalAssets.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"LocalAssets.d.ts","sourceRoot":"","sources":["../src/LocalAssets.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAejF"}

View File

@@ -0,0 +1,22 @@
import { getLocalAssets } from './PlatformUtils';
// localAssets are provided by the expo-updates module
const localAssets = getLocalAssets();
/**
* Returns the URI of a local asset from its hash, or null if the asset is not available locally
*/
export function getLocalAssetUri(hash, type) {
const localAssetsKey = hash;
const legacyLocalAssetsKey = `${hash}.${type ?? ''}`;
switch (true) {
case localAssetsKey in localAssets: {
return localAssets[localAssetsKey];
}
case legacyLocalAssetsKey in localAssets: {
// legacy updates store assets with an extension
return localAssets[legacyLocalAssetsKey];
}
default:
return null;
}
}
//# sourceMappingURL=LocalAssets.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"LocalAssets.js","sourceRoot":"","sources":["../src/LocalAssets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,sDAAsD;AACtD,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;AAErC;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAmB;IAChE,MAAM,cAAc,GAAG,IAAI,CAAC;IAC5B,MAAM,oBAAoB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;IAErD,QAAQ,IAAI,EAAE;QACZ,KAAK,cAAc,IAAI,WAAW,CAAC,CAAC;YAClC,OAAO,WAAW,CAAC,cAAc,CAAC,CAAC;SACpC;QACD,KAAK,oBAAoB,IAAI,WAAW,CAAC,CAAC;YACxC,gDAAgD;YAChD,OAAO,WAAW,CAAC,oBAAoB,CAAC,CAAC;SAC1C;QACD;YACE,OAAO,IAAI,CAAC;KACf;AACH,CAAC","sourcesContent":["import { getLocalAssets } from './PlatformUtils';\n\n// localAssets are provided by the expo-updates module\nconst localAssets = getLocalAssets();\n\n/**\n * Returns the URI of a local asset from its hash, or null if the asset is not available locally\n */\nexport function getLocalAssetUri(hash: string, type: string | null): string | null {\n const localAssetsKey = hash;\n const legacyLocalAssetsKey = `${hash}.${type ?? ''}`;\n\n switch (true) {\n case localAssetsKey in localAssets: {\n return localAssets[localAssetsKey];\n }\n case legacyLocalAssetsKey in localAssets: {\n // legacy updates store assets with an extension\n return localAssets[legacyLocalAssetsKey];\n }\n default:\n return null;\n }\n}\n"]}

View File

@@ -0,0 +1,2 @@
export declare function getLocalAssetUri(hash: string, type: string | null): string | null;
//# sourceMappingURL=LocalAssets.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"LocalAssets.web.d.ts","sourceRoot":"","sources":["../src/LocalAssets.web.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGjF"}

View File

@@ -0,0 +1,5 @@
export function getLocalAssetUri(hash, type) {
// noop on web
return null;
}
//# sourceMappingURL=LocalAssets.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"LocalAssets.web.js","sourceRoot":"","sources":["../src/LocalAssets.web.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAmB;IAChE,cAAc;IACd,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["export function getLocalAssetUri(hash: string, type: string | null): string | null {\n // noop on web\n return null;\n}\n"]}

View File

@@ -0,0 +1,6 @@
import Constants from 'expo-constants';
export declare const IS_ENV_WITH_LOCAL_ASSETS: boolean;
export declare function getLocalAssets(): Record<string, string>;
export declare function getManifest2(): typeof Constants.__unsafeNoWarnManifest2;
export declare const manifestBaseUrl: string | null;
//# sourceMappingURL=PlatformUtils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"PlatformUtils.d.ts","sourceRoot":"","sources":["../src/PlatformUtils.ts"],"names":[],"mappings":"AAAA,OAAO,SAA2B,MAAM,gBAAgB,CAAC;AAuBzD,eAAO,MAAM,wBAAwB,SAAuD,CAAC;AAI7F,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvD;AAED,wBAAgB,YAAY,IAAI,OAAO,SAAS,CAAC,uBAAuB,CAEvE;AAGD,eAAO,MAAM,eAAe,eAEpB,CAAC"}

View File

@@ -0,0 +1,28 @@
import Constants, { AppOwnership } from 'expo-constants';
import { requireOptionalNativeModule } from 'expo-modules-core';
import { getManifestBaseUrl } from './AssetUris';
const ExpoUpdates = requireOptionalNativeModule('ExpoUpdates');
const isRunningInExpoGo = Constants.appOwnership === AppOwnership.Expo;
// expo-updates (and Expo Go expo-updates override) manages assets from updates and exposes
// the ExpoUpdates.localAssets constant containing information about the assets.
const expoUpdatesIsInstalledAndEnabled = !!ExpoUpdates?.isEnabled;
const expoUpdatesIsUsingEmbeddedAssets = ExpoUpdates?.isUsingEmbeddedAssets;
// if expo-updates is installed but we're running directly from the embedded bundle, we don't want
// to override the AssetSourceResolver.
const shouldUseUpdatesAssetResolution = expoUpdatesIsInstalledAndEnabled && !expoUpdatesIsUsingEmbeddedAssets;
// Expo Go always uses the updates module for asset resolution (local assets) since it
// overrides the expo-updates module.
export const IS_ENV_WITH_LOCAL_ASSETS = isRunningInExpoGo || shouldUseUpdatesAssetResolution;
// Get the localAssets property from the ExpoUpdates native module so that we do
// not need to include expo-updates as a dependency of expo-asset
export function getLocalAssets() {
return ExpoUpdates?.localAssets ?? {};
}
export function getManifest2() {
return Constants.__unsafeNoWarnManifest2;
}
// Compute manifest base URL if available
export const manifestBaseUrl = Constants.experienceUrl
? getManifestBaseUrl(Constants.experienceUrl)
: null;
//# sourceMappingURL=PlatformUtils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"PlatformUtils.js","sourceRoot":"","sources":["../src/PlatformUtils.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAIhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,GAAG,2BAA2B,CAAoB,aAAa,CAAC,CAAC;AAElF,MAAM,iBAAiB,GAAG,SAAS,CAAC,YAAY,KAAK,YAAY,CAAC,IAAI,CAAC;AAEvE,2FAA2F;AAC3F,gFAAgF;AAChF,MAAM,gCAAgC,GAAG,CAAC,CAAC,WAAW,EAAE,SAAS,CAAC;AAClE,MAAM,gCAAgC,GAAG,WAAW,EAAE,qBAAqB,CAAC;AAE5E,kGAAkG;AAClG,uCAAuC;AACvC,MAAM,+BAA+B,GACnC,gCAAgC,IAAI,CAAC,gCAAgC,CAAC;AAExE,sFAAsF;AACtF,qCAAqC;AACrC,MAAM,CAAC,MAAM,wBAAwB,GAAG,iBAAiB,IAAI,+BAA+B,CAAC;AAE7F,gFAAgF;AAChF,iEAAiE;AACjE,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,EAAE,WAAW,IAAI,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,CAAC,uBAAuB,CAAC;AAC3C,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC,aAAa;IACpD,CAAC,CAAC,kBAAkB,CAAC,SAAS,CAAC,aAAa,CAAC;IAC7C,CAAC,CAAC,IAAI,CAAC","sourcesContent":["import Constants, { AppOwnership } from 'expo-constants';\nimport { requireOptionalNativeModule } from 'expo-modules-core';\n// @ts-ignore -- optional interface, will gracefully degrade to `any` if not installed\nimport type { ExpoUpdatesModule } from 'expo-updates';\n\nimport { getManifestBaseUrl } from './AssetUris';\n\nconst ExpoUpdates = requireOptionalNativeModule<ExpoUpdatesModule>('ExpoUpdates');\n\nconst isRunningInExpoGo = Constants.appOwnership === AppOwnership.Expo;\n\n// expo-updates (and Expo Go expo-updates override) manages assets from updates and exposes\n// the ExpoUpdates.localAssets constant containing information about the assets.\nconst expoUpdatesIsInstalledAndEnabled = !!ExpoUpdates?.isEnabled;\nconst expoUpdatesIsUsingEmbeddedAssets = ExpoUpdates?.isUsingEmbeddedAssets;\n\n// if expo-updates is installed but we're running directly from the embedded bundle, we don't want\n// to override the AssetSourceResolver.\nconst shouldUseUpdatesAssetResolution =\n expoUpdatesIsInstalledAndEnabled && !expoUpdatesIsUsingEmbeddedAssets;\n\n// Expo Go always uses the updates module for asset resolution (local assets) since it\n// overrides the expo-updates module.\nexport const IS_ENV_WITH_LOCAL_ASSETS = isRunningInExpoGo || shouldUseUpdatesAssetResolution;\n\n// Get the localAssets property from the ExpoUpdates native module so that we do\n// not need to include expo-updates as a dependency of expo-asset\nexport function getLocalAssets(): Record<string, string> {\n return ExpoUpdates?.localAssets ?? {};\n}\n\nexport function getManifest2(): typeof Constants.__unsafeNoWarnManifest2 {\n return Constants.__unsafeNoWarnManifest2;\n}\n\n// Compute manifest base URL if available\nexport const manifestBaseUrl = Constants.experienceUrl\n ? getManifestBaseUrl(Constants.experienceUrl)\n : null;\n"]}

View File

@@ -0,0 +1,5 @@
export declare const IS_ENV_WITH_LOCAL_ASSETS = false;
export declare function getLocalAssets(): Record<string, string>;
export declare function getManifest2(): {};
export declare const manifestBaseUrl: null;
//# sourceMappingURL=PlatformUtils.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"PlatformUtils.web.d.ts","sourceRoot":"","sources":["../src/PlatformUtils.web.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAE9C,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvD;AAED,wBAAgB,YAAY,OAE3B;AAGD,eAAO,MAAM,eAAe,MAAO,CAAC"}

View File

@@ -0,0 +1,10 @@
export const IS_ENV_WITH_LOCAL_ASSETS = false;
export function getLocalAssets() {
return {};
}
export function getManifest2() {
return {};
}
// Compute manifest base URL if available
export const manifestBaseUrl = null;
//# sourceMappingURL=PlatformUtils.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"PlatformUtils.web.js","sourceRoot":"","sources":["../src/PlatformUtils.web.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAE9C,MAAM,UAAU,cAAc;IAC5B,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC","sourcesContent":["export const IS_ENV_WITH_LOCAL_ASSETS = false;\n\nexport function getLocalAssets(): Record<string, string> {\n return {};\n}\n\nexport function getManifest2() {\n return {};\n}\n\n// Compute manifest base URL if available\nexport const manifestBaseUrl = null;\n"]}

View File

@@ -0,0 +1,4 @@
import './Asset.fx';
export * from './Asset';
export * from './AssetHooks';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AAEpB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}

View File

@@ -0,0 +1,4 @@
import './Asset.fx';
export * from './Asset';
export * from './AssetHooks';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AAEpB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC","sourcesContent":["import './Asset.fx';\n\nexport * from './Asset';\nexport * from './AssetHooks';\n"]}

View File

@@ -0,0 +1,9 @@
import AssetSourceResolver, { ResolvedAssetSource } from './AssetSourceResolver';
export declare function setCustomSourceTransformer(transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource): void;
/**
* `source` is either a number (opaque type returned by require('./foo.png'))
* or an `ImageSource` like { uri: '<http location || file path>' }
*/
export default function resolveAssetSource(source: any): ResolvedAssetSource | null;
export declare const pickScale: typeof AssetSourceResolver.pickScale;
//# sourceMappingURL=resolveAssetSource.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveAssetSource.d.ts","sourceRoot":"","sources":["../src/resolveAssetSource.ts"],"names":[],"mappings":"AAEA,OAAO,mBAAmB,EAAE,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAIjF,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,mBAAmB,GAClE,IAAI,CAEN;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,MAAM,EAAE,GAAG,GAAG,mBAAmB,GAAG,IAAI,CAoBlF;AAQD,eAAO,MAAQ,SAAS,sCAAwB,CAAC"}

View File

@@ -0,0 +1,33 @@
import { getAssetByID } from '@react-native/assets-registry/registry';
import AssetSourceResolver from './AssetSourceResolver';
let _customSourceTransformer;
export function setCustomSourceTransformer(transformer) {
_customSourceTransformer = transformer;
}
/**
* `source` is either a number (opaque type returned by require('./foo.png'))
* or an `ImageSource` like { uri: '<http location || file path>' }
*/
export default function resolveAssetSource(source) {
if (typeof source === 'object') {
return source;
}
const asset = getAssetByID(source);
if (!asset) {
return null;
}
const resolver = new AssetSourceResolver(
// Doesn't matter since this is removed on web
'https://expo.dev', null, asset);
if (_customSourceTransformer) {
return _customSourceTransformer(resolver);
}
return resolver.defaultAsset();
}
Object.defineProperty(resolveAssetSource, 'setCustomSourceTransformer', {
get() {
return setCustomSourceTransformer;
},
});
export const { pickScale } = AssetSourceResolver;
//# sourceMappingURL=resolveAssetSource.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveAssetSource.js","sourceRoot":"","sources":["../src/resolveAssetSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEtE,OAAO,mBAA4C,MAAM,uBAAuB,CAAC;AAEjF,IAAI,wBAAgF,CAAC;AAErF,MAAM,UAAU,0BAA0B,CACxC,WAAmE;IAEnE,wBAAwB,GAAG,WAAW,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,MAAW;IACpD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QAC9B,OAAO,MAAM,CAAC;KACf;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE;QACV,OAAO,IAAI,CAAC;KACb;IAED,MAAM,QAAQ,GAAG,IAAI,mBAAmB;IACtC,8CAA8C;IAC9C,kBAAkB,EAClB,IAAI,EACJ,KAAK,CACN,CAAC;IACF,IAAI,wBAAwB,EAAE;QAC5B,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;KAC3C;IACD,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,cAAc,CAAC,kBAAkB,EAAE,4BAA4B,EAAE;IACtE,GAAG;QACD,OAAO,0BAA0B,CAAC;IACpC,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC","sourcesContent":["import { getAssetByID } from '@react-native/assets-registry/registry';\n\nimport AssetSourceResolver, { ResolvedAssetSource } from './AssetSourceResolver';\n\nlet _customSourceTransformer: (resolver: AssetSourceResolver) => ResolvedAssetSource;\n\nexport function setCustomSourceTransformer(\n transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource\n): void {\n _customSourceTransformer = transformer;\n}\n\n/**\n * `source` is either a number (opaque type returned by require('./foo.png'))\n * or an `ImageSource` like { uri: '<http location || file path>' }\n */\nexport default function resolveAssetSource(source: any): ResolvedAssetSource | null {\n if (typeof source === 'object') {\n return source;\n }\n\n const asset = getAssetByID(source);\n if (!asset) {\n return null;\n }\n\n const resolver = new AssetSourceResolver(\n // Doesn't matter since this is removed on web\n 'https://expo.dev',\n null,\n asset\n );\n if (_customSourceTransformer) {\n return _customSourceTransformer(resolver);\n }\n return resolver.defaultAsset();\n}\n\nObject.defineProperty(resolveAssetSource, 'setCustomSourceTransformer', {\n get() {\n return setCustomSourceTransformer;\n },\n});\n\nexport const { pickScale } = AssetSourceResolver;\n"]}

View File

@@ -0,0 +1,4 @@
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
export default resolveAssetSource;
export * from 'react-native/Libraries/Image/resolveAssetSource';
//# sourceMappingURL=resolveAssetSource.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveAssetSource.native.d.ts","sourceRoot":"","sources":["../src/resolveAssetSource.native.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,eAAe,kBAAkB,CAAC;AAClC,cAAc,iDAAiD,CAAC"}

View File

@@ -0,0 +1,4 @@
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
export default resolveAssetSource;
export * from 'react-native/Libraries/Image/resolveAssetSource'; // eslint-disable-line import/export
//# sourceMappingURL=resolveAssetSource.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"resolveAssetSource.native.js","sourceRoot":"","sources":["../src/resolveAssetSource.native.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,eAAe,kBAAkB,CAAC;AAClC,cAAc,iDAAiD,CAAC,CAAC,oCAAoC","sourcesContent":["import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';\nexport default resolveAssetSource;\nexport * from 'react-native/Libraries/Image/resolveAssetSource'; // eslint-disable-line import/export\n"]}

View File

@@ -0,0 +1,9 @@
{
"platforms": ["ios", "android", "web", "tvos"],
"ios": {
"modules": ["AssetModule"]
},
"android": {
"modules": ["expo.modules.asset.AssetModule"]
}
}

View File

@@ -0,0 +1,92 @@
import ExpoModulesCore
import CryptoKit
internal class UnableToDownloadAssetException: GenericException<URL> {
override var reason: String {
"Unable to download asset from url: '\(param.absoluteString)'"
}
}
internal class UnableToSaveAssetToDirectoryException: GenericException<URL> {
override var reason: String {
"Unable to save asset to directory: '\(param.absoluteString)'"
}
}
public class AssetModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoAsset")
AsyncFunction("downloadAsync") { (url: URL, md5Hash: String?, type: String, promise: Promise) in
if url.isFileURL {
promise.resolve(url.standardizedFileURL.absoluteString)
return
}
guard let cacheFileId = md5Hash ?? getMD5Hash(fromURL: url),
let cachesDirectory = appContext?.fileSystem?.cachesDirectory,
let appContext = appContext else {
promise.reject(UnableToDownloadAssetException(url))
return
}
let localUrl = URL(fileURLWithPath: "\(cachesDirectory)/ExponentAsset-\(cacheFileId).\(type)")
guard let fileData = FileManager.default.contents(atPath: localUrl.path) else {
downloadAsset(appContext: appContext, url: url, localUrl: localUrl, promise: promise)
return
}
if md5Hash == nil || md5Hash == getMD5Hash(fromData: fileData) {
promise.resolve(localUrl.standardizedFileURL.absoluteString)
return
}
downloadAsset(appContext: appContext, url: url, localUrl: localUrl, promise: promise)
}
}
private func getMD5Hash(fromURL url: URL) -> String? {
guard let urlData = url.absoluteString.data(using: .utf8) else {
return nil
}
return getMD5Hash(fromData: urlData)
}
private func getMD5Hash(fromData data: Data?) -> String? {
guard let data = data else {
return nil
}
return Data(Insecure.MD5.hash(data: data)).map { String(format: "%02hhx", $0) }.joined()
}
func downloadAsset(appContext: AppContext, url: URL, localUrl: URL, promise: Promise) {
guard let fileSystem = appContext.fileSystem else {
promise.reject(UnableToSaveAssetToDirectoryException(url))
return
}
do {
try fileSystem.ensureDirExists(withPath: localUrl.path)
} catch {
promise.reject(UnableToSaveAssetToDirectoryException(localUrl))
return
}
guard fileSystem.permissions(forURI: localUrl).contains(EXFileSystemPermissionFlags.write) else {
promise.reject(UnableToSaveAssetToDirectoryException(localUrl))
return
}
let downloadTask = URLSession.shared.downloadTask(with: url) { urlOrNil, _, _ in
guard let fileURL = urlOrNil else {
promise.reject(UnableToDownloadAssetException(url))
return
}
do {
// the file may already exist, so we need to remove it first
try? FileManager.default.removeItem(at: localUrl)
try FileManager.default.moveItem(at: fileURL, to: localUrl)
promise.resolve(localUrl.standardizedFileURL.absoluteString)
} catch {
promise.reject(UnableToSaveAssetToDirectoryException(localUrl))
}
}
downloadTask.resume()
}
}

View File

@@ -0,0 +1,27 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'ExpoAsset'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = { :ios => '13.4', :tvos => '13.4' }
s.swift_version = '5.4'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
s.source_files = "**/*.{h,m,swift}"
end

View File

@@ -0,0 +1,14 @@
/**
* Automatically generated by expo-modules-test-core.
*
* This autogenerated file provides a mock for native Expo module,
* and works out of the box with the expo jest preset.
* */
export type URL = any;
export async function downloadAsync(
url: URL,
md5Hash: string | undefined,
type: string
): Promise<any> {}

View File

@@ -0,0 +1,52 @@
{
"name": "expo-asset",
"version": "10.0.10",
"description": "An Expo universal module to download assets and pass them into other APIs",
"main": "build/index.js",
"types": "build/index.d.ts",
"sideEffects": [
"*.fx.js",
"*.fx.web.js"
],
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react-native",
"expo",
"asset"
],
"repository": {
"type": "git",
"url": "https://github.com/expo/expo.git",
"directory": "packages/expo-asset"
},
"bugs": {
"url": "https://github.com/expo/expo/issues"
},
"author": "650 Industries, Inc.",
"license": "MIT",
"homepage": "https://docs.expo.dev/versions/latest/sdk/asset/",
"jest": {
"preset": "expo-module-scripts"
},
"dependencies": {
"expo-constants": "~16.0.0",
"invariant": "^2.2.4",
"md5-file": "^3.2.3"
},
"devDependencies": {
"@testing-library/react-hooks": "^7.0.1",
"expo-module-scripts": "^3.0.0"
},
"peerDependencies": {
"expo": "*"
},
"gitHead": "0d1a3567cb0fce9c54e1185654be88bd0c7842d4"
}

View File

@@ -0,0 +1,6 @@
export declare const IMAGE_TYPES: string[];
export declare const FONT_TYPES: string[];
export declare const MEDIA_TYPES: string[];
export declare const ACCEPTED_TYPES: string[];
export declare function resolveAssetPaths(assets: string[], projectRoot: string): Promise<string[]>;
export declare function validateAssets(assets: string[]): string[];

View File

@@ -0,0 +1,42 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateAssets = exports.resolveAssetPaths = exports.ACCEPTED_TYPES = exports.MEDIA_TYPES = exports.FONT_TYPES = exports.IMAGE_TYPES = void 0;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
exports.IMAGE_TYPES = ['.png', '.jpg', '.gif'];
exports.FONT_TYPES = ['.otf', '.ttf'];
exports.MEDIA_TYPES = ['.mp4', '.mp3', '.lottie'];
exports.ACCEPTED_TYPES = ['.json', '.db', ...exports.IMAGE_TYPES, ...exports.MEDIA_TYPES, ...exports.FONT_TYPES];
async function resolveAssetPaths(assets, projectRoot) {
const promises = assets.map(async (p) => {
const resolvedPath = path_1.default.resolve(projectRoot, p);
const stat = await promises_1.default.stat(resolvedPath);
if (stat.isDirectory()) {
const dir = await promises_1.default.readdir(resolvedPath);
return dir.map((file) => path_1.default.join(resolvedPath, file));
}
return [resolvedPath];
});
return (await Promise.all(promises)).flat();
}
exports.resolveAssetPaths = resolveAssetPaths;
function validateAssets(assets) {
return assets.filter((asset) => {
const ext = path_1.default.extname(asset);
const accepted = exports.ACCEPTED_TYPES.includes(ext);
const isFont = exports.FONT_TYPES.includes(ext);
if (!accepted) {
console.warn(`\`${ext}\` is not a supported asset type`);
return;
}
if (isFont) {
console.warn(`Fonts are not supported with the \`expo-asset\` plugin. Please use \`expo-font\` for this functionality. Ignoring ${asset}`);
return;
}
return asset;
});
}
exports.validateAssets = validateAssets;

View File

@@ -0,0 +1,6 @@
import { ConfigPlugin } from 'expo/config-plugins';
export type AssetProps = {
assets?: string[];
};
declare const _default: ConfigPlugin<AssetProps | null>;
export default _default;

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_plugins_1 = require("expo/config-plugins");
const withAssetsAndroid_1 = require("./withAssetsAndroid");
const withAssetsIos_1 = require("./withAssetsIos");
const pkg = require('expo-asset/package.json');
const withAssets = (config, props) => {
if (!props) {
return config;
}
if (props.assets && props.assets.length === 0) {
return config;
}
config = (0, withAssetsIos_1.withAssetsIos)(config, props.assets ?? []);
config = (0, withAssetsAndroid_1.withAssetsAndroid)(config, props.assets ?? []);
return config;
};
exports.default = (0, config_plugins_1.createRunOncePlugin)(withAssets, pkg.name, pkg.version);

View File

@@ -0,0 +1,2 @@
import { ConfigPlugin } from 'expo/config-plugins';
export declare const withAssetsAndroid: ConfigPlugin<string[]>;

View File

@@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withAssetsAndroid = void 0;
const config_plugins_1 = require("expo/config-plugins");
const fs_1 = __importDefault(require("fs"));
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("./utils");
const withAssetsAndroid = (config, assets) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'android',
async (config) => {
const resolvedAssets = await (0, utils_1.resolveAssetPaths)(assets, config.modRequest.projectRoot);
const validAssets = (0, utils_1.validateAssets)(resolvedAssets);
validAssets.forEach((asset) => {
const assetsDir = getAssetDir(asset, config.modRequest.platformProjectRoot);
fs_1.default.mkdirSync(assetsDir, { recursive: true });
});
await Promise.all(validAssets.map(async (asset) => {
const assetsDir = getAssetDir(asset, config.modRequest.platformProjectRoot);
const output = path_1.default.join(assetsDir, path_1.default.basename(asset));
await promises_1.default.copyFile(asset, output);
}));
return config;
},
]);
};
exports.withAssetsAndroid = withAssetsAndroid;
function getAssetDir(asset, root) {
const assetPath = ['app', 'src', 'main', 'assets'];
const resPath = ['app', 'src', 'main', 'res'];
const ext = path_1.default.extname(asset);
if (utils_1.IMAGE_TYPES.includes(ext)) {
return path_1.default.join(root, ...resPath, 'drawable');
}
else if (utils_1.FONT_TYPES.includes(ext)) {
return path_1.default.join(root, ...assetPath, 'fonts');
}
else if (utils_1.MEDIA_TYPES.includes(ext)) {
return path_1.default.join(root, ...resPath, 'raw');
}
else {
return path_1.default.join(root, ...assetPath);
}
}

View File

@@ -0,0 +1,2 @@
import { ConfigPlugin } from 'expo/config-plugins';
export declare const withAssetsIos: ConfigPlugin<string[]>;

View File

@@ -0,0 +1,82 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withAssetsIos = void 0;
const image_utils_1 = require("@expo/image-utils");
const AssetContents_1 = require("@expo/prebuild-config/build/plugins/icons/AssetContents");
const config_plugins_1 = require("expo/config-plugins");
const fs_extra_1 = require("fs-extra");
const path_1 = __importDefault(require("path"));
const utils_1 = require("./utils");
const IMAGE_DIR = 'Images.xcassets';
const withAssetsIos = (config, assets) => {
config = addAssetsToTarget(config, assets);
return config;
};
exports.withAssetsIos = withAssetsIos;
function addAssetsToTarget(config, assets) {
return (0, config_plugins_1.withXcodeProject)(config, async (config) => {
const resolvedAssets = await (0, utils_1.resolveAssetPaths)(assets, config.modRequest.projectRoot);
const validAssets = (0, utils_1.validateAssets)(resolvedAssets);
const project = config.modResults;
const platformProjectRoot = config.modRequest.platformProjectRoot;
config_plugins_1.IOSConfig.XcodeUtils.ensureGroupRecursively(project, 'Resources');
const images = validAssets.filter((asset) => utils_1.IMAGE_TYPES.includes(path_1.default.extname(asset)));
const assetsForResourcesDir = validAssets.filter((asset) => !utils_1.IMAGE_TYPES.includes(path_1.default.extname(asset)));
await addImageAssets(images, config.modRequest.projectRoot);
addResourceFiles(project, platformProjectRoot, assetsForResourcesDir);
return config;
});
}
function addResourceFiles(project, platformRoot, assets) {
for (const asset of assets) {
const assetPath = path_1.default.relative(platformRoot, asset);
config_plugins_1.IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: assetPath,
groupName: 'Resources',
project,
isBuildFile: true,
verbose: true,
});
}
}
async function addImageAssets(assets, root) {
const iosNamedProjectRoot = config_plugins_1.IOSConfig.Paths.getSourceRoot(root);
for (const asset of assets) {
const name = path_1.default.basename(asset, path_1.default.extname(asset));
const image = path_1.default.basename(asset);
const assetPath = path_1.default.resolve(iosNamedProjectRoot, `${IMAGE_DIR}/${name}.imageset`);
await (0, fs_extra_1.ensureDir)(assetPath);
const buffer = await (0, image_utils_1.generateImageAsync)({ projectRoot: root }, {
src: asset,
});
await (0, fs_extra_1.writeFile)(path_1.default.resolve(assetPath, image), buffer.source);
await writeContentsJsonFileAsync({
assetPath,
image,
});
}
}
async function writeContentsJsonFileAsync({ assetPath, image, }) {
const images = buildContentsJsonImages({ image });
await (0, AssetContents_1.writeContentsJsonAsync)(assetPath, { images });
}
function buildContentsJsonImages({ image }) {
return [
(0, AssetContents_1.createContentsJsonItem)({
idiom: 'universal',
filename: image,
scale: '1x',
}),
(0, AssetContents_1.createContentsJsonItem)({
idiom: 'universal',
scale: '2x',
}),
(0, AssetContents_1.createContentsJsonItem)({
idiom: 'universal',
scale: '3x',
}),
];
}

View File

@@ -0,0 +1,41 @@
import fs from 'fs/promises';
import path from 'path';
export const IMAGE_TYPES = ['.png', '.jpg', '.gif'];
export const FONT_TYPES = ['.otf', '.ttf'];
export const MEDIA_TYPES = ['.mp4', '.mp3', '.lottie'];
export const ACCEPTED_TYPES = ['.json', '.db', ...IMAGE_TYPES, ...MEDIA_TYPES, ...FONT_TYPES];
export async function resolveAssetPaths(assets: string[], projectRoot: string) {
const promises = assets.map(async (p) => {
const resolvedPath = path.resolve(projectRoot, p);
const stat = await fs.stat(resolvedPath);
if (stat.isDirectory()) {
const dir = await fs.readdir(resolvedPath);
return dir.map((file) => path.join(resolvedPath, file));
}
return [resolvedPath];
});
return (await Promise.all(promises)).flat();
}
export function validateAssets(assets: string[]) {
return assets.filter((asset) => {
const ext = path.extname(asset);
const accepted = ACCEPTED_TYPES.includes(ext);
const isFont = FONT_TYPES.includes(ext);
if (!accepted) {
console.warn(`\`${ext}\` is not a supported asset type`);
return;
}
if (isFont) {
console.warn(
`Fonts are not supported with the \`expo-asset\` plugin. Please use \`expo-font\` for this functionality. Ignoring ${asset}`
);
return;
}
return asset;
});
}

View File

@@ -0,0 +1,27 @@
import { ConfigPlugin, createRunOncePlugin } from 'expo/config-plugins';
import { withAssetsAndroid } from './withAssetsAndroid';
import { withAssetsIos } from './withAssetsIos';
const pkg = require('expo-asset/package.json');
export type AssetProps = {
assets?: string[];
};
const withAssets: ConfigPlugin<AssetProps | null> = (config, props) => {
if (!props) {
return config;
}
if (props.assets && props.assets.length === 0) {
return config;
}
config = withAssetsIos(config, props.assets ?? []);
config = withAssetsAndroid(config, props.assets ?? []);
return config;
};
export default createRunOncePlugin(withAssets, pkg.name, pkg.version);

View File

@@ -0,0 +1,46 @@
import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs';
import fsp from 'fs/promises';
import path from 'path';
import { FONT_TYPES, IMAGE_TYPES, MEDIA_TYPES, resolveAssetPaths, validateAssets } from './utils';
export const withAssetsAndroid: ConfigPlugin<string[]> = (config, assets) => {
return withDangerousMod(config, [
'android',
async (config) => {
const resolvedAssets = await resolveAssetPaths(assets, config.modRequest.projectRoot);
const validAssets = validateAssets(resolvedAssets);
validAssets.forEach((asset) => {
const assetsDir = getAssetDir(asset, config.modRequest.platformProjectRoot);
fs.mkdirSync(assetsDir, { recursive: true });
});
await Promise.all(
validAssets.map(async (asset) => {
const assetsDir = getAssetDir(asset, config.modRequest.platformProjectRoot);
const output = path.join(assetsDir, path.basename(asset));
await fsp.copyFile(asset, output);
})
);
return config;
},
]);
};
function getAssetDir(asset: string, root: string) {
const assetPath = ['app', 'src', 'main', 'assets'];
const resPath = ['app', 'src', 'main', 'res'];
const ext = path.extname(asset);
if (IMAGE_TYPES.includes(ext)) {
return path.join(root, ...resPath, 'drawable');
} else if (FONT_TYPES.includes(ext)) {
return path.join(root, ...assetPath, 'fonts');
} else if (MEDIA_TYPES.includes(ext)) {
return path.join(root, ...resPath, 'raw');
} else {
return path.join(root, ...assetPath);
}
}

View File

@@ -0,0 +1,102 @@
import { ExpoConfig } from '@expo/config-types';
import { ImageOptions, generateImageAsync } from '@expo/image-utils';
import {
ContentsJsonImage,
createContentsJsonItem,
writeContentsJsonAsync,
} from '@expo/prebuild-config/build/plugins/icons/AssetContents';
import { ConfigPlugin, IOSConfig, XcodeProject, withXcodeProject } from 'expo/config-plugins';
import { ensureDir, writeFile } from 'fs-extra';
import path from 'path';
import { IMAGE_TYPES, resolveAssetPaths, validateAssets } from './utils';
const IMAGE_DIR = 'Images.xcassets';
export const withAssetsIos: ConfigPlugin<string[]> = (config, assets) => {
config = addAssetsToTarget(config, assets);
return config;
};
function addAssetsToTarget(config: ExpoConfig, assets: string[]) {
return withXcodeProject(config, async (config) => {
const resolvedAssets = await resolveAssetPaths(assets, config.modRequest.projectRoot);
const validAssets = validateAssets(resolvedAssets);
const project = config.modResults;
const platformProjectRoot = config.modRequest.platformProjectRoot;
IOSConfig.XcodeUtils.ensureGroupRecursively(project, 'Resources');
const images = validAssets.filter((asset) => IMAGE_TYPES.includes(path.extname(asset)));
const assetsForResourcesDir = validAssets.filter(
(asset) => !IMAGE_TYPES.includes(path.extname(asset))
);
await addImageAssets(images, config.modRequest.projectRoot);
addResourceFiles(project, platformProjectRoot, assetsForResourcesDir);
return config;
});
}
function addResourceFiles(project: XcodeProject, platformRoot: string, assets: string[]) {
for (const asset of assets) {
const assetPath = path.relative(platformRoot, asset);
IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: assetPath,
groupName: 'Resources',
project,
isBuildFile: true,
verbose: true,
});
}
}
async function addImageAssets(assets: string[], root: string) {
const iosNamedProjectRoot = IOSConfig.Paths.getSourceRoot(root);
for (const asset of assets) {
const name = path.basename(asset, path.extname(asset));
const image = path.basename(asset);
const assetPath = path.resolve(iosNamedProjectRoot, `${IMAGE_DIR}/${name}.imageset`);
await ensureDir(assetPath);
const buffer = await generateImageAsync({ projectRoot: root }, {
src: asset,
} as unknown as ImageOptions);
await writeFile(path.resolve(assetPath, image), buffer.source);
await writeContentsJsonFileAsync({
assetPath,
image,
});
}
}
async function writeContentsJsonFileAsync({
assetPath,
image,
}: {
assetPath: string;
image: string;
}) {
const images = buildContentsJsonImages({ image });
await writeContentsJsonAsync(assetPath, { images });
}
function buildContentsJsonImages({ image }: { image: string }): ContentsJsonImage[] {
return [
createContentsJsonItem({
idiom: 'universal',
filename: image,
scale: '1x',
}),
createContentsJsonItem({
idiom: 'universal',
scale: '2x',
}),
createContentsJsonItem({
idiom: 'universal',
scale: '3x',
}),
] as ContentsJsonImage[];
}

View File

@@ -0,0 +1,9 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}

View File

@@ -0,0 +1,20 @@
import { Asset } from './Asset';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import { setCustomSourceTransformer } from './resolveAssetSource';
// Override React Native's asset resolution for `Image` components in contexts where it matters
if (IS_ENV_WITH_LOCAL_ASSETS) {
setCustomSourceTransformer((resolver) => {
try {
// Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists
if (resolver.asset.fileHashes) {
const asset = Asset.fromMetadata(resolver.asset);
return resolver.fromSource(asset.downloaded ? asset.localUri! : asset.uri);
} else {
return resolver.defaultAsset();
}
} catch {
return resolver.defaultAsset();
}
});
}

View File

@@ -0,0 +1,271 @@
import { getAssetByID } from '@react-native/assets-registry/registry';
import { Platform } from 'expo-modules-core';
import { AssetMetadata, selectAssetSource } from './AssetSources';
import * as AssetUris from './AssetUris';
import { downloadAsync } from './ExpoAsset';
import * as ImageAssets from './ImageAssets';
import { getLocalAssetUri } from './LocalAssets';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import resolveAssetSource from './resolveAssetSource';
// @docsMissing
export type AssetDescriptor = {
name: string;
type: string;
hash?: string | null;
uri: string;
width?: number | null;
height?: number | null;
};
type DownloadPromiseCallbacks = {
resolve: () => void;
reject: (error: Error) => void;
};
export { AssetMetadata };
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
*/
export class Asset {
private static byHash = {};
private static byUri = {};
/**
* The name of the asset file without the extension. Also without the part from `@` onward in the
* filename (used to specify scale factor for images).
*/
public name: string;
/**
* The extension of the asset filename.
*/
public readonly type: string;
/**
* The MD5 hash of the asset's data.
*/
public readonly hash: string | null = null;
/**
* A URI that points to the asset's data on the remote server. When running the published version
* of your app, this refers to the location on Expo's asset server where Expo has stored your
* asset. When running the app from Expo CLI during development, this URI points to Expo CLI's
* server running on your computer and the asset is served directly from your computer. If you
* are not using Classic Updates (legacy), this field should be ignored as we ensure your assets
* are on device before before running your application logic.
*/
public readonly uri: string;
/**
* If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the
* `file://` URI pointing to the local file on the device that contains the asset data.
*/
public localUri: string | null = null;
/**
* If the asset is an image, the width of the image data divided by the scale factor. The scale
* factor is the number after `@` in the filename, or `1` if not present.
*/
public width: number | null = null;
/**
* If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.
*/
public height: number | null = null;
private downloading: boolean = false;
/**
* Whether the asset has finished downloading from a call to [`downloadAsync()`](#downloadasync).
*/
public downloaded: boolean = false;
private _downloadCallbacks: DownloadPromiseCallbacks[] = [];
constructor({ name, type, hash = null, uri, width, height }: AssetDescriptor) {
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;
}
if (hash) {
this.localUri = getLocalAssetUri(hash, type);
if (this.localUri) {
this.downloaded = true;
}
}
if (Platform.OS === 'web') {
if (!name) {
this.name = AssetUris.getFilename(uri);
}
if (!type) {
this.type = AssetUris.getFileExtension(uri);
}
}
}
// @needsAudit
/**
* A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.
* @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be
* just one module or URL without an Array.
* @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been
* saved to disk.
* @example
* ```ts
* const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));
* ```
*/
static loadAsync(moduleId: number | number[] | string | string[]): Promise<Asset[]> {
const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId];
return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync()));
}
// @needsAudit
/**
* Returns the [`Asset`](#asset) instance representing an asset given its module or URL.
* @param virtualAssetModule The value of `require('path/to/file')` for the asset or external
* network URL
* @return The [`Asset`](#asset) instance for the asset.
*/
static fromModule(virtualAssetModule: number | string): Asset {
if (typeof virtualAssetModule === 'string') {
return Asset.fromURI(virtualAssetModule);
}
const meta = getAssetByID(virtualAssetModule);
if (!meta) {
throw new Error(`Module "${virtualAssetModule}" is missing from the asset registry`);
}
// Outside of the managed env we need the moduleId to initialize the asset
// because resolveAssetSource depends on it
if (!IS_ENV_WITH_LOCAL_ASSETS) {
// null-check is performed above with `getAssetByID`.
const { uri } = resolveAssetSource(virtualAssetModule)!;
const asset = new Asset({
name: meta.name,
type: meta.type,
hash: meta.hash,
uri,
width: meta.width,
height: meta.height,
});
// For images backward compatibility,
// keeps localUri the same as uri for React Native's Image that
// works fine with drawable resource names.
if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {
asset.localUri = asset.uri;
asset.downloaded = true;
}
Asset.byHash[meta.hash] = asset;
return asset;
}
return Asset.fromMetadata(meta);
}
// @docsMissing
static fromMetadata(meta: AssetMetadata): Asset {
// The hash of the whole asset, not to be confused with the hash of a specific file returned
// from `selectAssetSource`
const metaHash = meta.hash;
if (Asset.byHash[metaHash]) {
return Asset.byHash[metaHash];
}
const { uri, hash } = selectAssetSource(meta);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash,
uri,
width: meta.width,
height: meta.height,
});
Asset.byHash[metaHash] = asset;
return asset;
}
// @docsMissing
static fromURI(uri: string): Asset {
if (Asset.byUri[uri]) {
return Asset.byUri[uri];
}
// Possibly a Base64-encoded URI
let type = '';
if (uri.indexOf(';base64') > -1) {
type = uri.split(';')[0].split('/')[1];
} else {
const extension = AssetUris.getFileExtension(uri);
type = extension.startsWith('.') ? extension.substring(1) : extension;
}
const asset = new Asset({
name: '',
type,
hash: null,
uri,
});
Asset.byUri[uri] = asset;
return asset;
}
// @needsAudit
/**
* Downloads the asset data to a local file in the device's cache directory. Once the returned
* promise is fulfilled without error, the [`localUri`](#localuri) field of this asset points
* to a local file containing the asset data. The asset is only downloaded if an up-to-date local
* file for the asset isn't already present due to an earlier download. The downloaded `Asset`
* will be returned when the promise is resolved.
* @return Returns a Promise which fulfills with an `Asset` instance.
*/
async downloadAsync(): Promise<this> {
if (this.downloaded) {
return this;
}
if (this.downloading) {
await new Promise<void>((resolve, reject) => {
this._downloadCallbacks.push({ resolve, reject });
});
return this;
}
this.downloading = true;
try {
if (Platform.OS === 'web') {
if (ImageAssets.isImageType(this.type)) {
const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri);
this.width = width;
this.height = height;
this.name = name;
} else {
this.name = AssetUris.getFilename(this.uri);
}
}
this.localUri = await downloadAsync(this.uri, this.hash, this.type);
this.downloaded = true;
this._downloadCallbacks.forEach(({ resolve }) => resolve());
} catch (e) {
this._downloadCallbacks.forEach(({ reject }) => reject(e));
throw e;
} finally {
this.downloading = false;
this._downloadCallbacks = [];
}
return this;
}
}

View File

@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import { Asset } from './Asset';
// @needsAudit
/**
* Downloads and stores one or more assets locally.
* After the assets are loaded, this hook returns a list of asset instances.
* If something went wrong when loading the assets, an error is returned.
*
* > Note, the assets are not "reloaded" when you dynamically change the asset list.
*
* @return Returns an array containing:
* - on the first position, a list of all loaded assets. If they aren't loaded yet, this value is
* `undefined`.
* - on the second position, an error which encountered when loading the assets. If there was no
* error, this value is `undefined`.
*
* @example
* ```tsx
* const [assets, error] = useAssets([require('path/to/asset.jpg'), require('path/to/other.png')]);
*
* return assets ? <Image source={assets[0]} /> : null;
* ```
*/
export function useAssets(moduleIds: number | number[]): [Asset[] | undefined, Error | undefined] {
const [assets, setAssets] = useState<Asset[]>();
const [error, setError] = useState<Error>();
useEffect(() => {
Asset.loadAsync(moduleIds).then(setAssets).catch(setError);
}, []);
return [assets, error];
}

View File

@@ -0,0 +1,3 @@
import AssetSourceResolver from 'react-native/Libraries/Image/AssetSourceResolver';
export default AssetSourceResolver;
export * from 'react-native/Libraries/Image/AssetSourceResolver';

View File

@@ -0,0 +1,88 @@
import type { PackagerAsset } from '@react-native/assets-registry/registry';
import { Platform } from 'expo-modules-core';
import { PixelRatio } from 'react-native';
export type ResolvedAssetSource = {
__packager_asset: boolean;
width?: number;
height?: number;
uri: string;
scale: number;
};
// Returns the Metro dev server-specific asset location.
function getScaledAssetPath(asset): string {
const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
const type = !asset.type ? '' : `.${asset.type}`;
if (__DEV__) {
return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;
} else {
return asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + scaleSuffix + type;
}
}
export default class AssetSourceResolver {
private readonly serverUrl: string;
// where the jsbundle is being run from
// NOTE(EvanBacon): Never defined on web.
private readonly jsbundleUrl: string | undefined | null;
// the asset to resolve
public readonly asset: PackagerAsset;
constructor(
serverUrl: string | undefined | null,
jsbundleUrl: string | undefined | null,
asset: PackagerAsset
) {
this.serverUrl = serverUrl || 'https://expo.dev';
this.jsbundleUrl = null;
this.asset = asset;
}
// Always true for web runtimes
isLoadedFromServer(): boolean {
return true;
}
// Always false for web runtimes
isLoadedFromFileSystem(): boolean {
return false;
}
defaultAsset(): ResolvedAssetSource {
return this.assetServerURL();
}
/**
* @returns absolute remote URL for the hosted asset.
*/
assetServerURL(): ResolvedAssetSource {
const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl);
fromUrl.searchParams.set('platform', Platform.OS);
fromUrl.searchParams.set('hash', this.asset.hash);
return this.fromSource(
// Relative on web
fromUrl.toString().replace(fromUrl.origin, '')
);
}
fromSource(source: string): ResolvedAssetSource {
return {
__packager_asset: true,
width: this.asset.width ?? undefined,
height: this.asset.height ?? undefined,
uri: source,
scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
};
}
static pickScale(scales: number[], deviceScale: number): number {
for (let i = 0; i < scales.length; i++) {
if (scales[i] >= deviceScale) {
return scales[i];
}
}
return scales[scales.length - 1] || 1;
}
}

View File

@@ -0,0 +1,121 @@
import type { PackagerAsset } from '@react-native/assets-registry/registry';
import { Platform } from 'expo-modules-core';
import { PixelRatio, NativeModules } from 'react-native';
import AssetSourceResolver from './AssetSourceResolver';
import { getManifest2, manifestBaseUrl } from './PlatformUtils';
// @docsMissing
export type AssetMetadata = Pick<
PackagerAsset,
'httpServerLocation' | 'name' | 'hash' | 'type' | 'scales' | 'width' | 'height'
> & {
uri?: string;
fileHashes?: string[];
fileUris?: string[];
};
export type AssetSource = {
uri: string;
hash: string;
};
/**
* Selects the best file for the given asset (ex: choosing the best scale for images) and returns
* a { uri, hash } pair for the specific asset file.
*
* If the asset isn't an image with multiple scales, the first file is selected.
*/
export function selectAssetSource(meta: AssetMetadata): AssetSource {
// This logic is based on that of AssetSourceResolver, with additional support for file hashes and
// explicitly provided URIs
const scale = AssetSourceResolver.pickScale(meta.scales, PixelRatio.get());
const index = meta.scales.findIndex((s) => s === scale);
const hash = meta.fileHashes ? meta.fileHashes[index] ?? meta.fileHashes[0] : meta.hash;
// Allow asset processors to directly provide the URL to load
const uri = meta.fileUris ? meta.fileUris[index] ?? meta.fileUris[0] : meta.uri;
if (uri) {
return { uri: resolveUri(uri), hash };
}
const fileScale = scale === 1 ? '' : `@${scale}x`;
const fileExtension = meta.type ? `.${encodeURIComponent(meta.type)}` : '';
const suffix = `/${encodeURIComponent(meta.name)}${fileScale}${fileExtension}`;
const params = new URLSearchParams({
platform: Platform.OS,
hash: meta.hash,
});
// For assets with a specified absolute URL, we use the existing origin instead of prepending the
// development server or production CDN URL origin
if (/^https?:\/\//.test(meta.httpServerLocation)) {
const uri = meta.httpServerLocation + suffix + '?' + params;
return { uri, hash };
}
// For assets during development using manifest2, we use the development server's URL origin
const manifest2 = getManifest2();
const devServerUrl = manifest2?.extra?.expoGo?.developer
? 'http://' + manifest2.extra.expoGo.debuggerHost
: null;
if (devServerUrl) {
const baseUrl = new URL(meta.httpServerLocation + suffix, devServerUrl);
baseUrl.searchParams.set('platform', Platform.OS);
baseUrl.searchParams.set('hash', meta.hash);
return {
uri: baseUrl.href,
hash,
};
}
// Temporary fallback for loading assets in Expo Go home
if (NativeModules.ExponentKernel) {
return { uri: `https://classic-assets.eascdn.net/~assets/${encodeURIComponent(hash)}`, hash };
}
// In correctly configured apps, we arrive here if the asset is locally available on disk due to
// being managed by expo-updates, and `getLocalAssetUri(hash)` must return a local URI for this
// hash. Since the asset is local, we don't have a remote URL and specify an invalid URL (an empty
// string) as a placeholder.
return { uri: '', hash };
}
/**
* Resolves the given URI to an absolute URI. If the given URI is already an absolute URI, it is
* simply returned. Otherwise, if it is a relative URI, it is resolved relative to the manifest's
* base URI.
*/
export function resolveUri(uri: string): string {
// `manifestBaseUrl` is always an absolute URL or `null`.
return manifestBaseUrl ? new URL(uri, manifestBaseUrl).href : uri;
}
// A very cheap path canonicalization like path.join but without depending on a `path` polyfill.
export function pathJoin(...paths: string[]): string {
// Start by simply combining paths, without worrying about ".." or "."
const combined = paths
.map((part, index) => {
if (index === 0) {
return part.trim().replace(/\/*$/, '');
}
return part.trim().replace(/(^\/*|\/*$)/g, '');
})
.filter((part) => part.length > 0)
.join('/')
.split('/');
// Handle ".." and "." in paths
const resolved: string[] = [];
for (const part of combined) {
if (part === '..') {
resolved.pop(); // Remove the last element from the result
} else if (part !== '.') {
resolved.push(part);
}
}
return resolved.join('/');
}

Some files were not shown because too many files have changed in this diff Show More