@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.
- package/lib/commonjs/version.js +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/src/version.d.ts +1 -1
- package/lib/typescript/module/src/version.d.ts +1 -1
- package/package.json +4 -6
- package/android/src/test/kotlin/com/loyalytics/swan/templates/carousel/CarouselTemplateTest.kt +0 -125
- package/docs/IOS_NOTIFICATION_EXTENSION_SETUP.md +0 -335
- package/docs/SDK_INDUSTRY_REVIEW_REPORT.md +0 -347
- package/docs/Swan_Push_Notifications.postman_collection.json +0 -330
- package/docs/deep-link-attribution.md +0 -281
- package/scripts/test-carousel-push.js +0 -266
package/lib/commonjs/version.js
CHANGED
|
@@ -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.
|
|
11
|
+
const SDK_VERSION = exports.SDK_VERSION = '2.1.3-beta.4';
|
|
12
12
|
//# sourceMappingURL=version.js.map
|
package/lib/module/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.1.3-beta.
|
|
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.
|
|
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.
|
|
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",
|
package/android/src/test/kotlin/com/loyalytics/swan/templates/carousel/CarouselTemplateTest.kt
DELETED
|
@@ -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. 🎉
|