Showing
8 changed files
with
264 additions
and
29 deletions
| ... | @@ -1249,35 +1249,128 @@ class LegacyViewController: UIViewController { | ... | @@ -1249,35 +1249,128 @@ class LegacyViewController: UIViewController { |
| 1249 | 1249 | ||
| 1250 | ## 📱 Push Notifications | 1250 | ## 📱 Push Notifications |
| 1251 | 1251 | ||
| 1252 | -### Setup | 1252 | +### Complete AppDelegate Setup |
| 1253 | + | ||
| 1254 | +The framework provides full push notification support. Add these methods to your `AppDelegate.swift`: | ||
| 1253 | 1255 | ||
| 1254 | ```swift | 1256 | ```swift |
| 1255 | -// In AppDelegate | 1257 | +import SwiftWarplyFramework |
| 1256 | -func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { | 1258 | +import UserNotifications |
| 1259 | + | ||
| 1260 | +@main | ||
| 1261 | +class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { | ||
| 1262 | + | ||
| 1263 | + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { | ||
| 1264 | + | ||
| 1265 | + // 1️⃣ Configure and initialize SDK (as shown in Setup section above) | ||
| 1266 | + WarplySDK.shared.configure(appUuid: "YOUR_APP_UUID", merchantId: "YOUR_MERCHANT_ID") | ||
| 1267 | + WarplySDK.shared.initialize { success in | ||
| 1268 | + print("SDK Ready: \(success)") | ||
| 1269 | + } | ||
| 1270 | + | ||
| 1271 | + // 2️⃣ Check if app was launched from a push notification | ||
| 1272 | + WarplySDK.shared.handleLaunchOptions(launchOptions) | ||
| 1273 | + | ||
| 1274 | + // 3️⃣ Register for push notifications | ||
| 1275 | + UNUserNotificationCenter.current().delegate = self | ||
| 1276 | + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in | ||
| 1277 | + if granted { | ||
| 1278 | + DispatchQueue.main.async { | ||
| 1279 | + UIApplication.shared.registerForRemoteNotifications() | ||
| 1280 | + } | ||
| 1281 | + } | ||
| 1282 | + } | ||
| 1283 | + | ||
| 1284 | + return true | ||
| 1285 | + } | ||
| 1286 | + | ||
| 1287 | + // 4️⃣ Send device token to Warply server | ||
| 1288 | + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { | ||
| 1257 | let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() | 1289 | let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() |
| 1258 | WarplySDK.shared.updateDeviceToken(tokenString) | 1290 | WarplySDK.shared.updateDeviceToken(tokenString) |
| 1259 | -} | 1291 | + } |
| 1292 | + | ||
| 1293 | + // 5️⃣ Forward foreground notifications to SDK | ||
| 1294 | + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { | ||
| 1295 | + let userInfo = notification.request.content.userInfo as? [String: Any] ?? [:] | ||
| 1260 | 1296 | ||
| 1261 | -func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { | ||
| 1262 | - // Check if it's a Warply notification | ||
| 1263 | if WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) { | 1297 | if WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) { |
| 1264 | - print("Warply notification handled") | 1298 | + // Warply handles it (shows alert in foreground) |
| 1299 | + completionHandler([]) | ||
| 1265 | } else { | 1300 | } else { |
| 1266 | - // Handle other notifications | 1301 | + // Not a Warply notification — show normally |
| 1302 | + completionHandler([.banner, .sound, .badge]) | ||
| 1303 | + } | ||
| 1304 | + } | ||
| 1305 | + | ||
| 1306 | + // 6️⃣ Forward notification taps to SDK | ||
| 1307 | + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { | ||
| 1308 | + let userInfo = response.notification.request.content.userInfo as? [String: Any] ?? [:] | ||
| 1309 | + | ||
| 1310 | + // Let the SDK handle Warply notifications (opens campaign WebView) | ||
| 1311 | + _ = WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) | ||
| 1312 | + | ||
| 1313 | + completionHandler() | ||
| 1267 | } | 1314 | } |
| 1268 | } | 1315 | } |
| 1269 | ``` | 1316 | ``` |
| 1270 | 1317 | ||
| 1271 | -### Handle Notifications | 1318 | +### Handle Pending Notifications |
| 1319 | + | ||
| 1320 | +When the app is launched from a push notification (cold start), call this in your root view controller: | ||
| 1321 | + | ||
| 1322 | +```swift | ||
| 1323 | +class MainViewController: UIViewController { | ||
| 1324 | + override func viewDidAppear(_ animated: Bool) { | ||
| 1325 | + super.viewDidAppear(animated) | ||
| 1326 | + | ||
| 1327 | + // Handle any pending push notification from cold start | ||
| 1328 | + WarplySDK.shared.handlePendingNotification() | ||
| 1329 | + } | ||
| 1330 | +} | ||
| 1331 | +``` | ||
| 1332 | + | ||
| 1333 | +### Optional — Clear Badge on App Open | ||
| 1334 | + | ||
| 1335 | +The **badge** is the small red number circle on your app icon (e.g., showing "3" for 3 unread notifications). Call `resetBadge()` to clear it when the user opens the app: | ||
| 1336 | + | ||
| 1337 | +```swift | ||
| 1338 | +// In AppDelegate — clear the badge when user opens the app | ||
| 1339 | +func applicationDidBecomeActive(_ application: UIApplication) { | ||
| 1340 | + WarplySDK.shared.resetBadge() | ||
| 1341 | +} | ||
| 1342 | +``` | ||
| 1343 | + | ||
| 1344 | +### Optional — Listen for Push Events | ||
| 1345 | + | ||
| 1346 | +If you want to react to push notifications in your own code (in addition to the SDK's automatic handling): | ||
| 1272 | 1347 | ||
| 1273 | ```swift | 1348 | ```swift |
| 1274 | -// Manual notification handling | 1349 | +// Modern EventDispatcher approach |
| 1275 | -WarplySDK.shared.handleNotification(notificationPayload) | 1350 | +let subscription = WarplySDK.shared.subscribe(PushNotificationReceivedEvent.self) { event in |
| 1351 | + print("Push received! Session: \(event.sessionUuid ?? "unknown")") | ||
| 1352 | +} | ||
| 1353 | +``` | ||
| 1354 | + | ||
| 1355 | +### Advanced — Custom Push Handler (Most apps don't need this) | ||
| 1276 | 1356 | ||
| 1277 | -// Check if notification belongs to Warply SDK | 1357 | +By default, the SDK handles all Warply push notifications automatically (shows a campaign WebView). However, if the Warply backend sends pushes with a **custom action code** (`action != 0`), you can implement a handler to process them: |
| 1278 | -let isWarplyNotification = WarplySDK.shared.checkForLoyaltySDKNotification(payload) | 1358 | + |
| 1359 | +```swift | ||
| 1360 | +class MyPushHandler: WarplyPushHandler { | ||
| 1361 | + func didReceiveWarplyNotification(_ payload: WarplyNotificationPayload, whileAppWasIn state: WarplyApplicationState) { | ||
| 1362 | + // This is only called for pushes with action != 0 | ||
| 1363 | + // Standard offer pushes (action == 0) are handled automatically by the SDK | ||
| 1364 | + print("Custom action push: \(payload.action)") | ||
| 1365 | + } | ||
| 1366 | +} | ||
| 1367 | + | ||
| 1368 | +// Set the delegate | ||
| 1369 | +WarplySDK.shared.pushHandlerDelegate = myPushHandler | ||
| 1279 | ``` | 1370 | ``` |
| 1280 | 1371 | ||
| 1372 | +> **Note**: You only need this if the Warply/DEI backend team has told you they send custom action pushes. For standard offer notifications, the SDK handles everything automatically. | ||
| 1373 | + | ||
| 1281 | --- | 1374 | --- |
| 1282 | 1375 | ||
| 1283 | ## ⚠️ Error Handling | 1376 | ## ⚠️ Error Handling | ... | ... |
PUSH_NOTIFICATIONS_MIGRATION_PLAN.md
0 → 100644
This diff is collapsed. Click to expand it.
| ... | @@ -177,18 +177,46 @@ Task { | ... | @@ -177,18 +177,46 @@ Task { |
| 177 | 177 | ||
| 178 | ## 📱 Push Notifications | 178 | ## 📱 Push Notifications |
| 179 | 179 | ||
| 180 | +### Required Setup (AppDelegate) | ||
| 181 | + | ||
| 180 | ```swift | 182 | ```swift |
| 181 | -// AppDelegate | 183 | +// 1️⃣ In didFinishLaunchingWithOptions — check if app launched from push |
| 184 | +WarplySDK.shared.handleLaunchOptions(launchOptions) | ||
| 185 | + | ||
| 186 | +// 2️⃣ Send device token to server | ||
| 182 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { | 187 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { |
| 183 | let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() | 188 | let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() |
| 184 | WarplySDK.shared.updateDeviceToken(tokenString) | 189 | WarplySDK.shared.updateDeviceToken(tokenString) |
| 185 | } | 190 | } |
| 186 | 191 | ||
| 187 | -func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { | 192 | +// 3️⃣ Forward foreground notifications (UNUserNotificationCenterDelegate) |
| 193 | +func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, ...) { | ||
| 194 | + let userInfo = notification.request.content.userInfo as? [String: Any] ?? [:] | ||
| 188 | if WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) { | 195 | if WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) { |
| 189 | - // Warply notification handled | 196 | + completionHandler([]) // SDK handles it (shows alert) |
| 197 | + } else { | ||
| 198 | + completionHandler([.banner, .sound, .badge]) // Not a Warply push | ||
| 190 | } | 199 | } |
| 191 | } | 200 | } |
| 201 | + | ||
| 202 | +// 4️⃣ Forward notification taps (UNUserNotificationCenterDelegate) | ||
| 203 | +func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, ...) { | ||
| 204 | + let userInfo = response.notification.request.content.userInfo as? [String: Any] ?? [:] | ||
| 205 | + _ = WarplySDK.shared.checkForLoyaltySDKNotification(userInfo) | ||
| 206 | + completionHandler() | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +// 5️⃣ Handle pending notifications (in root ViewController's viewDidAppear) | ||
| 210 | +WarplySDK.shared.handlePendingNotification() | ||
| 211 | +``` | ||
| 212 | + | ||
| 213 | +### Optional — Clear Badge on App Open | ||
| 214 | + | ||
| 215 | +```swift | ||
| 216 | +// In applicationDidBecomeActive — clears the red number on the app icon | ||
| 217 | +func applicationDidBecomeActive(_ application: UIApplication) { | ||
| 218 | + WarplySDK.shared.resetBadge() | ||
| 219 | +} | ||
| 192 | ``` | 220 | ``` |
| 193 | 221 | ||
| 194 | --- | 222 | --- | ... | ... |
This diff is collapsed. Click to expand it.
| ... | @@ -226,7 +226,7 @@ public struct SeasonalsRetrievedEvent: WarplyEvent { | ... | @@ -226,7 +226,7 @@ public struct SeasonalsRetrievedEvent: WarplyEvent { |
| 226 | 226 | ||
| 227 | /// Coupons fetched event | 227 | /// Coupons fetched event |
| 228 | public struct CouponsRetrievedEvent: WarplyEvent { | 228 | public struct CouponsRetrievedEvent: WarplyEvent { |
| 229 | - public let name: String = "coupons_fetched" | 229 | +public let name: String = "coupons_fetched" |
| 230 | public let timestamp: Date | 230 | public let timestamp: Date |
| 231 | public let data: Any? | 231 | public let data: Any? |
| 232 | 232 | ||
| ... | @@ -236,6 +236,40 @@ public struct CouponsRetrievedEvent: WarplyEvent { | ... | @@ -236,6 +236,40 @@ public struct CouponsRetrievedEvent: WarplyEvent { |
| 236 | } | 236 | } |
| 237 | } | 237 | } |
| 238 | 238 | ||
| 239 | +/// Event posted when a Warply push notification is received | ||
| 240 | +/// Equivalent of old ObjC `NB_PushReceived` analytics event | ||
| 241 | +public struct PushNotificationReceivedEvent: WarplyEvent { | ||
| 242 | + public let name: String = "push_notification_received" | ||
| 243 | + public let timestamp: Date | ||
| 244 | + public let data: Any? | ||
| 245 | + | ||
| 246 | + /// The session UUID from the push notification payload | ||
| 247 | + public let sessionUuid: String? | ||
| 248 | + | ||
| 249 | + public init(data: Any? = nil, sessionUuid: String? = nil) { | ||
| 250 | + self.timestamp = Date() | ||
| 251 | + self.data = data | ||
| 252 | + self.sessionUuid = sessionUuid | ||
| 253 | + } | ||
| 254 | +} | ||
| 255 | + | ||
| 256 | +/// Event posted when a user interacts with (engages) a Warply push notification | ||
| 257 | +/// Equivalent of old ObjC `NB_PushAck` analytics event | ||
| 258 | +public struct PushNotificationEngagedEvent: WarplyEvent { | ||
| 259 | + public let name: String = "push_notification_engaged" | ||
| 260 | + public let timestamp: Date | ||
| 261 | + public let data: Any? | ||
| 262 | + | ||
| 263 | + /// The session UUID from the push notification payload | ||
| 264 | + public let sessionUuid: String? | ||
| 265 | + | ||
| 266 | + public init(data: Any? = nil, sessionUuid: String? = nil) { | ||
| 267 | + self.timestamp = Date() | ||
| 268 | + self.data = data | ||
| 269 | + self.sessionUuid = sessionUuid | ||
| 270 | + } | ||
| 271 | +} | ||
| 272 | + | ||
| 239 | // MARK: - Convenience Extensions | 273 | // MARK: - Convenience Extensions |
| 240 | 274 | ||
| 241 | extension EventDispatcher { | 275 | extension EventDispatcher { |
| ... | @@ -275,4 +309,20 @@ extension EventDispatcher { | ... | @@ -275,4 +309,20 @@ extension EventDispatcher { |
| 275 | public func postCouponsRetrievedEvent(sender: Any? = nil) { | 309 | public func postCouponsRetrievedEvent(sender: Any? = nil) { |
| 276 | post(CouponsRetrievedEvent(data: sender)) | 310 | post(CouponsRetrievedEvent(data: sender)) |
| 277 | } | 311 | } |
| 312 | + | ||
| 313 | + /// Post a push notification received event | ||
| 314 | + /// - Parameters: | ||
| 315 | + /// - sender: The event data (typically the raw push payload) | ||
| 316 | + /// - sessionUuid: The session UUID from the push notification | ||
| 317 | + public func postPushNotificationReceivedEvent(sender: Any? = nil, sessionUuid: String? = nil) { | ||
| 318 | + post(PushNotificationReceivedEvent(data: sender, sessionUuid: sessionUuid)) | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + /// Post a push notification engaged event | ||
| 322 | + /// - Parameters: | ||
| 323 | + /// - sender: The event data (typically the raw push payload) | ||
| 324 | + /// - sessionUuid: The session UUID from the push notification | ||
| 325 | + public func postPushNotificationEngagedEvent(sender: Any? = nil, sessionUuid: String? = nil) { | ||
| 326 | + post(PushNotificationEngagedEvent(data: sender, sessionUuid: sessionUuid)) | ||
| 327 | + } | ||
| 278 | } | 328 | } | ... | ... |
| ... | @@ -97,7 +97,7 @@ public enum Endpoint { | ... | @@ -97,7 +97,7 @@ public enum Endpoint { |
| 97 | case sendEvent(eventName: String, priority: Bool) | 97 | case sendEvent(eventName: String, priority: Bool) |
| 98 | 98 | ||
| 99 | // Device | 99 | // Device |
| 100 | - case sendDeviceInfo(deviceToken: String) | 100 | + case sendDeviceInfo(deviceInfo: [String: Any]) |
| 101 | 101 | ||
| 102 | // Network status | 102 | // Network status |
| 103 | case getNetworkStatus | 103 | case getNetworkStatus |
| ... | @@ -428,13 +428,10 @@ public enum Endpoint { | ... | @@ -428,13 +428,10 @@ public enum Endpoint { |
| 428 | ] | 428 | ] |
| 429 | ] | 429 | ] |
| 430 | 430 | ||
| 431 | - // Device Info endpoints - device structure | 431 | + // Device Info endpoints - comprehensive device info structure |
| 432 | - case .sendDeviceInfo(let deviceToken): | 432 | + // Matches old ObjC WLPushManager.deviceInfo + WLPushManager.applicationData |
| 433 | - return [ | 433 | + case .sendDeviceInfo(let deviceInfo): |
| 434 | - "device": [ | 434 | + return deviceInfo |
| 435 | - "device_token": deviceToken | ||
| 436 | - ] | ||
| 437 | - ] | ||
| 438 | 435 | ||
| 439 | // Network status - no body needed | 436 | // Network status - no body needed |
| 440 | case .getNetworkStatus: | 437 | case .getNetworkStatus: | ... | ... |
| ... | @@ -860,14 +860,16 @@ extension NetworkService { | ... | @@ -860,14 +860,16 @@ extension NetworkService { |
| 860 | } | 860 | } |
| 861 | } | 861 | } |
| 862 | 862 | ||
| 863 | - /// Update device token | 863 | + /// Send comprehensive device info to server |
| 864 | - public func updateDeviceToken(_ deviceToken: String) async { | 864 | + /// - Parameter deviceInfo: Full device info dictionary (built by WarplySDK.buildDeviceInfo()) |
| 865 | - let endpoint = Endpoint.sendDeviceInfo(deviceToken: deviceToken) | 865 | + public func sendDeviceInfo(_ deviceInfo: [String: Any]) async { |
| 866 | + let endpoint = Endpoint.sendDeviceInfo(deviceInfo: deviceInfo) | ||
| 866 | 867 | ||
| 867 | do { | 868 | do { |
| 868 | _ = try await requestRaw(endpoint) | 869 | _ = try await requestRaw(endpoint) |
| 870 | + print("✅ [NetworkService] Device info sent to server successfully") | ||
| 869 | } catch { | 871 | } catch { |
| 870 | - print("Failed to update device token: \(error)") | 872 | + print("❌ [NetworkService] Failed to send device info: \(error)") |
| 871 | } | 873 | } |
| 872 | } | 874 | } |
| 873 | 875 | ... | ... |
| ... | @@ -496,6 +496,71 @@ self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ... | @@ -496,6 +496,71 @@ self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) |
| 496 | ``` | 496 | ``` |
| 497 | Event naming convention: `custom_{success|error}_{operation}_loyalty` | 497 | Event naming convention: `custom_{success|error}_{operation}_loyalty` |
| 498 | 498 | ||
| 499 | +## 9.5 Push Notification System | ||
| 500 | + | ||
| 501 | +### Architecture (Option A: Host App Forwards) | ||
| 502 | + | ||
| 503 | +The framework does **NOT** set itself as `UNUserNotificationCenterDelegate`. Instead, the host app owns the delegate and forwards calls to the SDK. This avoids conflicts with Firebase, OneSignal, etc. | ||
| 504 | + | ||
| 505 | +### Push Notification Types | ||
| 506 | + | ||
| 507 | +| Type | Location | Purpose | | ||
| 508 | +|------|----------|---------| | ||
| 509 | +| `WarplyApplicationState` | `WarplySDK.swift` | Enum: `.active`, `.background`, `.closed` | | ||
| 510 | +| `WarplyPushHandler` | `WarplySDK.swift` | Protocol for custom push handling (action != 0) | | ||
| 511 | +| `WarplyNotificationPayload` | `WarplySDK.swift` | Parsed push payload (action, sessionUuid, message, aps, customData) | | ||
| 512 | +| `WarplyAPSItem` | `WarplySDK.swift` | Parsed APS dictionary (alert, badge, sound, category) | | ||
| 513 | +| `PushNotificationReceivedEvent` | `EventDispatcher.swift` | Event posted when push received | | ||
| 514 | +| `PushNotificationEngagedEvent` | `EventDispatcher.swift` | Event posted when user interacts with push | | ||
| 515 | + | ||
| 516 | +### Push Notification Detection | ||
| 517 | + | ||
| 518 | +Warply push notifications are identified by the `"_a"` key in the payload: | ||
| 519 | +```json | ||
| 520 | +{"_a": 0, "session_uuid": "abc-123", "aps": {"alert": "New offer!", "badge": 1}} | ||
| 521 | +``` | ||
| 522 | +- `"_a" == 0`: Default handling — framework presents `CampaignViewController` WebView | ||
| 523 | +- `"_a" != 0`: Custom action — forwarded to `pushHandlerDelegate` | ||
| 524 | + | ||
| 525 | +### Push Notification Methods | ||
| 526 | + | ||
| 527 | +| Method | Purpose | | ||
| 528 | +|--------|---------| | ||
| 529 | +| `updateDeviceToken(_:)` | Store token + send comprehensive device info to server | | ||
| 530 | +| `checkForLoyaltySDKNotification(_:)` | Detect Warply push by `"_a"` key, auto-handle if found | | ||
| 531 | +| `handleNotification(_:appState:)` | Route notification by action and app state | | ||
| 532 | +| `handleLaunchOptions(_:)` | Check launch options for push (cold start) | | ||
| 533 | +| `handlePendingNotification()` | Process stored notification from cold start | | ||
| 534 | +| `showPushCampaign(sessionUuid:)` | Present campaign WebView for a session UUID | | ||
| 535 | +| `resetBadge()` | Reset app icon badge to 0 | | ||
| 536 | +| `sendDeviceInfo()` | Force send device info to server | | ||
| 537 | +| `sendDeviceInfoIfNeeded()` | Smart send — only if info changed | | ||
| 538 | +| `buildDeviceInfo()` | Build comprehensive device info dictionary | | ||
| 539 | + | ||
| 540 | +### Push Analytics | ||
| 541 | + | ||
| 542 | +| Event Name | When Sent | Equivalent ObjC | | ||
| 543 | +|------------|-----------|-----------------| | ||
| 544 | +| `NB_PushReceived` | When Warply push is received | `logUserReceivedPush:` | | ||
| 545 | +| `NB_PushAck` | When user engages with push | `logUserEngagedPush:` | | ||
| 546 | + | ||
| 547 | +### Device Info Payload | ||
| 548 | + | ||
| 549 | +Sent to `/api/async/info/{appUUID}/` via `Endpoint.sendDeviceInfo`. Contains: | ||
| 550 | +- `device_token`, `platform` ("ios"), `vendor` ("apple"), `os_version` | ||
| 551 | +- `unique_device_id` (IDFV), `ios_system_name`, `ios_system_version`, `ios_model` | ||
| 552 | +- `ios_locale`, `ios_languages`, `screen_resolution` | ||
| 553 | +- `bundle_identifier`, `app_version`, `app_build`, `sdk_version` | ||
| 554 | +- `development` flag (DEBUG vs release) | ||
| 555 | + | ||
| 556 | +### Rich Push Presentation | ||
| 557 | + | ||
| 558 | +For `action == 0` pushes, the framework presents `CampaignViewController` internally: | ||
| 559 | +- URL: `{baseURL}/api/session/{session_uuid}` | ||
| 560 | +- **Active state**: Shows alert first, then WebView on user confirmation | ||
| 561 | +- **Background state**: Presents WebView immediately | ||
| 562 | +- **Closed state**: Stores as pending, shows via `handlePendingNotification()` | ||
| 563 | + | ||
| 499 | ## 10. Configuration System | 564 | ## 10. Configuration System |
| 500 | 565 | ||
| 501 | ### WarplyConfiguration (`Configuration/WarplyConfiguration.swift`) | 566 | ### WarplyConfiguration (`Configuration/WarplyConfiguration.swift`) | ... | ... |
-
Please register or login to post a comment