react-native-nami-sdk 3.3.2-3 → 3.3.2-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +14 -1
- package/android/src/main/java/com/namiml/reactnative/NamiBridgePackage.java +15 -0
- package/android/src/main/java/com/namiml/reactnative/NamiFlowManagerBridge.kt +22 -17
- package/android/src/main/java/com/namiml/reactnative/NamiOverlayControlBridge.kt +171 -0
- package/android/src/main/java/com/namiml/reactnative/ReactOverlayActivity.kt +29 -0
- package/android/src/main/res/values/styles.xml +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/specs/NativeNamiOverlayControl.d.ts +9 -0
- package/dist/src/NamiOverlayControl.d.ts +9 -0
- package/dist/src/NamiOverlayHost.d.ts +1 -0
- package/dist/src/overlay.d.ts +4 -0
- package/dist/src/registerOverlay.d.ts +1 -0
- package/dist/src/version.d.ts +1 -1
- package/index.ts +1 -0
- package/ios/Nami.swift +3 -3
- package/ios/NamiCampaignManagerBridge.m +1 -1
- package/ios/NamiCampaignManagerBridge.swift +19 -10
- package/ios/NamiCustomerManager.m +1 -1
- package/ios/NamiCustomerManager.swift +19 -10
- package/ios/NamiEntitlementManagerBridge.m +1 -1
- package/ios/NamiEntitlementManagerBridge.swift +17 -11
- package/ios/NamiFlowManagerBridge.m +1 -1
- package/ios/NamiFlowManagerBridge.swift +20 -21
- package/ios/NamiOverlayControlBridge.m +17 -0
- package/ios/NamiOverlayControlBridge.swift +136 -0
- package/ios/NamiPaywallManagerBridge.m +1 -1
- package/ios/NamiPaywallManagerBridge.swift +22 -19
- package/ios/NamiPurchaseManagerBridge.m +1 -1
- package/ios/NamiPurchaseManagerBridge.swift +20 -7
- package/package.json +8 -1
- package/react-native-nami-sdk.podspec +1 -1
- package/specs/NativeNamiOverlayControl.ts +9 -0
- package/src/NamiOverlayControl.tsx +48 -0
- 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.2.
|
|
89
|
-
amazonImplementation "com.namiml:sdk-amazon:3.3.2.
|
|
88
|
+
playImplementation "com.namiml:sdk-android:3.3.2.3"
|
|
89
|
+
amazonImplementation "com.namiml:sdk-amazon:3.3.2.3"
|
|
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"
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
<manifest
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
package="com.namiml.reactnative">
|
|
2
3
|
|
|
4
|
+
|
|
5
|
+
<application>
|
|
6
|
+
<activity
|
|
7
|
+
android:name=".ReactOverlayActivity"
|
|
8
|
+
android:theme="@style/Theme.TransparentReact"
|
|
9
|
+
android:exported="false"
|
|
10
|
+
android:launchMode="singleTop"
|
|
11
|
+
android:taskAffinity=""
|
|
12
|
+
android:noHistory="true"
|
|
13
|
+
android:excludeFromRecents="true"
|
|
14
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" />
|
|
15
|
+
</application>
|
|
3
16
|
</manifest>
|
|
@@ -29,6 +29,8 @@ public class NamiBridgePackage extends TurboReactPackage {
|
|
|
29
29
|
case NamiPaywallManagerBridgeModule.NAME -> new NamiPaywallManagerBridgeModule(context);
|
|
30
30
|
case NamiPurchaseManagerBridgeModule.NAME ->
|
|
31
31
|
new NamiPurchaseManagerBridgeModule(context);
|
|
32
|
+
case NamiOverlayControlBridgeModule.NAME ->
|
|
33
|
+
new NamiOverlayControlBridgeModule(context);
|
|
32
34
|
default -> null;
|
|
33
35
|
};
|
|
34
36
|
}
|
|
@@ -129,6 +131,19 @@ public class NamiBridgePackage extends TurboReactPackage {
|
|
|
129
131
|
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
130
132
|
)
|
|
131
133
|
);
|
|
134
|
+
|
|
135
|
+
moduleInfos.put(
|
|
136
|
+
NamiOverlayControlBridgeModule.NAME,
|
|
137
|
+
new ReactModuleInfo(
|
|
138
|
+
NamiOverlayControlBridgeModule.NAME,
|
|
139
|
+
NamiOverlayControlBridgeModule.NAME,
|
|
140
|
+
false,
|
|
141
|
+
false,
|
|
142
|
+
true,
|
|
143
|
+
false,
|
|
144
|
+
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
145
|
+
)
|
|
146
|
+
);
|
|
132
147
|
return moduleInfos;
|
|
133
148
|
};
|
|
134
149
|
}
|
|
@@ -4,39 +4,40 @@ import android.os.Handler
|
|
|
4
4
|
import android.os.Looper
|
|
5
5
|
import android.util.Log
|
|
6
6
|
import com.facebook.react.bridge.*
|
|
7
|
-
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
8
7
|
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
9
9
|
import com.facebook.react.turbomodule.core.interfaces.TurboModule
|
|
10
10
|
import com.namiml.flow.NamiFlowManager
|
|
11
11
|
|
|
12
12
|
@ReactModule(name = NamiFlowManagerBridgeModule.NAME)
|
|
13
13
|
class NamiFlowManagerBridgeModule internal constructor(
|
|
14
|
-
reactContext: ReactApplicationContext
|
|
15
|
-
) : ReactContextBaseJavaModule(reactContext),
|
|
16
|
-
|
|
14
|
+
reactContext: ReactApplicationContext,
|
|
15
|
+
) : ReactContextBaseJavaModule(reactContext),
|
|
16
|
+
TurboModule {
|
|
17
17
|
companion object {
|
|
18
18
|
const val NAME = "RNNamiFlowManager"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
override fun getName(): String
|
|
22
|
-
return NAME
|
|
23
|
-
}
|
|
21
|
+
override fun getName(): String = NAME
|
|
24
22
|
|
|
25
23
|
@ReactMethod
|
|
26
24
|
fun registerStepHandoff() {
|
|
27
25
|
NamiFlowManager.registerStepHandoff { handoffTag, handoffData ->
|
|
28
|
-
val payload =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
val payload =
|
|
27
|
+
Arguments.createMap().apply {
|
|
28
|
+
putString("handoffTag", handoffTag)
|
|
29
|
+
if (handoffData != null) {
|
|
30
|
+
try {
|
|
31
|
+
putMap("handoffData", Arguments.makeNativeMap(handoffData))
|
|
32
|
+
} catch (e: Exception) {
|
|
33
|
+
Log.d(NAME, "Failed to convert handoffData to NativeMap: ${e.localizedMessage}")
|
|
34
|
+
}
|
|
36
35
|
}
|
|
37
36
|
}
|
|
37
|
+
|
|
38
|
+
reactApplicationContext.runOnUiQueueThread {
|
|
39
|
+
sendEvent("Handoff", payload)
|
|
38
40
|
}
|
|
39
|
-
sendEvent("Handoff", payload)
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -74,7 +75,10 @@ class NamiFlowManagerBridgeModule internal constructor(
|
|
|
74
75
|
promise.resolve(NamiFlowManager.isFlowOpen())
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
private fun sendEvent(
|
|
78
|
+
private fun sendEvent(
|
|
79
|
+
eventName: String,
|
|
80
|
+
params: WritableMap?,
|
|
81
|
+
) {
|
|
78
82
|
reactApplicationContext
|
|
79
83
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
80
84
|
.emit(eventName, params)
|
|
@@ -82,5 +86,6 @@ class NamiFlowManagerBridgeModule internal constructor(
|
|
|
82
86
|
|
|
83
87
|
// Required for RN EventEmitter support
|
|
84
88
|
@ReactMethod fun addListener(eventName: String?) {}
|
|
89
|
+
|
|
85
90
|
@ReactMethod fun removeListeners(count: Int?) {}
|
|
86
91
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
package com.namiml.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.*
|
|
8
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
9
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
10
|
+
import com.facebook.react.turbomodule.core.interfaces.TurboModule
|
|
11
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
12
|
+
|
|
13
|
+
@ReactModule(name = NamiOverlayControlBridgeModule.NAME)
|
|
14
|
+
class NamiOverlayControlBridgeModule(private val ctx: ReactApplicationContext)
|
|
15
|
+
: ReactContextBaseJavaModule(ctx), TurboModule {
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val NAME = "RNNamiOverlayControl"
|
|
19
|
+
var currentOverlayActivity: ReactOverlayActivity? = null
|
|
20
|
+
var lastValidActivity: Activity? = null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun getName() = NAME
|
|
24
|
+
|
|
25
|
+
@ReactMethod
|
|
26
|
+
fun presentOverlay(promise: Promise) {
|
|
27
|
+
var theActivity: Activity? = null
|
|
28
|
+
if (reactApplicationContext.hasCurrentActivity()) {
|
|
29
|
+
theActivity = reactApplicationContext.currentActivity
|
|
30
|
+
|
|
31
|
+
// Check if it's the overlay activity that's finishing
|
|
32
|
+
if (theActivity is ReactOverlayActivity) {
|
|
33
|
+
if (theActivity.isFinishing || theActivity.isDestroyed) {
|
|
34
|
+
theActivity = lastValidActivity
|
|
35
|
+
if (theActivity == null || theActivity.isFinishing || theActivity.isDestroyed) {
|
|
36
|
+
promise.reject("NO_ACTIVITY", "No valid activity available")
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
lastValidActivity = theActivity
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
lastValidActivity = theActivity
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
theActivity = lastValidActivity
|
|
47
|
+
if (theActivity == null || theActivity.isFinishing || theActivity.isDestroyed) {
|
|
48
|
+
promise.reject("NO_ACTIVITY", "No valid activity available")
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (theActivity == null) {
|
|
54
|
+
promise.reject("NO_ACTIVITY", "No activity available")
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
startOverlayActivity(theActivity, promise)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private fun startOverlayActivity(activity: Activity, promise: Promise) {
|
|
62
|
+
try {
|
|
63
|
+
UiThreadUtil.runOnUiThread {
|
|
64
|
+
try {
|
|
65
|
+
val intent = Intent(activity, ReactOverlayActivity::class.java)
|
|
66
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
|
67
|
+
activity.startActivity(intent)
|
|
68
|
+
// Use modern transition API for Android 14+ (API 34+)
|
|
69
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
70
|
+
activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 0, 0)
|
|
71
|
+
} else {
|
|
72
|
+
@Suppress("DEPRECATION")
|
|
73
|
+
activity.overridePendingTransition(0, 0)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Emit ready event after a short delay to ensure activity is started
|
|
77
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
78
|
+
try {
|
|
79
|
+
if (ctx.catalystInstance != null) {
|
|
80
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
81
|
+
.emit("NamiOverlayReady", null)
|
|
82
|
+
}
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
Log.e(NAME, "Failed to emit NamiOverlayReady: ${e.message}")
|
|
85
|
+
}
|
|
86
|
+
}, 100)
|
|
87
|
+
|
|
88
|
+
promise.resolve(null)
|
|
89
|
+
} catch (uiError: Exception) {
|
|
90
|
+
Log.e(NAME, "Error in UI thread: ${uiError.message}", uiError)
|
|
91
|
+
promise.reject("UI_THREAD_ERROR", "Failed in UI thread: ${uiError.message}", uiError)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
Log.e(NAME, "Error presenting overlay: ${e.message}", e)
|
|
96
|
+
promise.reject("PRESENT_OVERLAY_ERROR", "Failed to present overlay: ${e.message}", e)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@ReactMethod
|
|
101
|
+
fun addListener(eventName: String?) {
|
|
102
|
+
// Required for NativeEventEmitter - no-op since we emit events directly
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@ReactMethod
|
|
106
|
+
fun removeListeners(count: Int?) {
|
|
107
|
+
// Required for NativeEventEmitter - no-op since we emit events directly
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactMethod
|
|
111
|
+
fun finishOverlay(result: ReadableMap?, promise: Promise) {
|
|
112
|
+
try {
|
|
113
|
+
// Emit result to main app listeners
|
|
114
|
+
val payload: WritableMap = Arguments.createMap().apply {
|
|
115
|
+
if (result != null) {
|
|
116
|
+
merge(result)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (ctx.catalystInstance != null) {
|
|
121
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
122
|
+
.emit("NamiOverlayResult", payload)
|
|
123
|
+
}
|
|
124
|
+
} catch (e: Exception) {
|
|
125
|
+
Log.e(NAME, "Failed to emit NamiOverlayResult: ${e.message}")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
UiThreadUtil.runOnUiThread {
|
|
129
|
+
// Try to finish the tracked overlay activity first
|
|
130
|
+
currentOverlayActivity?.let { overlay ->
|
|
131
|
+
overlay.finish()
|
|
132
|
+
// Use modern transition API for Android 14+ (API 34+)
|
|
133
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
134
|
+
overlay.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 0, 0)
|
|
135
|
+
} else {
|
|
136
|
+
@Suppress("DEPRECATION")
|
|
137
|
+
overlay.overridePendingTransition(0, 0)
|
|
138
|
+
}
|
|
139
|
+
currentOverlayActivity = null
|
|
140
|
+
|
|
141
|
+
// Wait for activity to actually finish before resolving promise
|
|
142
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
143
|
+
promise.resolve(null)
|
|
144
|
+
}, 100)
|
|
145
|
+
} ?: run {
|
|
146
|
+
// Fallback to current activity if it's an overlay
|
|
147
|
+
(ctx.currentActivity as? ReactOverlayActivity)?.apply {
|
|
148
|
+
finish()
|
|
149
|
+
// Use modern transition API for Android 14+ (API 34+)
|
|
150
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
151
|
+
overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 0, 0)
|
|
152
|
+
} else {
|
|
153
|
+
@Suppress("DEPRECATION")
|
|
154
|
+
overridePendingTransition(0, 0)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Wait for activity to actually finish before resolving promise
|
|
158
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
159
|
+
promise.resolve(null)
|
|
160
|
+
}, 100)
|
|
161
|
+
} ?: run {
|
|
162
|
+
promise.resolve(null)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
Log.e(NAME, "Failed to finish overlay: ${e.message}", e)
|
|
168
|
+
promise.reject("FINISH_OVERLAY_ERROR", "Failed to finish overlay", e)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package com.namiml.reactnative
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import com.facebook.react.ReactActivity
|
|
5
|
+
|
|
6
|
+
class ReactOverlayActivity : ReactActivity() {
|
|
7
|
+
override fun getMainComponentName(): String = "NamiOverlayHost"
|
|
8
|
+
|
|
9
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
10
|
+
super.onCreate(savedInstanceState)
|
|
11
|
+
overridePendingTransition(0, 0)
|
|
12
|
+
|
|
13
|
+
// Register this activity with the bridge module
|
|
14
|
+
NamiOverlayControlBridgeModule.currentOverlayActivity = this
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun onBackPressed() {
|
|
18
|
+
finish()
|
|
19
|
+
overridePendingTransition(0, 0)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun onDestroy() {
|
|
23
|
+
super.onDestroy()
|
|
24
|
+
// Clear the reference when activity is destroyed
|
|
25
|
+
if (NamiOverlayControlBridgeModule.currentOverlayActivity == this) {
|
|
26
|
+
NamiOverlayControlBridgeModule.currentOverlayActivity = null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<style name="Theme.TransparentReact" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
|
4
|
+
<item name="android:windowIsTranslucent">true</item>
|
|
5
|
+
<item name="android:windowBackground">@android:color/transparent</item>
|
|
6
|
+
<item name="android:windowNoTitle">true</item>
|
|
7
|
+
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
8
|
+
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
9
|
+
</style>
|
|
10
|
+
</resources>
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { NamiEntitlementManager } from './src/NamiEntitlementManager';
|
|
|
5
5
|
export { NamiPurchaseManager } from './src/NamiPurchaseManager';
|
|
6
6
|
export { NamiPaywallManager } from './src/NamiPaywallManager';
|
|
7
7
|
export { NamiFlowManager } from './src/NamiFlowManager';
|
|
8
|
+
export { NamiOverlayControl } from './src/NamiOverlayControl';
|
|
8
9
|
export * from './src/types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
presentOverlay(): Promise<void>;
|
|
4
|
+
finishOverlay(result?: {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
} | null): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: Spec;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NativeEventEmitter } from 'react-native';
|
|
2
|
+
export declare const NamiOverlayControl: {
|
|
3
|
+
emitter: NativeEventEmitter;
|
|
4
|
+
presentOverlay(): Promise<void>;
|
|
5
|
+
finishOverlay(result?: any): Promise<void>;
|
|
6
|
+
onOverlayReady(handler: () => void): () => void;
|
|
7
|
+
onOverlayResult(handler: (result: any) => void): () => void;
|
|
8
|
+
};
|
|
9
|
+
export default function NamiOverlayHost(): any;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function NamiOverlayHost(): null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function presentOverlay(): Promise<void>;
|
|
2
|
+
export declare function finishOverlay(result?: any): Promise<void>;
|
|
3
|
+
export declare function onOverlayReady(handler: () => void): () => void;
|
|
4
|
+
export declare function onOverlayResult(handler: (result: any) => void): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/version.d.ts
CHANGED
package/index.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { NamiEntitlementManager } from './src/NamiEntitlementManager';
|
|
|
5
5
|
export { NamiPurchaseManager } from './src/NamiPurchaseManager';
|
|
6
6
|
export { NamiPaywallManager } from './src/NamiPaywallManager';
|
|
7
7
|
export { NamiFlowManager } from './src/NamiFlowManager';
|
|
8
|
+
export { NamiOverlayControl } from './src/NamiOverlayControl';
|
|
8
9
|
export * from './src/types';
|
package/ios/Nami.swift
CHANGED
|
@@ -8,9 +8,9 @@ import Foundation
|
|
|
8
8
|
import NamiApple
|
|
9
9
|
import React
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
12
|
+
extension RNNami: RCTTurboModule {}
|
|
13
|
+
#endif
|
|
14
14
|
|
|
15
15
|
@objc(RNNami)
|
|
16
16
|
class RNNami: NSObject {
|
|
@@ -10,9 +10,9 @@ import NamiApple
|
|
|
10
10
|
import os
|
|
11
11
|
import React
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
14
|
+
extension RNNamiCampaignManager: RCTTurboModule {}
|
|
15
|
+
#endif
|
|
16
16
|
|
|
17
17
|
@objc(RNNamiCampaignManager)
|
|
18
18
|
class RNNamiCampaignManager: RCTEventEmitter {
|
|
@@ -20,7 +20,20 @@ class RNNamiCampaignManager: RCTEventEmitter {
|
|
|
20
20
|
|
|
21
21
|
override init() {
|
|
22
22
|
super.init()
|
|
23
|
-
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
26
|
+
|
|
27
|
+
private var hasListeners = false
|
|
28
|
+
override func startObserving() { hasListeners = true }
|
|
29
|
+
override func stopObserving() { hasListeners = false }
|
|
30
|
+
|
|
31
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
32
|
+
guard hasListeners else {
|
|
33
|
+
print("[RNNamiCampaignManager] Warning: no listeners, so event not being sent to JS.")
|
|
34
|
+
return
|
|
35
|
+
} // optional but avoids warnings
|
|
36
|
+
sendEvent(withName: name, body: body)
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
override func supportedEvents() -> [String]! {
|
|
@@ -138,9 +151,7 @@ class RNNamiCampaignManager: RCTEventEmitter {
|
|
|
138
151
|
"timeSpentOnPaywall": paywallEvent.timeSpentOnPaywall,
|
|
139
152
|
]
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
RNNamiCampaignManager.shared?.sendEvent(withName: "NamiPaywallEvent", body: payload)
|
|
143
|
-
}
|
|
154
|
+
safeSend(withName: "NamiPaywallEvent", body: payload)
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
func handleLaunch(callback: RCTResponseSenderBlock?, success: Bool, error: Error?) {
|
|
@@ -294,9 +305,7 @@ class RNNamiCampaignManager: RCTEventEmitter {
|
|
|
294
305
|
func registerForAvailableCampaigns() {
|
|
295
306
|
NamiCampaignManager.registerAvailableCampaignsHandler { availableCampaigns in
|
|
296
307
|
let dictionaries = availableCampaigns.map { campaign in self.campaignInToDictionary(campaign) }
|
|
297
|
-
|
|
298
|
-
RNNamiCampaignManager.shared?.sendEvent(withName: "AvailableCampaignsChanged", body: dictionaries)
|
|
299
|
-
}
|
|
308
|
+
self.safeSend(withName: "AvailableCampaignsChanged", body: dictionaries)
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
}
|
|
@@ -9,9 +9,9 @@ import Foundation
|
|
|
9
9
|
import NamiApple
|
|
10
10
|
import React
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
13
|
+
extension RNNamiCustomerManager: RCTTurboModule {}
|
|
14
|
+
#endif
|
|
15
15
|
|
|
16
16
|
@objc(RNNamiCustomerManager)
|
|
17
17
|
class RNNamiCustomerManager: RCTEventEmitter {
|
|
@@ -19,7 +19,20 @@ class RNNamiCustomerManager: RCTEventEmitter {
|
|
|
19
19
|
|
|
20
20
|
override init() {
|
|
21
21
|
super.init()
|
|
22
|
-
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
25
|
+
|
|
26
|
+
private var hasListeners = false
|
|
27
|
+
override func startObserving() { hasListeners = true }
|
|
28
|
+
override func stopObserving() { hasListeners = false }
|
|
29
|
+
|
|
30
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
31
|
+
guard hasListeners else {
|
|
32
|
+
print("[RNNamiCustomerManager] Warning: no listeners, so event not being sent to JS.")
|
|
33
|
+
return
|
|
34
|
+
} // optional but avoids warnings
|
|
35
|
+
sendEvent(withName: name, body: body)
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
override func supportedEvents() -> [String]! {
|
|
@@ -141,9 +154,7 @@ class RNNamiCustomerManager: RCTEventEmitter {
|
|
|
141
154
|
func registerJourneyStateHandler() {
|
|
142
155
|
NamiCustomerManager.registerJourneyStateHandler { journeyState in
|
|
143
156
|
let dictionary = self.journeyStateToDictionary(journeyState)
|
|
144
|
-
|
|
145
|
-
RNNamiCustomerManager.shared?.sendEvent(withName: "JourneyStateChanged", body: dictionary)
|
|
146
|
-
}
|
|
157
|
+
self.safeSend(withName: "JourneyStateChanged", body: dictionary)
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
160
|
|
|
@@ -184,9 +195,7 @@ class RNNamiCustomerManager: RCTEventEmitter {
|
|
|
184
195
|
"success": success,
|
|
185
196
|
"error": error?._code as Any,
|
|
186
197
|
]
|
|
187
|
-
|
|
188
|
-
RNNamiCustomerManager.shared?.sendEvent(withName: "AccountStateChanged", body: payload)
|
|
189
|
-
}
|
|
198
|
+
self.safeSend(withName: "AccountStateChanged", body: payload)
|
|
190
199
|
}
|
|
191
200
|
}
|
|
192
201
|
}
|
|
@@ -9,9 +9,9 @@ import Foundation
|
|
|
9
9
|
import NamiApple
|
|
10
10
|
import React
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
13
|
+
extension RNNamiEntitlementManager: RCTTurboModule {}
|
|
14
|
+
#endif
|
|
15
15
|
|
|
16
16
|
@objc(RNNamiEntitlementManager)
|
|
17
17
|
class RNNamiEntitlementManager: RCTEventEmitter {
|
|
@@ -22,8 +22,18 @@ class RNNamiEntitlementManager: RCTEventEmitter {
|
|
|
22
22
|
RNNamiEntitlementManager.shared = self
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
override
|
|
26
|
-
|
|
25
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
26
|
+
|
|
27
|
+
private var hasListeners = false
|
|
28
|
+
override func startObserving() { hasListeners = true }
|
|
29
|
+
override func stopObserving() { hasListeners = false }
|
|
30
|
+
|
|
31
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
32
|
+
guard hasListeners else {
|
|
33
|
+
print("[RNNamiEntitlementManager] Warning: no listeners, so event not being sent to JS.")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
sendEvent(withName: name, body: body)
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
override func supportedEvents() -> [String]! {
|
|
@@ -74,9 +84,7 @@ class RNNamiEntitlementManager: RCTEventEmitter {
|
|
|
74
84
|
func refresh() {
|
|
75
85
|
NamiEntitlementManager.refresh { entitlements in
|
|
76
86
|
let dicts = entitlements.map { self.entitlementToDictionary($0) }
|
|
77
|
-
|
|
78
|
-
RNNamiEntitlementManager.shared?.sendEvent(withName: "EntitlementsChanged", body: dicts)
|
|
79
|
-
}
|
|
87
|
+
self.safeSend(withName: "EntitlementsChanged", body: dicts)
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
|
|
@@ -84,9 +92,7 @@ class RNNamiEntitlementManager: RCTEventEmitter {
|
|
|
84
92
|
func registerActiveEntitlementsHandler() {
|
|
85
93
|
NamiEntitlementManager.registerActiveEntitlementsHandler { entitlements in
|
|
86
94
|
let dicts = entitlements.map { self.entitlementToDictionary($0) }
|
|
87
|
-
|
|
88
|
-
RNNamiEntitlementManager.shared?.sendEvent(withName: "EntitlementsChanged", body: dicts)
|
|
89
|
-
}
|
|
95
|
+
self.safeSend(withName: "EntitlementsChanged", body: dicts)
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -9,9 +9,9 @@ import Foundation
|
|
|
9
9
|
import NamiApple
|
|
10
10
|
import React
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
13
|
+
extension RNNamiFlowManager: RCTTurboModule {}
|
|
14
|
+
#endif
|
|
15
15
|
|
|
16
16
|
@objc(RNNamiFlowManager)
|
|
17
17
|
class RNNamiFlowManager: RCTEventEmitter {
|
|
@@ -19,11 +19,20 @@ class RNNamiFlowManager: RCTEventEmitter {
|
|
|
19
19
|
|
|
20
20
|
override init() {
|
|
21
21
|
super.init()
|
|
22
|
-
RNNamiFlowManager.shared = self
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
override
|
|
26
|
-
|
|
24
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
25
|
+
|
|
26
|
+
private var hasListeners = false
|
|
27
|
+
override func startObserving() { hasListeners = true }
|
|
28
|
+
override func stopObserving() { hasListeners = false }
|
|
29
|
+
|
|
30
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
31
|
+
guard hasListeners else {
|
|
32
|
+
print("[RNNamiFlowManager] Warning: no listeners, so event not being sent to JS.")
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
sendEvent(withName: name, body: body)
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
override func supportedEvents() -> [String]! {
|
|
@@ -39,36 +48,26 @@ class RNNamiFlowManager: RCTEventEmitter {
|
|
|
39
48
|
payload["handoffData"] = data
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
RNNamiFlowManager.shared?.sendEvent(withName: "Handoff", body: payload)
|
|
44
|
-
}
|
|
51
|
+
self.safeSend(withName: "Handoff", body: payload)
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
@objc func registerEventHandler() {
|
|
49
56
|
NamiFlowManager.registerEventHandler { payload in
|
|
50
|
-
|
|
51
|
-
RNNamiFlowManager.shared?.sendEvent(withName: "FlowEvent", body: payload)
|
|
52
|
-
}
|
|
57
|
+
self.safeSend(withName: "FlowEvent", body: payload)
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
@objc func resume() {
|
|
57
|
-
|
|
58
|
-
NamiFlowManager.resume()
|
|
59
|
-
}
|
|
62
|
+
NamiFlowManager.resume()
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
@objc func pause() {
|
|
63
|
-
|
|
64
|
-
NamiFlowManager.pause()
|
|
65
|
-
}
|
|
66
|
+
NamiFlowManager.pause()
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
@objc func finish() {
|
|
69
|
-
|
|
70
|
-
NamiFlowManager.finish()
|
|
71
|
-
}
|
|
70
|
+
NamiFlowManager.finish()
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
@objc func isFlowOpen(_ resolve: @escaping RCTPromiseResolveBlock, rejecter _: @escaping RCTPromiseRejectBlock) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(RNNamiOverlayControl, RCTEventEmitter)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(presentOverlay:(RCTPromiseResolveBlock)resolve
|
|
7
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(finishOverlay:(NSDictionary *)result
|
|
10
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
14
|
+
return YES;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
6
|
+
extension RNNamiPaywallManager: RCTTurboModule {}
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
@objc(RNNamiOverlayControl)
|
|
10
|
+
class NamiOverlayControlBridge: RCTEventEmitter {
|
|
11
|
+
private var overlayViewController: UIViewController?
|
|
12
|
+
private var hasListeners = false
|
|
13
|
+
private var isPresenting = false
|
|
14
|
+
private var isDismissing = false
|
|
15
|
+
|
|
16
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override func supportedEvents() -> [String]! {
|
|
21
|
+
return ["NamiOverlayReady", "NamiOverlayResult"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override func startObserving() { hasListeners = true }
|
|
25
|
+
override func stopObserving() { hasListeners = false }
|
|
26
|
+
|
|
27
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
28
|
+
guard hasListeners else {
|
|
29
|
+
print("[RNNamiOverlayControl] Warning: no listeners, so event not being sent to JS.")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
sendEvent(withName: name, body: body)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc func presentOverlay(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
36
|
+
DispatchQueue.main.async {
|
|
37
|
+
// If we're already presenting or dismissing, wait and retry
|
|
38
|
+
if self.isPresenting || self.isDismissing {
|
|
39
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
40
|
+
self.presentOverlay(resolve, rejecter: reject)
|
|
41
|
+
}
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Find the root view controller more reliably
|
|
46
|
+
var rootViewController: UIViewController?
|
|
47
|
+
|
|
48
|
+
if #available(iOS 13.0, *) {
|
|
49
|
+
rootViewController = UIApplication.shared.connectedScenes
|
|
50
|
+
.compactMap { $0 as? UIWindowScene }
|
|
51
|
+
.flatMap { $0.windows }
|
|
52
|
+
.first(where: { $0.isKeyWindow })?.rootViewController
|
|
53
|
+
} else {
|
|
54
|
+
rootViewController = UIApplication.shared.keyWindow?.rootViewController
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
guard let rootVC = rootViewController else {
|
|
58
|
+
reject("NO_ROOT_VIEW_CONTROLLER", "No root view controller available", nil)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create a React Native view controller for the overlay
|
|
63
|
+
guard let bridge = self.bridge else {
|
|
64
|
+
reject("NO_BRIDGE", "React Native bridge not available", nil)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let rootView = RCTRootView(
|
|
69
|
+
bridge: bridge,
|
|
70
|
+
moduleName: "NamiOverlayHost",
|
|
71
|
+
initialProperties: [:]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
let overlayViewController = UIViewController()
|
|
75
|
+
overlayViewController.view = rootView
|
|
76
|
+
overlayViewController.modalPresentationStyle = .overFullScreen
|
|
77
|
+
overlayViewController.modalTransitionStyle = .crossDissolve
|
|
78
|
+
overlayViewController.view.backgroundColor = UIColor.clear
|
|
79
|
+
|
|
80
|
+
self.overlayViewController = overlayViewController
|
|
81
|
+
|
|
82
|
+
// Find the top-most presented view controller
|
|
83
|
+
var topController = rootVC
|
|
84
|
+
while let presented = topController.presentedViewController {
|
|
85
|
+
topController = presented
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if the top controller can present (not already in the process of presenting/dismissing)
|
|
89
|
+
if topController.isBeingPresented || topController.isBeingDismissed {
|
|
90
|
+
// Wait longer and try again
|
|
91
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
92
|
+
self.presentOverlay(resolve, rejecter: reject)
|
|
93
|
+
}
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
self.isPresenting = true
|
|
98
|
+
|
|
99
|
+
topController.present(overlayViewController, animated: false) {
|
|
100
|
+
self.isPresenting = false
|
|
101
|
+
// Emit ready event after presentation completes
|
|
102
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
103
|
+
if self.hasListeners {
|
|
104
|
+
self.safeSend(withName: "NamiOverlayReady", body: nil)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
resolve(nil)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@objc func finishOverlay(_ result: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: @escaping RCTPromiseRejectBlock) {
|
|
113
|
+
DispatchQueue.main.async {
|
|
114
|
+
guard let overlayViewController = self.overlayViewController else {
|
|
115
|
+
resolve(nil)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Emit result to listeners
|
|
120
|
+
if self.hasListeners {
|
|
121
|
+
self.safeSend(withName: "NamiOverlayResult", body: result)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
self.isDismissing = true
|
|
125
|
+
|
|
126
|
+
overlayViewController.dismiss(animated: false) {
|
|
127
|
+
self.overlayViewController = nil
|
|
128
|
+
self.isDismissing = false
|
|
129
|
+
// Add a longer delay before resolving to ensure the view controller is fully dismissed
|
|
130
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
131
|
+
resolve(nil)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -9,9 +9,9 @@ import Foundation
|
|
|
9
9
|
import NamiApple
|
|
10
10
|
import React
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
13
|
+
extension RNNamiPaywallManager: RCTTurboModule {}
|
|
14
|
+
#endif
|
|
15
15
|
|
|
16
16
|
@objc(RNNamiPaywallManager)
|
|
17
17
|
class RNNamiPaywallManager: RCTEventEmitter {
|
|
@@ -19,7 +19,20 @@ class RNNamiPaywallManager: RCTEventEmitter {
|
|
|
19
19
|
|
|
20
20
|
override init() {
|
|
21
21
|
super.init()
|
|
22
|
-
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
25
|
+
|
|
26
|
+
private var hasListeners = false
|
|
27
|
+
override func startObserving() { hasListeners = true }
|
|
28
|
+
override func stopObserving() { hasListeners = false }
|
|
29
|
+
|
|
30
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
31
|
+
guard hasListeners else {
|
|
32
|
+
print("[RNNamiPaywallManager] Warning: no listeners, so event not being sent to JS.")
|
|
33
|
+
return
|
|
34
|
+
} // optional but avoids warnings
|
|
35
|
+
sendEvent(withName: name, body: body)
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
override func supportedEvents() -> [String]! {
|
|
@@ -80,9 +93,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
|
|
|
80
93
|
func registerBuySkuHandler() {
|
|
81
94
|
NamiPaywallManager.registerBuySkuHandler { sku in
|
|
82
95
|
let dictionary = RNNamiPurchaseManager.skuToSKUDict(sku)
|
|
83
|
-
|
|
84
|
-
RNNamiPaywallManager.shared?.sendEvent(withName: "RegisterBuySKU", body: dictionary)
|
|
85
|
-
}
|
|
96
|
+
self.safeSend(withName: "RegisterBuySKU", body: dictionary)
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
@@ -90,9 +101,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
|
|
|
90
101
|
func registerCloseHandler() {
|
|
91
102
|
NamiPaywallManager.registerCloseHandler { _ in
|
|
92
103
|
let dictionary = NSDictionary(dictionary: ["PaywallCloseRequested": true].compactMapValues { $0 })
|
|
93
|
-
|
|
94
|
-
RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallCloseRequested", body: dictionary)
|
|
95
|
-
}
|
|
104
|
+
self.safeSend(withName: "PaywallCloseRequested", body: dictionary)
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
|
|
@@ -100,9 +109,7 @@ class RNNamiPaywallManager: RCTEventEmitter {
|
|
|
100
109
|
func registerSignInHandler() {
|
|
101
110
|
NamiPaywallManager.registerSignInHandler { _ in
|
|
102
111
|
let dictionary = NSDictionary(dictionary: ["PaywallSignInRequested": true].compactMapValues { $0 })
|
|
103
|
-
|
|
104
|
-
RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallSignInRequested", body: dictionary)
|
|
105
|
-
}
|
|
112
|
+
self.safeSend(withName: "PaywallSignInRequested", body: dictionary)
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
|
|
@@ -110,18 +117,14 @@ class RNNamiPaywallManager: RCTEventEmitter {
|
|
|
110
117
|
func registerRestoreHandler() {
|
|
111
118
|
NamiPaywallManager.registerRestoreHandler {
|
|
112
119
|
let dictionary = NSDictionary(dictionary: ["PaywallRestoreRequested": true].compactMapValues { $0 })
|
|
113
|
-
|
|
114
|
-
RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallRestoreRequested", body: dictionary)
|
|
115
|
-
}
|
|
120
|
+
self.safeSend(withName: "PaywallRestoreRequested", body: dictionary)
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
@objc(registerDeeplinkActionHandler)
|
|
120
125
|
func registerDeeplinkActionHandler() {
|
|
121
126
|
NamiPaywallManager.registerDeeplinkActionHandler { url in
|
|
122
|
-
|
|
123
|
-
RNNamiPaywallManager.shared?.sendEvent(withName: "PaywallDeeplinkAction", body: url)
|
|
124
|
-
}
|
|
127
|
+
self.safeSend(withName: "PaywallDeeplinkAction", body: url)
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -9,9 +9,9 @@ import Foundation
|
|
|
9
9
|
import NamiApple
|
|
10
10
|
import React
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
13
|
+
extension RNNamiPurchaseManager: RCTTurboModule {}
|
|
14
|
+
#endif
|
|
15
15
|
|
|
16
16
|
@objc(RNNamiPurchaseManager)
|
|
17
17
|
class RNNamiPurchaseManager: RCTEventEmitter {
|
|
@@ -19,7 +19,20 @@ class RNNamiPurchaseManager: RCTEventEmitter {
|
|
|
19
19
|
|
|
20
20
|
override init() {
|
|
21
21
|
super.init()
|
|
22
|
-
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override class func requiresMainQueueSetup() -> Bool { true }
|
|
25
|
+
|
|
26
|
+
private var hasListeners = false
|
|
27
|
+
override func startObserving() { hasListeners = true }
|
|
28
|
+
override func stopObserving() { hasListeners = false }
|
|
29
|
+
|
|
30
|
+
private func safeSend(withName name: String, body: Any?) {
|
|
31
|
+
guard hasListeners else {
|
|
32
|
+
print("[RNNamiPurchaseManager] Warning: no listeners, so event not being sent to JS.")
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
sendEvent(withName: name, body: body)
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
override func supportedEvents() -> [String]! {
|
|
@@ -158,7 +171,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
|
|
|
158
171
|
"purchaseState": stateString,
|
|
159
172
|
"error": error?.localizedDescription,
|
|
160
173
|
]
|
|
161
|
-
self.
|
|
174
|
+
self.safeSend(withName: "PurchasesChanged", body: payload)
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
|
@@ -187,7 +200,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
|
|
|
187
200
|
"newPurchases": newPurchasesDictionaries,
|
|
188
201
|
"oldPurchases": oldPurchasesDictionaries,
|
|
189
202
|
]
|
|
190
|
-
|
|
203
|
+
self.safeSend(withName: "RestorePurchasesStateChanged", body: payload)
|
|
191
204
|
}
|
|
192
205
|
}
|
|
193
206
|
|
|
@@ -216,7 +229,7 @@ class RNNamiPurchaseManager: RCTEventEmitter {
|
|
|
216
229
|
"newPurchases": newPurchasesDictionaries,
|
|
217
230
|
"oldPurchases": oldPurchasesDictionaries,
|
|
218
231
|
]
|
|
219
|
-
|
|
232
|
+
self.safeSend(withName: "RestorePurchasesStateChanged", body: payload)
|
|
220
233
|
}
|
|
221
234
|
}
|
|
222
235
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nami-sdk",
|
|
3
|
-
"version": "3.3.2-
|
|
3
|
+
"version": "3.3.2-5",
|
|
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": "dist/index.d.ts",
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
"javaPackageName": "com.namiml.reactnative"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"react-native": {
|
|
16
|
+
"android": {
|
|
17
|
+
"sourceDir": "android",
|
|
18
|
+
"packageImportPath": "com.namiml.reactnative.NamiBridgePackage",
|
|
19
|
+
"libraryName": "react-native-nami-sdk"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
15
22
|
"scripts": {
|
|
16
23
|
"build": "tsc",
|
|
17
24
|
"generate:version": "ts-node scripts/generate-version.ts",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
presentOverlay(): Promise<void>;
|
|
6
|
+
finishOverlay(result?: { [key: string]: unknown } | null): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('RNNamiOverlayControl');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AppRegistry, View, StyleSheet } from 'react-native';
|
|
2
|
+
import {
|
|
3
|
+
TurboModuleRegistry,
|
|
4
|
+
NativeModules,
|
|
5
|
+
NativeEventEmitter,
|
|
6
|
+
} from 'react-native';
|
|
7
|
+
import type { Spec } from '../specs/NativeNamiOverlayControl';
|
|
8
|
+
|
|
9
|
+
const RNNamiOverlayControl: Spec =
|
|
10
|
+
TurboModuleRegistry.getEnforcing?.<Spec>('RNNamiOverlayControl') ??
|
|
11
|
+
NativeModules.RNNamiOverlayControl;
|
|
12
|
+
|
|
13
|
+
const emitter = new NativeEventEmitter(NativeModules.RNNamiOverlayControl);
|
|
14
|
+
|
|
15
|
+
export const NamiOverlayControl = {
|
|
16
|
+
emitter,
|
|
17
|
+
|
|
18
|
+
presentOverlay(): Promise<void> {
|
|
19
|
+
return RNNamiOverlayControl.presentOverlay();
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
finishOverlay(result?: any): Promise<void> {
|
|
23
|
+
return RNNamiOverlayControl.finishOverlay(result ?? null);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
onOverlayReady(handler: () => void) {
|
|
27
|
+
const sub = emitter.addListener('NamiOverlayReady', handler);
|
|
28
|
+
return () => sub.remove();
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
onOverlayResult(handler: (result: any) => void) {
|
|
32
|
+
const sub = emitter.addListener('NamiOverlayResult', handler);
|
|
33
|
+
return () => sub.remove();
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default function NamiOverlayHost() {
|
|
38
|
+
return <View style={styles.overlay} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const styles = StyleSheet.create({
|
|
42
|
+
overlay: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
backgroundColor: 'transparent',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
AppRegistry.registerComponent('NamiOverlayHost', () => NamiOverlayHost);
|
package/src/version.ts
CHANGED