1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package versioned.host.exp.exponent
3 
4 import android.content.Context
5 import android.os.Looper
6 import com.facebook.react.ReactPackage
7 import com.facebook.react.bridge.NativeModule
8 import com.facebook.react.bridge.ReactApplicationContext
9 import com.facebook.react.uimanager.ViewManager
10 import expo.modules.adapters.react.ReactModuleRegistryProvider
11 import expo.modules.core.interfaces.Package
12 import expo.modules.core.interfaces.SingletonModule
13 import expo.modules.kotlin.ModulesProvider
14 import expo.modules.random.RandomModule
15 import expo.modules.manifests.core.Manifest
16 import host.exp.exponent.Constants
17 import host.exp.exponent.analytics.EXL
18 import host.exp.exponent.kernel.ExperienceKey
19 // WHEN_VERSIONING_REMOVE_FROM_HERE
20 import host.exp.exponent.kernel.ExponentKernelModuleProvider
21 // WHEN_VERSIONING_REMOVE_TO_HERE
22 import host.exp.exponent.kernel.KernelConstants
23 import host.exp.exponent.utils.ScopedContext
24 import org.json.JSONException
25 import versioned.host.exp.exponent.modules.api.*
26 import versioned.host.exp.exponent.modules.api.cognito.RNAWSCognitoModule
27 import versioned.host.exp.exponent.modules.api.components.datetimepicker.RNDateTimePickerPackage
28 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerModule
29 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerPackage
30 import versioned.host.exp.exponent.modules.api.components.lottie.LottiePackage
31 import versioned.host.exp.exponent.modules.api.components.maps.MapsPackage
32 import versioned.host.exp.exponent.modules.api.components.maskedview.RNCMaskedViewPackage
33 import versioned.host.exp.exponent.modules.api.components.picker.RNCPickerPackage
34 import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.StripeSdkPackage
35 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementModule
36 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementPackage
37 import versioned.host.exp.exponent.modules.api.components.slider.ReactSliderPackage
38 import versioned.host.exp.exponent.modules.api.components.svg.SvgPackage
39 import versioned.host.exp.exponent.modules.api.components.pagerview.PagerViewPackage
40 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewModule
41 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewPackage
42 import versioned.host.exp.exponent.modules.api.netinfo.NetInfoModule
43 import versioned.host.exp.exponent.modules.api.notifications.NotificationsModule
44 import versioned.host.exp.exponent.modules.api.safeareacontext.SafeAreaContextPackage
45 import versioned.host.exp.exponent.modules.api.screens.RNScreensPackage
46 import versioned.host.exp.exponent.modules.api.viewshot.RNViewShotModule
47 import versioned.host.exp.exponent.modules.internal.DevMenuModule
48 import versioned.host.exp.exponent.modules.test.ExponentTestNativeModule
49 import versioned.host.exp.exponent.modules.universal.ExpoModuleRegistryAdapter
50 import versioned.host.exp.exponent.modules.universal.ScopedModuleRegistryAdapter
51 import java.io.UnsupportedEncodingException
52 
53 // This is an Expo module but not a unimodule
54 class ExponentPackage : ReactPackage {
55   private val isKernel: Boolean
56   private val experienceProperties: Map<String, Any?>
57   private val manifest: Manifest
58   private val moduleRegistryAdapter: ScopedModuleRegistryAdapter
59 
60   private constructor(
61     isKernel: Boolean,
62     experienceProperties: Map<String, Any?>,
63     manifest: Manifest,
64     expoPackages: List<Package>,
65     singletonModules: List<SingletonModule>?
66   ) {
67     this.isKernel = isKernel
68     this.experienceProperties = experienceProperties
69     this.manifest = manifest
70     moduleRegistryAdapter = createDefaultModuleRegistryAdapterForPackages(expoPackages, singletonModules)
71   }
72 
73   constructor(
74     experienceProperties: Map<String, Any?>,
75     manifest: Manifest,
76     expoPackages: List<Package>?,
77     delegate: ExponentPackageDelegate?,
78     singletonModules: List<SingletonModule>
79   ) {
80     isKernel = false
81     this.experienceProperties = experienceProperties
82     this.manifest = manifest
83     val packages = expoPackages ?: ExperiencePackagePicker.packages(manifest)
84     // Delegate may not be null only when the app is detached
85     moduleRegistryAdapter = createModuleRegistryAdapter(delegate, singletonModules, packages)
86   }
87 
88   private fun createModuleRegistryAdapter(
89     delegate: ExponentPackageDelegate?,
90     singletonModules: List<SingletonModule>,
91     packages: List<Package>
92   ): ScopedModuleRegistryAdapter {
93     var registryAdapter: ScopedModuleRegistryAdapter? = null
94     if (delegate != null) {
95       registryAdapter = delegate.getScopedModuleRegistryAdapterForPackages(packages, singletonModules)
96     }
97     if (registryAdapter == null) {
98       registryAdapter = createDefaultModuleRegistryAdapterForPackages(packages, singletonModules, ExperiencePackagePicker)
99     }
100     return registryAdapter
101   }
102 
103   override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
104     val isVerified = manifest.isVerified() ?: false
105     val nativeModules: MutableList<NativeModule> = mutableListOf(
106       URLHandlerModule(reactContext),
107       ShakeModule(reactContext),
108       KeyboardModule(reactContext)
109     )
110 
111     if (isKernel) {
112       // WHEN_VERSIONING_REMOVE_FROM_HERE
113       nativeModules.add((ExponentKernelModuleProvider.newInstance(reactContext) as NativeModule?)!!)
114       // WHEN_VERSIONING_REMOVE_TO_HERE
115     }
116     if (!isKernel && !Constants.isStandaloneApp()) {
117       // We need DevMenuModule only in non-home and non-standalone apps.
118       nativeModules.add(DevMenuModule(reactContext, experienceProperties, manifest))
119     }
120 
121     if (isVerified) {
122       try {
123         val experienceKey = ExperienceKey.fromManifest(manifest)
124         val scopedContext = ScopedContext(reactContext, experienceKey)
125         nativeModules.add(NotificationsModule(reactContext, experienceKey, manifest.getStableLegacyID(), manifest.getEASProjectID()))
126         nativeModules.add(RNViewShotModule(reactContext, scopedContext))
127         nativeModules.add(RandomModule(reactContext))
128         nativeModules.add(ExponentTestNativeModule(reactContext))
129         nativeModules.add(PedometerModule(reactContext))
130         nativeModules.add(ScreenOrientationModule(reactContext))
131         nativeModules.add(RNGestureHandlerModule(reactContext))
132         nativeModules.add(RNAWSCognitoModule(reactContext))
133         nativeModules.add(RNCWebViewModule(reactContext))
134         nativeModules.add(NetInfoModule(reactContext))
135         nativeModules.add(RNSharedElementModule(reactContext))
136         nativeModules.addAll(SvgPackage().createNativeModules(reactContext))
137         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
138         nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
139         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
140 
141         // Call to create native modules has to be at the bottom --
142         // -- ExpoModuleRegistryAdapter uses the list of native modules
143         // to create Bindings for internal modules.
144         nativeModules.addAll(
145           moduleRegistryAdapter.createNativeModules(
146             scopedContext,
147             experienceKey,
148             experienceProperties,
149             manifest,
150             nativeModules
151           )
152         )
153       } catch (e: JSONException) {
154         EXL.e(TAG, e.toString())
155       } catch (e: UnsupportedEncodingException) {
156         EXL.e(TAG, e.toString())
157       }
158     }
159     return nativeModules
160   }
161 
162   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
163     val viewManagers = mutableListOf<ViewManager<*, *>>()
164 
165     // Add view manager from 3rd party library packages.
166     addViewManagersFromPackages(
167       reactContext,
168       viewManagers,
169       listOf(
170         SvgPackage(),
171         MapsPackage(),
172         LottiePackage(),
173         RNGestureHandlerPackage(),
174         RNScreensPackage(),
175         RNCWebViewPackage(),
176         SafeAreaContextPackage(),
177         RNSharedElementPackage(),
178         RNDateTimePickerPackage(),
179         RNCMaskedViewPackage(),
180         RNCPickerPackage(),
181         ReactSliderPackage(),
182         PagerViewPackage(),
183         stripePackage
184       )
185     )
186     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
187     return viewManagers
188   }
189 
190   private fun addViewManagersFromPackages(
191     reactContext: ReactApplicationContext,
192     viewManagers: MutableList<ViewManager<*, *>>,
193     packages: List<ReactPackage>
194   ) {
195     for (pack in packages) {
196       viewManagers.addAll(pack.createViewManagers(reactContext))
197     }
198   }
199 
200   private fun createDefaultModuleRegistryAdapterForPackages(
201     packages: List<Package>,
202     singletonModules: List<SingletonModule>?,
203     modulesProvider: ModulesProvider? = null
204   ): ExpoModuleRegistryAdapter {
205     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules), modulesProvider)
206   }
207 
208   companion object {
209     private val TAG = ExponentPackage::class.java.simpleName
210 
211     private val singletonModules = mutableListOf<SingletonModule>()
212     private val singletonModulesClasses = mutableSetOf<Class<*>>()
213 
214     // Need to avoid initializing 2 StripeSdkPackages
215     private val stripePackage = StripeSdkPackage()
216 
217     fun kernelExponentPackage(
218       context: Context,
219       manifest: Manifest,
220       expoPackages: List<Package>,
221       initialURL: String?
222     ): ExponentPackage {
223       val kernelExperienceProperties = mutableMapOf(
224         KernelConstants.LINKING_URI_KEY to "exp://",
225         KernelConstants.IS_HEADLESS_KEY to false
226       ).apply {
227         if (initialURL != null) {
228           this[KernelConstants.INTENT_URI_KEY] = initialURL
229         }
230       }
231       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
232       return ExponentPackage(
233         true,
234         kernelExperienceProperties,
235         manifest,
236         expoPackages,
237         singletonModules
238       )
239     }
240 
241     fun getOrCreateSingletonModules(
242       context: Context?,
243       manifest: Manifest?,
244       providedExpoPackages: List<Package>?
245     ): List<SingletonModule> {
246       if (Looper.getMainLooper() != Looper.myLooper()) {
247         throw RuntimeException("Singleton modules must be created on the main thread.")
248       }
249 
250       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
251 
252       for (expoPackage in expoPackages) {
253         // For now we just accumulate more and more singleton modules,
254         // but in fact we should only return singleton modules from the requested
255         // unimodules. This solution also unnecessarily creates singleton modules
256         // which are going to be deallocated in a tick, but there's no better solution
257         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
258         // is TaskService which is safe to initialize more than once.
259         val packageSingletonModules = expoPackage.createSingletonModules(context)
260         for (singletonModule in packageSingletonModules) {
261           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
262             singletonModules.add(singletonModule)
263             singletonModulesClasses.add(singletonModule.javaClass)
264           }
265         }
266       }
267 
268       return singletonModules
269     }
270   }
271 }
272