expo-updates 1.0.0-canary-20250122-166c2cb → 1.0.0-canary-20250207-8bc5146
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 +8 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/updates/DisabledUpdatesController.kt +4 -0
- package/android/src/main/java/expo/modules/updates/EnabledUpdatesController.kt +7 -0
- package/android/src/main/java/expo/modules/updates/IUpdatesController.kt +2 -0
- package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +75 -14
- package/android/src/main/java/expo/modules/updates/UpdatesConfigurationOverride.kt +57 -0
- package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +9 -2
- package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +16 -0
- package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +6 -2
- package/android/src/main/java/expo/modules/updates/db/BuildData.kt +22 -25
- package/android/src/main/java/expo/modules/updates/db/dao/JSONDataDao.kt +26 -11
- package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecovery.kt +5 -4
- package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.kt +2 -3
- package/android/src/main/java/expo/modules/updates/manifest/EmbeddedManifestUtils.kt +1 -3
- package/android/src/main/java/expo/modules/updates/manifest/ManifestMetadata.kt +19 -12
- package/android/src/main/java/expo/modules/updates/procedures/StartupProcedure.kt +2 -1
- package/build/Updates.d.ts +11 -0
- package/build/Updates.d.ts.map +1 -1
- package/build/Updates.js +10 -0
- package/build/Updates.js.map +1 -1
- package/cli/build/syncConfigurationToNativeAsync.js +2 -2
- package/cli/src/syncConfigurationToNativeAsync.ts +2 -2
- package/e2e/fixtures/App.tsx +11 -0
- package/e2e/fixtures/Updates-bricking-measures-disabled.e2e.ts +77 -0
- package/e2e/fixtures/project_files/e2e/tests/utils/server.ts +26 -2
- package/e2e/setup/create-bricking-measures-disabled-eas-project.ts +43 -0
- package/e2e/setup/project.ts +73 -8
- package/expo-module.config.json +1 -3
- package/ios/EXUpdates/AppController.swift +1 -0
- package/ios/EXUpdates/Database/UpdatesBuildData.swift +37 -8
- package/ios/EXUpdates/Database/UpdatesDatabase.swift +36 -19
- package/ios/EXUpdates/DevLauncherAppController.swift +4 -0
- package/ios/EXUpdates/DisabledAppController.swift +4 -0
- package/ios/EXUpdates/EnabledAppController.swift +7 -0
- package/ios/EXUpdates/Exceptions.swift +10 -0
- package/ios/EXUpdates/UpdatesConfig.swift +60 -12
- package/ios/EXUpdates/UpdatesConfigOverride.swift +33 -0
- package/ios/EXUpdates/UpdatesModule.swift +16 -0
- package/ios/Tests/UpdatesBuildDataSpec.swift +103 -0
- package/package.json +11 -10
- package/src/Updates.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,15 +9,23 @@
|
|
|
9
9
|
### 🎉 New features
|
|
10
10
|
|
|
11
11
|
- Add new state machine context about startup procedure. ([#32433](https://github.com/expo/expo/pull/32433) by [@wschurman](https://github.com/wschurman))
|
|
12
|
+
- Add `Updates.setUpdateURLAndRequestHeadersOverride()` to allow overriding update URL configuration from the JS API. ([#34422](https://github.com/expo/expo/pull/34422), [#34423](https://github.com/expo/expo/pull/34423), [#34425](https://github.com/expo/expo/pull/34425), [#34426](https://github.com/expo/expo/pull/34426), [#34454](https://github.com/expo/expo/pull/34454), [#34455](https://github.com/expo/expo/pull/34455), [#34428](https://github.com/expo/expo/pull/34428), [#34404](https://github.com/expo/expo/pull/34404) by [@kudo](https://github.com/kudo), [@wschurman](https://github.com/wschurman))
|
|
12
13
|
|
|
13
14
|
### 🐛 Bug fixes
|
|
14
15
|
|
|
15
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))
|
|
16
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
|
+
- 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))
|
|
17
21
|
|
|
18
22
|
### 💡 Others
|
|
19
23
|
|
|
24
|
+
- [Android] Made ReactNativeFeatureFlags a parameter to the constructor of the ErrorRecovery class to be able to make tests pass ([#34363](https://github.com/expo/expo/pull/34363) by [@chrfalch](https://github.com/chrfalch))
|
|
20
25
|
- [iOS] Inject logger from controllers down to dependent objects. ([#34035](https://github.com/expo/expo/pull/34035) by [@wschurman](https://github.com/wschurman))
|
|
26
|
+
- [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))
|
|
27
|
+
- Fixed build error on iOS Expo Go. ([#34485](https://github.com/expo/expo/pull/34485) by [@kudo](https://github.com/kudo))
|
|
28
|
+
- Fixed Android unit test errors in BuilDataTest. ([#34510](https://github.com/expo/expo/pull/34510) by [@kudo](https://github.com/kudo))
|
|
21
29
|
|
|
22
30
|
## 0.26.10 - 2024-12-05
|
|
23
31
|
|
package/android/build.gradle
CHANGED
|
@@ -112,13 +112,13 @@ dependencies {
|
|
|
112
112
|
implementation("com.squareup.okhttp3:okhttp:4.9.2")
|
|
113
113
|
implementation("com.squareup.okhttp3:okhttp-urlconnection:4.9.2")
|
|
114
114
|
implementation("com.squareup.okhttp3:okhttp-brotli:4.9.2")
|
|
115
|
-
implementation("commons-io:commons-io:2.16.1")
|
|
116
115
|
implementation("org.bouncycastle:bcutil-jdk15to18:1.78.1")
|
|
117
116
|
|
|
118
117
|
testImplementation 'junit:junit:4.13.2'
|
|
119
118
|
testImplementation 'androidx.test:core:1.5.0'
|
|
120
119
|
testImplementation "io.mockk:mockk:$mockk_version"
|
|
121
120
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlinVersion()}"
|
|
121
|
+
testImplementation 'org.robolectric:robolectric:4.14.1'
|
|
122
122
|
|
|
123
123
|
androidTestImplementation 'com.squareup.okio:okio:2.9.0'
|
|
124
124
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
|
@@ -162,6 +162,10 @@ class DisabledUpdatesController(
|
|
|
162
162
|
callback.onFailure(UpdatesDisabledException("Updates.setExtraParamAsync() is not supported when expo-updates is not enabled."))
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
override fun setUpdateURLAndRequestHeadersOverride(configOverride: UpdatesConfigurationOverride?) {
|
|
166
|
+
throw UpdatesDisabledException("Updates.setUpdateURLAndRequestHeadersOverride() is not supported when expo-updates is not enabled.")
|
|
167
|
+
}
|
|
168
|
+
|
|
165
169
|
@Synchronized
|
|
166
170
|
private fun notifyController() {
|
|
167
171
|
if (launcher == null) {
|
|
@@ -262,6 +262,13 @@ class EnabledUpdatesController(
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
override fun setUpdateURLAndRequestHeadersOverride(configOverride: UpdatesConfigurationOverride?) {
|
|
266
|
+
if (!updatesConfiguration.disableAntiBrickingMeasures) {
|
|
267
|
+
throw CodedException("ERR_UPDATES_RUNTIME_OVERRIDE", "Must set disableAntiBrickingMeasures configuration to use updates overriding", null)
|
|
268
|
+
}
|
|
269
|
+
UpdatesConfigurationOverride.save(context, configOverride)
|
|
270
|
+
}
|
|
271
|
+
|
|
265
272
|
companion object {
|
|
266
273
|
private val TAG = EnabledUpdatesController::class.java.simpleName
|
|
267
274
|
}
|
|
@@ -165,4 +165,6 @@ interface IUpdatesController {
|
|
|
165
165
|
fun getExtraParams(callback: ModuleCallback<Bundle>)
|
|
166
166
|
|
|
167
167
|
fun setExtraParam(key: String, value: String?, callback: ModuleCallback<Unit>)
|
|
168
|
+
|
|
169
|
+
fun setUpdateURLAndRequestHeadersOverride(configOverride: UpdatesConfigurationOverride?)
|
|
168
170
|
}
|
|
@@ -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,
|
|
@@ -39,7 +37,8 @@ data class UpdatesConfiguration(
|
|
|
39
37
|
val codeSigningMetadata: Map<String, String>?,
|
|
40
38
|
val codeSigningIncludeManifestResponseCertificateChain: Boolean,
|
|
41
39
|
private val codeSigningAllowUnsignedManifests: Boolean,
|
|
42
|
-
val enableExpoUpdatesProtocolV0CompatibilityMode: Boolean // used only in Expo Go to prevent loading rollbacks and other directives, which don't make much sense in the context of Expo Go
|
|
40
|
+
val enableExpoUpdatesProtocolV0CompatibilityMode: Boolean, // used only in Expo Go to prevent loading rollbacks and other directives, which don't make much sense in the context of Expo Go
|
|
41
|
+
val disableAntiBrickingMeasures: Boolean
|
|
43
42
|
) {
|
|
44
43
|
enum class CheckAutomaticallyConfiguration {
|
|
45
44
|
NEVER {
|
|
@@ -60,12 +59,27 @@ data class UpdatesConfiguration(
|
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
constructor(
|
|
62
|
+
constructor(
|
|
63
|
+
context: Context?,
|
|
64
|
+
overrideMap: Map<String, Any>?
|
|
65
|
+
) : this(
|
|
66
|
+
context,
|
|
67
|
+
overrideMap,
|
|
68
|
+
disableAntiBrickingMeasures = getDisableAntiBrickingMeasures(context, overrideMap),
|
|
69
|
+
configOverride = context?.let { UpdatesConfigurationOverride.load(context) }
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
internal constructor(
|
|
73
|
+
context: Context?,
|
|
74
|
+
overrideMap: Map<String, Any>?,
|
|
75
|
+
disableAntiBrickingMeasures: Boolean,
|
|
76
|
+
configOverride: UpdatesConfigurationOverride?
|
|
77
|
+
) : this(
|
|
64
78
|
scopeKey = maybeGetDefaultScopeKey(
|
|
65
79
|
overrideMap?.readValueCheckingType<String>(UPDATES_CONFIGURATION_SCOPE_KEY_KEY) ?: context?.getMetadataValue("expo.modules.updates.EXPO_SCOPE_KEY"),
|
|
66
|
-
updateUrl =
|
|
80
|
+
updateUrl = getUpdateUrl(context, overrideMap, disableAntiBrickingMeasures, configOverride)!!
|
|
67
81
|
),
|
|
68
|
-
updateUrl =
|
|
82
|
+
updateUrl = getUpdateUrl(context, overrideMap, disableAntiBrickingMeasures, configOverride)!!,
|
|
69
83
|
runtimeVersionRaw = getRuntimeVersion(context, overrideMap),
|
|
70
84
|
launchWaitMs = overrideMap?.readValueCheckingType<Int>(UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_KEY) ?: context?.getMetadataValue("expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS") ?: UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_DEFAULT_VALUE,
|
|
71
85
|
checkOnLaunch = overrideMap?.readValueCheckingType<String>(UPDATES_CONFIGURATION_CHECK_ON_LAUNCH_KEY)?.let {
|
|
@@ -85,10 +99,8 @@ data class UpdatesConfiguration(
|
|
|
85
99
|
CheckAutomaticallyConfiguration.ALWAYS
|
|
86
100
|
}
|
|
87
101
|
},
|
|
88
|
-
hasEmbeddedUpdate =
|
|
89
|
-
requestHeaders = overrideMap
|
|
90
|
-
UpdatesUtils.getMapFromJSONString(it)
|
|
91
|
-
},
|
|
102
|
+
hasEmbeddedUpdate = getHasEmbeddedUpdate(context, overrideMap, disableAntiBrickingMeasures, configOverride),
|
|
103
|
+
requestHeaders = getRequestHeaders(context, overrideMap, disableAntiBrickingMeasures, configOverride),
|
|
92
104
|
codeSigningCertificate = overrideMap?.readValueCheckingType<String>(UPDATES_CONFIGURATION_CODE_SIGNING_CERTIFICATE) ?: context?.getMetadataValue("expo.modules.updates.CODE_SIGNING_CERTIFICATE"),
|
|
93
105
|
codeSigningMetadata = overrideMap?.readValueCheckingType<Map<String, String>>(UPDATES_CONFIGURATION_CODE_SIGNING_METADATA) ?: (context?.getMetadataValue<String>("expo.modules.updates.CODE_SIGNING_METADATA") ?: "{}").let {
|
|
94
106
|
UpdatesUtils.getMapFromJSONString(it)
|
|
@@ -99,7 +111,8 @@ data class UpdatesConfiguration(
|
|
|
99
111
|
codeSigningAllowUnsignedManifests = overrideMap?.readValueCheckingType<Boolean>(
|
|
100
112
|
UPDATES_CONFIGURATION_CODE_SIGNING_ALLOW_UNSIGNED_MANIFESTS
|
|
101
113
|
) ?: context?.getMetadataValue("expo.modules.updates.CODE_SIGNING_ALLOW_UNSIGNED_MANIFESTS") ?: false,
|
|
102
|
-
enableExpoUpdatesProtocolV0CompatibilityMode = overrideMap?.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_ENABLE_EXPO_UPDATES_PROTOCOL_V0_COMPATIBILITY_MODE) ?: context?.getMetadataValue("expo.modules.updates.ENABLE_EXPO_UPDATES_PROTOCOL_V0_COMPATIBILITY_MODE") ?: false
|
|
114
|
+
enableExpoUpdatesProtocolV0CompatibilityMode = overrideMap?.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_ENABLE_EXPO_UPDATES_PROTOCOL_V0_COMPATIBILITY_MODE) ?: context?.getMetadataValue("expo.modules.updates.ENABLE_EXPO_UPDATES_PROTOCOL_V0_COMPATIBILITY_MODE") ?: false,
|
|
115
|
+
disableAntiBrickingMeasures = getDisableAntiBrickingMeasures(context, overrideMap)
|
|
103
116
|
)
|
|
104
117
|
|
|
105
118
|
val codeSigningConfiguration: CodeSigningConfiguration? by lazy {
|
|
@@ -128,6 +141,7 @@ data class UpdatesConfiguration(
|
|
|
128
141
|
const val UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_KEY = "launchWaitMs"
|
|
129
142
|
const val UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY = "hasEmbeddedUpdate"
|
|
130
143
|
const val UPDATES_CONFIGURATION_ENABLE_EXPO_UPDATES_PROTOCOL_V0_COMPATIBILITY_MODE = "enableExpoUpdatesProtocolCompatibilityMode"
|
|
144
|
+
const val UPDATES_CONFIGURATION_DISABLE_ANTI_BRICKING_MEASURES = "disableAntiBrickingMeasures"
|
|
131
145
|
|
|
132
146
|
const val UPDATES_CONFIGURATION_CODE_SIGNING_CERTIFICATE = "codeSigningCertificate"
|
|
133
147
|
const val UPDATES_CONFIGURATION_CODE_SIGNING_METADATA = "codeSigningMetadata"
|
|
@@ -139,12 +153,57 @@ data class UpdatesConfiguration(
|
|
|
139
153
|
const val UPDATES_CONFIGURATION_RUNTIME_VERSION_READ_FINGERPRINT_FILE_SENTINEL = "file:fingerprint"
|
|
140
154
|
private const val FINGERPRINT_FILE_NAME = "fingerprint"
|
|
141
155
|
|
|
142
|
-
private fun
|
|
156
|
+
private fun getDisableAntiBrickingMeasures(context: Context?, overrideMap: Map<String, Any>?): Boolean {
|
|
157
|
+
return overrideMap?.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_DISABLE_ANTI_BRICKING_MEASURES) ?: context?.getMetadataValue("expo.modules.updates.DISABLE_ANTI_BRICKING_MEASURES") ?: false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun getHasEmbeddedUpdate(
|
|
161
|
+
context: Context?,
|
|
162
|
+
overrideMap: Map<String, Any>?,
|
|
163
|
+
disableAntiBrickingMeasures: Boolean,
|
|
164
|
+
configOverride: UpdatesConfigurationOverride?
|
|
165
|
+
): Boolean {
|
|
166
|
+
if (disableAntiBrickingMeasures && configOverride != null) {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
return overrideMap?.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY)
|
|
170
|
+
?: context?.getMetadataValue("expo.modules.updates.HAS_EMBEDDED_UPDATE")
|
|
171
|
+
?: true
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun getUpdateUrl(
|
|
175
|
+
context: Context?,
|
|
176
|
+
overrideMap: Map<String, Any>?,
|
|
177
|
+
disableAntiBrickingMeasures: Boolean,
|
|
178
|
+
configOverride: UpdatesConfigurationOverride?
|
|
179
|
+
): Uri? {
|
|
180
|
+
if (disableAntiBrickingMeasures) {
|
|
181
|
+
configOverride?.let {
|
|
182
|
+
return it.updateUrl
|
|
183
|
+
}
|
|
184
|
+
}
|
|
143
185
|
return overrideMap?.readValueCheckingType(UPDATES_CONFIGURATION_UPDATE_URL_KEY)
|
|
144
186
|
?: context?.getMetadataValue<String>("expo.modules.updates.EXPO_UPDATE_URL")
|
|
145
187
|
?.let { Uri.parse(it) }
|
|
146
188
|
}
|
|
147
189
|
|
|
190
|
+
private fun getRequestHeaders(
|
|
191
|
+
context: Context?,
|
|
192
|
+
overrideMap: Map<String, Any>?,
|
|
193
|
+
disableAntiBrickingMeasures: Boolean,
|
|
194
|
+
configOverride: UpdatesConfigurationOverride?
|
|
195
|
+
): Map<String, String> {
|
|
196
|
+
if (disableAntiBrickingMeasures) {
|
|
197
|
+
configOverride?.let {
|
|
198
|
+
return it.requestHeaders
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return overrideMap?.readValueCheckingType<Map<String, String>>(UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY)
|
|
202
|
+
?: (context?.getMetadataValue<String>("expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY") ?: "{}").let {
|
|
203
|
+
UpdatesUtils.getMapFromJSONString(it)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
148
207
|
private fun getIsEnabled(context: Context?, overrideMap: Map<String, Any>?): Boolean {
|
|
149
208
|
return overrideMap?.readValueCheckingType(UPDATES_CONFIGURATION_ENABLED_KEY) ?: context?.getMetadataValue("expo.modules.updates.ENABLED") ?: true
|
|
150
209
|
}
|
|
@@ -154,7 +213,7 @@ data class UpdatesConfiguration(
|
|
|
154
213
|
|
|
155
214
|
if (context != null && runtimeVersion == UPDATES_CONFIGURATION_RUNTIME_VERSION_READ_FINGERPRINT_FILE_SENTINEL) {
|
|
156
215
|
return context.assets.open(FINGERPRINT_FILE_NAME).use { stream ->
|
|
157
|
-
|
|
216
|
+
stream.bufferedReader(Charsets.UTF_8).use { it.readText() }
|
|
158
217
|
}
|
|
159
218
|
}
|
|
160
219
|
|
|
@@ -166,7 +225,9 @@ data class UpdatesConfiguration(
|
|
|
166
225
|
if (!isEnabledConfigSetting) {
|
|
167
226
|
return UpdatesConfigurationValidationResult.INVALID_NOT_ENABLED
|
|
168
227
|
}
|
|
169
|
-
|
|
228
|
+
val disableAntiBrickingMeasures = getDisableAntiBrickingMeasures(context, overrideMap)
|
|
229
|
+
val configOverride = if (context != null) UpdatesConfigurationOverride.load(context) else null
|
|
230
|
+
getUpdateUrl(context, overrideMap, disableAntiBrickingMeasures, configOverride) ?: return UpdatesConfigurationValidationResult.INVALID_MISSING_URL
|
|
170
231
|
|
|
171
232
|
if (getRuntimeVersion(context, overrideMap).isNullOrEmpty()) {
|
|
172
233
|
return UpdatesConfigurationValidationResult.INVALID_MISSING_RUNTIME_VERSION
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package expo.modules.updates
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import expo.modules.manifests.core.toMap
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* [UpdatesConfiguration] values set at runtime that override build-time configuration.
|
|
10
|
+
*/
|
|
11
|
+
data class UpdatesConfigurationOverride(
|
|
12
|
+
val updateUrl: Uri,
|
|
13
|
+
val requestHeaders: Map<String, String>
|
|
14
|
+
) {
|
|
15
|
+
private fun toJSONObject(): JSONObject {
|
|
16
|
+
return JSONObject().apply {
|
|
17
|
+
put("updateUrl", updateUrl.toString())
|
|
18
|
+
put("requestHeaders", JSONObject(requestHeaders))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
private const val UPDATES_PREFS_FILE = "dev.expo.updates.prefs"
|
|
24
|
+
private const val UPDATES_PREFS_KEY_UPDATES_CONFIGURATION_OVERRIDE = "updatesConfigOverride"
|
|
25
|
+
|
|
26
|
+
internal fun load(context: Context): UpdatesConfigurationOverride? {
|
|
27
|
+
val configOverride =
|
|
28
|
+
context.getSharedPreferences(UPDATES_PREFS_FILE, Context.MODE_PRIVATE)
|
|
29
|
+
?.getString(UPDATES_PREFS_KEY_UPDATES_CONFIGURATION_OVERRIDE, null)
|
|
30
|
+
return configOverride?.let {
|
|
31
|
+
fromJSONObject(JSONObject(it))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
internal fun save(context: Context, configOverride: UpdatesConfigurationOverride?) {
|
|
36
|
+
val prefs = context.getSharedPreferences(UPDATES_PREFS_FILE, Context.MODE_PRIVATE)
|
|
37
|
+
with(prefs.edit()) {
|
|
38
|
+
if (configOverride != null) {
|
|
39
|
+
putString(UPDATES_PREFS_KEY_UPDATES_CONFIGURATION_OVERRIDE, configOverride.toJSONObject().toString())
|
|
40
|
+
} else {
|
|
41
|
+
remove(UPDATES_PREFS_KEY_UPDATES_CONFIGURATION_OVERRIDE)
|
|
42
|
+
}
|
|
43
|
+
apply()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun fromJSONObject(json: JSONObject): UpdatesConfigurationOverride {
|
|
48
|
+
val requestHeaders = json.getJSONObject("requestHeaders")
|
|
49
|
+
.toMap()
|
|
50
|
+
.mapValues { it.value.toString() }
|
|
51
|
+
return UpdatesConfigurationOverride(
|
|
52
|
+
updateUrl = Uri.parse(json.getString("updateUrl")),
|
|
53
|
+
requestHeaders = requestHeaders
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -78,8 +78,11 @@ class UpdatesDevLauncherController(
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
@get:Synchronized
|
|
81
|
-
override val launchAssetFile: String
|
|
82
|
-
get()
|
|
81
|
+
override val launchAssetFile: String?
|
|
82
|
+
get() {
|
|
83
|
+
logger.warn("launchAssetFile should not be called from expo-dev-client build, except for Detox testing")
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
83
86
|
|
|
84
87
|
override val bundleAssetName: String
|
|
85
88
|
get() = throw Exception("IUpdatesController.bundleAssetName should not be called in dev client")
|
|
@@ -363,6 +366,10 @@ class UpdatesDevLauncherController(
|
|
|
363
366
|
callback.onFailure(NotAvailableInDevClientException("Updates.setExtraParamAsync() is not supported in development builds."))
|
|
364
367
|
}
|
|
365
368
|
|
|
369
|
+
override fun setUpdateURLAndRequestHeadersOverride(configOverride: UpdatesConfigurationOverride?) {
|
|
370
|
+
throw NotAvailableInDevClientException("Updates.setUpdateURLAndRequestHeadersOverride() is not supported in development builds.")
|
|
371
|
+
}
|
|
372
|
+
|
|
366
373
|
companion object {
|
|
367
374
|
private val TAG = UpdatesDevLauncherController::class.java.simpleName
|
|
368
375
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.updates
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
4
5
|
import android.os.AsyncTask
|
|
5
6
|
import android.os.Bundle
|
|
6
7
|
import expo.modules.kotlin.Promise
|
|
@@ -8,6 +9,8 @@ import expo.modules.kotlin.exception.CodedException
|
|
|
8
9
|
import expo.modules.kotlin.exception.Exceptions
|
|
9
10
|
import expo.modules.kotlin.modules.Module
|
|
10
11
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
12
|
+
import expo.modules.kotlin.records.Field
|
|
13
|
+
import expo.modules.kotlin.records.Record
|
|
11
14
|
import expo.modules.kotlin.types.Enumerable
|
|
12
15
|
import expo.modules.updates.events.IUpdatesEventManagerObserver
|
|
13
16
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
@@ -211,6 +214,10 @@ class UpdatesModule : Module(), IUpdatesEventManagerObserver {
|
|
|
211
214
|
}
|
|
212
215
|
}
|
|
213
216
|
}
|
|
217
|
+
|
|
218
|
+
Function("setUpdateURLAndRequestHeadersOverride") { configOverride: UpdatesConfigurationOverrideParam? ->
|
|
219
|
+
UpdatesController.instance.setUpdateURLAndRequestHeadersOverride(configOverride?.toUpdatesConfigurationOverride())
|
|
220
|
+
}
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
companion object {
|
|
@@ -253,4 +260,13 @@ class UpdatesModule : Module(), IUpdatesEventManagerObserver {
|
|
|
253
260
|
override fun onStateMachineContextEvent(context: UpdatesStateContext) {
|
|
254
261
|
sendEvent(UpdatesJSEvent.StateChange, Bundle().apply { putBundle("context", context.bundle) })
|
|
255
262
|
}
|
|
263
|
+
|
|
264
|
+
internal data class UpdatesConfigurationOverrideParam(
|
|
265
|
+
@Field val updateUrl: Uri,
|
|
266
|
+
@Field val requestHeaders: Map<String, String>
|
|
267
|
+
) : Record {
|
|
268
|
+
fun toUpdatesConfigurationOverride(): UpdatesConfigurationOverride {
|
|
269
|
+
return UpdatesConfigurationOverride(updateUrl, requestHeaders)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
256
272
|
}
|
|
@@ -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
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
package expo.modules.updates.db
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import expo.modules.jsonutils.getNullable
|
|
3
|
+
import expo.modules.manifests.core.toMap
|
|
5
4
|
import expo.modules.updates.UpdatesConfiguration
|
|
5
|
+
import expo.modules.updates.db.dao.JSONDataDao
|
|
6
|
+
import expo.modules.updates.manifest.ManifestMetadata
|
|
6
7
|
import org.json.JSONObject
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -27,8 +28,6 @@ import org.json.JSONObject
|
|
|
27
28
|
* UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY
|
|
28
29
|
*/
|
|
29
30
|
object BuildData {
|
|
30
|
-
private var staticBuildDataKey = "staticBuildData"
|
|
31
|
-
|
|
32
31
|
fun ensureBuildDataIsConsistent(
|
|
33
32
|
updatesConfiguration: UpdatesConfiguration,
|
|
34
33
|
database: UpdatesDatabase
|
|
@@ -39,6 +38,7 @@ object BuildData {
|
|
|
39
38
|
setBuildDataInDatabase(database, updatesConfiguration)
|
|
40
39
|
} else if (!isBuildDataConsistent(updatesConfiguration, buildJSON)) {
|
|
41
40
|
clearAllUpdatesFromDatabase(database)
|
|
41
|
+
clearManifestMetadataFromDatabase(database)
|
|
42
42
|
setBuildDataInDatabase(database, updatesConfiguration)
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -48,28 +48,17 @@ object BuildData {
|
|
|
48
48
|
database.updateDao().deleteUpdates(allUpdates)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
fun clearManifestMetadataFromDatabase(database: UpdatesDatabase) {
|
|
52
|
+
ManifestMetadata.clearMetadataForBuildDataClearOperation(database)
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
fun isBuildDataConsistent(
|
|
52
56
|
updatesConfiguration: UpdatesConfiguration,
|
|
53
57
|
databaseBuildData: JSONObject
|
|
54
58
|
): Boolean {
|
|
55
|
-
val configBuildData = getBuildDataFromConfig(updatesConfiguration)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
val requestHeadersKey = UpdatesConfiguration.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY
|
|
59
|
-
|
|
60
|
-
// check equality of the two JSONObjects. The build data object is string valued with the
|
|
61
|
-
// exception of "requestHeaders" which is a string valued object.
|
|
62
|
-
return mutableListOf<Boolean>().apply {
|
|
63
|
-
add(databaseBuildData.get(updateUrlKey).let { Uri.parse(it.toString()) } == configBuildData.get(updateUrlKey))
|
|
64
|
-
|
|
65
|
-
// loop through keys from both requestHeaders objects.
|
|
66
|
-
for (key in configBuildData.getJSONObject(requestHeadersKey).keys()) {
|
|
67
|
-
add(databaseBuildData.getJSONObject(requestHeadersKey).getNullable<String>(key) == configBuildData.getJSONObject(requestHeadersKey).getNullable(key))
|
|
68
|
-
}
|
|
69
|
-
for (key in databaseBuildData.getJSONObject(requestHeadersKey).keys()) {
|
|
70
|
-
add(databaseBuildData.getJSONObject(requestHeadersKey).getNullable<String>(key) == configBuildData.getJSONObject(requestHeadersKey).getNullable(key))
|
|
71
|
-
}
|
|
72
|
-
}.all { it }
|
|
59
|
+
val configBuildData = defaultBuildData + getBuildDataFromConfig(updatesConfiguration).toMap()
|
|
60
|
+
val dbBuildData = defaultBuildData + databaseBuildData.toMap()
|
|
61
|
+
return configBuildData == dbBuildData
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
fun setBuildDataInDatabase(
|
|
@@ -78,14 +67,14 @@ object BuildData {
|
|
|
78
67
|
) {
|
|
79
68
|
val buildDataJSON = getBuildDataFromConfig(updatesConfiguration)
|
|
80
69
|
database.jsonDataDao()?.setJSONStringForKey(
|
|
81
|
-
|
|
70
|
+
JSONDataDao.JSONDataKey.STATIC_BUILD_DATA,
|
|
82
71
|
buildDataJSON.toString(),
|
|
83
72
|
updatesConfiguration.scopeKey
|
|
84
73
|
)
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
fun getBuildDataFromDatabase(database: UpdatesDatabase, scopeKey: String): JSONObject? {
|
|
88
|
-
val buildJSONString = database.jsonDataDao()?.loadJSONStringForKey(
|
|
77
|
+
val buildJSONString = database.jsonDataDao()?.loadJSONStringForKey(JSONDataDao.JSONDataKey.STATIC_BUILD_DATA, scopeKey)
|
|
89
78
|
return if (buildJSONString == null) null else JSONObject(buildJSONString)
|
|
90
79
|
}
|
|
91
80
|
|
|
@@ -94,9 +83,17 @@ object BuildData {
|
|
|
94
83
|
for ((key, value) in updatesConfiguration.requestHeaders) put(key, value)
|
|
95
84
|
}
|
|
96
85
|
val buildData = JSONObject().apply {
|
|
97
|
-
put(UpdatesConfiguration.UPDATES_CONFIGURATION_UPDATE_URL_KEY, updatesConfiguration.updateUrl)
|
|
86
|
+
put(UpdatesConfiguration.UPDATES_CONFIGURATION_UPDATE_URL_KEY, updatesConfiguration.updateUrl.toString())
|
|
98
87
|
put(UpdatesConfiguration.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY, requestHeadersJSON)
|
|
88
|
+
put(UpdatesConfiguration.UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY, updatesConfiguration.hasEmbeddedUpdate)
|
|
99
89
|
}
|
|
100
90
|
return buildData
|
|
101
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fallback data specifically for migration while database data doesn't have these keys
|
|
95
|
+
*/
|
|
96
|
+
private val defaultBuildData = mapOf(
|
|
97
|
+
UpdatesConfiguration.UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY to true
|
|
98
|
+
)
|
|
102
99
|
}
|
|
@@ -12,6 +12,13 @@ import java.util.*
|
|
|
12
12
|
*/
|
|
13
13
|
@Dao
|
|
14
14
|
abstract class JSONDataDao {
|
|
15
|
+
enum class JSONDataKey(val key: String) {
|
|
16
|
+
STATIC_BUILD_DATA("staticBuildData"),
|
|
17
|
+
EXTRA_PARAMS("extraParams"),
|
|
18
|
+
MANIFEST_SERVER_DEFINED_HEADERS("serverDefinedHeaders"),
|
|
19
|
+
MANIFEST_FILTERS("manifestFilters")
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
@Query("SELECT * FROM json_data WHERE `key` = :key AND scope_key = :scopeKey ORDER BY last_updated DESC LIMIT 1;")
|
|
16
23
|
protected abstract fun loadJSONDataForKeyInternal(key: String, scopeKey: String): List<JSONDataEntity>
|
|
17
24
|
|
|
@@ -21,11 +28,14 @@ abstract class JSONDataDao {
|
|
|
21
28
|
@Query("DELETE FROM json_data WHERE `key` = :key AND scope_key = :scopeKey;")
|
|
22
29
|
protected abstract fun deleteJSONDataForKeyInternal(key: String, scopeKey: String)
|
|
23
30
|
|
|
31
|
+
@Query("DELETE FROM json_data WHERE `key` IN (:keys)")
|
|
32
|
+
protected abstract fun deleteJSONDataForKeysForAllScopeKeysInternal(keys: List<String>)
|
|
33
|
+
|
|
24
34
|
/**
|
|
25
35
|
* for public use
|
|
26
36
|
*/
|
|
27
|
-
fun loadJSONStringForKey(key:
|
|
28
|
-
val rows = loadJSONDataForKeyInternal(key, scopeKey)
|
|
37
|
+
fun loadJSONStringForKey(key: JSONDataKey, scopeKey: String): String? {
|
|
38
|
+
val rows = loadJSONDataForKeyInternal(key.key, scopeKey)
|
|
29
39
|
return if (rows.isEmpty()) {
|
|
30
40
|
null
|
|
31
41
|
} else {
|
|
@@ -34,25 +44,30 @@ abstract class JSONDataDao {
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
@Transaction
|
|
37
|
-
open fun setJSONStringForKey(key:
|
|
38
|
-
deleteJSONDataForKeyInternal(key, scopeKey)
|
|
39
|
-
insertJSONDataInternal(JSONDataEntity(key, value, Date(), scopeKey))
|
|
47
|
+
open fun setJSONStringForKey(key: JSONDataKey, value: String, scopeKey: String) {
|
|
48
|
+
deleteJSONDataForKeyInternal(key.key, scopeKey)
|
|
49
|
+
insertJSONDataInternal(JSONDataEntity(key.key, value, Date(), scopeKey))
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
@Transaction
|
|
43
|
-
open fun setMultipleFields(fields: Map<
|
|
53
|
+
open fun setMultipleFields(fields: Map<JSONDataKey, String>, scopeKey: String) {
|
|
44
54
|
val iterator = fields.entries.iterator()
|
|
45
55
|
while (iterator.hasNext()) {
|
|
46
56
|
val entry = iterator.next()
|
|
47
|
-
deleteJSONDataForKeyInternal(entry.key, scopeKey)
|
|
48
|
-
insertJSONDataInternal(JSONDataEntity(entry.key, entry.value, Date(), scopeKey))
|
|
57
|
+
deleteJSONDataForKeyInternal(entry.key.key, scopeKey)
|
|
58
|
+
insertJSONDataInternal(JSONDataEntity(entry.key.key, entry.value, Date(), scopeKey))
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
@Transaction
|
|
53
|
-
open fun updateJSONStringForKey(key:
|
|
63
|
+
open fun updateJSONStringForKey(key: JSONDataKey, scopeKey: String, updater: (previousValue: String?) -> String) {
|
|
54
64
|
val previousValue = loadJSONStringForKey(key, scopeKey)
|
|
55
|
-
deleteJSONDataForKeyInternal(key, scopeKey)
|
|
56
|
-
insertJSONDataInternal(JSONDataEntity(key, updater(previousValue), Date(), scopeKey))
|
|
65
|
+
deleteJSONDataForKeyInternal(key.key, scopeKey)
|
|
66
|
+
insertJSONDataInternal(JSONDataEntity(key.key, updater(previousValue), Date(), scopeKey))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Transaction
|
|
70
|
+
open fun deleteJSONDataForKeysForAllScopeKeys(keys: List<JSONDataKey>) {
|
|
71
|
+
deleteJSONDataForKeysForAllScopeKeysInternal(keys.map { it.key })
|
|
57
72
|
}
|
|
58
73
|
}
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.bridge.ReactMarker.MarkerListener
|
|
|
8
8
|
import com.facebook.react.bridge.ReactMarkerConstants
|
|
9
9
|
import com.facebook.react.devsupport.ReleaseDevSupportManager
|
|
10
10
|
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
|
11
|
-
import expo.modules.rncompatibility.
|
|
11
|
+
import expo.modules.rncompatibility.IReactNativeFeatureFlagsProvider
|
|
12
12
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
13
13
|
import expo.modules.updates.logging.UpdatesLogger
|
|
14
14
|
import java.lang.ref.WeakReference
|
|
@@ -27,7 +27,8 @@ import java.lang.ref.WeakReference
|
|
|
27
27
|
* and so there is no more need to trigger the error recovery pipeline.
|
|
28
28
|
*/
|
|
29
29
|
class ErrorRecovery(
|
|
30
|
-
private val logger: UpdatesLogger
|
|
30
|
+
private val logger: UpdatesLogger,
|
|
31
|
+
private val reactNativeFeatureFlagsProvider: IReactNativeFeatureFlagsProvider
|
|
31
32
|
) {
|
|
32
33
|
internal val handlerThread = HandlerThread("expo-updates-error-recovery")
|
|
33
34
|
internal lateinit var handler: Handler
|
|
@@ -97,7 +98,7 @@ class ErrorRecovery(
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
private fun registerErrorHandler(devSupportManager: DevSupportManager) {
|
|
100
|
-
if (
|
|
101
|
+
if (reactNativeFeatureFlagsProvider.enableBridgelessArchitecture) {
|
|
101
102
|
registerErrorHandlerImplBridgeless()
|
|
102
103
|
} else {
|
|
103
104
|
registerErrorHandlerImplBridge(devSupportManager)
|
|
@@ -130,7 +131,7 @@ class ErrorRecovery(
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
private fun unregisterErrorHandler() {
|
|
133
|
-
if (
|
|
134
|
+
if (reactNativeFeatureFlagsProvider.enableBridgelessArchitecture) {
|
|
134
135
|
unregisterErrorHandlerImplBridgeless()
|
|
135
136
|
} else {
|
|
136
137
|
unregisterErrorHandlerImplBridge()
|
|
@@ -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) {
|
|
@@ -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)
|