first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

22
node_modules/expo-asset/android/build.gradle generated vendored Normal file
View File

@@ -0,0 +1,22 @@
plugins {
id 'com.android.library'
id 'expo-module-gradle-plugin'
}
group = 'expo.modules.asset'
version = '55.0.8'
android {
namespace "expo.modules.asset"
defaultConfig {
versionCode 1
versionName "55.0.8"
}
}
dependencies {
testImplementation 'io.mockk:mockk:1.13.11'
testImplementation 'androidx.test:core:1.7.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.16'
}

View File

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

View File

@@ -0,0 +1,105 @@
package expo.modules.asset
import android.content.Context
import android.net.Uri
import android.util.Log
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 expo.modules.kotlin.services.FilePermissionService
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(context, requireNotNull(localUrl.parent))
.contains(FilePermissionService.Permission.WRITE)
) {
throw UnableToDownloadAssetException(uri.toString())
}
return withContext(appContext.backgroundCoroutineScope.coroutineContext) {
try {
val inputStream = when {
uri.toString().contains(":").not() -> openAssetResourceStream(context, uri.toString())
uri.toString().startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE) -> openAndroidResStream(context, uri.toString())
else -> uri.toURL().openStream()
}
inputStream.use { input ->
localUrl.outputStream().use { output ->
val bytesCopied = input.copyTo(output)
if (bytesCopied == 0L) {
Log.w("ExpoAsset", "Asset downloaded to $localUrl is empty. It might be conflicting with another asset, or corrupted.")
}
}
}
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" && !uri.toString().startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
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,61 @@
package expo.modules.asset
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import androidx.core.net.toUri
import expo.modules.core.errors.InvalidArgumentException
import java.io.InputStream
internal const val ANDROID_EMBEDDED_URL_BASE_RESOURCE = "file:///android_res/"
/**
* Opens an Android resource as stream.
*/
internal fun openAssetResourceStream(context: Context, assetName: String): InputStream {
val resId = findResourceId(context, assetName) ?: throw Resources.NotFoundException(assetName)
return context.resources.openRawResource(resId)
}
/**
* Opens an Android resource as stream for `file:///android_res/` format
*/
internal fun openAndroidResStream(context: Context, resourceFilePath: String): InputStream {
val resId = findResourceIdForAndroidResPath(context, resourceFilePath)
?: throw Resources.NotFoundException(resourceFilePath)
return context.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 }
}
@SuppressLint("DiscouragedApi")
private fun findResourceIdForAndroidResPath(context: Context, resourceFilePath: String): Int? {
if (!resourceFilePath.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
throw InvalidArgumentException("Invalid resource file path: $resourceFilePath")
}
val uri = resourceFilePath.toUri()
val pathSegments = uri.pathSegments
if (pathSegments.size < 3) {
throw InvalidArgumentException("Invalid resource file path: $resourceFilePath")
}
// Strip any qualifiers after a dash, for example "drawable-xhdpi" becomes "drawable"
val resourceDirectory = pathSegments[1].substringBefore('-')
// Strip file extension for resource name
val resourceFilename = pathSegments[2]
val resourceName = resourceFilename.substringBeforeLast('.', resourceFilename)
return context.resources.getIdentifier(
resourceName,
resourceDirectory,
context.packageName
).takeIf { it != 0 }
}