expo-updates 1.0.0-canary-20250131-5c4e588 → 1.0.0-canary-20250219-4a5dade
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/android/build.gradle +26 -16
- package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +1 -3
- package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +7 -3
- package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +10 -4
- package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.kt +2 -3
- package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt +3 -0
- package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +24 -1
- package/android/src/main/java/expo/modules/updates/loader/Loader.kt +9 -2
- package/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt +1 -1
- package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.kt +4 -1
- package/android/src/main/java/expo/modules/updates/manifest/EmbeddedManifestUtils.kt +1 -3
- package/cli/build/syncConfigurationToNativeAsync.js +2 -2
- package/cli/src/syncConfigurationToNativeAsync.ts +2 -2
- package/e2e/setup/project.ts +25 -7
- package/ios/EXUpdates/AppLoader/AppLoader.swift +10 -3
- package/ios/EXUpdates/AppLoader/EmbeddedAppLoader.swift +1 -1
- package/ios/EXUpdates/AppLoader/FileDownloader.swift +24 -0
- package/ios/EXUpdates/AppLoader/RemoteAppLoader.swift +2 -3
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
- Fix an issue where `launchFallbackUpdateFromDisk` is called from a foreground thread leading to ANRs. ([#33693](https://github.com/expo/expo/pull/33693) by [@alanjhughes](https://github.com/alanjhughes))
|
|
17
17
|
- [android] Use more robust mechanism for determining empty multipart bodies. ([#33977](https://github.com/expo/expo/pull/33977) by [@wschurman](https://github.com/wschurman))
|
|
18
18
|
- fix E2E tests in Detox debug build ([#32951](https://github.com/expo/expo/pull/32951) by [@matejkriz](https://github.com/matejkriz))
|
|
19
|
+
- Fix issue where syncing codesigning config for bare projects would clobber existing Expo.plist config ([#34597](https://github.com/expo/expo/pull/34597) by [@brentvatne](https://github.com/brentvatne))
|
|
20
|
+
- Removed Apache Commons IO dependency and fixed crash issue on Android 7. ([#34638](https://github.com/expo/expo/pull/34638) by [@kudo](https://github.com/kudo))
|
|
21
|
+
- [Android] Fix `bytesToHex` `ArrayIndexOutOfBoundsException` during conversion. ([#34855](https://github.com/expo/expo/pull/34855) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
19
22
|
|
|
20
23
|
### 💡 Others
|
|
21
24
|
|
|
@@ -24,6 +27,9 @@
|
|
|
24
27
|
- [apple] Migrate remaining `expo-module.config.json` to unified platform syntax. ([#34445](https://github.com/expo/expo/pull/34445) by [@reichhartd](https://github.com/reichhartd))
|
|
25
28
|
- Fixed build error on iOS Expo Go. ([#34485](https://github.com/expo/expo/pull/34485) by [@kudo](https://github.com/kudo))
|
|
26
29
|
- Fixed Android unit test errors in BuilDataTest. ([#34510](https://github.com/expo/expo/pull/34510) by [@kudo](https://github.com/kudo))
|
|
30
|
+
- Fixed incorrect error log on Android. ([#34785](https://github.com/expo/expo/pull/34785) by [@kudo](https://github.com/kudo))
|
|
31
|
+
- [Android] Started using expo modules gradle plugin. ([#34806](https://github.com/expo/expo/pull/34806) by [@lukmccall](https://github.com/lukmccall))
|
|
32
|
+
- Add update id headers to asset requests ([#34453](https://github.com/expo/expo/pull/34453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
27
33
|
|
|
28
34
|
## 0.26.10 - 2024-12-05
|
|
29
35
|
|
package/android/build.gradle
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
1
1
|
buildscript {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
ext {
|
|
3
|
+
boolish = { value ->
|
|
4
|
+
return value.toString().toBoolean()
|
|
5
|
+
}
|
|
6
|
+
getKspVersion = {
|
|
7
|
+
if (rootProject.hasProperty("kspVersion")) {
|
|
8
|
+
return rootProject["kspVersion"]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// We can remove this path once we update the test environment
|
|
12
|
+
// to use the same version of Kotlin as the `expo/expo` repo.
|
|
13
|
+
def kotlinVersion = rootProject["kotlinVersion"]
|
|
14
|
+
if (kotlinVersion == "2.0.21") {
|
|
15
|
+
return "2.0.21-1.0.28"
|
|
16
|
+
} else if (kotlinVersion == "1.9.25") {
|
|
17
|
+
return "1.9.25-1.0.20"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return "1.9.24-1.0.20"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
5
23
|
|
|
6
24
|
repositories {
|
|
7
25
|
mavenCentral()
|
|
8
26
|
}
|
|
9
27
|
|
|
10
28
|
dependencies {
|
|
11
|
-
classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:${
|
|
29
|
+
classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:${getKspVersion()}"
|
|
12
30
|
}
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
apply plugin: 'com.android.library'
|
|
34
|
+
apply plugin: 'expo-module-gradle-plugin'
|
|
16
35
|
apply plugin: 'com.google.devtools.ksp'
|
|
17
36
|
|
|
18
37
|
group = 'host.exp.exponent'
|
|
19
38
|
version = '0.26.9'
|
|
20
39
|
|
|
21
|
-
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
22
|
-
apply from: expoModulesCorePlugin
|
|
23
|
-
applyKotlinExpoModulesCorePlugin()
|
|
24
|
-
applyKspJvmToolchain()
|
|
25
|
-
useCoreDependencies()
|
|
26
|
-
useDefaultAndroidSdkVersions()
|
|
27
|
-
useExpoPublishing()
|
|
28
|
-
|
|
29
40
|
// Utility method to derive boolean values from the environment or from Java properties,
|
|
30
41
|
// and return them as strings to be used in BuildConfig fields
|
|
31
42
|
def getBoolStringFromPropOrEnv(String name, Boolean defaultValue) {
|
|
@@ -112,13 +123,12 @@ dependencies {
|
|
|
112
123
|
implementation("com.squareup.okhttp3:okhttp:4.9.2")
|
|
113
124
|
implementation("com.squareup.okhttp3:okhttp-urlconnection:4.9.2")
|
|
114
125
|
implementation("com.squareup.okhttp3:okhttp-brotli:4.9.2")
|
|
115
|
-
implementation("commons-io:commons-io:2.16.1")
|
|
116
126
|
implementation("org.bouncycastle:bcutil-jdk15to18:1.78.1")
|
|
117
127
|
|
|
118
128
|
testImplementation 'junit:junit:4.13.2'
|
|
119
129
|
testImplementation 'androidx.test:core:1.5.0'
|
|
120
130
|
testImplementation "io.mockk:mockk:$mockk_version"
|
|
121
|
-
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion
|
|
131
|
+
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion}"
|
|
122
132
|
testImplementation 'org.robolectric:robolectric:4.14.1'
|
|
123
133
|
|
|
124
134
|
androidTestImplementation 'com.squareup.okio:okio:2.9.0'
|
|
@@ -127,7 +137,7 @@ dependencies {
|
|
|
127
137
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
|
128
138
|
androidTestImplementation "io.mockk:mockk-android:$mockk_version"
|
|
129
139
|
androidTestImplementation "androidx.room:room-testing:$room_version"
|
|
130
|
-
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion
|
|
140
|
+
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion}"
|
|
131
141
|
|
|
132
|
-
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion
|
|
142
|
+
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}"
|
|
133
143
|
}
|
|
@@ -6,8 +6,6 @@ import android.net.Uri
|
|
|
6
6
|
import android.util.Log
|
|
7
7
|
import expo.modules.core.errors.InvalidArgumentException
|
|
8
8
|
import expo.modules.updates.codesigning.CodeSigningConfiguration
|
|
9
|
-
import org.apache.commons.io.IOUtils
|
|
10
|
-
import java.nio.charset.StandardCharsets
|
|
11
9
|
|
|
12
10
|
enum class UpdatesConfigurationValidationResult {
|
|
13
11
|
VALID,
|
|
@@ -215,7 +213,7 @@ data class UpdatesConfiguration(
|
|
|
215
213
|
|
|
216
214
|
if (context != null && runtimeVersion == UPDATES_CONFIGURATION_RUNTIME_VERSION_READ_FINGERPRINT_FILE_SENTINEL) {
|
|
217
215
|
return context.assets.open(FINGERPRINT_FILE_NAME).use { stream ->
|
|
218
|
-
|
|
216
|
+
stream.bufferedReader(Charsets.UTF_8).use { it.readText() }
|
|
219
217
|
}
|
|
220
218
|
}
|
|
221
219
|
|
|
@@ -8,7 +8,6 @@ import expo.modules.updates.UpdatesConfiguration.CheckAutomaticallyConfiguration
|
|
|
8
8
|
import expo.modules.updates.db.entity.AssetEntity
|
|
9
9
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
10
10
|
import expo.modules.updates.logging.UpdatesLogger
|
|
11
|
-
import org.apache.commons.io.FileUtils
|
|
12
11
|
import org.json.JSONArray
|
|
13
12
|
import org.json.JSONObject
|
|
14
13
|
import java.io.*
|
|
@@ -112,7 +111,12 @@ object UpdatesUtils {
|
|
|
112
111
|
// write file atomically by writing it to a temporary path and then renaming
|
|
113
112
|
// this protects us against partially written files if the process is interrupted
|
|
114
113
|
val tmpFile = File(destination.absolutePath + ".tmp")
|
|
115
|
-
|
|
114
|
+
tmpFile.parentFile?.mkdirs()
|
|
115
|
+
digestInputStream.use { input ->
|
|
116
|
+
tmpFile.outputStream().use { output ->
|
|
117
|
+
input.copyTo(output)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
116
120
|
|
|
117
121
|
// this message digest must be read after the input stream has been consumed in order to get the hash correctly
|
|
118
122
|
val md = digestInputStream.messageDigest
|
|
@@ -181,7 +185,7 @@ object UpdatesUtils {
|
|
|
181
185
|
fun bytesToHex(bytes: ByteArray): String {
|
|
182
186
|
val hexChars = CharArray(bytes.size * 2)
|
|
183
187
|
for (j in bytes.indices) {
|
|
184
|
-
val v =
|
|
188
|
+
val v = bytes[j].toInt() and 0xFF
|
|
185
189
|
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
|
186
190
|
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
|
187
191
|
}
|
|
@@ -15,8 +15,10 @@ import expo.modules.updates.loader.LoaderFiles
|
|
|
15
15
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
16
16
|
import expo.modules.updates.logging.UpdatesLogger
|
|
17
17
|
import expo.modules.updates.manifest.EmbeddedManifestUtils
|
|
18
|
+
import expo.modules.updates.manifest.EmbeddedUpdate
|
|
18
19
|
import expo.modules.updates.manifest.ManifestMetadata
|
|
19
20
|
import expo.modules.updates.selectionpolicy.SelectionPolicy
|
|
21
|
+
import org.json.JSONObject
|
|
20
22
|
import java.io.File
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -92,7 +94,10 @@ class DatabaseLauncher(
|
|
|
92
94
|
this.callback!!.onFailure(Exception("Launch asset relative path should not be null. Debug info: ${launchedUpdate!!.debugInfo()}"))
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
val
|
|
97
|
+
val embeddedUpdate = EmbeddedManifestUtils.getEmbeddedUpdate(context, configuration)
|
|
98
|
+
val extraHeaders = FileDownloader.getExtraHeadersForRemoteAssetRequest(launchedUpdate, embeddedUpdate?.updateEntity, launchedUpdate)
|
|
99
|
+
|
|
100
|
+
val launchAssetFile = ensureAssetExists(launchAsset, database, embeddedUpdate, extraHeaders)
|
|
96
101
|
if (launchAssetFile != null) {
|
|
97
102
|
this.launchAssetFile = launchAssetFile.toString()
|
|
98
103
|
}
|
|
@@ -107,7 +112,7 @@ class DatabaseLauncher(
|
|
|
107
112
|
}
|
|
108
113
|
val filename = asset.relativePath
|
|
109
114
|
if (filename != null) {
|
|
110
|
-
val assetFile = ensureAssetExists(asset, database)
|
|
115
|
+
val assetFile = ensureAssetExists(asset, database, embeddedUpdate, extraHeaders)
|
|
111
116
|
if (assetFile != null) {
|
|
112
117
|
this[asset] = Uri.fromFile(assetFile).toString()
|
|
113
118
|
}
|
|
@@ -170,13 +175,12 @@ class DatabaseLauncher(
|
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
fun ensureAssetExists(asset: AssetEntity, database: UpdatesDatabase): File? {
|
|
178
|
+
fun ensureAssetExists(asset: AssetEntity, database: UpdatesDatabase, embeddedUpdate: EmbeddedUpdate?, extraHeaders: JSONObject): File? {
|
|
174
179
|
val assetFile = File(updatesDirectory, asset.relativePath ?: "")
|
|
175
180
|
var assetFileExists = assetFile.exists()
|
|
176
181
|
if (!assetFileExists) {
|
|
177
182
|
// something has gone wrong, we're missing this asset
|
|
178
183
|
// first we check to see if a copy is embedded in the binary
|
|
179
|
-
val embeddedUpdate = EmbeddedManifestUtils.getEmbeddedUpdate(context, configuration)
|
|
180
184
|
if (embeddedUpdate != null) {
|
|
181
185
|
val embeddedAssets = embeddedUpdate.assetEntityList
|
|
182
186
|
var matchingEmbeddedAsset: AssetEntity? = null
|
|
@@ -204,9 +208,11 @@ class DatabaseLauncher(
|
|
|
204
208
|
return if (!assetFileExists) {
|
|
205
209
|
// we still don't have the asset locally, so try downloading it remotely
|
|
206
210
|
assetsToDownload++
|
|
211
|
+
|
|
207
212
|
fileDownloader.downloadAsset(
|
|
208
213
|
asset,
|
|
209
214
|
updatesDirectory,
|
|
215
|
+
extraHeaders,
|
|
210
216
|
object : AssetDownloadCallback {
|
|
211
217
|
override fun onFailure(e: Exception, assetEntity: AssetEntity) {
|
|
212
218
|
logger.error("Failed to load asset from disk or network", e, UpdatesErrorCode.AssetsFailedToLoad)
|
|
@@ -4,7 +4,6 @@ import android.content.Context
|
|
|
4
4
|
import android.os.AsyncTask
|
|
5
5
|
import expo.modules.updates.loader.EmbeddedLoader
|
|
6
6
|
import expo.modules.updates.logging.UpdatesLogger
|
|
7
|
-
import org.apache.commons.io.FileUtils
|
|
8
7
|
import java.io.File
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -30,7 +29,7 @@ class NoDatabaseLauncher @JvmOverloads constructor(
|
|
|
30
29
|
try {
|
|
31
30
|
val errorLogFile = File(context.filesDir, ERROR_LOG_FILENAME)
|
|
32
31
|
val exceptionString = fatalException.toString()
|
|
33
|
-
|
|
32
|
+
errorLogFile.appendText(exceptionString, Charsets.UTF_8)
|
|
34
33
|
} catch (e: Exception) {
|
|
35
34
|
logger.error("Failed to write fatal error to log", e)
|
|
36
35
|
}
|
|
@@ -47,7 +46,7 @@ class NoDatabaseLauncher @JvmOverloads constructor(
|
|
|
47
46
|
if (!errorLogFile.exists()) {
|
|
48
47
|
return null
|
|
49
48
|
}
|
|
50
|
-
val logContents =
|
|
49
|
+
val logContents = errorLogFile.readText(Charsets.UTF_8)
|
|
51
50
|
errorLogFile.delete()
|
|
52
51
|
logContents
|
|
53
52
|
} catch (e: Exception) {
|
|
@@ -7,6 +7,7 @@ import expo.modules.updates.db.UpdatesDatabase
|
|
|
7
7
|
import expo.modules.updates.loader.FileDownloader.AssetDownloadCallback
|
|
8
8
|
import expo.modules.updates.loader.FileDownloader.RemoteUpdateDownloadCallback
|
|
9
9
|
import expo.modules.updates.UpdatesUtils
|
|
10
|
+
import expo.modules.updates.db.entity.UpdateEntity
|
|
10
11
|
import expo.modules.updates.logging.UpdatesLogger
|
|
11
12
|
import java.io.File
|
|
12
13
|
import java.io.FileNotFoundException
|
|
@@ -71,6 +72,8 @@ class EmbeddedLoader internal constructor(
|
|
|
71
72
|
assetEntity: AssetEntity,
|
|
72
73
|
updatesDirectory: File?,
|
|
73
74
|
configuration: UpdatesConfiguration,
|
|
75
|
+
requestedUpdate: UpdateEntity?,
|
|
76
|
+
embeddedUpdate: UpdateEntity?,
|
|
74
77
|
callback: AssetDownloadCallback
|
|
75
78
|
) {
|
|
76
79
|
val filename = UpdatesUtils.createFilenameForAsset(assetEntity)
|
|
@@ -450,6 +450,7 @@ class FileDownloader(
|
|
|
450
450
|
fun downloadAsset(
|
|
451
451
|
asset: AssetEntity,
|
|
452
452
|
destinationDirectory: File?,
|
|
453
|
+
extraHeaders: JSONObject,
|
|
453
454
|
callback: AssetDownloadCallback
|
|
454
455
|
) {
|
|
455
456
|
if (asset.url == null) {
|
|
@@ -467,7 +468,7 @@ class FileDownloader(
|
|
|
467
468
|
} else {
|
|
468
469
|
try {
|
|
469
470
|
downloadAssetAndVerifyHashAndWriteToPath(
|
|
470
|
-
createRequestForAsset(asset, configuration, context),
|
|
471
|
+
createRequestForAsset(asset, extraHeaders, configuration, context),
|
|
471
472
|
asset.expectedHash,
|
|
472
473
|
path,
|
|
473
474
|
object : FileDownloadCallback {
|
|
@@ -595,12 +596,14 @@ class FileDownloader(
|
|
|
595
596
|
|
|
596
597
|
internal fun createRequestForAsset(
|
|
597
598
|
assetEntity: AssetEntity,
|
|
599
|
+
extraHeaders: JSONObject,
|
|
598
600
|
configuration: UpdatesConfiguration,
|
|
599
601
|
context: Context
|
|
600
602
|
): Request {
|
|
601
603
|
return Request.Builder()
|
|
602
604
|
.url(assetEntity.url!!.toString())
|
|
603
605
|
.addHeadersFromJSONObject(assetEntity.extraRequestHeaders)
|
|
606
|
+
.addHeadersFromJSONObject(extraHeaders)
|
|
604
607
|
.header("Expo-Platform", "android")
|
|
605
608
|
.header("Expo-Protocol-Version", "1")
|
|
606
609
|
.header("Expo-API-Version", "1")
|
|
@@ -701,5 +704,25 @@ class FileDownloader(
|
|
|
701
704
|
|
|
702
705
|
return extraHeaders
|
|
703
706
|
}
|
|
707
|
+
|
|
708
|
+
fun getExtraHeadersForRemoteAssetRequest(
|
|
709
|
+
launchedUpdate: UpdateEntity?,
|
|
710
|
+
embeddedUpdate: UpdateEntity?,
|
|
711
|
+
requestedUpdate: UpdateEntity?
|
|
712
|
+
): JSONObject {
|
|
713
|
+
val extraHeaders = JSONObject()
|
|
714
|
+
|
|
715
|
+
launchedUpdate?.let {
|
|
716
|
+
extraHeaders.put("Expo-Current-Update-ID", it.id.toString().lowercase())
|
|
717
|
+
}
|
|
718
|
+
embeddedUpdate?.let {
|
|
719
|
+
extraHeaders.put("Expo-Embedded-Update-ID", it.id.toString().lowercase())
|
|
720
|
+
}
|
|
721
|
+
requestedUpdate?.let {
|
|
722
|
+
extraHeaders.put("Expo-Requested-Update-ID", it.id.toString().lowercase())
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return extraHeaders
|
|
726
|
+
}
|
|
704
727
|
}
|
|
705
728
|
}
|
|
@@ -86,6 +86,8 @@ abstract class Loader protected constructor(
|
|
|
86
86
|
assetEntity: AssetEntity,
|
|
87
87
|
updatesDirectory: File?,
|
|
88
88
|
configuration: UpdatesConfiguration,
|
|
89
|
+
requestedUpdate: UpdateEntity?,
|
|
90
|
+
embeddedUpdate: UpdateEntity?,
|
|
89
91
|
callback: AssetDownloadCallback
|
|
90
92
|
)
|
|
91
93
|
|
|
@@ -204,7 +206,7 @@ abstract class Loader protected constructor(
|
|
|
204
206
|
// however, it's not ready, so we should try to download all the assets again.
|
|
205
207
|
updateEntity = existingUpdateEntity
|
|
206
208
|
}
|
|
207
|
-
downloadAllAssets(update
|
|
209
|
+
downloadAllAssets(update)
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
212
|
|
|
@@ -214,8 +216,11 @@ abstract class Loader protected constructor(
|
|
|
214
216
|
ERRORED
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
private fun downloadAllAssets(
|
|
219
|
+
private fun downloadAllAssets(update: Update) {
|
|
220
|
+
val assetList = update.assetEntityList
|
|
218
221
|
assetTotal = assetList.size
|
|
222
|
+
|
|
223
|
+
val embeddedUpdate = loaderFiles.readEmbeddedUpdate(context, configuration)
|
|
219
224
|
for (assetEntityCur in assetList) {
|
|
220
225
|
var assetEntity = assetEntityCur
|
|
221
226
|
|
|
@@ -243,6 +248,8 @@ abstract class Loader protected constructor(
|
|
|
243
248
|
assetEntity,
|
|
244
249
|
updatesDirectory,
|
|
245
250
|
configuration,
|
|
251
|
+
requestedUpdate = update.updateEntity,
|
|
252
|
+
embeddedUpdate = embeddedUpdate?.updateEntity,
|
|
246
253
|
object : AssetDownloadCallback {
|
|
247
254
|
override fun onFailure(e: Exception, assetEntity: AssetEntity) {
|
|
248
255
|
val identifier = if (assetEntity.hash != null) {
|
|
@@ -69,7 +69,7 @@ open class LoaderFiles {
|
|
|
69
69
|
context.resources.openRawResource(id)
|
|
70
70
|
.use { inputStream -> return UpdatesUtils.verifySHA256AndWriteToFile(inputStream, destination, null) }
|
|
71
71
|
} catch (e: Exception) {
|
|
72
|
-
Log.e(TAG, "Failed to copy asset
|
|
72
|
+
Log.e(TAG, "Failed to copy resource asset ${asset.resourcesFolder}/${asset.embeddedAssetFilename}", e)
|
|
73
73
|
throw e
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -55,9 +55,12 @@ class RemoteLoader internal constructor(
|
|
|
55
55
|
assetEntity: AssetEntity,
|
|
56
56
|
updatesDirectory: File?,
|
|
57
57
|
configuration: UpdatesConfiguration,
|
|
58
|
+
requestedUpdate: UpdateEntity?,
|
|
59
|
+
embeddedUpdate: UpdateEntity?,
|
|
58
60
|
callback: AssetDownloadCallback
|
|
59
61
|
) {
|
|
60
|
-
|
|
62
|
+
val extraHeaders = FileDownloader.getExtraHeadersForRemoteAssetRequest(launchedUpdate, embeddedUpdate, requestedUpdate)
|
|
63
|
+
mFileDownloader.downloadAsset(assetEntity, updatesDirectory, extraHeaders, callback)
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
companion object {
|
|
@@ -3,9 +3,7 @@ package expo.modules.updates.manifest
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import expo.modules.updates.UpdatesConfiguration
|
|
6
|
-
import org.apache.commons.io.IOUtils
|
|
7
6
|
import org.json.JSONObject
|
|
8
|
-
import java.nio.charset.StandardCharsets
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Helper object for accessing and memoizing the manifest embedded in the application package.
|
|
@@ -24,7 +22,7 @@ object EmbeddedManifestUtils {
|
|
|
24
22
|
if (sEmbeddedUpdate == null) {
|
|
25
23
|
try {
|
|
26
24
|
context.assets.open(MANIFEST_FILENAME).use { stream ->
|
|
27
|
-
val manifestString =
|
|
25
|
+
val manifestString = stream.bufferedReader(Charsets.UTF_8).use { it.readText() }
|
|
28
26
|
val manifestJson = JSONObject(manifestString)
|
|
29
27
|
// automatically verify embedded manifest since it was already codesigned
|
|
30
28
|
manifestJson.put("isVerified", true)
|
|
@@ -29,7 +29,7 @@ async function syncConfigurationToNativeAsync(options) {
|
|
|
29
29
|
exports.syncConfigurationToNativeAsync = syncConfigurationToNativeAsync;
|
|
30
30
|
async function syncConfigurationToNativeAndroidAsync(options) {
|
|
31
31
|
const { exp } = (0, config_1.getConfig)(options.projectRoot, {
|
|
32
|
-
isPublicConfig:
|
|
32
|
+
isPublicConfig: false,
|
|
33
33
|
skipSDKVersionRequirement: true,
|
|
34
34
|
});
|
|
35
35
|
// sync AndroidManifest.xml
|
|
@@ -50,7 +50,7 @@ async function syncConfigurationToNativeAndroidAsync(options) {
|
|
|
50
50
|
}
|
|
51
51
|
async function syncConfigurationToNativeIosAsync(options) {
|
|
52
52
|
const { exp } = (0, config_1.getConfig)(options.projectRoot, {
|
|
53
|
-
isPublicConfig:
|
|
53
|
+
isPublicConfig: false,
|
|
54
54
|
skipSDKVersionRequirement: true,
|
|
55
55
|
});
|
|
56
56
|
const expoPlist = await readExpoPlistAsync(options.projectRoot);
|
|
@@ -37,7 +37,7 @@ async function syncConfigurationToNativeAndroidAsync(
|
|
|
37
37
|
options: SyncConfigurationToNativeOptions
|
|
38
38
|
): Promise<void> {
|
|
39
39
|
const { exp } = getConfig(options.projectRoot, {
|
|
40
|
-
isPublicConfig:
|
|
40
|
+
isPublicConfig: false, // This must be false or it will drop codesigning config
|
|
41
41
|
skipSDKVersionRequirement: true,
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -84,7 +84,7 @@ async function syncConfigurationToNativeIosAsync(
|
|
|
84
84
|
options: SyncConfigurationToNativeOptions
|
|
85
85
|
): Promise<void> {
|
|
86
86
|
const { exp } = getConfig(options.projectRoot, {
|
|
87
|
-
isPublicConfig:
|
|
87
|
+
isPublicConfig: false, // This must be false or it will drop codesigning config
|
|
88
88
|
skipSDKVersionRequirement: true,
|
|
89
89
|
});
|
|
90
90
|
|
package/e2e/setup/project.ts
CHANGED
|
@@ -54,14 +54,16 @@ function getExpoDependencyChunks({
|
|
|
54
54
|
'expo-audio',
|
|
55
55
|
'expo-av',
|
|
56
56
|
'expo-blur',
|
|
57
|
+
'expo-crypto',
|
|
57
58
|
'expo-image',
|
|
58
59
|
'expo-linear-gradient',
|
|
59
60
|
'expo-linking',
|
|
60
61
|
'expo-localization',
|
|
61
|
-
'expo-
|
|
62
|
+
'expo-media-library',
|
|
62
63
|
'expo-network',
|
|
63
64
|
'expo-secure-store',
|
|
64
65
|
'expo-symbols',
|
|
66
|
+
'expo-system-ui',
|
|
65
67
|
'expo-ui',
|
|
66
68
|
'expo-video',
|
|
67
69
|
],
|
|
@@ -314,10 +316,10 @@ async function preparePackageJson(
|
|
|
314
316
|
|
|
315
317
|
const extraDevDependencies = configureE2E
|
|
316
318
|
? {
|
|
317
|
-
'@config-plugins/detox': '^
|
|
319
|
+
'@config-plugins/detox': '^9.0.0',
|
|
318
320
|
'@types/express': '^4.17.17',
|
|
319
321
|
'@types/jest': '^29.4.0',
|
|
320
|
-
detox: '^20.
|
|
322
|
+
detox: '^20.33.0',
|
|
321
323
|
express: '^4.18.2',
|
|
322
324
|
'form-data': '^4.0.0',
|
|
323
325
|
jest: '^29.3.1',
|
|
@@ -367,7 +369,7 @@ async function preparePackageJson(
|
|
|
367
369
|
...packageJson,
|
|
368
370
|
dependencies: {
|
|
369
371
|
...packageJson.dependencies,
|
|
370
|
-
'react-native': 'npm:react-native-tvos@~0.
|
|
372
|
+
'react-native': 'npm:react-native-tvos@~0.77.0-0',
|
|
371
373
|
'@react-native-tvos/config-tv': '^0.1.1',
|
|
372
374
|
},
|
|
373
375
|
expo: {
|
|
@@ -460,7 +462,15 @@ function transformAppJsonForE2E(
|
|
|
460
462
|
runtimeVersion: string,
|
|
461
463
|
isTV: boolean
|
|
462
464
|
) {
|
|
463
|
-
const plugins: any[] = [
|
|
465
|
+
const plugins: any[] = [
|
|
466
|
+
'expo-updates',
|
|
467
|
+
[
|
|
468
|
+
'@config-plugins/detox',
|
|
469
|
+
{
|
|
470
|
+
subdomains: Array.from(new Set(['10.0.2.2', 'localhost', process.env.UPDATES_HOST])),
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
];
|
|
464
474
|
if (isTV) {
|
|
465
475
|
plugins.push([
|
|
466
476
|
'@react-native-tvos/config-tv',
|
|
@@ -576,7 +586,15 @@ export function transformAppJsonForUpdatesDisabledE2E(
|
|
|
576
586
|
projectName: string,
|
|
577
587
|
runtimeVersion: string
|
|
578
588
|
) {
|
|
579
|
-
const plugins: any[] = [
|
|
589
|
+
const plugins: any[] = [
|
|
590
|
+
'expo-updates',
|
|
591
|
+
[
|
|
592
|
+
'@config-plugins/detox',
|
|
593
|
+
{
|
|
594
|
+
subdomains: Array.from(new Set(['10.0.2.2', 'localhost', process.env.UPDATES_HOST])),
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
];
|
|
580
598
|
return {
|
|
581
599
|
...appJson,
|
|
582
600
|
expo: {
|
|
@@ -770,7 +788,7 @@ export async function initAsync(
|
|
|
770
788
|
// enable proguard on Android
|
|
771
789
|
await fs.appendFile(
|
|
772
790
|
path.join(projectRoot, 'android', 'gradle.properties'),
|
|
773
|
-
'\nandroid.enableProguardInReleaseBuilds=true\
|
|
791
|
+
'\nandroid.enableProguardInReleaseBuilds=true\nEXPO_UPDATES_NATIVE_DEBUG=true',
|
|
774
792
|
'utf-8'
|
|
775
793
|
);
|
|
776
794
|
|
|
@@ -97,7 +97,7 @@ open class AppLoader: NSObject {
|
|
|
97
97
|
preconditionFailure("Must override in concrete class")
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
open func downloadAsset(_ asset: UpdateAsset) {
|
|
100
|
+
open func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
101
101
|
preconditionFailure("Must override in concrete class")
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -202,6 +202,13 @@ open class AppLoader: NSObject {
|
|
|
202
202
|
if let assets = updateManifest.assets(),
|
|
203
203
|
!assets.isEmpty {
|
|
204
204
|
self.assetsToLoad = assets
|
|
205
|
+
let embeddedUpdate = EmbeddedAppLoader.embeddedManifest(withConfig: self.config, database: self.database)
|
|
206
|
+
let extraHeaders = FileDownloader.extraHeadersForRemoteAssetRequest(
|
|
207
|
+
launchedUpdate: self.launchedUpdate,
|
|
208
|
+
embeddedUpdate: embeddedUpdate,
|
|
209
|
+
requestedUpdate: updateManifest
|
|
210
|
+
)
|
|
211
|
+
|
|
205
212
|
for asset in assets {
|
|
206
213
|
// before downloading, check to see if we already have this asset in the database
|
|
207
214
|
let matchingDbEntry = try? self.database.asset(withKey: asset.key)
|
|
@@ -226,11 +233,11 @@ open class AppLoader: NSObject {
|
|
|
226
233
|
self.handleAssetDownloadAlreadyExists(asset)
|
|
227
234
|
}
|
|
228
235
|
} else {
|
|
229
|
-
self.downloadAsset(asset)
|
|
236
|
+
self.downloadAsset(asset, extraHeaders: extraHeaders)
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
} else {
|
|
233
|
-
self.downloadAsset(asset)
|
|
240
|
+
self.downloadAsset(asset, extraHeaders: extraHeaders)
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
} else {
|
|
@@ -119,7 +119,7 @@ public final class EmbeddedAppLoader: AppLoader {
|
|
|
119
119
|
))
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
override public func downloadAsset(_ asset: UpdateAsset) {
|
|
122
|
+
override public func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
123
123
|
FileDownloader.assetFilesQueue.async {
|
|
124
124
|
self.handleAssetDownloadAlreadyExists(asset)
|
|
125
125
|
}
|
|
@@ -221,6 +221,30 @@ public final class FileDownloader {
|
|
|
221
221
|
return extraHeaders
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Get extra headers to pass into `downloadAsset:`
|
|
226
|
+
*/
|
|
227
|
+
static func extraHeadersForRemoteAssetRequest(
|
|
228
|
+
launchedUpdate: Update?,
|
|
229
|
+
embeddedUpdate: Update?,
|
|
230
|
+
requestedUpdate: Update?
|
|
231
|
+
) -> [String: Any] {
|
|
232
|
+
var extraHeaders: [String: Any] = [:]
|
|
233
|
+
if let launchedUpdate {
|
|
234
|
+
extraHeaders["Expo-Current-Update-ID"] = launchedUpdate.updateId.uuidString.lowercased()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if let embeddedUpdate {
|
|
238
|
+
extraHeaders["Expo-Embedded-Update-ID"] = embeddedUpdate.updateId.uuidString.lowercased()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if let requestedUpdate {
|
|
242
|
+
extraHeaders["Expo-Requested-Update-ID"] = requestedUpdate.updateId.uuidString.lowercased()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return extraHeaders
|
|
246
|
+
}
|
|
247
|
+
|
|
224
248
|
private static func setHTTPHeaderFields(_ headers: [String: Any?]?, onRequest request: inout URLRequest) {
|
|
225
249
|
guard let headers = headers else {
|
|
226
250
|
return
|
|
@@ -84,7 +84,7 @@ public final class RemoteAppLoader: AppLoader {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
override public func downloadAsset(_ asset: UpdateAsset) {
|
|
87
|
+
override public func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
88
88
|
let urlOnDisk = self.directory.appendingPathComponent(asset.filename)
|
|
89
89
|
|
|
90
90
|
FileDownloader.assetFilesQueue.async {
|
|
@@ -101,12 +101,11 @@ public final class RemoteAppLoader: AppLoader {
|
|
|
101
101
|
)
|
|
102
102
|
return
|
|
103
103
|
}
|
|
104
|
-
|
|
105
104
|
self.downloader.downloadAsset(
|
|
106
105
|
fromURL: assetUrl,
|
|
107
106
|
verifyingHash: asset.expectedHash,
|
|
108
107
|
toPath: urlOnDisk.path,
|
|
109
|
-
extraHeaders: asset.extraRequestHeaders ?? [:]
|
|
108
|
+
extraHeaders: extraHeaders.merging(asset.extraRequestHeaders ?? [:]) { current, _ in current }
|
|
110
109
|
) { data, response, _ in
|
|
111
110
|
DispatchQueue.global().async {
|
|
112
111
|
self.handleAssetDownload(withData: data, response: response, asset: asset)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "1.0.0-canary-
|
|
3
|
+
"version": "1.0.0-canary-20250219-4a5dade",
|
|
4
4
|
"description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@expo/code-signing-certificates": "0.0.5",
|
|
42
|
-
"@expo/config": "11.0.0-canary-
|
|
43
|
-
"@expo/config-plugins": "9.0.
|
|
42
|
+
"@expo/config": "11.0.0-canary-20250219-4a5dade",
|
|
43
|
+
"@expo/config-plugins": "9.0.16-canary-20250219-4a5dade",
|
|
44
44
|
"@expo/spawn-async": "^1.7.2",
|
|
45
45
|
"arg": "4.1.0",
|
|
46
46
|
"chalk": "^4.1.2",
|
|
47
|
-
"expo-eas-client": "0.13.3-canary-
|
|
48
|
-
"expo-manifests": "0.15.
|
|
49
|
-
"expo-structured-headers": "4.0.1-canary-
|
|
50
|
-
"expo-updates-interface": "1.0.1-canary-
|
|
47
|
+
"expo-eas-client": "0.13.3-canary-20250219-4a5dade",
|
|
48
|
+
"expo-manifests": "0.15.7-canary-20250219-4a5dade",
|
|
49
|
+
"expo-structured-headers": "4.0.1-canary-20250219-4a5dade",
|
|
50
|
+
"expo-updates-interface": "1.0.1-canary-20250219-4a5dade",
|
|
51
51
|
"fast-glob": "^3.3.2",
|
|
52
52
|
"fbemitter": "^3.0.0",
|
|
53
53
|
"ignore": "^5.3.1",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@types/jest": "^29.2.1",
|
|
58
58
|
"@types/node": "^18.19.34",
|
|
59
59
|
"@types/node-forge": "^1.0.0",
|
|
60
|
-
"expo-module-scripts": "4.0.
|
|
60
|
+
"expo-module-scripts": "4.0.5-canary-20250219-4a5dade",
|
|
61
61
|
"express": "^4.21.1",
|
|
62
62
|
"form-data": "^4.0.0",
|
|
63
63
|
"fs-extra": "~8.1.0",
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"xstate": "^4.37.2"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"expo": "53.0.0-canary-
|
|
68
|
+
"expo": "53.0.0-canary-20250219-4a5dade",
|
|
69
69
|
"react": "*"
|
|
70
|
-
}
|
|
70
|
+
},
|
|
71
|
+
"gitHead": "4a5daded61d3d8b9d501059039ac74c09c25675b"
|
|
71
72
|
}
|