@loyalytics/swan-react-native-sdk 2.1.3-beta.2 → 2.1.3-beta.4

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,5 +8,5 @@ exports.SDK_VERSION = void 0;
8
8
  // This file is generated from package.json version during build.
9
9
  // See scripts/generate-version.js
10
10
 
11
- const SDK_VERSION = exports.SDK_VERSION = '2.1.3-beta.2';
11
+ const SDK_VERSION = exports.SDK_VERSION = '2.1.3-beta.4';
12
12
  //# sourceMappingURL=version.js.map
@@ -4,5 +4,5 @@
4
4
  // This file is generated from package.json version during build.
5
5
  // See scripts/generate-version.js
6
6
 
7
- export const SDK_VERSION = '2.1.3-beta.2';
7
+ export const SDK_VERSION = '2.1.3-beta.4';
8
8
  //# sourceMappingURL=version.js.map
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "2.1.3-beta.2";
1
+ export declare const SDK_VERSION = "2.1.3-beta.4";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "2.1.3-beta.2";
1
+ export declare const SDK_VERSION = "2.1.3-beta.4";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loyalytics/swan-react-native-sdk",
3
- "version": "2.1.3-beta.2",
3
+ "version": "2.1.3-beta.4",
4
4
  "description": "React Native SDK for Swan",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
@@ -23,7 +23,6 @@
23
23
  "ios",
24
24
  "cpp",
25
25
  "scripts",
26
- "docs",
27
26
  "*.podspec",
28
27
  "react-native.config.json",
29
28
  "!ios/build",
@@ -35,7 +34,9 @@
35
34
  "!**/__tests__",
36
35
  "!**/__fixtures__",
37
36
  "!**/__mocks__",
38
- "!**/.*"
37
+ "!**/.*",
38
+ "!android/src/test",
39
+ "!scripts/test-carousel-push.js"
39
40
  ],
40
41
  "scripts": {
41
42
  "test": "jest",
@@ -214,9 +215,6 @@
214
215
  "@react-native-async-storage/async-storage": "2.2.0",
215
216
  "@react-native-community/geolocation": "^3.4.0",
216
217
  "@react-native-community/netinfo": "^11.0.0",
217
- "expo": "~52.0.47",
218
- "react": "*",
219
- "react-native": "*",
220
218
  "react-native-base64": "^0.2.1",
221
219
  "react-native-device-info": "^14.0.1",
222
220
  "react-native-shared-group-preferences": "^1.1.24",
@@ -1,125 +0,0 @@
1
- package com.loyalytics.swan.templates.carousel
2
-
3
- import org.junit.Assert.*
4
- import org.junit.Test
5
-
6
- class CarouselTemplateTest {
7
-
8
- // --- wrapIndex ---
9
-
10
- @Test
11
- fun `wrapIndex forward at end wraps to first`() {
12
- assertEquals(0, wrapIndex(3, 3))
13
- }
14
-
15
- @Test
16
- fun `wrapIndex backward at start wraps to last`() {
17
- assertEquals(2, wrapIndex(-1, 3))
18
- }
19
-
20
- @Test
21
- fun `wrapIndex normal forward`() {
22
- assertEquals(1, wrapIndex(1, 3))
23
- assertEquals(2, wrapIndex(2, 3))
24
- }
25
-
26
- @Test
27
- fun `wrapIndex normal backward`() {
28
- assertEquals(1, wrapIndex(1, 3))
29
- }
30
-
31
- @Test
32
- fun `wrapIndex with single item always returns 0`() {
33
- assertEquals(0, wrapIndex(0, 1))
34
- assertEquals(0, wrapIndex(1, 1))
35
- assertEquals(0, wrapIndex(-1, 1))
36
- }
37
-
38
- @Test
39
- fun `wrapIndex with size 0 returns 0`() {
40
- assertEquals(0, wrapIndex(0, 0))
41
- assertEquals(0, wrapIndex(5, 0))
42
- }
43
-
44
- @Test
45
- fun `wrapIndex large negative wraps correctly`() {
46
- // -5 with size 3: -5 mod 3 = -2, + 3 = 1
47
- assertEquals(1, wrapIndex(-5, 3))
48
- }
49
-
50
- @Test
51
- fun `wrapIndex large positive wraps correctly`() {
52
- // 7 with size 3: 7 mod 3 = 1
53
- assertEquals(1, wrapIndex(7, 3))
54
- }
55
-
56
- @Test
57
- fun `wrapIndex full cycle returns to start`() {
58
- // Going forward through all items returns to 0
59
- val size = 5
60
- for (i in 0 until size) {
61
- assertEquals(i, wrapIndex(i, size))
62
- }
63
- assertEquals(0, wrapIndex(size, size))
64
- }
65
-
66
- @Test
67
- fun `wrapIndex two items alternates correctly`() {
68
- assertEquals(0, wrapIndex(0, 2))
69
- assertEquals(1, wrapIndex(1, 2))
70
- assertEquals(0, wrapIndex(2, 2))
71
- assertEquals(1, wrapIndex(-1, 2))
72
- }
73
-
74
- @Test
75
- fun `wrapIndex filmstrip prev-current-next for 3 items`() {
76
- val size = 3
77
- // At index 0: prev=2, current=0, next=1
78
- assertEquals(2, wrapIndex(0 - 1, size))
79
- assertEquals(0, wrapIndex(0, size))
80
- assertEquals(1, wrapIndex(0 + 1, size))
81
-
82
- // At index 2: prev=1, current=2, next=0
83
- assertEquals(1, wrapIndex(2 - 1, size))
84
- assertEquals(2, wrapIndex(2, size))
85
- assertEquals(0, wrapIndex(2 + 1, size))
86
- }
87
-
88
- // --- canHandle ---
89
-
90
- @Test
91
- fun `canHandle returns true for carousel type`() {
92
- val template = CarouselTemplate()
93
- assertTrue(template.canHandle("carousel"))
94
- }
95
-
96
- @Test
97
- fun `canHandle returns false for other types`() {
98
- val template = CarouselTemplate()
99
- assertFalse(template.canHandle("timer"))
100
- assertFalse(template.canHandle("cta"))
101
- assertFalse(template.canHandle(""))
102
- assertFalse(template.canHandle("CAROUSEL"))
103
- assertFalse(template.canHandle("Carousel"))
104
- }
105
-
106
- // --- CarouselAutoRemoteViews.MAX_FLIPPER_IMAGES cap ---
107
- // (Cannot directly test private const, but we verify the build method
108
- // is accessible and the type is correct)
109
-
110
- @Test
111
- fun `CarouselAutoRemoteViews is a singleton object`() {
112
- // Verify the object exists and is accessible
113
- assertNotNull(CarouselAutoRemoteViews)
114
- }
115
-
116
- @Test
117
- fun `CarouselFilmstripRemoteViews is a singleton object`() {
118
- assertNotNull(CarouselFilmstripRemoteViews)
119
- }
120
-
121
- @Test
122
- fun `CarouselRemoteViews is a singleton object`() {
123
- assertNotNull(CarouselRemoteViews)
124
- }
125
- }
@@ -1,335 +0,0 @@
1
- # iOS Notification Service Extension Setup
2
-
3
- ## Overview
4
-
5
- The Swan SDK's Notification Service Extension enables:
6
- - ✅ **Delivery tracking** when app is killed or in background
7
- - ✅ **Rich media support** (images in notifications)
8
- - ✅ **Reliable ACKs** even when app is not running
9
-
10
- This extension is **required for iOS** to track push notification delivery in all app states.
11
-
12
- ---
13
-
14
- ## Why is this needed?
15
-
16
- ### The iOS Limitation
17
-
18
- On iOS, when your app is in background or killed state and receives a push notification with a visible alert, React Native's `setBackgroundMessageHandler` **is NOT called**. This is fundamental iOS behavior, not a bug.
19
-
20
- **What this means:**
21
- - ❌ No delivery tracking when app is killed
22
- - ❌ No custom notification display logic in background
23
- - ❌ No image processing without extension
24
-
25
- ### The Solution: Notification Service Extension
26
-
27
- A Notification Service Extension is a separate iOS app extension that:
28
- - ✅ Runs in a **separate process** from your main app
29
- - ✅ Intercepts **all incoming notifications** before they're displayed
30
- - ✅ Has **30 seconds** to process the notification
31
- - ✅ Can send delivery ACKs, download images, and modify content
32
-
33
- **Industry standard:** Firebase, OneSignal, CleverTap, Airship all use this approach.
34
-
35
- ---
36
-
37
- ## Installation
38
-
39
- ### Step 1: Install Dependencies
40
-
41
- ```bash
42
- npm install react-native-shared-group-preferences
43
- cd ios && pod install && cd ..
44
- ```
45
-
46
- **Why needed:** Share credentials between app and extension.
47
-
48
- ---
49
-
50
- ### Step 2: Configure in Xcode
51
-
52
- #### 2.1 Add Extension Target
53
-
54
- 1. Open `ios/*.xcworkspace` in Xcode
55
- 2. Go to **File → New → Target**
56
- 3. Choose **"Notification Service Extension"**
57
- 4. **Product Name:** `SwanNotificationServiceExtension`
58
- 5. **Language:** Swift
59
- 6. Click **Finish**
60
- 7. When prompted "Activate scheme?", click **Cancel**
61
-
62
- #### 2.2 Configure App Groups
63
-
64
- **App Groups** allow the main app and extension to share credentials.
65
-
66
- ##### Main App Target
67
-
68
- 1. Select your **main app target** (e.g., `YourApp`)
69
- 2. Go to **"Signing & Capabilities"** tab
70
- 3. Click **"+ Capability"**
71
- 4. Search for and add **"App Groups"**
72
- 5. Click **"+"** under App Groups
73
- 6. Enter: `group.swan.sdk.notifications`
74
- 7. Click **OK**
75
-
76
- ##### Extension Target
77
-
78
- 1. Select **SwanNotificationServiceExtension** target
79
- 2. Go to **"Signing & Capabilities"** tab
80
- 3. Click **"+ Capability"**
81
- 4. Search for and add **"App Groups"**
82
- 5. Click **"+"** under App Groups
83
- 6. Enter: `group.swan.sdk.notifications` (**MUST match main app!**)
84
- 7. Click **OK**
85
-
86
- **⚠️ IMPORTANT:** Both targets must have the **exact same** App Group ID.
87
-
88
- #### 2.3 Verify Bundle Identifier
89
-
90
- 1. Select **SwanNotificationServiceExtension** target
91
- 2. Go to **"General"** tab
92
- 3. Check **Bundle Identifier**: Should be `com.yourcompany.app.SwanNotificationServiceExtension`
93
- 4. Make sure it's a **child** of your main app's bundle ID
94
-
95
- ---
96
-
97
- ### Step 3: Run Setup Script
98
-
99
- Now that the extension target is created, run the setup script to copy the Swan implementation files. This will overwrite the default files created by Xcode.
100
-
101
- ```bash
102
- node node_modules/swan-react-native-sdk/scripts/setup-ios-extension.js
103
- ```
104
-
105
- **What it does:**
106
- - Copies extension files to `ios/SwanNotificationServiceExtension/`
107
- - Validates dependencies
108
- - Ensures the correct implementation is in place
109
-
110
- ---
111
-
112
- ### Step 4: Testing
113
-
114
- ### Build and Archive
115
-
116
- ```bash
117
- # Clean build
118
- cd ios
119
- rm -rf build
120
- xcodebuild clean -workspace YourApp.xcworkspace -scheme YourApp
121
-
122
- # Build
123
- xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release
124
-
125
- # Archive (for TestFlight or Ad Hoc)
126
- xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release archive
127
- ```
128
-
129
- ### Test on Real Device
130
-
131
- **IMPORTANT:** Push notifications don't work on iOS Simulator!
132
-
133
- 1. Archive your app (Product → Archive in Xcode)
134
- 2. Export to device or upload to TestFlight
135
- 3. Install on a real iOS device
136
- 4. Send a test notification
137
- 5. Check Xcode **Device Console** for logs
138
-
139
- ### Expected Logs
140
-
141
- When extension receives a notification:
142
-
143
- ```
144
- [SwanSDK Extension] Notification Service Extension triggered
145
- [SwanSDK Extension] Request identifier: 1234567890
146
- [SwanSDK Extension] UserInfo: {...}
147
- [SwanSDK Extension] Found messageId in gcm.message_id: 0:1234567890
148
- [SwanSDK Extension] Sending delivery ACK for messageId: 0:1234567890
149
- [SwanSDK Extension] Credentials loaded - appId: YOUR_APP_ID
150
- [SwanSDK Extension] ACK payload: {...}
151
- [SwanSDK Extension] ACK response status: 200
152
- [SwanSDK Extension] ✅ Delivery ACK sent successfully
153
- ```
154
-
155
- If image is present:
156
- ```
157
- [SwanSDK Extension] Downloading notification image: https://example.com/image.png
158
- [SwanSDK Extension] ✅ Image downloaded successfully
159
- ```
160
-
161
- ### View Logs on Real Device
162
-
163
- **Option 1: Xcode Device Console**
164
- 1. Connect device via USB
165
- 2. Xcode → Window → Devices and Simulators
166
- 3. Select your device
167
- 4. Click **"Open Console"**
168
- 5. Filter by "SwanSDK Extension"
169
-
170
- **Option 2: Console.app (macOS)**
171
- 1. Open Console app on Mac
172
- 2. Select your connected device
173
- 3. Search for "SwanSDK Extension"
174
-
175
- ---
176
-
177
- ## Troubleshooting
178
-
179
- ### Extension logs don't appear
180
-
181
- **Symptom:** No `[SwanSDK Extension]` logs when notification arrives
182
-
183
- **Possible causes:**
184
-
185
- 1. **Missing mutable-content: 1**
186
- - Check FCM payload includes `mutable-content: 1` in APNS
187
- - Without it, iOS won't trigger the extension
188
-
189
- 2. **App Groups not configured**
190
- - Verify both targets have "App Groups" capability
191
- - Verify App Group ID is `group.swan.sdk.notifications` in BOTH targets
192
- - IDs must match exactly (no typos!)
193
-
194
- 3. **Extension not included in build**
195
- - In Xcode, go to Product → Scheme → Edit Scheme
196
- - Check that SwanNotificationServiceExtension is included
197
-
198
- 4. **Testing on simulator**
199
- - Push notifications don't work on iOS Simulator
200
- - Test on a real device
201
-
202
- ### Extension runs but no ACK sent
203
-
204
- **Symptom:** See extension logs but backend doesn't receive ACK
205
-
206
- **Possible causes:**
207
-
208
- 1. **Credentials not saved to App Group**
209
- - Ensure SDK is initialized: `SwanSDK.init({ appId: '...' })`
210
- - Credentials are saved automatically on init
211
- - Check logs for: `[SharedCredentials] ✅ Credentials saved to iOS App Group`
212
-
213
- 2. **App Group ID mismatch**
214
- - Extension uses `group.swan.sdk.notifications` by default
215
- - If you changed it, update `NotificationService.swift` line 143
216
-
217
- 3. **Network issue**
218
- - Extension has only 30 seconds to complete
219
- - Check device has internet connection
220
-
221
- ### Images not appearing
222
-
223
- **Symptom:** Notification arrives but image doesn't display
224
-
225
- **Possible causes:**
226
-
227
- 1. **Image URL invalid**
228
- - Check image URL is accessible (try in browser)
229
- - Must be HTTPS (HTTP won't work)
230
-
231
- 2. **Image format unsupported**
232
- - Supported: JPEG, PNG, GIF
233
- - Max size: 10MB (iOS limit)
234
-
235
- 3. **mutable-content missing**
236
- - Extension won't run without `mutable-content: 1`
237
-
238
- ### Build errors
239
-
240
- **Error:** `No such module 'UserNotifications'`
241
- - Solution: Make sure deployment target is iOS 10.0+
242
-
243
- **Error:** `Use of undeclared type 'UNNotificationServiceExtension'`
244
- - Solution: Verify extension target is set to iOS 10.0+ deployment target
245
-
246
- **Error:** `Could not find or use auto-linked library 'swiftFoundation'`
247
- - Solution: Add `use_frameworks!` to Podfile
248
-
249
- **Error:** `CocoaPods Error: Unable to find compatibility version string for object version '70'`
250
- - Solution: In Xcode, select your Project (blue icon) → Build Settings (or Info) → Project Format. Change it to **Xcode 16.0-compatible** or later.
251
-
252
- ---
253
-
254
- ## Customization
255
-
256
- ### Custom App Group ID
257
-
258
- If you want to use a different App Group ID:
259
-
260
- 1. **In Xcode:** Update App Groups in both targets to use your custom ID (e.g., `group.com.yourcompany.app`)
261
-
262
- 2. **In SDK:** Configure before calling `SwanSDK.init()`:
263
-
264
- ```typescript
265
- import { SharedCredentialsManager } from 'swan-react-native-sdk';
266
-
267
- // Set custom App Group ID BEFORE SDK init
268
- SharedCredentialsManager.setAppGroupId('group.com.yourcompany.app');
269
-
270
- // Then initialize SDK
271
- await SwanSDK.init({ appId: 'YOUR_APP_ID' });
272
- ```
273
-
274
- 3. **In Extension:** Update `NotificationService.swift`:
275
-
276
- ```swift
277
- let appGroupId = "group.com.yourcompany.app"
278
- ```
279
-
280
-
281
-
282
- ## FAQs
283
-
284
- ### Q: Do I need to modify the extension code?
285
-
286
- **A:** No. The extension works out of the box. Only modify if you need custom App Group ID.
287
-
288
- ### Q: Will this increase my app size?
289
-
290
- **A:** Minimally. The extension adds ~50KB to your IPA.
291
-
292
- ### Q: Does this affect app performance?
293
-
294
- **A:** No. The extension runs in a separate process and only when notifications arrive.
295
-
296
- ### Q: Can I test without archiving?
297
-
298
- **A:** Yes, but you must run on a real device. Push doesn't work on simulator.
299
-
300
- ### Q: What if I already have a Notification Service Extension?
301
-
302
- **A:** You'll need to merge Swan's code into your existing extension. The key parts are:
303
- 1. Reading credentials from App Group
304
- 2. Sending delivery ACK
305
- 3. Downloading images
306
-
307
- ### Q: Do I need this for Android?
308
-
309
- **A:** No. Android data-only push notifications work without an extension. This is iOS-only.
310
-
311
- ---
312
-
313
- ## Support
314
-
315
- If you encounter issues:
316
-
317
- 1. Check logs in Xcode Device Console
318
- 2. Verify all steps in this guide
319
- 3. Test with the provided FCM payload example
320
- 4. Check that App Group IDs match in both targets
321
-
322
- For detailed debugging, enable verbose logging:
323
- - Main app: `SwanSDK.setLoggingEnabled(true)`
324
- - Extension: Logs are always enabled, check Device Console
325
-
326
- ---
327
-
328
- ## Summary
329
-
330
- ✅ **Install:** `npm install react-native-shared-group-preferences`
331
- ✅ **Xcode:** Add extension target + configure App Groups
332
- ✅ **Run:** `node node_modules/swan-react-native-sdk/scripts/setup-ios-extension.js`
333
- ✅ **Test:** Archive and test on real device
334
-
335
- That's it! Your iOS notifications now have reliable delivery tracking and rich media support. 🎉