@virex-tech/paywallo-sdk 2.2.3 → 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.
@@ -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', '1.9.22')}")
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', '1.9.22')}"
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
- implementation "androidx.security:security-crypto:1.0.0"
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
  }
@@ -1,6 +1,13 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
2
  package="com.paywallo.sdk">
3
3
 
4
+ <!--
5
+ Google Play Billing — required by PaywalloStoreKitModule (BillingClient).
6
+ Merged into the host app manifest automatically. Without this permission,
7
+ BillingClient.startConnection() fails silently at runtime.
8
+ -->
9
+ <uses-permission android:name="com.android.vending.BILLING" />
10
+
4
11
  <application>
5
12
  <!--
6
13
  PaywalloFirebaseMessagingService handles FCM token refresh and message delivery.
@@ -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 = (application as? ReactApplication)
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 (_: Exception) {
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.DeviceEventManagerModule
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 activityEventListener = object : BaseActivityEventListener() {
18
- override fun onRequestPermissionsResult(
19
- requestCode: Int,
20
- permissions: Array<out String>,
21
- grantResults: IntArray
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
- val activity = currentActivity
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
- } else {
94
- // If there's already a pending promise, reject it before storing the new one
95
- permissionPromise?.reject("PERMISSION_SUPERSEDED", "New permission request superseded this one")
96
- // Store promise and wait for onRequestPermissionsResult callback
97
- permissionPromise = promise
98
- ActivityCompat.requestPermissions(
99
- activity,
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
- override fun onCatalystInstanceDestroy() {
165
- reactContext.removeActivityEventListener(activityEventListener)
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.onCatalystInstanceDestroy()
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
- val activity = currentActivity
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
- override fun onCatalystInstanceDestroy() {
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.onCatalystInstanceDestroy()
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
- private val eventName: String,
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 = eventName
19
+ override fun getEventName(): String = name
18
20
 
19
21
  override fun getEventData(): WritableMap = payload
20
22
  }
package/dist/index.d.mts CHANGED
@@ -378,7 +378,7 @@ declare const PaywalloContext: React.Context<PaywalloContextValue | null>;
378
378
  * event in the V2 ingest envelope. The provider is called at flush time so
379
379
  * each field reflects the most recent SDK state.
380
380
  *
381
- * The shape mirrors the server-side `/api/v2/ingest/batch` contract (Phase
381
+ * The shape mirrors the server-side `/sdk/ingest/batch` contract (Phase
382
382
  * 3.1). Fields declared here are deduplicated across the batch — they exist
383
383
  * once on the envelope instead of `N` times on each `events[]` entry.
384
384
  */
@@ -410,11 +410,9 @@ interface EventContext {
410
410
  * `lifecycle` (foreground/background/session), `onboarding`,
411
411
  * `notification`, `custom`.
412
412
  *
413
- * Both priorities post to `/sdk/ingest/batch` (Phase 3.1 envelope) with
414
- * automatic fallback to `/sdk/events/batch` when the V2 endpoint is
415
- * absent (old server) or rejects the request. `OfflineQueue` handles
416
- * network-failure durability for both queues via the `PostFn` injected by
417
- * `ApiClient.postWithQueue`.
413
+ * Both priorities post to `/sdk/ingest/batch`. On 4xx the event is dropped.
414
+ * On 5xx or network failure, `OfflineQueue` handles durability via the
415
+ * `PostFn` injected by `ApiClient.postWithQueue`.
418
416
  */
419
417
  type EventPriority = "critical" | "normal";
420
418
  interface TrackOptions {
@@ -588,6 +586,68 @@ declare const networkMonitor: NetworkMonitorClass;
588
586
  */
589
587
  type DistinctIdProvider = () => string;
590
588
 
589
+ /**
590
+ * Attribution data captured at first touch (UTM params, click IDs, referrer).
591
+ * `capturedAt` is ISO 8601 UTC — generated automatically by `capture()`.
592
+ */
593
+ interface AttributionCapture {
594
+ readonly utmSource?: string;
595
+ readonly utmMedium?: string;
596
+ readonly utmCampaign?: string;
597
+ readonly utmContent?: string;
598
+ readonly utmTerm?: string;
599
+ readonly fbclid?: string;
600
+ readonly gclid?: string;
601
+ readonly ttclid?: string;
602
+ readonly tiktokCampaignId?: string;
603
+ readonly tiktokAdgroupId?: string;
604
+ readonly tiktokAdId?: string;
605
+ readonly installReferrerRaw?: string;
606
+ readonly installReferrerSource?: string;
607
+ readonly referrer?: string;
608
+ readonly capturedAt: string;
609
+ }
610
+ type AttributionInput = Omit<AttributionCapture, "capturedAt">;
611
+ /**
612
+ * Attribution module — first-write-wins persistence of UTM / click-ID data.
613
+ *
614
+ * Once a capture is stored, subsequent `capture()` calls are silently ignored.
615
+ * Use `clear()` to reset (e.g. on logout or in tests).
616
+ *
617
+ * @example
618
+ * await attributionTracker.capture({ utmSource: "google", gclid: "Cj0KCQ..." });
619
+ * const attr = attributionTracker.get(); // { utmSource: "google", capturedAt: "..." }
620
+ */
621
+ declare class AttributionTracker {
622
+ private cache;
623
+ private captureInProgress;
624
+ private readonly storage;
625
+ constructor(debug?: boolean);
626
+ /**
627
+ * Load persisted attribution from storage into in-memory cache.
628
+ * Called once on SDK init. Subsequent `get()` reads are synchronous.
629
+ */
630
+ loadFromStorage(): Promise<void>;
631
+ /**
632
+ * Persist attribution data. First-write wins — ignores if already captured
633
+ * or if all fields are absent (empty capture).
634
+ *
635
+ * @param data - Attribution fields without `capturedAt` (generated internally).
636
+ */
637
+ capture(data: AttributionInput): Promise<void>;
638
+ /**
639
+ * Returns the persisted attribution capture, or `null` if none exists.
640
+ * Synchronous — reads from in-memory cache (populated via `loadFromStorage()`).
641
+ */
642
+ get(): AttributionCapture | null;
643
+ /**
644
+ * Clears the persisted attribution from storage and in-memory cache.
645
+ * After `clear()`, `get()` returns `null` and the next `capture()` writes normally.
646
+ */
647
+ clear(): Promise<void>;
648
+ }
649
+ declare const attributionTracker: AttributionTracker;
650
+
591
651
  declare class IdentityError extends PaywalloError {
592
652
  constructor(code: string, message: string);
593
653
  }
@@ -661,7 +721,11 @@ interface FcmRemoteMessage {
661
721
  notification?: {
662
722
  title?: string;
663
723
  body?: string;
724
+ imageUrl?: string;
664
725
  };
726
+ title?: string;
727
+ body?: string;
728
+ imageUrl?: string;
665
729
  sentTime?: number;
666
730
  }
667
731
  /**
@@ -734,7 +798,7 @@ type PushPermissionStatus = "granted" | "denied" | "provisional" | "notDetermine
734
798
  */
735
799
  type NotificationEventType = "notification_sent" | "notification_delivered" | "notification_displayed" | "notification_clicked" | "notification_dismissed" | "notification_failed" | "notification_converted";
736
800
  /**
737
- * Payload sent to `POST /api/v2/push-tokens`. Server schema matches 1:1:
801
+ * Payload sent to `POST /sdk/push-tokens`. Server schema matches 1:1:
738
802
  * `{ token, platform, distinct_id, app_version, sdk_version }`.
739
803
  *
740
804
  * Optional client-side extras (deviceId/locale/timezone) are kept for context
@@ -753,6 +817,7 @@ interface DeviceTokenRegistration {
753
817
  }
754
818
  interface NotificationPayload {
755
819
  notificationId: string;
820
+ messageId?: string;
756
821
  campaignId: string;
757
822
  variantKey?: string;
758
823
  title: string;
@@ -1440,7 +1505,7 @@ declare const ONBOARDING_ERROR_CODES: {
1440
1505
  /**
1441
1506
  * Minimal surface the OnboardingManager needs from the unified event pipeline.
1442
1507
  * `PaywalloClient` satisfies this interface (forwarding to
1443
- * `ApiClient.trackEvent` → `EventBatcher` → `/api/v1/events/batch`).
1508
+ * `ApiClient.trackEvent` → `EventBatcher` → `/sdk/ingest/batch`).
1444
1509
  */
1445
1510
  interface OnboardingEventPipeline {
1446
1511
  trackEvent(eventName: string, distinctId: string, properties?: Record<string, unknown>, timestamp?: number): Promise<void>;
@@ -1785,4 +1850,4 @@ declare function hasVariables(text: string): boolean;
1785
1850
 
1786
1851
  declare const Paywallo: IPaywalloClient;
1787
1852
 
1788
- export { type AllPlansResponse, ApiClient, type ApiClientConfig, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedOfferings, type CachedPlans, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, type DeviceTokenRegistration, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IPushBridge, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, NOTIFICATIONS_ERROR_CODES, NativePushBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type NotificationEventType, type NotificationListener, type NotificationPayload, NotificationsError, NotificationsManager, ONBOARDING_ERROR_CODES, type Offering, type OfferingProduct, OfferingService, type OfferingServiceConfig, type OfflineQueueConfig, type OnboardingDropPayload, OnboardingError, OnboardingManager, type OnboardingStepPayload, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type Plan, PlanCache, type PlanProduct, PlanService, type PlanServiceConfig, type PlansResponse, type PrePromptHandle, type PrePromptOptions, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type PushPermissionStatus, type PushPlatform, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult$1 as RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, notificationsManager, offeringService, offlineQueue, onboardingManager, parseVariables, planCache, planService, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, useOfferingPurchase, useOfferings, useOnboarding, usePaywallContext, usePaywallo, usePlanPurchase, usePlans, useProducts, usePurchase, useSubscription };
1853
+ export { type AllPlansResponse, ApiClient, type ApiClientConfig, type AttributionCapture, AttributionTracker, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedOfferings, type CachedPlans, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, type DeviceTokenRegistration, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IPushBridge, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, NOTIFICATIONS_ERROR_CODES, NativePushBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type NotificationEventType, type NotificationListener, type NotificationPayload, NotificationsError, NotificationsManager, ONBOARDING_ERROR_CODES, type Offering, type OfferingProduct, OfferingService, type OfferingServiceConfig, type OfflineQueueConfig, type OnboardingDropPayload, OnboardingError, OnboardingManager, type OnboardingStepPayload, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type Plan, PlanCache, type PlanProduct, PlanService, type PlanServiceConfig, type PlansResponse, type PrePromptHandle, type PrePromptOptions, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type PushPermissionStatus, type PushPlatform, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult$1 as RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, attributionTracker, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, notificationsManager, offeringService, offlineQueue, onboardingManager, parseVariables, planCache, planService, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, useOfferingPurchase, useOfferings, useOnboarding, usePaywallContext, usePaywallo, usePlanPurchase, usePlans, useProducts, usePurchase, useSubscription };
package/dist/index.d.ts CHANGED
@@ -378,7 +378,7 @@ declare const PaywalloContext: React.Context<PaywalloContextValue | null>;
378
378
  * event in the V2 ingest envelope. The provider is called at flush time so
379
379
  * each field reflects the most recent SDK state.
380
380
  *
381
- * The shape mirrors the server-side `/api/v2/ingest/batch` contract (Phase
381
+ * The shape mirrors the server-side `/sdk/ingest/batch` contract (Phase
382
382
  * 3.1). Fields declared here are deduplicated across the batch — they exist
383
383
  * once on the envelope instead of `N` times on each `events[]` entry.
384
384
  */
@@ -410,11 +410,9 @@ interface EventContext {
410
410
  * `lifecycle` (foreground/background/session), `onboarding`,
411
411
  * `notification`, `custom`.
412
412
  *
413
- * Both priorities post to `/sdk/ingest/batch` (Phase 3.1 envelope) with
414
- * automatic fallback to `/sdk/events/batch` when the V2 endpoint is
415
- * absent (old server) or rejects the request. `OfflineQueue` handles
416
- * network-failure durability for both queues via the `PostFn` injected by
417
- * `ApiClient.postWithQueue`.
413
+ * Both priorities post to `/sdk/ingest/batch`. On 4xx the event is dropped.
414
+ * On 5xx or network failure, `OfflineQueue` handles durability via the
415
+ * `PostFn` injected by `ApiClient.postWithQueue`.
418
416
  */
419
417
  type EventPriority = "critical" | "normal";
420
418
  interface TrackOptions {
@@ -588,6 +586,68 @@ declare const networkMonitor: NetworkMonitorClass;
588
586
  */
589
587
  type DistinctIdProvider = () => string;
590
588
 
589
+ /**
590
+ * Attribution data captured at first touch (UTM params, click IDs, referrer).
591
+ * `capturedAt` is ISO 8601 UTC — generated automatically by `capture()`.
592
+ */
593
+ interface AttributionCapture {
594
+ readonly utmSource?: string;
595
+ readonly utmMedium?: string;
596
+ readonly utmCampaign?: string;
597
+ readonly utmContent?: string;
598
+ readonly utmTerm?: string;
599
+ readonly fbclid?: string;
600
+ readonly gclid?: string;
601
+ readonly ttclid?: string;
602
+ readonly tiktokCampaignId?: string;
603
+ readonly tiktokAdgroupId?: string;
604
+ readonly tiktokAdId?: string;
605
+ readonly installReferrerRaw?: string;
606
+ readonly installReferrerSource?: string;
607
+ readonly referrer?: string;
608
+ readonly capturedAt: string;
609
+ }
610
+ type AttributionInput = Omit<AttributionCapture, "capturedAt">;
611
+ /**
612
+ * Attribution module — first-write-wins persistence of UTM / click-ID data.
613
+ *
614
+ * Once a capture is stored, subsequent `capture()` calls are silently ignored.
615
+ * Use `clear()` to reset (e.g. on logout or in tests).
616
+ *
617
+ * @example
618
+ * await attributionTracker.capture({ utmSource: "google", gclid: "Cj0KCQ..." });
619
+ * const attr = attributionTracker.get(); // { utmSource: "google", capturedAt: "..." }
620
+ */
621
+ declare class AttributionTracker {
622
+ private cache;
623
+ private captureInProgress;
624
+ private readonly storage;
625
+ constructor(debug?: boolean);
626
+ /**
627
+ * Load persisted attribution from storage into in-memory cache.
628
+ * Called once on SDK init. Subsequent `get()` reads are synchronous.
629
+ */
630
+ loadFromStorage(): Promise<void>;
631
+ /**
632
+ * Persist attribution data. First-write wins — ignores if already captured
633
+ * or if all fields are absent (empty capture).
634
+ *
635
+ * @param data - Attribution fields without `capturedAt` (generated internally).
636
+ */
637
+ capture(data: AttributionInput): Promise<void>;
638
+ /**
639
+ * Returns the persisted attribution capture, or `null` if none exists.
640
+ * Synchronous — reads from in-memory cache (populated via `loadFromStorage()`).
641
+ */
642
+ get(): AttributionCapture | null;
643
+ /**
644
+ * Clears the persisted attribution from storage and in-memory cache.
645
+ * After `clear()`, `get()` returns `null` and the next `capture()` writes normally.
646
+ */
647
+ clear(): Promise<void>;
648
+ }
649
+ declare const attributionTracker: AttributionTracker;
650
+
591
651
  declare class IdentityError extends PaywalloError {
592
652
  constructor(code: string, message: string);
593
653
  }
@@ -661,7 +721,11 @@ interface FcmRemoteMessage {
661
721
  notification?: {
662
722
  title?: string;
663
723
  body?: string;
724
+ imageUrl?: string;
664
725
  };
726
+ title?: string;
727
+ body?: string;
728
+ imageUrl?: string;
665
729
  sentTime?: number;
666
730
  }
667
731
  /**
@@ -734,7 +798,7 @@ type PushPermissionStatus = "granted" | "denied" | "provisional" | "notDetermine
734
798
  */
735
799
  type NotificationEventType = "notification_sent" | "notification_delivered" | "notification_displayed" | "notification_clicked" | "notification_dismissed" | "notification_failed" | "notification_converted";
736
800
  /**
737
- * Payload sent to `POST /api/v2/push-tokens`. Server schema matches 1:1:
801
+ * Payload sent to `POST /sdk/push-tokens`. Server schema matches 1:1:
738
802
  * `{ token, platform, distinct_id, app_version, sdk_version }`.
739
803
  *
740
804
  * Optional client-side extras (deviceId/locale/timezone) are kept for context
@@ -753,6 +817,7 @@ interface DeviceTokenRegistration {
753
817
  }
754
818
  interface NotificationPayload {
755
819
  notificationId: string;
820
+ messageId?: string;
756
821
  campaignId: string;
757
822
  variantKey?: string;
758
823
  title: string;
@@ -1440,7 +1505,7 @@ declare const ONBOARDING_ERROR_CODES: {
1440
1505
  /**
1441
1506
  * Minimal surface the OnboardingManager needs from the unified event pipeline.
1442
1507
  * `PaywalloClient` satisfies this interface (forwarding to
1443
- * `ApiClient.trackEvent` → `EventBatcher` → `/api/v1/events/batch`).
1508
+ * `ApiClient.trackEvent` → `EventBatcher` → `/sdk/ingest/batch`).
1444
1509
  */
1445
1510
  interface OnboardingEventPipeline {
1446
1511
  trackEvent(eventName: string, distinctId: string, properties?: Record<string, unknown>, timestamp?: number): Promise<void>;
@@ -1785,4 +1850,4 @@ declare function hasVariables(text: string): boolean;
1785
1850
 
1786
1851
  declare const Paywallo: IPaywalloClient;
1787
1852
 
1788
- export { type AllPlansResponse, ApiClient, type ApiClientConfig, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedOfferings, type CachedPlans, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, type DeviceTokenRegistration, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IPushBridge, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, NOTIFICATIONS_ERROR_CODES, NativePushBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type NotificationEventType, type NotificationListener, type NotificationPayload, NotificationsError, NotificationsManager, ONBOARDING_ERROR_CODES, type Offering, type OfferingProduct, OfferingService, type OfferingServiceConfig, type OfflineQueueConfig, type OnboardingDropPayload, OnboardingError, OnboardingManager, type OnboardingStepPayload, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type Plan, PlanCache, type PlanProduct, PlanService, type PlanServiceConfig, type PlansResponse, type PrePromptHandle, type PrePromptOptions, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type PushPermissionStatus, type PushPlatform, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult$1 as RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, notificationsManager, offeringService, offlineQueue, onboardingManager, parseVariables, planCache, planService, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, useOfferingPurchase, useOfferings, useOnboarding, usePaywallContext, usePaywallo, usePlanPurchase, usePlans, useProducts, usePurchase, useSubscription };
1853
+ export { type AllPlansResponse, ApiClient, type ApiClientConfig, type AttributionCapture, AttributionTracker, type BlockType, type BoxSpacing, CAMPAIGN_ERROR_CODES, type CachedOfferings, type CachedPlans, type CachedSubscription, CampaignError, type CampaignPresentOptions, type CampaignResponse, type CampaignResult, type ConditionalFlagContext, type ConditionalFlagResult, type DeviceTokenRegistration, PURCHASE_ERROR_CODES as ERROR_CODES, type EmergencyPaywallResponse, type Environment, type FlagVariant, HttpClient, type HttpClientConfig, type HttpResponse, type IAPProduct, type IAPPurchase, IAPService, IDENTITY_ERROR_CODES, type IPaywalloClient, type IPushBridge, type IdentifyOptions, IdentityError, IdentityManager, type IdentityState, type LocalizedText, MetaBridge, NOTIFICATIONS_ERROR_CODES, NativePushBridge, type NetworkListener, type NetworkMonitorConfig, type NetworkState, type NotificationEventType, type NotificationListener, type NotificationPayload, NotificationsError, NotificationsManager, ONBOARDING_ERROR_CODES, type Offering, type OfferingProduct, OfferingService, type OfferingServiceConfig, type OfflineQueueConfig, type OnboardingDropPayload, OnboardingError, OnboardingManager, type OnboardingStepPayload, PAYWALL_ERROR_CODES, PURCHASE_ERROR_CODES, type PaywallBlockConfig, type PaywallConfig, PaywallContext, PaywallError, type PaywallErrorStrings$1 as PaywallErrorStrings, PaywallModal, type PaywallNode, type PaywallPresentOptions, type PaywallResult, type PaywallState, Paywallo, PaywalloClient, type PaywalloConfig, PaywalloContext, PaywalloError, type PaywalloInitConfig, PaywalloProvider, type PaywalloProviderProps, type Plan, PlanCache, type PlanProduct, PlanService, type PlanServiceConfig, type PlansResponse, type PrePromptHandle, type PrePromptOptions, type PreloadCampaignResult, type PreloadResult, type Product, type ProductPriceInfo, type Purchase, type PurchaseControllerConfig, PurchaseError, type PurchaseResult, type PurchaseState, type PushPermissionStatus, type PushPlatform, type QueueEvent, type QueueItem, type QueueListener, type QueueProcessorConfig, type RequestOptions, type RestoreResult$1 as RestoreResult, type RetryConfig, SESSION_ERROR_CODES, type SessionConfig, SessionError, SessionManager, type SessionState, type Subscription, SubscriptionCache, type SubscriptionInfo, type SubscriptionManagerConfig, type SubscriptionPlatform, type SubscriptionStatus$1 as SubscriptionStatus, type SubscriptionStatusResponse$1 as SubscriptionStatusResponse, type TapBehavior, type TrackEventOptions, type UserProperties, type ValidatePurchaseResponse, type VariableContext, attributionTracker, createPurchaseError, Paywallo as default, detectDeviceLanguage, getCurrentLanguage, getDefaultLanguage, getIAPService, getLocalizedString, hasVariables, identityManager, initLocalization, metaBridge, nativeStoreKit, networkMonitor, notificationsManager, offeringService, offlineQueue, onboardingManager, parseVariables, planCache, planService, queueProcessor, resolveText, sessionManager, setCurrentLanguage, setDefaultLanguage, subscriptionManager, useOfferingPurchase, useOfferings, useOnboarding, usePaywallContext, usePaywallo, usePlanPurchase, usePlans, useProducts, usePurchase, useSubscription };