SwiftWarplyFramework — AI Agent Skill File
Comprehensive knowledge base for AI coding agents (Claude Code, Gemini CLI, Codex, OpenCode) working on the SwiftWarplyFramework iOS SDK.
1. Project Overview
SwiftWarplyFramework is a native iOS loyalty/rewards SDK (version 2.4.0) built in Swift for the DEI (Public Power Corporation of Greece) engagement platform. It provides a complete loyalty program toolkit including campaigns, coupons, merchant discovery, user profiles, card management, transaction history, and market pass (supermarket deals) features.
- Language: Swift 5.9+
- Minimum iOS: 17.0
-
Repository:
https://git.warp.ly/open-source/warply_sdk_framework.git - Framework Type: Distributable iOS framework (CocoaPods + SPM)
-
Entry Point:
WarplySDK.shared(singleton) -
Backend: RESTful API on
engage-uat.dei.gr(UAT) /engage-prod.dei.gr(production) - Auth Model: JWT tokens (access + refresh) stored in local SQLite database, automatic refresh with circuit breaker
What This SDK Does
- Authenticates users via DEI login (email-based) or Cosmote partner flow (GUID-based)
- Manages loyalty campaigns, coupons, coupon sets, and personalized offers
- Handles card management (add/get/delete credit cards)
- Provides transaction and points history
- Manages supermarket/market pass deals with map integration
- Sends analytics events (Dynatrace integration)
- Provides pre-built UI screens (MyRewards, Profile, Coupon, Campaign WebView)
2. Architecture & Design Patterns
Core Patterns
| Pattern | Where Used | Details |
|---|---|---|
| Singleton |
WarplySDK.shared, NetworkService.shared, DatabaseManager.shared, EventDispatcher.shared
|
Main entry points for all subsystems |
| Actor Isolation |
TokenRefreshManager, FieldEncryption, KeychainManager, TokenRefreshCircuitBreaker, RequestQueue
|
Thread-safe concurrent access for security-critical components |
| Enum-based Routing |
Endpoint enum |
All API endpoints defined as enum cases with computed properties for path, method, parameters, auth type |
| Dual API Surface | All public SDK methods | Every API has both callback-based and async/await variants |
| Event Bus (Dual) |
SwiftEventBus + EventDispatcher
|
Events posted to both systems for backward compatibility |
| PropertyWrapper | @UserDefault<T> |
Type-safe UserDefaults access |
| Circuit Breaker | TokenRefreshCircuitBreaker |
Prevents excessive token refresh attempts |
| Builder/Factory |
WarplyConfiguration presets |
.development, .production, .testing, .highSecurity
|
Dependency Flow
WarplySDK (public API facade)
├── NetworkService (HTTP requests)
│ ├── Endpoint (URL/param/auth routing)
│ └── TokenRefreshManager (actor, JWT refresh)
│ └── DatabaseManager (token storage)
├── DatabaseManager (SQLite.swift)
│ └── FieldEncryption (actor, AES-256-GCM)
│ └── KeychainManager (actor, hardware-backed keys)
├── EventDispatcher (modern event system)
├── SDKState (in-memory campaign/coupon state)
└── UserDefaultsStore (non-sensitive preferences)
Threading Model
-
Main thread: UI operations, completion callbacks (all callbacks dispatched via
MainActor.run) -
Background: Network requests via
async/await, database operations - Actor isolation: Token refresh, encryption, keychain access — all actors prevent data races
-
Configuration queue:
DispatchQueue(label: "com.warply.sdk.configuration")for thread-safe config updates
3. Directory Structure
SwiftWarplyFramework/
├── Package.swift # SPM package definition
├── SwiftWarplyFramework.podspec # CocoaPods spec (v2.4.0)
├── skill.md # This file
├── SwiftWarplyFramework/
│ ├── SwiftWarplyFramework/
│ │ ├── Core/
│ │ │ └── WarplySDK.swift # Main SDK singleton — ALL public APIs
│ │ ├── Network/
│ │ │ ├── Endpoints.swift # Endpoint enum (all API routes)
│ │ │ ├── NetworkService.swift # HTTP client, request building, auth headers
│ │ │ └── TokenRefreshManager.swift # Actor-based token refresh with retry/circuit breaker
│ │ ├── Database/
│ │ │ └── DatabaseManager.swift # SQLite.swift wrapper, token/event/POI storage
│ │ ├── Security/
│ │ │ ├── FieldEncryption.swift # AES-256-GCM field-level encryption (actor)
│ │ │ └── KeychainManager.swift # iOS Keychain key management (actor)
│ │ ├── Configuration/
│ │ │ ├── WarplyConfiguration.swift # Main config container + presets
│ │ │ ├── DatabaseConfiguration.swift
│ │ │ ├── TokenConfiguration.swift
│ │ │ ├── NetworkConfiguration.swift
│ │ │ └── LoggingConfiguration.swift
│ │ ├── Events/
│ │ │ └── EventDispatcher.swift # Type-safe event system (replaces SwiftEventBus internally)
│ │ ├── models/ # All data models
│ │ │ ├── Campaign.swift # CampaignItemModel, LoyaltyContextualOfferModel
│ │ │ ├── Coupon.swift # CouponItemModel, CouponSetItemModel, RedeemedSMHistoryModel
│ │ │ ├── TokenModel.swift # JWT token model with expiration parsing
│ │ │ ├── Merchant.swift # MerchantModel
│ │ │ ├── ProfileModel.swift # User profile
│ │ │ ├── CardModel.swift # Credit card model
│ │ │ ├── TransactionModel.swift # Transaction history
│ │ │ ├── PointsHistoryModel.swift # Points history
│ │ │ ├── ArticleModel.swift # Content articles
│ │ │ ├── Market.swift # MarketPassDetailsModel
│ │ │ ├── Response.swift # VerifyTicketResponseModel, GenericResponseModel
│ │ │ ├── Models.swift # LoyaltySDKDynatraceEventModel, LoyaltyGiftsForYouPackage
│ │ │ ├── Events.swift # Event-related models
│ │ │ ├── Gifts.swift # Gift models
│ │ │ ├── OfferModel.swift # Offer models
│ │ │ ├── SectionModel.swift # UI section models
│ │ │ ├── CouponFilterModel.swift # Coupon filter UI models
│ │ │ ├── MerchantCategoryModel.swift # Merchant categories
│ │ │ └── QuestionnaireAnswerModel.swift
│ │ ├── screens/ # Pre-built view controllers
│ │ │ ├── MyRewardsViewController/ # Main rewards screen
│ │ │ ├── ProfileViewController/ # User profile screen
│ │ │ ├── CouponViewController/ # Single coupon detail
│ │ │ ├── CouponsetViewController/ # Coupon set detail
│ │ │ └── CampaignViewController/ # WebView for campaign URLs
│ │ ├── cells/ # Reusable table/collection view cells
│ │ │ ├── MyRewardsBannerOfferCollectionViewCell/
│ │ │ ├── MyRewardsBannerOffersScrollTableViewCell/
│ │ │ ├── MyRewardsOfferCollectionViewCell/
│ │ │ ├── MyRewardsOffersScrollTableViewCell/
│ │ │ ├── MyRewardsProfileInfoTableViewCell/
│ │ │ ├── ProfileCouponFiltersTableViewCell/
│ │ │ ├── ProfileCouponTableViewCell/
│ │ │ ├── ProfileFilterCollectionViewCell/
│ │ │ ├── ProfileHeaderTableViewCell/
│ │ │ └── ProfileQuestionnaireTableViewCell/
│ │ ├── Helpers/
│ │ │ ├── WarplyReactMethods.h # React Native bridge (Obj-C header)
│ │ │ └── WarplyReactMethods.m # React Native bridge (Obj-C impl)
│ │ ├── fonts/ # PingLCG font family (.otf)
│ │ ├── Media.xcassets/ # Image assets
│ │ ├── Main.storyboard # Storyboard for campaign WebView
│ │ ├── CopyableLabel.swift # UILabel subclass with copy support
│ │ ├── UIColorExtensions.swift # Color utilities
│ │ ├── ViewControllerExtensions.swift # VC extensions
│ │ ├── XIBLoader.swift # XIB/Bundle loading helpers
│ │ ├── MyEmptyClass.swift # Bundle reference helper
│ │ ├── SwiftWarplyFramework.h # Framework umbrella header
│ │ └── Info.plist
│ └── SwiftWarplyFramework.xcodeproj/
4. SDK Lifecycle
Initialization Flow (Required Order)
// Step 1: Configure (sets environment URLs, appUuid, merchantId)
WarplySDK.shared.configure(
appUuid: "your-32-char-hex-uuid",
merchantId: "",
environment: .production, // or .development
language: "el" // "el" or "en"
)
// Step 2: Initialize (validates config, initializes DB, registers device)
WarplySDK.shared.initialize { success in
// SDK ready to use
}
// OR async:
try await WarplySDK.shared.initialize()
// Step 3: Authenticate user (one of these methods)
// DEI Login:
WarplySDK.shared.deiLogin(email: "user@example.com") { response in ... }
// OR Cosmote flow:
WarplySDK.shared.verifyTicket(guid: "...", ticket: "...") { response in ... }
// OR Cosmote user:
WarplySDK.shared.getCosmoteUser(guid: "...") { response in ... }
What Happens During initialize()
- Validates
appUuidis not empty - Sets
Configuration.baseURLandConfiguration.hostfrom stored environment - Stores
appUuidin UserDefaults for NetworkService access - Initializes SQLite database (
DatabaseManager.shared.initializeDatabase()) - Performs automatic device registration with comprehensive device info
- Posts Dynatrace analytics event on success/failure
Authentication Token Flow
- Login methods (
deiLogin,verifyTicket,getCosmoteUser) receive JWT tokens from server - Tokens are parsed into
TokenModel(automatic JWTexpclaim extraction) -
TokenModelstored in SQLiterequestVariablestable viaDatabaseManager -
NetworkServicereads tokens from database for authenticated requests - Proactive refresh: tokens refreshed 5 minutes before expiration
- On 401 response: automatic token refresh + request retry
- Logout: tokens cleared from database
Environment Configuration
| Environment | Base URL | Host |
|---|---|---|
.development |
https://engage-uat.dei.gr |
engage-uat.dei.gr |
.production |
https://engage-prod.dei.gr |
engage-prod.dei.gr |
5. Core Components
WarplySDK (Core/WarplySDK.swift)
The sole public API surface of the framework. All client interactions go through WarplySDK.shared.
Key Responsibilities:
- SDK configuration and initialization
- All network API calls (campaigns, coupons, merchants, profile, cards, transactions)
- In-memory state management (campaigns, coupons, merchants, coupon sets)
- Event posting (dual: SwiftEventBus + EventDispatcher)
- Campaign URL construction for WebView
- UI presentation helpers (map, supermarket flow, dialogs)
Internal Architecture:
- Uses
NetworkServicefor all HTTP calls - Uses
DatabaseManagerfor token storage - Uses
SDKState(private class) for in-memory data cache - Uses
UserDefaultsStore(private class) for preferences - Uses
EventDispatcherfor modern event dispatching
State Management Pattern:
// All state is stored in SDKState.shared (private)
// Accessed via getter/setter methods on WarplySDK:
WarplySDK.shared.setCampaignList(campaigns) // stores sorted by _sorting
WarplySDK.shared.getCampaignList() // returns filtered (no ccms, no telco, no questionnaire)
WarplySDK.shared.getAllCampaignList() // returns unfiltered
WarplySDK.shared.setCouponList(coupons) // filters active (status==1), sorts by expiration
WarplySDK.shared.getCouponList() // returns active coupons
WarplySDK.shared.setOldCouponList(coupons) // filters redeemed (status==0), sorts by redeemed_date desc
Campaign Processing Logic (Authenticated Users):
- Fetch basic campaigns from
/api/mobile/v2/{appUUID}/context/ - Fetch personalized campaigns from
/oauth/{appUUID}/context - Merge both arrays
- Fetch coupon availability
- Set
_coupon_availabilityon each campaign matching its couponset - Filter: remove campaigns with
_coupon_availability == 0 - Separate carousel items (
_carousel == "true") - Remove ccms offers, telco, and questionnaire campaigns
- Sort by
_sortingfield
Campaign Processing Logic (Unauthenticated Users):
- Only fetch basic campaigns — return ALL without coupon filtering
6. Network Layer
NetworkService (Network/NetworkService.swift)
URLSession-based HTTP client with automatic auth header injection, token refresh, and request/response logging.
Key Features:
- Dynamic
baseURLfromConfiguration.baseURL - Network connectivity monitoring via
NWPathMonitor - Automatic placeholder replacement in URLs and request bodies
- Context response transformation (flattens
{"status":"1","context":{...}}pattern) - Proactive token refresh before requests (5 min threshold)
- Automatic 401 retry with token refresh
- Comprehensive request/response logging with sensitive data masking
Endpoint System (Network/Endpoints.swift)
All API routes are defined as cases of the Endpoint enum.
Endpoint Categories:
| Category | Base Path | Auth Type | Examples |
|---|---|---|---|
standardContext |
/api/mobile/v2/{appUUID}/context/ |
Standard (loyalty headers) | getCampaigns, getAvailableCoupons, getCouponSets, getMerchantCategories, getArticles |
authenticatedContext |
/oauth/{appUUID}/context |
Bearer Token | getCoupons, getProfile, getMarketPassDetails, addCard, getCards, deleteCard, getTransactionHistory, getPointsHistory, validateCoupon, redeemCoupon, getCampaignsPersonalized |
userManagement |
/user/{appUUID}/* |
Standard | register, changePassword, resetPassword, requestOtp |
authentication |
/oauth/{appUUID}/* |
Standard | refreshToken, logout |
partnerCosmote |
/partners/* |
Basic/Standard | verifyTicket, getCosmoteUser, deiLogin |
session |
/api/session/{sessionUuid} |
Standard (GET) | getSingleCampaign |
analytics |
/api/async/analytics/{appUUID}/ |
Standard | sendEvent |
deviceInfo |
/api/async/info/{appUUID}/ |
Standard | sendDeviceInfo |
Authentication Types:
| Type | Headers Added |
|---|---|
.standard |
loyalty-web-id, loyalty-date, loyalty-signature (SHA256 of apiKey+timestamp), platform headers, device headers |
.bearerToken |
All standard headers + Authorization: Bearer {access_token} (from database) |
.basicAuth |
All standard headers + Authorization: Basic {encoded_credentials} (for Cosmote partner endpoints) |
Headers Always Sent:
-
loyalty-web-id— merchant ID from Configuration -
loyalty-date— Unix timestamp -
loyalty-signature— SHA256 hash of"{apiKey}{timestamp}" Accept-Encoding: gzipAccept: application/jsonUser-Agent: gzipchannel: mobileloyalty-bundle-id: ios:{bundleId}unique-device-id: {IDFV}-
vendor: apple,platform: ios,os_version: {version}
Placeholder Replacement System:
-
{appUUID}→ App UUID from UserDefaults -
{access_token}→ Access token from database -
{refresh_token}→ Refresh token from database -
{web_id}→ Merchant ID from Configuration -
{api_key}→ Empty string (legacy, deprecated) -
{sessionUuid}→ Extracted from endpoint parameters
Context Response Transformation:
Server returns: {"status": "1", "context": {"MAPP_CAMPAIGNING": {...}}}
Transformed to: {"status": 1, "MAPP_CAMPAIGNING": {...}}
This flattening happens in transformContextResponse() for backward compatibility.
Request Parameter Structures: Most POST endpoints wrap parameters in a domain-specific key:
// Campaigns: {"campaigns": {"action": "retrieve", "language": "el", "filters": {...}}}
// Coupons: {"coupon": {"action": "user_coupons", "details": ["merchant","redemption"], ...}}
// Profile: {"consumer_data": {"action": "handle_user_details", "process": "get"}}
// Cards: {"cards": {"action": "add_card", "card_number": "...", ...}}
// Merchants: {"shops": {"language": "el", "action": "retrieve_multilingual"}}
// Articles: {"content": {"language": "el", "action": "retrieve_multilingual"}}
TokenRefreshManager (Network/TokenRefreshManager.swift)
Actor-based coordinator for JWT token refresh with retry and circuit breaker.
Retry Logic:
- Configurable
maxRetryAttempts(default: 3) andretryDelays(default: [0.0, 1.0, 5.0]) - 401/4xx errors: no retry (permanent failure)
- 5xx/network errors: retry with backoff
- After all retries fail: clears tokens from database
Circuit Breaker:
- States:
closed(normal) →open(blocking) →halfOpen(testing recovery) - Opens after configurable
failureThreshold(default: 5) consecutive failures - Recovery timeout: configurable (default: 300s / 5 minutes)
Request Deduplication:
- If a refresh is already in progress, subsequent callers await the same
Task - Prevents multiple simultaneous refresh API calls
7. Database Layer
DatabaseManager (Database/DatabaseManager.swift)
SQLite.swift-based database manager using raw SQL (not Expression builders) for all operations.
Database File: {Documents}/WarplyCache_{bundleId}.db
Tables (Schema Version 1):
| Table | Columns | Purpose |
|---|---|---|
requestVariables |
id INTEGER PK, client_id TEXT, client_secret TEXT, access_token TEXT, refresh_token TEXT |
JWT token storage (single row, UPSERT) |
events |
_id INTEGER PK, type TEXT, time TEXT, data BLOB, priority INTEGER |
Offline analytics event queue |
pois |
id INTEGER PK, lat REAL, lon REAL, radius REAL |
Geofencing points of interest |
schema_version |
id INTEGER PK, version INTEGER UNIQUE, created_at TEXT |
Migration tracking |
Schema Migration System:
- Version tracked in
schema_versiontable -
currentDatabaseVersion = 1,supportedVersions = [1] - Migration runs in a transaction for atomicity
- Fresh installs:
createAllTables()+ set version - Upgrades:
migrateDatabase(from:to:)with per-version migration functions
Token Management Methods:
// Basic CRUD
storeTokens(accessToken:refreshToken:clientId:clientSecret:) // UPSERT
getAccessToken() async throws -> String?
getRefreshToken() async throws -> String?
getClientCredentials() async throws -> (clientId: String?, clientSecret: String?)
clearTokens() async throws
// TokenModel integration (preferred)
storeTokenModel(_ tokenModel: TokenModel) async throws
getTokenModel() async throws -> TokenModel?
getValidTokenModel() async throws -> TokenModel? // nil if expired
getTokenModelSync() throws -> TokenModel? // synchronous variant
updateTokensAtomically(from:to:) async throws // race-condition safe
// Encryption-aware (smart routing)
storeTokenModelSmart(_ tokenModel: TokenModel) // auto-chooses encrypted/plain
getTokenModelSmart() async throws -> TokenModel? // auto-chooses decrypted/plain
// Performance
getCachedTokenModel() async throws -> TokenModel? // 60-second memory cache
Concurrency: Uses DispatchQueue(label: "com.warply.database", qos: .utility) for thread safety.
Maintenance Methods: checkDatabaseIntegrity(), vacuumDatabase(), recreateDatabase(), getDatabaseStats()
8. Security Layer
FieldEncryption (Security/FieldEncryption.swift)
Actor-based AES-256-GCM field-level encryption for sensitive token data.
Algorithm: AES-256-GCM (via Apple CryptoKit)
- Provides both encryption AND authentication (AEAD)
- Output format: nonce + ciphertext + authentication tag (combined)
- Keys: 256-bit (32 bytes) from iOS Keychain
Key Methods:
// Low-level (provide your own key)
encryptToken(_ token: String, using key: Data) throws -> Data
decryptToken(_ encryptedData: Data, using key: Data) throws -> String
// High-level (auto key from KeychainManager, with caching)
encryptSensitiveData(_ data: String) async throws -> Data
decryptSensitiveData(_ encryptedData: Data) async throws -> String
// Batch operations (single key retrieval for multiple items)
encryptSensitiveDataBatch(_ dataArray: [String]) async throws -> [Data]
decryptSensitiveDataBatch(_ encryptedDataArray: [Data]) async throws -> [String]
Key Caching: Encryption key cached in memory for 300 seconds (5 min) to reduce Keychain lookups.
Encrypted Token Storage: When encryption is enabled, tokens are stored as Base64-encoded encrypted data in the database. The system detects encrypted vs. plain text by checking if data is valid Base64 but NOT a JWT (JWTs start with "eyJ").
KeychainManager (Security/KeychainManager.swift)
Actor-based iOS Keychain wrapper for hardware-backed encryption key management.
Key Features:
-
Bundle ID isolation: Each client app gets its own Keychain namespace via
com.warply.sdk.{bundleId} -
Auto key generation:
getOrCreateDatabaseKey()creates 256-bit key on first call viaSecRandomCopyBytes -
Security level:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly— key only available when device is unlocked, never backed up
Keychain Item Structure:
- Service:
com.warply.sdk.{bundleId} - Account:
database_encryption_key - Class:
kSecClassGenericPassword - Value: 32 bytes (256-bit AES key)
9. Event System
EventDispatcher (Events/EventDispatcher.swift)
Modern Swift event system designed to eventually replace SwiftEventBus.
Architecture:
- Thread-safe using concurrent
DispatchQueuewith barrier writes - Handlers always dispatched on
DispatchQueue.main - Subscription-based with auto-cleanup via
deinit
Event Types (Protocol-based):
public protocol WarplyEvent {
var name: String { get }
var timestamp: Date { get }
var data: Any? { get }
}
Built-in Event Types:
| Event Struct | Event Name | Triggered When |
|---|---|---|
CampaignsRetrievedEvent |
"campaigns_retrieved" |
Campaigns fetched from server |
CouponsRetrievedEvent |
"coupons_fetched" |
Coupons fetched from server |
MarketPassDetailsEvent |
"market_pass_details_fetched" |
Market pass details received |
CCMSRetrievedEvent |
"ccms_retrieved" |
CCMS campaigns set |
SeasonalsRetrievedEvent |
"seasonals_retrieved" |
Seasonal list set |
DynatraceEvent |
"dynatrace" |
Analytics event |
GenericWarplyEvent |
(any string) | Backward compatibility |
Dual Posting Pattern (CRITICAL): All events in WarplySDK are posted to BOTH systems for backward compatibility:
// Internal helper method in WarplySDK:
private func postFrameworkEvent(_ eventName: String, sender: Any? = nil) {
SwiftEventBus.post(eventName, sender: sender) // Client compatibility
eventDispatcher.post(eventName, sender: sender) // Modern internal
}
Client Subscription API:
// String-based (backward compatible)
WarplySDK.shared.subscribe(to: "campaigns_retrieved") { data in ... }
// Type-safe (modern)
WarplySDK.shared.subscribe(CampaignsRetrievedEvent.self) { event in ... }
Dynatrace Analytics Pattern: Every SDK API call posts a Dynatrace event on success AND failure:
let dynatraceEvent = LoyaltySDKDynatraceEventModel()
dynatraceEvent._eventName = "custom_success_campaigns_loyalty" // or custom_error_*
dynatraceEvent._parameters = nil
self.postFrameworkEvent("dynatrace", sender: dynatraceEvent)
Event naming convention: custom_{success|error}_{operation}_loyalty
9.5 Push Notification System
Architecture (Option A: Host App Forwards)
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.
Push Notification Types
| Type | Location | Purpose |
|---|---|---|
WarplyApplicationState |
WarplySDK.swift |
Enum: .active, .background, .closed
|
WarplyPushHandler |
WarplySDK.swift |
Protocol for custom push handling (action != 0) |
WarplyNotificationPayload |
WarplySDK.swift |
Parsed push payload (action, sessionUuid, message, aps, customData) |
WarplyAPSItem |
WarplySDK.swift |
Parsed APS dictionary (alert, badge, sound, category) |
PushNotificationReceivedEvent |
EventDispatcher.swift |
Event posted when push received |
PushNotificationEngagedEvent |
EventDispatcher.swift |
Event posted when user interacts with push |
Push Notification Detection
Warply push notifications are identified by the "_a" key in the payload:
{"_a": 0, "session_uuid": "abc-123", "aps": {"alert": "New offer!", "badge": 1}}
-
"_a" == 0: Default handling — framework presentsCampaignViewControllerWebView -
"_a" != 0: Custom action — forwarded topushHandlerDelegate
Push Notification Methods
| Method | Purpose |
|---|---|
updateDeviceToken(_:) |
Store token + send comprehensive device info to server |
checkForLoyaltySDKNotification(_:) |
Detect Warply push by "_a" key, auto-handle if found |
handleNotification(_:appState:) |
Route notification by action and app state |
handleLaunchOptions(_:) |
Check launch options for push (cold start) |
handlePendingNotification() |
Process stored notification from cold start |
showPushCampaign(sessionUuid:) |
Present campaign WebView for a session UUID |
resetBadge() |
Reset app icon badge to 0 |
sendDeviceInfo() |
Force send device info to server |
sendDeviceInfoIfNeeded() |
Smart send — only if info changed |
buildDeviceInfo() |
Build comprehensive device info dictionary |
Push Analytics
| Event Name | When Sent | Equivalent ObjC |
|---|---|---|
NB_PushReceived |
When Warply push is received | logUserReceivedPush: |
NB_PushAck |
When user engages with push | logUserEngagedPush: |
Device Info Payload
Sent to /api/async/info/{appUUID}/ via Endpoint.sendDeviceInfo. Contains:
-
device_token,platform("ios"),vendor("apple"),os_version -
unique_device_id(IDFV),ios_system_name,ios_system_version,ios_model -
ios_locale,ios_languages,screen_resolution -
bundle_identifier,app_version,app_build,sdk_version -
developmentflag (DEBUG vs release)
Rich Push Presentation
For action == 0 pushes, the framework presents CampaignViewController internally:
- URL:
{baseURL}/api/session/{session_uuid} - Active state: Shows alert first, then WebView on user confirmation
- Background state: Presents WebView immediately
-
Closed state: Stores as pending, shows via
handlePendingNotification()
10. Configuration System
WarplyConfiguration (Configuration/WarplyConfiguration.swift)
Codable struct with nested sub-configurations for each subsystem.
Sub-configurations:
| Config Struct | Controls | Key Fields |
|---|---|---|
WarplyDatabaseConfig |
Encryption, WAL mode, cache |
encryptionEnabled, dataProtectionClass, enableWALMode, cacheSize, useKeychainForKeys
|
WarplyTokenConfig |
Refresh retry, circuit breaker |
maxRetryAttempts, retryDelays, circuitBreakerThreshold, circuitBreakerResetTime, refreshThresholdMinutes
|
WarplyNetworkConfig |
Timeouts, retries, caching |
requestTimeout, resourceTimeout, maxRetryAttempts, retryDelay, maxConcurrentRequests, enableResponseCaching, enableExponentialBackoff, allowsCellularAccess
|
WarplyLoggingConfig |
Log levels, masking |
logLevel (.none/.error/.warning/.info/.debug/.verbose), enableDatabaseLogging, enableNetworkLogging, enableTokenLogging, maskSensitiveData, enableFileLogging
|
Preset Configurations:
| Preset | Encryption | Log Level | Timeout | Token Retries | Use Case |
|---|---|---|---|---|---|
.development |
Off | .verbose |
15s | 2 | Local dev, full logging |
.production |
On | .warning |
30s | 3 | Release builds |
.testing |
Off | .error |
5s | 1 | Unit/integration tests |
.highSecurity |
On | .error |
20s | 2 | Sensitive environments, WiFi only |
Usage:
// Apply preset
try await WarplySDK.shared.configure(WarplyConfiguration.production)
// Custom config
var config = WarplyConfiguration.development
config.databaseConfig.encryptionEnabled = true
config.tokenConfig.maxRetryAttempts = 5
try await WarplySDK.shared.configure(config)
// Per-component configuration
try await WarplySDK.shared.configureDatabaseSecurity(dbConfig)
try await WarplySDK.shared.configureTokenManagement(tokenConfig)
try await WarplySDK.shared.configureLogging(loggingConfig)
try await WarplySDK.shared.configureNetwork(networkConfig)
Validation: Each config struct has a validate() method that throws ConfigurationError with specific error types and recovery suggestions.
Serialization: WarplyConfiguration is Codable — can be saved/loaded as JSON via toJSONData(), fromJSONData(), saveToFile(at:), loadFromFile(at:).
11. Data Models
All models use dictionary-based initialization (init(dictionary: [String: Any])) to parse JSON responses.
Key Model Classes
| Model | File | Key Fields | Notes |
|---|---|---|---|
CampaignItemModel |
Campaign.swift |
session_uuid, index_url, title, subtitle, offer_category, logo_url, couponset, _sorting, _type, _carousel, _coupon_availability, _banner_img, _filter, _start_date, _end_date
|
class (not struct), mutable properties via _fieldName pattern |
LoyaltyContextualOfferModel |
Campaign.swift |
_loyaltyCampaignId, _eligibleAssets, _offerName, _sessionId
|
CCMS contextual offers |
CouponItemModel |
Coupon.swift |
couponset_uuid, name, coupon, barcode, status (1=active, 0=redeemed), expiration, redeemed_date, couponset_data, merchant_details
|
Status: 1=active, 0=redeemed |
CouponSetItemModel |
Coupon.swift |
_uuid, _name, _description, _img_preview, _expiration, _discount, _discount_type, _final_price, _couponset_type, _merchant_uuid, _terms, _shop_availability
|
Expiration stored as raw date string "yyyy-MM-dd HH:mm" |
TokenModel |
TokenModel.swift |
accessToken, refreshToken, clientId, clientSecret, expirationDate, isExpired, shouldRefresh, isValid, canRefresh
|
struct, JWT auto-parsing, 5-min proactive refresh window |
MerchantModel |
Merchant.swift | Standard merchant fields | Location-aware merchant data |
ProfileModel |
ProfileModel.swift | User profile fields | Consumer data |
CardModel |
CardModel.swift | Card number, issuer, holder, expiration | Credit card data |
TransactionModel |
TransactionModel.swift |
transactionDate, transaction details |
Sorted by date descending |
PointsHistoryModel |
PointsHistoryModel.swift |
entryDate, points entries |
Sorted by date descending |
MarketPassDetailsModel |
Market.swift | Supermarket pass details | Market/supermarket feature |
RedeemedSMHistoryModel |
Coupon.swift |
_totalRedeemedValue, _redeemedCouponList
|
Supermarket coupon redemption history |
ArticleModel |
ArticleModel.swift | Content/article fields | Carousel content |
MerchantCategoryModel |
MerchantCategoryModel.swift | Category fields | Merchant categorization |
VerifyTicketResponseModel |
Response.swift |
getStatus (1=success) |
Generic API response wrapper |
GenericResponseModel |
Response.swift | Generic response fields | Flexible response wrapper |
LoyaltySDKDynatraceEventModel |
Models.swift |
_eventName, _parameters
|
Dynatrace analytics event payload |
LoyaltyGiftsForYouPackage |
Models.swift/Gifts.swift | Seasonal gift data | Seasonal promotions |
Model Property Accessor Pattern
Models use a underscore-prefixed computed property pattern for public access:
// Private stored property
private var category_title: String?
// Public accessor (getter + setter)
public var _category_title: String? {
get { return self.category_title }
set(newValue) { self.category_title = newValue }
}
Some newer models use read-only accessors:
public var _uuid: String { get { return self.uuid ?? "" } }
Date Handling in Models
-
CampaignItemModel: Raw strings from server, formatted via
formattedStartDate(format:)/formattedEndDate(format:) -
CouponSetItemModel: Expiration as "yyyy-MM-dd HH:mm", formatted via
formattedExpiration(format:),_expiration_formatted(dd/MM/yyyy) -
CouponItemModel: Dates converted during init to "dd/MM/yyyy" display format,
redeemed_dateasDateobject - Input formats tried:
"yyyy-MM-dd HH:mm:ssZZZZZ","yyyy-MM-dd HH:mm:ss","yyyy-MM-dd HH:mm:ss.SSSSSS"
URL Cleaning
Campaign model has a static helper that cleans escaped URLs:
private static func cleanEscapedUrl(_ url: String?) -> String? {
return url?.replacingOccurrences(of: "\\/", with: "/")
}
Applied to: index_url, logo_url, banner_img, coupon_img, campaign_url, banner_img_mobile
12. UI Layer
Pre-built Screens
All screens use XIB-based layouts (not SwiftUI) with Bundle.frameworkBundle for resource loading.
| Screen | Storyboard ID | Purpose |
|---|---|---|
MyRewardsViewController |
— (XIB) | Main rewards dashboard with campaigns, offers, banners |
ProfileViewController |
— (XIB) | User profile with coupons, filters, questionnaires |
CouponViewController |
— (XIB) | Single coupon detail view |
CouponsetViewController |
— (XIB) | Coupon set detail view |
CampaignViewController |
"CampaignViewController" (Storyboard) |
WebView for campaign URLs |
CampaignViewController (WebView)
Used to display campaign web content. Configured via:
let vc = storyboard.instantiateViewController(withIdentifier: "CampaignViewController") as! CampaignViewController
vc.campaignUrl = url // Campaign web URL
vc.params = params // JSON string with tokens and config
vc.showHeader = false // Header visibility
vc.isPresented = true/false // Modal vs push navigation
Navigation Pattern
Screens support both push (navigation controller) and modal presentation:
if let navigationController = controller.navigationController {
vc.isPresented = false
navigationController.pushViewController(vc, animated: true)
} else {
vc.isPresented = true
vc.modalPresentationStyle = .fullScreen
controller.present(vc, animated: true, completion: nil)
}
Cell Architecture
Table/Collection view cells are XIB-based, each in its own directory:
-
MyRewardsBannerOfferCollectionViewCell— Banner offer in collection view -
MyRewardsOfferCollectionViewCell— Standard offer in collection view -
MyRewardsBannerOffersScrollTableViewCell— Horizontal scrolling banner offers -
MyRewardsOffersScrollTableViewCell— Horizontal scrolling offers -
MyRewardsProfileInfoTableViewCell— Profile info header -
ProfileCouponTableViewCell— Coupon row in profile -
ProfileCouponFiltersTableViewCell— Filter chips for coupons -
ProfileFilterCollectionViewCell— Individual filter chip -
ProfileHeaderTableViewCell— Profile section header -
ProfileQuestionnaireTableViewCell— Questionnaire entry
Fonts
Custom fonts bundled: PingLCG family (Bold, Light, Regular) in .otf format.
Bundle Resource Loading
Framework resources loaded via Bundle.frameworkBundle (defined in XIBLoader.swift/MyEmptyClass.swift), which resolves the correct bundle whether using CocoaPods resource bundles or SPM.
13. API Patterns & Conventions
Dual API Surface (Callback + Async/Await)
Every public API method has two variants:
// Callback-based (original pattern)
public func getCoupons(language: String, completion: @escaping ([CouponItemModel]?) -> Void, failureCallback: @escaping (Int) -> Void)
// Async/await wrapper (bridges to callback version)
public func getCoupons(language: String) async throws -> [CouponItemModel] {
return try await withCheckedThrowingContinuation { continuation in
getCoupons(language: language, completion: { coupons in
if let coupons = coupons {
continuation.resume(returning: coupons)
} else {
continuation.resume(throwing: WarplyError.networkError)
}
}, failureCallback: { errorCode in
continuation.resume(throwing: WarplyError.unknownError(errorCode))
})
}
}
Standard Method Body Pattern
Every API method follows this exact pattern:
- Create
Task { }block - Call
networkService.requestRaw(endpoint)or specific convenience method - Parse response inside
await MainActor.run { } - Check status (
response["status"] as? Int == 1orresponse["MAPP_*-status"] as? Int == 1) - Post Dynatrace success/error event
- Call completion handler on main thread
Response Status Checking
Different response structures have different status locations:
// Standard context responses (after transformation):
response["status"] as? Int == 1
response["MAPP_CAMPAIGNING-status"] as? Int == 1
response["MAPP_COUPON-status"] as? Int == 1
response["MAPP_SHOPS-status"] as? Int == 1
// VerifyTicketResponseModel:
tempResponse.getStatus == 1
Language Handling Pattern
Methods with optional language default to applicationLocale:
public func getCoupons(language: String? = nil, ...) {
let finalLanguage = language ?? self.applicationLocale
// use finalLanguage
}
Complete Public API Reference
Authentication:
configure(appUuid:merchantId:environment:language:)-
initialize(callback:)/initialize() async throws -
deiLogin(email:completion:failureCallback:)/deiLogin(email:) async throws verifyTicket(guid:ticket:completion:)-
getCosmoteUser(guid:completion:)/getCosmoteUser(guid:) async throws logout(completion:)
Campaigns:
-
getCampaigns(language:filters:completion:failureCallback:)/getCampaigns(language:filters:) async throws -
getCampaignsPersonalized(language:filters:completion:failureCallback:)/ async variant -
getSingleCampaign(sessionUuid:completion:)/ async variant -
getSupermarketCampaign(language:completion:)/ async variant
Coupons:
-
getCoupons(language:completion:failureCallback:)/ async variant getCouponsUniversal(language:completion:failureCallback:)-
getCouponSets(language:completion:failureCallback:)/ async variant -
getAvailableCoupons(completion:)/ async variant -
validateCoupon(_:completion:)/ async variant -
redeemCoupon(productId:productUuid:merchantId:completion:)/ async variant
User Management:
-
changePassword(oldPassword:newPassword:completion:)/ async variant -
resetPassword(email:completion:)/ async variant -
requestOtp(phoneNumber:completion:)/ async variant -
getProfile(completion:failureCallback:)/ async variant
Cards:
-
addCard(cardNumber:cardIssuer:cardHolder:expirationMonth:expirationYear:completion:)/ async variant -
getCards(completion:)/ async variant -
deleteCard(token:completion:)/ async variant
Transactions:
-
getTransactionHistory(productDetail:completion:)/ async variant -
getPointsHistory(completion:)/ async variant
Merchants:
-
getMerchants(language:categories:defaultShown:center:tags:uuid:distance:parentUuids:completion:failureCallback:)/ async variant -
getMerchantCategories(language:completion:failureCallback:)/ async variant
Content:
-
getArticles(language:categories:completion:failureCallback:)/ async variant
Market/Supermarket:
-
getMarketPassDetails(completion:failureCallback:)/ async variant -
getRedeemedSMHistory(language:completion:failureCallback:)/ async variant -
openSupermarketsMap(_:)— presents map WebView -
openSuperMarketsFlow(_:)— presents supermarket campaign
State:
-
setCampaignList(_:)/getCampaignList()/getAllCampaignList() -
setCouponList(_:)/getCouponList() -
setOldCouponList(_:)/getOldCouponList() -
setCouponSetList(_:)/getCouponSetList() -
setMerchantList(_:)/getMerchantList() -
setCarouselList(_:)/getCarouselList() -
setSupermarketCampaign(_:)/getSupermarketCampaign() -
setMarketPassDetails(_:)/getMarketPassDetails() -
setCCMSLoyaltyCampaigns(campaigns:)/getCCMSLoyaltyCampaigns() -
setSeasonalList(_:)/getSeasonalList()
Utilities:
-
constructCampaignUrl(_:)/constructCampaignParams(_:)/constructCampaignParams(campaign:isMap:) constructCcmsUrl(_:)showDialog(_:_:_:)getMarketPassMapUrl()-
getNetworkStatus()— returns 1 (connected) or 0 (disconnected) updateRefreshToken(accessToken:refreshToken:)updateDeviceToken(_:)
14. Coding Standards & Conventions
Naming Conventions
-
Files: PascalCase matching the primary type (
CampaignItemModel→Campaign.swift) -
Classes/Structs: PascalCase (
CampaignItemModel,NetworkService) -
Private properties: snake_case for model fields (
category_title,banner_img) -
Public accessors: underscore prefix (
_category_title,_banner_img) - Constants: PascalCase for enum cases, camelCase for static properties
-
Endpoint enum cases: camelCase (
getCampaigns,verifyTicket)
Logging Convention
Emoji-prefixed print statements throughout the codebase:
✅ Success operations
❌ Errors/failures
⚠️ Warnings
🔴 Critical errors
🟡 Token should refresh
🟢 Token valid
🔐 Token/auth operations
🔒 Encryption operations
🔓 Decryption operations
🔑 Key operations
🗄️ Database operations
🗑️ Deletion/clearing
📊 Analytics/stats
📤 Request logging
📥 Response logging
🔗 URL operations
🔄 Refresh/retry/update
🚨 Circuit breaker
🧪 Test operations
💡 Suggestions
⏱️ Timing
🔧 Configuration
🏭 Production
🚦 Request queue
⚛️ Atomic operations
Access Control
-
public: All types, methods, and properties intended for client use -
public final class: WarplySDK, NetworkService (prevent subclassing) -
private/internal: Implementation details, state management -
actor: All security-critical components (TokenRefreshManager, FieldEncryption, KeychainManager)
Error Handling Convention
- Errors are logged immediately at the point of occurrence
- Every error posts a Dynatrace analytics event
- Callback methods return
nilon failure, async methodsthrow -
handleError()private method centralizes error conversion and logging
UserDefaults Keys
-
appUuidUD— App UUID -
merchantIdUD— Merchant ID -
languageUD— Language -
isDarkModeEnabledUD— Dark mode flag -
environmentUD— "development" or "production" -
trackersEnabled— Analytics tracking flag -
device_token— Push notification device token
Configuration Static Properties
Configuration.baseURL // Current base URL
Configuration.host // Current host
Configuration.errorDomain // Error domain (same as host)
Configuration.merchantId // Merchant ID
Configuration.language // "el" or "en"
Configuration.verifyURL // Verification URL
15. Dependencies & Distribution
External Dependencies
| Dependency | Version | Purpose |
|---|---|---|
SQLite.swift |
0.12.2 (exact) |
SQLite database access (raw SQL, not ORM) |
SwiftEventBus |
5.0.0+ |
Event bus for client-facing events (backward compatibility) |
RSBarcodes_Swift |
5.2.0+ |
Barcode generation for coupon display |
Distribution Methods
CocoaPods (SwiftWarplyFramework.podspec):
pod 'SwiftWarplyFramework', '~> 2.4.0'
- Resource bundles:
ResourcesBundlecontaining.xcassetsand.otffonts - Source files:
SwiftWarplyFramework/SwiftWarplyFramework/**/*.{h,m,swift,xib,storyboard} - Excludes: build artifacts, xcodeproj files
Swift Package Manager (Package.swift):
.package(url: "https://git.warp.ly/open-source/warply_sdk_framework.git", from: "2.4.0")
- Target path:
SwiftWarplyFramework/SwiftWarplyFramework - Excludes:
Helpers/WarplyReactMethods.h,Helpers/WarplyReactMethods.m,Info.plist - Resources:
.process()for xcassets, fonts, storyboards, XIBs
System Frameworks Used
-
Foundation— Core types -
UIKit— UI components -
Network—NWPathMonitorfor connectivity -
CryptoKit— AES-256-GCM encryption -
Security— Keychain Services -
CommonCrypto— SHA256 hashing for loyalty-signature header
16. Common Tasks & Recipes
Adding a New API Endpoint
-
Add endpoint case in
Endpoints.swift:case myNewEndpoint(param1: String, param2: Int) -
Add path in the
pathcomputed property:case .myNewEndpoint: return "/oauth/{appUUID}/context" // or appropriate path -
Add method (most are POST):
case .myNewEndpoint: return .POST -
Add parameters with correct wrapper key:
case .myNewEndpoint(let param1, let param2): return ["consumer_data": ["action": "my_action", "param1": param1, "param2": param2]] -
Set auth type in
authType:case .myNewEndpoint: return .bearerToken // or .standard -
Set category in
category:case .myNewEndpoint: return .authenticatedContext -
Add method in
WarplySDK.swift(both callback + async variants):// Callback version public func myNewMethod(param1: String, param2: Int, completion: @escaping (MyModel?) -> Void, failureCallback: @escaping (Int) -> Void) { Task { do { let endpoint = Endpoint.myNewEndpoint(param1: param1, param2: param2) let response = try await networkService.requestRaw(endpoint) await MainActor.run { if response["status"] as? Int == 1 { let dynatraceEvent = LoyaltySDKDynatraceEventModel() dynatraceEvent._eventName = "custom_success_my_new_method_loyalty" dynatraceEvent._parameters = nil self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) // Parse and return result completion(parsedResult) } else { let dynatraceEvent = LoyaltySDKDynatraceEventModel() dynatraceEvent._eventName = "custom_error_my_new_method_loyalty" dynatraceEvent._parameters = nil self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) failureCallback(-1) } } } catch { await MainActor.run { self.handleError(error, context: "myNewMethod", endpoint: "myNewEndpoint", failureCallback: failureCallback) } } } }
// Async variant public func myNewMethod(param1: String, param2: Int) async throws -> MyModel { return try await withCheckedThrowingContinuation { continuation in myNewMethod(param1: param1, param2: param2, completion: { result in if let result = result { continuation.resume(returning: result) } else { continuation.resume(throwing: WarplyError.networkError) } }, failureCallback: { errorCode in continuation.resume(throwing: WarplyError.unknownError(errorCode)) }) } }
### Adding a New Data Model
1. Create file in `models/` directory
2. Use `class` (not struct) for consistency with existing models
3. Implement `init(dictionary: [String: Any])` for JSON parsing
4. Use `_fieldName` accessor pattern for public properties
5. Handle optional fields with `?? ""` or `?? 0` defaults
6. Clean URLs with `cleanEscapedUrl()` if applicable
### Adding a New Database Table
1. Increment `currentDatabaseVersion` in `DatabaseManager.swift`
2. Add new version to `supportedVersions` array
3. Create migration function `performMigrationToV{N}()`
4. Add case to `performMigration(to:)` switch
5. Create table using raw SQL (`try db.execute(...)`)
### Adding a New Event Type
1. Create struct conforming to `WarplyEvent` in `EventDispatcher.swift`
2. Add convenience method in `EventDispatcher` extension
3. Add `InternalFrameworkEvent` case in `WarplySDK.swift` if it's internal
4. Post using `postFrameworkEvent()` or `postInternalFrameworkEvent()`
5. **Always post to both SwiftEventBus and EventDispatcher**
### Adding a New Configuration Option
1. Add property to appropriate config struct (`WarplyDatabaseConfig`, etc.)
2. Add validation in `validate()` method
3. Add to `getSummary()` return dictionary
4. Add to the preset configurations (`.development`, `.production`, etc.)
5. Ensure Codable compliance (add to CodingKeys if needed)
## 17. Error Handling
### Error Types
**`WarplyError`** (public, SDK-level):
| Case | Code | Description |
|------|------|-------------|
| `.networkError` | -1000 | Generic network failure |
| `.invalidResponse` | -1001 | Invalid/unparseable response |
| `.authenticationFailed` | 401 | Auth failure |
| `.dataParsingError` | -1002 | JSON parsing failure |
| `.serverError(Int)` | (code) | HTTP server error |
| `.noInternetConnection` | -1009 | No network connectivity |
| `.requestTimeout` | -1001 | Request timed out |
| `.unknownError(Int)` | (code) | Catch-all |
**`NetworkError`** (public, network-level):
| Case | Code | Description |
|------|------|-------------|
| `.invalidURL` | -1001 | Bad URL construction |
| `.noData` | -1002 | Empty response body |
| `.decodingError(Error)` | -1003 | JSON decode failure |
| `.serverError(Int)` | (code) | HTTP 4xx/5xx |
| `.networkError(Error)` | (varies) | URLSession error |
| `.authenticationRequired` | 401 | Token needed/expired |
| `.invalidResponse` | -1004 | Non-JSON response |
**`DatabaseError`** (internal):
- `.connectionNotAvailable` — DB not initialized
- `.tableCreationFailed` — Schema creation error
- `.queryFailed(String)` — SQL execution error
- `.migrationFailed(String)` — Version migration error
- `.corruptedDatabase` — Integrity check failed
**`TokenRefreshError`** (public):
- `.noTokensAvailable` — No tokens in DB
- `.invalidRefreshToken` — Bad refresh token
- `.networkError(Error)` — Network failure during refresh
- `.serverError(Int)` — Server error during refresh
- `.maxRetriesExceeded` — All retry attempts failed
- `.refreshInProgress` — Concurrent refresh attempt
- `.invalidResponse` — Bad refresh response
**`EncryptionError`** (internal):
- `.invalidKey` (4001), `.encryptionFailed` (4002), `.decryptionFailed` (4003), `.invalidData` (4004), `.keyGenerationFailed` (4005)
**`ConfigurationError`** (public):
- `.invalidRefreshThreshold`, `.invalidRetryAttempts`, `.retryDelaysMismatch`, `.invalidTimeout`, `.invalidCacheSize`, `.invalidCircuitBreakerThreshold`, etc.
- All include `errorDescription` and `recoverySuggestion`
### Error Flow
1. Network/DB error occurs → caught in `catch` block
2. `handleError()` called → converts to `WarplyError` via `convertNetworkError()`
3. Error logged with context via `logError()`
4. Dynatrace error event posted via `postErrorAnalytics()`
5. `failureCallback(errorCode)` called on main thread
## 18. Testing
### Mock Support
**`MockNetworkService`** (available in `#if DEBUG`):
```swift
let mockService = MockNetworkService()
mockService.setMockResponse(["status": 1, "result": [...]], for: .getCampaigns(...))
mockService.setShouldFail(true, with: .networkError(...))
Test Configuration
Use WarplyConfiguration.testing preset:
- Encryption disabled for simpler test setup
- 5-second request timeout for fast tests
- Single retry attempt
- 0.1s retry delay
- Analytics and auto-registration disabled
SQLite Test
let result = await WarplySDK.shared.testSQLiteConnection()
// Returns true if SQLite.swift is working correctly
Database Diagnostics
// Check integrity
let isIntact = try await DatabaseManager.shared.checkDatabaseIntegrity()
// Get stats
let stats = try await DatabaseManager.shared.getDatabaseStats()
// Returns (tokensCount: Int, eventsCount: Int, poisCount: Int)
// Get version info
let versionInfo = try await DatabaseManager.shared.getDatabaseVersionInfo()
// Token validation
let validationResult = try await DatabaseManager.shared.validateStoredTokens()
// Token status
let status = try await DatabaseManager.shared.getTokenStatus()
Configuration Diagnostics
let summary = WarplySDK.shared.getConfigurationSummary()
let config = WarplySDK.shared.getCurrentConfiguration()
Encryption Diagnostics
let encryptionStats = try await DatabaseManager.shared.getEncryptionStats()
let isEncryptionWorking = await DatabaseManager.shared.validateEncryptionSetup()
let keychainDiag = await KeychainManager.shared.getDiagnosticInfo()
Quick Reference Card
| What | Where | Key |
|---|---|---|
| All public APIs | Core/WarplySDK.swift |
WarplySDK.shared |
| API routes | Network/Endpoints.swift |
Endpoint enum |
| HTTP client | Network/NetworkService.swift |
NetworkService.shared |
| Token refresh | Network/TokenRefreshManager.swift |
TokenRefreshManager.shared |
| Token storage | Database/DatabaseManager.swift |
DatabaseManager.shared |
| Token model | models/TokenModel.swift |
TokenModel struct |
| Encryption | Security/FieldEncryption.swift |
FieldEncryption.shared |
| Keychain | Security/KeychainManager.swift |
KeychainManager.shared |
| Events | Events/EventDispatcher.swift |
EventDispatcher.shared |
| Config | Configuration/WarplyConfiguration.swift |
WarplyConfiguration struct |
| Campaigns | models/Campaign.swift |
CampaignItemModel class |
| Coupons | models/Coupon.swift |
CouponItemModel class |
Critical Rules for AI Agents:
- Always maintain dual event posting (SwiftEventBus + EventDispatcher)
- Always post Dynatrace analytics on both success and failure paths
- Always provide both callback AND async/await variants for public methods
-
Always dispatch completion callbacks on main thread via
MainActor.run - Use raw SQL in DatabaseManager (not SQLite.swift Expression builders)
- Never log sensitive data (tokens, passwords, card numbers) — use masking
- All security components must be actors (not classes)
-
Models use
classnotstructwith_fieldNameaccessor pattern - Token storage is in database only — never in UserDefaults
- All response parsing must handle the context transformation pattern