react-native-nami-sdk 3.3.3 → 3.3.5

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.
@@ -85,8 +85,8 @@ dependencies {
85
85
  implementation fileTree(dir: 'libs', include: ['*.jar'])
86
86
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
87
87
 
88
- playImplementation "com.namiml:sdk-android:3.3.3"
89
- amazonImplementation "com.namiml:sdk-amazon:3.3.3"
88
+ playImplementation "com.namiml:sdk-android:3.3.5.1"
89
+ amazonImplementation "com.namiml:sdk-amazon:3.3.5.1"
90
90
 
91
91
  implementation "com.facebook.react:react-native:+" // From node_modules
92
92
  coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
@@ -3,23 +3,24 @@ package com.namiml.reactnative
3
3
  import android.app.Activity
4
4
  import android.net.Uri
5
5
  import android.util.Log
6
+ import androidx.core.net.toUri
6
7
  import com.facebook.react.bridge.*
7
8
  import com.facebook.react.module.annotations.ReactModule
8
9
  import com.facebook.react.modules.core.DeviceEventManagerModule
9
10
  import com.facebook.react.turbomodule.core.interfaces.TurboModule
10
11
  import com.namiml.billing.NamiPurchase
12
+ import com.namiml.campaign.LaunchCampaignError
11
13
  import com.namiml.campaign.LaunchCampaignResult
12
14
  import com.namiml.campaign.NamiCampaign
13
15
  import com.namiml.campaign.NamiCampaignManager
14
16
  import com.namiml.paywall.model.NamiPaywallEvent
15
17
  import com.namiml.paywall.model.PaywallLaunchContext
16
- import androidx.core.net.toUri
17
18
 
18
19
  @ReactModule(name = NamiCampaignManagerBridgeModule.NAME)
19
20
  class NamiCampaignManagerBridgeModule internal constructor(
20
- private val reactContext: ReactApplicationContext
21
- ) : ReactContextBaseJavaModule(reactContext), TurboModule {
22
-
21
+ private val reactContext: ReactApplicationContext,
22
+ ) : ReactContextBaseJavaModule(reactContext),
23
+ TurboModule {
23
24
  companion object {
24
25
  const val NAME = "RNNamiCampaignManager"
25
26
  const val CAMPAIGN_ID = "campaignId"
@@ -42,10 +43,7 @@ class NamiCampaignManagerBridgeModule internal constructor(
42
43
  const val NAMI_PAYWALL_EVENT = "NamiPaywallEvent"
43
44
  }
44
45
 
45
-
46
- override fun getName(): String {
47
- return NAME
48
- }
46
+ override fun getName(): String = NAME
49
47
 
50
48
  private fun campaignToReadableMap(campaign: NamiCampaign): ReadableMap {
51
49
  val readableMap = Arguments.createMap()
@@ -94,7 +92,37 @@ class NamiCampaignManagerBridgeModule internal constructor(
94
92
  val keyIterator = attr.keySetIterator()
95
93
  while (keyIterator.hasNextKey()) {
96
94
  val key = keyIterator.nextKey()
97
- customAttributes[key] = attr.getString(key) ?: ""
95
+ try {
96
+ // Handle different data types and store with proper types
97
+ val value: Any =
98
+ when (attr.getType(key)) {
99
+ ReadableType.String -> attr.getString(key) ?: ""
100
+ ReadableType.Boolean -> attr.getBoolean(key)
101
+ ReadableType.Number -> {
102
+ // Try to get as int first, then as double
103
+ try {
104
+ attr.getInt(key)
105
+ } catch (e: Exception) {
106
+ attr.getDouble(key)
107
+ }
108
+ }
109
+ ReadableType.Null -> ""
110
+ ReadableType.Array -> attr.getArray(key)?.toArrayList() ?: emptyList<Any>()
111
+ ReadableType.Map -> attr.getMap(key)?.toHashMap() ?: emptyMap<String, Any>()
112
+ }
113
+ customAttributes[key] = value
114
+ } catch (e: Exception) {
115
+ // Log the error and try to store the value as a string representation
116
+ Log.w(NAME, "Error parsing customAttribute '$key': ${e.message}. Attempting string conversion.")
117
+ try {
118
+ // Fallback: try to get the dynamic value and convert to string
119
+ val dynamicValue = attr.getDynamic(key)
120
+ customAttributes[key] = dynamicValue.asString() ?: ""
121
+ } catch (fallbackError: Exception) {
122
+ // If all else fails, log and skip this attribute
123
+ Log.e(NAME, "Failed to parse customAttribute '$key': ${fallbackError.message}. Skipping.")
124
+ }
125
+ }
98
126
  }
99
127
  Log.d(NAME, "customAttributes $customAttributes")
100
128
  }
@@ -120,8 +148,7 @@ class NamiCampaignManagerBridgeModule internal constructor(
120
148
 
121
149
  if (theActivity != null) {
122
150
  reactApplicationContext.runOnUiQueueThread {
123
- val paywallActionCallback = {
124
- paywallEvent: NamiPaywallEvent ->
151
+ val paywallActionCallback = { paywallEvent: NamiPaywallEvent ->
125
152
  handlePaywallCallback(
126
153
  paywallEvent,
127
154
  actionCallback,
@@ -184,7 +211,13 @@ class NamiCampaignManagerBridgeModule internal constructor(
184
211
  putString("id", paywallEvent.sku?.id ?: "")
185
212
  putString("skuId", paywallEvent.sku?.skuId ?: "")
186
213
  putString("name", paywallEvent.sku?.name ?: "")
187
- putString("type", paywallEvent.sku?.type.toString().lowercase())
214
+ putString(
215
+ "type",
216
+ paywallEvent.sku
217
+ ?.type
218
+ .toString()
219
+ .lowercase(),
220
+ )
188
221
  }
189
222
  resultMap.putMap(SKU, skuMap)
190
223
  }
@@ -222,8 +255,8 @@ class NamiCampaignManagerBridgeModule internal constructor(
222
255
  emitEvent(NAMI_PAYWALL_EVENT, resultMap)
223
256
  }
224
257
 
225
- private fun createPurchaseArray(purchases: List<NamiPurchase>?): WritableArray {
226
- return WritableNativeArray().apply {
258
+ private fun createPurchaseArray(purchases: List<NamiPurchase>?): WritableArray =
259
+ WritableNativeArray().apply {
227
260
  purchases?.forEach { purchase ->
228
261
  try {
229
262
  pushMap(purchase.toPurchaseDict())
@@ -232,7 +265,6 @@ class NamiCampaignManagerBridgeModule internal constructor(
232
265
  }
233
266
  }
234
267
  }
235
- }
236
268
 
237
269
  private fun emitEvent(
238
270
  event: String,
@@ -246,17 +278,46 @@ class NamiCampaignManagerBridgeModule internal constructor(
246
278
  }
247
279
  }
248
280
 
281
+ /**
282
+ * Maps Android LaunchCampaignError enum values to iOS-compatible error codes
283
+ * to ensure consistent error handling across platforms in React Native.
284
+ */
285
+ private fun mapErrorCodeToIOS(androidError: LaunchCampaignError): Int =
286
+ when (androidError) {
287
+ LaunchCampaignError.DEFAULT_CAMPAIGN_NOT_FOUND -> 0
288
+ LaunchCampaignError.LABELED_CAMPAIGN_NOT_FOUND -> 1
289
+ LaunchCampaignError.CAMPAIGN_DATA_NOT_FOUND -> 2
290
+ LaunchCampaignError.PAYWALL_ALREADY_DISPLAYED -> 3
291
+ LaunchCampaignError.SDK_NOT_INITIALIZED -> 4
292
+ LaunchCampaignError.URL_CAMPAIGN_NOT_FOUND -> 6
293
+ LaunchCampaignError.PRODUCT_GROUPS_NOT_FOUND -> 8
294
+ }
295
+
249
296
  private fun handleResult(
250
297
  result: LaunchCampaignResult,
251
298
  resultCallback: Callback,
252
299
  ) {
253
- val resultMap = Arguments.createMap()
254
300
  when (result) {
255
301
  is LaunchCampaignResult.Success -> {
256
302
  resultCallback.invoke(true)
257
303
  }
258
304
  is LaunchCampaignResult.Failure -> {
259
- resultCallback.invoke(false, "${result.error}")
305
+ val error = result.error
306
+ val errorInfo =
307
+ if (error is LaunchCampaignError) {
308
+ Arguments.createMap().apply {
309
+ putInt("code", mapErrorCodeToIOS(error))
310
+ putString("domain", "LaunchCampaignError")
311
+ putString("message", error.errorMessage.ifEmpty { error.toString() })
312
+ }
313
+ } else {
314
+ Arguments.createMap().apply {
315
+ putInt("code", -1)
316
+ putString("domain", "Unknown")
317
+ putString("message", error.toString())
318
+ }
319
+ }
320
+ resultCallback.invoke(false, errorInfo)
260
321
  }
261
322
  }
262
323
  }
@@ -279,13 +340,38 @@ class NamiCampaignManagerBridgeModule internal constructor(
279
340
  val isCampaignAvailable =
280
341
  when {
281
342
  campaignSource == null -> NamiCampaignManager.isCampaignAvailable()
282
- campaignSource.toUri().scheme != null -> NamiCampaignManager.isCampaignAvailable(
283
- campaignSource.toUri())
343
+ campaignSource.toUri().scheme != null ->
344
+ NamiCampaignManager.isCampaignAvailable(campaignSource.toUri())
284
345
  else -> NamiCampaignManager.isCampaignAvailable(campaignSource)
285
346
  }
286
347
  promise.resolve(isCampaignAvailable)
287
348
  }
288
349
 
350
+ @ReactMethod
351
+ fun isFlow(
352
+ label: String?,
353
+ withUrl: String?,
354
+ promise: Promise,
355
+ ) {
356
+ try {
357
+ val uri =
358
+ if (!withUrl.isNullOrEmpty()) {
359
+ val parsedUri = Uri.parse(withUrl)
360
+ if (parsedUri.scheme.isNullOrEmpty()) {
361
+ promise.reject("ISFLOW_ERROR", "Invalid URL format: missing scheme", null)
362
+ return
363
+ }
364
+ parsedUri
365
+ } else {
366
+ null
367
+ }
368
+ val result = NamiCampaignManager.isFlow(label = label, uri = uri)
369
+ promise.resolve(result)
370
+ } catch (e: Exception) {
371
+ promise.reject("ISFLOW_ERROR", "Failed to check if campaign is flow: ${e.message}", e)
372
+ }
373
+ }
374
+
289
375
  @ReactMethod
290
376
  fun refresh(promise: Promise) {
291
377
  NamiCampaignManager.refresh { campaigns ->
@@ -317,4 +403,34 @@ class NamiCampaignManagerBridgeModule internal constructor(
317
403
  @ReactMethod
318
404
  fun removeListeners(count: Int?) {
319
405
  }
406
+
407
+ @ReactMethod
408
+ fun productGroups(
409
+ label: String?,
410
+ withUrl: String?,
411
+ promise: Promise,
412
+ ) {
413
+ try {
414
+ val uri =
415
+ if (!withUrl.isNullOrEmpty()) {
416
+ val parsedUri = Uri.parse(withUrl)
417
+ if (parsedUri.scheme.isNullOrEmpty()) {
418
+ promise.reject("PRODUCTGROUPS_ERROR", "Invalid URL format: missing scheme", null)
419
+ return
420
+ }
421
+ parsedUri
422
+ } else {
423
+ null
424
+ }
425
+
426
+ val productGroups = NamiCampaignManager.getProductGroups(label = label, uri = uri)
427
+ val array = WritableNativeArray()
428
+ productGroups.forEach { group: String ->
429
+ array.pushString(group)
430
+ }
431
+ promise.resolve(array)
432
+ } catch (e: Exception) {
433
+ promise.reject("PRODUCTGROUPS_ERROR", "Failed to get product groups: ${e.message}", e)
434
+ }
435
+ }
320
436
  }
@@ -52,7 +52,11 @@ class NamiFlowManagerBridgeModule internal constructor(
52
52
  @ReactMethod
53
53
  fun resume() {
54
54
  Handler(Looper.getMainLooper()).postDelayed({
55
- NamiFlowManager.resume()
55
+ if (reactApplicationContext.hasCurrentActivity()) {
56
+ NamiFlowManager.resume(reactApplicationContext.currentActivity)
57
+ } else {
58
+ NamiFlowManager.resume()
59
+ }
56
60
  }, 100L)
57
61
  }
58
62
 
@@ -18,12 +18,31 @@ class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
18
18
  const val NAME = "RNNamiOverlayControl"
19
19
  var currentOverlayActivity: ReactOverlayActivity? = null
20
20
  var lastValidActivity: Activity? = null
21
+ private var isPresentingOverlay = false
22
+ private val pendingPromises = mutableListOf<Promise>()
23
+
24
+ // Internal method to clear the presenting flag (for use by ReactOverlayActivity)
25
+ internal fun clearPresentingFlag() {
26
+ isPresentingOverlay = false
27
+ }
28
+
29
+ // Internal method to check if an overlay is currently being presented
30
+ internal fun isOverlayActive(): Boolean {
31
+ return isPresentingOverlay || currentOverlayActivity != null
32
+ }
21
33
  }
22
34
 
23
35
  override fun getName() = NAME
24
36
 
25
37
  @ReactMethod
26
38
  fun presentOverlay(promise: Promise) {
39
+ // Check if we're already presenting an overlay
40
+ if (isPresentingOverlay || currentOverlayActivity != null) {
41
+ // If there's already an active overlay, reject the new call
42
+ promise.reject("OVERLAY_ALREADY_ACTIVE", "An overlay is already being presented or is currently active")
43
+ return
44
+ }
45
+
27
46
  var theActivity: Activity? = null
28
47
  if (reactApplicationContext.hasCurrentActivity()) {
29
48
  theActivity = reactApplicationContext.currentActivity
@@ -55,6 +74,8 @@ class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
55
74
  return
56
75
  }
57
76
 
77
+ // Set flag to indicate we're presenting an overlay
78
+ isPresentingOverlay = true
58
79
  startOverlayActivity(theActivity, promise)
59
80
  }
60
81
 
@@ -88,11 +109,15 @@ class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
88
109
  promise.resolve(null)
89
110
  } catch (uiError: Exception) {
90
111
  Log.e(NAME, "Error in UI thread: ${uiError.message}", uiError)
112
+ // Clear the presenting flag on error
113
+ isPresentingOverlay = false
91
114
  promise.reject("UI_THREAD_ERROR", "Failed in UI thread: ${uiError.message}", uiError)
92
115
  }
93
116
  }
94
117
  } catch (e: Exception) {
95
118
  Log.e(NAME, "Error presenting overlay: ${e.message}", e)
119
+ // Clear the presenting flag on error
120
+ isPresentingOverlay = false
96
121
  promise.reject("PRESENT_OVERLAY_ERROR", "Failed to present overlay: ${e.message}", e)
97
122
  }
98
123
  }
@@ -137,6 +162,8 @@ class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
137
162
  overlay.overridePendingTransition(0, 0)
138
163
  }
139
164
  currentOverlayActivity = null
165
+ // Clear the presenting flag when overlay is finished
166
+ isPresentingOverlay = false
140
167
 
141
168
  // Wait for activity to actually finish before resolving promise
142
169
  android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
@@ -153,12 +180,16 @@ class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
153
180
  @Suppress("DEPRECATION")
154
181
  overridePendingTransition(0, 0)
155
182
  }
183
+ // Clear the presenting flag when overlay is finished
184
+ isPresentingOverlay = false
156
185
 
157
186
  // Wait for activity to actually finish before resolving promise
158
187
  android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
159
188
  promise.resolve(null)
160
189
  }, 100)
161
190
  } ?: run {
191
+ // Clear the presenting flag even if no activity was found
192
+ isPresentingOverlay = false
162
193
  promise.resolve(null)
163
194
  }
164
195
  }
@@ -24,6 +24,8 @@ class ReactOverlayActivity : ReactActivity() {
24
24
  // Clear the reference when activity is destroyed
25
25
  if (NamiOverlayControlBridgeModule.currentOverlayActivity == this) {
26
26
  NamiOverlayControlBridgeModule.currentOverlayActivity = null
27
+ // Also clear the presenting flag to prevent stuck states
28
+ NamiOverlayControlBridgeModule.clearPresentingFlag()
27
29
  }
28
30
  }
29
31
  }
@@ -71,6 +71,7 @@ export interface Spec extends TurboModule {
71
71
  value?: string;
72
72
  }[]>;
73
73
  isCampaignAvailable(source?: string): Promise<boolean>;
74
+ isFlow(label?: string | null, withUrl?: string | null): Promise<boolean>;
74
75
  refresh(): Promise<{
75
76
  id?: string;
76
77
  rule?: string;
@@ -80,6 +81,7 @@ export interface Spec extends TurboModule {
80
81
  value?: string;
81
82
  }[]>;
82
83
  registerAvailableCampaignsHandler(): void;
84
+ productGroups(label: string | null, withUrl: string | null): Promise<string[]>;
83
85
  }
84
86
  declare const _default: Spec;
85
87
  export default _default;
@@ -64,7 +64,7 @@ export interface Spec extends TurboModule {
64
64
  isHidden(): Promise<boolean>;
65
65
  isPaywallOpen(): Promise<boolean>;
66
66
  buySkuCancel(): void;
67
- setProductDetails(productDetails: string, allowOffers?: boolean): void;
67
+ setProductDetails(productDetails: string, allowOffers: boolean): void;
68
68
  setAppSuppliedVideoDetails(url: string, name?: string): void;
69
69
  allowUserInteraction(allowed: boolean): void;
70
70
  }
@@ -17,6 +17,7 @@ export declare const NamiCampaignManager: {
17
17
  value?: string;
18
18
  }[]>;
19
19
  isCampaignAvailable: (campaignName: string | null) => Promise<boolean>;
20
+ isFlow: (label?: string | null, withUrl?: string | null) => Promise<boolean>;
20
21
  refresh: () => Promise<{
21
22
  id?: string;
22
23
  rule?: string;
@@ -26,4 +27,5 @@ export declare const NamiCampaignManager: {
26
27
  value?: string;
27
28
  }[]>;
28
29
  registerAvailableCampaignsHandler: (callback: (campaigns: NamiCampaign[]) => void) => (() => void);
30
+ getProductGroups: (label?: string | null, withUrl?: string | null) => Promise<string[]>;
29
31
  };
@@ -24,7 +24,7 @@ export declare const NamiPaywallManager: {
24
24
  isHidden: () => Promise<boolean>;
25
25
  isPaywallOpen: () => Promise<boolean>;
26
26
  buySkuCancel: () => void;
27
- setProductDetails: (productDetails: string, allowOffers?: boolean) => void;
27
+ setProductDetails: (productDetails: string, allowOffers: boolean) => void;
28
28
  setAppSuppliedVideoDetails: (url: string, name?: string) => void;
29
29
  allowUserInteraction: (allowed: boolean) => void;
30
30
  };
@@ -2,4 +2,4 @@
2
2
  * Auto-generated file. Do not edit manually.
3
3
  * React Native Nami SDK version.
4
4
  */
5
- export declare const NAMI_REACT_NATIVE_VERSION = "3.3.3";
5
+ export declare const NAMI_REACT_NATIVE_VERSION = "3.3.5";
@@ -16,10 +16,14 @@ RCT_EXTERN_METHOD(allCampaigns:(RCTPromiseResolveBlock)resolve rejecter:(RCTProm
16
16
 
17
17
  RCT_EXTERN_METHOD(isCampaignAvailable:(nullable NSString *)campaignSource resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
18
18
 
19
+ RCT_EXTERN_METHOD(isFlow:(nullable NSString *)label withUrl:(nullable NSString *)withUrl resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
20
+
19
21
  RCT_EXTERN_METHOD(refresh:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
20
22
 
21
23
  RCT_EXTERN_METHOD(registerAvailableCampaignsHandler)
22
24
 
25
+ RCT_EXTERN_METHOD(productGroups:(nullable NSString *)label withUrl:(nullable NSString *)withUrl resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
26
+
23
27
  + (BOOL)requiresMainQueueSetup {
24
28
  return YES;
25
29
  }
@@ -289,6 +289,22 @@ class RNNamiCampaignManager: RCTEventEmitter {
289
289
  resolve(isCampaignAvailable)
290
290
  }
291
291
 
292
+ @objc(isFlow:withUrl:resolver:rejecter:)
293
+ func isFlow(
294
+ label: String?,
295
+ withUrl: String?,
296
+ resolve: @escaping RCTPromiseResolveBlock,
297
+ reject _: @escaping RCTPromiseRejectBlock
298
+ ) {
299
+ var url: URL?
300
+ if let withUrl = withUrl {
301
+ url = URL(string: withUrl)
302
+ }
303
+
304
+ let result = NamiCampaignManager.isFlow(label: label, url: url)
305
+ resolve(result)
306
+ }
307
+
292
308
  @objc(refresh:rejecter:)
293
309
  func refresh(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
294
310
  NamiCampaignManager.refresh { campaigns in
@@ -304,4 +320,20 @@ class RNNamiCampaignManager: RCTEventEmitter {
304
320
  self.safeSend(withName: "AvailableCampaignsChanged", body: dictionaries)
305
321
  }
306
322
  }
323
+
324
+ @objc(productGroups:withUrl:resolver:rejecter:)
325
+ func productGroups(
326
+ label: String?,
327
+ withUrl: String?,
328
+ resolve: @escaping RCTPromiseResolveBlock,
329
+ reject _: @escaping RCTPromiseRejectBlock
330
+ ) {
331
+ var url: URL?
332
+ if let withUrl = withUrl {
333
+ url = URL(string: withUrl)
334
+ }
335
+
336
+ let productGroups = NamiCampaignManager.productGroups(label: label, url: url)
337
+ resolve(productGroups)
338
+ }
307
339
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-nami-sdk",
3
- "version": "3.3.3",
4
- "description": "React Native SDK for Nami - No-code paywall management with A/B testing.",
3
+ "version": "3.3.5",
4
+ "description": "React Native SDK for Nami - No-code paywall and onboarding flows with A/B testing.",
5
5
  "main": "index.ts",
6
6
  "types": "dist/index.d.ts",
7
7
  "codegenConfig": {
@@ -35,7 +35,6 @@
35
35
  "keywords": [
36
36
  "in-app-purchase",
37
37
  "ios",
38
- "ipados",
39
38
  "tvos",
40
39
  "paywall",
41
40
  "react-native",
@@ -43,7 +42,8 @@
43
42
  "subscriptions",
44
43
  "iap",
45
44
  "play-billing",
46
- "payments"
45
+ "payments",
46
+ "onboarding"
47
47
  ],
48
48
  "author": {
49
49
  "username": "hellonami",
@@ -52,12 +52,8 @@
52
52
  },
53
53
  "contributors": [
54
54
  {
55
- "name": "Francisco Pena",
56
- "email": "francisco.pena@namiml.com"
57
- },
58
- {
59
- "name": "Kendall Gelner",
60
- "email": "kendall.gelner@namiml.com"
55
+ "name": "Dan Burcaw",
56
+ "email": "dan.burcaw@namiml.com"
61
57
  }
62
58
  ],
63
59
  "license": "SEE LICENSE FILE",
@@ -70,11 +66,6 @@
70
66
  "react": ">=18",
71
67
  "react-native": ">=0.73"
72
68
  },
73
- "peerDependenciesMeta": {
74
- "react-native": {
75
- "optional": true
76
- }
77
- },
78
69
  "devDependencies": {
79
70
  "@react-native/eslint-config": "^0.80.0",
80
71
  "@types/jest": "^29.5.2",
@@ -21,7 +21,7 @@ Pod::Spec.new do |s|
21
21
  s.requires_arc = true
22
22
  s.swift_version = '5.0' # or your supported version
23
23
 
24
- s.dependency 'Nami', '3.3.3'
24
+ s.dependency 'Nami', '3.3.5'
25
25
  s.dependency 'React'
26
26
 
27
27
  pod_target_xcconfig = {
@@ -78,6 +78,8 @@ export interface Spec extends TurboModule {
78
78
 
79
79
  isCampaignAvailable(source?: string): Promise<boolean>;
80
80
 
81
+ isFlow(label?: string | null, withUrl?: string | null): Promise<boolean>;
82
+
81
83
  refresh(): Promise<
82
84
  {
83
85
  id?: string;
@@ -90,6 +92,11 @@ export interface Spec extends TurboModule {
90
92
  >;
91
93
 
92
94
  registerAvailableCampaignsHandler(): void;
95
+
96
+ productGroups(
97
+ label: string | null,
98
+ withUrl: string | null,
99
+ ): Promise<string[]>;
93
100
  }
94
101
 
95
102
  export default TurboModuleRegistry.getEnforcing<Spec>('RNNamiCampaignManager');
@@ -76,7 +76,7 @@ export interface Spec extends TurboModule {
76
76
 
77
77
  buySkuCancel(): void;
78
78
 
79
- setProductDetails(productDetails: string, allowOffers?: boolean): void;
79
+ setProductDetails(productDetails: string, allowOffers: boolean): void;
80
80
 
81
81
  setAppSuppliedVideoDetails(url: string, name?: string): void;
82
82
 
@@ -97,6 +97,10 @@ export const NamiCampaignManager = {
97
97
  );
98
98
  },
99
99
 
100
+ isFlow: async (label?: string | null, withUrl?: string | null) => {
101
+ return await RNNamiCampaignManager.isFlow(label, withUrl);
102
+ },
103
+
100
104
  refresh: async () => {
101
105
  return await RNNamiCampaignManager.refresh();
102
106
  },
@@ -111,4 +115,11 @@ export const NamiCampaignManager = {
111
115
  RNNamiCampaignManager.registerAvailableCampaignsHandler?.();
112
116
  return () => sub.remove();
113
117
  },
118
+
119
+ getProductGroups: async (label?: string | null, withUrl?: string | null) => {
120
+ return await RNNamiCampaignManager.productGroups(
121
+ label ?? null,
122
+ withUrl ?? null,
123
+ );
124
+ },
114
125
  };
@@ -16,7 +16,18 @@ export const NamiOverlayControl = {
16
16
  emitter,
17
17
 
18
18
  presentOverlay(): Promise<void> {
19
- return RNNamiOverlayControl.presentOverlay();
19
+ return RNNamiOverlayControl.presentOverlay().catch((error) => {
20
+ // Handle the case where an overlay is already active
21
+ if (error.code === 'OVERLAY_ALREADY_ACTIVE') {
22
+ console.warn(
23
+ 'NamiOverlayControl: An overlay is already being presented. Ignoring duplicate call.',
24
+ );
25
+ // Return a resolved promise to maintain backward compatibility
26
+ return Promise.resolve();
27
+ }
28
+ // Re-throw other errors
29
+ throw error;
30
+ });
20
31
  },
21
32
 
22
33
  finishOverlay(result?: any): Promise<void> {
@@ -106,7 +106,7 @@ export const NamiPaywallManager = {
106
106
  RNNamiPaywallManager.buySkuCancel();
107
107
  },
108
108
 
109
- setProductDetails: (productDetails: string, allowOffers?: boolean): void => {
109
+ setProductDetails: (productDetails: string, allowOffers: boolean): void => {
110
110
  RNNamiPaywallManager.setProductDetails(productDetails, allowOffers);
111
111
  },
112
112
 
package/src/version.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Auto-generated file. Do not edit manually.
3
3
  * React Native Nami SDK version.
4
4
  */
5
- export const NAMI_REACT_NATIVE_VERSION = '3.3.3';
5
+ export const NAMI_REACT_NATIVE_VERSION = '3.3.5';