@ionic/portals-react-native 0.3.0 → 0.4.1
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 +2 -2
- package/ReactNativePortals.podspec +2 -2
- package/android/build.gradle +1 -1
- package/android/src/main/java/io/ionic/portals/reactnative/PortalView.kt +14 -16
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeLiveUpdatesModule.kt +57 -74
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalManager.kt +84 -21
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +19 -22
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPackage.kt +1 -0
- package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeWebVitalsModule.kt +52 -0
- package/ios/AssetMap+Dict.swift +28 -0
- package/ios/ConcurrentDictionary.swift +45 -0
- package/ios/LiveUpdateManager+Async.swift +7 -7
- package/ios/Podfile +2 -2
- package/ios/Podfile.lock +13 -13
- package/ios/Portal.swift +108 -0
- package/ios/PortalManager.m +1 -1
- package/ios/PortalView.swift +1 -1
- package/ios/PortalWebVitals.m +15 -0
- package/ios/PortalsConfig.swift +18 -7
- package/ios/PortalsPubSub.swift +1 -0
- package/ios/PortalsReactNative.swift +1 -1
- package/ios/ReactNativePortals.xcodeproj/project.pbxproj +24 -4
- package/ios/SyncResult+Dict.swift +35 -0
- package/ios/WebVitals.swift +35 -0
- package/lib/commonjs/PortalView.android.js.map +1 -1
- package/lib/commonjs/PortalView.js.map +1 -1
- package/lib/commonjs/index.js +63 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/PortalView.android.js.map +1 -1
- package/lib/module/PortalView.js.map +1 -1
- package/lib/module/index.js +55 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/PortalView.android.d.ts +2 -2
- package/lib/typescript/PortalView.d.ts +2 -2
- package/lib/typescript/index.d.ts +48 -4
- package/package.json +1 -1
- package/src/index.ts +128 -5
- package/ios/Portal+Dict.swift +0 -35
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
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
13
|
</p>
|
|
14
14
|
<p align="center">
|
|
15
|
-
<a href="https://github.com/ionic-team/ionic-portals/actions
|
|
15
|
+
<a href="https://github.com/ionic-team/react-native-ionic-portals/actions/workflows/verify.yml"><img src="https://img.shields.io/github/actions/workflow/status/ionic-team/react-native-ionic-portals/verify.yml?branch=main&style=flat-square" /></a>
|
|
16
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>
|
|
17
17
|
</p>
|
|
18
18
|
<p align="center">
|
|
19
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>
|
|
20
|
-
<a href="https://twitter.com/ionicframework"><img src="https://img.shields.io/
|
|
20
|
+
<a href="https://twitter.com/ionicframework"><img src="https://img.shields.io/badge/follow-%40ionicframework-1DA1F2?logo=twitter" alt="Follow @ionicframework"></a>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -16,6 +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.7.
|
|
20
|
-
s.dependency 'IonicLiveUpdates', '~> 0.
|
|
19
|
+
s.dependency 'IonicPortals', '~> 0.7.3'
|
|
20
|
+
s.dependency 'IonicLiveUpdates', '~> 0.4.0'
|
|
21
21
|
end
|
package/android/build.gradle
CHANGED
|
@@ -125,7 +125,7 @@ dependencies {
|
|
|
125
125
|
//noinspection GradleDynamicVersion
|
|
126
126
|
api "io.ionic:portals:0.7.+"
|
|
127
127
|
//noinspection GradleDynamicVersion
|
|
128
|
-
api "io.ionic:liveupdates:0.
|
|
128
|
+
api "io.ionic:liveupdates:0.4.+"
|
|
129
129
|
|
|
130
130
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3"
|
|
131
131
|
}
|
|
@@ -12,14 +12,12 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
12
12
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
13
13
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
14
14
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
15
|
-
import com.getcapacitor.CapConfig
|
|
16
|
-
import io.ionic.portals.Portal
|
|
17
15
|
import io.ionic.portals.PortalFragment
|
|
18
|
-
import io.ionic.portals.
|
|
16
|
+
import io.ionic.portals.WebVitals
|
|
19
17
|
|
|
20
18
|
private data class PortalViewState(
|
|
21
19
|
var fragment: PortalFragment?,
|
|
22
|
-
var portal:
|
|
20
|
+
var portal: RNPortal?,
|
|
23
21
|
var initialContext: HashMap<String, Any>?
|
|
24
22
|
)
|
|
25
23
|
|
|
@@ -31,10 +29,10 @@ internal class PortalViewManager(private val context: ReactApplicationContext) :
|
|
|
31
29
|
@ReactProp(name = "portal")
|
|
32
30
|
fun setPortal(viewGroup: ViewGroup, portal: ReadableMap) {
|
|
33
31
|
val name = portal.getString("name") ?: return
|
|
34
|
-
when (
|
|
32
|
+
when (fragmentMap[viewGroup.id]) {
|
|
35
33
|
null -> fragmentMap[viewGroup.id] = PortalViewState(
|
|
36
34
|
fragment = null,
|
|
37
|
-
portal =
|
|
35
|
+
portal = RNPortalManager.getPortal(name),
|
|
38
36
|
initialContext = portal.getMap("initialContext")?.toHashMap()
|
|
39
37
|
)
|
|
40
38
|
}
|
|
@@ -69,18 +67,18 @@ internal class PortalViewManager(private val context: ReactApplicationContext) :
|
|
|
69
67
|
val parentView = root.findViewById<ViewGroup>(viewId)
|
|
70
68
|
setupLayout(parentView)
|
|
71
69
|
|
|
72
|
-
val portalFragment = PortalFragment(portal)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
val portalFragment = PortalFragment(portal.portal)
|
|
71
|
+
if (portal.onFCP != null || portal.onFID != null || portal.onTTFB != null) {
|
|
72
|
+
portalFragment.webVitalsCallback = { metric, duration ->
|
|
73
|
+
when (metric) {
|
|
74
|
+
WebVitals.Metric.FCP -> portal.onFCP?.let { it(duration) }
|
|
75
|
+
WebVitals.Metric.FID -> portal.onFID?.let { it(duration) }
|
|
76
|
+
WebVitals.Metric.TTFB -> portal.onTTFB?.let { it(duration) }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
81
80
|
|
|
82
81
|
viewState.initialContext?.let(portalFragment::setInitialContext)
|
|
83
|
-
|
|
84
82
|
viewState.fragment = portalFragment
|
|
85
83
|
|
|
86
84
|
val fragmentActivity = context.currentActivity as? FragmentActivity ?: return
|
|
@@ -4,7 +4,9 @@ import android.content.Context
|
|
|
4
4
|
import com.facebook.react.bridge.*
|
|
5
5
|
import io.ionic.liveupdates.LiveUpdate
|
|
6
6
|
import io.ionic.liveupdates.LiveUpdateManager
|
|
7
|
-
import io.ionic.liveupdates.
|
|
7
|
+
import io.ionic.liveupdates.data.model.FailResult
|
|
8
|
+
import io.ionic.liveupdates.data.model.Snapshot
|
|
9
|
+
import io.ionic.liveupdates.data.model.SyncResult
|
|
8
10
|
import io.ionic.liveupdates.network.SyncCallback
|
|
9
11
|
import kotlinx.coroutines.*
|
|
10
12
|
import kotlinx.coroutines.channels.awaitClose
|
|
@@ -18,48 +20,18 @@ internal object LiveUpdatesModule {
|
|
|
18
20
|
.asCoroutineDispatcher()
|
|
19
21
|
)
|
|
20
22
|
|
|
21
|
-
private fun callbackToMap(
|
|
22
|
-
liveUpdate: LiveUpdate,
|
|
23
|
-
failStep: FailStep?,
|
|
24
|
-
failMsg: String?
|
|
25
|
-
): ReadableMap =
|
|
26
|
-
if (failStep != null) {
|
|
27
|
-
val map = WritableNativeMap()
|
|
28
|
-
map.putString("appId", liveUpdate.appId)
|
|
29
|
-
map.putString("failStep", failStep.name)
|
|
30
|
-
map.putString("message", failMsg ?: "Sync failed for unknown reason")
|
|
31
|
-
map
|
|
32
|
-
} else {
|
|
33
|
-
liveUpdate.toReadableMap()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private fun callbackToResult(
|
|
37
|
-
liveUpdate: LiveUpdate,
|
|
38
|
-
failStep: FailStep?,
|
|
39
|
-
failMsg: String?
|
|
40
|
-
): LiveUpdateResult =
|
|
41
|
-
if (failStep != null) {
|
|
42
|
-
LiveUpdateError(
|
|
43
|
-
appId = liveUpdate.appId,
|
|
44
|
-
failStep = failStep.name,
|
|
45
|
-
failMsg = failMsg ?: "Sync failed for unknown reason"
|
|
46
|
-
)
|
|
47
|
-
} else {
|
|
48
|
-
LiveUpdateSuccess(liveUpdate)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
23
|
fun syncOne(appId: String, context: Context, promise: Promise) {
|
|
52
24
|
LiveUpdateManager.sync(
|
|
53
25
|
context = context,
|
|
54
26
|
appId = appId,
|
|
55
27
|
callback = object : SyncCallback {
|
|
56
|
-
override fun onAppComplete(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
promise.resolve(
|
|
28
|
+
override fun onAppComplete(syncResult: SyncResult) {
|
|
29
|
+
promise.resolve(syncResult.toReadableMap())
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun onAppComplete(failResult: FailResult) {
|
|
34
|
+
promise.resolve(failResult.toReadableMap())
|
|
63
35
|
}
|
|
64
36
|
|
|
65
37
|
override fun onSyncComplete() {
|
|
@@ -82,7 +54,6 @@ internal object LiveUpdatesModule {
|
|
|
82
54
|
sync(emptyArray(), context, promise)
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
@OptIn(ExperimentalCoroutinesApi::class)
|
|
86
57
|
private fun sync(appIds: Array<String>, context: Context, promise: Promise) {
|
|
87
58
|
liveUpdateScope.launch {
|
|
88
59
|
val results = callbackFlow {
|
|
@@ -90,12 +61,12 @@ internal object LiveUpdatesModule {
|
|
|
90
61
|
context = context,
|
|
91
62
|
appIds = appIds,
|
|
92
63
|
callback = object : SyncCallback {
|
|
93
|
-
override fun onAppComplete(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
) {
|
|
98
|
-
trySend(
|
|
64
|
+
override fun onAppComplete(syncResult: SyncResult) {
|
|
65
|
+
trySend(LiveUpdateSuccess(syncResult))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun onAppComplete(failResult: FailResult) {
|
|
69
|
+
trySend(LiveUpdateFailure(failResult))
|
|
99
70
|
}
|
|
100
71
|
|
|
101
72
|
override fun onSyncComplete() {
|
|
@@ -109,8 +80,8 @@ internal object LiveUpdatesModule {
|
|
|
109
80
|
|
|
110
81
|
val syncResults = results.fold(SyncResults.empty()) { syncResults, result ->
|
|
111
82
|
when (result) {
|
|
112
|
-
is LiveUpdateSuccess -> syncResults.
|
|
113
|
-
is
|
|
83
|
+
is LiveUpdateSuccess -> syncResults.results.add(result)
|
|
84
|
+
is LiveUpdateFailure -> syncResults.errors.add(result)
|
|
114
85
|
}
|
|
115
86
|
|
|
116
87
|
return@fold syncResults
|
|
@@ -121,43 +92,24 @@ internal object LiveUpdatesModule {
|
|
|
121
92
|
}
|
|
122
93
|
}
|
|
123
94
|
|
|
124
|
-
fun LiveUpdate.toReadableMap(): ReadableMap {
|
|
125
|
-
val map = WritableNativeMap()
|
|
126
|
-
map.putString("appId", appId)
|
|
127
|
-
map.putString("channel", channelName)
|
|
128
|
-
return map
|
|
129
|
-
}
|
|
130
|
-
|
|
131
95
|
private sealed class LiveUpdateResult
|
|
132
|
-
|
|
133
|
-
private data class
|
|
134
|
-
LiveUpdateResult() {
|
|
135
|
-
val asReadableMap: ReadableMap
|
|
136
|
-
get() {
|
|
137
|
-
val map = WritableNativeMap()
|
|
138
|
-
map.putString("appId", appId)
|
|
139
|
-
map.putString("failStep", failStep)
|
|
140
|
-
map.putString("message", failMsg)
|
|
141
|
-
return map
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private data class LiveUpdateSuccess(val liveUpdate: LiveUpdate) : LiveUpdateResult()
|
|
96
|
+
private data class LiveUpdateFailure(val failure: FailResult) : LiveUpdateResult()
|
|
97
|
+
private data class LiveUpdateSuccess(val success: SyncResult): LiveUpdateResult()
|
|
146
98
|
|
|
147
99
|
private data class SyncResults(
|
|
148
|
-
val
|
|
149
|
-
val errors: MutableList<
|
|
100
|
+
val results: MutableList<LiveUpdateSuccess>,
|
|
101
|
+
val errors: MutableList<LiveUpdateFailure>
|
|
150
102
|
) {
|
|
151
103
|
val asReadableMap: ReadableMap
|
|
152
104
|
get() {
|
|
153
105
|
val map = WritableNativeMap()
|
|
154
106
|
|
|
155
|
-
val
|
|
156
|
-
|
|
157
|
-
map.putArray("
|
|
107
|
+
val syncResultsArray = WritableNativeArray()
|
|
108
|
+
results.forEach { syncResultsArray.pushMap(it.success.toReadableMap()) }
|
|
109
|
+
map.putArray("results", syncResultsArray)
|
|
158
110
|
|
|
159
111
|
val errorsArray = WritableNativeArray()
|
|
160
|
-
errors.forEach { errorsArray.pushMap(it.
|
|
112
|
+
errors.forEach { errorsArray.pushMap(it.failure.toReadableMap()) }
|
|
161
113
|
map.putArray("errors", errorsArray)
|
|
162
114
|
|
|
163
115
|
return map
|
|
@@ -167,3 +119,34 @@ private data class SyncResults(
|
|
|
167
119
|
fun empty() = SyncResults(mutableListOf(), mutableListOf())
|
|
168
120
|
}
|
|
169
121
|
}
|
|
122
|
+
|
|
123
|
+
fun LiveUpdate.toReadableMap(): ReadableMap {
|
|
124
|
+
val map = WritableNativeMap()
|
|
125
|
+
map.putString("appId", appId)
|
|
126
|
+
map.putString("channel", channelName)
|
|
127
|
+
return map
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fun Snapshot.toReadableMap(): ReadableMap {
|
|
131
|
+
val map = WritableNativeMap()
|
|
132
|
+
map.putString("id", id)
|
|
133
|
+
map.putString("buildId", buildId)
|
|
134
|
+
return map
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fun SyncResult.toReadableMap(): ReadableMap {
|
|
138
|
+
val map = WritableNativeMap()
|
|
139
|
+
map.putMap("liveUpdate", liveUpdate.toReadableMap())
|
|
140
|
+
map.putMap("snapshot", snapshot?.let(Snapshot::toReadableMap))
|
|
141
|
+
map.putString("source", source.name.lowercase())
|
|
142
|
+
map.putBoolean("activeApplicationPathChanged", latestAppDirectoryChanged)
|
|
143
|
+
return map
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun FailResult.toReadableMap(): ReadableMap {
|
|
147
|
+
val map = WritableNativeMap()
|
|
148
|
+
map.putString("appId", liveUpdate.appId)
|
|
149
|
+
map.putString("failStep", failStep.name)
|
|
150
|
+
map.putString("message", failMsg ?: "Sync failed for unknown reason.")
|
|
151
|
+
return map
|
|
152
|
+
}
|
|
@@ -4,24 +4,52 @@ import com.facebook.react.bridge.*
|
|
|
4
4
|
import com.getcapacitor.Plugin
|
|
5
5
|
import io.ionic.liveupdates.LiveUpdate
|
|
6
6
|
import io.ionic.liveupdates.LiveUpdateManager
|
|
7
|
-
import io.ionic.portals
|
|
8
|
-
import io.ionic.portals.PortalBuilder
|
|
9
|
-
import io.ionic.portals.PortalManager
|
|
10
|
-
import io.ionic.portals.PortalsPlugin
|
|
7
|
+
import io.ionic.portals.*
|
|
11
8
|
import org.json.JSONArray
|
|
12
9
|
import org.json.JSONException
|
|
13
10
|
import org.json.JSONObject
|
|
14
11
|
import java.io.BufferedReader
|
|
15
12
|
import java.io.IOException
|
|
13
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
internal data class RNPortal(
|
|
17
|
+
val portal: Portal,
|
|
18
|
+
val index: String?,
|
|
19
|
+
val plugins: List<PortalPlugin>,
|
|
20
|
+
var onFCP: ((Long) -> Unit)? = null,
|
|
21
|
+
var onTTFB: ((Long) -> Unit)? = null,
|
|
22
|
+
var onFID: ((Long) -> Unit)? = null
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
internal data class PortalPlugin(val androidClassPath: String, val iosClassName: String) {
|
|
26
|
+
companion object {
|
|
27
|
+
const val iosClassNameKey = "iosClassName"
|
|
28
|
+
const val androidClassPathKey = "androidClassPath"
|
|
29
|
+
fun fromReadableMap(map: ReadableMap): PortalPlugin? {
|
|
30
|
+
val androidClassPath = map.getString(androidClassPathKey) ?: return null
|
|
31
|
+
val iosClassName = map.getString(iosClassNameKey) ?: return null
|
|
32
|
+
return PortalPlugin(androidClassPath, iosClassName)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun toReadableMap(): ReadableMap {
|
|
37
|
+
val map = WritableNativeMap()
|
|
38
|
+
map.putString(iosClassNameKey, iosClassName)
|
|
39
|
+
map.putString(androidClassPathKey, androidClassPath)
|
|
40
|
+
return map
|
|
41
|
+
}
|
|
42
|
+
}
|
|
16
43
|
|
|
17
44
|
internal object RNPortalManager {
|
|
18
45
|
private val manager = PortalManager
|
|
19
|
-
|
|
46
|
+
private val portals: ConcurrentHashMap<String, RNPortal> = ConcurrentHashMap()
|
|
20
47
|
private lateinit var reactApplicationContext: ReactApplicationContext
|
|
21
48
|
private var usesSecureLiveUpdates = false
|
|
22
49
|
|
|
23
50
|
fun register(key: String) = manager.register(key)
|
|
24
|
-
|
|
51
|
+
|
|
52
|
+
fun addPortal(map: ReadableMap): RNPortal? {
|
|
25
53
|
val name = map.getString("name") ?: return null
|
|
26
54
|
val portalBuilder = PortalBuilder(name)
|
|
27
55
|
|
|
@@ -32,28 +60,56 @@ internal object RNPortalManager {
|
|
|
32
60
|
?.toHashMap()
|
|
33
61
|
?.let(portalBuilder::setInitialContext)
|
|
34
62
|
|
|
35
|
-
map.getString("index")
|
|
36
|
-
?.let { indexMap[name] = "/$it" }
|
|
37
63
|
|
|
38
|
-
map.getArray("
|
|
39
|
-
?.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
val plugins: List<PortalPlugin> = map.getArray("plugins")
|
|
65
|
+
?.let { rnArray ->
|
|
66
|
+
val list = mutableListOf<PortalPlugin>()
|
|
67
|
+
for (idx in 0 until rnArray.size()) {
|
|
68
|
+
rnArray.getMap(idx)
|
|
69
|
+
?.let(PortalPlugin.Companion::fromReadableMap)
|
|
70
|
+
?.let(list::add)
|
|
71
|
+
}
|
|
72
|
+
return@let list
|
|
73
|
+
} ?: listOf()
|
|
74
|
+
|
|
75
|
+
plugins.map {
|
|
76
|
+
Class.forName(it.androidClassPath)
|
|
77
|
+
.asSubclass(Plugin::class.java)
|
|
78
|
+
}
|
|
79
|
+
.forEach(portalBuilder::addPlugin)
|
|
80
|
+
|
|
81
|
+
val assetMaps: List<AssetMap> = map.getArray("assetMaps")
|
|
82
|
+
?.let { rnArray ->
|
|
83
|
+
val list = mutableListOf<AssetMap>()
|
|
84
|
+
|
|
85
|
+
for (idx in 0 until rnArray.size()) {
|
|
86
|
+
rnArray.getMap(idx)
|
|
87
|
+
?.let assetMap@{ map ->
|
|
88
|
+
val name = map.getString("name") ?: return@assetMap null
|
|
89
|
+
AssetMap(
|
|
90
|
+
name = name,
|
|
91
|
+
virtualPath = map.getString("virtualPath") ?: "/$name",
|
|
92
|
+
path = map.getString("startDir") ?: ""
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
?.let(list::add)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return@let list
|
|
99
|
+
} ?: listOf()
|
|
100
|
+
|
|
101
|
+
assetMaps.forEach(portalBuilder::addAssetMap)
|
|
46
102
|
|
|
47
103
|
map.getMap("liveUpdate")
|
|
48
104
|
?.let { readableMap ->
|
|
49
105
|
val appId = readableMap.getString("appId") ?: return@let null
|
|
50
106
|
val channel = readableMap.getString("channel") ?: return@let null
|
|
51
107
|
val syncOnAdd = readableMap.getBoolean("syncOnAdd")
|
|
52
|
-
Pair(LiveUpdate(appId, channel, usesSecureLiveUpdates), syncOnAdd)
|
|
108
|
+
Pair(LiveUpdate(appId, channel, RNPortalManager.usesSecureLiveUpdates), syncOnAdd)
|
|
53
109
|
}
|
|
54
110
|
?.let { (liveUpdate, updateOnAppLoad) ->
|
|
55
111
|
portalBuilder.setLiveUpdateConfig(
|
|
56
|
-
context = reactApplicationContext,
|
|
112
|
+
context = RNPortalManager.reactApplicationContext,
|
|
57
113
|
liveUpdateConfig = liveUpdate,
|
|
58
114
|
updateOnAppLoad = updateOnAppLoad
|
|
59
115
|
)
|
|
@@ -63,11 +119,18 @@ internal object RNPortalManager {
|
|
|
63
119
|
.addPlugin(PortalsPlugin::class.java)
|
|
64
120
|
.create()
|
|
65
121
|
|
|
66
|
-
|
|
67
|
-
|
|
122
|
+
val rnPortal = RNPortal(
|
|
123
|
+
portal = portal,
|
|
124
|
+
index = map.getString("index"),
|
|
125
|
+
plugins = plugins
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
portals[rnPortal.portal.name] = rnPortal
|
|
129
|
+
return rnPortal
|
|
68
130
|
}
|
|
69
131
|
|
|
70
|
-
fun getPortal(name: String):
|
|
132
|
+
fun getPortal(name: String): RNPortal = portals[name]
|
|
133
|
+
?: throw IllegalStateException("Portal with portalId $name not found in RNPortalManager")
|
|
71
134
|
|
|
72
135
|
fun enableSecureLiveUpdates(keyPath: String) {
|
|
73
136
|
LiveUpdateManager.secureLiveUpdatePEM = keyPath
|
|
@@ -89,35 +89,32 @@ fun Map<*, *>.toReadableMap(): ReadableMap {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
fun List<*>.toReadableArray(): ReadableArray {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return@fold array
|
|
92
|
+
fun List<*>.toReadableArray(): ReadableArray = fold(WritableNativeArray()) { array, value ->
|
|
93
|
+
when (value) {
|
|
94
|
+
is String -> array.pushString(value)
|
|
95
|
+
is Boolean -> array.pushBoolean(value)
|
|
96
|
+
is Int -> array.pushInt(value)
|
|
97
|
+
is Double -> array.pushDouble(value)
|
|
98
|
+
is PortalPlugin -> array.pushMap(value.toReadableMap())
|
|
99
|
+
is Map<*, *> -> array.pushMap(value.toReadableMap())
|
|
100
|
+
is List<*> -> array.pushArray(value.toReadableArray())
|
|
101
|
+
null -> array.pushNull()
|
|
102
|
+
else -> array.pushString(value.toString())
|
|
106
103
|
}
|
|
104
|
+
return@fold array
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
fun Portal.toReadableMap(): ReadableMap {
|
|
107
|
+
internal fun RNPortal.toReadableMap(): ReadableMap {
|
|
111
108
|
val map = WritableNativeMap()
|
|
112
|
-
map.putString("name", name)
|
|
113
|
-
map.putString("startDir", startDir)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
initialContext?.let {
|
|
109
|
+
map.putString("name", portal.name)
|
|
110
|
+
map.putString("startDir", portal.startDir)
|
|
111
|
+
map.putArray("plugins", plugins.toReadableArray())
|
|
112
|
+
index?.let { map.putString("index", it) }
|
|
113
|
+
portal.initialContext?.let {
|
|
117
114
|
if (it is Map<*, *>) map.putMap("initialContext", it.toReadableMap())
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
liveUpdateConfig?.let { map.putMap("liveUpdate", it.toReadableMap()) }
|
|
117
|
+
portal.liveUpdateConfig?.let { map.putMap("liveUpdate", it.toReadableMap()) }
|
|
121
118
|
|
|
122
119
|
return map
|
|
123
120
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package io.ionic.portals.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
8
|
+
|
|
9
|
+
internal class PortalWebVitalsModule(reactContext: ReactApplicationContext): ReactContextBaseJavaModule(reactContext) {
|
|
10
|
+
override fun getName() = "IONPortalsWebVitals"
|
|
11
|
+
|
|
12
|
+
@ReactMethod
|
|
13
|
+
fun registerOnFirstContentfulPaint(portalName: String, promise: Promise) {
|
|
14
|
+
RNPortalManager.getPortal(portalName).onFCP = { duration ->
|
|
15
|
+
reactApplicationContext
|
|
16
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
17
|
+
.emit("vitals:fcp", mapOf("duration" to duration, "portalName" to portalName).toReadableMap())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
promise.resolve(null)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@ReactMethod
|
|
24
|
+
fun registerOnFirstInputDelay(portalName: String, promise: Promise) {
|
|
25
|
+
RNPortalManager.getPortal(portalName).onFID = { duration ->
|
|
26
|
+
reactApplicationContext
|
|
27
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
28
|
+
.emit("vitals:fid", mapOf("duration" to duration, "portalName" to portalName).toReadableMap())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
promise.resolve(null)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@ReactMethod
|
|
35
|
+
fun registerOnTimeToFirstByte(portalName: String, promise: Promise) {
|
|
36
|
+
RNPortalManager.getPortal(portalName).onTTFB = { duration ->
|
|
37
|
+
reactApplicationContext
|
|
38
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
39
|
+
.emit("vitals:ttfb", mapOf("duration" to duration, "portalName" to portalName).toReadableMap())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
promise.resolve(null)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@ReactMethod
|
|
46
|
+
fun addListener(eventName: String) {
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@ReactMethod
|
|
50
|
+
fun removeListeners(count: Int) {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AssetMap+Dict.swift
|
|
3
|
+
// ReactNativePortals
|
|
4
|
+
//
|
|
5
|
+
// Created by Steven Sherry on 3/29/23.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import IonicPortals
|
|
10
|
+
|
|
11
|
+
extension AssetMap {
|
|
12
|
+
init?(_ dict: [String: Any]) {
|
|
13
|
+
guard let name = dict["name"] as? String else { return nil }
|
|
14
|
+
self.init(
|
|
15
|
+
name: name,
|
|
16
|
+
virtualPath: dict["virtualPath"] as? String,
|
|
17
|
+
startDir: dict["startDir"] as? String ?? ""
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var dict: [String: Any] {
|
|
22
|
+
return [
|
|
23
|
+
"name": name,
|
|
24
|
+
"virtualPath": virtualPath,
|
|
25
|
+
"startDir": startDir
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ConcurrentHashMap.swift
|
|
3
|
+
// ReactNativePortals
|
|
4
|
+
//
|
|
5
|
+
// Created by Steven Sherry on 3/31/23.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
class ConcurrentDictionary<Key: Hashable, Value> {
|
|
12
|
+
private var _dict: Dictionary<Key, Value>
|
|
13
|
+
var dict: Dictionary<Key, Value> {
|
|
14
|
+
queue.sync { _dict }
|
|
15
|
+
}
|
|
16
|
+
private let queue: DispatchQueue
|
|
17
|
+
|
|
18
|
+
init(label: String, dict: [Key: Value] = [:]) {
|
|
19
|
+
queue = DispatchQueue(label: label, qos: .userInitiated, attributes: .concurrent)
|
|
20
|
+
self._dict = dict
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
subscript(_ key: Key) -> Value? {
|
|
24
|
+
get { queue.sync { _dict[key] } }
|
|
25
|
+
set {
|
|
26
|
+
queue.async(flags: .barrier) { [weak self] in
|
|
27
|
+
self?._dict[key] = newValue
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
extension ConcurrentDictionary: Collection {
|
|
34
|
+
var startIndex: Dictionary<Key, Value>.Index { dict.startIndex }
|
|
35
|
+
var endIndex: Dictionary<Key, Value>.Index { dict.endIndex }
|
|
36
|
+
func index(after i: Dictionary<Key, Value>.Index) -> Dictionary<Key, Value>.Index {
|
|
37
|
+
dict.index(after: i)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
subscript(position: Dictionary<Key, Value>.Index) -> Dictionary<Key, Value>.Element {
|
|
41
|
+
get {
|
|
42
|
+
dict[position]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|