1 package expo.modules.devlauncher.helpers
2
3 import android.content.Context
4 import android.net.Uri
5 import android.util.Log
6 import com.facebook.react.ReactNativeHost
7 import com.facebook.react.ReactPackage
8 import com.facebook.react.bridge.JSBundleLoader
9 import com.facebook.react.devsupport.DevLauncherInternalSettings
10 import expo.interfaces.devmenu.annotations.ContainsDevMenuExtension
11 import expo.modules.devlauncher.react.DevLauncherDevSupportManagerSwapper
12 import expo.modules.devlauncher.rncompatibility.DevLauncherDevSupportManager
13 import okhttp3.HttpUrl
14
injectReactInterceptornull15 fun injectReactInterceptor(
16 context: Context,
17 reactNativeHost: ReactNativeHost,
18 url: Uri
19 ): Boolean {
20 val port = if (url.port != -1) url.port else HttpUrl.defaultPort(url.scheme)
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 }
32
33 injectDevSupportManager(reactNativeHost)
34
35 val result = injectDebugServerHost(
36 context,
37 reactNativeHost,
38 debugServerHost,
39 appBundleName
40 )
41 (reactNativeHost.reactInstanceManager.devSupportManager as? DevLauncherDevSupportManager)?.startInspectorWhenDevLauncherReady()
42
43 return result
44 }
45
injectDevSupportManagernull46 fun injectDevSupportManager(
47 reactNativeHost: ReactNativeHost
48 ) {
49 DevLauncherDevSupportManagerSwapper()
50 .swapDevSupportManagerImpl(reactNativeHost.reactInstanceManager)
51 }
52
injectDebugServerHostnull53 fun injectDebugServerHost(
54 context: Context,
55 reactNativeHost: ReactNativeHost,
56 debugServerHost: String,
57 appBundleName: String
58 ): Boolean {
59 return try {
60 val instanceManager = reactNativeHost.reactInstanceManager
61 val settings = DevLauncherInternalSettings(context, debugServerHost)
62 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 // set useDeveloperSupport to true in case it was previously set to false from loading a published app
79 val mUseDeveloperSupportField = instanceManager.javaClass.getDeclaredField("mUseDeveloperSupport")
80 mUseDeveloperSupportField.isAccessible = true
81 mUseDeveloperSupportField[instanceManager] = true
82 true
83 } catch (e: Exception) {
84 Log.e("DevLauncher", "Unable to inject debug server host settings.", e)
85 false
86 }
87 }
88
injectLocalBundleLoadernull89 fun injectLocalBundleLoader(
90 reactNativeHost: ReactNativeHost,
91 bundlePath: String
92 ): Boolean {
93 return try {
94 val instanceManager = reactNativeHost.reactInstanceManager
95 val instanceManagerClass = instanceManager.javaClass
96
97 val jsBundleLoader = JSBundleLoader.createFileLoader(bundlePath)
98 val mBundleLoaderField = instanceManagerClass.getDeclaredField("mBundleLoader")
99 mBundleLoaderField.isAccessible = true
100 mBundleLoaderField[instanceManager] = jsBundleLoader
101
102 val mUseDeveloperSupportField = instanceManagerClass.getDeclaredField("mUseDeveloperSupport")
103 mUseDeveloperSupportField.isAccessible = true
104 mUseDeveloperSupportField[instanceManager] = false
105 true
106 } catch (e: Exception) {
107 Log.e("DevLauncher", "Unable to load local bundle file", e)
108 false
109 }
110 }
111
findDevMenuPackagenull112 fun findDevMenuPackage(): ReactPackage? {
113 return try {
114 val clazz = Class.forName("expo.modules.devmenu.DevMenuPackage")
115 clazz.newInstance() as? ReactPackage
116 } catch (e: Exception) {
117 null
118 }
119 }
120
findPackagesWithDevMenuExtensionnull121 fun findPackagesWithDevMenuExtension(reactNativeHost: ReactNativeHost): List<ReactPackage> {
122 return try {
123 val clazz = Class.forName("com.facebook.react.PackageList")
124 val ctor = clazz.getConstructor(ReactNativeHost::class.java)
125 val packageList = ctor.newInstance(reactNativeHost)
126
127 val getPackagesMethod = packageList.javaClass.getDeclaredMethod("getPackages")
128 val packages = getPackagesMethod.invoke(packageList) as List<*>
129 return packages
130 .filterIsInstance<ReactPackage>()
131 .filter {
132 it.javaClass.isAnnotationPresent(ContainsDevMenuExtension::class.java)
133 }
134 } catch (e: Exception) {
135 Log.e("DevLauncher", "Unable find packages with dev menu extension.`.", e)
136 emptyList()
137 }
138 }
139