@virex-tech/paywallo-sdk 2.2.4 → 2.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 +5 -3
- package/android/src/main/java/com/paywallo/sdk/PaywalloFirebaseMessagingService.kt +33 -5
- package/android/src/main/java/com/paywallo/sdk/PaywalloNotificationsModule.kt +47 -30
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +7 -3
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewEvent.kt +4 -2
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +51 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
}
|
|
9
9
|
dependencies {
|
|
10
10
|
classpath("com.android.tools.build:gradle:${safeExtGet('androidGradlePluginVersion', '7.4.2')}")
|
|
11
|
-
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '
|
|
11
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '2.0.21')}")
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -48,12 +48,14 @@ repositories {
|
|
|
48
48
|
|
|
49
49
|
dependencies {
|
|
50
50
|
implementation "com.facebook.react:react-native:+"
|
|
51
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '
|
|
51
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '2.0.21')}"
|
|
52
52
|
implementation "com.android.billingclient:billing-ktx:7.0.0"
|
|
53
53
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
54
54
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
55
55
|
compileOnly "com.facebook.android:facebook-android-sdk:17.+"
|
|
56
56
|
compileOnly "com.google.firebase:firebase-messaging:24.0.0"
|
|
57
|
-
|
|
57
|
+
// 1.1.0+ required for MasterKey.Builder API used in PaywalloStorageModule.
|
|
58
|
+
// 1.0.0 only ships the deprecated MasterKeys.getOrCreate(...) static helper.
|
|
59
|
+
implementation "androidx.security:security-crypto:1.1.0"
|
|
58
60
|
implementation "com.android.installreferrer:installreferrer:2.2"
|
|
59
61
|
}
|
|
@@ -2,6 +2,7 @@ package com.paywallo.sdk
|
|
|
2
2
|
|
|
3
3
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
4
4
|
import com.google.firebase.messaging.RemoteMessage
|
|
5
|
+
import com.facebook.react.bridge.ReactContext
|
|
5
6
|
import com.facebook.react.bridge.WritableNativeMap
|
|
6
7
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
7
8
|
import com.facebook.react.ReactApplication
|
|
@@ -46,10 +47,7 @@ class PaywalloFirebaseMessagingService : FirebaseMessagingService() {
|
|
|
46
47
|
|
|
47
48
|
private fun emitEvent(eventName: String, data: WritableNativeMap) {
|
|
48
49
|
try {
|
|
49
|
-
val reactContext = (
|
|
50
|
-
?.reactNativeHost
|
|
51
|
-
?.reactInstanceManager
|
|
52
|
-
?.currentReactContext
|
|
50
|
+
val reactContext = resolveReactContext()
|
|
53
51
|
if (reactContext != null) {
|
|
54
52
|
reactContext
|
|
55
53
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
@@ -58,10 +56,40 @@ class PaywalloFirebaseMessagingService : FirebaseMessagingService() {
|
|
|
58
56
|
// Cold start: React context not ready — buffer for getInitialNotification()
|
|
59
57
|
pendingMessage = data
|
|
60
58
|
}
|
|
61
|
-
} catch (_:
|
|
59
|
+
} catch (_: Throwable) {
|
|
62
60
|
if (eventName == MESSAGE_RECEIVED_EVENT) {
|
|
63
61
|
pendingMessage = data
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the active ReactContext across both architectures:
|
|
68
|
+
* - New Architecture (bridgeless): ReactApplication.reactHost.currentReactContext
|
|
69
|
+
* - Legacy bridge: ReactApplication.reactNativeHost.reactInstanceManager.currentReactContext
|
|
70
|
+
*
|
|
71
|
+
* Note: in the New Architecture, accessing `reactNativeHost` throws
|
|
72
|
+
* RuntimeException("You should not use ReactNativeHost directly in the New Architecture"),
|
|
73
|
+
* so we MUST try `reactHost` first and only fall back to the legacy path when reactHost is null.
|
|
74
|
+
* Each path is guarded by try/catch so a throw on one doesn't break the other.
|
|
75
|
+
*/
|
|
76
|
+
private fun resolveReactContext(): ReactContext? {
|
|
77
|
+
val app = application as? ReactApplication ?: return null
|
|
78
|
+
|
|
79
|
+
// New Architecture path (RN 0.74+ bridgeless)
|
|
80
|
+
try {
|
|
81
|
+
val ctx = app.reactHost?.currentReactContext
|
|
82
|
+
if (ctx != null) return ctx
|
|
83
|
+
} catch (_: Throwable) {
|
|
84
|
+
// fall through to legacy
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Legacy bridge path (Old Architecture)
|
|
88
|
+
return try {
|
|
89
|
+
@Suppress("DEPRECATION")
|
|
90
|
+
app.reactNativeHost.reactInstanceManager.currentReactContext
|
|
91
|
+
} catch (_: Throwable) {
|
|
92
|
+
null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
67
95
|
}
|
|
@@ -3,10 +3,10 @@ package com.paywallo.sdk
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.content.pm.PackageManager
|
|
5
5
|
import android.os.Build
|
|
6
|
-
import androidx.core.app.ActivityCompat
|
|
7
6
|
import androidx.core.content.ContextCompat
|
|
8
7
|
import com.facebook.react.bridge.*
|
|
9
|
-
import com.facebook.react.modules.core.
|
|
8
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
9
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
10
10
|
|
|
11
11
|
class PaywalloNotificationsModule(private val reactContext: ReactApplicationContext) :
|
|
12
12
|
ReactContextBaseJavaModule(reactContext) {
|
|
@@ -14,26 +14,21 @@ class PaywalloNotificationsModule(private val reactContext: ReactApplicationCont
|
|
|
14
14
|
private var permissionPromise: Promise? = null
|
|
15
15
|
private val PERMISSION_REQUEST_CODE = 1001
|
|
16
16
|
|
|
17
|
-
private val
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (requestCode != PERMISSION_REQUEST_CODE) return
|
|
24
|
-
val promise = permissionPromise ?: return
|
|
25
|
-
permissionPromise = null
|
|
17
|
+
private val permissionListener = PermissionListener { requestCode, _, grantResults ->
|
|
18
|
+
if (requestCode != PERMISSION_REQUEST_CODE) {
|
|
19
|
+
return@PermissionListener false
|
|
20
|
+
}
|
|
21
|
+
val promise = permissionPromise
|
|
22
|
+
permissionPromise = null
|
|
26
23
|
|
|
24
|
+
if (promise != null) {
|
|
27
25
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
28
26
|
promise.resolve("granted")
|
|
29
27
|
} else {
|
|
30
28
|
promise.resolve("denied")
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
init {
|
|
36
|
-
reactContext.addActivityEventListener(activityEventListener)
|
|
31
|
+
true
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
override fun getName() = "PaywalloNotifications"
|
|
@@ -79,7 +74,10 @@ class PaywalloNotificationsModule(private val reactContext: ReactApplicationCont
|
|
|
79
74
|
fun requestPermission(options: ReadableMap?, promise: Promise) {
|
|
80
75
|
// Android 13+ (API 33) requires POST_NOTIFICATIONS runtime permission
|
|
81
76
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
82
|
-
|
|
77
|
+
// RN 0.80+ removed the synthetic `currentActivity` property on
|
|
78
|
+
// ReactContextBaseJavaModule (base class was rewritten in Kotlin).
|
|
79
|
+
// Reach it via the ReactContext, which is still Java-backed.
|
|
80
|
+
val activity = reactContext.currentActivity
|
|
83
81
|
if (activity == null) {
|
|
84
82
|
promise.resolve("denied")
|
|
85
83
|
return
|
|
@@ -90,23 +88,41 @@ class PaywalloNotificationsModule(private val reactContext: ReactApplicationCont
|
|
|
90
88
|
|
|
91
89
|
if (granted) {
|
|
92
90
|
promise.resolve("granted")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
|
101
|
-
PERMISSION_REQUEST_CODE
|
|
102
|
-
)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
val permissionAware = activity as? PermissionAwareActivity
|
|
95
|
+
if (permissionAware == null) {
|
|
96
|
+
promise.resolve("denied")
|
|
97
|
+
return
|
|
103
98
|
}
|
|
99
|
+
|
|
100
|
+
// If there's already a pending promise, reject it before storing the new one
|
|
101
|
+
permissionPromise?.reject("PERMISSION_SUPERSEDED", "New permission request superseded this one")
|
|
102
|
+
// Store promise and wait for permission listener callback
|
|
103
|
+
permissionPromise = promise
|
|
104
|
+
permissionAware.requestPermissions(
|
|
105
|
+
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
|
106
|
+
PERMISSION_REQUEST_CODE,
|
|
107
|
+
permissionListener
|
|
108
|
+
)
|
|
104
109
|
} else {
|
|
105
110
|
// Pre-Android 13: notifications always allowed
|
|
106
111
|
promise.resolve("granted")
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
// iOS calls UIApplication.registerForRemoteNotifications() here to obtain
|
|
116
|
+
// an APNS token. On Android there's no equivalent: FCM tokens are minted
|
|
117
|
+
// automatically by Firebase and surfaced via FirebaseMessagingService.onNewToken.
|
|
118
|
+
// Exposing this as a no-op keeps the cross-platform bridge contract and
|
|
119
|
+
// silences the "method not found" warning from NativeModules when the JS
|
|
120
|
+
// bridge calls it after permission is granted.
|
|
121
|
+
@ReactMethod
|
|
122
|
+
fun registerForPush(promise: Promise) {
|
|
123
|
+
promise.resolve(null)
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
@ReactMethod
|
|
111
127
|
fun hasPermission(promise: Promise) {
|
|
112
128
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
@@ -130,7 +146,7 @@ class PaywalloNotificationsModule(private val reactContext: ReactApplicationCont
|
|
|
130
146
|
}
|
|
131
147
|
|
|
132
148
|
// Check if the app was opened from a notification tap
|
|
133
|
-
val activity = currentActivity
|
|
149
|
+
val activity = reactContext.currentActivity
|
|
134
150
|
val intent = activity?.intent
|
|
135
151
|
val extras = intent?.extras
|
|
136
152
|
|
|
@@ -161,9 +177,10 @@ class PaywalloNotificationsModule(private val reactContext: ReactApplicationCont
|
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
179
|
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
// invalidate() is the post-TurboModule replacement for onCatalystInstanceDestroy.
|
|
181
|
+
// The latter is @Deprecated(forRemoval = true) since RN 0.74 and will be removed.
|
|
182
|
+
override fun invalidate() {
|
|
166
183
|
permissionPromise = null
|
|
167
|
-
super.
|
|
184
|
+
super.invalidate()
|
|
168
185
|
}
|
|
169
186
|
}
|
|
@@ -195,7 +195,9 @@ class PaywalloStoreKitModule(private val reactContext: ReactApplicationContext)
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
val client = billingClient!!
|
|
198
|
-
|
|
198
|
+
// RN 0.80+ removed the synthetic `currentActivity` property on
|
|
199
|
+
// ReactContextBaseJavaModule. Reach it via ReactContext instead.
|
|
200
|
+
val activity = reactContext.currentActivity
|
|
199
201
|
|
|
200
202
|
if (activity == null) {
|
|
201
203
|
promise.reject("NO_ACTIVITY", "No current activity")
|
|
@@ -427,13 +429,15 @@ class PaywalloStoreKitModule(private val reactContext: ReactApplicationContext)
|
|
|
427
429
|
}
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
|
|
432
|
+
// invalidate() is the post-TurboModule replacement for onCatalystInstanceDestroy.
|
|
433
|
+
// The latter is @Deprecated(forRemoval = true) since RN 0.74 and will be removed.
|
|
434
|
+
override fun invalidate() {
|
|
431
435
|
// Reject any pending purchase promise so the JS side doesn't hang forever
|
|
432
436
|
purchasePromise?.reject("MODULE_DESTROYED", "Module was destroyed before purchase completed")
|
|
433
437
|
purchasePromise = null
|
|
434
438
|
scope.cancel()
|
|
435
439
|
billingClient?.endConnection()
|
|
436
440
|
billingClient = null
|
|
437
|
-
super.
|
|
441
|
+
super.invalidate()
|
|
438
442
|
}
|
|
439
443
|
}
|
|
@@ -10,11 +10,13 @@ import com.facebook.react.uimanager.events.Event
|
|
|
10
10
|
class PaywalloWebViewEvent(
|
|
11
11
|
surfaceId: Int,
|
|
12
12
|
viewId: Int,
|
|
13
|
-
|
|
13
|
+
// RN 0.81 `Event.eventName` is now an open `val` on the base class.
|
|
14
|
+
// Using `eventName` as a constructor parameter would shadow it.
|
|
15
|
+
private val name: String,
|
|
14
16
|
private val payload: WritableMap
|
|
15
17
|
) : Event<PaywalloWebViewEvent>(surfaceId, viewId) {
|
|
16
18
|
|
|
17
|
-
override fun getEventName(): String =
|
|
19
|
+
override fun getEventName(): String = name
|
|
18
20
|
|
|
19
21
|
override fun getEventData(): WritableMap = payload
|
|
20
22
|
}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2024,13 +2024,21 @@ var init_NativePushBridge = __esm({
|
|
|
2024
2024
|
if (!raw || typeof raw !== "object") return null;
|
|
2025
2025
|
const r = raw;
|
|
2026
2026
|
const notification = r["notification"];
|
|
2027
|
+
const asString = (v) => typeof v === "string" ? v : void 0;
|
|
2027
2028
|
return {
|
|
2028
|
-
messageId:
|
|
2029
|
+
messageId: asString(r["messageId"]),
|
|
2029
2030
|
data: typeof r["data"] === "object" && r["data"] !== null ? r["data"] : void 0,
|
|
2030
2031
|
notification: notification && typeof notification === "object" ? {
|
|
2031
|
-
title:
|
|
2032
|
-
body:
|
|
2032
|
+
title: asString(notification["title"]),
|
|
2033
|
+
body: asString(notification["body"]),
|
|
2034
|
+
imageUrl: asString(notification["imageUrl"])
|
|
2033
2035
|
} : void 0,
|
|
2036
|
+
// Pass through top-level title/body/imageUrl emitted by the native modules
|
|
2037
|
+
// (both iOS and Android currently emit these at the top level rather than
|
|
2038
|
+
// nested under `notification`).
|
|
2039
|
+
title: asString(r["title"]),
|
|
2040
|
+
body: asString(r["body"]),
|
|
2041
|
+
imageUrl: asString(r["imageUrl"]),
|
|
2034
2042
|
sentTime: typeof r["sentTime"] === "number" ? r["sentTime"] : void 0
|
|
2035
2043
|
};
|
|
2036
2044
|
} catch {
|
|
@@ -2169,8 +2177,9 @@ function setupForegroundHandler(deps, onReceive) {
|
|
|
2169
2177
|
const data = message.data ?? {};
|
|
2170
2178
|
const payload = extractPayload({
|
|
2171
2179
|
...data,
|
|
2172
|
-
title: data["title"] ?? message.notification?.title ?? "",
|
|
2173
|
-
body: data["body"] ?? message.notification?.body ?? ""
|
|
2180
|
+
title: data["title"] ?? message.notification?.title ?? message.title ?? "",
|
|
2181
|
+
body: data["body"] ?? message.notification?.body ?? message.body ?? "",
|
|
2182
|
+
imageUrl: data["imageUrl"] ?? message.notification?.imageUrl ?? message.imageUrl
|
|
2174
2183
|
});
|
|
2175
2184
|
if (debug) console.log("[Paywallo PUSH] foreground message received:", payload.notificationId);
|
|
2176
2185
|
onReceive(payload);
|
|
@@ -2361,8 +2370,8 @@ async function getInitialNotificationPayload(bridge) {
|
|
|
2361
2370
|
notificationId: String(data["notificationId"] ?? data["notification_id"] ?? ""),
|
|
2362
2371
|
campaignId: String(data["campaignId"] ?? data["campaign_id"] ?? ""),
|
|
2363
2372
|
variantKey: data["variantKey"] != null ? String(data["variantKey"]) : void 0,
|
|
2364
|
-
title: String(data["title"] ?? message.notification?.title ?? ""),
|
|
2365
|
-
body: String(data["body"] ?? message.notification?.body ?? ""),
|
|
2373
|
+
title: String(data["title"] ?? message.notification?.title ?? message.title ?? ""),
|
|
2374
|
+
body: String(data["body"] ?? message.notification?.body ?? message.body ?? ""),
|
|
2366
2375
|
data
|
|
2367
2376
|
};
|
|
2368
2377
|
}
|
|
@@ -2377,8 +2386,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2377
2386
|
// src/core/version.ts
|
|
2378
2387
|
function resolveVersion() {
|
|
2379
2388
|
try {
|
|
2380
|
-
if ("2.2.
|
|
2381
|
-
return "2.2.
|
|
2389
|
+
if ("2.2.5") {
|
|
2390
|
+
return "2.2.5";
|
|
2382
2391
|
}
|
|
2383
2392
|
} catch {
|
|
2384
2393
|
}
|
|
@@ -4723,12 +4732,24 @@ var init_QueueProcessor = __esm({
|
|
|
4723
4732
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4724
4733
|
const headers = { ...persistedHeaders, ...freshHeaders };
|
|
4725
4734
|
this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4735
|
+
let response;
|
|
4736
|
+
try {
|
|
4737
|
+
response = await this.httpClient.request("/sdk/ingest/batch", {
|
|
4738
|
+
method: "POST",
|
|
4739
|
+
body: { events: bodies },
|
|
4740
|
+
headers,
|
|
4741
|
+
skipRetry: false
|
|
4742
|
+
});
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
for (const item of batch) {
|
|
4745
|
+
retryIds.add(item.id);
|
|
4746
|
+
}
|
|
4747
|
+
this.log("Event batch threw network error - will retry", {
|
|
4748
|
+
size: batch.length,
|
|
4749
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4750
|
+
});
|
|
4751
|
+
continue;
|
|
4752
|
+
}
|
|
4732
4753
|
if (response.ok) {
|
|
4733
4754
|
for (const item of batch) {
|
|
4734
4755
|
await offlineQueue.removeItem(item.id);
|
|
@@ -4768,12 +4789,21 @@ var init_QueueProcessor = __esm({
|
|
|
4768
4789
|
});
|
|
4769
4790
|
const persistedHeaders = item.headers ?? {};
|
|
4770
4791
|
const freshHeaders = this.config.getFreshHeaders?.() ?? {};
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4792
|
+
let response;
|
|
4793
|
+
try {
|
|
4794
|
+
response = await this.httpClient.request(item.url, {
|
|
4795
|
+
method: item.method,
|
|
4796
|
+
body: item.body,
|
|
4797
|
+
headers: { ...persistedHeaders, ...freshHeaders },
|
|
4798
|
+
skipRetry: false
|
|
4799
|
+
});
|
|
4800
|
+
} catch (error) {
|
|
4801
|
+
this.log("Request threw network error - will retry", {
|
|
4802
|
+
id: item.id,
|
|
4803
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4804
|
+
});
|
|
4805
|
+
return { success: false, permanent: false };
|
|
4806
|
+
}
|
|
4777
4807
|
if (response.ok) {
|
|
4778
4808
|
this.log("Queue item processed successfully", { id: item.id });
|
|
4779
4809
|
return { success: true, permanent: false };
|