react-native-nami-sdk 3.2.2 → 3.2.3-2

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/README.md CHANGED
@@ -2,20 +2,18 @@
2
2
 
3
3
  # React Native Bridge for the Nami SDK
4
4
 
5
- Nami is easy subscriptions & in-app purchases, with powerful built-in paywalls and A/B testing.
5
+ Nami ML gives you everything you need to power your paywall, streamline subscription management, and drive revenue growth through instantly deployable paywalls, precise targeting and segmentation, and enterprise-grade security and scaleability.
6
6
 
7
- This library helps you easily offer in-app purchases and subscriptions using Apple App Store StoreKit & Google Play Billing APIs.
8
- - No IAP code to write.
9
- - Focus on your app experience.
10
- - All edge cases are handled and no server is required.
11
- - Includes is powerful built-in paywalls templates, built with native SwiftUI and Jetpack Compose
12
- - Update paywalls easily using a browser-based paywall CMS.
13
- - Conduct paywall A/B tests, to improve your conversion rate.
14
- - Robust subscription analytics, webhooks, and much more.
7
+ Go beyond basic IAP and focus on results with:
15
8
 
16
- Requires an account with Nami. The free tier is generous and includes everything you need to get up and running.
9
+ * Library of smart paywall templates to choose from, implemented natively
10
+ * No-code paywall creator so you can launch a new paywall design instantly, without submitting an app update
11
+ * Experimentation engine to run paywall A/B tests so you can improve your conversion rates
12
+ * Built-in IAP & subscription management and analytics, so you don't need another solution
17
13
 
18
- See https://www.namiml.com for more details and to create a free account.
14
+ Nami is simple adopt while giving you the tools you need to improve revenue. Our free tier is generous, and gives you everything you need to get started. [Sign up for a free account](https://app.namiml.com/join/)
15
+
16
+ Get started by heading over to our [quick start guide](https://learn.namiml.com/public-docs/get-started/quickstart-guide)
19
17
 
20
18
  ## Getting started with React Native and Nami
21
19
 
@@ -37,7 +37,7 @@ android {
37
37
  buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
38
38
  defaultConfig {
39
39
  minSdkVersion 22
40
- targetSdkVersion 33
40
+ targetSdkVersion 34
41
41
  versionCode 1
42
42
  versionName "1.0"
43
43
  }
@@ -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.2.2"
89
- amazonImplementation "com.namiml:sdk-amazon:3.2.2"
88
+ playImplementation "com.namiml:sdk-android:3.2.3.1"
89
+ amazonImplementation "com.namiml:sdk-amazon:3.2.3.1"
90
90
 
91
91
  implementation "com.facebook.react:react-native:+" // From node_modules
92
92
  coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
@@ -106,7 +106,7 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
106
106
  } else {
107
107
  Arguments.createArray()
108
108
  }
109
- val settingsList = mutableListOf("extendedClientInfo:react-native:3.2.2")
109
+ val settingsList = mutableListOf("extendedClientInfo:react-native:3.2.3")
110
110
  namiCommandsReact?.toArrayList()?.filterIsInstance<String>()?.let { commandsFromReact ->
111
111
  settingsList.addAll(commandsFromReact)
112
112
  }
@@ -21,7 +21,7 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
21
21
  const val CAMPAIGN_LABEL = "campaignLabel"
22
22
  const val PAYWALL_ID = "paywallId"
23
23
  const val ACTION = "action"
24
- const val SKU_ID = "skuId"
24
+ const val SKU = "sku"
25
25
  const val PURCHASE_ERROR = "purchaseError"
26
26
  const val PURCHASES = "purchases"
27
27
  const val CAMPAIGN_NAME = "campaignName"
@@ -51,7 +51,13 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
51
51
  }
52
52
 
53
53
  @ReactMethod
54
- fun launch(label: String?, withUrl: String?, context: ReadableMap?, resultCallback: Callback, actionCallback: Callback) {
54
+ fun launch(
55
+ label: String?,
56
+ withUrl: String?,
57
+ context: ReadableMap?,
58
+ resultCallback: Callback,
59
+ actionCallback: Callback,
60
+ ) {
55
61
  var theActivity: Activity? = null
56
62
  if (reactApplicationContext.hasCurrentActivity()) {
57
63
  theActivity = reactApplicationContext.getCurrentActivity()
@@ -61,7 +67,7 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
61
67
  if (context != null) {
62
68
  val productGroups: MutableList<String> = mutableListOf()
63
69
  val customAttributes: MutableMap<String, String> = mutableMapOf()
64
- val customObject: MutableMap<String, String> = mutableMapOf()
70
+ var customObject: MutableMap<String, Any> = mutableMapOf()
65
71
 
66
72
  if (context.hasKey("productGroups")) {
67
73
  val groups = context.getArray("productGroups")
@@ -91,12 +97,11 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
91
97
  if (context.hasKey("customObject")) {
92
98
  val attr = context.getMap("customObject")
93
99
  if (attr != null) {
94
- val keyIterator = attr.keySetIterator()
95
- while (keyIterator.hasNextKey()) {
96
- val key = keyIterator.nextKey()
97
- customObject[key] = attr.getString(key) ?: ""
100
+ try {
101
+ customObject = attr.toHashMap().toMutableMap()
102
+ } catch (e: Exception) {
103
+ Log.d(LOG_TAG, "Unable to parse PaywallLaunchContext customObject $customObject")
98
104
  }
99
- Log.d(LOG_TAG, "customObject $customObject")
100
105
  }
101
106
  }
102
107
 
@@ -148,48 +153,59 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
148
153
  actionCallback: Callback,
149
154
  ) {
150
155
  val actionString = paywallEvent.action.toString()
151
- val skuString = paywallEvent.sku?.skuId ?: ""
152
156
 
153
157
  val purchasesArray = createPurchaseArray(paywallEvent.purchases)
154
158
 
155
- val resultMap = Arguments.createMap().apply {
156
- putString(CAMPAIGN_ID, paywallEvent.campaignId)
157
- putString(CAMPAIGN_LABEL, paywallEvent.campaignLabel ?: "")
158
- putString(PAYWALL_ID, paywallEvent.paywallId)
159
- putString(ACTION, actionString)
160
- putString(SKU_ID, skuString)
161
- putString(PURCHASE_ERROR, paywallEvent.purchaseError ?: "")
162
- putArray(PURCHASES, purchasesArray)
163
- putString(CAMPAIGN_NAME, paywallEvent.campaignName ?: "")
164
- putString(CAMPAIGN_TYPE, paywallEvent.campaignType ?: "")
165
- putString(CAMPAIGN_URL, paywallEvent.campaignUrl ?: "")
166
- putString(PAYWALL_NAME, paywallEvent.paywallName ?: "")
167
- putString(SEGMENT_ID, paywallEvent.segmentId ?: "")
168
- putString(EXTERNAL_SEGMENT_ID, paywallEvent.externalSegmentId ?: "")
169
- putString(DEEP_LINK_URL, paywallEvent.deeplinkUrl ?: "")
159
+ val resultMap =
160
+ Arguments.createMap().apply {
161
+ putString(CAMPAIGN_ID, paywallEvent.campaignId)
162
+ putString(CAMPAIGN_LABEL, paywallEvent.campaignLabel ?: "")
163
+ putString(PAYWALL_ID, paywallEvent.paywallId)
164
+ putString(ACTION, actionString)
165
+ putString(PURCHASE_ERROR, paywallEvent.purchaseError ?: "")
166
+ putArray(PURCHASES, purchasesArray)
167
+ putString(CAMPAIGN_NAME, paywallEvent.campaignName ?: "")
168
+ putString(CAMPAIGN_TYPE, paywallEvent.campaignType ?: "")
169
+ putString(CAMPAIGN_URL, paywallEvent.campaignUrl ?: "")
170
+ putString(PAYWALL_NAME, paywallEvent.paywallName ?: "")
171
+ putString(SEGMENT_ID, paywallEvent.segmentId ?: "")
172
+ putString(EXTERNAL_SEGMENT_ID, paywallEvent.externalSegmentId ?: "")
173
+ putString(DEEP_LINK_URL, paywallEvent.deeplinkUrl ?: "")
174
+ }
170
175
 
176
+ if (paywallEvent.sku != null) {
177
+ val skuMap =
178
+ Arguments.createMap().apply {
179
+ putString("id", paywallEvent.sku?.id ?: "")
180
+ putString("skuId", paywallEvent.sku?.skuId ?: "")
181
+ putString("name", paywallEvent.sku?.name ?: "")
182
+ putString("type", paywallEvent.sku?.type.toString().lowercase())
183
+ }
184
+ resultMap.putMap(SKU, skuMap)
171
185
  }
172
186
 
173
187
  if (paywallEvent.componentChange != null) {
174
- val componentChangeMap = Arguments.createMap().apply {
175
- putString("id", paywallEvent.componentChange?.id ?: "")
176
- putString("name", paywallEvent.componentChange?.name ?: "")
177
- }
188
+ val componentChangeMap =
189
+ Arguments.createMap().apply {
190
+ putString("id", paywallEvent.componentChange?.id ?: "")
191
+ putString("name", paywallEvent.componentChange?.name ?: "")
192
+ }
178
193
 
179
194
  resultMap.putMap(COMPONENT_CHANGE, componentChangeMap)
180
195
  }
181
196
 
182
197
  if (paywallEvent.videoMetadata != null) {
183
- val videoMetadataMap = Arguments.createMap().apply {
184
- putString("id", paywallEvent.videoMetadata?.id ?: "")
185
- putString("name", paywallEvent.videoMetadata?.name ?: "")
186
- putString("url", paywallEvent.videoMetadata?.url ?: "")
187
- putBoolean("autoplayVideo", paywallEvent.videoMetadata?.autoplayVideo ?: false)
188
- putBoolean("muteByDefault", paywallEvent.videoMetadata?.muteByDefault ?: false)
189
- putBoolean("loopVideo", paywallEvent.videoMetadata?.loopVideo ?: false)
190
- putDouble("contentDuration", paywallEvent.videoMetadata?.contentDuration ?: 0.0)
191
- putDouble("contentTimecode", paywallEvent.videoMetadata?.contentTimecode ?: 0.0)
192
- }
198
+ val videoMetadataMap =
199
+ Arguments.createMap().apply {
200
+ putString("id", paywallEvent.videoMetadata?.id ?: "")
201
+ putString("name", paywallEvent.videoMetadata?.name ?: "")
202
+ putString("url", paywallEvent.videoMetadata?.url ?: "")
203
+ putBoolean("autoplayVideo", paywallEvent.videoMetadata?.autoplayVideo ?: false)
204
+ putBoolean("muteByDefault", paywallEvent.videoMetadata?.muteByDefault ?: false)
205
+ putBoolean("loopVideo", paywallEvent.videoMetadata?.loopVideo ?: false)
206
+ putDouble("contentDuration", paywallEvent.videoMetadata?.contentDuration ?: 0.0)
207
+ putDouble("contentTimecode", paywallEvent.videoMetadata?.contentTimecode ?: 0.0)
208
+ }
193
209
 
194
210
  resultMap.putMap(VIDEO_METADATA, videoMetadataMap)
195
211
  }
@@ -213,7 +229,10 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
213
229
  }
214
230
  }
215
231
 
216
- private fun emitEvent(event: String, map: WritableMap) {
232
+ private fun emitEvent(
233
+ event: String,
234
+ map: WritableMap,
235
+ ) {
217
236
  val emitter = reactApplicationContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
218
237
  if (emitter is DeviceEventManagerModule.RCTDeviceEventEmitter) {
219
238
  emitter.emit(event, map)
@@ -222,7 +241,10 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
222
241
  }
223
242
  }
224
243
 
225
- private fun handleResult(result: LaunchCampaignResult, resultCallback: Callback) {
244
+ private fun handleResult(
245
+ result: LaunchCampaignResult,
246
+ resultCallback: Callback,
247
+ ) {
226
248
  val resultMap = Arguments.createMap()
227
249
  when (result) {
228
250
  is LaunchCampaignResult.Success -> {
@@ -258,18 +280,22 @@ class NamiCampaignManagerBridgeModule(reactContext: ReactApplicationContext) :
258
280
  }
259
281
 
260
282
  @ReactMethod
261
- fun isCampaignAvailable(campaignSource: String?, promise: Promise) {
262
- val isCampaignAvailable = when {
263
- campaignSource == null -> NamiCampaignManager.isCampaignAvailable()
264
- Uri.parse(campaignSource)?.scheme != null -> NamiCampaignManager.isCampaignAvailable(Uri.parse(campaignSource))
265
- else -> NamiCampaignManager.isCampaignAvailable(campaignSource)
266
- }
283
+ fun isCampaignAvailable(
284
+ campaignSource: String?,
285
+ promise: Promise,
286
+ ) {
287
+ val isCampaignAvailable =
288
+ when {
289
+ campaignSource == null -> NamiCampaignManager.isCampaignAvailable()
290
+ Uri.parse(campaignSource)?.scheme != null -> NamiCampaignManager.isCampaignAvailable(Uri.parse(campaignSource))
291
+ else -> NamiCampaignManager.isCampaignAvailable(campaignSource)
292
+ }
267
293
  promise.resolve(isCampaignAvailable)
268
294
  }
269
295
 
270
296
  @ReactMethod
271
297
  fun refresh() {
272
- NamiCampaignManager.refresh() { }
298
+ NamiCampaignManager.refresh { }
273
299
  }
274
300
 
275
301
  @ReactMethod
@@ -6,6 +6,7 @@ import subprocess
6
6
 
7
7
  # Regex to validate version numbers
8
8
  PROD_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
9
+ PROD_VERSION_EXT_RE = re.compile(r"^\d+\.\d+\.\d+-\d$")
9
10
  PRERELEASE_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+-(alpha|beta|rc)\.\d+$")
10
11
 
11
12
  early_access = str(os.getenv("EARLY_ACCESS"))
@@ -21,7 +22,7 @@ git_long_hash = (
21
22
  )
22
23
 
23
24
  # Check what kind of release this is and guard against non-conforming version numbers
24
- if PROD_VERSION_RE.match(nami_sdk_version):
25
+ if PROD_VERSION_RE.match(nami_sdk_version) or PROD_VERSION_EXT_RE.match(nami_sdk_version):
25
26
  if early_access == "true":
26
27
  print(f"Early access value ('{early_access}') is not compatible with production version format '{nami_sdk_version}'")
27
28
  sys.exit(1)
package/ios/Nami.m CHANGED
@@ -50,7 +50,7 @@ RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict completion: (RCTResponse
50
50
  }
51
51
 
52
52
  // Start commands with header iformation for Nami to let them know this is a React client.
53
- NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:3.2.2"]];
53
+ NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:3.2.3"]];
54
54
 
55
55
  // Add additional namiCommands app may have sent in.
56
56
  NSObject *appCommandStrings = configDict[@"namiCommands"];
@@ -106,6 +106,15 @@ class RNNamiCampaignManager: RCTEventEmitter {
106
106
 
107
107
  let dictionaries = paywallEvent.purchases.map { purchase in RNNamiPurchaseManager.purchaseToPurchaseDict(purchase) }
108
108
 
109
+ var skuDict: [String: Any?] = [:]
110
+
111
+ if let sku = paywallEvent.sku {
112
+ skuDict["id"] = sku.id
113
+ skuDict["name"] = sku.name
114
+ skuDict["skuId"] = sku.skuId
115
+ skuDict["type"] = sku.type.description
116
+ }
117
+
109
118
  var componentChange: [String: Any?] = [:]
110
119
 
111
120
  if let eventComponentChange = paywallEvent.componentChange {
@@ -137,7 +146,7 @@ class RNNamiCampaignManager: RCTEventEmitter {
137
146
  "segmentId": paywallEvent.segmentId,
138
147
  "externalSegmentId": paywallEvent.externalSegmentId,
139
148
  "action": actionString,
140
- "skuId": paywallEvent.sku?.skuId,
149
+ "sku": skuDict,
141
150
  "purchaseError": errorSting,
142
151
  "purchases": dictionaries,
143
152
  "deeplinkUrl": paywallEvent.deeplinkUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nami-sdk",
3
- "version": "3.2.2",
3
+ "version": "3.2.3-2",
4
4
  "description": "React Native Module for Nami - Easy subscriptions & in-app purchases, with powerful built-in paywalls and A/B testing.",
5
5
  "main": "index.ts",
6
6
  "types": "index.d.ts",
@@ -20,7 +20,7 @@ Pod::Spec.new do |s|
20
20
  s.source_files = "ios/**/*.{h,m,swift}"
21
21
  s.requires_arc = true
22
22
 
23
- s.dependency 'Nami', '3.2.2'
23
+ s.dependency 'Nami', '3.2.3'
24
24
  s.dependency 'React'
25
25
 
26
26
  end