expo-dev-launcher 4.0.0 → 4.0.2

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/android/build.gradle +1 -1
  3. package/android/src/debug/java/expo/modules/devlauncher/DevLauncherController.kt +43 -16
  4. package/android/src/debug/java/expo/modules/devlauncher/DevLauncherPackageDelegate.kt +1 -1
  5. package/android/src/debug/java/expo/modules/devlauncher/helpers/DevLauncherReactUtils.kt +158 -46
  6. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherActivity.kt +10 -8
  7. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherNetworkInterceptor.kt +12 -11
  8. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherReactHost.kt +109 -0
  9. package/android/src/debug/java/expo/modules/devlauncher/launcher/{DevLauncherClientHost.kt → DevLauncherReactNativeHost.kt} +2 -2
  10. package/android/src/debug/java/expo/modules/devlauncher/launcher/errors/DevLauncherErrorActivity.kt +1 -1
  11. package/android/src/debug/java/expo/modules/devlauncher/launcher/errors/DevLauncherUncaughtExceptionHandler.kt +4 -3
  12. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoader.kt +6 -6
  13. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoaderFactory.kt +2 -3
  14. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherExpoAppLoader.kt +2 -2
  15. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherLocalAppLoader.kt +2 -2
  16. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherPublishedAppLoader.kt +2 -2
  17. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherReactNativeAppLoader.kt +2 -2
  18. package/android/src/debug/java/expo/modules/devlauncher/react/DevLauncherDevSupportManagerSwapper.kt +119 -57
  19. package/android/src/main/java/com/facebook/react/devsupport/NonFinalBridgeDevSupportManager.java +273 -0
  20. package/android/src/main/java/com/facebook/react/runtime/NonFinalBridgelessDevSupportManager.java +177 -0
  21. package/android/src/main/java/expo/modules/devlauncher/launcher/DevLauncherControllerInterface.kt +4 -4
  22. package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherBridgeDevSupportManager.kt +75 -0
  23. package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherBridgelessDevSupportManager.kt +55 -0
  24. package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherDevSupportManagerFactory.kt +3 -3
  25. package/android/src/release/java/expo/modules/devlauncher/DevLauncherController.kt +13 -9
  26. package/android/src/release/java/expo/modules/devlauncher/launcher/DevLauncherReactHost.kt +11 -0
  27. package/android/src/release/java/expo/modules/devlauncher/launcher/{DevLauncherClientHost.kt → DevLauncherReactNativeHost.kt} +1 -1
  28. package/android/src/testDebug/java/expo/modules/devlauncher/DevLauncherControllerTest.kt +3 -3
  29. package/android/src/testDebug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoaderFactoryTest.kt +3 -4
  30. package/build/DevLauncherErrorManager.js.map +1 -1
  31. package/package.json +4 -4
  32. package/plugin/build/pluginConfig.js +2 -1
  33. package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherDevSupportManager.kt +0 -286
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 4.0.2 — 2024-04-22
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 4.0.1 — 2024-04-19
18
+
19
+ ### 🎉 New features
20
+
21
+ - Added bridgeless mode support on Android. ([#28162](https://github.com/expo/expo/pull/28162) by [@kudo](https://github.com/kudo))
22
+
13
23
  ## 4.0.0 — 2024-04-18
14
24
 
15
25
  ### 🛠 Breaking changes
@@ -17,7 +17,7 @@ android {
17
17
  namespace "expo.modules.devlauncher"
18
18
  defaultConfig {
19
19
  versionCode 9
20
- versionName "4.0.0"
20
+ versionName "4.0.2"
21
21
  }
22
22
 
23
23
  buildTypes {
@@ -7,24 +7,40 @@ import android.net.Uri
7
7
  import androidx.annotation.UiThread
8
8
  import com.facebook.react.ReactActivity
9
9
  import com.facebook.react.ReactActivityDelegate
10
- import com.facebook.react.ReactNativeHost
10
+ import com.facebook.react.ReactApplication
11
11
  import com.facebook.react.ReactPackage
12
12
  import com.facebook.react.bridge.ReactContext
13
- import expo.modules.devlauncher.helpers.*
13
+ import expo.modules.devlauncher.helpers.DevLauncherInstallationIDHelper
14
+ import expo.modules.devlauncher.helpers.DevLauncherMetadataHelper
15
+ import expo.modules.devlauncher.helpers.DevLauncherUrl
16
+ import expo.interfaces.devmenu.ReactHostWrapper
17
+ import expo.modules.devlauncher.helpers.getFieldInClassHierarchy
18
+ import expo.modules.devlauncher.helpers.hasUrlQueryParam
19
+ import expo.modules.devlauncher.helpers.isDevLauncherUrl
20
+ import expo.modules.devlauncher.helpers.runBlockingOnMainThread
14
21
  import expo.modules.devlauncher.koin.DevLauncherKoinComponent
15
22
  import expo.modules.devlauncher.koin.DevLauncherKoinContext
16
23
  import expo.modules.devlauncher.koin.devLauncherKoin
17
24
  import expo.modules.devlauncher.koin.optInject
18
- import expo.modules.devlauncher.launcher.*
25
+ import expo.modules.devlauncher.launcher.DevLauncherActivity
26
+ import expo.modules.devlauncher.launcher.DevLauncherAppEntry
27
+ import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
28
+ import expo.modules.devlauncher.launcher.DevLauncherIntentRegistryInterface
29
+ import expo.modules.devlauncher.launcher.DevLauncherLifecycle
30
+ import expo.modules.devlauncher.launcher.DevLauncherNetworkInterceptor
31
+ import expo.modules.devlauncher.launcher.DevLauncherReactActivityDelegateSupplier
32
+ import expo.modules.devlauncher.launcher.DevLauncherReactHost
33
+ import expo.modules.devlauncher.launcher.DevLauncherReactNativeHost
34
+ import expo.modules.devlauncher.launcher.DevLauncherRecentlyOpenedAppsRegistry
19
35
  import expo.modules.devlauncher.launcher.errors.DevLauncherAppError
20
36
  import expo.modules.devlauncher.launcher.errors.DevLauncherErrorActivity
21
37
  import expo.modules.devlauncher.launcher.errors.DevLauncherUncaughtExceptionHandler
22
38
  import expo.modules.devlauncher.launcher.loaders.DevLauncherAppLoaderFactoryInterface
23
39
  import expo.modules.devlauncher.launcher.manifest.DevLauncherManifestParser
24
- import expo.modules.devmenu.DevMenuManager
25
40
  import expo.modules.devlauncher.react.activitydelegates.DevLauncherReactActivityNOPDelegate
26
41
  import expo.modules.devlauncher.react.activitydelegates.DevLauncherReactActivityRedirectDelegate
27
42
  import expo.modules.devlauncher.tests.DevLauncherTestInterceptor
43
+ import expo.modules.devmenu.DevMenuManager
28
44
  import expo.modules.manifests.core.Manifest
29
45
  import expo.modules.updatesinterface.UpdatesInterface
30
46
  import kotlinx.coroutines.CoroutineScope
@@ -36,7 +52,7 @@ import org.koin.core.component.inject
36
52
  import org.koin.dsl.module
37
53
 
38
54
  // Use this to load from a development server for the development client launcher UI
39
- // private final String DEV_LAUNCHER_HOST = "10.0.0.175:8090";
55
+ // private val DEV_LAUNCHER_HOST = "10.0.0.175:8090";
40
56
  private val DEV_LAUNCHER_HOST: String? = null
41
57
 
42
58
  private const val NEW_ACTIVITY_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK or
@@ -48,7 +64,7 @@ class DevLauncherController private constructor() :
48
64
  private val context: Context by lazy {
49
65
  DevLauncherKoinContext.app.koin.get()
50
66
  }
51
- override val appHost: ReactNativeHost by inject()
67
+ override val appHost: ReactHostWrapper by inject()
52
68
  private val httpClient: OkHttpClient by inject()
53
69
  private val lifecycle: DevLauncherLifecycle by inject()
54
70
  private val pendingIntentRegistry: DevLauncherIntentRegistryInterface by inject()
@@ -66,7 +82,12 @@ class DevLauncherController private constructor() :
66
82
  )
67
83
  override val coroutineScope = CoroutineScope(Dispatchers.Default)
68
84
 
69
- override val devClientHost = DevLauncherClientHost((context as Application), DEV_LAUNCHER_HOST)
85
+ override val devClientHost by lazy {
86
+ ReactHostWrapper(
87
+ reactNativeHost = DevLauncherReactNativeHost(context as Application, DEV_LAUNCHER_HOST),
88
+ reactHost = DevLauncherReactHost.create(context as Application, DEV_LAUNCHER_HOST)
89
+ )
90
+ }
70
91
 
71
92
  private val recentlyOpedAppsRegistry = DevLauncherRecentlyOpenedAppsRegistry(context)
72
93
  override var manifest: Manifest? = null
@@ -98,7 +119,7 @@ class DevLauncherController private constructor() :
98
119
  coroutineScope.launch {
99
120
  loadApp(
100
121
  latestLoadedApp,
101
- appHost.reactInstanceManager.currentReactContext?.currentActivity as? ReactActivity?
122
+ appHost.currentReactContext?.currentActivity as? ReactActivity?
102
123
  )
103
124
  }
104
125
  }
@@ -258,8 +279,8 @@ class DevLauncherController private constructor() :
258
279
  return false
259
280
  }
260
281
 
261
- private fun ensureHostWasCleared(host: ReactNativeHost, activityToBeInvalidated: ReactActivity? = null) {
262
- if (host.hasInstance()) {
282
+ private fun ensureHostWasCleared(host: ReactHostWrapper, activityToBeInvalidated: ReactActivity? = null) {
283
+ if (host.hasInstance) {
263
284
  runBlockingOnMainThread {
264
285
  clearHost(host, activityToBeInvalidated)
265
286
  }
@@ -277,8 +298,8 @@ class DevLauncherController private constructor() :
277
298
  }
278
299
 
279
300
  @UiThread
280
- private fun clearHost(host: ReactNativeHost, activityToBeInvalidated: ReactActivity?) {
281
- host.clear()
301
+ private fun clearHost(host: ReactHostWrapper, activityToBeInvalidated: ReactActivity?) {
302
+ host.destroy()
282
303
  activityToBeInvalidated?.let {
283
304
  invalidateActivity(it)
284
305
  }
@@ -364,7 +385,7 @@ class DevLauncherController private constructor() :
364
385
  }
365
386
 
366
387
  @JvmStatic
367
- fun initialize(context: Context, appHost: ReactNativeHost) {
388
+ internal fun initialize(context: Context, reactHost: ReactHostWrapper) {
368
389
  val testInterceptor = DevLauncherKoinContext.app.koin.get<DevLauncherTestInterceptor>()
369
390
  if (!testInterceptor.allowReinitialization()) {
370
391
  check(!wasInitialized()) { "DevelopmentClientController was initialized." }
@@ -373,7 +394,7 @@ class DevLauncherController private constructor() :
373
394
  listOf(
374
395
  module {
375
396
  single { context }
376
- single { appHost }
397
+ single { reactHost }
377
398
  }
378
399
  ),
379
400
  allowOverride = true
@@ -394,8 +415,14 @@ class DevLauncherController private constructor() :
394
415
  }
395
416
 
396
417
  @JvmStatic
397
- fun initialize(context: Context, appHost: ReactNativeHost, additionalPackages: List<ReactPackage>? = null, launcherClass: Class<*>? = null) {
398
- initialize(context, appHost)
418
+ fun initialize(context: Context, reactHost: ReactHostWrapper, launcherClass: Class<*>? = null) {
419
+ initialize(context, reactHost)
420
+ sLauncherClass = launcherClass
421
+ }
422
+
423
+ @JvmStatic
424
+ fun initialize(reactApplication: ReactApplication, additionalPackages: List<ReactPackage>? = null, launcherClass: Class<*>? = null) {
425
+ initialize(reactApplication as Context, ReactHostWrapper(reactApplication.reactNativeHost, reactApplication.reactHost))
399
426
  sAdditionalPackages = additionalPackages
400
427
  sLauncherClass = launcherClass
401
428
  }
@@ -35,7 +35,7 @@ object DevLauncherPackageDelegate {
35
35
  object : ApplicationLifecycleListener {
36
36
  override fun onCreate(application: Application?) {
37
37
  check(application is ReactApplication)
38
- DevLauncherController.initialize(application, application.reactNativeHost)
38
+ DevLauncherController.initialize(application)
39
39
  UpdatesControllerRegistry.controller?.get()?.let {
40
40
  DevLauncherController.instance.updatesInterface = it
41
41
  it.updatesInterfaceCallbacks = WeakReference(DevLauncherController.instance)
@@ -1,53 +1,69 @@
1
1
  package expo.modules.devlauncher.helpers
2
2
 
3
+ import android.app.Application
3
4
  import android.content.Context
4
5
  import android.net.Uri
5
6
  import android.util.Log
7
+ import com.facebook.react.ReactHost
6
8
  import com.facebook.react.ReactNativeHost
7
9
  import com.facebook.react.ReactPackage
8
10
  import com.facebook.react.bridge.JSBundleLoader
11
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
12
+ import com.facebook.react.defaults.DefaultReactHostDelegate
9
13
  import com.facebook.react.devsupport.DevLauncherInternalSettings
14
+ import com.facebook.react.devsupport.DevSupportManagerBase
15
+ import com.facebook.react.devsupport.interfaces.DevSupportManager
16
+ import com.facebook.react.runtime.ReactHostDelegate
17
+ import com.facebook.react.runtime.ReactHostImpl
18
+ import expo.interfaces.devmenu.ReactHostWrapper
10
19
  import expo.interfaces.devmenu.annotations.ContainsDevMenuExtension
11
20
  import expo.modules.devlauncher.react.DevLauncherDevSupportManagerSwapper
12
- import expo.modules.devlauncher.rncompatibility.DevLauncherDevSupportManager
21
+ import expo.modules.devlauncher.rncompatibility.DevLauncherBridgeDevSupportManager
22
+ import expo.modules.devlauncher.rncompatibility.DevLauncherBridgelessDevSupportManager
23
+ import expo.modules.devmenu.helpers.setPrivateDeclaredFieldValue
13
24
  import okhttp3.HttpUrl
14
25
 
26
+ // Sync this class name with ExpoReactHostFactory.kt
27
+ private const val EXPO_REACT_HOST_DELEGATE_CLASS = "expo.modules.ExpoReactHostFactory.ExpoReactHostDelegate"
28
+
15
29
  fun injectReactInterceptor(
16
30
  context: Context,
17
- reactNativeHost: ReactNativeHost,
31
+ reactHost: ReactHostWrapper,
18
32
  url: Uri
19
33
  ): Boolean {
20
- val port = if (url.port != -1) url.port else HttpUrl.defaultPort(url.scheme ?: "http")
21
- val debugServerHost = url.host + ":" + port
22
- // We need to remove "/" which is added to begin of the path by the Uri
23
- // and the bundle type
24
- val appBundleName = if (url.path.isNullOrEmpty()) {
25
- "index"
26
- } else {
27
- url.path
28
- ?.substring(1)
29
- ?.replace(".bundle", "")
30
- ?: "index"
31
- }
34
+ val (debugServerHost, appBundleName) = parseUrl(url)
32
35
 
33
- injectDevSupportManager(reactNativeHost)
36
+ injectDevSupportManager(reactHost)
34
37
 
35
38
  val result = injectDebugServerHost(
36
39
  context,
37
- reactNativeHost,
40
+ reactHost,
38
41
  debugServerHost,
39
42
  appBundleName
40
43
  )
41
- (reactNativeHost.reactInstanceManager.devSupportManager as? DevLauncherDevSupportManager)?.startInspectorWhenDevLauncherReady()
42
-
44
+ if (reactHost.isBridgelessMode) {
45
+ (reactHost.devSupportManager as? DevLauncherBridgelessDevSupportManager)?.startInspectorWhenDevLauncherReady()
46
+ } else {
47
+ (reactHost.devSupportManager as? DevLauncherBridgeDevSupportManager)?.startInspectorWhenDevLauncherReady()
48
+ }
43
49
  return result
44
50
  }
45
51
 
46
- fun injectDevSupportManager(
47
- reactNativeHost: ReactNativeHost
48
- ) {
49
- DevLauncherDevSupportManagerSwapper()
50
- .swapDevSupportManagerImpl(reactNativeHost.reactInstanceManager)
52
+ private fun injectDevSupportManager(reactHost: ReactHostWrapper) {
53
+ DevLauncherDevSupportManagerSwapper().swapDevSupportManagerImpl(reactHost)
54
+ }
55
+
56
+ fun injectDebugServerHost(
57
+ context: Context,
58
+ reactHost: ReactHostWrapper,
59
+ debugServerHost: String,
60
+ appBundleName: String
61
+ ): Boolean {
62
+ if (reactHost.isBridgelessMode) {
63
+ return injectDebugServerHost(context, reactHost.reactHost, debugServerHost, appBundleName)
64
+ } else {
65
+ return injectDebugServerHost(context, reactHost.reactNativeHost, debugServerHost, appBundleName)
66
+ }
51
67
  }
52
68
 
53
69
  fun injectDebugServerHost(
@@ -58,27 +74,8 @@ fun injectDebugServerHost(
58
74
  ): Boolean {
59
75
  return try {
60
76
  val instanceManager = reactNativeHost.reactInstanceManager
61
- val settings = DevLauncherInternalSettings(context, debugServerHost)
62
77
  val devSupportManager = instanceManager.devSupportManager
63
- val devSupportManagerBaseClass: Class<*>? = devSupportManager.javaClass.superclass
64
- devSupportManagerBaseClass!!.setProtectedDeclaredField(
65
- devSupportManager,
66
- "mJSAppBundleName",
67
- appBundleName
68
- )
69
- val mDevSettingsField = devSupportManagerBaseClass.getDeclaredField("mDevSettings")
70
- mDevSettingsField.isAccessible = true
71
- mDevSettingsField[devSupportManager] = settings
72
- val mDevServerHelperField = devSupportManagerBaseClass.getDeclaredField("mDevServerHelper")
73
- mDevServerHelperField.isAccessible = true
74
- val devServerHelper = mDevServerHelperField[devSupportManager]
75
- val mSettingsField = devServerHelper.javaClass.getDeclaredField("mSettings")
76
- mSettingsField.isAccessible = true
77
- mSettingsField[devServerHelper] = settings
78
-
79
- val packagerConnectionSettingsField = devServerHelper.javaClass.getDeclaredField("mPackagerConnectionSettings")
80
- packagerConnectionSettingsField.isAccessible = true
81
- packagerConnectionSettingsField[devServerHelper] = settings.public_getPackagerConnectionSettings()
78
+ injectDebugServerHost(context, devSupportManager, debugServerHost, appBundleName)
82
79
 
83
80
  // set useDeveloperSupport to true in case it was previously set to false from loading a published app
84
81
  val mUseDeveloperSupportField = instanceManager.javaClass.getDeclaredField("mUseDeveloperSupport")
@@ -91,7 +88,62 @@ fun injectDebugServerHost(
91
88
  }
92
89
  }
93
90
 
91
+ fun injectDebugServerHost(
92
+ context: Context,
93
+ reactHost: ReactHost,
94
+ debugServerHost: String,
95
+ appBundleName: String
96
+ ): Boolean {
97
+ return try {
98
+ val devSupportManager = requireNotNull(reactHost.devSupportManager)
99
+ injectDebugServerHost(context, devSupportManager, debugServerHost, appBundleName)
100
+ true
101
+ } catch (e: Exception) {
102
+ Log.e("DevLauncher", "Unable to inject debug server host settings.", e)
103
+ false
104
+ }
105
+ }
106
+
107
+ private fun injectDebugServerHost(
108
+ context: Context,
109
+ devSupportManager: DevSupportManager,
110
+ debugServerHost: String,
111
+ appBundleName: String
112
+ ) {
113
+ val settings = DevLauncherInternalSettings(context, debugServerHost)
114
+ val devSupportManagerBaseClass: Class<*> = DevSupportManagerBase::class.java
115
+ devSupportManagerBaseClass.setProtectedDeclaredField(
116
+ devSupportManager,
117
+ "mJSAppBundleName",
118
+ appBundleName
119
+ )
120
+ val mDevSettingsField = devSupportManagerBaseClass.getDeclaredField("mDevSettings")
121
+ mDevSettingsField.isAccessible = true
122
+ mDevSettingsField[devSupportManager] = settings
123
+ val mDevServerHelperField = devSupportManagerBaseClass.getDeclaredField("mDevServerHelper")
124
+ mDevServerHelperField.isAccessible = true
125
+ val devServerHelper = mDevServerHelperField[devSupportManager]
126
+ val mSettingsField = devServerHelper.javaClass.getDeclaredField("mSettings")
127
+ mSettingsField.isAccessible = true
128
+ mSettingsField[devServerHelper] = settings
129
+
130
+ val packagerConnectionSettingsField = devServerHelper.javaClass.getDeclaredField("mPackagerConnectionSettings")
131
+ packagerConnectionSettingsField.isAccessible = true
132
+ packagerConnectionSettingsField[devServerHelper] = settings.public_getPackagerConnectionSettings()
133
+ }
134
+
94
135
  fun injectLocalBundleLoader(
136
+ reactHost: ReactHostWrapper,
137
+ bundlePath: String
138
+ ): Boolean {
139
+ return if (reactHost.isBridgelessMode) {
140
+ injectLocalBundleLoader(reactHost.reactHost, bundlePath)
141
+ } else {
142
+ injectLocalBundleLoader(reactHost.reactNativeHost, bundlePath)
143
+ }
144
+ }
145
+
146
+ private fun injectLocalBundleLoader(
95
147
  reactNativeHost: ReactNativeHost,
96
148
  bundlePath: String
97
149
  ): Boolean {
@@ -114,6 +166,50 @@ fun injectLocalBundleLoader(
114
166
  }
115
167
  }
116
168
 
169
+ @OptIn(UnstableReactNativeAPI::class)
170
+ private fun injectLocalBundleLoader(
171
+ reactHost: ReactHost,
172
+ bundlePath: String
173
+ ): Boolean {
174
+ return try {
175
+ check(reactHost is ReactHostImpl)
176
+
177
+ // [0] Disable `mAllowPackagerServerAccess`
178
+ // so that ReactHost could use jsBundlerLoader from ReactHostDelegate
179
+ val reactHostClass = ReactHostImpl::class.java
180
+ val mAllowPackagerServerAccessField = reactHostClass.getDeclaredField("mAllowPackagerServerAccess")
181
+ mAllowPackagerServerAccessField.isAccessible = true
182
+ mAllowPackagerServerAccessField[reactHost] = false
183
+
184
+ val newJsBundleLoader = JSBundleLoader.createFileLoader(bundlePath)
185
+
186
+ // [1] Replace the ReactHostDelegate.jsBundlerLoader with our new loader
187
+ val mReactHostDelegateField = reactHostClass.getDeclaredField("mReactHostDelegate")
188
+ mReactHostDelegateField.isAccessible = true
189
+ val reactHostDelegate = mReactHostDelegateField[reactHost] as ReactHostDelegate
190
+ if (reactHostDelegate.javaClass.canonicalName == EXPO_REACT_HOST_DELEGATE_CLASS) {
191
+ reactHostDelegate.javaClass.setPrivateDeclaredFieldValue(
192
+ "_jsBundleLoader",
193
+ reactHostDelegate,
194
+ newJsBundleLoader
195
+ )
196
+ } else if (reactHostDelegate is DefaultReactHostDelegate) {
197
+ DefaultReactHostDelegate::class.java.setPrivateDeclaredFieldValue(
198
+ "jsBundleLoader",
199
+ reactHostDelegate,
200
+ newJsBundleLoader
201
+ )
202
+ } else {
203
+ throw IllegalStateException("[injectLocalBundleLoader] Unsupported reactHostDelegate: ${reactHostDelegate.javaClass}")
204
+ }
205
+
206
+ true
207
+ } catch (e: Exception) {
208
+ Log.e("DevLauncher", "Unable to load local bundle file", e)
209
+ false
210
+ }
211
+ }
212
+
117
213
  fun findDevMenuPackage(): ReactPackage? {
118
214
  return try {
119
215
  val clazz = Class.forName("expo.modules.devmenu.DevMenuPackage")
@@ -123,11 +219,11 @@ fun findDevMenuPackage(): ReactPackage? {
123
219
  }
124
220
  }
125
221
 
126
- fun findPackagesWithDevMenuExtension(reactNativeHost: ReactNativeHost): List<ReactPackage> {
222
+ fun findPackagesWithDevMenuExtension(application: Application): List<ReactPackage> {
127
223
  return try {
128
224
  val clazz = Class.forName("com.facebook.react.PackageList")
129
- val ctor = clazz.getConstructor(ReactNativeHost::class.java)
130
- val packageList = ctor.newInstance(reactNativeHost)
225
+ val ctor = clazz.getConstructor(Application::class.java)
226
+ val packageList = ctor.newInstance(application)
131
227
 
132
228
  val getPackagesMethod = packageList.javaClass.getDeclaredMethod("getPackages")
133
229
  val packages = getPackagesMethod.invoke(packageList) as List<*>
@@ -141,3 +237,19 @@ fun findPackagesWithDevMenuExtension(reactNativeHost: ReactNativeHost): List<Rea
141
237
  emptyList()
142
238
  }
143
239
  }
240
+
241
+ private fun parseUrl(url: Uri): Pair<String, String> {
242
+ val port = if (url.port != -1) url.port else HttpUrl.defaultPort(url.scheme ?: "http")
243
+ val debugServerHost = url.host + ":" + port
244
+ // We need to remove "/" which is added to begin of the path by the Uri
245
+ // and the bundle type
246
+ val appBundleName = if (url.path.isNullOrEmpty()) {
247
+ "index"
248
+ } else {
249
+ url.path
250
+ ?.substring(1)
251
+ ?.replace(".bundle", "")
252
+ ?: "index"
253
+ }
254
+ return Pair(debugServerHost, appBundleName)
255
+ }
@@ -8,11 +8,11 @@ import android.view.View
8
8
  import android.view.ViewGroup
9
9
  import com.facebook.react.ReactActivity
10
10
  import com.facebook.react.ReactActivityDelegate
11
- import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
12
- import com.facebook.react.defaults.DefaultReactActivityDelegate
13
- import com.facebook.react.ReactInstanceManager
11
+ import com.facebook.react.ReactInstanceEventListener
14
12
  import com.facebook.react.ReactRootView
15
13
  import com.facebook.react.bridge.ReactContext
14
+ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
15
+ import com.facebook.react.defaults.DefaultReactActivityDelegate
16
16
  import expo.modules.core.utilities.EmulatorUtilities
17
17
  import expo.modules.devlauncher.koin.DevLauncherKoinComponent
18
18
  import expo.modules.devlauncher.splashscreen.DevLauncherSplashScreen
@@ -22,7 +22,7 @@ import org.koin.core.component.inject
22
22
 
23
23
  const val SEARCH_FOR_ROOT_VIEW_INTERVAL = 20L
24
24
 
25
- class DevLauncherActivity : ReactActivity(), ReactInstanceManager.ReactInstanceEventListener, DevLauncherKoinComponent {
25
+ class DevLauncherActivity : ReactActivity(), ReactInstanceEventListener, DevLauncherKoinComponent {
26
26
  private val controller: DevLauncherControllerInterface by inject()
27
27
  private var devMenuManager: DevMenuManager = DevMenuManager
28
28
  private var splashScreen: DevLauncherSplashScreen? = null
@@ -35,7 +35,9 @@ class DevLauncherActivity : ReactActivity(), ReactInstanceManager.ReactInstanceE
35
35
  override fun createReactActivityDelegate(): ReactActivityDelegate {
36
36
  return object : DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) {
37
37
 
38
- override fun getReactNativeHost() = controller.devClientHost
38
+ override fun getReactNativeHost() = controller.devClientHost.reactNativeHost
39
+
40
+ override fun getReactHost() = controller.devClientHost.reactHost
39
41
 
40
42
  override fun getLaunchOptions() = Bundle().apply {
41
43
  putBoolean("isSimulator", isSimulator)
@@ -59,12 +61,12 @@ class DevLauncherActivity : ReactActivity(), ReactInstanceManager.ReactInstanceE
59
61
 
60
62
  override fun onPostCreate(savedInstanceState: Bundle?) {
61
63
  super.onPostCreate(savedInstanceState)
62
- reactInstanceManager.currentReactContext?.let {
64
+ controller.devClientHost.currentReactContext?.let {
63
65
  onReactContextInitialized(it)
64
66
  return
65
67
  }
66
68
 
67
- reactInstanceManager.addReactInstanceEventListener(this)
69
+ controller.devClientHost.addReactInstanceEventListener(this)
68
70
  }
69
71
 
70
72
  override fun onPause() {
@@ -82,7 +84,7 @@ class DevLauncherActivity : ReactActivity(), ReactInstanceManager.ReactInstanceE
82
84
  }
83
85
 
84
86
  override fun onReactContextInitialized(context: ReactContext) {
85
- reactInstanceManager.removeReactInstanceEventListener(this)
87
+ controller.devClientHost.removeReactInstanceEventListener(this)
86
88
  }
87
89
 
88
90
  private val isSimulator
@@ -2,12 +2,13 @@
2
2
 
3
3
  package expo.modules.devlauncher.launcher
4
4
 
5
- import com.facebook.react.ReactInstanceManager
6
5
  import com.facebook.react.bridge.Inspector
7
6
  import com.facebook.react.common.LifecycleState
8
7
  import com.facebook.react.devsupport.DevServerHelper
8
+ import com.facebook.react.devsupport.DevSupportManagerBase
9
9
  import com.facebook.react.devsupport.InspectorPackagerConnection
10
10
  import expo.modules.devlauncher.DevLauncherController
11
+ import expo.interfaces.devmenu.ReactHostWrapper
11
12
  import expo.modules.kotlin.devtools.ExpoRequestCdpInterceptor
12
13
  import java.io.Closeable
13
14
  import java.lang.ref.WeakReference
@@ -16,20 +17,20 @@ import java.lang.reflect.Method
16
17
 
17
18
  internal class DevLauncherNetworkInterceptor(controller: DevLauncherController) : Closeable, ExpoRequestCdpInterceptor.Delegate {
18
19
  private val weakController = WeakReference(controller)
19
- private var reactInstanceHashCode: Int = 0
20
+ private var reactHostHashCode: Int = 0
20
21
  private var _inspectorPackagerConnection: InspectorPackagerConnectionWrapper? = null
21
22
 
22
23
  private val inspectorPackagerConnection: InspectorPackagerConnectionWrapper
23
24
  get() {
24
- val reactInstanceManager = requireNotNull(weakController.get()?.appHost?.reactInstanceManager)
25
- if (reactInstanceHashCode != reactInstanceManager.hashCode()) {
25
+ val reactHost = requireNotNull(weakController.get()?.appHost)
26
+ if (reactHostHashCode != reactHost.hashCode()) {
26
27
  _inspectorPackagerConnection?.clear()
27
28
  _inspectorPackagerConnection = null
28
- reactInstanceHashCode = 0
29
+ reactHostHashCode = 0
29
30
  }
30
31
  if (_inspectorPackagerConnection == null) {
31
- _inspectorPackagerConnection = InspectorPackagerConnectionWrapper(reactInstanceManager)
32
- reactInstanceHashCode = reactInstanceManager.hashCode()
32
+ _inspectorPackagerConnection = InspectorPackagerConnectionWrapper(reactHost)
33
+ reactHostHashCode = reactHost.hashCode()
33
34
  }
34
35
  return requireNotNull(_inspectorPackagerConnection)
35
36
  }
@@ -42,7 +43,7 @@ internal class DevLauncherNetworkInterceptor(controller: DevLauncherController)
42
43
  * Returns true when it is allowed to send CDP events
43
44
  */
44
45
  private fun shouldEmitEvents(): Boolean {
45
- return DevLauncherController.wasInitialized() && weakController.get()?.appHost?.reactInstanceManager?.lifecycleState == LifecycleState.RESUMED
46
+ return DevLauncherController.wasInitialized() && weakController.get()?.appHost?.lifecycleState == LifecycleState.RESUMED
46
47
  }
47
48
 
48
49
  //region Closeable implementations
@@ -63,7 +64,7 @@ internal class DevLauncherNetworkInterceptor(controller: DevLauncherController)
63
64
  /**
64
65
  * A `InspectorPackagerConnection` wrapper to expose private members with reflection
65
66
  */
66
- internal class InspectorPackagerConnectionWrapper constructor(reactInstanceManager: ReactInstanceManager) {
67
+ internal class InspectorPackagerConnectionWrapper constructor(reactHost: ReactHostWrapper) {
67
68
  private var inspectorPackagerConnectionWeak: WeakReference<InspectorPackagerConnection> = WeakReference(null)
68
69
  private val devServerHelperWeak: WeakReference<DevServerHelper>
69
70
  private val inspectorPackagerConnectionField: Field
@@ -84,8 +85,8 @@ internal class InspectorPackagerConnectionWrapper constructor(reactInstanceManag
84
85
  }
85
86
 
86
87
  init {
87
- val devSupportManager = reactInstanceManager.devSupportManager
88
- val devSupportManagerBaseClass: Class<*> = devSupportManager.javaClass.superclass
88
+ val devSupportManager = requireNotNull(reactHost.devSupportManager)
89
+ val devSupportManagerBaseClass = DevSupportManagerBase::class.java
89
90
  val mDevServerHelperField = devSupportManagerBaseClass.getDeclaredField("mDevServerHelper")
90
91
  mDevServerHelperField.isAccessible = true
91
92
  val devServerHelper = mDevServerHelperField[devSupportManager]