@ionic/portals-react-native 0.5.1 → 0.6.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 (48) hide show
  1. package/ReactNativePortals.podspec +39 -14
  2. package/android/build.gradle +78 -108
  3. package/android/gradle.properties +5 -3
  4. package/android/src/main/java/io/ionic/portals/reactnative/PortalView.kt +11 -8
  5. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalManager.kt +37 -16
  6. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +2 -3
  7. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeWebVitalsModule.kt +11 -19
  8. package/ios/IonicPortals+Codable.swift +77 -0
  9. package/ios/LiveUpdateManager+Async.swift +16 -46
  10. package/ios/Podfile +29 -4
  11. package/ios/Podfile.lock +478 -241
  12. package/ios/Portal.swift +50 -54
  13. package/ios/PortalView.swift +26 -8
  14. package/ios/PortalsConfig.swift +1 -88
  15. package/ios/PortalsReactNative-Bridging-Header.h +2 -0
  16. package/ios/PortalsReactNative.swift +41 -36
  17. package/ios/SyncResult+SyncError+Encodable.swift +55 -0
  18. package/ios/WebVitals.swift +19 -26
  19. package/package.json +62 -43
  20. package/src/{PortalView.android.tsx → BasePortalView.android.tsx} +2 -3
  21. package/src/{PortalView.tsx → BasePortalView.tsx} +2 -3
  22. package/src/{index.ts → index.tsx} +77 -39
  23. package/ios/LiveUpdate+Dict.swift +0 -30
  24. package/ios/LiveUpdateManagerError+Dict.swift +0 -19
  25. package/ios/ReactNativePortals.xcodeproj/project.pbxproj +0 -465
  26. package/ios/ReactNativePortals.xcodeproj/xcshareddata/xcschemes/ReactNativePortals.xcscheme +0 -67
  27. package/ios/ReactNativePortals.xcworkspace/contents.xcworkspacedata +0 -10
  28. package/ios/ReactNativePortals.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  29. package/ios/SyncResult+Dict.swift +0 -35
  30. package/lib/commonjs/PortalView.android.js +0 -28
  31. package/lib/commonjs/PortalView.android.js.map +0 -1
  32. package/lib/commonjs/PortalView.js +0 -15
  33. package/lib/commonjs/PortalView.js.map +0 -1
  34. package/lib/commonjs/index.js +0 -200
  35. package/lib/commonjs/index.js.map +0 -1
  36. package/lib/module/PortalView.android.js +0 -20
  37. package/lib/module/PortalView.android.js.map +0 -1
  38. package/lib/module/PortalView.js +0 -8
  39. package/lib/module/PortalView.js.map +0 -1
  40. package/lib/module/index.js +0 -176
  41. package/lib/module/index.js.map +0 -1
  42. package/lib/typescript/PortalView.android.d.ts +0 -4
  43. package/lib/typescript/PortalView.d.ts +0 -4
  44. package/lib/typescript/index.d.ts +0 -181
  45. /package/ios/{PortalManager.m → PortalManager.mm} +0 -0
  46. /package/ios/{PortalView.m → PortalView.mm} +0 -0
  47. /package/ios/{PortalWebVitals.m → PortalWebVitals.mm} +0 -0
  48. /package/ios/{PortalsPubSub.m → PortalsPubSub.mm} +0 -0
package/ios/Portal.swift CHANGED
@@ -12,13 +12,26 @@ import IonicPortals
12
12
 
13
13
  @dynamicMemberLookup
14
14
  struct Portal {
15
- struct Plugin {
15
+ struct Plugin: Codable {
16
16
  var iosClassName: String
17
17
  var androidClassPath: String
18
18
  }
19
19
 
20
20
  var _portal: IonicPortals.Portal
21
21
  var plugins: [Plugin]
22
+ private var webVitals: [String]?
23
+ var usesWebVitals: Bool {
24
+ get {
25
+ webVitals?.contains("fcp") ?? false
26
+ }
27
+ set {
28
+ if newValue {
29
+ webVitals = ["fcp"]
30
+ } else {
31
+ webVitals = nil
32
+ }
33
+ }
34
+ }
22
35
 
23
36
  subscript<T>(dynamicMember keypath: Swift.WritableKeyPath<IonicPortals.Portal, T>) -> T {
24
37
  get { _portal[keyPath: keypath] }
@@ -31,66 +44,49 @@ struct Portal {
31
44
  }
32
45
 
33
46
  extension Portal {
34
- init?(_ dict: [String: Any], _ liveUpdateManager: LiveUpdateManager) {
35
- guard let name = dict["name"] as? String else { return nil }
36
- var plugins: [Portal.Plugin] = []
37
-
38
- if let capPlugins = dict["plugins"] as? Array<[String: String]> {
39
- plugins = capPlugins.compactMap(Portal.Plugin.init)
40
- }
41
-
42
- var assetMaps: [AssetMap] = []
43
-
44
- if let maps = dict["assetMaps"] as? Array<[String: String]> {
45
- assetMaps = maps.compactMap(AssetMap.init)
46
- }
47
-
48
- self._portal = IonicPortals.Portal(
49
- name: name,
50
- startDir: dict["startDir"] as? String,
51
- index: dict["index"] as? String ?? "index.html",
52
- initialContext: JSTypes.coerceDictionaryToJSObject(dict["initialContext"] as? [String: Any]) ?? [:],
53
- assetMaps: assetMaps,
54
- plugins: plugins.toCapPlugin,
55
- liveUpdateManager: liveUpdateManager,
56
- liveUpdateConfig: (dict["liveUpdate"] as? [String: Any]).flatMap(LiveUpdate.init)
57
- )
58
-
59
- self.plugins = plugins
47
+ func encode(to encoder: JSValueEncoder) throws -> JSObject {
48
+ var object = try encoder.encodeJSObject(self)
49
+ object["initialContext"] = _portal.initialContext
50
+ return object
60
51
  }
61
52
 
62
- var dict: [String: Any] {
63
- var base = [
64
- "name": self.name,
65
- "startDir": self.startDir,
66
- "index": self.index,
67
- "initialContext": self.initialContext,
68
- "liveUpdate": self.liveUpdateConfig?.dict as Any
69
- ]
70
-
71
- if !plugins.isEmpty {
72
- base["plugins"] = plugins.map(\.dict)
73
- }
74
-
75
- return base
53
+ static func decode(from jsObject: JSObject, with decoder: JSValueDecoder) throws -> Portal {
54
+ var portal = try decoder.decode(Portal.self, from: jsObject)
55
+ portal.initialContext = jsObject["initialContext"] as? JSObject ?? [:]
56
+ return portal
76
57
  }
77
58
  }
78
59
 
79
- extension Portal.Plugin {
80
- init?(_ dict: [String: String]) {
81
- guard let iosClassName = dict["iosClassName"],
82
- let androidClassPath = dict["androidClassPath"]
83
- else { return nil }
84
-
85
- self.iosClassName = iosClassName
86
- self.androidClassPath = androidClassPath
60
+ extension Portal: Encodable {
61
+ enum CodingKeys: String, CodingKey {
62
+ case name, startDir, plugins, index, initialContext, assetMaps, liveUpdate, webVitals
63
+ }
64
+
65
+ public func encode(to encoder: Encoder) throws {
66
+ var container = encoder.container(keyedBy: CodingKeys.self)
67
+ try container.encode(self.name, forKey: .name)
68
+ try container.encode(self.startDir, forKey: .startDir)
69
+ try container.encode(self.plugins, forKey: .plugins)
70
+ try container.encode(self.index, forKey: .index)
71
+ try container.encode(self.assetMaps, forKey: .assetMaps)
72
+ try container.encode(self.liveUpdateConfig, forKey: .liveUpdate)
73
+ try container.encodeIfPresent(webVitals, forKey: .webVitals)
87
74
  }
75
+ }
88
76
 
89
- var dict: [String: String] {
90
- return [
91
- "iosClassName": iosClassName,
92
- "androidClassPath": androidClassPath
93
- ]
77
+ extension Portal: Decodable {
78
+ public init(from decoder: Decoder) throws {
79
+ let container = try decoder.container(keyedBy: CodingKeys.self)
80
+ let name = try container.decode(String.self, forKey: .name)
81
+ let startDir = try container.decodeIfPresent(String.self, forKey: .startDir) ?? ""
82
+ let plugins = try container.decodeIfPresent([Plugin].self, forKey: .plugins) ?? []
83
+ let index = try container.decodeIfPresent(String.self, forKey: .index) ?? "index.html"
84
+ let assetMaps = try container.decodeIfPresent([AssetMap].self, forKey: .assetMaps) ?? []
85
+ let liveUpdateConfig = try container.decodeIfPresent(LiveUpdate.self, forKey: .liveUpdate)
86
+ let webVitals = try container.decodeIfPresent([String].self, forKey: .webVitals)
87
+
88
+ let portal = IonicPortals.Portal(name: name, startDir: startDir, index: index, assetMaps: assetMaps, plugins: plugins.toCapPlugin, liveUpdateManager: PortalsReactNative.lum, liveUpdateConfig: liveUpdateConfig)
89
+ self.init(_portal: portal, plugins: plugins, webVitals: webVitals)
94
90
  }
95
91
  }
96
92
 
@@ -23,10 +23,7 @@ class PortalView: UIView {
23
23
  @objc var portal: [String: Any]? {
24
24
  get {
25
25
  guard let _portal = _portal else { return nil }
26
- return [
27
- "name": _portal.name,
28
- "initialContext": _portal.initialContext
29
- ]
26
+ return try? _portal.encode(to: JSValueEncoder(optionalEncodingStrategy: .undefined))
30
27
  }
31
28
 
32
29
  set {
@@ -34,12 +31,33 @@ class PortalView: UIView {
34
31
  let name = portalDict["name"] as? String
35
32
  else { return }
36
33
 
37
- var portal = PortalsReactNative.getPortal(named: name)
34
+ var portal: Portal
38
35
 
39
- if let initialContext = portalDict["initialContext"] as? [String: Any] {
40
- portal?.initialContext = JSTypes.coerceDictionaryToJSObject(initialContext) ?? [:]
36
+ if var deprecatedPortal = PortalsReactNative.getPortal(named: name) {
37
+ if let initialContext = portalDict["initialContext"] as? [String: Any] {
38
+ deprecatedPortal.initialContext = JSTypes.coerceDictionaryToJSObject(initialContext) ?? [:]
39
+ }
40
+ portal = deprecatedPortal
41
+ } else {
42
+ let jsObject = JSTypes.coerceDictionaryToJSObject(portalDict) ?? [:]
43
+ do {
44
+ portal = try Portal.decode(from: jsObject, with: JSValueDecoder())
45
+ } catch {
46
+ print(error.localizedDescription)
47
+ return
48
+ }
49
+ }
50
+ if portal.usesWebVitals {
51
+ var vitalsPlugin = WebVitalsPlugin { portalName, duration in
52
+ IonicPortals.PortalsPubSub
53
+ .shared
54
+ .publish(
55
+ ["portalName": portalName, "duration": duration],
56
+ to: "webVitals:received"
57
+ )
58
+ }
59
+ portal._portal.plugins.append(.instance(vitalsPlugin))
41
60
  }
42
-
43
61
  _portal = portal
44
62
  }
45
63
  }
@@ -10,94 +10,7 @@ import Capacitor
10
10
  import IonicLiveUpdates
11
11
  import IonicPortals
12
12
 
13
- struct PortalsConfig {
14
- var portals: [Portal]
13
+ struct PortalsConfig: Decodable {
15
14
  var registrationKey: String?
16
15
  var secureLiveUpdatesPublicKey: String?
17
-
18
- struct Portal {
19
- var name: String
20
- var startDir: String?
21
- var index: String?
22
- var initialContext: JSObject?
23
- var assetMaps: [AssetMap]?
24
- var plugins: [ReactNativePortals.Portal.Plugin]?
25
- var liveUpdate: LiveUpdate?
26
-
27
- func portal(with liveUpdateManager: LiveUpdateManager) -> ReactNativePortals.Portal {
28
- return .init(
29
- _portal: .init(
30
- name: name,
31
- startDir: startDir,
32
- index: index ?? "index.html",
33
- initialContext: initialContext ?? [:],
34
- assetMaps: assetMaps ?? [],
35
- plugins: plugins?.toCapPlugin ?? [],
36
- liveUpdateManager: liveUpdateManager,
37
- liveUpdateConfig: liveUpdate.map { .init(appId: $0.appId, channel: $0.channel, syncOnAdd: $0.syncOnAdd) }
38
- ),
39
- plugins: plugins ?? []
40
- )
41
- }
42
-
43
- struct LiveUpdate {
44
- var channel: String
45
- var appId: String
46
- var syncOnAdd: Bool
47
- }
48
- }
49
16
  }
50
-
51
- extension PortalsConfig {
52
- init?(_ dict: [String: Any]) {
53
- guard let rawPortals = dict["portals"] as? [[String: Any]] else {
54
- print("Portals configuration must contain a 'portals' property.")
55
- return nil
56
- }
57
-
58
- let portals = rawPortals.compactMap(Portal.init)
59
- guard portals.count == rawPortals.count else {
60
- print("Invalid portals configuration.")
61
- return nil
62
- }
63
-
64
- self.portals = portals
65
- registrationKey = dict["registrationKey"] as? String
66
- secureLiveUpdatesPublicKey = dict["liveUpdatesKey"] as? String
67
- }
68
- }
69
-
70
- extension PortalsConfig.Portal {
71
- init?(_ dict: [String: Any]) {
72
- guard let name = dict["name"] as? String else {
73
- print("Portal confifguration must contain a 'name' property.")
74
- return nil
75
- }
76
-
77
- self.name = name
78
- startDir = dict["startDir"] as? String
79
- index = dict["index"] as? String
80
- initialContext = (dict["initialContext"] as? [String: Any])
81
- .flatMap { JSTypes.coerceDictionaryToJSObject($0) }
82
- plugins = (dict["plugins"] as? Array<[String: String]>)
83
- .flatMap { $0.compactMap(ReactNativePortals.Portal.Plugin.init) }
84
- assetMaps = (dict["assetMaps"] as? Array<[String: String]>)
85
- .flatMap { $0.compactMap(AssetMap.init) }
86
- liveUpdate = (dict["liveUpdate"] as? [String: Any])
87
- .flatMap(LiveUpdate.init)
88
- }
89
- }
90
-
91
- extension PortalsConfig.Portal.LiveUpdate {
92
- init?(_ dict: [String: Any]) {
93
- guard let appId = dict["appId"] as? String else {
94
- print("LiveUpdate configuration must contain an 'appId' property.")
95
- return nil
96
- }
97
-
98
- self.appId = appId
99
- channel = dict["channel"] as? String ?? "production"
100
- syncOnAdd = dict["syncOnAdd"] as? Bool ?? true
101
- }
102
- }
103
-
@@ -0,0 +1,2 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
@@ -1,42 +1,33 @@
1
1
  import Foundation
2
+ import Capacitor
2
3
  import IonicPortals
3
4
  import IonicLiveUpdates
4
5
  import React
5
6
 
6
7
  @objc(IONPortalsReactNative)
7
8
  public class PortalsReactNative: NSObject {
8
- private var lum: LiveUpdateManager
9
+ internal private(set) static var lum: LiveUpdateManager = .shared
10
+ @available(*, deprecated, message: "This will be removed in the next release")
9
11
  internal static var portals = ConcurrentDictionary<String, Portal>(label: "com.portals.reactnative", dict: [:])
12
+ let encoder = JSValueEncoder(optionalEncodingStrategy: .undefined)
13
+ let decoder = JSValueDecoder()
10
14
 
11
15
  public override init() {
12
16
  guard let configUrl = Bundle.main.url(forResource: "portals.config.json", withExtension: nil) else {
13
- lum = .shared
14
17
  return
15
18
  }
16
19
 
17
20
  guard let configData = try? Data(contentsOf: configUrl),
18
- let jsonData = try? JSONSerialization.jsonObject(with: configData) as? [String: Any],
19
- let portalsConfig = PortalsConfig(jsonData)
21
+ let portalsConfig = try? JSONDecoder().decode(PortalsConfig.self, from: configData)
20
22
  else { fatalError("Portals config data is malformed. Aborting.") }
21
23
 
22
24
  if let registrationKey = portalsConfig.registrationKey {
23
25
  PortalsRegistrationManager.shared.register(key: registrationKey)
24
26
  }
25
27
 
26
- let liveUpdateManager: LiveUpdateManager
27
28
  if let publicKeyPath = portalsConfig.secureLiveUpdatesPublicKey {
28
29
  guard let publicKeyUrl = Bundle.main.url(forResource: publicKeyPath, withExtension: nil) else { fatalError("Public key not found at \(publicKeyPath)") }
29
- liveUpdateManager = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
30
- } else {
31
- liveUpdateManager = .shared
32
- }
33
-
34
- lum = liveUpdateManager
35
-
36
- let portals = portalsConfig.portals.map { $0.portal(with: liveUpdateManager) }
37
-
38
- for portal in portals {
39
- Self.portals[portal.name] = portal
30
+ Self.lum = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
40
31
  }
41
32
  }
42
33
 
@@ -47,39 +38,53 @@ public class PortalsReactNative: NSObject {
47
38
 
48
39
  @objc func enableSecureLiveUpdates(_ publicKeyPath: String, resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
49
40
  guard let publicKeyUrl = Bundle.main.url(forResource: publicKeyPath, withExtension: nil) else { fatalError("Public key not found at \(publicKeyPath)") }
50
- lum = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
41
+ Self.lum = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
51
42
  resolver(())
52
43
  }
53
44
 
45
+ @available(*, deprecated, message: "This will be removed in the next release")
54
46
  @objc func addPortal(_ portalDict: [String: Any], resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
55
- guard let portal = Portal(portalDict, lum) else { return rejector(nil, "Invalid Portal configuration", nil) }
56
- Self.portals[portal.name] = portal
57
- resolver(portal.dict)
47
+ do {
48
+ let portal = try Portal.decode(from: JSTypes.coerceDictionaryToJSObject(portalDict) ?? [:], with: decoder)
49
+ Self.portals[portal.name] = portal
50
+ resolver(try encoder.encode(portal))
51
+ } catch {
52
+ rejector(nil, "Invalid Portal configuration", error)
53
+ }
58
54
  }
59
55
 
56
+ @available(*, deprecated, message: "This will be removed in the next release")
60
57
  @objc func addPortals(_ portalsArray: [[String: Any]], resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
61
- let portals = portalsArray.compactMap { Portal($0, lum) }
62
-
63
- for portal in portals {
64
- Self.portals[portal.name] = portal
58
+ do {
59
+ let portals = try decoder.decode([Portal].self, from: JSTypes.coerceArrayToJSArray(portalsArray) ?? [])
60
+ for portal in portals {
61
+ Self.portals[portal.name] = portal
62
+ }
63
+ resolver(try encoder.encode(portals))
64
+ } catch {
65
+ rejector(nil, "Invalid Portal configuration", error)
65
66
  }
66
-
67
- resolver(portals.map(\.dict))
68
67
  }
69
68
 
69
+ @available(*, deprecated, message: "This will be removed in the next release")
70
70
  static func getPortal(named name: String) -> Portal? { portals[name] }
71
71
 
72
+ @available(*, deprecated, message: "This will be removed in the next release")
72
73
  @objc func getPortal(_ name: String, resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
73
74
  guard let portal = Self.getPortal(named: name) else { return rejector(nil, "Portal named \(name) not registered", nil) }
74
- resolver(portal.dict)
75
+ do {
76
+ resolver(try encoder.encode(portal))
77
+ } catch {
78
+ rejector(nil, "Invalid Portal configuration", error)
79
+ }
75
80
  }
76
81
 
77
82
  @objc func syncOne(_ appId: String, resolver: @escaping RCTPromiseResolveBlock, rejector: @escaping RCTPromiseRejectBlock) {
78
- lum.sync(appId: appId, isParallel: true) { result in
79
- switch result {
80
- case .success(let update):
81
- resolver(update.dict)
82
- case .failure(let error):
83
+ Task {
84
+ do {
85
+ let result = try await Self.lum.sync(appId: appId)
86
+ resolver(try? JSValueEncoder(optionalEncodingStrategy: .undefined).encode(result))
87
+ } catch {
83
88
  rejector(nil, nil, error)
84
89
  }
85
90
  }
@@ -87,15 +92,15 @@ public class PortalsReactNative: NSObject {
87
92
 
88
93
  @objc func syncSome(_ appIds: [String], resolver: @escaping RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
89
94
  Task {
90
- let syncResult = await lum.syncSome(appIds)
91
- resolver(syncResult.dict)
95
+ let syncResult = await Self.lum.syncSome(appIds)
96
+ resolver(try? syncResult.dict)
92
97
  }
93
98
  }
94
99
 
95
100
  @objc func syncAll(_ resolver: @escaping RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
96
101
  Task {
97
- let syncResult = await lum.syncAll()
98
- resolver(syncResult.dict)
102
+ let syncResult = await Self.lum.syncAll()
103
+ resolver(try? syncResult.dict)
99
104
  }
100
105
  }
101
106
 
@@ -0,0 +1,55 @@
1
+ //
2
+ // SyncResult+Dict.swift
3
+ // ReactNativePortals
4
+ //
5
+ // Created by Steven Sherry on 3/28/23.
6
+ // Copyright © 2023 Facebook. All rights reserved.
7
+ //
8
+
9
+ import IonicLiveUpdates
10
+
11
+ extension LiveUpdateManager.SyncResult: Encodable {
12
+ enum TopLevelKeys: String, CodingKey { case liveUpdate, snapshot, source, activeApplicationPathChanged }
13
+ enum LiveUpdateKeys: String, CodingKey { case appId, channel }
14
+ enum SnapshotKeys: String, CodingKey { case id, buildId }
15
+
16
+ public func encode(to encoder: Encoder) throws {
17
+ var topLevelContainer = encoder.container(keyedBy: TopLevelKeys.self)
18
+
19
+ var liveUpdateContainer = topLevelContainer.nestedContainer(keyedBy: LiveUpdateKeys.self, forKey: .liveUpdate)
20
+ try liveUpdateContainer.encode(liveUpdate.appId, forKey: .appId)
21
+ try liveUpdateContainer.encode(liveUpdate.channel, forKey: .channel)
22
+
23
+ if let snapshot {
24
+ var snapshotContainer = topLevelContainer.nestedContainer(keyedBy: SnapshotKeys.self, forKey: .snapshot)
25
+ try snapshotContainer.encode(snapshot.id, forKey: .id)
26
+ try snapshotContainer.encode(snapshot.buildId, forKey: .buildId)
27
+ }
28
+
29
+ if case let .cache(pathsChanged) = source {
30
+ try topLevelContainer.encode("cache", forKey: .source)
31
+ try topLevelContainer.encode(pathsChanged, forKey: .activeApplicationPathChanged)
32
+ } else {
33
+ try topLevelContainer.encode("download", forKey: .source)
34
+ try topLevelContainer.encode(true, forKey: .activeApplicationPathChanged)
35
+ }
36
+ }
37
+ }
38
+
39
+ extension LiveUpdateManager.SyncError: Encodable {
40
+ enum CodingKeys: String, CodingKey { case appId, failStep, message }
41
+
42
+ public func encode(to encoder: Encoder) throws {
43
+ var container = encoder.container(keyedBy: CodingKeys.self)
44
+ let failStep: String
45
+ if case .secureUpdateError = reason {
46
+ failStep = "VERIFY"
47
+ } else {
48
+ failStep = self.failStep.rawValue.uppercased()
49
+ }
50
+
51
+ try container.encode(appId, forKey: .appId)
52
+ try container.encode(failStep, forKey: .failStep)
53
+ try container.encode(errorDescription, forKey: .message)
54
+ }
55
+ }
@@ -8,10 +8,27 @@
8
8
 
9
9
  import IonicPortals
10
10
  import React
11
+ import Combine
11
12
 
12
13
  @objc(IONPortalsWebVitals)
13
14
  class WebVitals: RCTEventEmitter {
14
15
  private let fcp = "vitals:fcp"
16
+ private var subscription: AnyCancellable?
17
+
18
+ override init() {
19
+ super.init()
20
+ subscription = IonicPortals.PortalsPubSub
21
+ .shared
22
+ .publisher(for: "webVitals:received")
23
+ .data()
24
+ .sink { [weak self] data in
25
+ guard let self = self else { return }
26
+ self.sendEvent(
27
+ withName: self.fcp,
28
+ body: data
29
+ )
30
+ }
31
+ }
15
32
 
16
33
  override func supportedEvents() -> [String] {
17
34
  [fcp]
@@ -21,33 +38,9 @@ class WebVitals: RCTEventEmitter {
21
38
  guard var portal = PortalsReactNative.portals[portalName] else {
22
39
  return resolver(())
23
40
  }
24
-
25
- var portalPlugins = portal._portal.plugins.filter { plugin in
26
- switch plugin {
27
- case .instance(let plugin):
28
- return type(of: plugin) != WebVitalsPlugin.self
29
- case .type:
30
- return true
31
- }
32
- }
33
-
34
-
35
- var vitalsPlugin = WebVitalsPlugin { [weak self] _, duration in
36
- guard let self = self else { return }
37
- self.sendEvent(
38
- withName: self.fcp,
39
- body: [
40
- "portalName": portalName,
41
- "duration": duration
42
- ]
43
- )
44
- }
45
-
46
- portalPlugins.append(.instance(vitalsPlugin))
47
- portal._portal.plugins = portalPlugins
48
-
41
+
42
+ portal.usesWebVitals = true
49
43
  PortalsReactNative.portals[portalName] = portal
50
-
51
44
  resolver(())
52
45
  }
53
46