@virex-tech/paywallo-sdk 1.0.0
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/LICENSE +21 -0
- package/MonetySdk.podspec +19 -0
- package/README.md +164 -0
- package/android/build.gradle +54 -0
- package/android/src/main/java/com/monety/sdk/MonetySdkPackage.kt +16 -0
- package/android/src/main/java/com/monety/sdk/PanelStoreKitModule.kt +315 -0
- package/android/src/main/java/com/monety/sdk/PanelWebView.kt +74 -0
- package/android/src/main/java/com/monety/sdk/PanelWebViewManager.kt +52 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +16 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +315 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebView.kt +74 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewManager.kt +52 -0
- package/dist/index.d.mts +1141 -0
- package/dist/index.d.ts +1141 -0
- package/dist/index.js +4793 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4709 -0
- package/dist/index.mjs.map +1 -0
- package/ios/PanelStoreKit.m +21 -0
- package/ios/PanelStoreKit.swift +245 -0
- package/ios/PanelWebView.h +15 -0
- package/ios/PanelWebView.m +182 -0
- package/ios/PanelWebViewManager.m +41 -0
- package/ios/PanelWebViewModule.h +8 -0
- package/ios/PanelWebViewModule.m +42 -0
- package/ios/PaywalloStoreKit.m +21 -0
- package/ios/PaywalloStoreKit.swift +245 -0
- package/ios/PaywalloWebView.h +15 -0
- package/ios/PaywalloWebView.m +182 -0
- package/ios/PaywalloWebViewManager.m +41 -0
- package/ios/PaywalloWebViewModule.h +8 -0
- package/ios/PaywalloWebViewModule.m +42 -0
- package/package.json +93 -0
- package/react-native.config.js +14 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.monety.sdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReadableArray
|
|
5
|
+
import com.facebook.react.common.MapBuilder
|
|
6
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
9
|
+
|
|
10
|
+
class PanelWebViewManager(private val reactContext: ReactApplicationContext) : SimpleViewManager<PanelWebView>() {
|
|
11
|
+
|
|
12
|
+
companion object {
|
|
13
|
+
const val REACT_CLASS = "PanelWebView"
|
|
14
|
+
const val COMMAND_INJECT_JS = 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun getName(): String = REACT_CLASS
|
|
18
|
+
|
|
19
|
+
override fun createViewInstance(context: ThemedReactContext): PanelWebView {
|
|
20
|
+
return PanelWebView(context)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@ReactProp(name = "sourceUrl")
|
|
24
|
+
fun setSourceUrl(view: PanelWebView, url: String?) {
|
|
25
|
+
view.setSourceUrl(url)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@ReactProp(name = "sourceHtml")
|
|
29
|
+
fun setSourceHtml(view: PanelWebView, html: String?) {
|
|
30
|
+
view.setSourceHtml(html)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
|
|
34
|
+
return MapBuilder.builder<String, Any>()
|
|
35
|
+
.put("onMessage", MapBuilder.of("registrationName", "onMessage"))
|
|
36
|
+
.put("onLoadEnd", MapBuilder.of("registrationName", "onLoadEnd"))
|
|
37
|
+
.build()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun getCommandsMap(): Map<String, Int>? {
|
|
41
|
+
return MapBuilder.of("injectJavaScript", COMMAND_INJECT_JS)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun receiveCommand(view: PanelWebView, commandId: String?, args: ReadableArray?) {
|
|
45
|
+
when (commandId) {
|
|
46
|
+
"injectJavaScript" -> {
|
|
47
|
+
val script = args?.getString(0) ?: return
|
|
48
|
+
view.injectJavaScript(script)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.paywallo.sdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class PaywalloSdkPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(PaywalloStoreKitModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return listOf(PaywalloWebViewManager(reactContext))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
package com.paywallo.sdk
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import com.android.billingclient.api.*
|
|
5
|
+
import com.facebook.react.bridge.*
|
|
6
|
+
import kotlinx.coroutines.*
|
|
7
|
+
|
|
8
|
+
class PaywalloStoreKitModule(private val reactContext: ReactApplicationContext) :
|
|
9
|
+
ReactContextBaseJavaModule(reactContext), PurchasesUpdatedListener {
|
|
10
|
+
|
|
11
|
+
private var billingClient: BillingClient? = null
|
|
12
|
+
private var purchasePromise: Promise? = null
|
|
13
|
+
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
14
|
+
|
|
15
|
+
override fun getName() = "PaywalloStoreKit"
|
|
16
|
+
|
|
17
|
+
private fun ensureBillingClient(): BillingClient {
|
|
18
|
+
if (billingClient == null || billingClient?.isReady != true) {
|
|
19
|
+
billingClient = BillingClient.newBuilder(reactContext)
|
|
20
|
+
.setListener(this)
|
|
21
|
+
.enablePendingPurchases()
|
|
22
|
+
.build()
|
|
23
|
+
}
|
|
24
|
+
return billingClient!!
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private suspend fun connectBilling(): Boolean {
|
|
28
|
+
val client = ensureBillingClient()
|
|
29
|
+
if (client.isReady) return true
|
|
30
|
+
|
|
31
|
+
return suspendCancellableCoroutine { cont ->
|
|
32
|
+
client.startConnection(object : BillingClientStateListener {
|
|
33
|
+
override fun onBillingSetupFinished(result: BillingResult) {
|
|
34
|
+
if (cont.isActive) {
|
|
35
|
+
cont.resume(result.responseCode == BillingClient.BillingResponseCode.OK) {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
override fun onBillingServiceDisconnected() {
|
|
39
|
+
if (cont.isActive) {
|
|
40
|
+
cont.resume(false) {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@ReactMethod
|
|
48
|
+
fun getProducts(productIds: ReadableArray, promise: Promise) {
|
|
49
|
+
scope.launch {
|
|
50
|
+
try {
|
|
51
|
+
if (!connectBilling()) {
|
|
52
|
+
promise.reject("BILLING_ERROR", "Failed to connect to billing service")
|
|
53
|
+
return@launch
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
val ids = mutableListOf<String>()
|
|
57
|
+
for (i in 0 until productIds.size()) {
|
|
58
|
+
productIds.getString(i)?.let { ids.add(it) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Try subscriptions first, then one-time
|
|
62
|
+
val subsProducts = queryProducts(ids, BillingClient.ProductType.SUBS)
|
|
63
|
+
val inappProducts = queryProducts(ids, BillingClient.ProductType.INAPP)
|
|
64
|
+
|
|
65
|
+
val result = WritableNativeArray()
|
|
66
|
+
|
|
67
|
+
for (details in subsProducts + inappProducts) {
|
|
68
|
+
val map = WritableNativeMap()
|
|
69
|
+
map.putString("productId", details.productId)
|
|
70
|
+
map.putString("title", details.title)
|
|
71
|
+
map.putString("description", details.description)
|
|
72
|
+
|
|
73
|
+
val subDetails = details.subscriptionOfferDetails
|
|
74
|
+
if (subDetails != null && subDetails.isNotEmpty()) {
|
|
75
|
+
val offer = subDetails[0]
|
|
76
|
+
val pricingPhases = offer.pricingPhases.pricingPhaseList
|
|
77
|
+
|
|
78
|
+
// Find the main pricing phase (not trial)
|
|
79
|
+
val mainPhase = if (pricingPhases.size > 1) {
|
|
80
|
+
pricingPhases.last()
|
|
81
|
+
} else {
|
|
82
|
+
pricingPhases.first()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
map.putString("price", (mainPhase.priceAmountMicros / 1_000_000.0).toString())
|
|
86
|
+
map.putDouble("priceValue", mainPhase.priceAmountMicros / 1_000_000.0)
|
|
87
|
+
map.putString("currency", mainPhase.priceCurrencyCode)
|
|
88
|
+
map.putString("localizedPrice", mainPhase.formattedPrice)
|
|
89
|
+
map.putString("type", "subscription")
|
|
90
|
+
map.putString("subscriptionPeriod", mainPhase.billingPeriod)
|
|
91
|
+
|
|
92
|
+
// Check for free trial
|
|
93
|
+
if (pricingPhases.size > 1) {
|
|
94
|
+
val trialPhase = pricingPhases.first()
|
|
95
|
+
if (trialPhase.priceAmountMicros == 0L) {
|
|
96
|
+
map.putString("freeTrialPeriod", trialPhase.billingPeriod)
|
|
97
|
+
} else {
|
|
98
|
+
map.putString("introductoryPrice", trialPhase.formattedPrice)
|
|
99
|
+
map.putDouble("introductoryPriceValue", trialPhase.priceAmountMicros / 1_000_000.0)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
val oneTime = details.oneTimePurchaseOfferDetails
|
|
104
|
+
if (oneTime != null) {
|
|
105
|
+
map.putString("price", (oneTime.priceAmountMicros / 1_000_000.0).toString())
|
|
106
|
+
map.putDouble("priceValue", oneTime.priceAmountMicros / 1_000_000.0)
|
|
107
|
+
map.putString("currency", oneTime.priceCurrencyCode)
|
|
108
|
+
map.putString("localizedPrice", oneTime.formattedPrice)
|
|
109
|
+
map.putString("type", "non_consumable")
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
result.pushMap(map)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
promise.resolve(result)
|
|
117
|
+
} catch (e: Exception) {
|
|
118
|
+
promise.reject("PRODUCTS_ERROR", "Failed to load products: ${e.message}", e)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private suspend fun queryProducts(ids: List<String>, type: String): List<ProductDetails> {
|
|
124
|
+
val client = billingClient ?: return emptyList()
|
|
125
|
+
|
|
126
|
+
val productList = ids.map { id ->
|
|
127
|
+
QueryProductDetailsParams.Product.newBuilder()
|
|
128
|
+
.setProductId(id)
|
|
129
|
+
.setProductType(type)
|
|
130
|
+
.build()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
val params = QueryProductDetailsParams.newBuilder()
|
|
134
|
+
.setProductList(productList)
|
|
135
|
+
.build()
|
|
136
|
+
|
|
137
|
+
return suspendCancellableCoroutine { cont ->
|
|
138
|
+
client.queryProductDetailsAsync(params) { result, detailsList ->
|
|
139
|
+
if (cont.isActive) {
|
|
140
|
+
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
141
|
+
cont.resume(detailsList ?: emptyList()) {}
|
|
142
|
+
} else {
|
|
143
|
+
cont.resume(emptyList()) {}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@ReactMethod
|
|
151
|
+
fun purchase(productId: String, promise: Promise) {
|
|
152
|
+
scope.launch {
|
|
153
|
+
try {
|
|
154
|
+
if (!connectBilling()) {
|
|
155
|
+
promise.reject("BILLING_ERROR", "Failed to connect to billing service")
|
|
156
|
+
return@launch
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
val client = billingClient!!
|
|
160
|
+
val activity = currentActivity
|
|
161
|
+
|
|
162
|
+
if (activity == null) {
|
|
163
|
+
promise.reject("NO_ACTIVITY", "No current activity")
|
|
164
|
+
return@launch
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Query product details
|
|
168
|
+
val subsProducts = queryProducts(listOf(productId), BillingClient.ProductType.SUBS)
|
|
169
|
+
val inappProducts = queryProducts(listOf(productId), BillingClient.ProductType.INAPP)
|
|
170
|
+
val details = (subsProducts + inappProducts).firstOrNull()
|
|
171
|
+
|
|
172
|
+
if (details == null) {
|
|
173
|
+
promise.reject("PRODUCT_NOT_FOUND", "Product $productId not found")
|
|
174
|
+
return@launch
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
purchasePromise = promise
|
|
178
|
+
|
|
179
|
+
val flowParamsBuilder = BillingFlowParams.newBuilder()
|
|
180
|
+
|
|
181
|
+
val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
|
|
182
|
+
.setProductDetails(details)
|
|
183
|
+
|
|
184
|
+
// For subscriptions, set the offer token
|
|
185
|
+
details.subscriptionOfferDetails?.firstOrNull()?.let { offer ->
|
|
186
|
+
productDetailsParams.setOfferToken(offer.offerToken)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
flowParamsBuilder.setProductDetailsParamsList(
|
|
190
|
+
listOf(productDetailsParams.build())
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
client.launchBillingFlow(activity, flowParamsBuilder.build())
|
|
194
|
+
} catch (e: Exception) {
|
|
195
|
+
purchasePromise = null
|
|
196
|
+
promise.reject("PURCHASE_ERROR", "Purchase failed: ${e.message}", e)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
|
|
202
|
+
val promise = purchasePromise ?: return
|
|
203
|
+
purchasePromise = null
|
|
204
|
+
|
|
205
|
+
when (result.responseCode) {
|
|
206
|
+
BillingClient.BillingResponseCode.OK -> {
|
|
207
|
+
val purchase = purchases?.firstOrNull()
|
|
208
|
+
if (purchase != null) {
|
|
209
|
+
val map = WritableNativeMap()
|
|
210
|
+
map.putString("transactionId", purchase.orderId ?: purchase.purchaseToken)
|
|
211
|
+
map.putString("productId", purchase.products.firstOrNull() ?: "")
|
|
212
|
+
map.putString("receipt", purchase.purchaseToken)
|
|
213
|
+
map.putDouble("transactionDate", purchase.purchaseTime.toDouble())
|
|
214
|
+
promise.resolve(map)
|
|
215
|
+
} else {
|
|
216
|
+
promise.resolve(null)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
BillingClient.BillingResponseCode.USER_CANCELED -> {
|
|
220
|
+
promise.resolve(null)
|
|
221
|
+
}
|
|
222
|
+
else -> {
|
|
223
|
+
promise.reject("PURCHASE_ERROR", "Purchase failed: ${result.debugMessage}")
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@ReactMethod
|
|
229
|
+
fun finishTransaction(transactionId: String, promise: Promise) {
|
|
230
|
+
scope.launch {
|
|
231
|
+
try {
|
|
232
|
+
if (!connectBilling()) {
|
|
233
|
+
promise.resolve(null)
|
|
234
|
+
return@launch
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
val client = billingClient!!
|
|
238
|
+
|
|
239
|
+
// Query purchases to find the one to acknowledge
|
|
240
|
+
val subsResult = client.queryPurchasesAsync(
|
|
241
|
+
QueryPurchasesParams.newBuilder()
|
|
242
|
+
.setProductType(BillingClient.ProductType.SUBS)
|
|
243
|
+
.build()
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
val allPurchases = subsResult.purchasesList
|
|
247
|
+
|
|
248
|
+
val purchase = allPurchases.firstOrNull {
|
|
249
|
+
it.orderId == transactionId || it.purchaseToken == transactionId
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (purchase != null && !purchase.isAcknowledged) {
|
|
253
|
+
val ackParams = AcknowledgePurchaseParams.newBuilder()
|
|
254
|
+
.setPurchaseToken(purchase.purchaseToken)
|
|
255
|
+
.build()
|
|
256
|
+
|
|
257
|
+
client.acknowledgePurchase(ackParams) { ackResult ->
|
|
258
|
+
if (ackResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
259
|
+
promise.resolve(null)
|
|
260
|
+
} else {
|
|
261
|
+
promise.reject("ACK_ERROR", "Failed to acknowledge: ${ackResult.debugMessage}")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
promise.resolve(null)
|
|
266
|
+
}
|
|
267
|
+
} catch (e: Exception) {
|
|
268
|
+
promise.resolve(null)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@ReactMethod
|
|
274
|
+
fun getActiveTransactions(promise: Promise) {
|
|
275
|
+
scope.launch {
|
|
276
|
+
try {
|
|
277
|
+
if (!connectBilling()) {
|
|
278
|
+
promise.resolve(WritableNativeArray())
|
|
279
|
+
return@launch
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
val client = billingClient!!
|
|
283
|
+
val result = WritableNativeArray()
|
|
284
|
+
|
|
285
|
+
val subsResult = client.queryPurchasesAsync(
|
|
286
|
+
QueryPurchasesParams.newBuilder()
|
|
287
|
+
.setProductType(BillingClient.ProductType.SUBS)
|
|
288
|
+
.build()
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
for (purchase in subsResult.purchasesList) {
|
|
292
|
+
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
|
|
293
|
+
val map = WritableNativeMap()
|
|
294
|
+
map.putString("transactionId", purchase.orderId ?: purchase.purchaseToken)
|
|
295
|
+
map.putString("productId", purchase.products.firstOrNull() ?: "")
|
|
296
|
+
map.putString("receipt", purchase.purchaseToken)
|
|
297
|
+
map.putDouble("transactionDate", purchase.purchaseTime.toDouble())
|
|
298
|
+
result.pushMap(map)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
promise.resolve(result)
|
|
303
|
+
} catch (e: Exception) {
|
|
304
|
+
promise.resolve(WritableNativeArray())
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
override fun onCatalystInstanceDestroy() {
|
|
310
|
+
scope.cancel()
|
|
311
|
+
billingClient?.endConnection()
|
|
312
|
+
billingClient = null
|
|
313
|
+
super.onCatalystInstanceDestroy()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.paywallo.sdk
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.webkit.JavascriptInterface
|
|
5
|
+
import android.webkit.WebChromeClient
|
|
6
|
+
import android.webkit.WebSettings
|
|
7
|
+
import android.webkit.WebView
|
|
8
|
+
import android.webkit.WebViewClient
|
|
9
|
+
import com.facebook.react.bridge.Arguments
|
|
10
|
+
import com.facebook.react.bridge.ReactContext
|
|
11
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
12
|
+
|
|
13
|
+
@SuppressLint("SetJavaScriptEnabled", "ViewConstructor")
|
|
14
|
+
class PaywalloWebView(context: ReactContext) : WebView(context) {
|
|
15
|
+
|
|
16
|
+
private val reactContext = context
|
|
17
|
+
|
|
18
|
+
init {
|
|
19
|
+
setupWebView()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private fun setupWebView() {
|
|
23
|
+
settings.apply {
|
|
24
|
+
javaScriptEnabled = true
|
|
25
|
+
domStorageEnabled = true
|
|
26
|
+
mediaPlaybackRequiresUserGesture = false
|
|
27
|
+
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
|
28
|
+
cacheMode = WebSettings.LOAD_DEFAULT
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
webViewClient = object : WebViewClient() {
|
|
32
|
+
override fun onPageFinished(view: WebView?, url: String?) {
|
|
33
|
+
super.onPageFinished(view, url)
|
|
34
|
+
dispatchEvent("onLoadEnd", null)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
webChromeClient = WebChromeClient()
|
|
39
|
+
|
|
40
|
+
addJavascriptInterface(WebAppInterface(), "ReactNativeWebView")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private var pendingHtml: String? = null
|
|
44
|
+
|
|
45
|
+
fun setSourceUrl(url: String?) {
|
|
46
|
+
if (pendingHtml.isNullOrEmpty()) {
|
|
47
|
+
url?.let { loadUrl(it) }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun setSourceHtml(html: String?) {
|
|
52
|
+
pendingHtml = html
|
|
53
|
+
html?.let { loadDataWithBaseURL("https://app.paywallo.io", it, "text/html", "UTF-8", null) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun injectJavaScript(script: String) {
|
|
57
|
+
evaluateJavascript(script, null)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun dispatchEvent(eventName: String, data: String?) {
|
|
61
|
+
val event = Arguments.createMap().apply {
|
|
62
|
+
data?.let { putString("data", it) }
|
|
63
|
+
}
|
|
64
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
65
|
+
.receiveEvent(id, eventName, event)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
inner class WebAppInterface {
|
|
69
|
+
@JavascriptInterface
|
|
70
|
+
fun postMessage(message: String) {
|
|
71
|
+
post { dispatchEvent("onMessage", message) }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.paywallo.sdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReadableArray
|
|
5
|
+
import com.facebook.react.common.MapBuilder
|
|
6
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
9
|
+
|
|
10
|
+
class PaywalloWebViewManager(private val reactContext: ReactApplicationContext) : SimpleViewManager<PaywalloWebView>() {
|
|
11
|
+
|
|
12
|
+
companion object {
|
|
13
|
+
const val REACT_CLASS = "PaywalloWebView"
|
|
14
|
+
const val COMMAND_INJECT_JS = 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun getName(): String = REACT_CLASS
|
|
18
|
+
|
|
19
|
+
override fun createViewInstance(context: ThemedReactContext): PaywalloWebView {
|
|
20
|
+
return PaywalloWebView(context)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@ReactProp(name = "sourceUrl")
|
|
24
|
+
fun setSourceUrl(view: PaywalloWebView, url: String?) {
|
|
25
|
+
view.setSourceUrl(url)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@ReactProp(name = "sourceHtml")
|
|
29
|
+
fun setSourceHtml(view: PaywalloWebView, html: String?) {
|
|
30
|
+
view.setSourceHtml(html)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
|
|
34
|
+
return MapBuilder.builder<String, Any>()
|
|
35
|
+
.put("onMessage", MapBuilder.of("registrationName", "onMessage"))
|
|
36
|
+
.put("onLoadEnd", MapBuilder.of("registrationName", "onLoadEnd"))
|
|
37
|
+
.build()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun getCommandsMap(): Map<String, Int>? {
|
|
41
|
+
return MapBuilder.of("injectJavaScript", COMMAND_INJECT_JS)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun receiveCommand(view: PaywalloWebView, commandId: String?, args: ReadableArray?) {
|
|
45
|
+
when (commandId) {
|
|
46
|
+
"injectJavaScript" -> {
|
|
47
|
+
val script = args?.getString(0) ?: return
|
|
48
|
+
view.injectJavaScript(script)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|