@loyalytics/swan-react-native-sdk 2.1.3-beta.0 → 2.1.3-beta.1
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/android/build.gradle +66 -0
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/kotlin/com/loyalytics/swan/SwanNotificationModule.kt +43 -0
- package/android/src/main/kotlin/com/loyalytics/swan/SwanNotificationPackage.kt +16 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/SwanNotificationActionReceiver.kt +49 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/SwanNotificationTemplate.kt +20 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/SwanTemplateRegistry.kt +47 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/carousel/CarouselAutoRemoteViews.kt +103 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/carousel/CarouselFilmstripRemoteViews.kt +132 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/carousel/CarouselRemoteViews.kt +129 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/carousel/CarouselTemplate.kt +412 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/common/NotificationBitmapCache.kt +70 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/common/NotificationImageLoader.kt +97 -0
- package/android/src/main/kotlin/com/loyalytics/swan/templates/common/NotificationStateManager.kt +85 -0
- package/android/src/main/res/anim/swan_fade_in.xml +6 -0
- package/android/src/main/res/anim/swan_fade_out.xml +6 -0
- package/android/src/main/res/anim/swan_slide_in_right.xml +8 -0
- package/android/src/main/res/anim/swan_slide_out_left.xml +8 -0
- package/android/src/main/res/drawable/swan_ic_chevron_left.xml +11 -0
- package/android/src/main/res/drawable/swan_ic_chevron_right.xml +11 -0
- package/android/src/main/res/layout/swan_carousel_auto_expanded.xml +51 -0
- package/android/src/main/res/layout/swan_carousel_collapsed.xml +31 -0
- package/android/src/main/res/layout/swan_carousel_expanded.xml +96 -0
- package/android/src/main/res/layout/swan_carousel_filmstrip_expanded.xml +115 -0
- package/android/src/main/res/layout/swan_carousel_flipper_item.xml +7 -0
- package/android/src/test/kotlin/com/loyalytics/swan/templates/carousel/CarouselTemplateTest.kt +125 -0
- package/docs/SDK_INDUSTRY_REVIEW_REPORT.md +347 -0
- package/docs/Swan_Push_Notifications.postman_collection.json +330 -0
- package/docs/deep-link-attribution.md +281 -0
- package/ios/SwanNotificationContentExtension/Info.plist +40 -0
- package/ios/SwanNotificationContentExtension/MainInterface.storyboard +19 -0
- package/ios/SwanNotificationContentExtension/NotificationViewController.swift +190 -0
- package/ios/SwanNotificationContentExtension/SwanNotificationContentExtension.entitlements +10 -0
- package/ios/SwanNotificationContentExtension/common/ImageDownloader.swift +32 -0
- package/ios/SwanNotificationContentExtension/templates/CarouselView.swift +336 -0
- package/lib/commonjs/constants/ApiUrls.js.map +1 -1
- package/lib/commonjs/index.js +117 -35
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/providers/NullPushProvider.js.map +1 -1
- package/lib/commonjs/services/DeviceRegistrationService.js.map +1 -1
- package/lib/commonjs/state/AuthStateMachine.js.map +1 -1
- package/lib/commonjs/state/DeviceStateMachine.js.map +1 -1
- package/lib/commonjs/state/PushStateMachine.js.map +1 -1
- package/lib/commonjs/utils/FirebaseNotificationManager.js.map +1 -1
- package/lib/commonjs/utils/Logger.js.map +1 -1
- package/lib/commonjs/utils/SharedCredentialsManager.js +28 -0
- package/lib/commonjs/utils/SharedCredentialsManager.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/index.js +117 -35
- package/lib/module/index.js.map +1 -1
- package/lib/module/providers/NullPushProvider.js.map +1 -1
- package/lib/module/services/DeviceRegistrationService.js.map +1 -1
- package/lib/module/state/AuthStateMachine.js.map +1 -1
- package/lib/module/state/DeviceStateMachine.js.map +1 -1
- package/lib/module/state/PushStateMachine.js.map +1 -1
- package/lib/module/utils/FirebaseNotificationManager.js.map +1 -1
- package/lib/module/utils/Logger.js.map +1 -1
- package/lib/module/utils/SharedCredentialsManager.js +28 -0
- package/lib/module/utils/SharedCredentialsManager.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/src/constants/ApiUrls.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/providers/NullPushProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/services/DeviceRegistrationService.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/state/AuthStateMachine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/state/DeviceStateMachine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/state/PushStateMachine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/utils/FirebaseNotificationManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/utils/Logger.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/module/src/constants/ApiUrls.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/providers/NullPushProvider.d.ts.map +1 -1
- package/lib/typescript/module/src/services/DeviceRegistrationService.d.ts.map +1 -1
- package/lib/typescript/module/src/state/AuthStateMachine.d.ts.map +1 -1
- package/lib/typescript/module/src/state/DeviceStateMachine.d.ts.map +1 -1
- package/lib/typescript/module/src/state/PushStateMachine.d.ts.map +1 -1
- package/lib/typescript/module/src/utils/FirebaseNotificationManager.d.ts.map +1 -1
- package/lib/typescript/module/src/utils/Logger.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/package.json +7 -3
- package/react-native.config.json +12 -0
- package/scripts/setup-ios-extension.js +100 -20
- package/scripts/test-carousel-push.js +266 -0
- package/swan-react-native-sdk.podspec +18 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
{
|
|
2
|
+
"info": {
|
|
3
|
+
"_postman_id": "swan-push-notifications-v1",
|
|
4
|
+
"name": "Swan SDK — Push Notifications",
|
|
5
|
+
"description": "Complete collection for testing all Swan SDK push notification types across Android and iOS.\n\n## Setup\n1. Set `project_id` to your Firebase project ID\n2. Set `fcm_token` to the device's FCM token\n3. Set `access_token` by running: `gcloud auth print-access-token`\n\n## How it works\n- All requests use the FCM v1 HTTP API\n- Variables are shared across all requests\n- Just update the token and send",
|
|
6
|
+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
7
|
+
},
|
|
8
|
+
"variable": [
|
|
9
|
+
{
|
|
10
|
+
"key": "project_id",
|
|
11
|
+
"value": "loyalytics-app",
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"key": "fcm_token",
|
|
16
|
+
"value": "<PASTE_DEVICE_FCM_TOKEN_HERE>",
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "access_token",
|
|
21
|
+
"value": "<PASTE_GCLOUD_ACCESS_TOKEN_HERE>",
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"key": "fcm_url",
|
|
26
|
+
"value": "https://fcm.googleapis.com/v1/projects/{{project_id}}/messages:send",
|
|
27
|
+
"type": "string"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"item": [
|
|
31
|
+
{
|
|
32
|
+
"name": "Android",
|
|
33
|
+
"item": [
|
|
34
|
+
{
|
|
35
|
+
"name": "Standard Push",
|
|
36
|
+
"request": {
|
|
37
|
+
"method": "POST",
|
|
38
|
+
"header": [
|
|
39
|
+
{
|
|
40
|
+
"key": "Authorization",
|
|
41
|
+
"value": "Bearer {{access_token}}"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"key": "Content-Type",
|
|
45
|
+
"value": "application/json"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"url": {
|
|
49
|
+
"raw": "{{fcm_url}}",
|
|
50
|
+
"host": ["{{fcm_url}}"]
|
|
51
|
+
},
|
|
52
|
+
"body": {
|
|
53
|
+
"mode": "raw",
|
|
54
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"title\": \"Your order has shipped!\",\n \"body\": \"Track your package #ABC123\",\n \"channelId\": \"swan_transactional\",\n \"route\": \"/orders/123\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "Standard Push — With Image",
|
|
60
|
+
"request": {
|
|
61
|
+
"method": "POST",
|
|
62
|
+
"header": [
|
|
63
|
+
{
|
|
64
|
+
"key": "Authorization",
|
|
65
|
+
"value": "Bearer {{access_token}}"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"key": "Content-Type",
|
|
69
|
+
"value": "application/json"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"url": {
|
|
73
|
+
"raw": "{{fcm_url}}",
|
|
74
|
+
"host": ["{{fcm_url}}"]
|
|
75
|
+
},
|
|
76
|
+
"body": {
|
|
77
|
+
"mode": "raw",
|
|
78
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"title\": \"Summer Sale!\",\n \"body\": \"50% off everything\",\n \"channelId\": \"swan_promotional\",\n \"route\": \"/sales/summer\",\n \"image\": \"https://picsum.photos/id/1/1024/512\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "Carousel — Manual / Standard",
|
|
84
|
+
"request": {
|
|
85
|
+
"method": "POST",
|
|
86
|
+
"header": [
|
|
87
|
+
{
|
|
88
|
+
"key": "Authorization",
|
|
89
|
+
"value": "Bearer {{access_token}}"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"key": "Content-Type",
|
|
93
|
+
"value": "application/json"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"url": {
|
|
97
|
+
"raw": "{{fcm_url}}",
|
|
98
|
+
"host": ["{{fcm_url}}"]
|
|
99
|
+
},
|
|
100
|
+
"body": {
|
|
101
|
+
"mode": "raw",
|
|
102
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"manual\",\n \"carouselVariant\": \"standard\",\n \"title\": \"Weekend Flash Sale\",\n \"body\": \"Swipe to explore deals!\",\n \"defaultRoute\": \"/collections/weekend-sale\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/1/720/360\\\",\\\"title\\\":\\\"Summer Collection\\\",\\\"body\\\":\\\"50% off\\\",\\\"route\\\":\\\"/product/summer\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/20/720/360\\\",\\\"title\\\":\\\"New Arrivals\\\",\\\"body\\\":\\\"Just dropped\\\",\\\"route\\\":\\\"/product/new\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/30/720/360\\\",\\\"title\\\":\\\"Best Sellers\\\",\\\"body\\\":\\\"Top picks\\\",\\\"route\\\":\\\"/product/best\\\"}]\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "Carousel — Manual / Filmstrip",
|
|
108
|
+
"request": {
|
|
109
|
+
"method": "POST",
|
|
110
|
+
"header": [
|
|
111
|
+
{
|
|
112
|
+
"key": "Authorization",
|
|
113
|
+
"value": "Bearer {{access_token}}"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"key": "Content-Type",
|
|
117
|
+
"value": "application/json"
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"url": {
|
|
121
|
+
"raw": "{{fcm_url}}",
|
|
122
|
+
"host": ["{{fcm_url}}"]
|
|
123
|
+
},
|
|
124
|
+
"body": {
|
|
125
|
+
"mode": "raw",
|
|
126
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"manual\",\n \"carouselVariant\": \"filmstrip\",\n \"title\": \"Trending Now\",\n \"body\": \"See what's popular\",\n \"defaultRoute\": \"/trending\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/40/720/360\\\",\\\"title\\\":\\\"Watches\\\",\\\"body\\\":\\\"From $99\\\",\\\"route\\\":\\\"/product/watches\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/50/720/360\\\",\\\"title\\\":\\\"Sneakers\\\",\\\"body\\\":\\\"New colors\\\",\\\"route\\\":\\\"/product/sneakers\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/60/720/360\\\",\\\"title\\\":\\\"Bags\\\",\\\"body\\\":\\\"Premium leather\\\",\\\"route\\\":\\\"/product/bags\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/70/720/360\\\",\\\"title\\\":\\\"Sunglasses\\\",\\\"body\\\":\\\"UV protection\\\",\\\"route\\\":\\\"/product/sunglasses\\\"}]\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "Carousel — Auto Scroll",
|
|
132
|
+
"request": {
|
|
133
|
+
"method": "POST",
|
|
134
|
+
"header": [
|
|
135
|
+
{
|
|
136
|
+
"key": "Authorization",
|
|
137
|
+
"value": "Bearer {{access_token}}"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"key": "Content-Type",
|
|
141
|
+
"value": "application/json"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"url": {
|
|
145
|
+
"raw": "{{fcm_url}}",
|
|
146
|
+
"host": ["{{fcm_url}}"]
|
|
147
|
+
},
|
|
148
|
+
"body": {
|
|
149
|
+
"mode": "raw",
|
|
150
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"auto\",\n \"carouselVariant\": \"standard\",\n \"carouselInterval\": \"4000\",\n \"title\": \"Flash Deals\",\n \"body\": \"Auto-scrolling deals!\",\n \"defaultRoute\": \"/deals\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/10/720/360\\\",\\\"title\\\":\\\"Deal 1\\\",\\\"body\\\":\\\"60% off\\\",\\\"route\\\":\\\"/deal/1\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/11/720/360\\\",\\\"title\\\":\\\"Deal 2\\\",\\\"body\\\":\\\"Buy 1 Get 1\\\",\\\"route\\\":\\\"/deal/2\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/12/720/360\\\",\\\"title\\\":\\\"Deal 3\\\",\\\"body\\\":\\\"Free shipping\\\",\\\"route\\\":\\\"/deal/3\\\"}]\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "Silent Push",
|
|
156
|
+
"request": {
|
|
157
|
+
"method": "POST",
|
|
158
|
+
"header": [
|
|
159
|
+
{
|
|
160
|
+
"key": "Authorization",
|
|
161
|
+
"value": "Bearer {{access_token}}"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"key": "Content-Type",
|
|
165
|
+
"value": "application/json"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"url": {
|
|
169
|
+
"raw": "{{fcm_url}}",
|
|
170
|
+
"host": ["{{fcm_url}}"]
|
|
171
|
+
},
|
|
172
|
+
"body": {
|
|
173
|
+
"mode": "raw",
|
|
174
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"silent\": \"true\",\n \"action\": \"sync_config\"\n },\n \"android\": {\n \"priority\": \"high\"\n }\n }\n}"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "iOS",
|
|
182
|
+
"item": [
|
|
183
|
+
{
|
|
184
|
+
"name": "Standard Push",
|
|
185
|
+
"request": {
|
|
186
|
+
"method": "POST",
|
|
187
|
+
"header": [
|
|
188
|
+
{
|
|
189
|
+
"key": "Authorization",
|
|
190
|
+
"value": "Bearer {{access_token}}"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"key": "Content-Type",
|
|
194
|
+
"value": "application/json"
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"url": {
|
|
198
|
+
"raw": "{{fcm_url}}",
|
|
199
|
+
"host": ["{{fcm_url}}"]
|
|
200
|
+
},
|
|
201
|
+
"body": {
|
|
202
|
+
"mode": "raw",
|
|
203
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"title\": \"Your order has shipped!\",\n \"body\": \"Track your package #ABC123\",\n \"route\": \"/orders/123\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"alert\",\n \"apns-priority\": \"10\"\n },\n \"payload\": {\n \"aps\": {\n \"content-available\": 1\n }\n }\n }\n }\n}"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "Standard Push — With Image",
|
|
209
|
+
"request": {
|
|
210
|
+
"method": "POST",
|
|
211
|
+
"header": [
|
|
212
|
+
{
|
|
213
|
+
"key": "Authorization",
|
|
214
|
+
"value": "Bearer {{access_token}}"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"key": "Content-Type",
|
|
218
|
+
"value": "application/json"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"url": {
|
|
222
|
+
"raw": "{{fcm_url}}",
|
|
223
|
+
"host": ["{{fcm_url}}"]
|
|
224
|
+
},
|
|
225
|
+
"body": {
|
|
226
|
+
"mode": "raw",
|
|
227
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"title\": \"Summer Sale!\",\n \"body\": \"50% off everything\",\n \"route\": \"/sales/summer\",\n \"image\": \"https://picsum.photos/id/1/1024/512\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"alert\",\n \"apns-priority\": \"10\"\n },\n \"payload\": {\n \"aps\": {\n \"content-available\": 1\n }\n }\n }\n }\n}"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "Carousel — Manual / Standard",
|
|
233
|
+
"request": {
|
|
234
|
+
"method": "POST",
|
|
235
|
+
"header": [
|
|
236
|
+
{
|
|
237
|
+
"key": "Authorization",
|
|
238
|
+
"value": "Bearer {{access_token}}"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"key": "Content-Type",
|
|
242
|
+
"value": "application/json"
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
"url": {
|
|
246
|
+
"raw": "{{fcm_url}}",
|
|
247
|
+
"host": ["{{fcm_url}}"]
|
|
248
|
+
},
|
|
249
|
+
"body": {
|
|
250
|
+
"mode": "raw",
|
|
251
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"manual\",\n \"carouselVariant\": \"standard\",\n \"title\": \"Weekend Flash Sale\",\n \"body\": \"Swipe to explore deals!\",\n \"defaultRoute\": \"/collections/weekend-sale\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/1/720/360\\\",\\\"title\\\":\\\"Summer Collection\\\",\\\"body\\\":\\\"50% off\\\",\\\"route\\\":\\\"/product/summer\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/20/720/360\\\",\\\"title\\\":\\\"New Arrivals\\\",\\\"body\\\":\\\"Just dropped\\\",\\\"route\\\":\\\"/product/new\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/30/720/360\\\",\\\"title\\\":\\\"Best Sellers\\\",\\\"body\\\":\\\"Top picks\\\",\\\"route\\\":\\\"/product/best\\\"}]\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"alert\",\n \"apns-priority\": \"10\"\n },\n \"payload\": {\n \"aps\": {\n \"mutable-content\": 1,\n \"category\": \"swan_carousel\",\n \"alert\": {\n \"title\": \"Weekend Flash Sale\",\n \"body\": \"Swipe to explore deals!\"\n }\n }\n }\n }\n }\n}"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "Carousel — Manual / Filmstrip",
|
|
257
|
+
"request": {
|
|
258
|
+
"method": "POST",
|
|
259
|
+
"header": [
|
|
260
|
+
{
|
|
261
|
+
"key": "Authorization",
|
|
262
|
+
"value": "Bearer {{access_token}}"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"key": "Content-Type",
|
|
266
|
+
"value": "application/json"
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
"url": {
|
|
270
|
+
"raw": "{{fcm_url}}",
|
|
271
|
+
"host": ["{{fcm_url}}"]
|
|
272
|
+
},
|
|
273
|
+
"body": {
|
|
274
|
+
"mode": "raw",
|
|
275
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"manual\",\n \"carouselVariant\": \"filmstrip\",\n \"title\": \"Trending Now\",\n \"body\": \"See what's popular\",\n \"defaultRoute\": \"/trending\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/40/720/360\\\",\\\"title\\\":\\\"Watches\\\",\\\"body\\\":\\\"From $99\\\",\\\"route\\\":\\\"/product/watches\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/50/720/360\\\",\\\"title\\\":\\\"Sneakers\\\",\\\"body\\\":\\\"New colors\\\",\\\"route\\\":\\\"/product/sneakers\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/60/720/360\\\",\\\"title\\\":\\\"Bags\\\",\\\"body\\\":\\\"Premium leather\\\",\\\"route\\\":\\\"/product/bags\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/70/720/360\\\",\\\"title\\\":\\\"Sunglasses\\\",\\\"body\\\":\\\"UV protection\\\",\\\"route\\\":\\\"/product/sunglasses\\\"}]\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"alert\",\n \"apns-priority\": \"10\"\n },\n \"payload\": {\n \"aps\": {\n \"mutable-content\": 1,\n \"category\": \"swan_carousel\",\n \"alert\": {\n \"title\": \"Trending Now\",\n \"body\": \"See what's popular\"\n }\n }\n }\n }\n }\n}"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "Carousel — Auto Scroll",
|
|
281
|
+
"request": {
|
|
282
|
+
"method": "POST",
|
|
283
|
+
"header": [
|
|
284
|
+
{
|
|
285
|
+
"key": "Authorization",
|
|
286
|
+
"value": "Bearer {{access_token}}"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"key": "Content-Type",
|
|
290
|
+
"value": "application/json"
|
|
291
|
+
}
|
|
292
|
+
],
|
|
293
|
+
"url": {
|
|
294
|
+
"raw": "{{fcm_url}}",
|
|
295
|
+
"host": ["{{fcm_url}}"]
|
|
296
|
+
},
|
|
297
|
+
"body": {
|
|
298
|
+
"mode": "raw",
|
|
299
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"notificationType\": \"carousel\",\n \"carouselMode\": \"auto\",\n \"carouselVariant\": \"standard\",\n \"carouselInterval\": \"4000\",\n \"title\": \"Flash Deals\",\n \"body\": \"Auto-scrolling deals!\",\n \"defaultRoute\": \"/deals\",\n \"channelId\": \"swan_notifications\",\n \"carouselItems\": \"[{\\\"imageUrl\\\":\\\"https://picsum.photos/id/10/720/360\\\",\\\"title\\\":\\\"Deal 1\\\",\\\"body\\\":\\\"60% off\\\",\\\"route\\\":\\\"/deal/1\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/11/720/360\\\",\\\"title\\\":\\\"Deal 2\\\",\\\"body\\\":\\\"Buy 1 Get 1\\\",\\\"route\\\":\\\"/deal/2\\\"},{\\\"imageUrl\\\":\\\"https://picsum.photos/id/12/720/360\\\",\\\"title\\\":\\\"Deal 3\\\",\\\"body\\\":\\\"Free shipping\\\",\\\"route\\\":\\\"/deal/3\\\"}]\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"alert\",\n \"apns-priority\": \"10\"\n },\n \"payload\": {\n \"aps\": {\n \"mutable-content\": 1,\n \"category\": \"swan_carousel\",\n \"alert\": {\n \"title\": \"Flash Deals\",\n \"body\": \"Auto-scrolling deals!\"\n }\n }\n }\n }\n }\n}"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"name": "Silent Push",
|
|
305
|
+
"request": {
|
|
306
|
+
"method": "POST",
|
|
307
|
+
"header": [
|
|
308
|
+
{
|
|
309
|
+
"key": "Authorization",
|
|
310
|
+
"value": "Bearer {{access_token}}"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"key": "Content-Type",
|
|
314
|
+
"value": "application/json"
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
"url": {
|
|
318
|
+
"raw": "{{fcm_url}}",
|
|
319
|
+
"host": ["{{fcm_url}}"]
|
|
320
|
+
},
|
|
321
|
+
"body": {
|
|
322
|
+
"mode": "raw",
|
|
323
|
+
"raw": "{\n \"message\": {\n \"token\": \"{{fcm_token}}\",\n \"data\": {\n \"silent\": \"true\",\n \"action\": \"sync_config\"\n },\n \"apns\": {\n \"headers\": {\n \"apns-push-type\": \"background\",\n \"apns-priority\": \"5\"\n },\n \"payload\": {\n \"aps\": {\n \"content-available\": 1\n }\n }\n }\n }\n}"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Deep Link Attribution Tracking
|
|
2
|
+
|
|
3
|
+
Track campaign clicks from email, SMS, and WhatsApp to measure which campaigns drive app engagement.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When your marketing team sends emails, SMS, or WhatsApp messages containing links to your app, the Swan SDK automatically detects these clicks and reports them to the Swan backend for campaign attribution. This enables funnel tracking: **link click → app open → add to cart → purchase**.
|
|
8
|
+
|
|
9
|
+
**No code changes required.** The SDK handles everything automatically after `init()`. Your existing deep link routing continues to work as-is — the SDK only adds silent attribution tracking on top.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
User receives email/SMS/WhatsApp with campaign link
|
|
15
|
+
↓
|
|
16
|
+
User taps link → App opens via deep link
|
|
17
|
+
↓
|
|
18
|
+
React Native Linking API fires → Swan SDK intercepts
|
|
19
|
+
↓
|
|
20
|
+
SDK checks for swan_ parameters in the URL
|
|
21
|
+
↓
|
|
22
|
+
┌─ swan_ params found → Send click ACK to Swan backend
|
|
23
|
+
└─ No swan_ params → Ignore (not a Swan campaign link)
|
|
24
|
+
↓
|
|
25
|
+
App's existing deep link routing handles navigation (unaffected)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The SDK supports both:
|
|
29
|
+
- **Warm start** — app is already running in the background
|
|
30
|
+
- **Cold start** — app is launched from a killed state via the deep link
|
|
31
|
+
|
|
32
|
+
## Prerequisites
|
|
33
|
+
|
|
34
|
+
For the SDK to receive deep links, your app must be configured to handle them at the platform level. Most React Native apps that support any form of deep linking already have this set up.
|
|
35
|
+
|
|
36
|
+
### Android
|
|
37
|
+
|
|
38
|
+
Add intent filters to your `AndroidManifest.xml` inside the `<activity>` tag for `MainActivity`:
|
|
39
|
+
|
|
40
|
+
**Custom scheme** (e.g., `yourapp://products/123`):
|
|
41
|
+
|
|
42
|
+
```xml
|
|
43
|
+
<intent-filter>
|
|
44
|
+
<action android:name="android.intent.action.VIEW" />
|
|
45
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
46
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
47
|
+
<data android:scheme="yourapp" />
|
|
48
|
+
</intent-filter>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**HTTPS App Links** (e.g., `https://yourstore.com/products/123`):
|
|
52
|
+
|
|
53
|
+
```xml
|
|
54
|
+
<intent-filter android:autoVerify="true">
|
|
55
|
+
<action android:name="android.intent.action.VIEW" />
|
|
56
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
57
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
58
|
+
<data android:scheme="https" android:host="yourstore.com" />
|
|
59
|
+
</intent-filter>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> For HTTPS App Links, you also need to host a Digital Asset Links file at `https://yourstore.com/.well-known/assetlinks.json`. See the [Android App Links documentation](https://developer.android.com/training/app-links) for details.
|
|
63
|
+
|
|
64
|
+
### iOS
|
|
65
|
+
|
|
66
|
+
**Custom scheme:**
|
|
67
|
+
|
|
68
|
+
Add your URL scheme in Xcode: **Target → Info → URL Types → Add URL Scheme** (e.g., `yourapp`).
|
|
69
|
+
|
|
70
|
+
Or in `Info.plist`:
|
|
71
|
+
|
|
72
|
+
```xml
|
|
73
|
+
<key>CFBundleURLTypes</key>
|
|
74
|
+
<array>
|
|
75
|
+
<dict>
|
|
76
|
+
<key>CFBundleURLSchemes</key>
|
|
77
|
+
<array>
|
|
78
|
+
<string>yourapp</string>
|
|
79
|
+
</array>
|
|
80
|
+
</dict>
|
|
81
|
+
</array>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**AppDelegate setup:**
|
|
85
|
+
|
|
86
|
+
Ensure your `AppDelegate.mm` passes URLs to React Native (this is included by default in most React Native projects):
|
|
87
|
+
|
|
88
|
+
```objc
|
|
89
|
+
- (BOOL)application:(UIApplication *)application
|
|
90
|
+
openURL:(NSURL *)url
|
|
91
|
+
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
|
|
92
|
+
{
|
|
93
|
+
return [RCTLinkingManager application:application openURL:url options:options];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// For Universal Links
|
|
97
|
+
- (BOOL)application:(UIApplication *)application
|
|
98
|
+
continueUserActivity:(nonnull NSUserActivity *)userActivity
|
|
99
|
+
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
|
|
100
|
+
{
|
|
101
|
+
return [RCTLinkingManager application:application
|
|
102
|
+
continueUserActivity:userActivity
|
|
103
|
+
restorationHandler:restorationHandler];
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Universal Links** (e.g., `https://yourstore.com/products/123`):
|
|
108
|
+
|
|
109
|
+
1. Add the Associated Domains entitlement in Xcode: `applinks:yourstore.com`
|
|
110
|
+
2. Host an `apple-app-site-association` file at `https://yourstore.com/.well-known/apple-app-site-association`
|
|
111
|
+
|
|
112
|
+
See the [Apple Universal Links documentation](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) for details.
|
|
113
|
+
|
|
114
|
+
### Expo
|
|
115
|
+
|
|
116
|
+
Add the `scheme` property to your `app.json`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"expo": {
|
|
121
|
+
"scheme": "yourapp"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Then run `npx expo prebuild` to regenerate native projects.
|
|
127
|
+
|
|
128
|
+
## Campaign URL Format
|
|
129
|
+
|
|
130
|
+
For attribution tracking to work, the backend must append Swan parameters to the campaign links before sending them. The SDK looks for query parameters prefixed with `swan_`.
|
|
131
|
+
|
|
132
|
+
### Required Parameters
|
|
133
|
+
|
|
134
|
+
| Parameter | Description | Example |
|
|
135
|
+
|-----------|-------------|---------|
|
|
136
|
+
| `swan_comm_id` | Communication ID (generated by Swan before sending) | `swan_comm_id=comm_abc123` |
|
|
137
|
+
|
|
138
|
+
### Optional Parameters
|
|
139
|
+
|
|
140
|
+
| Parameter | Description | Example |
|
|
141
|
+
|-----------|-------------|---------|
|
|
142
|
+
| `swan_link_id` | Identifies which link within the message was clicked (useful for emails with multiple CTAs) | `swan_link_id=hero_banner` |
|
|
143
|
+
|
|
144
|
+
### Example Campaign URLs
|
|
145
|
+
|
|
146
|
+
**Email with multiple links:**
|
|
147
|
+
```
|
|
148
|
+
https://yourstore.com/sale?swan_comm_id=email_001&swan_link_id=hero_banner
|
|
149
|
+
https://yourstore.com/products/shoes?swan_comm_id=email_001&swan_link_id=category_cta
|
|
150
|
+
https://yourstore.com/cart?swan_comm_id=email_001&swan_link_id=footer_link
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**SMS with single link:**
|
|
154
|
+
```
|
|
155
|
+
https://yourstore.com/offer/50off?swan_comm_id=sms_002
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**WhatsApp with custom scheme:**
|
|
159
|
+
```
|
|
160
|
+
yourapp://products/flash-sale?swan_comm_id=wa_003&swan_link_id=buy_now
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> URLs without `swan_comm_id` are silently ignored by the SDK — there is zero overhead for non-campaign links.
|
|
164
|
+
|
|
165
|
+
## Integration
|
|
166
|
+
|
|
167
|
+
**No code changes required.** The SDK sets up deep link listeners automatically during `init()`. As long as:
|
|
168
|
+
|
|
169
|
+
1. Your app's platform configuration handles deep links (see Prerequisites above)
|
|
170
|
+
2. Campaign URLs include `swan_comm_id` as a query parameter
|
|
171
|
+
|
|
172
|
+
...the SDK will automatically track clicks and send them to the Swan backend.
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
import SwanEcomSDK from '@loyalytics/swan-react-native-sdk';
|
|
176
|
+
|
|
177
|
+
// This is all you need — deep link tracking is included automatically
|
|
178
|
+
const sdk = SwanEcomSDK.getInstance('YOUR_APP_ID', {
|
|
179
|
+
isProduction: true,
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Your existing deep link navigation code continues to work unchanged. The SDK does not interfere with routing — it only reads the URL parameters for attribution.
|
|
184
|
+
|
|
185
|
+
## Backend Webhook Payload
|
|
186
|
+
|
|
187
|
+
When a campaign deep link is clicked, the SDK sends a click acknowledgment to the Swan webhook endpoint. The payload includes:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"commId": "email_001",
|
|
192
|
+
"appId": "your-app-id",
|
|
193
|
+
"CDID": "customer-device-id",
|
|
194
|
+
"event": "clicked",
|
|
195
|
+
"deviceId": "device-id",
|
|
196
|
+
"type": "deepLink",
|
|
197
|
+
"linkId": "hero_banner"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Field | Always present | Description |
|
|
202
|
+
|-------|---------------|-------------|
|
|
203
|
+
| `commId` | Yes | The `swan_comm_id` from the URL |
|
|
204
|
+
| `type` | Yes | Always `"deepLink"` — distinguishes from push notification clicks |
|
|
205
|
+
| `event` | Yes | Always `"clicked"` |
|
|
206
|
+
| `linkId` | Only if `swan_link_id` was in the URL | Identifies which specific link was clicked |
|
|
207
|
+
| `appId` | Yes | Your app ID |
|
|
208
|
+
| `CDID` | Yes | Customer device ID (logged-in user) or generated anonymous ID |
|
|
209
|
+
| `deviceId` | Yes | Device identifier |
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
212
|
+
|
|
213
|
+
### Android
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Campaign link with all parameters
|
|
217
|
+
adb shell am start -a android.intent.action.VIEW \
|
|
218
|
+
-d "yourapp://products/123?swan_comm_id=test_001&swan_link_id=cta1"
|
|
219
|
+
|
|
220
|
+
# Campaign link without link ID (single-link SMS)
|
|
221
|
+
adb shell am start -a android.intent.action.VIEW \
|
|
222
|
+
-d "yourapp://offer?swan_comm_id=test_002"
|
|
223
|
+
|
|
224
|
+
# Non-campaign link (should be silently ignored)
|
|
225
|
+
adb shell am start -a android.intent.action.VIEW \
|
|
226
|
+
-d "yourapp://products/123?utm_source=google"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### iOS Simulator
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Campaign link with all parameters
|
|
233
|
+
xcrun simctl openurl booted \
|
|
234
|
+
"yourapp://products/123?swan_comm_id=test_001&swan_link_id=cta1"
|
|
235
|
+
|
|
236
|
+
# Campaign link without link ID
|
|
237
|
+
xcrun simctl openurl booted \
|
|
238
|
+
"yourapp://offer?swan_comm_id=test_002"
|
|
239
|
+
|
|
240
|
+
# Non-campaign link (should be silently ignored)
|
|
241
|
+
xcrun simctl openurl booted \
|
|
242
|
+
"yourapp://products/123?utm_source=google"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Expected Log Output
|
|
246
|
+
|
|
247
|
+
Enable SDK logging to verify attribution tracking:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
SwanEcomSDK.setLoggingEnabled(true);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Campaign link detected:**
|
|
254
|
+
```
|
|
255
|
+
[SwanSDK] Deep link received (warm start): yourapp://products/123?swan_comm_id=test_001&swan_link_id=cta1
|
|
256
|
+
[SwanSDK] Deep link attribution: found SWAN parameters: {"swan_comm_id":"test_001","swan_link_id":"cta1"}
|
|
257
|
+
[SwanSDK] Notification ACK queued: test_001 clicked
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Non-campaign link (no output):**
|
|
261
|
+
```
|
|
262
|
+
[SwanSDK] Deep link received (warm start): yourapp://products/123?utm_source=google
|
|
263
|
+
```
|
|
264
|
+
No further logs — the SDK silently ignores it.
|
|
265
|
+
|
|
266
|
+
## FAQ
|
|
267
|
+
|
|
268
|
+
**Q: Do I need to change any code in my app?**
|
|
269
|
+
No. The SDK handles deep link attribution automatically. Your existing navigation and deep link routing remains unchanged.
|
|
270
|
+
|
|
271
|
+
**Q: What if my app already uses React Navigation's deep linking?**
|
|
272
|
+
That's fine. The SDK uses `Linking.addEventListener` to listen for URLs, which works alongside React Navigation's `linking` configuration. Both receive the URL — React Navigation handles routing, the SDK handles attribution.
|
|
273
|
+
|
|
274
|
+
**Q: What happens if the user clicks a link but is offline?**
|
|
275
|
+
The click event is queued in the SDK's local SQLite database and sent to the backend when the network is restored.
|
|
276
|
+
|
|
277
|
+
**Q: Does this add any overhead for non-campaign links?**
|
|
278
|
+
Negligible. The SDK checks for `swan_` prefixed parameters in the URL query string. If none are found, it returns immediately with no further processing.
|
|
279
|
+
|
|
280
|
+
**Q: What SDK version is required?**
|
|
281
|
+
Deep link attribution tracking is available from version **2.1.0** onwards.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDisplayName</key>
|
|
6
|
+
<string>SwanNotificationContent</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0</string>
|
|
19
|
+
<key>CFBundleVersion</key>
|
|
20
|
+
<string>1</string>
|
|
21
|
+
<key>NSExtension</key>
|
|
22
|
+
<dict>
|
|
23
|
+
<key>NSExtensionAttributes</key>
|
|
24
|
+
<dict>
|
|
25
|
+
<key>UNNotificationExtensionCategory</key>
|
|
26
|
+
<array>
|
|
27
|
+
<string>swan_carousel</string>
|
|
28
|
+
</array>
|
|
29
|
+
<key>UNNotificationExtensionInitialContentSizeRatio</key>
|
|
30
|
+
<real>0.7</real>
|
|
31
|
+
<key>UNNotificationExtensionDefaultContentHidden</key>
|
|
32
|
+
<false/>
|
|
33
|
+
</dict>
|
|
34
|
+
<key>NSExtensionPointIdentifier</key>
|
|
35
|
+
<string>com.apple.usernotifications.content-extension</string>
|
|
36
|
+
<key>NSExtensionPrincipalClass</key>
|
|
37
|
+
<string>SwanNotificationContentExtension.NotificationViewController</string>
|
|
38
|
+
</dict>
|
|
39
|
+
</dict>
|
|
40
|
+
</plist>
|