@loyalytics/swan-react-native-sdk 2.7.0 → 2.7.1-beta.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/ios/SwanNotificationContentExtension/Info.plist +2 -0
- package/ios/SwanNotificationContentExtension/MainInterface.storyboard +1 -2
- package/ios/SwanNotificationContentExtension/NotificationViewController.swift +5 -4
- package/ios/SwanNotificationContentExtension/templates/CarouselView.swift +6 -11
- package/lib/commonjs/index.js +57 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/SharedCredentialsManager.js +26 -0
- package/lib/commonjs/utils/SharedCredentialsManager.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/index.js +57 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/SharedCredentialsManager.js +26 -0
- package/lib/module/utils/SharedCredentialsManager.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/utils/SharedCredentialsManager.d.ts +13 -0
- package/lib/typescript/commonjs/src/utils/SharedCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/version.d.ts +1 -1
- package/lib/typescript/commonjs/src/version.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/utils/SharedCredentialsManager.d.ts +13 -0
- package/lib/typescript/module/src/utils/SharedCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/module/src/version.d.ts +1 -1
- package/lib/typescript/module/src/version.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
<real>0.7</real>
|
|
31
31
|
<key>UNNotificationExtensionDefaultContentHidden</key>
|
|
32
32
|
<false/>
|
|
33
|
+
<key>UNNotificationExtensionUserInteractionEnabled</key>
|
|
34
|
+
<true/>
|
|
33
35
|
</dict>
|
|
34
36
|
<key>NSExtensionPointIdentifier</key>
|
|
35
37
|
<string>com.apple.usernotifications.content-extension</string>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="
|
|
3
|
-
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
|
2
|
+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17139" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="jFH-bh-RYg">
|
|
4
3
|
<scenes>
|
|
5
4
|
<scene sceneID="8ME-jv-LvP">
|
|
6
5
|
<objects>
|
|
@@ -131,13 +131,14 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
|
|
|
131
131
|
|
|
132
132
|
self.currentUserInfo = userInfo
|
|
133
133
|
|
|
134
|
-
// Tap on carousel image
|
|
134
|
+
// Tap on carousel image opens the app via deep link URL (CleverTap pattern)
|
|
135
135
|
// Falls back to App Group + performNotificationDefaultAction if openURL fails
|
|
136
|
-
carousel.onItemTapped = { [weak self] in
|
|
136
|
+
carousel.onItemTapped = { [weak self] tappedIndex in
|
|
137
137
|
guard let self = self, let cv = self.carouselView else { return }
|
|
138
|
+
let tappedItem = tappedIndex < items.count ? items[tappedIndex] : nil
|
|
138
139
|
self.openDeepLink(
|
|
139
|
-
itemIndex:
|
|
140
|
-
itemRoute:
|
|
140
|
+
itemIndex: tappedIndex,
|
|
141
|
+
itemRoute: tappedItem?.route,
|
|
141
142
|
userInfo: userInfo
|
|
142
143
|
) { opened in
|
|
143
144
|
if !opened {
|
|
@@ -79,8 +79,8 @@ class CarouselView: UIView {
|
|
|
79
79
|
return label
|
|
80
80
|
}()
|
|
81
81
|
|
|
82
|
-
/// Called when user taps a carousel
|
|
83
|
-
var onItemTapped: (() -> Void)?
|
|
82
|
+
/// Called when user taps a carousel image, passes the tapped item index
|
|
83
|
+
var onItemTapped: ((Int) -> Void)?
|
|
84
84
|
|
|
85
85
|
// Image cache
|
|
86
86
|
private var imageCache: [Int: UIImage] = [:]
|
|
@@ -109,10 +109,6 @@ class CarouselView: UIView {
|
|
|
109
109
|
addSubview($0)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Tap anywhere on carousel opens the app
|
|
113
|
-
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
|
114
|
-
self.addGestureRecognizer(tap)
|
|
115
|
-
|
|
116
112
|
NSLayoutConstraint.activate([
|
|
117
113
|
collectionView.topAnchor.constraint(equalTo: topAnchor),
|
|
118
114
|
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
@@ -203,10 +199,6 @@ class CarouselView: UIView {
|
|
|
203
199
|
autoScrollTimer = nil
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
@objc private func handleTap() {
|
|
207
|
-
onItemTapped?()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
202
|
// MARK: - Helpers
|
|
211
203
|
|
|
212
204
|
private func updateLabels() {
|
|
@@ -280,7 +272,10 @@ extension CarouselView: UICollectionViewDataSource, UICollectionViewDelegate, UI
|
|
|
280
272
|
}
|
|
281
273
|
|
|
282
274
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
283
|
-
|
|
275
|
+
currentIndex = indexPath.item
|
|
276
|
+
pageControl.currentPage = indexPath.item
|
|
277
|
+
updateLabels()
|
|
278
|
+
onItemTapped?(indexPath.item)
|
|
284
279
|
}
|
|
285
280
|
|
|
286
281
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
package/lib/commonjs/index.js
CHANGED
|
@@ -153,8 +153,17 @@ const directAckSentIds = new Set();
|
|
|
153
153
|
function _resetClickDedup() {
|
|
154
154
|
processedClickIds.clear();
|
|
155
155
|
directAckSentIds.clear();
|
|
156
|
+
_lastCarouselClickHandledAt = 0;
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Timestamp of the last iOS carousel click handled by checkPendingCarouselClick().
|
|
161
|
+
* Used by createNotificationOpenedHandler to detect when checkPendingCarouselClick
|
|
162
|
+
* already consumed the one-time App Group click data, avoiding a duplicate
|
|
163
|
+
* NOTIFICATION_OPENED emission with the wrong (default) route.
|
|
164
|
+
*/
|
|
165
|
+
let _lastCarouselClickHandledAt = 0;
|
|
166
|
+
|
|
158
167
|
/**
|
|
159
168
|
* Notification deep link payload
|
|
160
169
|
* Emitted when user clicks on a push notification
|
|
@@ -1899,6 +1908,12 @@ class SwanSDK {
|
|
|
1899
1908
|
return;
|
|
1900
1909
|
}
|
|
1901
1910
|
|
|
1911
|
+
// Signal that we handled this iOS carousel click, so
|
|
1912
|
+
// createNotificationOpenedHandler can skip its duplicate emit.
|
|
1913
|
+
if (_reactNative.Platform.OS === 'ios') {
|
|
1914
|
+
_lastCarouselClickHandledAt = Date.now();
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1902
1917
|
// Send click ACK
|
|
1903
1918
|
if (messageId) {
|
|
1904
1919
|
this.sendNotificationAck(messageId, 'clicked');
|
|
@@ -3831,16 +3846,53 @@ function createNotificationOpenedHandler() {
|
|
|
3831
3846
|
// For carousel notifications, the default route is in 'defaultRoute' field
|
|
3832
3847
|
let route = notificationData.route || notificationData.defaultRoute;
|
|
3833
3848
|
|
|
3834
|
-
// For carousel on iOS,
|
|
3849
|
+
// For carousel on iOS, Content Extension writes per-item click data to
|
|
3850
|
+
// App Group. Two handlers race to read it:
|
|
3851
|
+
// 1. checkPendingCarouselClick() — fires on AppState 'active'
|
|
3852
|
+
// 2. This handler — fires on Firebase onNotificationOpenedApp
|
|
3853
|
+
//
|
|
3854
|
+
// readTemplateClickData() is destructive (clears after read), so the
|
|
3855
|
+
// second reader gets null and falls back to defaultRoute — wrong.
|
|
3856
|
+
//
|
|
3857
|
+
// Fix: peek (non-destructive) to check if data exists. If it does,
|
|
3858
|
+
// defer to checkPendingCarouselClick which has exponential backoff
|
|
3859
|
+
// and is the canonical iOS carousel click handler.
|
|
3860
|
+
// If no data: either already consumed or simple tap (no Content Extension).
|
|
3835
3861
|
if (isIOSCarousel) {
|
|
3836
3862
|
try {
|
|
3837
|
-
const clickData = await _SharedCredentialsManager.SharedCredentialsManager.
|
|
3863
|
+
const clickData = await _SharedCredentialsManager.SharedCredentialsManager.peekTemplateClickData();
|
|
3838
3864
|
if (clickData?.route) {
|
|
3839
|
-
|
|
3840
|
-
|
|
3865
|
+
// Content Extension click data exists — checkPendingCarouselClick
|
|
3866
|
+
// will read it (with backoff) and emit NOTIFICATION_OPENED.
|
|
3867
|
+
// Send ACKs here too as safety net: if app is already foregrounded,
|
|
3868
|
+
// AppState won't transition to 'active' so checkPendingCarouselClick
|
|
3869
|
+
// may never fire.
|
|
3870
|
+
_Logger.default.log('[SwanSDK] iOS carousel click data found, deferring to checkPendingCarouselClick');
|
|
3871
|
+
if (messageId) {
|
|
3872
|
+
await sdkInstance.sendNotificationAck(messageId, 'delivered');
|
|
3873
|
+
}
|
|
3874
|
+
if (messageId) {
|
|
3875
|
+
await sdkInstance.sendNotificationAck(messageId, 'clicked');
|
|
3876
|
+
}
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
// No click data from peek. Either:
|
|
3881
|
+
// (a) checkPendingCarouselClick already consumed it
|
|
3882
|
+
// (b) Simple tap — no Content Extension interaction
|
|
3883
|
+
// Brief wait then check if (a).
|
|
3884
|
+
await new Promise(r => setTimeout(r, 300));
|
|
3885
|
+
if (Date.now() - _lastCarouselClickHandledAt < 5000) {
|
|
3886
|
+
_Logger.default.log('[SwanSDK] iOS carousel click already handled by checkPendingCarouselClick');
|
|
3887
|
+
if (messageId) {
|
|
3888
|
+
await sdkInstance.sendNotificationAck(messageId, 'clicked');
|
|
3889
|
+
}
|
|
3890
|
+
return;
|
|
3841
3891
|
}
|
|
3892
|
+
// Case (b): simple tap, proceed with defaultRoute below
|
|
3893
|
+
_Logger.default.log('[SwanSDK] iOS carousel simple tap, using defaultRoute:', route);
|
|
3842
3894
|
} catch (err) {
|
|
3843
|
-
_Logger.default.warn('[SwanSDK] Failed to
|
|
3895
|
+
_Logger.default.warn('[SwanSDK] Failed to peek Content Extension click data:', err);
|
|
3844
3896
|
}
|
|
3845
3897
|
}
|
|
3846
3898
|
const deepLinkPayload = {
|