Skip to main content

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)

reachu-config.json
{
"apiKey": "your-api-key",
"environment": "sandbox",
"liveShow": {
"campaignId": 0
}
}

Or simply omit campaignId:

reachu-config.json
{
"apiKey": "your-api-key",
"environment": "sandbox"
}

Option 2: With Active Campaign

reachu-config.json
{
"apiKey": "your-api-key",
"environment": "sandbox",
"liveShow": {
"campaignId": 10
}
}

Campaign States

1. Upcoming(Before startDate)

  • Components are NOT displayed - ⏳ Waits for campaign_started event
  • 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_ended event
  • 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 type
  • config: New complete merged configuration

**Action:**Component configuration is updated and UI refreshes immediately.

Usage in Code

Check Campaign State

ContentView.swift
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

ContentView.swift
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

App.swift
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 active
  • RCheckoutOverlay - Hidden if campaign not active
  • RFloatingCartIndicator - Hidden if campaign not active
  • Any component using ReachuComponentWrapper or .reachuOnly()

Complete Example

App.swift
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 if customConfig is 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

  1. **If campaignId is 0 or not configured:**SDK functions normally without restrictions
  2. **WebSocket connection:**Connects automatically if campaignId > 0
  3. **Automatic reconnection:**If connection drops, attempts to reconnect automatically
  4. **One component per type:**Only one component of each type can be active at a time
  5. **No dates configured:**If there's no startDate or endDate, 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:

  1. Market availability
  2. Campaign active state

If either is false, components are hidden automatically.