Campaign Lifecycle Management
The Reachu SDK includes a complete campaign lifecycle management system that allows you to control when and how components are displayed based on campaign start and end dates.
Overview
The Campaign Lifecycle system enables:
- Time-based campaignswith automatic start/end dates
- Real-time updatesvia WebSocket connections
- Component visibility controlbased on campaign state
- Automatic component hidingwhen campaigns end
- Manual component activationduring active campaigns
Default Behavior
Without Campaign (campaignId: 0)
- All components work normally
- No visibility restrictions
- SDK functions as before
With Campaign (campaignId > 0)
- SDK respects campaign lifecycle
- Components shown only when campaign is active
- Real-time updates via WebSocket
Configuration
Option 1: No Campaign (Normal Behavior)
{
"apiKey": "your-api-key",
"environment": "sandbox",
"liveShow": {
"campaignId": 0
}
}
Or simply omit campaignId:
{
"apiKey": "your-api-key",
"environment": "sandbox"
}
Option 2: With Active Campaign
{
"apiKey": "your-api-key",
"environment": "sandbox",
"liveShow": {
"campaignId": 10
}
}
Campaign States
1. Upcoming(Before startDate)
- Components are NOT displayed - ⏳ Waits for
campaign_startedevent - WebSocket connected but waiting
2. Active(Between startDate and endDate)
- Components display normally
- Can be activated/deactivated manually
- Receives real-time events
- WebSocket connected and active
3. Ended(After endDate)
- All components automatically hidden - Receives
campaign_endedevent - WebSocket disconnected
WebSocket Events
The SDK automatically listens for these events:
campaign_started
Sent when a campaign's start date is reached.
{
"type": "campaign_started",
"campaignId": 10,
"startDate": "2024-12-25T10:00:00Z",
"endDate": "2024-12-31T23:59:59Z"
}
**Action:**Campaign activates and active components are loaded.
campaign_ended
Sent when a campaign's end date is reached.
{
"type": "campaign_ended",
"campaignId": 10,
"endDate": "2024-12-31T23:59:59Z"
}
**Action:**All components are hidden immediately.
component_status_changed
Sent when a component is manually activated or deactivated during an active campaign.
{
"type": "component_status_changed",
"data": {
"componentId": 8,
"campaignComponentId": 15,
"componentType": "product_banner",
"status": "active",
"config": {
"productId": "408841",
"backgroundImageUrl": "https://storage.googleapis.com/banner.jpg",
"title": "Oferta Especial",
"subtitle": "50% de descuento",
"ctaText": "Comprar Ahora",
"ctaLink": "https://tienda.com/ofertas",
"deeplink": "pregnancy://product/408841",
"titleColor": "#FFFFFF",
"subtitleColor": "#F0F0F0",
"buttonBackgroundColor": "#FF6B6B",
"buttonTextColor": "#FFFFFF",
"backgroundColor": "rgba(0, 0, 0, 0.5)",
"overlayOpacity": 0.3,
"bannerHeight": 250,
"titleFontSize": 28,
"subtitleFontSize": 18,
"buttonFontSize": 16,
"textAlignment": "left",
"contentVerticalAlignment": "bottom"
}
}
}
Structure: - componentId: Template component ID (Int)
campaignComponentId: Campaign-specific component ID (Int)componentType: Component type (e.g., "product_banner", "product_carousel")status: "active" or "inactive"config: Complete merged configuration (customConfig + defaults)
Action: - If status === "active": Component is shown
- If
status === "inactive": Component is hidden
component_config_updated
Sent when a component's configuration is updated (e.g., admin saves customization).
{
"type": "component_config_updated",
"data": {
"componentId": 8,
"campaignComponentId": 15,
"componentType": "product_banner",
"config": {
"productId": "408841",
"backgroundImageUrl": "https://storage.googleapis.com/banner.jpg",
"title": "Nueva Oferta",
"subtitle": "¡Solo 24 horas!",
"ctaText": "Aprovechar",
"backgroundColor": "rgba(128, 0, 128, 0.7)",
"titleFontSize": 26,
"textAlignment": "center",
"contentVerticalAlignment": "center"
}
}
}
Structure: - componentId: Template component ID (Int)
campaignComponentId: Campaign-specific component ID (Int)componentType: Component typeconfig: New complete merged configuration
**Action:**Component configuration is updated and UI refreshes immediately.
Usage in Code
Check Campaign State
import ReachuCore
struct ContentView: View {
var body: some View {
// Check if campaign is active
if CampaignManager.shared.isCampaignActive {
// Show components
}
// Check specific state
switch CampaignManager.shared.campaignState {
case .upcoming:
Text("Campaign starts soon!")
case .active:
Text("Campaign is active")
case .ended:
Text("Campaign has ended")
}
}
}
Get Active Components
import ReachuCore
struct ContentView: View {
var body: some View {
// Check if component type should be shown
if CampaignManager.shared.shouldShowComponent(type: "banner") {
BannerView()
}
// Get active component by type
if let banner = CampaignManager.shared.getActiveComponent(type: "banner") {
BannerView(config: banner.config)
}
}
}
Listen to Events
import Combine
import ReachuCore
@main
struct MyApp: App {
@StateObject private var cancellables = Set<AnyCancellable>()
init() {
ConfigurationLoader.loadConfiguration()
// Listen for campaign ended
NotificationCenter.default.publisher(for: .campaignEnded)
.sink { notification in
let campaignId = notification.userInfo?["campaignId"] as? Int
print("Campaign \(campaignId ?? 0) ended")
}
.store(in: &cancellables)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Components That Respect Campaigns
All these components automatically hide if the campaign is not active:
RProductSlider- Hidden if campaign not activeRCheckoutOverlay- Hidden if campaign not activeRFloatingCartIndicator- Hidden if campaign not active- Any component using
ReachuComponentWrapperor.reachuOnly()
Complete Example
import SwiftUI
import ReachuCore
import ReachuUI
@main
struct MyApp: App {
init() {
// Load configuration
ConfigurationLoader.loadConfiguration()
// CampaignManager initializes automatically
// If campaignId > 0, connects to WebSocket
// If campaignId == 0, works normally without restrictions
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@StateObject private var cartManager = CartManager()
var body: some View {
NavigationView {
ScrollView {
VStack {
// Your normal content
Text("My App Content")
// Reachu components - automatically hidden if:
// 1. Market not available
// 2. Campaign not active
RProductSlider(
title: "Recommended Products",
layout: .cards,
currency: cartManager.currency,
country: cartManager.country
)
.environmentObject(cartManager)
}
}
}
.environmentObject(cartManager)
.overlay {
// Cart indicator also respects campaign state
RFloatingCartIndicator()
.environmentObject(cartManager)
}
.sheet(isPresented: $cartManager.isCheckoutPresented) {
RCheckoutOverlay()
.environmentObject(cartManager)
}
}
}
API Endpoints
The SDK expects these backend endpoints:
GET /api/campaigns/{campaignId}
Returns campaign information including startDate and endDate.
Response:
{
"id": 10,
"name": "Holiday Sale 2024",
"startDate": "2024-12-25T10:00:00Z",
"endDate": "2024-12-31T23:59:59Z"
}
GET /api/campaigns/{campaignId}/components
Returns all active components for the campaign.
Response Structure:
{
"components": [
{
"id": 15,
"campaignId": 3,
"componentId": 8,
"status": "active",
"scheduledTime": null,
"endTime": null,
"customConfig": {
"productId": "408841",
"backgroundImageUrl": "https://storage.googleapis.com/banner.jpg",
"title": "Oferta Especial",
"subtitle": "50% de descuento",
"ctaText": "Comprar Ahora",
"ctaLink": "https://tienda.com/ofertas",
"deeplink": "pregnancy://product/408841",
"titleColor": "#FFFFFF",
"subtitleColor": "#F0F0F0",
"buttonBackgroundColor": "#FF6B6B",
"buttonTextColor": "#FFFFFF",
"backgroundColor": "rgba(0, 0, 0, 0.5)",
"overlayOpacity": 0.3,
"bannerHeight": 250,
"titleFontSize": 28,
"subtitleFontSize": 18,
"buttonFontSize": 16,
"textAlignment": "left",
"contentVerticalAlignment": "bottom"
},
"component": {
"id": 8,
"type": "product_banner",
"name": "Product Banner",
"config": {
"productId": "",
"backgroundImageUrl": "",
"titleColor": "#FFFFFF",
"backgroundColor": "rgba(0, 0, 0, 0.3)",
"bannerHeight": 200
}
}
}
]
}
Configuration Priority: - customConfig: Campaign-specific configuration (highest priority - used if present)
component.config: Template defaults (used ifcustomConfigis empty)
The SDK automatically uses customConfig if available, otherwise falls back to component.config.
WebSocket wss://your-domain/ws/{campaignId}
WebSocket connection for real-time events.
Error Handling
Campaign Not Found (404)
- SDK functions normally without restrictions
- No errors shown to user
WebSocket Connection Error
- Automatic reconnection with exponential backoff
- Maximum 5 retry attempts
- If fails, SDK functions normally based on initial state
Unknown Component Type
- Silently ignored
- Doesn't affect other components
Logs
No Campaign
[CampaignManager] No campaign configured (campaignId: 0) - SDK works normally
Active Campaign
[CampaignManager] Initializing campaign: 10
[CampaignManager] Campaign 10 is active
[CampaignManager] Loaded 3 active components
[CampaignWebSocket] Connecting to: wss://your-domain/ws/10
Campaign Ended
[CampaignManager] Campaign 10 has ended - hiding all components
[CampaignWebSocket] Received event: campaign_ended
Important Notes
- **If
campaignIdis 0 or not configured:**SDK functions normally without restrictions - **WebSocket connection:**Connects automatically if
campaignId > 0 - **Automatic reconnection:**If connection drops, attempts to reconnect automatically
- **One component per type:**Only one component of each type can be active at a time
- **No dates configured:**If there's no
startDateorendDate, campaign is considered always active
Integration with Market Availability
The campaign system works together with market availability:
// Both checks must pass for components to show
if ReachuConfiguration.shared.shouldUseSDK &&
CampaignManager.shared.isCampaignActive {
// Show components
}
Components automatically respect both:
- Market availability
- Campaign active state
If either is false, components are hidden automatically.