@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.
Files changed (38) hide show
  1. package/README.md +2 -2
  2. package/ReactNativePortals.podspec +2 -2
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/io/ionic/portals/reactnative/PortalView.kt +14 -16
  5. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeLiveUpdatesModule.kt +57 -74
  6. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalManager.kt +84 -21
  7. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +19 -22
  8. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPackage.kt +1 -0
  9. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeWebVitalsModule.kt +52 -0
  10. package/ios/AssetMap+Dict.swift +28 -0
  11. package/ios/ConcurrentDictionary.swift +45 -0
  12. package/ios/LiveUpdateManager+Async.swift +7 -7
  13. package/ios/Podfile +2 -2
  14. package/ios/Podfile.lock +13 -13
  15. package/ios/Portal.swift +108 -0
  16. package/ios/PortalManager.m +1 -1
  17. package/ios/PortalView.swift +1 -1
  18. package/ios/PortalWebVitals.m +15 -0
  19. package/ios/PortalsConfig.swift +18 -7
  20. package/ios/PortalsPubSub.swift +1 -0
  21. package/ios/PortalsReactNative.swift +1 -1
  22. package/ios/ReactNativePortals.xcodeproj/project.pbxproj +24 -4
  23. package/ios/SyncResult+Dict.swift +35 -0
  24. package/ios/WebVitals.swift +35 -0
  25. package/lib/commonjs/PortalView.android.js.map +1 -1
  26. package/lib/commonjs/PortalView.js.map +1 -1
  27. package/lib/commonjs/index.js +63 -3
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/module/PortalView.android.js.map +1 -1
  30. package/lib/module/PortalView.js.map +1 -1
  31. package/lib/module/index.js +55 -2
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/typescript/PortalView.android.d.ts +2 -2
  34. package/lib/typescript/PortalView.d.ts +2 -2
  35. package/lib/typescript/index.d.ts +48 -4
  36. package/package.json +1 -1
  37. package/src/index.ts +128 -5
  38. 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?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/react-native-ionic-portals/Verify?style=flat-square" /></a>
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/twitter/follow/ionicframework" /></a>
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.0'
20
- s.dependency 'IonicLiveUpdates', '~> 0.2.0'
19
+ s.dependency 'IonicPortals', '~> 0.7.3'
20
+ s.dependency 'IonicLiveUpdates', '~> 0.4.0'
21
21
  end
@@ -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.2.+"
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.PortalManager
16
+ import io.ionic.portals.WebVitals
19
17
 
20
18
  private data class PortalViewState(
21
19
  var fragment: PortalFragment?,
22
- var portal: 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 (val viewState = fragmentMap[viewGroup.id]) {
32
+ when (fragmentMap[viewGroup.id]) {
35
33
  null -> fragmentMap[viewGroup.id] = PortalViewState(
36
34
  fragment = null,
37
- portal = PortalManager.getPortal(name),
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
- val configBuilder = CapConfig.Builder(context)
75
- .setInitialFocus(false)
76
-
77
- RNPortalManager.indexMap[portal.name]
78
- ?.let(configBuilder::setStartPath)
79
-
80
- portalFragment.setConfig(configBuilder.create())
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.network.FailStep
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
- liveUpdate: LiveUpdate,
58
- failStep: FailStep?,
59
- failMsg: String?
60
- ) {
61
- val map = callbackToMap(liveUpdate, failStep, failMsg)
62
- promise.resolve(map)
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
- liveUpdate: LiveUpdate,
95
- failStep: FailStep?,
96
- failMsg: String?
97
- ) {
98
- trySend(callbackToResult(liveUpdate, failStep, failMsg))
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.liveUpdates.add(result.liveUpdate)
113
- is LiveUpdateError -> syncResults.errors.add(result)
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 LiveUpdateError(val appId: String, val failStep: String, val failMsg: String) :
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 liveUpdates: MutableList<LiveUpdate>,
149
- val errors: MutableList<LiveUpdateError>
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 liveUpdatesArray = WritableNativeArray()
156
- liveUpdates.forEach { liveUpdatesArray.pushMap(it.toReadableMap()) }
157
- map.putArray("liveUpdates", liveUpdatesArray)
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.asReadableMap) }
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.Portal
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
- internal val indexMap: MutableMap<String, String> = mutableMapOf()
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
- fun addPortal(map: ReadableMap): Portal? {
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("androidPlugins")
39
- ?.toArrayList()
40
- ?.mapNotNull { it as? String }
41
- ?.map {
42
- Class.forName(it)
43
- .asSubclass(Plugin::class.java)
44
- }
45
- ?.forEach(portalBuilder::addPlugin)
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
- manager.addPortal(portal)
67
- return portal
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): Portal = manager.getPortal(name)
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
- return (0 until size).fold(WritableNativeArray()) { array, index ->
94
- when (val value = get(index)) {
95
- is String -> array.pushString(value)
96
- is Boolean -> array.pushBoolean(value)
97
- is Int -> array.pushInt(value)
98
- is Double -> array.pushDouble(value)
99
- is Map<*, *> -> array.pushMap(value.toReadableMap())
100
- is List<*> -> array.pushArray(value.toReadableArray())
101
- null -> array.pushNull()
102
- else -> array.pushString(value.toString())
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
- @Suppress("UNCHECKED_CAST")
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
- RNPortalManager.indexMap[name]?.let { map.putString("index", it) }
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
  }
@@ -11,6 +11,7 @@ class ReactNativePortalsPackage : ReactPackage {
11
11
  return listOf(
12
12
  PortalManagerModule(reactContext),
13
13
  PortalsPubSubModule(reactContext),
14
+ PortalWebVitalsModule(reactContext)
14
15
  )
15
16
  }
16
17
 
@@ -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
+ }