expo-updates 0.24.3 → 0.24.4
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 +2 -2
- package/android/src/main/java/expo/modules/updates/DisabledUpdatesController.kt +47 -8
- package/android/src/main/java/expo/modules/updates/EnabledUpdatesController.kt +7 -7
- package/android/src/main/java/expo/modules/updates/IUpdatesController.kt +9 -0
- package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +4 -3
- package/android/src/main/java/expo/modules/updates/UpdatesController.kt +5 -2
- package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +8 -3
- package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +5 -7
- package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +2 -3
- package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +3 -1
- package/android/src/main/java/expo/modules/updates/codesigning/CertificateChain.kt +3 -1
- package/android/src/main/java/expo/modules/updates/codesigning/CodeSigningConfiguration.kt +1 -1
- package/android/src/main/java/expo/modules/updates/codesigning/SignatureHeaderInfo.kt +9 -3
- package/android/src/main/java/expo/modules/updates/db/BuildData.kt +2 -2
- package/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt +3 -1
- package/android/src/main/java/expo/modules/updates/db/dao/JSONDataDao.kt +3 -1
- package/android/src/main/java/expo/modules/updates/db/entity/UpdateEntity.kt +1 -1
- package/android/src/main/java/expo/modules/updates/db/enums/UpdateStatus.kt +3 -0
- package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt +5 -1
- package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +4 -4
- package/android/src/main/java/expo/modules/updates/loader/LegacySignatureUtils.kt +3 -1
- package/android/src/main/java/expo/modules/updates/loader/Loader.kt +20 -9
- package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +10 -3
- package/android/src/main/java/expo/modules/updates/logging/UpdatesLogEntry.kt +1 -1
- package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +8 -4
- package/android/src/main/java/expo/modules/updates/manifest/ResponseHeaderData.kt +1 -1
- package/android/src/main/java/expo/modules/updates/procedures/RecreateReactContextProcedure.kt +30 -0
- package/android/src/main/java/expo/modules/updates/procedures/RelaunchProcedure.kt +1 -1
- package/android/src/main/java/expo/modules/updates/procedures/StartupProcedure.kt +11 -4
- package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicyFilterAware.kt +6 -2
- package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateContext.kt +1 -1
- package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateMachine.kt +2 -2
- package/build/Updates.d.ts.map +1 -1
- package/build/Updates.js +19 -11
- package/build/Updates.js.map +1 -1
- package/e2e/README.md +2 -2
- package/e2e/fixtures/App-updates-disabled.tsx +22 -0
- package/e2e/fixtures/Updates-dev-client.e2e.ts +77 -0
- package/e2e/fixtures/Updates-disabled.e2e.ts +38 -0
- package/e2e/setup/create-dev-client-eas-project.ts +41 -0
- package/e2e/setup/project.ts +75 -36
- package/ios/EXUpdates/AppController.swift +15 -1
- package/ios/EXUpdates/DevLauncherAppController.swift +7 -3
- package/ios/EXUpdates/DisabledAppController.swift +16 -4
- package/ios/EXUpdates/EnabledAppController.swift +2 -1
- package/ios/EXUpdates/Procedures/RecreateReactContextProcedure.swift +31 -0
- package/ios/EXUpdates/UpdatesModule.swift +5 -6
- package/package.json +2 -2
- package/src/Updates.ts +30 -11
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 0.24.4 — 2023-12-19
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- Add relaunch to disabled and dev client controllers. ([#25973](https://github.com/expo/expo/pull/25973) by [@wschurman](https://github.com/wschurman))
|
|
18
|
+
|
|
13
19
|
## 0.24.3 — 2023-12-15
|
|
14
20
|
|
|
15
21
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
|
|
|
4
4
|
apply plugin: 'maven-publish'
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '0.24.
|
|
7
|
+
version = '0.24.4'
|
|
8
8
|
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
10
10
|
if (expoModulesCorePlugin.exists()) {
|
|
@@ -122,7 +122,7 @@ android {
|
|
|
122
122
|
namespace "expo.modules.updates"
|
|
123
123
|
defaultConfig {
|
|
124
124
|
versionCode 31
|
|
125
|
-
versionName '0.24.
|
|
125
|
+
versionName '0.24.4'
|
|
126
126
|
consumerProguardFiles("proguard-rules.pro")
|
|
127
127
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
128
128
|
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
@file:Suppress("UnusedImport") // this needs to stay for versioning to work
|
|
2
|
+
|
|
1
3
|
package expo.modules.updates
|
|
2
4
|
|
|
3
5
|
import android.content.Context
|
|
4
6
|
import android.os.Bundle
|
|
5
7
|
import android.util.Log
|
|
8
|
+
import com.facebook.react.ReactApplication
|
|
6
9
|
import com.facebook.react.ReactInstanceManager
|
|
10
|
+
import com.facebook.react.ReactNativeHost
|
|
11
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
12
|
import expo.modules.kotlin.exception.CodedException
|
|
13
|
+
import expo.modules.kotlin.exception.toCodedException
|
|
8
14
|
import expo.modules.updates.UpdatesConfiguration.Companion.UPDATES_CONFIGURATION_RELEASE_CHANNEL_DEFAULT_VALUE
|
|
9
15
|
import expo.modules.updates.launcher.Launcher
|
|
10
16
|
import expo.modules.updates.launcher.NoDatabaseLauncher
|
|
17
|
+
import expo.modules.updates.logging.UpdatesLogger
|
|
18
|
+
import expo.modules.updates.procedures.RecreateReactContextProcedure
|
|
19
|
+
import expo.modules.updates.statemachine.UpdatesStateChangeEventSender
|
|
11
20
|
import expo.modules.updates.statemachine.UpdatesStateContext
|
|
21
|
+
import expo.modules.updates.statemachine.UpdatesStateEventType
|
|
22
|
+
import expo.modules.updates.statemachine.UpdatesStateMachine
|
|
12
23
|
import java.io.File
|
|
13
|
-
|
|
14
|
-
// this needs to stay for versioning to work
|
|
15
|
-
/* ktlint-disable no-unused-imports */
|
|
16
|
-
import expo.modules.updates.UpdatesConfiguration
|
|
17
|
-
/* ktlint-enable no-unused-imports */
|
|
24
|
+
import java.lang.ref.WeakReference
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Updates controller for applications that either disable updates explicitly or have an error
|
|
@@ -27,7 +34,15 @@ class DisabledUpdatesController(
|
|
|
27
34
|
private val context: Context,
|
|
28
35
|
private val fatalException: Exception?,
|
|
29
36
|
private val isMissingRuntimeVersion: Boolean
|
|
30
|
-
) : IUpdatesController {
|
|
37
|
+
) : IUpdatesController, UpdatesStateChangeEventSender {
|
|
38
|
+
private val reactNativeHost: WeakReference<ReactNativeHost>? = if (context is ReactApplication) {
|
|
39
|
+
WeakReference(context.reactNativeHost)
|
|
40
|
+
} else {
|
|
41
|
+
null
|
|
42
|
+
}
|
|
43
|
+
private val logger = UpdatesLogger(context)
|
|
44
|
+
private val stateMachine = UpdatesStateMachine(context, this)
|
|
45
|
+
|
|
31
46
|
private var isStarted = false
|
|
32
47
|
private var launcher: Launcher? = null
|
|
33
48
|
private var isLoaderTaskFinished = false
|
|
@@ -82,15 +97,28 @@ class DisabledUpdatesController(
|
|
|
82
97
|
requestHeaders = mapOf(),
|
|
83
98
|
localAssetFiles = launcher?.localAssetFiles,
|
|
84
99
|
isMissingRuntimeVersion = isMissingRuntimeVersion,
|
|
100
|
+
shouldDeferToNativeForAPIMethodAvailabilityInDevelopment = false
|
|
85
101
|
)
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
override fun relaunchReactApplicationForModule(callback: IUpdatesController.ModuleCallback<Unit>) {
|
|
89
|
-
|
|
105
|
+
val procedure = RecreateReactContextProcedure(
|
|
106
|
+
reactNativeHost,
|
|
107
|
+
object : Launcher.LauncherCallback {
|
|
108
|
+
override fun onFailure(e: Exception) {
|
|
109
|
+
callback.onFailure(e.toCodedException())
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override fun onSuccess() {
|
|
113
|
+
callback.onSuccess(Unit)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
stateMachine.queueExecution(procedure)
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
override fun getNativeStateMachineContext(callback: IUpdatesController.ModuleCallback<UpdatesStateContext>) {
|
|
93
|
-
callback.
|
|
121
|
+
callback.onSuccess(stateMachine.context)
|
|
94
122
|
}
|
|
95
123
|
|
|
96
124
|
override fun checkForUpdate(
|
|
@@ -129,4 +157,15 @@ class DisabledUpdatesController(
|
|
|
129
157
|
companion object {
|
|
130
158
|
private val TAG = DisabledUpdatesController::class.java.simpleName
|
|
131
159
|
}
|
|
160
|
+
|
|
161
|
+
override fun sendUpdateStateChangeEventToBridge(
|
|
162
|
+
eventType: UpdatesStateEventType,
|
|
163
|
+
context: UpdatesStateContext
|
|
164
|
+
) {
|
|
165
|
+
sendEventToJS(EnabledUpdatesController.UPDATES_STATE_CHANGE_EVENT_NAME, eventType.type, context.writableMap)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun sendEventToJS(eventName: String, eventType: String, params: WritableMap?) {
|
|
169
|
+
UpdatesUtils.sendEventToReactNative(reactNativeHost, logger, eventName, eventType, params)
|
|
170
|
+
}
|
|
132
171
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@file:Suppress("UnusedImport") // this needs to stay for versioning to work
|
|
2
|
+
|
|
1
3
|
package expo.modules.updates
|
|
2
4
|
|
|
3
5
|
import android.content.Context
|
|
@@ -33,9 +35,6 @@ import java.io.File
|
|
|
33
35
|
import java.lang.ref.WeakReference
|
|
34
36
|
|
|
35
37
|
// this needs to stay for versioning to work
|
|
36
|
-
/* ktlint-disable no-unused-imports */
|
|
37
|
-
import expo.modules.updates.UpdatesConfiguration
|
|
38
|
-
/* ktlint-enable no-unused-imports */
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* Updates controller for applications that have updates enabled and properly-configured.
|
|
@@ -45,7 +44,7 @@ class EnabledUpdatesController(
|
|
|
45
44
|
private val updatesConfiguration: UpdatesConfiguration,
|
|
46
45
|
override val updatesDirectory: File
|
|
47
46
|
) : IUpdatesController, UpdatesStateChangeEventSender {
|
|
48
|
-
private
|
|
47
|
+
private val reactNativeHost: WeakReference<ReactNativeHost>? = if (context is ReactApplication) {
|
|
49
48
|
WeakReference(context.reactNativeHost)
|
|
50
49
|
} else {
|
|
51
50
|
null
|
|
@@ -198,6 +197,7 @@ class EnabledUpdatesController(
|
|
|
198
197
|
requestHeaders = updatesConfiguration.requestHeaders,
|
|
199
198
|
localAssetFiles = localAssetFiles,
|
|
200
199
|
isMissingRuntimeVersion = false,
|
|
200
|
+
shouldDeferToNativeForAPIMethodAvailabilityInDevelopment = false
|
|
201
201
|
)
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -244,7 +244,7 @@ class EnabledUpdatesController(
|
|
|
244
244
|
try {
|
|
245
245
|
val result = ManifestMetadata.getExtraParams(
|
|
246
246
|
databaseHolder.database,
|
|
247
|
-
updatesConfiguration
|
|
247
|
+
updatesConfiguration
|
|
248
248
|
)
|
|
249
249
|
databaseHolder.releaseDatabase()
|
|
250
250
|
val resultMap = when (result) {
|
|
@@ -290,7 +290,7 @@ class EnabledUpdatesController(
|
|
|
290
290
|
private const val UPDATE_NO_UPDATE_AVAILABLE_EVENT = "noUpdateAvailable"
|
|
291
291
|
private const val UPDATE_ERROR_EVENT = "error"
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
const val UPDATES_EVENT_NAME = "Expo.nativeUpdatesEvent"
|
|
294
|
+
const val UPDATES_STATE_CHANGE_EVENT_NAME = "Expo.nativeUpdatesStateChangeEvent"
|
|
295
295
|
}
|
|
296
296
|
}
|
|
@@ -78,6 +78,15 @@ interface IUpdatesController {
|
|
|
78
78
|
* updates will be disabled and a warning will be logged.
|
|
79
79
|
*/
|
|
80
80
|
val isMissingRuntimeVersion: Boolean,
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether the JS API methods should allow calling the native module methods and thus the methods
|
|
84
|
+
* on the controller in development. For non-expo development we want to throw
|
|
85
|
+
* at the JS layer since there isn't a controller set up. But for development within Expo Go
|
|
86
|
+
* or a Dev Client, which have their own controller/JS API implementations, we want the JS API
|
|
87
|
+
* calls to go through.
|
|
88
|
+
*/
|
|
89
|
+
val shouldDeferToNativeForAPIMethodAvailabilityInDevelopment: Boolean
|
|
81
90
|
)
|
|
82
91
|
fun getConstantsForModule(): UpdatesModuleConstants
|
|
83
92
|
|
|
@@ -40,7 +40,7 @@ data class UpdatesConfiguration(
|
|
|
40
40
|
val codeSigningMetadata: Map<String, String>?,
|
|
41
41
|
val codeSigningIncludeManifestResponseCertificateChain: Boolean,
|
|
42
42
|
private val codeSigningAllowUnsignedManifests: Boolean,
|
|
43
|
-
val enableExpoUpdatesProtocolV0CompatibilityMode: Boolean
|
|
43
|
+
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
|
|
44
44
|
) {
|
|
45
45
|
enum class CheckAutomaticallyConfiguration {
|
|
46
46
|
NEVER {
|
|
@@ -55,6 +55,7 @@ data class UpdatesConfiguration(
|
|
|
55
55
|
ALWAYS {
|
|
56
56
|
override fun toJSString() = "ALWAYS"
|
|
57
57
|
};
|
|
58
|
+
|
|
58
59
|
open fun toJSString(): String {
|
|
59
60
|
throw InvalidArgumentException("Unsupported CheckAutomaticallyConfiguration value")
|
|
60
61
|
}
|
|
@@ -64,7 +65,7 @@ data class UpdatesConfiguration(
|
|
|
64
65
|
expectsSignedManifest = overrideMap?.readValueCheckingType(UPDATES_CONFIGURATION_EXPECTS_EXPO_SIGNED_MANIFEST) ?: false,
|
|
65
66
|
scopeKey = maybeGetDefaultScopeKey(
|
|
66
67
|
overrideMap?.readValueCheckingType<String>(UPDATES_CONFIGURATION_SCOPE_KEY_KEY) ?: context?.getMetadataValue("expo.modules.updates.EXPO_SCOPE_KEY"),
|
|
67
|
-
updateUrl = getUpdatesUrl(context, overrideMap)
|
|
68
|
+
updateUrl = getUpdatesUrl(context, overrideMap)!!
|
|
68
69
|
),
|
|
69
70
|
updateUrl = getUpdatesUrl(context, overrideMap)!!,
|
|
70
71
|
sdkVersion = getSDKVersion(context, overrideMap),
|
|
@@ -102,7 +103,7 @@ data class UpdatesConfiguration(
|
|
|
102
103
|
codeSigningAllowUnsignedManifests = overrideMap?.readValueCheckingType<Boolean>(
|
|
103
104
|
UPDATES_CONFIGURATION_CODE_SIGNING_ALLOW_UNSIGNED_MANIFESTS
|
|
104
105
|
) ?: context?.getMetadataValue("expo.modules.updates.CODE_SIGNING_ALLOW_UNSIGNED_MANIFESTS") ?: false,
|
|
105
|
-
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
|
|
106
|
+
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
|
|
106
107
|
)
|
|
107
108
|
|
|
108
109
|
val codeSigningConfiguration: CodeSigningConfiguration? by lazy {
|
|
@@ -6,6 +6,7 @@ import com.facebook.react.ReactNativeHost
|
|
|
6
6
|
import expo.modules.updates.loader.LoaderTask
|
|
7
7
|
import expo.modules.updates.logging.UpdatesErrorCode
|
|
8
8
|
import expo.modules.updates.logging.UpdatesLogger
|
|
9
|
+
import expo.modules.updatesinterface.UpdatesInterfaceCallbacks
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Main entry point to expo-updates. Singleton that keeps track of updates state, holds references
|
|
@@ -22,6 +23,7 @@ import expo.modules.updates.logging.UpdatesLogger
|
|
|
22
23
|
class UpdatesController {
|
|
23
24
|
companion object {
|
|
24
25
|
private var singletonInstance: IUpdatesController? = null
|
|
26
|
+
|
|
25
27
|
@JvmStatic val instance: IUpdatesController
|
|
26
28
|
get() {
|
|
27
29
|
return checkNotNull(singletonInstance) { "UpdatesController.instance was called before the module was initialized" }
|
|
@@ -70,7 +72,7 @@ class UpdatesController {
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
@JvmStatic fun initializeAsDevLauncherWithoutStarting(context: Context): UpdatesDevLauncherController {
|
|
75
|
+
@JvmStatic fun initializeAsDevLauncherWithoutStarting(context: Context, callbacks: UpdatesInterfaceCallbacks): UpdatesDevLauncherController {
|
|
74
76
|
check(singletonInstance == null) { "UpdatesController must not be initialized prior to calling initializeAsDevLauncherWithoutStarting" }
|
|
75
77
|
|
|
76
78
|
var updatesDirectoryException: Exception? = null
|
|
@@ -91,7 +93,8 @@ class UpdatesController {
|
|
|
91
93
|
initialUpdatesConfiguration,
|
|
92
94
|
updatesDirectory,
|
|
93
95
|
updatesDirectoryException,
|
|
94
|
-
UpdatesConfiguration.isMissingRuntimeVersion(context, null)
|
|
96
|
+
UpdatesConfiguration.isMissingRuntimeVersion(context, null),
|
|
97
|
+
callbacks
|
|
95
98
|
)
|
|
96
99
|
singletonInstance = instance
|
|
97
100
|
return instance
|
|
@@ -23,6 +23,7 @@ import expo.modules.updates.selectionpolicy.ReaperSelectionPolicyDevelopmentClie
|
|
|
23
23
|
import expo.modules.updates.selectionpolicy.SelectionPolicy
|
|
24
24
|
import expo.modules.updates.selectionpolicy.SelectionPolicyFactory
|
|
25
25
|
import expo.modules.updates.statemachine.UpdatesStateContext
|
|
26
|
+
import expo.modules.updatesinterface.UpdatesInterfaceCallbacks
|
|
26
27
|
import expo.modules.updatesinterface.UpdatesInterface
|
|
27
28
|
import org.json.JSONObject
|
|
28
29
|
import java.io.File
|
|
@@ -42,7 +43,8 @@ class UpdatesDevLauncherController(
|
|
|
42
43
|
initialUpdatesConfiguration: UpdatesConfiguration?,
|
|
43
44
|
override val updatesDirectory: File?,
|
|
44
45
|
private val updatesDirectoryException: Exception?,
|
|
45
|
-
private val isMissingRuntimeVersion: Boolean
|
|
46
|
+
private val isMissingRuntimeVersion: Boolean,
|
|
47
|
+
private val callbacks: UpdatesInterfaceCallbacks
|
|
46
48
|
) : IUpdatesController, UpdatesInterface {
|
|
47
49
|
override val isEmergencyLaunch = updatesDirectoryException != null
|
|
48
50
|
|
|
@@ -224,7 +226,8 @@ class UpdatesDevLauncherController(
|
|
|
224
226
|
selectionPolicy
|
|
225
227
|
)
|
|
226
228
|
launcher.launch(
|
|
227
|
-
databaseHolder.database,
|
|
229
|
+
databaseHolder.database,
|
|
230
|
+
context,
|
|
228
231
|
object : Launcher.LauncherCallback {
|
|
229
232
|
override fun onFailure(e: Exception) {
|
|
230
233
|
databaseHolder.releaseDatabase()
|
|
@@ -287,13 +290,15 @@ class UpdatesDevLauncherController(
|
|
|
287
290
|
requestHeaders = updatesConfiguration?.requestHeaders ?: mapOf(),
|
|
288
291
|
localAssetFiles = localAssetFiles,
|
|
289
292
|
isMissingRuntimeVersion = isMissingRuntimeVersion,
|
|
293
|
+
shouldDeferToNativeForAPIMethodAvailabilityInDevelopment = true
|
|
290
294
|
)
|
|
291
295
|
}
|
|
292
296
|
|
|
293
297
|
override fun relaunchReactApplicationForModule(
|
|
294
298
|
callback: IUpdatesController.ModuleCallback<Unit>
|
|
295
299
|
) {
|
|
296
|
-
|
|
300
|
+
callbacks.onRequestRelaunch()
|
|
301
|
+
callback.onSuccess(Unit)
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
override fun getNativeStateMachineContext(callback: IUpdatesController.ModuleCallback<UpdatesStateContext>) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@file:Suppress("UnusedImport") // this needs to stay for versioning to work
|
|
2
|
+
|
|
1
3
|
package expo.modules.updates
|
|
2
4
|
|
|
3
5
|
import android.content.Context
|
|
@@ -17,11 +19,6 @@ import expo.modules.updates.statemachine.UpdatesStateContext
|
|
|
17
19
|
import java.util.Date
|
|
18
20
|
|
|
19
21
|
// these unused imports must stay because of versioning
|
|
20
|
-
/* ktlint-disable no-unused-imports */
|
|
21
|
-
import expo.modules.updates.IUpdatesController
|
|
22
|
-
import expo.modules.updates.UpdatesConfiguration
|
|
23
|
-
import expo.modules.updates.UpdatesController
|
|
24
|
-
/* ktlint-enable no-unused-imports */
|
|
25
22
|
|
|
26
23
|
/**
|
|
27
24
|
* Exported module which provides to the JS runtime information about the currently running update
|
|
@@ -56,7 +53,7 @@ class UpdatesModule : Module() {
|
|
|
56
53
|
constants["runtimeVersion"] = constantsForModule.runtimeVersion ?: ""
|
|
57
54
|
constants["checkAutomatically"] = constantsForModule.checkOnLaunch.toJSString()
|
|
58
55
|
constants["channel"] = constantsForModule.requestHeaders["expo-channel-name"] ?: ""
|
|
59
|
-
constants["
|
|
56
|
+
constants["shouldDeferToNativeForAPIMethodAvailabilityInDevelopment"] = constantsForModule.shouldDeferToNativeForAPIMethodAvailabilityInDevelopment || BuildConfig.EX_UPDATES_NATIVE_DEBUG
|
|
60
57
|
|
|
61
58
|
if (launchedUpdate != null) {
|
|
62
59
|
constants["updateId"] = launchedUpdate.id.toString()
|
|
@@ -222,7 +219,8 @@ class UpdatesModule : Module() {
|
|
|
222
219
|
AsyncFunction("setExtraParamAsync") { key: String, value: String?, promise: Promise ->
|
|
223
220
|
logger.debug("Called setExtraParamAsync with key = $key, value = $value")
|
|
224
221
|
UpdatesController.instance.setExtraParam(
|
|
225
|
-
key,
|
|
222
|
+
key,
|
|
223
|
+
value,
|
|
226
224
|
object : IUpdatesController.ModuleCallback<Unit> {
|
|
227
225
|
override fun onSuccess(result: Unit) {
|
|
228
226
|
promise.resolve(null)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@file:Suppress("UnusedImport") // this needs to stay for versioning to work
|
|
2
|
+
|
|
1
3
|
package expo.modules.updates
|
|
2
4
|
|
|
3
5
|
import android.content.Context
|
|
@@ -18,9 +20,6 @@ import kotlinx.coroutines.launch
|
|
|
18
20
|
import kotlinx.coroutines.withContext
|
|
19
21
|
|
|
20
22
|
// these unused imports must stay because of versioning
|
|
21
|
-
/* ktlint-disable no-unused-imports */
|
|
22
|
-
import expo.modules.updates.UpdatesController
|
|
23
|
-
/* ktlint-enable no-unused-imports */
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Defines the internal and exported modules for expo-updates, as well as the auto-setup behavior in
|
|
@@ -155,7 +155,9 @@ object UpdatesUtils {
|
|
|
155
155
|
return if (asset.key == null) {
|
|
156
156
|
// create a filename that's unlikely to collide with any other asset
|
|
157
157
|
"asset-" + Date().time + "-" + Random().nextInt() + fileExtension
|
|
158
|
-
} else
|
|
158
|
+
} else {
|
|
159
|
+
asset.key + fileExtension
|
|
160
|
+
}
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
fun sendEventToReactNative(
|
|
@@ -64,7 +64,9 @@ class CertificateChain(private val certificateStrings: List<String>) {
|
|
|
64
64
|
}?.let {
|
|
65
65
|
if (it is DEROctetString) {
|
|
66
66
|
it.octets.decodeToString()
|
|
67
|
-
} else
|
|
67
|
+
} else {
|
|
68
|
+
null
|
|
69
|
+
}
|
|
68
70
|
}?.let {
|
|
69
71
|
val components = it.split(',').map { component -> component.trim() }
|
|
70
72
|
if (components.size != 2) {
|
|
@@ -18,13 +18,19 @@ data class SignatureHeaderInfo(val signature: String, val keyId: String, val alg
|
|
|
18
18
|
|
|
19
19
|
val signature = if (sigFieldValue is StringItem) {
|
|
20
20
|
sigFieldValue.get()
|
|
21
|
-
} else
|
|
21
|
+
} else {
|
|
22
|
+
throw Exception("Structured field $CODE_SIGNING_SIGNATURE_STRUCTURED_FIELD_KEY_SIGNATURE not found in expo-signature header")
|
|
23
|
+
}
|
|
22
24
|
val keyId = if (keyIdFieldValue is StringItem) {
|
|
23
25
|
keyIdFieldValue.get()
|
|
24
|
-
} else
|
|
26
|
+
} else {
|
|
27
|
+
CODE_SIGNING_METADATA_DEFAULT_KEY_ID
|
|
28
|
+
}
|
|
25
29
|
val alg = if (algFieldValue is StringItem) {
|
|
26
30
|
algFieldValue.get()
|
|
27
|
-
} else
|
|
31
|
+
} else {
|
|
32
|
+
null
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
return SignatureHeaderInfo(signature, keyId, CodeSigningAlgorithm.parseFromString(alg))
|
|
30
36
|
}
|
|
@@ -33,7 +33,7 @@ object BuildData {
|
|
|
33
33
|
|
|
34
34
|
fun ensureBuildDataIsConsistent(
|
|
35
35
|
updatesConfiguration: UpdatesConfiguration,
|
|
36
|
-
database: UpdatesDatabase
|
|
36
|
+
database: UpdatesDatabase
|
|
37
37
|
) {
|
|
38
38
|
val scopeKey = updatesConfiguration.scopeKey
|
|
39
39
|
val buildJSON = getBuildDataFromDatabase(database, scopeKey)
|
|
@@ -78,7 +78,7 @@ object BuildData {
|
|
|
78
78
|
|
|
79
79
|
fun setBuildDataInDatabase(
|
|
80
80
|
database: UpdatesDatabase,
|
|
81
|
-
updatesConfiguration: UpdatesConfiguration
|
|
81
|
+
updatesConfiguration: UpdatesConfiguration
|
|
82
82
|
) {
|
|
83
83
|
val buildDataJSON = getBuildDataFromConfig(updatesConfiguration)
|
|
84
84
|
database.jsonDataDao()?.setJSONStringForKey(
|
|
@@ -37,7 +37,7 @@ class UpdateEntity(
|
|
|
37
37
|
@field:ColumnInfo(name = "commit_time") var commitTime: Date,
|
|
38
38
|
@field:ColumnInfo(name = "runtime_version") var runtimeVersion: String,
|
|
39
39
|
@field:ColumnInfo(name = "scope_key") var scopeKey: String,
|
|
40
|
-
@field:ColumnInfo(name = "manifest") var manifest: JSONObject
|
|
40
|
+
@field:ColumnInfo(name = "manifest") var manifest: JSONObject
|
|
41
41
|
) {
|
|
42
42
|
@ColumnInfo(name = "launch_asset_id")
|
|
43
43
|
var launchAssetId: Long? = null
|
|
@@ -9,11 +9,13 @@ enum class UpdateStatus {
|
|
|
9
9
|
* The update has been fully downloaded and is ready to launch.
|
|
10
10
|
*/
|
|
11
11
|
READY,
|
|
12
|
+
|
|
12
13
|
/**
|
|
13
14
|
* The update manifest has been download from the server but not all assets have finished
|
|
14
15
|
* downloading successfully.
|
|
15
16
|
*/
|
|
16
17
|
PENDING,
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* The update has been partially loaded (copied) from its location embedded in the app bundle, but
|
|
19
21
|
* not all assets have been copied successfully. The update may be able to be launched directly
|
|
@@ -21,6 +23,7 @@ enum class UpdateStatus {
|
|
|
21
23
|
* installed.
|
|
22
24
|
*/
|
|
23
25
|
EMBEDDED,
|
|
26
|
+
|
|
24
27
|
/**
|
|
25
28
|
* The update manifest has been downloaded and indicates that the update is being served from a
|
|
26
29
|
* developer tool. It can be launched by a host application that can run a development bundle.
|
|
@@ -30,7 +30,11 @@ class EmbeddedLoader internal constructor(
|
|
|
30
30
|
updatesDirectory: File,
|
|
31
31
|
private val loaderFiles: LoaderFiles
|
|
32
32
|
) : Loader(
|
|
33
|
-
context,
|
|
33
|
+
context,
|
|
34
|
+
configuration,
|
|
35
|
+
database,
|
|
36
|
+
updatesDirectory,
|
|
37
|
+
loaderFiles
|
|
34
38
|
) {
|
|
35
39
|
|
|
36
40
|
constructor(
|
|
@@ -105,7 +105,7 @@ open class FileDownloader(context: Context, private val client: OkHttpClient) {
|
|
|
105
105
|
protocolVersionRaw = responseHeaders["expo-protocol-version"],
|
|
106
106
|
manifestFiltersRaw = responseHeaders["expo-manifest-filters"],
|
|
107
107
|
serverDefinedHeadersRaw = responseHeaders["expo-server-defined-headers"],
|
|
108
|
-
manifestSignature = responseHeaders["expo-manifest-signature"]
|
|
108
|
+
manifestSignature = responseHeaders["expo-manifest-signature"]
|
|
109
109
|
)
|
|
110
110
|
val responseBody = response.body
|
|
111
111
|
|
|
@@ -378,7 +378,7 @@ open class FileDownloader(context: Context, private val client: OkHttpClient) {
|
|
|
378
378
|
val signatureValidationResult = codeSigningConfiguration.validateSignature(
|
|
379
379
|
directiveResponsePartInfo.responsePartHeaderData.signature,
|
|
380
380
|
body.toByteArray(),
|
|
381
|
-
certificateChainFromManifestResponse
|
|
381
|
+
certificateChainFromManifestResponse
|
|
382
382
|
)
|
|
383
383
|
if (signatureValidationResult.validationResult == ValidationResult.INVALID) {
|
|
384
384
|
throw IOException("Directive download was successful, but signature was incorrect")
|
|
@@ -658,7 +658,7 @@ open class FileDownloader(context: Context, private val client: OkHttpClient) {
|
|
|
658
658
|
val signatureValidationResult = codeSigningConfiguration.validateSignature(
|
|
659
659
|
responsePartHeaderData.signature,
|
|
660
660
|
bodyString.toByteArray(),
|
|
661
|
-
certificateChainFromManifestResponse
|
|
661
|
+
certificateChainFromManifestResponse
|
|
662
662
|
)
|
|
663
663
|
if (signatureValidationResult.validationResult == ValidationResult.INVALID) {
|
|
664
664
|
throw IOException("Manifest download was successful, but signature was incorrect")
|
|
@@ -746,7 +746,7 @@ open class FileDownloader(context: Context, private val client: OkHttpClient) {
|
|
|
746
746
|
internal fun createRequestForAsset(
|
|
747
747
|
assetEntity: AssetEntity,
|
|
748
748
|
configuration: UpdatesConfiguration,
|
|
749
|
-
context: Context
|
|
749
|
+
context: Context
|
|
750
750
|
): Request {
|
|
751
751
|
return Request.Builder()
|
|
752
752
|
.url(assetEntity.url!!.toString())
|
|
@@ -95,7 +95,9 @@ private fun verifyPublicRSASignature(
|
|
|
95
95
|
val signature = Signature.getInstance("SHA256withRSA")
|
|
96
96
|
val decodedPublicKey = Base64.decode(publicKeyNoComments, Base64.DEFAULT)
|
|
97
97
|
val publicKeySpec = X509EncodedKeySpec(decodedPublicKey)
|
|
98
|
-
|
|
98
|
+
|
|
99
|
+
@SuppressLint("InlinedApi")
|
|
100
|
+
val keyFactory = KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
|
|
99
101
|
val key = keyFactory.generatePublic(publicKeySpec)
|
|
100
102
|
signature.initVerify(key)
|
|
101
103
|
signature.update(plainText.toByteArray())
|
|
@@ -97,7 +97,9 @@ abstract class Loader protected constructor(
|
|
|
97
97
|
this.callback = callback
|
|
98
98
|
|
|
99
99
|
loadRemoteUpdate(
|
|
100
|
-
context,
|
|
100
|
+
context,
|
|
101
|
+
database,
|
|
102
|
+
configuration,
|
|
101
103
|
object : RemoteUpdateDownloadCallback {
|
|
102
104
|
override fun onFailure(message: String, e: Exception) {
|
|
103
105
|
finishWithError(message, e)
|
|
@@ -215,7 +217,9 @@ abstract class Loader protected constructor(
|
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
private enum class AssetLoadResult {
|
|
218
|
-
FINISHED,
|
|
220
|
+
FINISHED,
|
|
221
|
+
ALREADY_EXISTS,
|
|
222
|
+
ERRORED
|
|
219
223
|
}
|
|
220
224
|
|
|
221
225
|
private fun downloadAllAssets(assetList: List<AssetEntity>) {
|
|
@@ -234,9 +238,9 @@ abstract class Loader protected constructor(
|
|
|
234
238
|
// if we already have a local copy of this asset, don't try to download it again!
|
|
235
239
|
if (assetEntity.relativePath != null && loaderFiles.fileExists(
|
|
236
240
|
File(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
241
|
+
updatesDirectory,
|
|
242
|
+
assetEntity.relativePath
|
|
243
|
+
)
|
|
240
244
|
)
|
|
241
245
|
) {
|
|
242
246
|
handleAssetDownloadCompleted(assetEntity, AssetLoadResult.ALREADY_EXISTS)
|
|
@@ -244,12 +248,19 @@ abstract class Loader protected constructor(
|
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
loadAsset(
|
|
247
|
-
context,
|
|
251
|
+
context,
|
|
252
|
+
assetEntity,
|
|
253
|
+
updatesDirectory,
|
|
254
|
+
configuration,
|
|
248
255
|
object : AssetDownloadCallback {
|
|
249
256
|
override fun onFailure(e: Exception, assetEntity: AssetEntity) {
|
|
250
|
-
val identifier = if (assetEntity.hash != null)
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
val identifier = if (assetEntity.hash != null) {
|
|
258
|
+
"hash " + UpdatesUtils.bytesToHex(
|
|
259
|
+
assetEntity.hash!!
|
|
260
|
+
)
|
|
261
|
+
} else {
|
|
262
|
+
"key " + assetEntity.key
|
|
263
|
+
}
|
|
253
264
|
Log.e(TAG, "Failed to download asset with $identifier", e)
|
|
254
265
|
handleAssetDownloadCompleted(assetEntity, AssetLoadResult.ERRORED)
|
|
255
266
|
}
|