react-native-nami-sdk 3.3.3 → 3.3.4
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.
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/namiml/reactnative/NamiCampaignManagerBridge.kt +102 -19
- package/android/src/main/java/com/namiml/reactnative/NamiFlowManagerBridge.kt +5 -1
- package/dist/specs/NativeNamiCampaignManager.d.ts +1 -0
- package/dist/specs/NativeNamiPaywallManager.d.ts +1 -1
- package/dist/src/NamiCampaignManager.d.ts +1 -0
- package/dist/src/NamiPaywallManager.d.ts +1 -1
- package/dist/src/version.d.ts +1 -1
- package/ios/NamiCampaignManagerBridge.m +2 -0
- package/ios/NamiCampaignManagerBridge.swift +16 -0
- package/package.json +6 -15
- package/react-native-nami-sdk.podspec +1 -1
- package/specs/NativeNamiCampaignManager.ts +2 -0
- package/specs/NativeNamiPaywallManager.ts +1 -1
- package/src/NamiCampaignManager.ts +4 -0
- package/src/NamiPaywallManager.ts +1 -1
- package/src/version.ts +1 -1
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
89
|
-
amazonImplementation "com.namiml:sdk-amazon:3.3.
|
|
88
|
+
playImplementation "com.namiml:sdk-android:3.3.4"
|
|
89
|
+
amazonImplementation "com.namiml:sdk-amazon:3.3.4"
|
|
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),
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,35 @@ class NamiCampaignManagerBridgeModule internal constructor(
|
|
|
279
340
|
val isCampaignAvailable =
|
|
280
341
|
when {
|
|
281
342
|
campaignSource == null -> NamiCampaignManager.isCampaignAvailable()
|
|
282
|
-
campaignSource.toUri().scheme != null ->
|
|
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 = if (!withUrl.isNullOrEmpty()) {
|
|
358
|
+
val parsedUri = Uri.parse(withUrl)
|
|
359
|
+
if (parsedUri.scheme.isNullOrEmpty()) {
|
|
360
|
+
promise.reject("ISFLOW_ERROR", "Invalid URL format: missing scheme", null)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
parsedUri
|
|
364
|
+
} else null
|
|
365
|
+
val result = NamiCampaignManager.isFlow(label = label, uri = uri)
|
|
366
|
+
promise.resolve(result)
|
|
367
|
+
} catch (e: Exception) {
|
|
368
|
+
promise.reject("ISFLOW_ERROR", "Failed to check if campaign is flow: ${e.message}", e)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
289
372
|
@ReactMethod
|
|
290
373
|
fun refresh(promise: Promise) {
|
|
291
374
|
NamiCampaignManager.refresh { campaigns ->
|
|
@@ -52,7 +52,11 @@ class NamiFlowManagerBridgeModule internal constructor(
|
|
|
52
52
|
@ReactMethod
|
|
53
53
|
fun resume() {
|
|
54
54
|
Handler(Looper.getMainLooper()).postDelayed({
|
|
55
|
-
|
|
55
|
+
if (reactApplicationContext.hasCurrentActivity()) {
|
|
56
|
+
NamiFlowManager.resume(reactApplicationContext.currentActivity)
|
|
57
|
+
} else {
|
|
58
|
+
NamiFlowManager.resume()
|
|
59
|
+
}
|
|
56
60
|
}, 100L)
|
|
57
61
|
}
|
|
58
62
|
|
|
@@ -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;
|
|
@@ -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
|
|
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;
|
|
@@ -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
|
|
27
|
+
setProductDetails: (productDetails: string, allowOffers: boolean) => void;
|
|
28
28
|
setAppSuppliedVideoDetails: (url: string, name?: string) => void;
|
|
29
29
|
allowUserInteraction: (allowed: boolean) => void;
|
|
30
30
|
};
|
package/dist/src/version.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ 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)
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nami-sdk",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "React Native SDK for Nami - No-code paywall
|
|
3
|
+
"version": "3.3.4",
|
|
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": "
|
|
56
|
-
"email": "
|
|
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",
|
|
@@ -76,7 +76,7 @@ export interface Spec extends TurboModule {
|
|
|
76
76
|
|
|
77
77
|
buySkuCancel(): void;
|
|
78
78
|
|
|
79
|
-
setProductDetails(productDetails: string, allowOffers
|
|
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
|
},
|
|
@@ -106,7 +106,7 @@ export const NamiPaywallManager = {
|
|
|
106
106
|
RNNamiPaywallManager.buySkuCancel();
|
|
107
107
|
},
|
|
108
108
|
|
|
109
|
-
setProductDetails: (productDetails: string, allowOffers
|
|
109
|
+
setProductDetails: (productDetails: string, allowOffers: boolean): void => {
|
|
110
110
|
RNNamiPaywallManager.setProductDetails(productDetails, allowOffers);
|
|
111
111
|
},
|
|
112
112
|
|
package/src/version.ts
CHANGED