@ionic/portals-react-native 0.5.2 → 0.7.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 (49) 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 +24 -24
  6. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +0 -34
  7. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativeWebVitalsModule.kt +0 -34
  8. package/ios/AssetMap+Codable.swift +33 -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/{PortalManager.m → PortalManager.mm} +0 -3
  14. package/ios/PortalView.swift +22 -12
  15. package/ios/{PortalWebVitals.m → PortalWebVitals.mm} +0 -1
  16. package/ios/PortalsConfig.swift +1 -88
  17. package/ios/PortalsReactNative-Bridging-Header.h +2 -0
  18. package/ios/PortalsReactNative.swift +17 -51
  19. package/ios/SyncResult+SyncError+Encodable.swift +55 -0
  20. package/ios/WebVitals.swift +17 -34
  21. package/package.json +62 -43
  22. package/src/{PortalView.android.tsx → BasePortalView.android.tsx} +2 -3
  23. package/src/{PortalView.tsx → BasePortalView.tsx} +2 -3
  24. package/src/{index.ts → index.tsx} +43 -96
  25. package/ios/AssetMap+Dict.swift +0 -28
  26. package/ios/LiveUpdate+Dict.swift +0 -30
  27. package/ios/LiveUpdateManagerError+Dict.swift +0 -19
  28. package/ios/ReactNativePortals.xcodeproj/project.pbxproj +0 -465
  29. package/ios/ReactNativePortals.xcodeproj/xcshareddata/xcschemes/ReactNativePortals.xcscheme +0 -67
  30. package/ios/ReactNativePortals.xcworkspace/contents.xcworkspacedata +0 -10
  31. package/ios/ReactNativePortals.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  32. package/ios/SyncResult+Dict.swift +0 -35
  33. package/lib/commonjs/PortalView.android.js +0 -28
  34. package/lib/commonjs/PortalView.android.js.map +0 -1
  35. package/lib/commonjs/PortalView.js +0 -15
  36. package/lib/commonjs/PortalView.js.map +0 -1
  37. package/lib/commonjs/index.js +0 -200
  38. package/lib/commonjs/index.js.map +0 -1
  39. package/lib/module/PortalView.android.js +0 -20
  40. package/lib/module/PortalView.android.js.map +0 -1
  41. package/lib/module/PortalView.js +0 -8
  42. package/lib/module/PortalView.js.map +0 -1
  43. package/lib/module/index.js +0 -176
  44. package/lib/module/index.js.map +0 -1
  45. package/lib/typescript/PortalView.android.d.ts +0 -4
  46. package/lib/typescript/PortalView.d.ts +0 -4
  47. package/lib/typescript/index.d.ts +0 -181
  48. /package/ios/{PortalView.m → PortalView.mm} +0 -0
  49. /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
 
@@ -11,9 +11,6 @@
11
11
  @interface RCT_EXTERN_MODULE(IONPortalsReactNative, NSObject)
12
12
  RCT_EXTERN_METHOD(register: (NSString *) key resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
13
13
  RCT_EXTERN_METHOD(enableSecureLiveUpdates: (NSString *) publicKeyPath resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
14
- RCT_EXTERN_METHOD(addPortal: (NSDictionary) portal resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
15
- RCT_EXTERN_METHOD(addPortals: (NSArray) portals resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
16
- RCT_EXTERN_METHOD(getPortal: (NSString *) name resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
17
14
  RCT_EXTERN_METHOD(syncOne: (NSString *) appId resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
18
15
  RCT_EXTERN_METHOD(syncSome: (NSArray) appIds resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
19
16
  RCT_EXTERN_METHOD(syncAll: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
@@ -23,23 +23,33 @@ 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 {
33
- guard let portalDict = newValue,
34
- let name = portalDict["name"] as? String
35
- else { return }
36
-
37
- var portal = PortalsReactNative.getPortal(named: name)
30
+ guard let portalDict = newValue else { return }
31
+
32
+ var portal: Portal
38
33
 
39
- if let initialContext = portalDict["initialContext"] as? [String: Any] {
40
- portal?.initialContext = JSTypes.coerceDictionaryToJSObject(initialContext) ?? [:]
34
+ do {
35
+ let jsObject = JSTypes.coerceDictionaryToJSObject(portalDict) ?? [:]
36
+ portal = try Portal.decode(from: jsObject, with: JSValueDecoder())
37
+ } catch {
38
+ print(error.localizedDescription)
39
+ return
40
+ }
41
+
42
+ if portal.usesWebVitals {
43
+ let vitalsPlugin = WebVitalsPlugin { portalName, duration in
44
+ IonicPortals.PortalsPubSub
45
+ .shared
46
+ .publish(
47
+ ["portalName": portalName, "duration": duration],
48
+ to: "webVitals:received"
49
+ )
50
+ }
51
+ portal._portal.plugins.append(.instance(vitalsPlugin))
41
52
  }
42
-
43
53
  _portal = portal
44
54
  }
45
55
  }
@@ -10,6 +10,5 @@
10
10
  #import <React/RCTEventEmitter.h>
11
11
 
12
12
  @interface RCT_EXTERN_MODULE(IONPortalsWebVitals, RCTEventEmitter)
13
- RCT_EXTERN_METHOD(registerOnFirstContentfulPaint: (NSString *) portalName resolver: (RCTPromiseResolveBlock) resolver rejector: (RCTPromiseRejectBlock) rejector)
14
13
  @end
15
14
 
@@ -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,31 @@
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 static var portals = ConcurrentDictionary<String, Portal>(label: "com.portals.reactnative", dict: [:])
9
+ internal private(set) static var lum: LiveUpdateManager = .shared
10
+ let encoder = JSValueEncoder(optionalEncodingStrategy: .undefined)
11
+ let decoder = JSValueDecoder()
10
12
 
11
13
  public override init() {
12
14
  guard let configUrl = Bundle.main.url(forResource: "portals.config.json", withExtension: nil) else {
13
- lum = .shared
14
15
  return
15
16
  }
16
17
 
17
18
  guard let configData = try? Data(contentsOf: configUrl),
18
- let jsonData = try? JSONSerialization.jsonObject(with: configData) as? [String: Any],
19
- let portalsConfig = PortalsConfig(jsonData)
19
+ let portalsConfig = try? JSONDecoder().decode(PortalsConfig.self, from: configData)
20
20
  else { fatalError("Portals config data is malformed. Aborting.") }
21
21
 
22
22
  if let registrationKey = portalsConfig.registrationKey {
23
23
  PortalsRegistrationManager.shared.register(key: registrationKey)
24
24
  }
25
25
 
26
- let liveUpdateManager: LiveUpdateManager
27
26
  if let publicKeyPath = portalsConfig.secureLiveUpdatesPublicKey {
28
27
  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
28
+ Self.lum = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
40
29
  }
41
30
  }
42
31
 
@@ -47,39 +36,16 @@ public class PortalsReactNative: NSObject {
47
36
 
48
37
  @objc func enableSecureLiveUpdates(_ publicKeyPath: String, resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
49
38
  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)
39
+ Self.lum = SecureLiveUpdateManager(named: "secure-updates", publicKeyUrl: publicKeyUrl)
51
40
  resolver(())
52
41
  }
53
-
54
- @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)
58
- }
59
-
60
- @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
65
- }
66
-
67
- resolver(portals.map(\.dict))
68
- }
69
-
70
- static func getPortal(named name: String) -> Portal? { portals[name] }
71
-
72
- @objc func getPortal(_ name: String, resolver: RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
73
- guard let portal = Self.getPortal(named: name) else { return rejector(nil, "Portal named \(name) not registered", nil) }
74
- resolver(portal.dict)
75
- }
76
-
42
+
77
43
  @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):
44
+ Task {
45
+ do {
46
+ let result = try await Self.lum.sync(appId: appId)
47
+ resolver(try? JSValueEncoder(optionalEncodingStrategy: .undefined).encode(result))
48
+ } catch {
83
49
  rejector(nil, nil, error)
84
50
  }
85
51
  }
@@ -87,15 +53,15 @@ public class PortalsReactNative: NSObject {
87
53
 
88
54
  @objc func syncSome(_ appIds: [String], resolver: @escaping RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
89
55
  Task {
90
- let syncResult = await lum.syncSome(appIds)
91
- resolver(syncResult.dict)
56
+ let syncResult = await Self.lum.syncSome(appIds)
57
+ resolver(try? syncResult.dict)
92
58
  }
93
59
  }
94
60
 
95
61
  @objc func syncAll(_ resolver: @escaping RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
96
62
  Task {
97
- let syncResult = await lum.syncAll()
98
- resolver(syncResult.dict)
63
+ let syncResult = await Self.lum.syncAll()
64
+ resolver(try? syncResult.dict)
99
65
  }
100
66
  }
101
67
 
@@ -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,48 +8,31 @@
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]
18
35
  }
19
-
20
- @objc func registerOnFirstContentfulPaint(_ portalName: String, resolver: @escaping RCTPromiseResolveBlock, rejector: RCTPromiseRejectBlock) {
21
- guard var portal = PortalsReactNative.portals[portalName] else {
22
- return resolver(())
23
- }
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
36
 
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
-
49
- PortalsReactNative.portals[portalName] = portal
50
-
51
- resolver(())
52
- }
53
-
54
37
  override class func requiresMainQueueSetup() -> Bool { true }
55
38
  }