@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.
- package/README.md +11 -159
- package/ReactNativePortals.podspec +2 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/io/ionic/portals/reactnative/PortalView.kt +132 -0
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeLiveUpdatesModule.kt +10 -28
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalManager.kt +149 -0
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +73 -232
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPackage.kt +0 -1
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPubSub.kt +45 -0
- package/ios/LiveUpdate+Dict.swift +30 -0
- package/ios/LiveUpdateManager+Async.swift +69 -0
- package/ios/LiveUpdateManagerError+Dict.swift +19 -0
- package/ios/Podfile +2 -1
- package/ios/Podfile.lock +8 -7
- package/ios/Portal+Dict.swift +35 -0
- package/ios/PortalManager.m +10 -4
- package/ios/PortalView.m +1 -1
- package/ios/PortalView.swift +67 -0
- package/ios/PortalsConfig.swift +92 -0
- package/ios/PortalsPubSub.m +3 -3
- package/ios/PortalsPubSub.swift +47 -0
- package/ios/PortalsReactNative.swift +104 -0
- package/ios/ReactNativePortals.xcodeproj/project.pbxproj +32 -8
- package/lib/commonjs/PortalView.android.js +2 -10
- package/lib/commonjs/PortalView.android.js.map +1 -1
- package/lib/commonjs/PortalView.js +0 -6
- package/lib/commonjs/PortalView.js.map +1 -1
- package/lib/commonjs/index.js +54 -38
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/PortalView.android.js +2 -5
- package/lib/module/PortalView.android.js.map +1 -1
- package/lib/module/PortalView.js +0 -2
- package/lib/module/PortalView.js.map +1 -1
- package/lib/module/index.js +52 -19
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +27 -2
- package/package.json +3 -1
- package/src/index.ts +47 -12
- package/ios/LiveUpdatesManager.m +0 -16
- 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
|
|
6
|
+
⚡️ A supercharged native Web View for React Native ⚡️
|
|
7
7
|
</div>
|
|
8
8
|
<br />
|
|
9
9
|
<p align="center">
|
|
10
|
-
<
|
|
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
|
|
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
|
-
|
|
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 [
|
|
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)
|
|
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
|
-
|
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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 =
|
|
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
|
+
}
|