react-native-nami-sdk 3.3.2 → 3.3.3

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 (37) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/AndroidManifest.xml +14 -1
  3. package/android/src/main/java/com/namiml/reactnative/NamiBridgePackage.java +15 -0
  4. package/android/src/main/java/com/namiml/reactnative/NamiFlowManagerBridge.kt +22 -17
  5. package/android/src/main/java/com/namiml/reactnative/NamiOverlayControlBridge.kt +171 -0
  6. package/android/src/main/java/com/namiml/reactnative/ReactOverlayActivity.kt +29 -0
  7. package/android/src/main/res/values/styles.xml +10 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/specs/NativeNamiFlowManager.d.ts +1 -0
  10. package/dist/specs/NativeNamiOverlayControl.d.ts +9 -0
  11. package/dist/src/NamiOverlayControl.d.ts +9 -0
  12. package/dist/src/NamiOverlayHost.d.ts +1 -0
  13. package/dist/src/overlay.d.ts +4 -0
  14. package/dist/src/registerOverlay.d.ts +1 -0
  15. package/dist/src/version.d.ts +1 -1
  16. package/index.ts +1 -0
  17. package/ios/Nami.swift +21 -8
  18. package/ios/NamiCampaignManagerBridge.m +3 -2
  19. package/ios/NamiCampaignManagerBridge.swift +16 -11
  20. package/ios/NamiCustomerManager.m +3 -2
  21. package/ios/NamiCustomerManager.swift +44 -21
  22. package/ios/NamiEntitlementManagerBridge.m +3 -2
  23. package/ios/NamiEntitlementManagerBridge.swift +14 -12
  24. package/ios/NamiFlowManagerBridge.m +1 -1
  25. package/ios/NamiFlowManagerBridge.swift +17 -22
  26. package/ios/NamiOverlayControlBridge.m +17 -0
  27. package/ios/NamiOverlayControlBridge.swift +132 -0
  28. package/ios/NamiPaywallManagerBridge.m +2 -2
  29. package/ios/NamiPaywallManagerBridge.swift +19 -20
  30. package/ios/NamiPurchaseManagerBridge.m +2 -2
  31. package/ios/NamiPurchaseManagerBridge.swift +17 -8
  32. package/package.json +15 -3
  33. package/react-native-nami-sdk.podspec +19 -9
  34. package/specs/NativeNamiFlowManager.ts +1 -0
  35. package/specs/NativeNamiOverlayControl.ts +9 -0
  36. package/src/NamiOverlayControl.tsx +48 -0
  37. package/src/version.ts +1 -1
@@ -9,17 +9,26 @@ import Foundation
9
9
  import NamiApple
10
10
  import React
11
11
 
12
- // #if RCT_NEW_ARCH_ENABLED
13
- // extension RNNamiCustomerManager: RCTTurboModule {}
14
- // #endif
15
-
16
12
  @objc(RNNamiCustomerManager)
17
13
  class RNNamiCustomerManager: RCTEventEmitter {
18
14
  public static var shared: RNNamiCustomerManager?
19
15
 
20
16
  override init() {
21
17
  super.init()
22
- RNNamiCustomerManager.shared = self
18
+ }
19
+
20
+ override class func requiresMainQueueSetup() -> Bool { true }
21
+
22
+ private var hasListeners = false
23
+ override func startObserving() { hasListeners = true }
24
+ override func stopObserving() { hasListeners = false }
25
+
26
+ private func safeSend(withName name: String, body: Any?) {
27
+ guard hasListeners else {
28
+ print("[RNNamiCustomerManager] Warning: no listeners, so event not being sent to JS.")
29
+ return
30
+ } // optional but avoids warnings
31
+ sendEvent(withName: name, body: body)
23
32
  }
24
33
 
25
34
  override func supportedEvents() -> [String]! {
@@ -46,8 +55,10 @@ class RNNamiCustomerManager: RCTEventEmitter {
46
55
 
47
56
  @objc(getCustomerAttribute:resolver:rejecter:)
48
57
  func getCustomerAttribute(key: String, resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
49
- let customerAttribute = NamiCustomerManager.getCustomerAttribute(key: key)
50
- resolve(customerAttribute)
58
+ DispatchQueue.main.async {
59
+ let customerAttribute = NamiCustomerManager.getCustomerAttribute(key: key)
60
+ resolve(customerAttribute)
61
+ }
51
62
  }
52
63
 
53
64
  @objc(clearCustomerAttribute:)
@@ -78,54 +89,68 @@ class RNNamiCustomerManager: RCTEventEmitter {
78
89
  @objc(inAnonymousMode:rejecter:)
79
90
  func inAnonymousMode(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
80
91
  let inAnonymousMode: Bool = NamiCustomerManager.inAnonymousMode()
81
- resolve(inAnonymousMode)
92
+ DispatchQueue.main.async {
93
+ resolve(inAnonymousMode)
94
+ }
82
95
  }
83
96
 
84
97
  @objc(journeyState:rejecter:)
85
98
  func journeyState(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
86
99
  if let journeyState = NamiCustomerManager.journeyState() {
87
100
  let dictionary = journeyStateToDictionary(journeyState)
88
- resolve(dictionary)
101
+ DispatchQueue.main.async {
102
+ resolve(dictionary)
103
+ }
89
104
  } else {
90
- resolve(nil)
105
+ DispatchQueue.main.async {
106
+ resolve(nil)
107
+ }
91
108
  }
92
109
  }
93
110
 
94
111
  @objc(isLoggedIn:rejecter:)
95
112
  func isLoggedIn(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
96
113
  let isLoggedIn = NamiCustomerManager.isLoggedIn()
97
- resolve(isLoggedIn)
114
+ DispatchQueue.main.async {
115
+ resolve(isLoggedIn)
116
+ }
98
117
  }
99
118
 
100
119
  @objc(loggedInId:rejecter:)
101
120
  func loggedInId(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
102
121
  let id = NamiCustomerManager.loggedInId()
103
- resolve(id)
122
+ DispatchQueue.main.async {
123
+ resolve(id)
124
+ }
104
125
  }
105
126
 
106
127
  @objc(deviceId:rejecter:)
107
128
  func deviceId(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
108
129
  let id = NamiCustomerManager.deviceId()
109
- resolve(id)
130
+ DispatchQueue.main.async {
131
+ resolve(id)
132
+ }
110
133
  }
111
134
 
112
135
  @objc(login:)
113
136
  func login(customerId: String) {
114
- NamiCustomerManager.login(withId: customerId)
137
+ DispatchQueue.main.async {
138
+ NamiCustomerManager.login(withId: customerId)
139
+ }
115
140
  }
116
141
 
117
142
  @objc(logout)
118
143
  func logout() {
119
- NamiCustomerManager.logout()
144
+ DispatchQueue.main.async {
145
+ NamiCustomerManager.logout()
146
+ }
120
147
  }
121
148
 
122
149
  @objc(registerJourneyStateHandler)
123
150
  func registerJourneyStateHandler() {
124
151
  NamiCustomerManager.registerJourneyStateHandler { journeyState in
125
152
  let dictionary = self.journeyStateToDictionary(journeyState)
126
- DispatchQueue.main.async {
127
- RNNamiCustomerManager.shared?.sendEvent(withName: "JourneyStateChanged", body: dictionary)
128
- }
153
+ self.safeSend(withName: "JourneyStateChanged", body: dictionary)
129
154
  }
130
155
  }
131
156
 
@@ -166,9 +191,7 @@ class RNNamiCustomerManager: RCTEventEmitter {
166
191
  "success": success,
167
192
  "error": error?._code as Any,
168
193
  ]
169
- DispatchQueue.main.async {
170
- RNNamiCustomerManager.shared?.sendEvent(withName: "AccountStateChanged", body: payload)
171
- }
194
+ self.safeSend(withName: "AccountStateChanged", body: payload)
172
195
  }
173
196
  }
174
197
  }
@@ -6,8 +6,9 @@
6
6
  //
7
7
 
8
8
  #import <React/RCTBridgeModule.h>
9
+ #import <React/RCTEventEmitter.h>
9
10
 
10
- @interface RCT_EXTERN_MODULE(RNNamiEntitlementManager, NSObject)
11
+ @interface RCT_EXTERN_MODULE(RNNamiEntitlementManager, RCTEventEmitter)
11
12
 
12
13
  RCT_EXTERN_METHOD(isEntitlementActive:(nullable NSString *)referenceId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
13
14
 
@@ -20,7 +21,7 @@ RCT_EXTERN_METHOD(registerActiveEntitlementsHandler)
20
21
  RCT_EXTERN_METHOD(clearProvisionalEntitlementGrants)
21
22
 
22
23
  + (BOOL)requiresMainQueueSetup {
23
- return NO;
24
+ return YES;
24
25
  }
25
26
 
26
27
  @end
@@ -9,10 +9,6 @@ import Foundation
9
9
  import NamiApple
10
10
  import React
11
11
 
12
- // #if RCT_NEW_ARCH_ENABLED
13
- // extension RNNamiEntitlementManager: RCTTurboModule {}
14
- // #endif
15
-
16
12
  @objc(RNNamiEntitlementManager)
17
13
  class RNNamiEntitlementManager: RCTEventEmitter {
18
14
  public static var shared: RNNamiEntitlementManager?
@@ -22,8 +18,18 @@ class RNNamiEntitlementManager: RCTEventEmitter {
22
18
  RNNamiEntitlementManager.shared = self
23
19
  }
24
20
 
25
- override static func requiresMainQueueSetup() -> Bool {
26
- return false
21
+ override class func requiresMainQueueSetup() -> Bool { true }
22
+
23
+ private var hasListeners = false
24
+ override func startObserving() { hasListeners = true }
25
+ override func stopObserving() { hasListeners = false }
26
+
27
+ private func safeSend(withName name: String, body: Any?) {
28
+ guard hasListeners else {
29
+ print("[RNNamiEntitlementManager] Warning: no listeners, so event not being sent to JS.")
30
+ return
31
+ }
32
+ sendEvent(withName: name, body: body)
27
33
  }
28
34
 
29
35
  override func supportedEvents() -> [String]! {
@@ -74,9 +80,7 @@ class RNNamiEntitlementManager: RCTEventEmitter {
74
80
  func refresh() {
75
81
  NamiEntitlementManager.refresh { entitlements in
76
82
  let dicts = entitlements.map { self.entitlementToDictionary($0) }
77
- DispatchQueue.main.async {
78
- RNNamiEntitlementManager.shared?.sendEvent(withName: "EntitlementsChanged", body: dicts)
79
- }
83
+ self.safeSend(withName: "EntitlementsChanged", body: dicts)
80
84
  }
81
85
  }
82
86
 
@@ -84,9 +88,7 @@ class RNNamiEntitlementManager: RCTEventEmitter {
84
88
  func registerActiveEntitlementsHandler() {
85
89
  NamiEntitlementManager.registerActiveEntitlementsHandler { entitlements in
86
90
  let dicts = entitlements.map { self.entitlementToDictionary($0) }
87
- DispatchQueue.main.async {
88
- RNNamiEntitlementManager.shared?.sendEvent(withName: "EntitlementsChanged", body: dicts)
89
- }
91
+ self.safeSend(withName: "EntitlementsChanged", body: dicts)
90
92
  }
91
93
  }
92
94
 
@@ -17,7 +17,7 @@ RCT_EXTERN_METHOD(finish)
17
17
  RCT_EXTERN_METHOD(isFlowOpen:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
18
18
 
19
19
  + (BOOL)requiresMainQueueSetup {
20
- return NO;
20
+ return YES;
21
21
  }
22
22
 
23
23
  @end
@@ -9,21 +9,26 @@ import Foundation
9
9
  import NamiApple
10
10
  import React
11
11
 
12
- // #if RCT_NEW_ARCH_ENABLED
13
- // extension RNNamiFlowManager: RCTTurboModule {}
14
- // #endif
15
-
16
12
  @objc(RNNamiFlowManager)
17
13
  class RNNamiFlowManager: RCTEventEmitter {
18
14
  public static var shared: RNNamiFlowManager?
19
15
 
20
16
  override init() {
21
17
  super.init()
22
- RNNamiFlowManager.shared = self
23
18
  }
24
19
 
25
- override static func requiresMainQueueSetup() -> Bool {
26
- return false
20
+ override class func requiresMainQueueSetup() -> Bool { true }
21
+
22
+ private var hasListeners = false
23
+ override func startObserving() { hasListeners = true }
24
+ override func stopObserving() { hasListeners = false }
25
+
26
+ private func safeSend(withName name: String, body: Any?) {
27
+ guard hasListeners else {
28
+ print("[RNNamiFlowManager] Warning: no listeners, so event not being sent to JS.")
29
+ return
30
+ }
31
+ sendEvent(withName: name, body: body)
27
32
  }
28
33
 
29
34
  override func supportedEvents() -> [String]! {
@@ -39,36 +44,26 @@ class RNNamiFlowManager: RCTEventEmitter {
39
44
  payload["handoffData"] = data
40
45
  }
41
46
 
42
- DispatchQueue.main.async {
43
- RNNamiFlowManager.shared?.sendEvent(withName: "Handoff", body: payload)
44
- }
47
+ self.safeSend(withName: "Handoff", body: payload)
45
48
  }
46
49
  }
47
50
 
48
51
  @objc func registerEventHandler() {
49
52
  NamiFlowManager.registerEventHandler { payload in
50
- DispatchQueue.main.async {
51
- RNNamiFlowManager.shared?.sendEvent(withName: "FlowEvent", body: payload)
52
- }
53
+ self.safeSend(withName: "FlowEvent", body: payload)
53
54
  }
54
55
  }
55
56
 
56
57
  @objc func resume() {
57
- DispatchQueue.main.async {
58
- NamiFlowManager.resume()
59
- }
58
+ NamiFlowManager.resume()
60
59
  }
61
60
 
62
61
  @objc func pause() {
63
- DispatchQueue.main.async {
64
- NamiFlowManager.pause()
65
- }
62
+ NamiFlowManager.pause()
66
63
  }
67
64
 
68
65
  @objc func finish() {
69
- DispatchQueue.main.async {
70
- NamiFlowManager.finish()
71
- }
66
+ NamiFlowManager.finish()
72
67
  }
73
68
 
74
69
  @objc func isFlowOpen(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: @escaping RCTPromiseRejectBlock) {
@@ -0,0 +1,17 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(RNNamiOverlayControl, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(presentOverlay:(RCTPromiseResolveBlock)resolve
7
+ rejecter:(RCTPromiseRejectBlock)reject)
8
+
9
+ RCT_EXTERN_METHOD(finishOverlay:(NSDictionary *)result
10
+ resolver:(RCTPromiseResolveBlock)resolve
11
+ rejecter:(RCTPromiseRejectBlock)reject)
12
+
13
+ + (BOOL)requiresMainQueueSetup {
14
+ return YES;
15
+ }
16
+
17
+ @end
@@ -0,0 +1,132 @@
1
+ import Foundation
2
+ import React
3
+ import UIKit
4
+
5
+ @objc(RNNamiOverlayControl)
6
+ class NamiOverlayControlBridge: RCTEventEmitter {
7
+ private var overlayViewController: UIViewController?
8
+ private var hasListeners = false
9
+ private var isPresenting = false
10
+ private var isDismissing = false
11
+
12
+ override static func requiresMainQueueSetup() -> Bool {
13
+ return true
14
+ }
15
+
16
+ override func supportedEvents() -> [String]! {
17
+ return ["NamiOverlayReady", "NamiOverlayResult"]
18
+ }
19
+
20
+ override func startObserving() { hasListeners = true }
21
+ override func stopObserving() { hasListeners = false }
22
+
23
+ private func safeSend(withName name: String, body: Any?) {
24
+ guard hasListeners else {
25
+ print("[RNNamiOverlayControl] Warning: no listeners, so event not being sent to JS.")
26
+ return
27
+ }
28
+ sendEvent(withName: name, body: body)
29
+ }
30
+
31
+ @objc func presentOverlay(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
32
+ DispatchQueue.main.async {
33
+ // If we're already presenting or dismissing, wait and retry
34
+ if self.isPresenting || self.isDismissing {
35
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
36
+ self.presentOverlay(resolve, rejecter: reject)
37
+ }
38
+ return
39
+ }
40
+
41
+ // Find the root view controller more reliably
42
+ var rootViewController: UIViewController?
43
+
44
+ if #available(iOS 13.0, *) {
45
+ rootViewController = UIApplication.shared.connectedScenes
46
+ .compactMap { $0 as? UIWindowScene }
47
+ .flatMap { $0.windows }
48
+ .first(where: { $0.isKeyWindow })?.rootViewController
49
+ } else {
50
+ rootViewController = UIApplication.shared.keyWindow?.rootViewController
51
+ }
52
+
53
+ guard let rootVC = rootViewController else {
54
+ reject("NO_ROOT_VIEW_CONTROLLER", "No root view controller available", nil)
55
+ return
56
+ }
57
+
58
+ // Create a React Native view controller for the overlay
59
+ guard let bridge = self.bridge else {
60
+ reject("NO_BRIDGE", "React Native bridge not available", nil)
61
+ return
62
+ }
63
+
64
+ let rootView = RCTRootView(
65
+ bridge: bridge,
66
+ moduleName: "NamiOverlayHost",
67
+ initialProperties: [:]
68
+ )
69
+
70
+ let overlayViewController = UIViewController()
71
+ overlayViewController.view = rootView
72
+ overlayViewController.modalPresentationStyle = .overFullScreen
73
+ overlayViewController.modalTransitionStyle = .crossDissolve
74
+ overlayViewController.view.backgroundColor = UIColor.clear
75
+
76
+ self.overlayViewController = overlayViewController
77
+
78
+ // Find the top-most presented view controller
79
+ var topController = rootVC
80
+ while let presented = topController.presentedViewController {
81
+ topController = presented
82
+ }
83
+
84
+ // Check if the top controller can present (not already in the process of presenting/dismissing)
85
+ if topController.isBeingPresented || topController.isBeingDismissed {
86
+ // Wait longer and try again
87
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
88
+ self.presentOverlay(resolve, rejecter: reject)
89
+ }
90
+ return
91
+ }
92
+
93
+ self.isPresenting = true
94
+
95
+ topController.present(overlayViewController, animated: false) {
96
+ self.isPresenting = false
97
+ // Emit ready event after presentation completes
98
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
99
+ if self.hasListeners {
100
+ self.safeSend(withName: "NamiOverlayReady", body: nil)
101
+ }
102
+ }
103
+ resolve(nil)
104
+ }
105
+ }
106
+ }
107
+
108
+ @objc func finishOverlay(_ result: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: @escaping RCTPromiseRejectBlock) {
109
+ DispatchQueue.main.async {
110
+ guard let overlayViewController = self.overlayViewController else {
111
+ resolve(nil)
112
+ return
113
+ }
114
+
115
+ // Emit result to listeners
116
+ if self.hasListeners {
117
+ self.safeSend(withName: "NamiOverlayResult", body: result)
118
+ }
119
+
120
+ self.isDismissing = true
121
+
122
+ overlayViewController.dismiss(animated: false) {
123
+ self.overlayViewController = nil
124
+ self.isDismissing = false
125
+ // Add a longer delay before resolving to ensure the view controller is fully dismissed
126
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
127
+ resolve(nil)
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
@@ -13,7 +13,7 @@
13
13
 
14
14
  #import "React/RCTViewManager.h"
15
15
 
16
- @interface RCT_EXTERN_MODULE(RNNamiPaywallManager, NSObject)
16
+ @interface RCT_EXTERN_MODULE(RNNamiPaywallManager, RCTEventEmitter)
17
17
 
18
18
  RCT_EXTERN_METHOD(buySkuComplete:(NSDictionary)dict)
19
19
 
@@ -46,7 +46,7 @@ RCT_EXTERN_METHOD(setAppSuppliedVideoDetails:(NSString *)url name:(nullable NSSt
46
46
  RCT_EXTERN_METHOD(allowUserInteraction:(BOOL *)allowed)
47
47
 
48
48
  + (BOOL)requiresMainQueueSetup {
49
- return NO;
49
+ return YES;
50
50
  }
51
51
 
52
52
  @end
@@ -9,17 +9,26 @@ import Foundation
9
9
  import NamiApple
10
10
  import React
11
11
 
12
- // #if RCT_NEW_ARCH_ENABLED
13
- // extension RNNamiPaywallManager: RCTTurboModule {}
14
- // #endif
15
-
16
12
  @objc(RNNamiPaywallManager)
17
13
  class RNNamiPaywallManager: RCTEventEmitter {
18
14
  public static var shared: RNNamiPaywallManager?
19
15
 
20
16
  override init() {
21
17
  super.init()
22
- RNNamiPaywallManager.shared = self
18
+ }
19
+
20
+ override class func requiresMainQueueSetup() -> Bool { true }
21
+
22
+ private var hasListeners = false
23
+ override func startObserving() { hasListeners = true }
24
+ override func stopObserving() { hasListeners = false }
25
+
26
+ private func safeSend(withName name: String, body: Any?) {
27
+ guard hasListeners else {
28
+ print("[RNNamiPaywallManager] Warning: no listeners, so event not being sent to JS.")
29
+ return
30
+ } // optional but avoids warnings
31
+ sendEvent(withName: name, body: body)
23
32
  }
24
33
 
25
34
  override func supportedEvents() -> [String]! {
@@ -80,9 +89,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
80
89
  func registerBuySkuHandler() {
81
90
  NamiPaywallManager.registerBuySkuHandler { sku in
82
91
  let dictionary = RNNamiPurchaseManager.skuToSKUDict(sku)
83
- DispatchQueue.main.async {
84
- RNNamiPaywallManager.shared?.sendEvent(withName: "RegisterBuySKU", body: dictionary)
85
- }
92
+ self.safeSend(withName: "RegisterBuySKU", body: dictionary)
86
93
  }
87
94
  }
88
95
 
@@ -90,9 +97,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
90
97
  func registerCloseHandler() {
91
98
  NamiPaywallManager.registerCloseHandler { _ in
92
99
  let dictionary = NSDictionary(dictionary: ["PaywallCloseRequested": true].compactMapValues { $0 })
93
- DispatchQueue.main.async {
94
- RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallCloseRequested", body: dictionary)
95
- }
100
+ self.safeSend(withName: "PaywallCloseRequested", body: dictionary)
96
101
  }
97
102
  }
98
103
 
@@ -100,9 +105,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
100
105
  func registerSignInHandler() {
101
106
  NamiPaywallManager.registerSignInHandler { _ in
102
107
  let dictionary = NSDictionary(dictionary: ["PaywallSignInRequested": true].compactMapValues { $0 })
103
- DispatchQueue.main.async {
104
- RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallSignInRequested", body: dictionary)
105
- }
108
+ self.safeSend(withName: "PaywallSignInRequested", body: dictionary)
106
109
  }
107
110
  }
108
111
 
@@ -110,18 +113,14 @@ class RNNamiPaywallManager: RCTEventEmitter {
110
113
  func registerRestoreHandler() {
111
114
  NamiPaywallManager.registerRestoreHandler {
112
115
  let dictionary = NSDictionary(dictionary: ["PaywallRestoreRequested": true].compactMapValues { $0 })
113
- DispatchQueue.main.async {
114
- RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallRestoreRequested", body: dictionary)
115
- }
116
+ self.safeSend(withName: "PaywallRestoreRequested", body: dictionary)
116
117
  }
117
118
  }
118
119
 
119
120
  @objc(registerDeeplinkActionHandler)
120
121
  func registerDeeplinkActionHandler() {
121
122
  NamiPaywallManager.registerDeeplinkActionHandler { url in
122
- DispatchQueue.main.async {
123
- RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallDeeplinkAction", body: url)
124
- }
123
+ self.safeSend(withName: "PaywallDeeplinkAction", body: url)
125
124
  }
126
125
  }
127
126
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  #import "React/RCTViewManager.h"
15
15
 
16
- @interface RCT_EXTERN_MODULE(RNNamiPurchaseManager, NSObject)
16
+ @interface RCT_EXTERN_MODULE(RNNamiPurchaseManager, RCTEventEmitter)
17
17
 
18
18
  RCT_EXTERN_METHOD(allPurchases:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
19
19
 
@@ -30,7 +30,7 @@ RCT_EXTERN_METHOD(presentCodeRedemptionSheet)
30
30
  RCT_EXTERN_METHOD(restorePurchases)
31
31
 
32
32
  + (BOOL)requiresMainQueueSetup {
33
- return NO;
33
+ return YES;
34
34
  }
35
35
 
36
36
  @end
@@ -9,17 +9,26 @@ import Foundation
9
9
  import NamiApple
10
10
  import React
11
11
 
12
- // #if RCT_NEW_ARCH_ENABLED
13
- // extension RNNamiPurchaseManager: RCTTurboModule {}
14
- // #endif
15
-
16
12
  @objc(RNNamiPurchaseManager)
17
13
  class RNNamiPurchaseManager: RCTEventEmitter {
18
14
  public static var shared: RNNamiPurchaseManager?
19
15
 
20
16
  override init() {
21
17
  super.init()
22
- RNNamiPurchaseManager.shared = self
18
+ }
19
+
20
+ override class func requiresMainQueueSetup() -> Bool { true }
21
+
22
+ private var hasListeners = false
23
+ override func startObserving() { hasListeners = true }
24
+ override func stopObserving() { hasListeners = false }
25
+
26
+ private func safeSend(withName name: String, body: Any?) {
27
+ guard hasListeners else {
28
+ print("[RNNamiPurchaseManager] Warning: no listeners, so event not being sent to JS.")
29
+ return
30
+ }
31
+ sendEvent(withName: name, body: body)
23
32
  }
24
33
 
25
34
  override func supportedEvents() -> [String]! {
@@ -158,7 +167,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
158
167
  "purchaseState": stateString,
159
168
  "error": error?.localizedDescription,
160
169
  ]
161
- self.sendEvent(withName: "PurchasesChanged", body: payload)
170
+ self.safeSend(withName: "PurchasesChanged", body: payload)
162
171
  }
163
172
  }
164
173
 
@@ -187,7 +196,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
187
196
  "newPurchases": newPurchasesDictionaries,
188
197
  "oldPurchases": oldPurchasesDictionaries,
189
198
  ]
190
- RNNamiPurchaseManager.shared?.sendEvent(withName: "RestorePurchasesStateChanged", body: payload)
199
+ self.safeSend(withName: "RestorePurchasesStateChanged", body: payload)
191
200
  }
192
201
  }
193
202
 
@@ -216,7 +225,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
216
225
  "newPurchases": newPurchasesDictionaries,
217
226
  "oldPurchases": oldPurchasesDictionaries,
218
227
  ]
219
- RNNamiPurchaseManager.shared?.sendEvent(withName: "RestorePurchasesStateChanged", body: payload)
228
+ self.safeSend(withName: "RestorePurchasesStateChanged", body: payload)
220
229
  }
221
230
  }
222
231
 
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "react-native-nami-sdk",
3
- "version": "3.3.2",
4
- "description": "React Native Module for Nami - Easy subscriptions & in-app purchases, with powerful built-in paywalls and A/B testing.",
3
+ "version": "3.3.3",
4
+ "description": "React Native SDK for Nami - No-code paywall management with A/B testing.",
5
5
  "main": "index.ts",
6
6
  "types": "dist/index.d.ts",
7
7
  "codegenConfig": {
8
- "name": "Nami",
8
+ "name": "NativeNamiSpec",
9
9
  "type": "modules",
10
10
  "jsSrcsDir": "./specs",
11
11
  "android": {
12
12
  "javaPackageName": "com.namiml.reactnative"
13
13
  }
14
14
  },
15
+ "react-native": {
16
+ "android": {
17
+ "sourceDir": "android",
18
+ "packageImportPath": "com.namiml.reactnative.NamiBridgePackage",
19
+ "libraryName": "react-native-nami-sdk"
20
+ }
21
+ },
15
22
  "scripts": {
16
23
  "build": "tsc",
17
24
  "generate:version": "ts-node scripts/generate-version.ts",
@@ -63,6 +70,11 @@
63
70
  "react": ">=18",
64
71
  "react-native": ">=0.73"
65
72
  },
73
+ "peerDependenciesMeta": {
74
+ "react-native": {
75
+ "optional": true
76
+ }
77
+ },
66
78
  "devDependencies": {
67
79
  "@react-native/eslint-config": "^0.80.0",
68
80
  "@types/jest": "^29.5.2",