expo-updates 0.25.21 → 0.25.23
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 +12 -0
- package/android/build.gradle +34 -2
- package/android/src/react-native-75/main/expo/modules/updates/errorrecovery/ErrorRecovery.kt +172 -0
- package/fingerprint.d.ts +1 -0
- package/fingerprint.js +1 -0
- package/package.json +2 -2
- /package/android/src/{main/java → react-native-74/main}/expo/modules/updates/errorrecovery/ErrorRecovery.kt +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 0.25.23 — 2024-08-21
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- Fixed build error with React Native 0.75 on Android. ([#31084](https://github.com/expo/expo/pull/31084) by [@kudo](https://github.com/kudo))
|
|
18
|
+
|
|
19
|
+
## 0.25.22 — 2024-08-07
|
|
20
|
+
|
|
21
|
+
### 💡 Others
|
|
22
|
+
|
|
23
|
+
- Re-exported `@expo/fingerprint` as `expo-updates/fingerprint`. ([#30757](https://github.com/expo/expo/pull/30757) by [@kudo](https://github.com/kudo))
|
|
24
|
+
|
|
13
25
|
## 0.25.21 — 2024-07-22
|
|
14
26
|
|
|
15
27
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
|
|
|
2
2
|
apply plugin: 'kotlin-kapt'
|
|
3
3
|
|
|
4
4
|
group = 'host.exp.exponent'
|
|
5
|
-
version = '0.25.
|
|
5
|
+
version = '0.25.23'
|
|
6
6
|
|
|
7
7
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
8
8
|
apply from: expoModulesCorePlugin
|
|
@@ -48,7 +48,7 @@ android {
|
|
|
48
48
|
namespace "expo.modules.updates"
|
|
49
49
|
defaultConfig {
|
|
50
50
|
versionCode 31
|
|
51
|
-
versionName '0.25.
|
|
51
|
+
versionName '0.25.23'
|
|
52
52
|
consumerProguardFiles("proguard-rules.pro")
|
|
53
53
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
54
54
|
|
|
@@ -76,6 +76,13 @@ android {
|
|
|
76
76
|
main.assets.srcDirs += files("$projectDir/src/main/certificates".toString())
|
|
77
77
|
androidTest.assets.srcDirs += files("$projectDir/src/androidTest/schemas".toString())
|
|
78
78
|
androidTest.assets.srcDirs += files("$projectDir/src/androidTest/certificates".toString())
|
|
79
|
+
|
|
80
|
+
def rnVersion = getRNVersion()
|
|
81
|
+
if (rnVersion >= versionToNumber(0, 75, 0)) {
|
|
82
|
+
main.java.srcDirs += "src/react-native-75/main"
|
|
83
|
+
} else {
|
|
84
|
+
main.java.srcDirs += "src/react-native-74/main"
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -118,3 +125,28 @@ dependencies {
|
|
|
118
125
|
|
|
119
126
|
implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion()}"
|
|
120
127
|
}
|
|
128
|
+
|
|
129
|
+
def versionToNumber(major, minor, patch) {
|
|
130
|
+
return patch * 100 + minor * 10000 + major * 1000000
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def getNodeModulesPackageVersion(packageName, overridePropName) {
|
|
134
|
+
def nodeModulesVersion = providers.exec {
|
|
135
|
+
workingDir(projectDir)
|
|
136
|
+
commandLine("node", "-e", "console.log(require('$packageName/package.json').version);")
|
|
137
|
+
}.standardOutput.asText.get().trim()
|
|
138
|
+
def version = safeExtGet(overridePropName, nodeModulesVersion)
|
|
139
|
+
|
|
140
|
+
def coreVersion = version.split("-")[0]
|
|
141
|
+
def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() }
|
|
142
|
+
|
|
143
|
+
return versionToNumber(
|
|
144
|
+
major,
|
|
145
|
+
minor,
|
|
146
|
+
patch
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def getRNVersion() {
|
|
151
|
+
return getNodeModulesPackageVersion("react-native", "reactNativeVersion")
|
|
152
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
package expo.modules.updates.errorrecovery
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.HandlerThread
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.DefaultJSExceptionHandler
|
|
8
|
+
import com.facebook.react.bridge.ReactMarker
|
|
9
|
+
import com.facebook.react.bridge.ReactMarker.MarkerListener
|
|
10
|
+
import com.facebook.react.bridge.ReactMarkerConstants
|
|
11
|
+
import com.facebook.react.config.ReactFeatureFlags
|
|
12
|
+
import com.facebook.react.devsupport.ReleaseDevSupportManager
|
|
13
|
+
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
|
14
|
+
import expo.modules.updates.logging.UpdatesErrorCode
|
|
15
|
+
import expo.modules.updates.logging.UpdatesLogger
|
|
16
|
+
import java.lang.ref.WeakReference
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Entry point for the error recovery flow. Responsible for initializing the error recovery handler
|
|
20
|
+
* and handler thread, and for registering (and unregistering) listeners to lifecycle events so that
|
|
21
|
+
* the appropriate error recovery flows will be triggered.
|
|
22
|
+
*
|
|
23
|
+
* The error recovery flow is intended to be lightweight and is *not* a full safety net whose
|
|
24
|
+
* purpose is to avoid crashes at all costs. Rather, its primary purpose is to prevent bad updates
|
|
25
|
+
* from "bricking" an app by causing crashes before there is ever a chance to download a fix.
|
|
26
|
+
*
|
|
27
|
+
* Notably, the error listener will be unregistered 10 seconds after content has appeared; we assume
|
|
28
|
+
* that by this point, expo-updates has had enough time to download a new update if there is one,
|
|
29
|
+
* and so there is no more need to trigger the error recovery pipeline.
|
|
30
|
+
*/
|
|
31
|
+
class ErrorRecovery(
|
|
32
|
+
private val context: Context
|
|
33
|
+
) {
|
|
34
|
+
internal val handlerThread = HandlerThread("expo-updates-error-recovery")
|
|
35
|
+
internal lateinit var handler: Handler
|
|
36
|
+
internal val logger = UpdatesLogger(context)
|
|
37
|
+
|
|
38
|
+
private var weakDevSupportManager: WeakReference<DevSupportManager>? = null
|
|
39
|
+
private var previousExceptionHandler: DefaultJSExceptionHandler? = null
|
|
40
|
+
private var shouldHandleReactInstanceException = false
|
|
41
|
+
|
|
42
|
+
fun initialize(delegate: ErrorRecoveryDelegate) {
|
|
43
|
+
if (!::handler.isInitialized) {
|
|
44
|
+
handlerThread.start()
|
|
45
|
+
handler = ErrorRecoveryHandler(handlerThread.looper, delegate, logger)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fun startMonitoring(devSupportManager: DevSupportManager) {
|
|
50
|
+
registerContentAppearedListener()
|
|
51
|
+
registerErrorHandler(devSupportManager)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Exception notifications sending from [expo.modules.core.interfaces.ReactNativeHostHandler]
|
|
56
|
+
* This is only used for bridgeless mode.
|
|
57
|
+
*/
|
|
58
|
+
internal fun onReactInstanceException(exception: Exception) {
|
|
59
|
+
if (shouldHandleReactInstanceException) {
|
|
60
|
+
handleException(exception)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun notifyNewRemoteLoadStatus(newStatus: ErrorRecoveryDelegate.RemoteLoadStatus) {
|
|
65
|
+
logger.info("ErrorRecovery: remote load status changed: $newStatus")
|
|
66
|
+
handler.sendMessage(handler.obtainMessage(ErrorRecoveryHandler.MessageType.REMOTE_LOAD_STATUS_CHANGED, newStatus))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
internal fun handleException(exception: Exception) {
|
|
70
|
+
logger.error("ErrorRecovery: exception encountered: ${exception.localizedMessage}", UpdatesErrorCode.Unknown, exception)
|
|
71
|
+
handler.sendMessage(handler.obtainMessage(ErrorRecoveryHandler.MessageType.EXCEPTION_ENCOUNTERED, exception))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
internal fun handleContentAppeared() {
|
|
75
|
+
handler.sendMessage(handler.obtainMessage(ErrorRecoveryHandler.MessageType.CONTENT_APPEARED))
|
|
76
|
+
|
|
77
|
+
unregisterContentAppearedListener()
|
|
78
|
+
|
|
79
|
+
// wait 10s before unsetting error handlers; even though we won't try to relaunch if our
|
|
80
|
+
// handlers are triggered after now, we still want to give the app a reasonable window of time
|
|
81
|
+
// to start the WAIT_FOR_REMOTE_UPDATE task and check for a new update is there is one
|
|
82
|
+
//
|
|
83
|
+
// it's safe to use the handler thread for this since nothing else
|
|
84
|
+
// touches this class's fields
|
|
85
|
+
handler.postDelayed({ unregisterErrorHandler() }, 10000)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private val contentAppearedListener = MarkerListener { name, _, _ ->
|
|
89
|
+
if (name == ReactMarkerConstants.CONTENT_APPEARED) {
|
|
90
|
+
handleContentAppeared()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private fun registerContentAppearedListener() {
|
|
95
|
+
ReactMarker.addListener(contentAppearedListener)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private fun unregisterContentAppearedListener() {
|
|
99
|
+
ReactMarker.removeListener(contentAppearedListener)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun registerErrorHandler(devSupportManager: DevSupportManager) {
|
|
103
|
+
if (ReactFeatureFlags.enableBridgelessArchitecture) {
|
|
104
|
+
registerErrorHandlerImplBridgeless(devSupportManager)
|
|
105
|
+
} else {
|
|
106
|
+
registerErrorHandlerImplBridge(devSupportManager)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private fun registerErrorHandlerImplBridgeless(devSupportManager: DevSupportManager) {
|
|
111
|
+
shouldHandleReactInstanceException = true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private fun registerErrorHandlerImplBridge(devSupportManager: DevSupportManager) {
|
|
115
|
+
if (devSupportManager !is ReleaseDevSupportManager) {
|
|
116
|
+
Log.d(TAG, "Unexpected type of ReactInstanceManager.DevSupportManager. expo-updates error recovery will not behave properly.")
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
val defaultJSExceptionHandler = object : DefaultJSExceptionHandler() {
|
|
121
|
+
override fun handleException(e: Exception) {
|
|
122
|
+
this@ErrorRecovery.handleException(e)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
val devSupportManagerClass = devSupportManager.javaClass
|
|
126
|
+
previousExceptionHandler = devSupportManagerClass.getDeclaredField("mDefaultJSExceptionHandler").let { field ->
|
|
127
|
+
field.isAccessible = true
|
|
128
|
+
val previousValue = field[devSupportManager]
|
|
129
|
+
field[devSupportManager] = defaultJSExceptionHandler
|
|
130
|
+
return@let previousValue as DefaultJSExceptionHandler
|
|
131
|
+
}
|
|
132
|
+
weakDevSupportManager = WeakReference(devSupportManager)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private fun unregisterErrorHandler() {
|
|
136
|
+
if (ReactFeatureFlags.enableBridgelessArchitecture) {
|
|
137
|
+
unregisterErrorHandlerImplBridgeless()
|
|
138
|
+
} else {
|
|
139
|
+
unregisterErrorHandlerImplBridge()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private fun unregisterErrorHandlerImplBridgeless() {
|
|
144
|
+
shouldHandleReactInstanceException = false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private fun unregisterErrorHandlerImplBridge() {
|
|
148
|
+
weakDevSupportManager?.get()?.let { devSupportManager ->
|
|
149
|
+
if (devSupportManager !is ReleaseDevSupportManager) {
|
|
150
|
+
Log.d(TAG, "Unexpected type of ReactInstanceManager.DevSupportManager. expo-updates could not unregister its error handler")
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
if (previousExceptionHandler == null) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
val devSupportManagerClass = devSupportManager.javaClass
|
|
158
|
+
devSupportManagerClass.getDeclaredField("mDefaultJSExceptionHandler").let { field ->
|
|
159
|
+
field.isAccessible = true
|
|
160
|
+
field[devSupportManager] = previousExceptionHandler
|
|
161
|
+
}
|
|
162
|
+
weakDevSupportManager = null
|
|
163
|
+
}
|
|
164
|
+
// quitSafely will wait for processing messages to finish but cancel all messages scheduled for
|
|
165
|
+
// a future time, so delay for a few more seconds in case there are any scheduled messages
|
|
166
|
+
handler.postDelayed({ handlerThread.quitSafely() }, 10000)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
companion object {
|
|
170
|
+
private val TAG = ErrorRecovery::class.java.simpleName
|
|
171
|
+
}
|
|
172
|
+
}
|
package/fingerprint.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@expo/fingerprint';
|
package/fingerprint.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('@expo/fingerprint');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.23",
|
|
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",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"expo": "*"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "a9cfcf600ccaf5122932629472eb3bb2adb941fe"
|
|
71
71
|
}
|
|
File without changes
|