@loyalytics/swan-react-native-sdk 2.5.1-beta.1 → 2.5.1-beta.3

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.
@@ -84,7 +84,8 @@ class CarouselTemplate : SwanNotificationTemplate {
84
84
  notificationId: Int,
85
85
  messageId: String,
86
86
  itemIndex: Int,
87
- itemRoute: String
87
+ itemRoute: String,
88
+ isContentIntent: Boolean = false
88
89
  ): PendingIntent {
89
90
  val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
90
91
  ?: Intent(Intent.ACTION_MAIN).apply {
@@ -107,8 +108,15 @@ class CarouselTemplate : SwanNotificationTemplate {
107
108
  PendingIntent.FLAG_UPDATE_CURRENT
108
109
  }
109
110
 
111
+ // Use different request codes for image click vs content intent to prevent collision
112
+ val requestCode = if (isContentIntent) {
113
+ "${notificationId}CONTENT_CLICK".hashCode()
114
+ } else {
115
+ "$notificationId${ACTION_CLICK}$itemIndex".hashCode()
116
+ }
117
+
110
118
  return PendingIntent.getActivity(
111
- context, "$notificationId${ACTION_CLICK}$itemIndex".hashCode(), launchIntent, flags
119
+ context, requestCode, launchIntent, flags
112
120
  )
113
121
  }
114
122
  }
@@ -447,13 +455,16 @@ class CarouselTemplate : SwanNotificationTemplate {
447
455
  extras: Bundle
448
456
  ): PendingIntent {
449
457
  // For CLICK, use PendingIntent.getActivity() to avoid Android 12+ trampoline restriction
458
+ // This is always the content intent (notification body tap) — image clicks go through
459
+ // createClickActivityPendingIntent() directly from RemoteViews builders
450
460
  if (action == ACTION_CLICK) {
451
461
  return createClickActivityPendingIntent(
452
462
  context,
453
463
  notificationId,
454
464
  extras.getString(EXTRA_MESSAGE_ID, ""),
455
465
  extras.getInt(EXTRA_ITEM_INDEX, 0),
456
- extras.getString(EXTRA_ITEM_ROUTE, "")
466
+ extras.getString(EXTRA_ITEM_ROUTE, ""),
467
+ isContentIntent = true
457
468
  )
458
469
  }
459
470
 
@@ -160,6 +160,7 @@ function _resetClickDedup() {
160
160
 
161
161
  class SwanSDK {
162
162
  listeners = {};
163
+ coldStartCheckDone = false;
163
164
  SDK_VERSION = _version.SDK_VERSION;
164
165
  isProduction = 'STAGE';
165
166
  appId = '';
@@ -628,6 +629,16 @@ class SwanSDK {
628
629
  }
629
630
  this.listeners[event].push(callback);
630
631
 
632
+ // On first NOTIFICATION_OPENED listener, check for pending cold-start notifications.
633
+ // The native side holds click data until consumed — we just need to ask when ready.
634
+ if (event === SwanSDK.EVENTS.NOTIFICATION_OPENED && !this.coldStartCheckDone) {
635
+ this.coldStartCheckDone = true;
636
+ // Defer to next microtask so the listener is fully registered before events emit
637
+ Promise.resolve().then(() => {
638
+ this.checkPendingCarouselClick();
639
+ });
640
+ }
641
+
631
642
  // Return a subscription object for easy cleanup
632
643
  return {
633
644
  remove: () => {
@@ -958,9 +969,6 @@ class SwanSDK {
958
969
  _Logger.default.log('[SwanSDK] Tracking initial app launch event...');
959
970
  this.appLaunched();
960
971
 
961
- // Check for pending carousel click (covers killed/quit → fresh launch)
962
- this.checkPendingCarouselClick();
963
-
964
972
  // AppState listener setup (always runs)
965
973
  _Logger.default.log('[SwanSDK] Setting up AppState listener...');
966
974
  this.setupAppStateListener();
@@ -1826,11 +1834,12 @@ class SwanSDK {
1826
1834
  // Read click data from App Group (written by Content Extension)
1827
1835
  clickData = await _SharedCredentialsManager.SharedCredentialsManager.readTemplateClickData();
1828
1836
 
1829
- // If no data yet, retry after a short delay.
1830
- // The Content Extension may still be writing when the app foregrounds.
1837
+ // Exponential backoff: Content Extension may still be writing to App Group
1831
1838
  if (!clickData) {
1832
- await new Promise(r => setTimeout(r, 500));
1833
- clickData = await _SharedCredentialsManager.SharedCredentialsManager.readTemplateClickData();
1839
+ for (let attempt = 0; attempt < 4 && !clickData; attempt++) {
1840
+ await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt)));
1841
+ clickData = await _SharedCredentialsManager.SharedCredentialsManager.readTemplateClickData();
1842
+ }
1834
1843
  }
1835
1844
  }
1836
1845
  if (!clickData) return;