expo-dev-launcher 4.0.0 → 4.0.1
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 +1 -1
- package/android/src/debug/java/expo/modules/devlauncher/DevLauncherController.kt +43 -16
- package/android/src/debug/java/expo/modules/devlauncher/DevLauncherPackageDelegate.kt +1 -1
- package/android/src/debug/java/expo/modules/devlauncher/helpers/DevLauncherReactUtils.kt +158 -46
- package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherActivity.kt +10 -8
- package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherNetworkInterceptor.kt +12 -11
- package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherReactHost.kt +109 -0
- package/android/src/debug/java/expo/modules/devlauncher/launcher/{DevLauncherClientHost.kt → DevLauncherReactNativeHost.kt} +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/launcher/errors/DevLauncherErrorActivity.kt +1 -1
- package/android/src/debug/java/expo/modules/devlauncher/launcher/errors/DevLauncherUncaughtExceptionHandler.kt +4 -3
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoader.kt +6 -6
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoaderFactory.kt +2 -3
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherExpoAppLoader.kt +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherLocalAppLoader.kt +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherPublishedAppLoader.kt +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherReactNativeAppLoader.kt +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/react/DevLauncherDevSupportManagerSwapper.kt +119 -57
- package/android/src/main/java/com/facebook/react/devsupport/NonFinalBridgeDevSupportManager.java +273 -0
- package/android/src/main/java/com/facebook/react/runtime/NonFinalBridgelessDevSupportManager.java +177 -0
- package/android/src/main/java/expo/modules/devlauncher/launcher/DevLauncherControllerInterface.kt +4 -4
- package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherBridgeDevSupportManager.kt +75 -0
- package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherBridgelessDevSupportManager.kt +55 -0
- package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherDevSupportManagerFactory.kt +3 -3
- package/android/src/release/java/expo/modules/devlauncher/DevLauncherController.kt +13 -9
- package/android/src/release/java/expo/modules/devlauncher/launcher/DevLauncherReactHost.kt +11 -0
- package/android/src/release/java/expo/modules/devlauncher/launcher/{DevLauncherClientHost.kt → DevLauncherReactNativeHost.kt} +1 -1
- package/android/src/testDebug/java/expo/modules/devlauncher/DevLauncherControllerTest.kt +3 -3
- package/android/src/testDebug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoaderFactoryTest.kt +3 -4
- package/package.json +3 -3
- package/android/src/react-native-74/debug/expo/modules/devlauncher/rncompatibility/DevLauncherDevSupportManager.kt +0 -286
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 4.0.1 — 2024-04-19
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Added bridgeless mode support on Android. ([#28162](https://github.com/expo/expo/pull/28162) by [@kudo](https://github.com/kudo))
|
|
18
|
+
|
|
13
19
|
## 4.0.0 — 2024-04-18
|
|
14
20
|
|
|
15
21
|
### 🛠 Breaking changes
|
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
281
|
-
host.
|
|
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,
|
|
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 {
|
|
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,
|
|
398
|
-
initialize(context,
|
|
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
|
|
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.
|
|
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
|
-
|
|
31
|
+
reactHost: ReactHostWrapper,
|
|
18
32
|
url: Uri
|
|
19
33
|
): Boolean {
|
|
20
|
-
val
|
|
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(
|
|
36
|
+
injectDevSupportManager(reactHost)
|
|
34
37
|
|
|
35
38
|
val result = injectDebugServerHost(
|
|
36
39
|
context,
|
|
37
|
-
|
|
40
|
+
reactHost,
|
|
38
41
|
debugServerHost,
|
|
39
42
|
appBundleName
|
|
40
43
|
)
|
|
41
|
-
(
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
130
|
-
val packageList = ctor.newInstance(
|
|
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.
|
|
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(),
|
|
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
|
-
|
|
64
|
+
controller.devClientHost.currentReactContext?.let {
|
|
63
65
|
onReactContextInitialized(it)
|
|
64
66
|
return
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
|
|
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
|
-
|
|
87
|
+
controller.devClientHost.removeReactInstanceEventListener(this)
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
private val isSimulator
|
package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherNetworkInterceptor.kt
CHANGED
|
@@ -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
|
|
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
|
|
25
|
-
if (
|
|
25
|
+
val reactHost = requireNotNull(weakController.get()?.appHost)
|
|
26
|
+
if (reactHostHashCode != reactHost.hashCode()) {
|
|
26
27
|
_inspectorPackagerConnection?.clear()
|
|
27
28
|
_inspectorPackagerConnection = null
|
|
28
|
-
|
|
29
|
+
reactHostHashCode = 0
|
|
29
30
|
}
|
|
30
31
|
if (_inspectorPackagerConnection == null) {
|
|
31
|
-
_inspectorPackagerConnection = InspectorPackagerConnectionWrapper(
|
|
32
|
-
|
|
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?.
|
|
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(
|
|
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 =
|
|
88
|
-
val devSupportManagerBaseClass
|
|
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]
|