react-native-nami-sdk 1.2.1 → 2.0.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/.github/workflows/build.yml +68 -0
- package/README.md +1 -1
- package/android/build.gradle +7 -10
- package/android/src/main/AndroidManifest.xml +1 -2
- package/android/src/main/java/com/nami/reactlibrary/NamiBridgeModule.kt +22 -7
- package/android/src/main/java/com/nami/reactlibrary/NamiCustomerManagerBridge.kt +21 -16
- package/android/src/main/java/com/nami/reactlibrary/NamiEmitter.kt +15 -33
- package/android/src/main/java/com/nami/reactlibrary/NamiPurchaseManagerBridge.kt +7 -0
- package/android/src/main/java/com/nami/reactlibrary/NamiUtil.kt +72 -35
- package/build-utils/preflight.py +45 -0
- package/ios/Nami.m +15 -2
- package/ios/NamiBridgeUtil.h +2 -0
- package/ios/NamiBridgeUtil.m +24 -0
- package/ios/NamiCustomerManager.m +1 -12
- package/ios/NamiEmitter.m +78 -2
- package/ios/NamiPaywallManagerBridge.m +1 -1
- package/ios/NamiPurchaseManagerBridge.m +51 -12
- package/ios/Podfile +46 -38
- package/ios/RNNami.xcodeproj/project.pbxproj +4 -2
- package/package.json +15 -11
- package/react-native-nami-sdk.podspec +3 -3
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Build Bridge
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
ref:
|
|
7
|
+
description: "Git Tag, Branch or SHA to build"
|
|
8
|
+
required: true
|
|
9
|
+
default: "develop"
|
|
10
|
+
earlyaccess:
|
|
11
|
+
description: "Is this a early access build?"
|
|
12
|
+
default: true
|
|
13
|
+
type: boolean
|
|
14
|
+
secrets:
|
|
15
|
+
BUILD_USER_PAT:
|
|
16
|
+
required: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
name: Build Bridge release
|
|
21
|
+
timeout-minutes: 30
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
actions: write
|
|
25
|
+
contents: write
|
|
26
|
+
id-token: write
|
|
27
|
+
steps:
|
|
28
|
+
|
|
29
|
+
# clone the repo at a specific version
|
|
30
|
+
- name: Checkout ${{ inputs.ref }}
|
|
31
|
+
uses: actions/checkout@v2
|
|
32
|
+
with:
|
|
33
|
+
ref: ${{ inputs.ref }}
|
|
34
|
+
|
|
35
|
+
# human error checks
|
|
36
|
+
- name: Set up Python
|
|
37
|
+
uses: actions/setup-python@v1
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.10"
|
|
40
|
+
|
|
41
|
+
- name: Release Preflight Checks
|
|
42
|
+
run: |
|
|
43
|
+
python3 build-utils/preflight.py
|
|
44
|
+
env:
|
|
45
|
+
NAMI_SDK_VERSION: ${{ inputs.version }}
|
|
46
|
+
|
|
47
|
+
- name: Setup .npmrc
|
|
48
|
+
run: |
|
|
49
|
+
cat .npmrc
|
|
50
|
+
|
|
51
|
+
- name: Run Yarn
|
|
52
|
+
run: |
|
|
53
|
+
yarn --frozen-lockfile
|
|
54
|
+
env:
|
|
55
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
56
|
+
|
|
57
|
+
- name: Publish
|
|
58
|
+
run: |
|
|
59
|
+
npm publish --access public
|
|
60
|
+
env:
|
|
61
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
62
|
+
|
|
63
|
+
- name: Create Github Release
|
|
64
|
+
run: |-
|
|
65
|
+
#sh gh-release-command.sh
|
|
66
|
+
echo "Release me"
|
|
67
|
+
env:
|
|
68
|
+
GITHUB_TOKEN: ${{ github.token }}
|
package/README.md
CHANGED
package/android/build.gradle
CHANGED
|
@@ -10,10 +10,7 @@ import groovy.json.JsonSlurper
|
|
|
10
10
|
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
|
|
11
11
|
// original location:
|
|
12
12
|
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
|
|
13
|
-
def
|
|
14
|
-
def DEFAULT_BUILD_TOOLS_VERSION = '29.0.3'
|
|
15
|
-
def DEFAULT_MIN_SDK_VERSION = 21
|
|
16
|
-
def DEFAULT_TARGET_SDK_VERSION = 30
|
|
13
|
+
def DEFAULT_BUILD_TOOLS_VERSION = '30.0.2'
|
|
17
14
|
def safeExtGet(prop, fallback) {
|
|
18
15
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
19
16
|
}
|
|
@@ -28,8 +25,8 @@ buildscript {
|
|
|
28
25
|
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
|
|
29
26
|
if (project == rootProject) {
|
|
30
27
|
repositories {
|
|
28
|
+
mavenCentral()
|
|
31
29
|
google()
|
|
32
|
-
jcenter()
|
|
33
30
|
}
|
|
34
31
|
dependencies {
|
|
35
32
|
classpath 'com.android.tools.build:gradle:4.2.1'
|
|
@@ -38,11 +35,11 @@ buildscript {
|
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
android {
|
|
41
|
-
compileSdkVersion
|
|
38
|
+
compileSdkVersion 31
|
|
42
39
|
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
|
|
43
40
|
defaultConfig {
|
|
44
|
-
minSdkVersion
|
|
45
|
-
targetSdkVersion
|
|
41
|
+
minSdkVersion 26
|
|
42
|
+
targetSdkVersion 31
|
|
46
43
|
versionCode 1
|
|
47
44
|
versionName "1.0"
|
|
48
45
|
}
|
|
@@ -58,6 +55,7 @@ android {
|
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
repositories {
|
|
58
|
+
mavenCentral()
|
|
61
59
|
// ref: https://www.baeldung.com/maven-local-repository
|
|
62
60
|
maven { url("https://nami-android.s3.amazonaws.com/") }
|
|
63
61
|
maven {
|
|
@@ -69,7 +67,6 @@ repositories {
|
|
|
69
67
|
url("$rootDir/../node_modules/jsc-android/dist")
|
|
70
68
|
}
|
|
71
69
|
google()
|
|
72
|
-
jcenter()
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
dependencies {
|
|
@@ -77,7 +74,7 @@ dependencies {
|
|
|
77
74
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
78
75
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
|
79
76
|
|
|
80
|
-
implementation "com.namiml:sdk-android:
|
|
77
|
+
implementation "com.namiml:sdk-android:2.0.0"
|
|
81
78
|
|
|
82
79
|
implementation 'com.facebook.react:react-native:+' // From node_modules
|
|
83
80
|
}
|
|
@@ -15,6 +15,7 @@ import com.facebook.react.bridge.WritableNativeArray
|
|
|
15
15
|
import com.namiml.Nami
|
|
16
16
|
import com.namiml.NamiConfiguration
|
|
17
17
|
import com.namiml.NamiExternalIdentifierType
|
|
18
|
+
import com.namiml.NamiLanguageCode
|
|
18
19
|
import com.namiml.NamiLogLevel
|
|
19
20
|
|
|
20
21
|
class NamiBridgeModule(reactContext: ReactApplicationContext) :
|
|
@@ -26,6 +27,7 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
26
27
|
private const val CONFIG_MAP_DEVELOPMENT_MODE_KEY = "developmentMode"
|
|
27
28
|
private const val CONFIG_MAP_BYPASS_STORE_KEY = "bypassStore"
|
|
28
29
|
private const val CONFIG_MAP_NAMI_COMMANDS_KEY = "namiCommands"
|
|
30
|
+
private const val CONFIG_MAP_LANGUAGE_CODE_KEY = "namiLanguageCode"
|
|
29
31
|
private const val PLATFORM_ID_ERROR_VALUE = "APPPLATFORMID_NOT_FOUND"
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -33,12 +35,6 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
33
35
|
return "NamiBridge"
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
// @ReactMethod
|
|
37
|
-
// public void sampleMethod(String stringArgument, int numberArgument, Callback callback) {
|
|
38
|
-
// // TODO: Implement some actually useful functionality
|
|
39
|
-
// callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument);
|
|
40
|
-
// }
|
|
41
|
-
|
|
42
38
|
@ReactMethod
|
|
43
39
|
fun configure(configDict: ReadableMap) {
|
|
44
40
|
|
|
@@ -106,13 +102,32 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
106
102
|
builder.bypassStore = true
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
val languageCode = if (configDict.hasKey(CONFIG_MAP_LANGUAGE_CODE_KEY)) {
|
|
106
|
+
configDict.getString(CONFIG_MAP_LANGUAGE_CODE_KEY)
|
|
107
|
+
} else {
|
|
108
|
+
null
|
|
109
|
+
}
|
|
110
|
+
languageCode?.let { code ->
|
|
111
|
+
NamiLanguageCode.values().find { it.code == code }.let { namiLanguageCode ->
|
|
112
|
+
if (namiLanguageCode == null) {
|
|
113
|
+
Log.w(
|
|
114
|
+
LOG_TAG,
|
|
115
|
+
"Nami language code from config dictionary \"$code\" not " +
|
|
116
|
+
"found in list of available Nami Language Codes:\n"
|
|
117
|
+
)
|
|
118
|
+
} else {
|
|
119
|
+
builder.namiLanguageCode = namiLanguageCode
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
109
124
|
val namiCommandsReact: ReadableArray? =
|
|
110
125
|
if (configDict.hasKey(CONFIG_MAP_NAMI_COMMANDS_KEY)) {
|
|
111
126
|
configDict.getArray(CONFIG_MAP_NAMI_COMMANDS_KEY)
|
|
112
127
|
} else {
|
|
113
128
|
Arguments.createArray()
|
|
114
129
|
}
|
|
115
|
-
val settingsList = mutableListOf("extendedClientInfo:react-native:
|
|
130
|
+
val settingsList = mutableListOf("extendedClientInfo:react-native:1.2.1")
|
|
116
131
|
namiCommandsReact?.toArrayList()?.filterIsInstance<String>()?.let { commandsFromReact ->
|
|
117
132
|
settingsList.addAll(commandsFromReact)
|
|
118
133
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package com.nami.reactlibrary
|
|
2
2
|
|
|
3
|
+
import android.util.Log
|
|
3
4
|
import com.facebook.react.bridge.Callback
|
|
4
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
6
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
7
|
import com.facebook.react.bridge.ReactMethod
|
|
7
|
-
import com.facebook.react.
|
|
8
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
9
|
+
import com.namiml.customer.CustomerJourneyState
|
|
8
10
|
import com.namiml.customer.NamiCustomerManager
|
|
9
11
|
|
|
10
12
|
class NamiCustomerManagerBridgeModule(reactContext: ReactApplicationContext) :
|
|
@@ -14,24 +16,27 @@ class NamiCustomerManagerBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
14
16
|
return "NamiCustomerManagerBridge"
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
init {
|
|
20
|
+
NamiCustomerManager.registerCustomerJourneyChangedListener { customerJourneyState ->
|
|
21
|
+
emitCustomerJourneyChanged(customerJourneyState)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private fun emitCustomerJourneyChanged(customerJourneyState: CustomerJourneyState) {
|
|
26
|
+
Log.i(LOG_TAG, "Emitting CustomerJourneyState changed")
|
|
27
|
+
try {
|
|
28
|
+
reactApplicationContext
|
|
29
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
30
|
+
.emit("CustomerJourneyStateChanged", customerJourneyState.toDict())
|
|
31
|
+
} catch (e: Exception) {
|
|
32
|
+
Log.e(LOG_TAG, "Caught Exception: " + e.message)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
@ReactMethod
|
|
18
37
|
fun currentCustomerJourneyState(resultsCallback: Callback) {
|
|
19
38
|
reactApplicationContext.runOnUiQueueThread {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
val formerSubscriber = journeyState?.formerSubscriber ?: false
|
|
23
|
-
val inGracePeriod = journeyState?.inGracePeriod ?: false
|
|
24
|
-
val inTrialPeriod = journeyState?.inTrialPeriod ?: false
|
|
25
|
-
val inIntroOfferPeriod = journeyState?.inIntroOfferPeriod ?: false
|
|
26
|
-
|
|
27
|
-
val journeyDict = WritableNativeMap().apply {
|
|
28
|
-
putBoolean("formerSubscriber", formerSubscriber)
|
|
29
|
-
putBoolean("inGracePeriod", inGracePeriod)
|
|
30
|
-
putBoolean("inTrialPeriod", inTrialPeriod)
|
|
31
|
-
putBoolean("inIntroOfferPeriod", inIntroOfferPeriod)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
resultsCallback.invoke(journeyDict)
|
|
39
|
+
resultsCallback.invoke(NamiCustomerManager.currentCustomerJourneyState().toDict())
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
}
|
|
@@ -11,7 +11,8 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
11
11
|
import com.namiml.billing.NamiPurchase
|
|
12
12
|
import com.namiml.billing.NamiPurchaseManager
|
|
13
13
|
import com.namiml.billing.NamiPurchaseState
|
|
14
|
-
import com.namiml.
|
|
14
|
+
import com.namiml.customer.CustomerJourneyState
|
|
15
|
+
import com.namiml.customer.NamiCustomerManager
|
|
15
16
|
import com.namiml.paywall.NamiPaywall
|
|
16
17
|
import com.namiml.paywall.NamiPaywallManager
|
|
17
18
|
import com.namiml.paywall.NamiSKU
|
|
@@ -32,15 +33,15 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
override fun initialize() {
|
|
35
|
-
NamiPaywallManager.registerSignInListener {
|
|
36
|
+
NamiPaywallManager.registerSignInListener { _, _, developerPaywallID ->
|
|
36
37
|
Log.i(LOG_TAG, "Sign in clicked with developerPaywallID $developerPaywallID")
|
|
37
38
|
|
|
38
|
-
emitSignInActivated(
|
|
39
|
+
emitSignInActivated(developerPaywallID)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
Log.i(LOG_TAG, "In Emitter Initialize()")
|
|
42
43
|
|
|
43
|
-
NamiPaywallManager.registerPaywallRaiseListener {
|
|
44
|
+
NamiPaywallManager.registerPaywallRaiseListener { _, paywallData, products, developerPaywallId ->
|
|
44
45
|
Log.i(
|
|
45
46
|
LOG_TAG,
|
|
46
47
|
"Products from registerPaywallRaiseListener callback are $products"
|
|
@@ -53,28 +54,24 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
53
54
|
NamiPurchaseManager.registerPurchasesChangedListener { list, namiPurchaseState, s ->
|
|
54
55
|
emitPurchaseMade(list, namiPurchaseState, s)
|
|
55
56
|
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
fun emitEntitlementsChanged(entitlements: List<NamiEntitlement>) {
|
|
59
|
-
val map = Arguments.createMap()
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
resultArray.pushMap(entitlement.toEntitlementDict())
|
|
58
|
+
NamiCustomerManager.registerCustomerJourneyChangedListener {
|
|
59
|
+
emitCustomerJourneyChanged(it)
|
|
64
60
|
}
|
|
65
|
-
|
|
61
|
+
}
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
private fun emitCustomerJourneyChanged(customerJourneyState: CustomerJourneyState) {
|
|
64
|
+
Log.i(LOG_TAG, "Emitting CustomerJourneyChanged $customerJourneyState")
|
|
68
65
|
try {
|
|
69
66
|
reactApplicationContext
|
|
70
67
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
71
|
-
.emit("
|
|
68
|
+
.emit("CustomerJourneyStateChanged", customerJourneyState.toDict())
|
|
72
69
|
} catch (e: Exception) {
|
|
73
70
|
Log.e(LOG_TAG, "Caught Exception: " + e.message)
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
fun emitPurchaseMade(
|
|
74
|
+
private fun emitPurchaseMade(
|
|
78
75
|
purchases: List<NamiPurchase>,
|
|
79
76
|
purchaseState: NamiPurchaseState,
|
|
80
77
|
errorString: String?
|
|
@@ -117,7 +114,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
fun emitPaywallRaise(
|
|
117
|
+
private fun emitPaywallRaise(
|
|
121
118
|
namiPaywall: NamiPaywall,
|
|
122
119
|
productDicts: List<NamiSKU>,
|
|
123
120
|
paywallDeveloperID: String?
|
|
@@ -139,7 +136,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
139
136
|
skusArray.pushMap(sku.toSkuDict())
|
|
140
137
|
}
|
|
141
138
|
|
|
142
|
-
map.putArray("
|
|
139
|
+
map.putArray("namiSkus", skusArray)
|
|
143
140
|
|
|
144
141
|
try {
|
|
145
142
|
reactApplicationContext
|
|
@@ -150,7 +147,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
150
147
|
}
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
fun emitSignInActivated(
|
|
150
|
+
private fun emitSignInActivated(paywallDeveloperID: String?) {
|
|
154
151
|
|
|
155
152
|
val map = Arguments.createMap().apply {
|
|
156
153
|
putString("developerPaywallID", paywallDeveloperID)
|
|
@@ -163,19 +160,4 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
|
|
|
163
160
|
Log.e(LOG_TAG, "Caught Exception: " + e.message)
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
|
-
|
|
167
|
-
fun emitPurchaseMade(paywallDeveloperID: String) {
|
|
168
|
-
|
|
169
|
-
val map = Arguments.createMap().apply {
|
|
170
|
-
putString("key1", "Value1")
|
|
171
|
-
putString("key1", "Value1")
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
reactApplicationContext
|
|
175
|
-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
176
|
-
.emit("customEventName", map)
|
|
177
|
-
} catch (e: Exception) {
|
|
178
|
-
Log.e(LOG_TAG, "Caught Exception: " + e.message)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
163
|
}
|
|
@@ -70,6 +70,13 @@ class NamiPurchaseManagerBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
@ReactMethod
|
|
74
|
+
fun consumePurchasedSKU(skuRefId: String) {
|
|
75
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
76
|
+
NamiPurchaseManager.consumePurchasedSKU(skuRefId)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
@ReactMethod
|
|
74
81
|
fun anySKUIDPurchased(skuIDs: ReadableArray, resultsCallback: Callback) {
|
|
75
82
|
reactApplicationContext.runOnUiQueueThread {
|
|
@@ -6,12 +6,15 @@ import com.facebook.react.bridge.WritableArray
|
|
|
6
6
|
import com.facebook.react.bridge.WritableMap
|
|
7
7
|
import com.facebook.react.bridge.WritableNativeArray
|
|
8
8
|
import com.facebook.react.bridge.WritableNativeMap
|
|
9
|
-
import com.namiml.api.model.FormattedSku
|
|
10
9
|
import com.namiml.billing.NamiPurchase
|
|
10
|
+
import com.namiml.customer.CustomerJourneyState
|
|
11
11
|
import com.namiml.entitlement.NamiEntitlement
|
|
12
|
+
import com.namiml.paywall.LegalCitations
|
|
13
|
+
import com.namiml.paywall.NamiLocaleConfig
|
|
12
14
|
import com.namiml.paywall.NamiPaywall
|
|
13
15
|
import com.namiml.paywall.NamiPurchaseSource
|
|
14
16
|
import com.namiml.paywall.NamiSKU
|
|
17
|
+
import com.namiml.paywall.PaywallDisplayOptions
|
|
15
18
|
import com.namiml.paywall.PaywallStyleData
|
|
16
19
|
import com.namiml.paywall.SubscriptionPeriod
|
|
17
20
|
import com.namiml.util.extensions.getFormattedPrice
|
|
@@ -22,19 +25,9 @@ import java.util.Date
|
|
|
22
25
|
import java.util.Locale
|
|
23
26
|
import java.util.TimeZone
|
|
24
27
|
|
|
25
|
-
fun FormattedSku.toFormattedSkuDict(): WritableMap {
|
|
26
|
-
val map: WritableMap = Arguments.createMap()
|
|
27
|
-
map.putString("sku_ref_id", this.skuId)
|
|
28
|
-
map.putBoolean("featured", this.featured)
|
|
29
|
-
return map
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
fun NamiPaywall.toNamiPaywallDict(): WritableMap {
|
|
33
29
|
|
|
34
30
|
val paywallMap: WritableMap = Arguments.createMap()
|
|
35
|
-
// These don't seem to exist, inconsistent between Android and iOS, leave in marketing content
|
|
36
|
-
//paywallMap.putString("title", title.orEmpty())
|
|
37
|
-
//paywallMap.putString("body", body.orEmpty())
|
|
38
31
|
|
|
39
32
|
val marketingContentMap = Arguments.createMap()
|
|
40
33
|
marketingContentMap.putString("title", title.orEmpty())
|
|
@@ -49,37 +42,55 @@ fun NamiPaywall.toNamiPaywallDict(): WritableMap {
|
|
|
49
42
|
|
|
50
43
|
Log.i(LOG_TAG, "extraData items are $extraDataMap")
|
|
51
44
|
paywallMap.putString("id", id)
|
|
52
|
-
paywallMap.
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
paywallMap.putMap("backgrounds", Arguments.createMap().apply {
|
|
46
|
+
putString("phone", backgroundImageUrlPhone.orEmpty())
|
|
47
|
+
putString("tablet", backgroundImageUrlTablet.orEmpty())
|
|
48
|
+
})
|
|
55
49
|
paywallMap.putString("purchase_terms", purchaseTerms.orEmpty())
|
|
56
|
-
paywallMap.putString("tos_link", tosLink.orEmpty())
|
|
57
50
|
paywallMap.putString("name", name.orEmpty())
|
|
58
|
-
paywallMap.putString("
|
|
51
|
+
paywallMap.putString("paywall_type", type)
|
|
52
|
+
paywallMap.putMap("locale_config", localeConfig.toDict())
|
|
53
|
+
paywallMap.putMap("legal_citations", legalCitations?.toDict())
|
|
54
|
+
paywallMap.putMap("display_options", displayOptions.toDict())
|
|
59
55
|
paywallMap.putString("developer_paywall_id", developerPaywallId.orEmpty())
|
|
60
|
-
|
|
61
|
-
val allowClosing = allowClosing
|
|
62
|
-
paywallMap.putBoolean("allow_closing", allowClosing)
|
|
63
|
-
|
|
64
|
-
val restoreControl = restoreControl
|
|
65
|
-
paywallMap.putBoolean("restore_control", restoreControl)
|
|
66
|
-
|
|
67
|
-
val signInControl = signInControl
|
|
68
|
-
paywallMap.putBoolean("sign_in_control", signInControl)
|
|
69
|
-
|
|
70
|
-
val formattedSkusArray: WritableArray = Arguments.createArray()
|
|
71
|
-
for (formattedSku in formattedSkus) {
|
|
72
|
-
formattedSkusArray.pushMap(formattedSku.toFormattedSkuDict())
|
|
73
|
-
}
|
|
74
|
-
paywallMap.putArray("formatted_skus", formattedSkusArray)
|
|
75
|
-
|
|
76
56
|
styleData?.let {
|
|
77
57
|
paywallMap.putMap("styleData", it.toPaywallStylingDict())
|
|
78
58
|
}
|
|
59
|
+
return paywallMap
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private fun NamiLocaleConfig.toDict(): WritableMap {
|
|
63
|
+
return Arguments.createMap().apply {
|
|
64
|
+
putString("close_button_text", closeButtonText)
|
|
65
|
+
putString("sign_in_button_text", signInButtonText)
|
|
66
|
+
putString("restore_purchase_button_text", restorePurchaseButtonText)
|
|
67
|
+
putString("purchase_button_hint_text_to_speech", purchaseButtonHintTextToSpeech)
|
|
68
|
+
putString("purchase_terms_prefix_hint_text_to_speech", purchaseTermsPrefixHintTextToSpeech)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
79
71
|
|
|
80
|
-
|
|
72
|
+
private fun PaywallDisplayOptions.toDict(): WritableMap {
|
|
73
|
+
return Arguments.createMap().apply {
|
|
74
|
+
putBoolean("allow_closing", allowClosing)
|
|
75
|
+
putBoolean("restore_control", restoreControl)
|
|
76
|
+
putBoolean("sign_in_control", signInControl)
|
|
77
|
+
putString("scrollable_region_size", scrollableRegionSize)
|
|
78
|
+
putBoolean("show_nami_purchase_success_message", shouldShowNamiPurchaseSuccessMessage)
|
|
79
|
+
putBoolean("skus_in_scrollable_region", showSkusInScrollableRegion)
|
|
80
|
+
putBoolean("use_bottom_overlay", useBottomOverlay)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
private fun LegalCitations.toDict(): WritableMap {
|
|
85
|
+
return Arguments.createMap().apply {
|
|
86
|
+
putString("id", id)
|
|
87
|
+
putString("privacy_url", privacyUrl)
|
|
88
|
+
putString("privacy_text", privacyText)
|
|
89
|
+
putString("tos_url", tosUrl)
|
|
90
|
+
putString("tos_text", tosText)
|
|
91
|
+
putString("clickwrap_text", clickWrapText)
|
|
92
|
+
putString("language", language)
|
|
93
|
+
}
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
fun PaywallStyleData.toPaywallStylingDict(): WritableMap {
|
|
@@ -107,6 +118,9 @@ fun PaywallStyleData.toPaywallStylingDict(): WritableMap {
|
|
|
107
118
|
|
|
108
119
|
styleMap.putString("skuButtonColor", skuButtonColor)
|
|
109
120
|
styleMap.putString("skuButtonTextColor", skuButtonTextColor)
|
|
121
|
+
styleMap.putString("skuSubDisplayTextColor", skuSubDisplayTextColor)
|
|
122
|
+
styleMap.putString("skuSubDisplayTextShadowColor", skuSubDisplayTextShadowColor)
|
|
123
|
+
styleMap.putDouble("skuSubDisplayTextShadowRadius", skuSubDisplayTextShadowRadius.toDouble())
|
|
110
124
|
|
|
111
125
|
styleMap.putString("featuredSkusButtonColor", featuredSkuButtonColor)
|
|
112
126
|
styleMap.putString("featuredSkusButtonTextColor", featuredSkuButtonTextColor)
|
|
@@ -196,6 +210,7 @@ fun NamiSKU.toSkuDict(): WritableMap {
|
|
|
196
210
|
productDict.putString("localizedDescription", skuDetails.description)
|
|
197
211
|
productDict.putString("localizedPrice", skuDetails.price)
|
|
198
212
|
productDict.putString("localizedMultipliedPrice", skuDetails.price)
|
|
213
|
+
productDict.putBoolean("featured", featured)
|
|
199
214
|
productDict.putString("displayText", displayText)
|
|
200
215
|
productDict.putString("displaySubText", displaySubText)
|
|
201
216
|
productDict.putString("price", skuDetails.getFormattedPrice().toString())
|
|
@@ -219,6 +234,9 @@ fun NamiSKU.toSkuDict(): WritableMap {
|
|
|
219
234
|
SubscriptionPeriod.ANNUAL -> {
|
|
220
235
|
"year"
|
|
221
236
|
}
|
|
237
|
+
SubscriptionPeriod.FOUR_WEEKS -> {
|
|
238
|
+
"four_weeks"
|
|
239
|
+
}
|
|
222
240
|
else -> {
|
|
223
241
|
null
|
|
224
242
|
}
|
|
@@ -267,6 +285,26 @@ fun NamiPurchase.toPurchaseDict(): WritableMap {
|
|
|
267
285
|
return purchaseMap
|
|
268
286
|
}
|
|
269
287
|
|
|
288
|
+
fun CustomerJourneyState?.toDict(): WritableMap {
|
|
289
|
+
val formerSubscriber = this?.formerSubscriber ?: false
|
|
290
|
+
val inGracePeriod = this?.inGracePeriod ?: false
|
|
291
|
+
val inTrialPeriod = this?.inTrialPeriod ?: false
|
|
292
|
+
val inIntroOfferPeriod = this?.inIntroOfferPeriod ?: false
|
|
293
|
+
val inPause = this?.inPause ?: false
|
|
294
|
+
val inAccountHold = this?.inAccountHold ?: false
|
|
295
|
+
val isCancelled = this?.isCancelled ?: false
|
|
296
|
+
|
|
297
|
+
return WritableNativeMap().apply {
|
|
298
|
+
putBoolean("formerSubscriber", formerSubscriber)
|
|
299
|
+
putBoolean("inGracePeriod", inGracePeriod)
|
|
300
|
+
putBoolean("inTrialPeriod", inTrialPeriod)
|
|
301
|
+
putBoolean("inIntroOfferPeriod", inIntroOfferPeriod)
|
|
302
|
+
putBoolean("inPause", inPause)
|
|
303
|
+
putBoolean("inAccountHold", inAccountHold)
|
|
304
|
+
putBoolean("isCancelled", isCancelled)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
270
308
|
fun NamiEntitlement.toEntitlementDict(): WritableMap? {
|
|
271
309
|
val resultMap: WritableMap = WritableNativeMap()
|
|
272
310
|
resultMap.putString("referenceID", referenceId)
|
|
@@ -321,4 +359,3 @@ fun Date.toJavascriptDate(): String {
|
|
|
321
359
|
}
|
|
322
360
|
return df.format(this)
|
|
323
361
|
}
|
|
324
|
-
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
# Regex to validate version numbers
|
|
8
|
+
PROD_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
|
9
|
+
PRERELEASE_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+-(alpha|beta|rc)\.\d{2}$")
|
|
10
|
+
|
|
11
|
+
early_access = str(os.getenv("EARLY_ACCESS"))
|
|
12
|
+
|
|
13
|
+
# get the version out of source of truth
|
|
14
|
+
with open("package.json", "r") as f:
|
|
15
|
+
package = json.load(f)
|
|
16
|
+
nami_sdk_version = package["version"]
|
|
17
|
+
|
|
18
|
+
# Get git version
|
|
19
|
+
git_long_hash = (
|
|
20
|
+
subprocess.check_output(["git", "log", "-1", "--format=%H"]).decode("utf-8").strip()
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# 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 early_access == "true":
|
|
26
|
+
print(f"Early access value ('{early_access}') is not compatible with production version format '{nami_sdk_version}'")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
type_mods = ""
|
|
29
|
+
elif PRERELEASE_VERSION_RE.match(nami_sdk_version):
|
|
30
|
+
if early_access == "false":
|
|
31
|
+
print(f"Early access value ('{early_access}') is not compatible with early access version format '{nami_sdk_version}'")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
type_mods = "--prerelease"
|
|
34
|
+
else:
|
|
35
|
+
print(f"SDK version '{nami_sdk_version}' does not conform to version spec")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
# generate a shell command to create a release later
|
|
39
|
+
with open("gh-release-command.sh", "w") as f:
|
|
40
|
+
f.write(
|
|
41
|
+
f"gh release create --generate-notes --title v{nami_sdk_version} {type_mods} --target {git_long_hash} v{nami_sdk_version}"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# report our status
|
|
45
|
+
print(f"Identified Version: {nami_sdk_version}")
|
package/ios/Nami.m
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
@implementation NamiBridge (RCTExternModule)
|
|
22
22
|
|
|
23
23
|
RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
|
|
24
|
-
|
|
24
|
+
if ([configDict count] == 0 || [configDict[@"logLevel"] isEqual: @"DEBUG"] ) {
|
|
25
|
+
NSLog(@"Configure dictionary is %@", configDict);
|
|
26
|
+
}
|
|
25
27
|
NSString *appID = configDict[@"appPlatformID-apple"];
|
|
26
28
|
|
|
27
29
|
if ([appID length] > 0 ) {
|
|
@@ -39,6 +41,17 @@ RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
|
|
|
39
41
|
config.logLevel = NamiLogLevelDebug;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
NSString *languageString = configDict[@"namiLanguageCode"];
|
|
45
|
+
if ([logLevelString length] > 0) {
|
|
46
|
+
NSLog(@"Nami language code from config dictionary is %@", languageString);
|
|
47
|
+
if ([[NamiLanguageCodes allAvailiableNamiLanguageCodes]
|
|
48
|
+
containsObject:[languageString lowercaseString]] ) {
|
|
49
|
+
config.namiLanguageCode = languageString;
|
|
50
|
+
} else {
|
|
51
|
+
NSLog(@"Warning: Nami language code from config dictionary %@ not found in list of available Nami Language Codes:\n%@", languageString, [NamiLanguageCodes allAvailiableNamiLanguageCodes]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
NSObject *bypassString = configDict[@"bypassStore"];
|
|
43
56
|
if ( bypassString != NULL )
|
|
44
57
|
{
|
|
@@ -70,7 +83,7 @@ RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
|
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
// Start commands with header iformation for Nami to let them know this is a React client.
|
|
73
|
-
NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:0.
|
|
86
|
+
NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:2.0.2"]];
|
|
74
87
|
|
|
75
88
|
// Add additional namiCommands app may have sent in.
|
|
76
89
|
NSObject *appCommandStrings = configDict[@"namiCommands"];
|
package/ios/NamiBridgeUtil.h
CHANGED
package/ios/NamiBridgeUtil.m
CHANGED
|
@@ -205,6 +205,30 @@
|
|
|
205
205
|
return [NSISO8601DateFormatter stringFromDate:purchaseTimestamp timeZone:UTC formatOptions:options];
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
|
|
209
|
+
+ (NSDictionary<NSString *,NSString *> *) customerJourneyStateDict {
|
|
210
|
+
CustomerJourneyState *journeyState = [NamiCustomerManager currentCustomerJourneyState];
|
|
211
|
+
|
|
212
|
+
BOOL formerSubscriber = [journeyState formerSubscriber];
|
|
213
|
+
BOOL inGracePeriod = [journeyState inGracePeriod];
|
|
214
|
+
BOOL inTrialPeriod = [journeyState inTrialPeriod];
|
|
215
|
+
BOOL inIntroOfferPeriod = [journeyState inIntroOfferPeriod];
|
|
216
|
+
|
|
217
|
+
BOOL isCancelled = [journeyState isCancelled];
|
|
218
|
+
BOOL inPause = [journeyState inPause];
|
|
219
|
+
BOOL inAccountHold = [journeyState inAccountHold];
|
|
220
|
+
|
|
221
|
+
NSDictionary *journeyDict = @{@"formerSubscriber":@(formerSubscriber),
|
|
222
|
+
@"inGracePeriod":@(inGracePeriod),
|
|
223
|
+
@"inTrialPeriod":@(inTrialPeriod),
|
|
224
|
+
@"inIntroOfferPeriod":@(inIntroOfferPeriod),
|
|
225
|
+
@"isCancelled":@(isCancelled),
|
|
226
|
+
@"inPause":@(inPause),
|
|
227
|
+
@"inAccountHold":@(inAccountHold)
|
|
228
|
+
};
|
|
229
|
+
return journeyDict;
|
|
230
|
+
}
|
|
231
|
+
|
|
208
232
|
+ (NSDictionary<NSString *,NSString *> *) paywallStylingToPaywallStylingDict:(PaywallStyleData *)styling {
|
|
209
233
|
NSMutableDictionary<NSString *,id> *stylingDict = [NSMutableDictionary new];
|
|
210
234
|
if (styling != nil) {
|
|
@@ -22,18 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
RCT_EXPORT_METHOD(currentCustomerJourneyState:(RCTResponseSenderBlock)completion)
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
BOOL formerSubscriber = [journeyState formerSubscriber];
|
|
28
|
-
BOOL inGracePeriod = [journeyState inGracePeriod];
|
|
29
|
-
BOOL inTrialPeriod = [journeyState inTrialPeriod];
|
|
30
|
-
BOOL inIntroOfferPeriod = [journeyState inIntroOfferPeriod];
|
|
31
|
-
|
|
32
|
-
NSDictionary *journeyDict = @{@"formerSubscriber":@(formerSubscriber),
|
|
33
|
-
@"inGracePeriod":@(inGracePeriod),
|
|
34
|
-
@"inTrialPeriod":@(inTrialPeriod),
|
|
35
|
-
@"inIntroOfferPeriod":@(inIntroOfferPeriod)
|
|
36
|
-
};
|
|
25
|
+
NSDictionary *journeyDict = [NamiBridgeUtil customerJourneyStateDict];
|
|
37
26
|
|
|
38
27
|
completion(@[journeyDict]);
|
|
39
28
|
}
|
package/ios/NamiEmitter.m
CHANGED
|
@@ -54,6 +54,14 @@ static NamiEmitter *namiEmitter;
|
|
|
54
54
|
[NamiPaywallManager registerBlockingPaywallClosedHandler:^{
|
|
55
55
|
[self sendBlockingPaywallClosed];
|
|
56
56
|
}];
|
|
57
|
+
|
|
58
|
+
[NamiPurchaseManager registerRestorePurchasesHandlerWithRestorePurchasesStateHandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
|
|
59
|
+
[self sendRestorePurchasesStateChanged:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
|
|
60
|
+
}];
|
|
61
|
+
|
|
62
|
+
[NamiCustomerManager registerJourneyStateChangedHandler:^(CustomerJourneyState * _Nonnull journeyState) {
|
|
63
|
+
[self sendEventCustomerJourneyStateChanged:journeyState];
|
|
64
|
+
}];
|
|
57
65
|
|
|
58
66
|
}
|
|
59
67
|
namiEmitter = self;
|
|
@@ -89,7 +97,7 @@ static NamiEmitter *namiEmitter;
|
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
- (NSArray<NSString *> *)supportedEvents {
|
|
92
|
-
return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed", @"PreparePaywallFinished" ];
|
|
100
|
+
return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed", @"PreparePaywallFinished", @"RestorePurchasesStateChanged", @"CustomerJourneyStateChanged" ];
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
- (NSDictionary<NSString *, NSObject *> *)constantsToExport {
|
|
@@ -186,6 +194,13 @@ bool hasNamiEmitterListeners;
|
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
196
|
|
|
197
|
+
- (void)sendEventCustomerJourneyStateChanged:(CustomerJourneyState *)journeyState {
|
|
198
|
+
if (hasNamiEmitterListeners) {
|
|
199
|
+
NSDictionary *sendDict = [NamiBridgeUtil customerJourneyStateDict];
|
|
200
|
+
[self sendEventWithName:@"CustomerJourneyStateChanged" body:sendDict];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
189
204
|
- (void)sendEventPurchaseMadeWithPurchases:(NSArray<NamiPurchase *>*)purchases withState:(NamiPurchaseState)purchaseState error:(NSError *)error {
|
|
190
205
|
if (hasNamiEmitterListeners) {
|
|
191
206
|
|
|
@@ -259,12 +274,73 @@ bool hasNamiEmitterListeners;
|
|
|
259
274
|
[paywallMeta removeObjectForKey:@"title"];
|
|
260
275
|
[paywallMeta removeObjectForKey:@"style"];
|
|
261
276
|
|
|
262
|
-
[self sendEventWithName:@"AppPaywallActivate" body:@{ @"
|
|
277
|
+
[self sendEventWithName:@"AppPaywallActivate" body:@{ @"namiSkus": skuDicts,
|
|
263
278
|
@"developerPaywallID": developerPaywallID,
|
|
264
279
|
@"paywallMetadata": paywallMeta }];
|
|
265
280
|
}
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
- (NSDictionary *)buildRestorePurchasesStateChangedDict: (enum NamiRestorePurchasesState) state
|
|
284
|
+
newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
|
|
285
|
+
oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
|
|
286
|
+
error: (NSError * _Nullable) error {
|
|
287
|
+
NSString *errorDesc = [error localizedDescription];
|
|
288
|
+
NSDictionary *initialDict;
|
|
289
|
+
if ([errorDesc length] > 0) {
|
|
290
|
+
initialDict = @{@"state": [NSNumber numberWithBool:state], @"stateDesc": [self restorePurchaseStateDescriptionFromCode:state], @"error": [error localizedDescription]};
|
|
291
|
+
} else {
|
|
292
|
+
initialDict = @{@"state": [NSNumber numberWithBool:state], @"stateDesc": [self restorePurchaseStateDescriptionFromCode:state]};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
NSMutableDictionary *retDict = [NSMutableDictionary dictionary];
|
|
296
|
+
[retDict addEntriesFromDictionary:initialDict];
|
|
297
|
+
|
|
298
|
+
NSMutableArray *newPurchaseDicts = [NSMutableArray array];
|
|
299
|
+
for ( NamiPurchase *purchaseRecord in newPurchases ) {
|
|
300
|
+
if ( purchaseRecord.skuID == nil ) {
|
|
301
|
+
}
|
|
302
|
+
NSDictionary *purchaseDict = [NamiBridgeUtil purchaseToPurchaseDict:purchaseRecord];
|
|
303
|
+
[newPurchaseDicts addObject:purchaseDict];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
NSMutableArray *oldPurchaseDicts = [NSMutableArray array];
|
|
307
|
+
for ( NamiPurchase *purchaseRecord in oldPurchases ) {
|
|
308
|
+
if ( purchaseRecord.skuID == nil ) {
|
|
309
|
+
}
|
|
310
|
+
NSDictionary *purchaseDict = [NamiBridgeUtil purchaseToPurchaseDict:purchaseRecord];
|
|
311
|
+
[oldPurchaseDicts addObject:purchaseDict];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
retDict[@"newPurchases"] = newPurchaseDicts;
|
|
315
|
+
retDict[@"oldPurchases"] = oldPurchaseDicts;
|
|
316
|
+
|
|
317
|
+
NSLog(@"NamiBridge: Info: RestorePurchases state change: %@", retDict);
|
|
318
|
+
|
|
319
|
+
return retDict;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
- (void)sendRestorePurchasesStateChanged: (enum NamiRestorePurchasesState) state
|
|
323
|
+
newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
|
|
324
|
+
oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
|
|
325
|
+
error: (NSError * _Nullable) error {
|
|
326
|
+
NSDictionary * retDict = [self buildRestorePurchasesStateChangedDict:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
|
|
327
|
+
[self sendEventWithName:@"RestorePurchasesStateChanged" body:retDict];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
- (NSString *) restorePurchaseStateDescriptionFromCode:(NamiRestorePurchasesState)stateCode {
|
|
331
|
+
switch (stateCode) {
|
|
332
|
+
case NamiRestorePurchasesStateStarted:
|
|
333
|
+
return @"started";
|
|
334
|
+
break;
|
|
335
|
+
case NamiRestorePurchasesStateFinished:
|
|
336
|
+
return @"finished";
|
|
337
|
+
break;
|
|
338
|
+
case NamiRestorePurchasesStateError:
|
|
339
|
+
return @"error";
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
268
344
|
- (void) sendBlockingPaywallClosed {
|
|
269
345
|
// Let system know a blocking paywall has been closed, in case they want to react specifically.
|
|
270
346
|
if (hasNamiEmitterListeners) {
|
|
@@ -95,7 +95,7 @@ RCT_EXPORT_METHOD(fetchCustomPaywallMetaForDeveloperID:(NSString *)developerPayw
|
|
|
95
95
|
paywallMeta[@"styleData"] = paywallStylingDict;
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
NSArray *wrapperArray = @[@{ @"
|
|
98
|
+
NSArray *wrapperArray = @[@{ @"namiSkus": productDicts,
|
|
99
99
|
@"developerPaywallID": developerPaywallID,
|
|
100
100
|
@"paywallMetadata": paywallMeta }];
|
|
101
101
|
completion(wrapperArray);
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
#import "React/RCTViewManager.h"
|
|
17
17
|
|
|
18
|
+
@interface NamiEmitter : RCTEventEmitter
|
|
19
|
+
- (void)sendRestorePurchasesStateChanged: (enum NamiRestorePurchasesState) state
|
|
20
|
+
newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
|
|
21
|
+
oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
|
|
22
|
+
error: (NSError * _Nullable) error;
|
|
23
|
+
- (NSDictionary *)buildRestorePurchasesStateChangedDict: (enum NamiRestorePurchasesState) state
|
|
24
|
+
newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
|
|
25
|
+
oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
|
|
26
|
+
error: (NSError * _Nullable) error;
|
|
27
|
+
+ (NamiEmitter *) reactInstance;
|
|
28
|
+
@end
|
|
18
29
|
|
|
19
30
|
@interface NamiPurchaseManagerBridge : NSObject <RCTBridgeModule>
|
|
20
31
|
@end
|
|
@@ -48,23 +59,26 @@ RCT_EXPORT_METHOD(isSKUIDPurchased:(nonnull NSString*)skuID completion:(RCTRespo
|
|
|
48
59
|
completion(@[[NSNumber numberWithBool:active]]);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
RCT_EXPORT_METHOD(
|
|
62
|
+
RCT_EXPORT_METHOD(restorePurchasesWithCompletionHandler:(RCTResponseSenderBlock)completion)
|
|
52
63
|
{
|
|
53
|
-
NSLog(@"NamiBridge: Info: Calling
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
NSDictionary *retDict;
|
|
57
|
-
if ([errorDesc length] > 0) {
|
|
58
|
-
retDict = @{@"success": [NSNumber numberWithBool:success], @"error": [error localizedDescription]};
|
|
59
|
-
} else {
|
|
60
|
-
retDict = @{@"success": [NSNumber numberWithBool:success]};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
NSLog(@"NamiBridge: Info: RestorePurchases Returned %@", retDict);
|
|
64
|
+
NSLog(@"NamiBridge: Info: Calling RestorePurchasesWithCompletionHandler");
|
|
65
|
+
|
|
66
|
+
[NamiPurchaseManager restorePurchasesWithStatehandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
|
|
67
|
+
NSDictionary *retDict = [[NamiEmitter reactInstance] buildRestorePurchasesStateChangedDict:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
|
|
64
68
|
completion(@[retDict]);
|
|
65
69
|
}];
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
RCT_EXPORT_METHOD(restorePurchases)
|
|
73
|
+
{
|
|
74
|
+
NSLog(@"NamiBridge: Info: Calling RestorePurchases");
|
|
75
|
+
|
|
76
|
+
[NamiPurchaseManager restorePurchasesWithStatehandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
|
|
77
|
+
[[NamiEmitter reactInstance] sendRestorePurchasesStateChanged:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
|
|
78
|
+
}];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Determines if any one of the passed in SKUID's have been purchased.
|
|
68
82
|
RCT_EXPORT_METHOD(anySKUIDPurchased:(nonnull NSArray*)skuIDs completion:(RCTResponseSenderBlock)completion)
|
|
69
83
|
{
|
|
70
84
|
BOOL active = false;
|
|
@@ -78,6 +92,31 @@ RCT_EXPORT_METHOD(anySKUIDPurchased:(nonnull NSArray*)skuIDs completion:(RCTResp
|
|
|
78
92
|
completion(@[[NSNumber numberWithBool:active]]);
|
|
79
93
|
}
|
|
80
94
|
|
|
95
|
+
/// For consumable purchases, removes the SKU from Nami so a product may be purchased again.
|
|
96
|
+
RCT_EXPORT_METHOD(consumePurchasedSKU:(nonnull NSString*)skuID)
|
|
97
|
+
{
|
|
98
|
+
[NamiPurchaseManager consumePurchasedSKUWithSkuID:skuID];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// For consumable purchases, removes the SKU from Nami so a product may be purchased again.
|
|
102
|
+
RCT_EXPORT_METHOD(presentCodeRedemptionSheet)
|
|
103
|
+
{
|
|
104
|
+
if (@available(iOS 14.0, *)) {
|
|
105
|
+
[NamiPurchaseManager presentCodeRedemptionSheet];
|
|
106
|
+
} else {
|
|
107
|
+
NSLog(@"NamiBridge: Warning: presentCodeRedemptionSheet only present in iOS14 and higher");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
RCT_EXPORT_METHOD(canPresentCodeRedemptionSheet:(RCTResponseSenderBlock)completion)
|
|
112
|
+
{
|
|
113
|
+
if (@available(iOS 14.0, *)) {
|
|
114
|
+
completion(@[[NSNumber numberWithBool:true]]);
|
|
115
|
+
} else {
|
|
116
|
+
completion(@[[NSNumber numberWithBool:false]]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
81
120
|
/// This method does the purchase work, and can optionally be fed a paywall metadata object to pass along to the purchase flow.
|
|
82
121
|
- (void) doSKUPurchaseWithSKUID:(nonnull NSString*)skuID namiPaywall:(NamiPaywall * _Nullable)namiPaywall completion:(RCTResponseSenderBlock)completion {
|
|
83
122
|
[NamiPurchaseManager skusForSKUIDsWithSkuIDs:@[skuID] productHandler:^(BOOL success, NSArray<NamiSKU *> * _Nullable products, NSArray<NSString *> * _Nullable invalidProducts, NSError * _Nullable error) {
|
package/ios/Podfile
CHANGED
|
@@ -1,48 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
require_relative '../node_modules/react-native/scripts/react_native_pods'
|
|
2
3
|
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
|
3
4
|
|
|
5
|
+
platform :ios, '11.2'
|
|
6
|
+
|
|
4
7
|
source 'https://cdn.cocoapods.org/'
|
|
5
8
|
|
|
6
9
|
target 'RNNami' do
|
|
7
|
-
|
|
8
|
-
pod 'Nami', '2.
|
|
9
|
-
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
|
10
|
-
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
|
|
11
|
-
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
|
|
12
|
-
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
|
|
13
|
-
pod 'React', :path => '../node_modules/react-native/'
|
|
14
|
-
pod 'React-Core', :path => '../node_modules/react-native/'
|
|
15
|
-
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
|
|
16
|
-
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
|
|
17
|
-
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
|
|
18
|
-
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
|
|
19
|
-
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
|
|
20
|
-
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
|
|
21
|
-
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
|
|
22
|
-
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
|
|
23
|
-
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
|
|
24
|
-
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
|
|
25
|
-
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
|
|
26
|
-
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
|
|
27
|
-
|
|
28
|
-
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
|
|
29
|
-
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
|
|
30
|
-
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
|
|
31
|
-
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
|
|
32
|
-
pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
|
|
33
|
-
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
|
|
34
|
-
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
|
|
35
|
-
|
|
36
|
-
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
|
37
|
-
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
|
38
|
-
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
|
39
|
-
|
|
40
|
-
use_native_modules!
|
|
10
|
+
config = use_native_modules!
|
|
11
|
+
pod 'Nami', '2.9.5'
|
|
41
12
|
|
|
42
|
-
|
|
13
|
+
use_react_native!(
|
|
14
|
+
:path => config[:reactNativePath],
|
|
15
|
+
# to enable hermes on iOS, change `false` to `true` and then install pods
|
|
16
|
+
:hermes_enabled => false
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Enables Flipper.
|
|
20
|
+
#
|
|
21
|
+
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
|
22
|
+
# you should disable the next line.
|
|
23
|
+
use_flipper!()
|
|
43
24
|
|
|
44
|
-
post_install do |installer|
|
|
45
|
-
|
|
25
|
+
post_install do |installer|
|
|
26
|
+
react_native_post_install(installer)
|
|
27
|
+
#__apply_Xcode_12_5_M1_post_install_workaround(installer)
|
|
28
|
+
|
|
29
|
+
puts("Attempting to add Nami.xcframework reference to react-native-nami-sdk project.")
|
|
46
30
|
installer.pods_project.targets.each do |target|
|
|
47
31
|
if target.name == "Pods-RNNami"
|
|
48
32
|
puts("Found Pods-RNNami target.")
|
|
@@ -60,4 +44,28 @@ post_install do |installer|
|
|
|
60
44
|
end
|
|
61
45
|
end
|
|
62
46
|
end
|
|
47
|
+
## Fix for XCode 12+, hotpatch of react files should be a great idea
|
|
48
|
+
find_and_replace("../node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm",
|
|
49
|
+
"_initializeModules:(NSArray<id<RCTBridgeModule>> *)modules", "_initializeModules:(NSArray<Class> *)modules")
|
|
50
|
+
find_and_replace("../node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm",
|
|
51
|
+
"RCTBridgeModuleNameForClass(module))", "RCTBridgeModuleNameForClass(Class(module)))")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_and_replace(dir, findstr, replacestr)
|
|
61
|
+
Dir[dir].each do |name|
|
|
62
|
+
text = File.read(name)
|
|
63
|
+
replace = text.gsub(findstr,replacestr)
|
|
64
|
+
if text != replace
|
|
65
|
+
puts "Fix: " + name
|
|
66
|
+
File.open(name, "w") { |file| file.puts replace }
|
|
67
|
+
STDOUT.flush
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
Dir[dir + '*/'].each(&method(:find_and_replace))
|
|
63
71
|
end
|
|
@@ -230,6 +230,7 @@
|
|
|
230
230
|
COPY_PHASE_STRIP = NO;
|
|
231
231
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
232
232
|
ENABLE_TESTABILITY = YES;
|
|
233
|
+
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
|
233
234
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
234
235
|
GCC_DYNAMIC_NO_PIC = NO;
|
|
235
236
|
GCC_NO_COMMON_BLOCKS = YES;
|
|
@@ -245,7 +246,7 @@
|
|
|
245
246
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
246
247
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
247
248
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
248
|
-
IPHONEOS_DEPLOYMENT_TARGET =
|
|
249
|
+
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
|
|
249
250
|
MTL_ENABLE_DEBUG_INFO = YES;
|
|
250
251
|
ONLY_ACTIVE_ARCH = YES;
|
|
251
252
|
SDKROOT = iphoneos;
|
|
@@ -274,6 +275,7 @@
|
|
|
274
275
|
COPY_PHASE_STRIP = YES;
|
|
275
276
|
ENABLE_NS_ASSERTIONS = NO;
|
|
276
277
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
278
|
+
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = " ";
|
|
277
279
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
278
280
|
GCC_NO_COMMON_BLOCKS = YES;
|
|
279
281
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
@@ -282,7 +284,7 @@
|
|
|
282
284
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
283
285
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
284
286
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
285
|
-
IPHONEOS_DEPLOYMENT_TARGET =
|
|
287
|
+
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
|
|
286
288
|
MTL_ENABLE_DEBUG_INFO = NO;
|
|
287
289
|
SDKROOT = iphoneos;
|
|
288
290
|
VALIDATE_PRODUCT = YES;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nami-sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "React Native Module for Nami -
|
|
3
|
+
"version": "2.0.2",
|
|
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.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
"paywall",
|
|
21
21
|
"react-native",
|
|
22
22
|
"storekit",
|
|
23
|
-
"subscriptions"
|
|
23
|
+
"subscriptions",
|
|
24
|
+
"iap",
|
|
25
|
+
"play-billing",
|
|
26
|
+
"payments"
|
|
24
27
|
],
|
|
25
28
|
"author": {
|
|
26
|
-
"username": "
|
|
27
|
-
"name": "Nami ML
|
|
28
|
-
"email": "
|
|
29
|
+
"username": "hellonami",
|
|
30
|
+
"name": "Nami ML Inc.",
|
|
31
|
+
"email": "hello@namiml.com"
|
|
29
32
|
},
|
|
30
33
|
"contributors": [
|
|
31
34
|
{
|
|
@@ -39,18 +42,19 @@
|
|
|
39
42
|
],
|
|
40
43
|
"license": "SEE LICENSE FILE",
|
|
41
44
|
"peerDependencies": {
|
|
42
|
-
"react": "^
|
|
43
|
-
"react-native": "
|
|
45
|
+
"react": "^17.0.2",
|
|
46
|
+
"react-native": "^0.65.2"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
|
-
"react": "^
|
|
47
|
-
"react-native": "^0.
|
|
49
|
+
"react": "^17.0.2",
|
|
50
|
+
"react-native": "^0.65.2",
|
|
51
|
+
"react-native-codegen": "^0.0.12"
|
|
48
52
|
},
|
|
49
53
|
"repository": {
|
|
50
54
|
"type": "git",
|
|
51
55
|
"url": "git+https://github.com/namiml/react-native-nami-sdk.git"
|
|
52
56
|
},
|
|
53
|
-
"homepage": "https://www.
|
|
57
|
+
"homepage": "https://www.namiml.com",
|
|
54
58
|
"bugs": {
|
|
55
59
|
"url": "https://github.com/namiml/react-native-nami-sdk/issues"
|
|
56
60
|
}
|
|
@@ -8,17 +8,17 @@ Pod::Spec.new do |s|
|
|
|
8
8
|
s.version = package['version']
|
|
9
9
|
s.summary = package['description']
|
|
10
10
|
|
|
11
|
-
s.author = { "Nami ML
|
|
11
|
+
s.author = { "Nami ML Inc." => "info@namiml.com" }
|
|
12
12
|
|
|
13
13
|
s.homepage = package['homepage']
|
|
14
14
|
s.license = package['license']
|
|
15
15
|
|
|
16
|
-
s.platform = :ios, "11.
|
|
16
|
+
s.platform = :ios, "11.2"
|
|
17
17
|
s.source = { :git => "https://github.com/namiml/react-native-nami-sdk.git", :tag => "#{s.version}" }
|
|
18
18
|
s.source_files = "ios/**/*.{h,m}"
|
|
19
19
|
s.requires_arc = true
|
|
20
20
|
|
|
21
|
-
s.dependency 'Nami', '2.
|
|
21
|
+
s.dependency 'Nami', '2.9.5'
|
|
22
22
|
s.dependency 'React'
|
|
23
23
|
|
|
24
24
|
end
|