@ionic/portals-react-native 0.1.0 → 0.2.0

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 (40) hide show
  1. package/README.md +11 -159
  2. package/ReactNativePortals.podspec +2 -1
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/io/ionic/portals/reactnative/PortalView.kt +132 -0
  5. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeLiveUpdatesModule.kt +10 -28
  6. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalManager.kt +149 -0
  7. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +73 -232
  8. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPackage.kt +0 -1
  9. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPubSub.kt +45 -0
  10. package/ios/LiveUpdate+Dict.swift +30 -0
  11. package/ios/LiveUpdateManager+Async.swift +69 -0
  12. package/ios/LiveUpdateManagerError+Dict.swift +19 -0
  13. package/ios/Podfile +2 -1
  14. package/ios/Podfile.lock +8 -7
  15. package/ios/Portal+Dict.swift +35 -0
  16. package/ios/PortalManager.m +10 -4
  17. package/ios/PortalView.m +1 -1
  18. package/ios/PortalView.swift +67 -0
  19. package/ios/PortalsConfig.swift +92 -0
  20. package/ios/PortalsPubSub.m +3 -3
  21. package/ios/PortalsPubSub.swift +47 -0
  22. package/ios/PortalsReactNative.swift +104 -0
  23. package/ios/ReactNativePortals.xcodeproj/project.pbxproj +32 -8
  24. package/lib/commonjs/PortalView.android.js +2 -10
  25. package/lib/commonjs/PortalView.android.js.map +1 -1
  26. package/lib/commonjs/PortalView.js +0 -6
  27. package/lib/commonjs/PortalView.js.map +1 -1
  28. package/lib/commonjs/index.js +54 -38
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/module/PortalView.android.js +2 -5
  31. package/lib/module/PortalView.android.js.map +1 -1
  32. package/lib/module/PortalView.js +0 -2
  33. package/lib/module/PortalView.js.map +1 -1
  34. package/lib/module/index.js +52 -19
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/index.d.ts +27 -2
  37. package/package.json +3 -1
  38. package/src/index.ts +47 -12
  39. package/ios/LiveUpdatesManager.m +0 -16
  40. package/ios/ReactNativePortals.swift +0 -262
package/README.md CHANGED
@@ -3,13 +3,17 @@
3
3
  <img src="https://user-images.githubusercontent.com/5769389/134952353-7d7b4145-3a80-4946-9b08-17b3a22c03a1.png" width="560" />
4
4
  </div>
5
5
  <div align="center">
6
- ⚡️ A supercharged native Web View for iOS and Android ⚡️
6
+ ⚡️ A supercharged native Web View for React Native ⚡️
7
7
  </div>
8
8
  <br />
9
9
  <p align="center">
10
- <a href="https://github.com/ionic-team/react-native-ionic-portals/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/ionic-portals/CI?style=flat-square" /></a>
10
+ <img src="https://img.shields.io/badge/platform-iOS%2013%2B-lightgrey?style=flat-square" alt="Supports iOS 13 and up" />
11
+ <img src="https://img.shields.io/badge/platform-Android%20SDK%2021%2B-brightgreen?style=flat-square" alt="Supports Android SDK 21 and up" />
12
+ <img src="https://img.shields.io/badge/platform-React%20Native%200.63.3%2B-blue?style=flat-square" alt="Supports React Native 0.63.3 and up" />
13
+ </p>
14
+ <p align="center">
15
+ <a href="https://github.com/ionic-team/ionic-portals/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/react-native-ionic-portals/Verify?style=flat-square" /></a>
11
16
  <a href="https://www.npmjs.com/package/@ionic/portals-react-native"><img src="https://img.shields.io/npm/l/@ionic/portals-react-native?style=flat-square" /></a>
12
- <a href="https://www.npmjs.com/package/@ionic/portals-react-native"><img src="https://img.shields.io/npm/v/@ionic/portals-react-native?style=flat-square" /></a>
13
17
  </p>
14
18
  <p align="center">
15
19
  <a href="https://ionic.io/docs/portals"><img src="https://img.shields.io/static/v1?label=docs&message=ionic.io/portals&color=blue&style=flat-square" /></a>
@@ -18,166 +22,15 @@
18
22
 
19
23
  ---
20
24
 
21
- Ionic Portals is a supercharged native Web View component for iOS and Android that lets you add web-based experiences to React Native mobile apps. It enables React Native and web teams to better collaborate and bring new and existing web experiences to mobile in a safe, controlled way.
25
+ Ionic Portals is a supercharged native Web View component for React Native that lets you add web-based experiences to native mobile apps. It enables native and web teams to better collaborate and bring new and existing web experiences to mobile in a safe, controlled way.
22
26
 
23
27
  ## Getting Started
24
28
 
25
- ### Installation
26
- `npm install @ionic/portals-react-native`
27
- or
28
- `yarn add @ionic/portals-react-native`
29
-
30
- ### Usage
31
- Register Portals with your [product key](#Registration):
32
- ```javascript
33
- import { register } from '@ionic/portals-react-native';
34
-
35
- register('YOUR_PORTAL_KEY_HERE');
36
- ```
37
-
38
- Create a Portal and add it to the portal registry:
39
- ```javascript
40
- import { addPortal } from '@ionic/portals-react-native';
41
- const helloPortal = {
42
- // A unique name to reference later
43
- name: 'hello',
44
- // This is the location of your web bundle relative to the asset directory in Android and Bundle.main in iOS
45
- // This will default to the name of the portal
46
- startDir: 'portals/hello',
47
- // Any initial state to be provided to a Portal if needed
48
- initialContext: {
49
- greeting: 'Hello, world!'
50
- }
51
- };
52
-
53
- addPortal(helloPortal);
54
- ```
55
-
56
- Create a PortalView in your view hierarchy:
57
- ```javascript
58
- import { PortalView } from '@ionic/portals-react-native';
59
-
60
- <PortalView
61
- // The name of the portal to be used in the view
62
- name='hello'
63
-
64
- // Set any initial context you may want to override.
65
- initialContext={{ greeting: 'Goodbye!' }}
66
-
67
- // Setting a size is required
68
- style={{ flex: 1, height: 300 }}
69
- />
70
- ```
71
-
72
- #### iOS Specific Configuration
73
- ##### AppDelegate
74
- Both Capacitor and React Native have classes named `AppDelegate`. To prevent a clash that can prevent your React Native application from launching,
75
- you will need to rename `AppDelegate` to something else:
76
- ```objective-c
77
- // AppDelegate.h
78
- @interface RNAppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
79
- ```
80
-
81
- ```objective-c
82
- // AppDelegate.m
83
- @implementation RNAppDelegate
84
- @end
85
- ```
86
-
87
- ```objective-c
88
- // main.m
89
- #import <UIKit/UIKit.h>
90
-
91
- #import "AppDelegate.h"
92
-
93
- int main(int argc, char *argv[])
94
- {
95
- @autoreleasepool {
96
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([RNAppDelegate class]));
97
- }
98
- }
99
- ```
100
-
101
- ##### Podfile
102
- There are two methods you may use to ensure Portals can integrate into your React Native application: a custom `pre_install` hook or adding `use_frameworks!` to your Podfile. Only one of these approaches is needed to ensure that Capacitor is compiled as a dynamic framework.
103
-
104
- **pre_install**
105
-
106
- Using the `pre_install` hook allows you to keep all the other React Native dependencies as static frameworks:
107
- ```ruby
108
- # These frameworks are required to be dynamic.
109
- dynamic_frameworks = ['Capacitor', 'CapacitorCordova']
110
-
111
- pre_install do |installer|
112
- installer.pod_targets.each do |pod|
113
- if dynamic_frameworks.include?(pod.name)
114
- def pod.static_framework?
115
- false
116
- end
117
- def pod.build_type
118
- Pod::BuildType.dynamic_framework
119
- end
120
- end
121
- end
122
- end
123
- ```
124
-
125
- **use_frameworks**
126
-
127
- Alternative to the `pre_install` hook, you can add `use_frameworks!` to your Podfile application target. Using this approach requires removing `use_flipper!()` from the Podfile.
128
-
129
- ### Communicating between React Native and Web
130
- One of the key features of Ionic Portals for React Native is facilitating communication between the web and React Native layers of your application.
131
- Publishing a message to the web:
132
- ```javascript
133
- import { publish } from '@ionic/portals-react-native';
134
-
135
- publish('topic', { number: 1 })
136
- ```
137
-
138
- Subscribe to messages from the web:
139
- ```javascript
140
- import { subscribe } from '@ionic/portals-react-native';
141
-
142
- let subscriptionReference = await subscribe('topic', message => {
143
- // Here you have access to:
144
- // message.data - Any data sent from the web
145
- // message.subscriptionRef - The subscription reference used to manage the lifecycle of the subscription
146
- // message.topic - The topic the message was published on
147
- })
148
- ```
149
-
150
- When you no longer need to receive events, unsubscribe:
151
- ```javascript
152
- import { unsubscribe } from '@ionic/portals-react-native';
153
-
154
- unsubscribe('channel:topic', subscriptionReference)
155
- ```
156
-
157
- To see an example of Portals Pub/Sub in action that manages the lifecycle of a subscription with the lifecycle of a React Native component, refer to the [`PubSubLabel`](https://github.com/ionic-team/react-native-ionic-portals/blob/af19df0d66059d85ab8dde493504368c3bf39127/example/App.tsx#L53) implementation in the example project.
158
-
159
- ### Using Capacitor Plugins
160
- If you need to use any Capacitor plugins, the classpath of the Android plugins will have to be provided to the `Portal` `androidPlugins` property.
161
-
162
- ```javascript
163
- const helloPortal = {
164
- name: 'hello',
165
- startDir: 'portals/hello',
166
- androidPlugins: ['com.capacitorjs.plugins.camera.CameraPlugin'],
167
- initialContext: {
168
- greeting: 'Hello, world!'
169
- }
170
- };
171
- ```
172
-
173
- No configuration for iOS is needed since plugins are automatically registered when the Capacitor bridge initializes on iOS.
174
-
175
- ### Bundling Your Web Apps
176
- Currently there is no tooling for bundling your web apps directly as part of @ionic/portals-react-native. Please follow the [native guides](https://ionic.io/docs/portals/how-to/pull-in-web-bundle#setup-the-web-asset-directory) to manage this as part of the native build process.
29
+ See our docs to [get started with Portals](https://ionic.io/docs/portals/getting-started/guide).
177
30
 
178
31
  ## Registration
179
32
 
180
- Ionic Portals for React Native requires a key to use. Once you have integrated Portals into your project, login to your ionic account to get a key. See our doc on [how to register for free and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [usage](#Usage) section on how to add your key to your React Native application.
33
+ The Ionic Portals library for React Native requires a **free** license key to use. Once you have integrated Portals into your project, login to your ionic account to get a key. See our doc on [how to register for free and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [React Native](https://ionic.io/docs/portals/getting-started/react-native) getting started guides to see where to add your key.
181
34
 
182
35
  ## FAQ
183
36
 
@@ -191,7 +44,6 @@ See our [license](https://github.com/ionic-team/ionic-portals/blob/main/LICENSE.
191
44
 
192
45
  ### How is Portals Related to Capacitor and Ionic?
193
46
 
194
- Ionic Portals is a solution that lets you add web-based experiences to your native mobile apps. Portals uses [Capacitor](https://capacitorjs.com) as a bridge between the native code and the web code to allow for cross-communication between the two layers. Because Portals uses Capacitor under the hood, you are able to use any existing [Capacitor Plugins](https://capacitorjs.com/docs/plugins) and even most [Cordova Plugins](https://capacitorjs.com/docs/plugins/cordova) while continuing to use your existing native workflow. Portals for React Native brings these capabilities to React Native applications.
47
+ Ionic Portals is a solution that lets you add web-based experiences to your native mobile apps. Portals uses [Capacitor](https://capacitorjs.com) as a bridge between the native code and the web code to allow for cross-communication between the two layers. Because Portals uses Capacitor under the hood, you are able to use any existing [Capacitor Plugins](https://capacitorjs.com/docs/plugins) while continuing to use your existing native workflow.
195
48
 
196
49
  [Ionic Framework](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to build top quality native and progressive web apps with web technologies. Your web experiences can be developed with Ionic, but it is not necessary to use Portals.
197
-
@@ -16,5 +16,6 @@ Pod::Spec.new do |s|
16
16
  s.source_files = "ios/**/*.{h,m,mm,swift}"
17
17
 
18
18
  s.dependency "React-Core"
19
- s.dependency "IonicPortals", "~> 0.6.3"
19
+ s.dependency "IonicPortals", "~> 0.6.4"
20
+ s.dependency "IonicLiveUpdates", "~> 0.2.0"
20
21
  end
@@ -126,7 +126,7 @@ dependencies {
126
126
  //noinspection GradleDynamicVersion
127
127
  api "io.ionic:portals:0.6.+"
128
128
  //noinspection GradleDynamicVersion
129
- api "io.ionic:liveupdates:0.0.+"
129
+ api "io.ionic:liveupdates:0.1.+"
130
130
 
131
131
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
132
132
  }
@@ -0,0 +1,132 @@
1
+ package io.ionic.portals.reactnative
2
+
3
+ import android.util.Log
4
+ import android.view.Choreographer
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import android.widget.FrameLayout
8
+ import androidx.fragment.app.FragmentActivity
9
+ import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.facebook.react.bridge.ReadableArray
11
+ import com.facebook.react.bridge.ReadableMap
12
+ import com.facebook.react.uimanager.ThemedReactContext
13
+ import com.facebook.react.uimanager.ViewGroupManager
14
+ import com.facebook.react.uimanager.annotations.ReactProp
15
+ import com.getcapacitor.CapConfig
16
+ import io.ionic.portals.Portal
17
+ import io.ionic.portals.PortalFragment
18
+ import io.ionic.portals.PortalManager
19
+
20
+ private data class PortalViewState(
21
+ var fragment: PortalFragment?,
22
+ var portal: Portal?,
23
+ var initialContext: HashMap<String, Any>?
24
+ )
25
+
26
+ internal class PortalViewManager(private val context: ReactApplicationContext) :
27
+ ViewGroupManager<FrameLayout>() {
28
+ private val createId = 1
29
+ private val fragmentMap = mutableMapOf<Int, PortalViewState>()
30
+
31
+ @ReactProp(name = "portal")
32
+ fun setPortal(viewGroup: ViewGroup, portal: ReadableMap) {
33
+ val name = portal.getString("name") ?: return
34
+ when (val viewState = fragmentMap[viewGroup.id]) {
35
+ null -> fragmentMap[viewGroup.id] = PortalViewState(
36
+ fragment = null,
37
+ portal = PortalManager.getPortal(name),
38
+ initialContext = portal.getMap("initialContext")?.toHashMap()
39
+ )
40
+ }
41
+ }
42
+
43
+ override fun getName() = "AndroidPortalView"
44
+
45
+ override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
46
+ return FrameLayout(reactContext)
47
+ }
48
+
49
+ override fun getCommandsMap(): MutableMap<String, Int> {
50
+ return mutableMapOf("create" to createId)
51
+ }
52
+
53
+ override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
54
+ super.receiveCommand(root, commandId, args)
55
+ val viewId = args?.getInt(0) ?: return
56
+
57
+ @Suppress("NAME_SHADOWING")
58
+ val commandId = commandId?.toIntOrNull() ?: return
59
+
60
+ when (commandId) {
61
+ createId -> createFragment(root, viewId)
62
+ }
63
+ }
64
+
65
+ private fun createFragment(root: FrameLayout, viewId: Int) {
66
+ val viewState = fragmentMap[viewId] ?: return
67
+ val portal = viewState.portal ?: return
68
+
69
+ val parentView = root.findViewById<ViewGroup>(viewId)
70
+ setupLayout(parentView)
71
+
72
+ val portalFragment = PortalFragment(portal)
73
+
74
+ val configBuilder = CapConfig.Builder(context)
75
+ .setInitialFocus(false)
76
+
77
+ RNPortalManager.indexMap[portal.name]
78
+ ?.let(configBuilder::setStartPath)
79
+
80
+ portalFragment.setConfig(configBuilder.create())
81
+
82
+ viewState.initialContext?.let(portalFragment::setInitialContext)
83
+
84
+ viewState.fragment = portalFragment
85
+
86
+ val fragmentActivity = context.currentActivity as? FragmentActivity ?: return
87
+ fragmentActivity.supportFragmentManager
88
+ .beginTransaction()
89
+ .replace(viewId, portalFragment, "$viewId")
90
+ .commit()
91
+ }
92
+
93
+ override fun onDropViewInstance(view: FrameLayout) {
94
+ super.onDropViewInstance(view)
95
+ val viewState = fragmentMap[view.id] ?: return
96
+
97
+ try {
98
+ viewState.fragment
99
+ ?.parentFragmentManager
100
+ ?.beginTransaction()
101
+ ?.remove(viewState.fragment!!)
102
+ ?.commit()
103
+ } catch (e: IllegalStateException) {
104
+ Log.i("io.ionic.portals.rn", "Parent fragment manager not available")
105
+ }
106
+
107
+ fragmentMap.remove(view.id)
108
+ }
109
+
110
+ private fun setupLayout(view: ViewGroup) {
111
+ Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
112
+ override fun doFrame(frameTimeNanos: Long) {
113
+ layoutPortal(view)
114
+ view.viewTreeObserver.dispatchOnGlobalLayout()
115
+ Choreographer.getInstance().postFrameCallback(this)
116
+ }
117
+ })
118
+ }
119
+
120
+ private fun layoutPortal(view: ViewGroup) {
121
+ for (i in 0 until view.childCount) {
122
+ val child = view.getChildAt(i)
123
+
124
+ child.measure(
125
+ View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY),
126
+ View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY)
127
+ )
128
+
129
+ child.layout(0, 0, child.measuredWidth, child.measuredHeight)
130
+ }
131
+ }
132
+ }
@@ -1,5 +1,6 @@
1
1
  package io.ionic.portals.reactnative
2
2
 
3
+ import android.content.Context
3
4
  import com.facebook.react.bridge.*
4
5
  import io.ionic.liveupdates.LiveUpdate
5
6
  import io.ionic.liveupdates.LiveUpdateManager
@@ -11,11 +12,7 @@ import kotlinx.coroutines.flow.callbackFlow
11
12
  import kotlinx.coroutines.flow.fold
12
13
  import java.util.concurrent.Executors
13
14
 
14
- internal class LiveUpdatesModule(reactContext: ReactApplicationContext) :
15
- ReactContextBaseJavaModule(reactContext) {
16
-
17
- override fun getName() = "IONLiveUpdatesManager"
18
-
15
+ internal object LiveUpdatesModule {
19
16
  private val liveUpdateScope = CoroutineScope(
20
17
  Executors.newFixedThreadPool(4)
21
18
  .asCoroutineDispatcher()
@@ -51,22 +48,9 @@ internal class LiveUpdatesModule(reactContext: ReactApplicationContext) :
51
48
  LiveUpdateSuccess(liveUpdate)
52
49
  }
53
50
 
54
-
55
- @ReactMethod
56
- fun addLiveUpdate(map: ReadableMap) {
57
- val appId = map.getString("appId") ?: return
58
- val channel = map.getString("channel") ?: return
59
-
60
- LiveUpdateManager.addLiveUpdateInstance(
61
- context = reactApplicationContext,
62
- liveUpdate = LiveUpdate(appId, channel)
63
- )
64
- }
65
-
66
- @ReactMethod
67
- fun syncOne(appId: String, promise: Promise) {
51
+ fun syncOne(appId: String, context: Context, promise: Promise) {
68
52
  LiveUpdateManager.sync(
69
- context = reactApplicationContext,
53
+ context = context,
70
54
  appId = appId,
71
55
  callback = object : SyncCallback {
72
56
  override fun onAppComplete(
@@ -85,27 +69,25 @@ internal class LiveUpdatesModule(reactContext: ReactApplicationContext) :
85
69
  )
86
70
  }
87
71
 
88
- @ReactMethod
89
- fun syncSome(appIds: ReadableArray, promise: Promise) {
72
+ fun syncSome(appIds: ReadableArray, context: Context, promise: Promise) {
90
73
  @Suppress("NAME_SHADOWING")
91
74
  val appIds = (0 until appIds.size())
92
75
  .mapNotNull(appIds::getString)
93
76
  .toTypedArray()
94
77
 
95
- sync(appIds, promise)
78
+ sync(appIds, context, promise)
96
79
  }
97
80
 
98
- @ReactMethod
99
- fun syncAll(promise: Promise) {
100
- sync(emptyArray(), promise)
81
+ fun syncAll(context: Context, promise: Promise) {
82
+ sync(emptyArray(), context, promise)
101
83
  }
102
84
 
103
85
  @OptIn(ExperimentalCoroutinesApi::class)
104
- private fun sync(appIds: Array<String>, promise: Promise) {
86
+ private fun sync(appIds: Array<String>, context: Context, promise: Promise) {
105
87
  liveUpdateScope.launch {
106
88
  val results = callbackFlow {
107
89
  LiveUpdateManager.sync(
108
- context = reactApplicationContext,
90
+ context = context,
109
91
  appIds = appIds,
110
92
  callback = object : SyncCallback {
111
93
  override fun onAppComplete(
@@ -0,0 +1,149 @@
1
+ package io.ionic.portals.reactnative
2
+
3
+ import com.facebook.react.bridge.*
4
+ import com.getcapacitor.Plugin
5
+ import io.ionic.liveupdates.LiveUpdate
6
+ import io.ionic.liveupdates.LiveUpdateManager
7
+ import io.ionic.portals.Portal
8
+ import io.ionic.portals.PortalBuilder
9
+ import io.ionic.portals.PortalManager
10
+ import io.ionic.portals.PortalsPlugin
11
+ import org.json.JSONArray
12
+ import org.json.JSONException
13
+ import org.json.JSONObject
14
+ import java.io.BufferedReader
15
+ import java.io.IOException
16
+
17
+ internal object RNPortalManager {
18
+ private val manager = PortalManager
19
+ internal val indexMap: MutableMap<String, String> = mutableMapOf()
20
+ private lateinit var reactApplicationContext: ReactApplicationContext
21
+ private var usesSecureLiveUpdates = false
22
+
23
+ fun register(key: String) = manager.register(key)
24
+ fun addPortal(map: ReadableMap): Portal? {
25
+ val name = map.getString("name") ?: return null
26
+ val portalBuilder = PortalBuilder(name)
27
+
28
+ map.getString("startDir")
29
+ ?.let(portalBuilder::setStartDir)
30
+
31
+ map.getMap("initialContext")
32
+ ?.toHashMap()
33
+ ?.let(portalBuilder::setInitialContext)
34
+
35
+ map.getString("index")
36
+ ?.let { indexMap[name] = "/$it" }
37
+
38
+ map.getArray("androidPlugins")
39
+ ?.toArrayList()
40
+ ?.mapNotNull { it as? String }
41
+ ?.map {
42
+ Class.forName(it)
43
+ .asSubclass(Plugin::class.java)
44
+ }
45
+ ?.forEach(portalBuilder::addPlugin)
46
+
47
+ map.getMap("liveUpdate")
48
+ ?.let { readableMap ->
49
+ val appId = readableMap.getString("appId") ?: return@let null
50
+ val channel = readableMap.getString("channel") ?: return@let null
51
+ val syncOnAdd = readableMap.getBoolean("syncOnAdd")
52
+ Pair(LiveUpdate(appId, channel, usesSecureLiveUpdates), syncOnAdd)
53
+ }
54
+ ?.let { (liveUpdate, updateOnAppLoad) ->
55
+ portalBuilder.setLiveUpdateConfig(
56
+ context = reactApplicationContext,
57
+ liveUpdateConfig = liveUpdate,
58
+ updateOnAppLoad = updateOnAppLoad
59
+ )
60
+ }
61
+
62
+ val portal = portalBuilder
63
+ .addPlugin(PortalsPlugin::class.java)
64
+ .create()
65
+
66
+ manager.addPortal(portal)
67
+ return portal
68
+ }
69
+
70
+ fun getPortal(name: String): Portal = manager.getPortal(name)
71
+
72
+ fun enableSecureLiveUpdates(keyPath: String) {
73
+ LiveUpdateManager.secureLiveUpdatePEM = keyPath
74
+ usesSecureLiveUpdates = true
75
+ }
76
+
77
+ fun registerFromConfigIfAvailable(context: ReactApplicationContext) {
78
+ reactApplicationContext = context
79
+ val config = try {
80
+ context.assets.open("portals.config.json")
81
+ .bufferedReader()
82
+ .use(BufferedReader::readText)
83
+ } catch (e: IOException) {
84
+ return
85
+ }
86
+
87
+ val configJson = try {
88
+ JSONObject(config)
89
+ } catch (e: JSONException) {
90
+ throw Error("Portals config data is malformed. Aborting.", e)
91
+ }
92
+
93
+ val registrationKey =
94
+ if (!configJson.isNull("registrationKey")) configJson.getString("registrationKey") else null
95
+ registrationKey?.let(::register)
96
+
97
+ val liveUpdatesKey =
98
+ if (!configJson.isNull("liveUpdatesKey")) configJson.getString("liveUpdatesKey") else null
99
+ liveUpdatesKey?.let {
100
+ LiveUpdateManager.secureLiveUpdatePEM = it
101
+ usesSecureLiveUpdates = true
102
+ }
103
+
104
+ val portalJsonArray = configJson.getJSONArray("portals")
105
+
106
+ for (index in 0 until portalJsonArray.length()) {
107
+ val portalJson = portalJsonArray.getJSONObject(index)
108
+ addPortal(portalJson.toReactMap())
109
+ }
110
+ }
111
+ }
112
+
113
+ internal fun JSONObject.toReactMap(): ReadableMap =
114
+ keys().asSequence().fold(WritableNativeMap()) { map, key ->
115
+ try {
116
+ when (val value = get(key)) {
117
+ is JSONObject -> map.putMap(key, value.toReactMap())
118
+ is JSONArray -> map.putArray(key, value.toReactArray())
119
+ is Boolean -> map.putBoolean(key, value)
120
+ is Int -> map.putInt(key, value)
121
+ is Double -> map.putDouble(key, value)
122
+ is String -> map.putString(key, value)
123
+ null -> map.putNull(key)
124
+ else -> map.putString(key, value.toString())
125
+ }
126
+ } catch (_: JSONException) {
127
+ }
128
+
129
+ return@fold map
130
+ }
131
+
132
+ private fun JSONArray.toReactArray(): ReadableArray =
133
+ (0 until length()).fold(WritableNativeArray()) { array, index ->
134
+ try {
135
+ when (val value = get(index)) {
136
+ is JSONObject -> array.pushMap(value.toReactMap())
137
+ is JSONArray -> array.pushArray(value.toReactArray())
138
+ is Boolean -> array.pushBoolean(value)
139
+ is Int -> array.pushInt(value)
140
+ is Double -> array.pushDouble(value)
141
+ is String -> array.pushString(value)
142
+ null -> array.pushNull()
143
+ else -> array.pushString(value.toString())
144
+ }
145
+ } catch (_: JSONException) {
146
+ }
147
+
148
+ return@fold array
149
+ }