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 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._
@@ -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.21'
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.21'
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
+ }
@@ -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.21",
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": "7254cc3de7d97ccaf7e9c0864188cb233ba919e5"
70
+ "gitHead": "a9cfcf600ccaf5122932629472eb3bb2adb941fe"
71
71
  }