1 package expo.modules.devmenu
2 
3 import android.app.Application
4 import android.content.Context
5 import android.util.Log
6 import com.facebook.react.ReactApplication
7 import com.facebook.react.ReactInstanceManager
8 import com.facebook.react.ReactNativeHost
9 import com.facebook.react.ReactPackage
10 import com.facebook.react.ReactPackageTurboModuleManagerDelegate
11 import com.facebook.react.bridge.JSIModulePackage
12 import com.facebook.react.bridge.JavaScriptExecutorFactory
13 import com.facebook.react.config.ReactFeatureFlags
14 import com.facebook.react.defaults.DefaultJSIModulePackage
15 import com.facebook.react.devsupport.DevMenuReactInternalSettings
16 import com.facebook.react.devsupport.DevServerHelper
17 import com.facebook.react.shell.MainReactPackage
18 import devmenu.com.th3rdwave.safeareacontext.SafeAreaProviderManager
19 import expo.modules.adapters.react.ModuleRegistryAdapter
20 import expo.modules.adapters.react.ReactModuleRegistryProvider
21 import expo.modules.devmenu.modules.DevMenuInternalModule
22 import expo.modules.devmenu.modules.DevMenuPreferences
23 import expo.modules.devmenu.react.createNonDebuggableJavaScriptExecutorFactory
24 import expo.modules.kotlin.ModulesProvider
25 import java.io.BufferedReader
26 import java.io.FileNotFoundException
27 import java.io.InputStreamReader
28 
29 /**
30  * Class that represents react host used by dev menu.
31  */
32 class DevMenuHost(application: Application) : ReactNativeHost(application) {
33 
getPackagesnull34   override fun getPackages(): List<ReactPackage> {
35     val packages = mutableListOf(
36       MainReactPackage(null),
37       ModuleRegistryAdapter(
38         ReactModuleRegistryProvider(emptyList()),
39         object : ModulesProvider {
40           override fun getModulesList() =
41             listOf(
42               DevMenuInternalModule::class.java,
43               DevMenuPreferences::class.java,
44               SafeAreaProviderManager::class.java
45             )
46         }
47       ),
48       DevMenuPackage(),
49     )
50 
51     try {
52       val devLauncherPackage = Class.forName("expo.modules.devlauncher.DevLauncherPackage")
53       val pkg = devLauncherPackage.getConstructor().newInstance() as ReactPackage
54       packages.add(pkg)
55     } catch (e: ClassNotFoundException) {
56       // dev launcher is not installed in this project
57     }
58 
59     return packages
60   }
61 
getUseDeveloperSupportnull62   override fun getUseDeveloperSupport() = false // change it and run `yarn start` in `expo-dev-menu` to launch dev menu from local packager
63 
64   override fun getBundleAssetName() = "EXDevMenuApp.android.js"
65 
66   override fun getJSMainModuleName() = "index"
67 
68   fun getContext(): Context = super.getApplication()
69 
70   override fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory? {
71     return createNonDebuggableJavaScriptExecutorFactory(application)
72   }
73 
createReactInstanceManagernull74   override fun createReactInstanceManager(): ReactInstanceManager {
75     val reactInstanceManager = super.createReactInstanceManager()
76 
77     if (useDeveloperSupport) {
78       // To use a different packager url, we need to replace internal RN objects.
79       try {
80         val serverIp = BufferedReader(
81           InputStreamReader(super.getApplication().assets.open("dev-menu-packager-host"))
82         ).use {
83           it.readLine()
84         }
85 
86         val devMenuInternalReactSettings = DevMenuReactInternalSettings(serverIp, super.getApplication())
87 
88         val devSupportManager = reactInstanceManager.devSupportManager
89         val devSupportManagerBaseClass = devSupportManager.javaClass.superclass!!
90         setPrivateField(
91           obj = devSupportManager,
92           objClass = devSupportManagerBaseClass,
93           field = "mDevSettings",
94           newValue = devMenuInternalReactSettings
95         )
96 
97         val devServerHelper: DevServerHelper = getPrivateFiled(devSupportManager, devSupportManagerBaseClass, "mDevServerHelper")
98         setPrivateField(
99           obj = devServerHelper,
100           objClass = devServerHelper.javaClass,
101           field = "mSettings",
102           newValue = devMenuInternalReactSettings
103         )
104 
105         Log.i(DEV_MENU_TAG, "DevSettings was correctly injected.")
106       } catch (e: FileNotFoundException) {
107         Log.e(DEV_MENU_TAG, "Couldn't find `dev-menu-packager-host` file.", e)
108       } catch (e: Exception) {
109         Log.e(DEV_MENU_TAG, "Couldn't inject DevSettings object.", e)
110       }
111     }
112 
113     return reactInstanceManager
114   }
115 
getReactPackageTurboModuleManagerDelegateBuildernull116   override fun getReactPackageTurboModuleManagerDelegateBuilder(): ReactPackageTurboModuleManagerDelegate.Builder? {
117     if (!ReactFeatureFlags.useTurboModules) {
118       return null
119     }
120     val appHost = (application as ReactApplication)?.reactNativeHost ?: return null
121     val method = ReactNativeHost::class.java.getDeclaredMethod("getReactPackageTurboModuleManagerDelegateBuilder")
122     method.isAccessible = true
123     return method.invoke(appHost) as ReactPackageTurboModuleManagerDelegate.Builder
124   }
getJSIModulePackagenull125   override fun getJSIModulePackage(): JSIModulePackage? =
126     if (ReactFeatureFlags.enableFabricRenderer) {
127       DefaultJSIModulePackage(this)
128     } else {
129       null
130     }
131 }
132