@encorekit/react-native 1.1.15
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 +201 -0
- package/android/build.gradle +80 -0
- package/android/gradle.properties +11 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/encorereactsdk/EncoreReactSDKModule.kt +216 -0
- package/android/src/main/java/com/encorereactsdk/EncoreReactSDKPackage.kt +22 -0
- package/encore-react-sdk.podspec +24 -0
- package/ios/EncoreReactSDK-Bridging-Header.h +7 -0
- package/ios/EncoreReactSDK.m +42 -0
- package/ios/EncoreReactSDK.swift +229 -0
- package/lib/commonjs/hooks.js +38 -0
- package/lib/commonjs/hooks.js.map +1 -0
- package/lib/commonjs/index.js +87 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/hooks.js +29 -0
- package/lib/module/hooks.js.map +1 -0
- package/lib/module/index.js +69 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/hooks.d.ts +11 -0
- package/lib/typescript/src/hooks.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +77 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +168 -0
- package/src/hooks.tsx +38 -0
- package/src/index.ts +147 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Encore React Native SDK
|
|
2
|
+
|
|
3
|
+
Thin React Native bridge to the native [Encore iOS SDK](https://github.com/EncoreKit/ios-sdk) and [Encore Android SDK](https://github.com/EncoreKit/android-sdk). Presents retention offers with native UI and delegates purchases to your existing subscription manager.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @encorekit/react-native
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### iOS
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd ios && pod install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Android
|
|
18
|
+
|
|
19
|
+
No additional setup required — `com.encorekit:encore` resolves from Maven Central, which is included by default in Android projects. The React Native module auto-links with RN 0.60+.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Wrap Your App with EncoreProvider
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { EncoreProvider } from '@encorekit/react-native';
|
|
27
|
+
|
|
28
|
+
export default function App() {
|
|
29
|
+
return (
|
|
30
|
+
<EncoreProvider apiKey="your_api_key" options={{ logLevel: 'debug' }}>
|
|
31
|
+
<YourApp />
|
|
32
|
+
</EncoreProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The provider calls `configure` and `registerCallbacks` once on mount.
|
|
38
|
+
|
|
39
|
+
### 2. Identify Users
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import Encore from '@encorekit/react-native';
|
|
43
|
+
|
|
44
|
+
// After authentication
|
|
45
|
+
Encore.identify('user_123', {
|
|
46
|
+
email: 'user@example.com',
|
|
47
|
+
subscriptionTier: 'premium',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// On logout
|
|
51
|
+
Encore.reset();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Show a Placement
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
const result = await Encore.show('paywall');
|
|
58
|
+
// result.status: 'granted' | 'not_granted' | 'completed' | 'dismissed' | 'no_offers'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 4. Handle Callbacks
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Purchase request — delegate to your subscription manager
|
|
65
|
+
Encore.onPurchaseRequest(async ({ requestId, productId, placementId }) => {
|
|
66
|
+
try {
|
|
67
|
+
await RevenueCat.purchase(productId);
|
|
68
|
+
await Encore.completePurchaseRequest(requestId, true);
|
|
69
|
+
} catch {
|
|
70
|
+
await Encore.completePurchaseRequest(requestId, false);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Passthrough — user dismissed without purchasing
|
|
75
|
+
Encore.onPassthrough(({ placementId }) => {
|
|
76
|
+
// Resume your original flow
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Purchase complete (fire-and-forget, for syncing with backends)
|
|
80
|
+
Encore.onPurchaseComplete(({ productId, transactionId }) => {
|
|
81
|
+
console.log('Purchase completed:', productId);
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each `on*` method returns an unsubscribe function:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const unsubscribe = Encore.onPassthrough(handler);
|
|
89
|
+
// Later:
|
|
90
|
+
unsubscribe();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### `configure(apiKey, options?)`
|
|
96
|
+
|
|
97
|
+
Initialize the SDK. Called automatically by `EncoreProvider`.
|
|
98
|
+
|
|
99
|
+
| Parameter | Type | Description |
|
|
100
|
+
|:----------|:-----|:------------|
|
|
101
|
+
| `apiKey` | `string` | Your Encore API key |
|
|
102
|
+
| `options.logLevel` | `'none' \| 'error' \| 'warn' \| 'info' \| 'debug'` | Log verbosity (default: `'none'`) |
|
|
103
|
+
|
|
104
|
+
### `identify(userId, attributes?)`
|
|
105
|
+
|
|
106
|
+
Associate a user ID with SDK events.
|
|
107
|
+
|
|
108
|
+
### `setUserAttributes(attributes)`
|
|
109
|
+
|
|
110
|
+
Merge attributes into the current user profile.
|
|
111
|
+
|
|
112
|
+
### `reset()`
|
|
113
|
+
|
|
114
|
+
Clear user data and generate a new anonymous ID. Call on logout.
|
|
115
|
+
|
|
116
|
+
### `show(placementId)`
|
|
117
|
+
|
|
118
|
+
Present a native offer sheet. Returns `PlacementResult`.
|
|
119
|
+
|
|
120
|
+
### `registerCallbacks()`
|
|
121
|
+
|
|
122
|
+
Register native event handlers (purchase request, purchase complete, passthrough). Called automatically by `EncoreProvider`.
|
|
123
|
+
|
|
124
|
+
### `completePurchaseRequest(requestId, success)`
|
|
125
|
+
|
|
126
|
+
Signal completion of a purchase request initiated via `onPurchaseRequest`. The native SDK waits for this call before proceeding.
|
|
127
|
+
|
|
128
|
+
### `onPurchaseRequest(handler)`
|
|
129
|
+
|
|
130
|
+
Subscribe to purchase request events. Your handler receives `{ requestId, productId, placementId }` and must call `completePurchaseRequest` when done.
|
|
131
|
+
|
|
132
|
+
### `onPurchaseComplete(handler)`
|
|
133
|
+
|
|
134
|
+
Subscribe to purchase completion events. Receives `{ productId, transactionId }`.
|
|
135
|
+
|
|
136
|
+
### `onPassthrough(handler)`
|
|
137
|
+
|
|
138
|
+
Subscribe to passthrough events (user dismissed without purchasing). Receives `{ placementId }`.
|
|
139
|
+
|
|
140
|
+
### `EncoreProvider`
|
|
141
|
+
|
|
142
|
+
React context provider. Props:
|
|
143
|
+
|
|
144
|
+
| Prop | Type | Description |
|
|
145
|
+
|:-----|:-----|:------------|
|
|
146
|
+
| `apiKey` | `string` | Your Encore API key |
|
|
147
|
+
| `options` | `ConfigureOptions` | Optional configuration |
|
|
148
|
+
|
|
149
|
+
### `useEncoreContext()`
|
|
150
|
+
|
|
151
|
+
Hook returning the full `EncoreSDK` interface. Must be used within `EncoreProvider`.
|
|
152
|
+
|
|
153
|
+
## Types
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
interface UserAttributes {
|
|
157
|
+
email?: string;
|
|
158
|
+
firstName?: string;
|
|
159
|
+
lastName?: string;
|
|
160
|
+
phoneNumber?: string;
|
|
161
|
+
postalCode?: string;
|
|
162
|
+
city?: string;
|
|
163
|
+
state?: string;
|
|
164
|
+
countryCode?: string;
|
|
165
|
+
latitude?: string;
|
|
166
|
+
longitude?: string;
|
|
167
|
+
dateOfBirth?: string;
|
|
168
|
+
gender?: string;
|
|
169
|
+
language?: string;
|
|
170
|
+
subscriptionTier?: string;
|
|
171
|
+
monthsSubscribed?: string;
|
|
172
|
+
billingCycle?: string;
|
|
173
|
+
lastPaymentAmount?: string;
|
|
174
|
+
lastActiveDate?: string;
|
|
175
|
+
totalSessions?: string;
|
|
176
|
+
custom?: Record<string, string>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface PlacementResult {
|
|
180
|
+
status: 'granted' | 'not_granted' | 'completed' | 'dismissed' | 'no_offers';
|
|
181
|
+
reason?: string;
|
|
182
|
+
entitlement?: string;
|
|
183
|
+
offerId?: string;
|
|
184
|
+
campaignId?: string;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Requirements
|
|
189
|
+
|
|
190
|
+
- React Native 0.60+
|
|
191
|
+
- iOS 15.0+
|
|
192
|
+
- Android API 21+
|
|
193
|
+
- Node 16+
|
|
194
|
+
|
|
195
|
+
## Note on Entitlements
|
|
196
|
+
|
|
197
|
+
Entitlement tracking is handled by the native SDKs and your subscription manager. The React Native bridge does not expose entitlement query methods directly — use your subscription manager's React Native SDK (RevenueCat, Adapty, etc.) for entitlement checks.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import groovy.json.JsonSlurper
|
|
2
|
+
|
|
3
|
+
def packageJson = new JsonSlurper().parseText(file("$projectDir/../package.json").text)
|
|
4
|
+
def encoreAndroidVersion = packageJson.sdkVersions.android["com.encorekit:encore"]
|
|
5
|
+
|
|
6
|
+
buildscript {
|
|
7
|
+
ext.safeExtGet = {prop, fallback ->
|
|
8
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply plugin: "com.android.library"
|
|
13
|
+
apply plugin: "kotlin-android"
|
|
14
|
+
def getExtOrDefault(name) {
|
|
15
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["EncoreReactSDK_" + name]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def getExtOrIntegerDefault(name) {
|
|
19
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["EncoreReactSDK_" + name]).toInteger()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
android {
|
|
23
|
+
namespace "com.encorereactsdk"
|
|
24
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
25
|
+
|
|
26
|
+
defaultConfig {
|
|
27
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
28
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
buildTypes {
|
|
32
|
+
release {
|
|
33
|
+
minifyEnabled false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
lint {
|
|
38
|
+
disable "GradleCompatible"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
compileOptions {
|
|
42
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
43
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
kotlinOptions {
|
|
47
|
+
jvmTarget = "17"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def minKotlinVersion = project.properties["EncoreReactSDK_kotlinVersion"]
|
|
52
|
+
def hostKotlinVersion = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : null
|
|
53
|
+
def kotlin_version = hostKotlinVersion ?: minKotlinVersion
|
|
54
|
+
|
|
55
|
+
if (hostKotlinVersion != null) {
|
|
56
|
+
def hostParts = hostKotlinVersion.tokenize('.').collect { it.toInteger() }
|
|
57
|
+
def minParts = minKotlinVersion.tokenize('.').collect { it.toInteger() }
|
|
58
|
+
def hostVer = hostParts[0] * 10000 + hostParts[1] * 100 + hostParts[2]
|
|
59
|
+
def minVer = minParts[0] * 10000 + minParts[1] * 100 + minParts[2]
|
|
60
|
+
if (hostVer < minVer) {
|
|
61
|
+
throw new GradleException(
|
|
62
|
+
"@encorekit/react-native requires Kotlin >= ${minKotlinVersion}, " +
|
|
63
|
+
"but your project uses ${hostKotlinVersion}. " +
|
|
64
|
+
"Update kotlinVersion in your root build.gradle."
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
dependencies {
|
|
70
|
+
// Host app provides these
|
|
71
|
+
compileOnly "com.facebook.react:react-android:+"
|
|
72
|
+
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
73
|
+
|
|
74
|
+
// Bridge uses coroutines for async calls to native SDK
|
|
75
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
76
|
+
|
|
77
|
+
// Encore native Android SDK — we own this version
|
|
78
|
+
implementation "com.encorekit:encore:${encoreAndroidVersion}"
|
|
79
|
+
}
|
|
80
|
+
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// EncoreReactSDKModule.kt
|
|
2
|
+
// Android bridge — delegates all calls to the native encore-android-sdk.
|
|
3
|
+
|
|
4
|
+
package com.encorereactsdk
|
|
5
|
+
|
|
6
|
+
import com.encorekit.encore.Encore
|
|
7
|
+
import com.encorekit.encore.core.canonical.user.UserAttributes
|
|
8
|
+
import com.encorekit.encore.core.infrastructure.logging.LogLevel
|
|
9
|
+
import com.facebook.react.bridge.*
|
|
10
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
11
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
13
|
+
import kotlinx.coroutines.CoroutineScope
|
|
14
|
+
import kotlinx.coroutines.Dispatchers
|
|
15
|
+
import kotlinx.coroutines.SupervisorJob
|
|
16
|
+
import kotlinx.coroutines.launch
|
|
17
|
+
|
|
18
|
+
@ReactModule(name = EncoreReactSDKModule.NAME)
|
|
19
|
+
class EncoreReactSDKModule(reactContext: ReactApplicationContext) :
|
|
20
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
21
|
+
|
|
22
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
23
|
+
@Volatile private var currentDeferred: CompletableDeferred<Boolean>? = null
|
|
24
|
+
|
|
25
|
+
companion object {
|
|
26
|
+
const val NAME = "EncoreReactSDK"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun getName(): String = NAME
|
|
30
|
+
|
|
31
|
+
// -- Configuration --
|
|
32
|
+
|
|
33
|
+
@ReactMethod
|
|
34
|
+
fun configure(apiKey: String, options: ReadableMap, promise: Promise) {
|
|
35
|
+
val logLevel = when (options.getStringOrNull("logLevel")) {
|
|
36
|
+
"error" -> LogLevel.ERROR
|
|
37
|
+
"warn" -> LogLevel.WARN
|
|
38
|
+
"info" -> LogLevel.INFO
|
|
39
|
+
"debug" -> LogLevel.DEBUG
|
|
40
|
+
else -> LogLevel.NONE
|
|
41
|
+
}
|
|
42
|
+
Encore.shared.configure(reactApplicationContext, apiKey = apiKey, logLevel = logLevel)
|
|
43
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// -- User Identity --
|
|
47
|
+
|
|
48
|
+
@ReactMethod
|
|
49
|
+
fun identify(userId: String, attributes: ReadableMap?, promise: Promise) {
|
|
50
|
+
Encore.shared.identify(userId = userId, attributes = parseAttributes(attributes))
|
|
51
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@ReactMethod
|
|
55
|
+
fun setUserAttributes(attributes: ReadableMap, promise: Promise) {
|
|
56
|
+
parseAttributes(attributes)?.let { Encore.shared.setUserAttributes(it) }
|
|
57
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@ReactMethod
|
|
61
|
+
fun reset(promise: Promise) {
|
|
62
|
+
Encore.shared.reset()
|
|
63
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// -- Claim Control --
|
|
67
|
+
|
|
68
|
+
@ReactMethod
|
|
69
|
+
fun setClaimEnabled(enabled: Boolean, promise: Promise) {
|
|
70
|
+
Encore.shared.placements.isClaimEnabled = enabled
|
|
71
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// -- Offers --
|
|
75
|
+
|
|
76
|
+
@ReactMethod
|
|
77
|
+
fun show(placementId: String, promise: Promise) {
|
|
78
|
+
val activity = currentActivity
|
|
79
|
+
if (activity == null) {
|
|
80
|
+
promise.reject("NO_ACTIVITY", "No current activity available")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
scope.launch {
|
|
85
|
+
try {
|
|
86
|
+
val result = Encore.shared.placement(placementId).show(activity)
|
|
87
|
+
val map = Arguments.createMap()
|
|
88
|
+
when (result) {
|
|
89
|
+
is com.encorekit.encore.features.offers.PresentationResult.Completed -> {
|
|
90
|
+
map.putString("status", "completed")
|
|
91
|
+
map.putString("offerId", result.offerId)
|
|
92
|
+
map.putString("campaignId", result.campaignId)
|
|
93
|
+
}
|
|
94
|
+
is com.encorekit.encore.features.offers.PresentationResult.Dismissed -> {
|
|
95
|
+
map.putString("status", "dismissed")
|
|
96
|
+
map.putString("reason", result.reason.value)
|
|
97
|
+
}
|
|
98
|
+
is com.encorekit.encore.features.offers.PresentationResult.NoOffers -> {
|
|
99
|
+
map.putString("status", "no_offers")
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
promise.resolve(map)
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
promise.reject("SHOW_FAILED", e.message, e)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// -- Callbacks --
|
|
110
|
+
|
|
111
|
+
@ReactMethod
|
|
112
|
+
fun registerCallbacks(promise: Promise) {
|
|
113
|
+
Encore.shared
|
|
114
|
+
.onPurchaseRequest { request ->
|
|
115
|
+
// Fail stale deferred so the previous purchase flow resolves
|
|
116
|
+
currentDeferred?.complete(false)
|
|
117
|
+
|
|
118
|
+
val deferred = CompletableDeferred<Boolean>()
|
|
119
|
+
currentDeferred = deferred
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
sendEvent("onPurchaseRequest", Arguments.createMap().apply {
|
|
123
|
+
putString("productId", request.productId)
|
|
124
|
+
putString("placementId", request.placementId)
|
|
125
|
+
putString("promoOfferId", request.promoOfferId)
|
|
126
|
+
})
|
|
127
|
+
} catch (_: Exception) {
|
|
128
|
+
// sendEvent can throw during context teardown — safe to ignore
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
val success = deferred.await()
|
|
132
|
+
currentDeferred = null
|
|
133
|
+
|
|
134
|
+
if (!success) {
|
|
135
|
+
throw Exception("Purchase failed by JS handler")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
.onPurchaseComplete { result, productId ->
|
|
139
|
+
sendEvent("onPurchaseComplete", Arguments.createMap().apply {
|
|
140
|
+
putString("productId", productId)
|
|
141
|
+
putString("purchaseToken", result.purchaseToken)
|
|
142
|
+
putString("orderId", result.orderId)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
.onPassthrough { placementId ->
|
|
146
|
+
sendEvent("onPassthrough", Arguments.createMap().apply {
|
|
147
|
+
putString("placementId", placementId)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@ReactMethod
|
|
155
|
+
fun completePurchaseRequest(success: Boolean, promise: Promise) {
|
|
156
|
+
val deferred = currentDeferred
|
|
157
|
+
if (deferred != null) {
|
|
158
|
+
deferred.complete(success)
|
|
159
|
+
promise.resolve(Arguments.createMap().apply { putBoolean("success", true) })
|
|
160
|
+
} else {
|
|
161
|
+
promise.resolve(Arguments.createMap().apply {
|
|
162
|
+
putBoolean("success", false)
|
|
163
|
+
putString("error", "No pending purchase request")
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -- Helpers --
|
|
169
|
+
|
|
170
|
+
private fun sendEvent(eventName: String, params: WritableMap) {
|
|
171
|
+
reactApplicationContext
|
|
172
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
173
|
+
.emit(eventName, params)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private fun parseAttributes(map: ReadableMap?): UserAttributes? {
|
|
177
|
+
map ?: return null
|
|
178
|
+
return UserAttributes(
|
|
179
|
+
email = map.getStringOrNull("email"),
|
|
180
|
+
firstName = map.getStringOrNull("firstName"),
|
|
181
|
+
lastName = map.getStringOrNull("lastName"),
|
|
182
|
+
phoneNumber = map.getStringOrNull("phoneNumber"),
|
|
183
|
+
postalCode = map.getStringOrNull("postalCode"),
|
|
184
|
+
city = map.getStringOrNull("city"),
|
|
185
|
+
state = map.getStringOrNull("state"),
|
|
186
|
+
countryCode = map.getStringOrNull("countryCode"),
|
|
187
|
+
latitude = map.getStringOrNull("latitude"),
|
|
188
|
+
longitude = map.getStringOrNull("longitude"),
|
|
189
|
+
dateOfBirth = map.getStringOrNull("dateOfBirth"),
|
|
190
|
+
gender = map.getStringOrNull("gender"),
|
|
191
|
+
language = map.getStringOrNull("language"),
|
|
192
|
+
subscriptionTier = map.getStringOrNull("subscriptionTier"),
|
|
193
|
+
monthsSubscribed = map.getStringOrNull("monthsSubscribed"),
|
|
194
|
+
billingCycle = map.getStringOrNull("billingCycle"),
|
|
195
|
+
lastPaymentAmount = map.getStringOrNull("lastPaymentAmount"),
|
|
196
|
+
lastActiveDate = map.getStringOrNull("lastActiveDate"),
|
|
197
|
+
totalSessions = map.getStringOrNull("totalSessions"),
|
|
198
|
+
custom = map.getCustomMap(),
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private fun ReadableMap.getStringOrNull(key: String): String? =
|
|
203
|
+
if (hasKey(key)) getString(key) else null
|
|
204
|
+
|
|
205
|
+
private fun ReadableMap.getCustomMap(): Map<String, String> {
|
|
206
|
+
if (!hasKey("custom")) return emptyMap()
|
|
207
|
+
val customMap = getMap("custom") ?: return emptyMap()
|
|
208
|
+
val result = mutableMapOf<String, String>()
|
|
209
|
+
val iterator = customMap.keySetIterator()
|
|
210
|
+
while (iterator.hasNextKey()) {
|
|
211
|
+
val key = iterator.nextKey()
|
|
212
|
+
customMap.getString(key)?.let { result[key] = it }
|
|
213
|
+
}
|
|
214
|
+
return result
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// EncoreReactSDKPackage.kt
|
|
2
|
+
// React Native Package for Android
|
|
3
|
+
|
|
4
|
+
package com.encorereactsdk
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.ReactPackage
|
|
7
|
+
import com.facebook.react.bridge.NativeModule
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.uimanager.ViewManager
|
|
10
|
+
|
|
11
|
+
class EncoreReactSDKPackage : ReactPackage {
|
|
12
|
+
@Deprecated("Deprecated in React Native")
|
|
13
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
14
|
+
return listOf(EncoreReactSDKModule(reactContext))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
18
|
+
return emptyList()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "encore-react-sdk"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => "15.0" }
|
|
14
|
+
s.source = { :git => "https://github.com/EncoreKit/react-native-sdk.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
+
|
|
18
|
+
ios_version = package["sdkVersions"]["ios"]["EncoreKit"]
|
|
19
|
+
s.dependency "React-Core"
|
|
20
|
+
s.dependency "EncoreKit", ios_version
|
|
21
|
+
|
|
22
|
+
s.swift_version = "5.9"
|
|
23
|
+
s.frameworks = "SafariServices", "UIKit"
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// EncoreReactSDK.m
|
|
2
|
+
// Objective-C bridge declarations for React Native.
|
|
3
|
+
// Each method here corresponds to a Swift method in EncoreReactSDK.swift.
|
|
4
|
+
|
|
5
|
+
#import <React/RCTBridgeModule.h>
|
|
6
|
+
#import <React/RCTEventEmitter.h>
|
|
7
|
+
|
|
8
|
+
@interface RCT_EXTERN_MODULE(EncoreReactSDK, RCTEventEmitter)
|
|
9
|
+
|
|
10
|
+
RCT_EXTERN_METHOD(configure:(NSString *)apiKey
|
|
11
|
+
options:(NSDictionary *)options
|
|
12
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(identify:(NSString *)userId
|
|
16
|
+
attributes:(NSDictionary *)attributes
|
|
17
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
|
|
20
|
+
RCT_EXTERN_METHOD(setUserAttributes:(NSDictionary *)attributes
|
|
21
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
|
|
24
|
+
RCT_EXTERN_METHOD(reset:(RCTPromiseResolveBlock)resolve
|
|
25
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
26
|
+
|
|
27
|
+
RCT_EXTERN_METHOD(setClaimEnabled:(BOOL)enabled
|
|
28
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
29
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
30
|
+
|
|
31
|
+
RCT_EXTERN_METHOD(show:(NSString *)placementId
|
|
32
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
33
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
34
|
+
|
|
35
|
+
RCT_EXTERN_METHOD(registerCallbacks:(RCTPromiseResolveBlock)resolve
|
|
36
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
37
|
+
|
|
38
|
+
RCT_EXTERN_METHOD(completePurchaseRequest:(BOOL)success
|
|
39
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
40
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
41
|
+
|
|
42
|
+
@end
|