### Install SDK from Git Branch via Cocoapods Source: https://github.com/customerio/customerio-ios/blob/main/docs/SUPPORT.md Replace existing Customer.io pod entries in your Podfile with these to install from a specific Git branch. Ensure 'CustomerIOCommon' and 'CustomerIOMessagingPush' are included. ```ruby # Replace these: pod 'CustomerIO/Tracking', "~> 2.0.0" pod 'CustomerIO/MessagingPushFCM', "~> 2.0.0" # With these: branch_name = 'demo-bug-fix' pod 'CustomerIOCommon', :git => 'https://github.com/customerio/customerio-ios.git', :branch => branch_name pod 'CustomerIOMessagingPush', :git => 'https://github.com/customerio/customerio-ios.git', :branch => branch_name pod 'CustomerIODataPipelines', :git => 'https://github.com/customerio/customerio-ios.git', :branch => branch_name # If using FCM for push notifications, add the FCM module: pod 'CustomerIOMessagingPushFCM', :git => 'https://github.com/customerio/customerio-ios.git', :branch => branch_name # Or, use the APN push notifications module: pod 'CustomerIOMessagingPushAPN', :git => 'https://github.com/customerio/customerio-ios.git', :branch => branch_name # Do not forget to include "CustomerIOCommon" and "CustomerIOMessagingPush" as shown above. They are required # when installing branch builds. ``` -------------------------------- ### Install Lefthook Git Hooks Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEVELOPMENT.md Install the lefthook tool using Homebrew and then install the git hooks for the project. This ensures consistent commit behavior. ```bash $ brew install lefthook $ lefthook install SYNCING SERVED HOOKS: pre-commit, prepare-commit-msg ``` -------------------------------- ### Install Lefthook Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Install the lefthook Git hook manager using Homebrew. This tool helps automate tasks before commits and pushes. ```bash brew install lefthook ``` -------------------------------- ### Configure Lefthook Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Install the Git hooks managed by lefthook into your repository. Ensure this is done before committing. ```bash lefthook install ``` -------------------------------- ### Run Specific Tests with xcodebuild Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Execute tests from the command line without rebuilding. This example targets a specific test class within the MessagingPushTests suite. Replace the simulator ID with your target. ```bash xcodebuild test-without-building -workspace ./.swiftpm/xcode/package.xcworkspace -destination 'platform=iOS Simulator,id=A7D4BD50-572C-491A-9713-4A3B9DB04B44' -scheme Customer.io-Package -only-testing:MessagingPushTests/CioProviderAgnosticAppDelegateTests ``` -------------------------------- ### Identify with Background Queue Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/BACKGROUND-QUEUE.md Example of the simplified SDK design using the background queue, where the SDK handles result processing internally. ```swift // no need to handle the result! SDK does it for you! CustomerIO.shared.identify("Dana") ``` -------------------------------- ### Build Code for Testing with xcodebuild Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Rebuild the project for testing, especially after initial setup or code changes. Replace SIMULATOR_ID with your target simulator's ID. ```bash xcodebuild build-for-testing -destination 'platform=iOS Simulator,id=SIMULATOR_ID' -allowProvisioningUpdates -scheme Customer.io-Package -workspace ./.swiftpm/xcode/package.xcworkspace ``` -------------------------------- ### Asynchronous Identify with Callback Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/BACKGROUND-QUEUE.md Example of an older asynchronous SDK design requiring manual result handling. ```swift CustomerIO.shared.identify("Dana") { result in // handle the result. If success or error switch (result) { ... } } ``` -------------------------------- ### Background Queue - Processing Logs Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/QA.md These logs show the background queue actively processing tasks. They indicate when the queue starts running, the tasks remaining, and the success or failure of individual tasks. ```text queue timer: now running queue queue run request sent queue tasks left to run: 5 out of 5 queue next task to run: 38383838-383838383-49490495, identifyProfile, IdentifyProfile(identifier: "dana900") queue task 38383838-383838383-49490495 ran successfully queue deleting task 38383838-383838383-49490495 queue deleting task 38383838-383838383-49490495 success: true ``` -------------------------------- ### Compile a Sample App Source: https://github.com/customerio/customerio-ios/blob/main/Apps/README.md Use this command to set up a specific sample application for compilation. Replace 'CocoaPods-FCM' with the name of the desired app directory. ```bash make setup_sample_app app=CocoaPods-FCM ``` -------------------------------- ### List Available Simulators Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Command to list all available simulators on your system. This is a prerequisite before building or testing. ```bash xcrun simctl list devices available ``` -------------------------------- ### Clone Repository Source: https://github.com/customerio/customerio-ios/blob/main/Apps/VisionOS/README.md Clone the customerio-ios repository to access the sample app. This is the first step to running the application. ```bash git clone https://github.com/customerio/customerio-ios ``` -------------------------------- ### Initialize SDK with Logging Enabled Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/QA.md Use this code to initialize the Customer.io SDK in your mobile application. Ensure that SDK logging is enabled for QA purposes. ```swift CustomerIO.initialize(cdpApiKey: Env.cdpApiKey) { config in config.autoTrackDeviceAttributes = true } ``` -------------------------------- ### Build Whole SDK Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Builds the entire Customer.io iOS SDK. Replace 'SIMULATOR_ID' with a valid simulator ID. ```bash xcodebuild -scheme Customer.io-Package -configuration Debug -workspace ./.swiftpm/xcode/package.xcworkspace -destination 'platform=iOS Simulator,id=SIMULATOR_ID' -allowProvisioningUpdates build ``` -------------------------------- ### Format Code Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Run this command to format the code according to the project's style guidelines. It should be executed before linting. ```bash make format ``` -------------------------------- ### Check Push Interaction Type Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEEPLINKS-AND-NOTIFICATIONS.md Determines if the user clicked on the push notification or swiped it away. No setup required beyond standard notification handling. ```swift var didClickOnPush: Bool { actionIdentifier == UNNotificationDefaultActionIdentifier } var didSwipeAwayPush: Bool { actionIdentifier == UNNotificationDismissActionIdentifier } ``` -------------------------------- ### Initialize Data Pipeline Module Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Configures and initializes the Data Pipeline module using a builder pattern. Ensure you replace 'your-site-id' and 'your-api-key' with your actual credentials. ```swift let config = DataPipelineConfigBuilder() .siteId("your-site-id") .apiKey("your-api-key") .build() DataPipeline.initialize(moduleConfig: config) DataPipeline.shared.identify(userId: "customer-id") ``` -------------------------------- ### Enable Debug Logging During SDK Initialization Source: https://github.com/customerio/customerio-ios/blob/main/docs/SUPPORT.md Enable debug logs by setting the logLevel to .debug during CustomerIO.initialize. This is recommended for development and testing phases. ```swift CustomerIO.initialize(siteId: "YOUR SITE ID", apiKey: "YOUR API KEY", region: Region.US) { config in config.logLevel = .debug } ``` -------------------------------- ### Message Lifecycle - Queue Fetch to Store Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Illustrates the flow from fetching the user queue to storing and processing messages within the SDK's state. ```swift QueueManager.fetchUserQueue(state:) │ ▼ POST /api/v4/users → [InAppMessageResponse] │ ▼ Convert to [Message]; separate anonymous from identified messages │ ├── Anonymous messages → AnonymousMessageManager.updateMessagesLocalStore(...) │ └── Returns eligible anonymous messages (frequency/delay/dismiss rules; 60-min cache) │ └── Identified messages + eligible anonymous messages │ ▼ dispatch(.processMessageQueue(messages:)) │ ▼ State: messagesInQueue = Set ``` -------------------------------- ### CI Build Matrix Configuration Source: https://github.com/customerio/customerio-ios/blob/main/Apps/README.md Add a new entry to the 'sample-app' matrix in the CI workflow file to include your new sample app in automated builds. The value should be the directory name of your new app within the 'Apps/' directory. ```yaml ... matrix: # Use a matrix allowing us to build multiple apps in parallel. Just add an entry to the matrix and it will build! sample-app: - "Foo" ``` ```yaml ... matrix: # Use a matrix allowing us to build multiple apps in parallel. Just add an entry to the matrix and it will build! sample-app: - "CocoaPods-FCM" ``` -------------------------------- ### Build Single Module Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Use this command to build a single module within the workspace. Ensure you replace SIMULATOR_ID with the appropriate simulator identifier. ```bash xcodebuild -scheme MessagingPushAPN -configuration Debug -workspace ./.swiftpm/xcode/package.xcworkspace -destination 'platform=iOS Simulator,id=SIMULATOR_ID' -allowProvisioningUpdates build ``` -------------------------------- ### Background Queue File System Structure Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/BACKGROUND-QUEUE.md Illustrates the directory structure for storing queue inventory and individual task JSON files on the device. ```plaintext Documents/ io.customer/ queue/ inventory.json tasks/ 3838383949849493939393.json 2939929292001919202002.json ``` -------------------------------- ### Generate Boilerplate Code Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEVELOPMENT.md Run this command to generate necessary boilerplate code, such as dependency injection graphs and mocks. This is often required after modifying SDK code to resolve compile-time errors. ```bash make generate ``` -------------------------------- ### Configure Foreground Push Display Options Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEEPLINKS-AND-NOTIFICATIONS.md Sets the presentation options for push notifications when the app is in the foreground. Use `completionHandler` with the desired options. ```swift // showPushAppInForeground == true completionHandler([.list, .banner, .badge, .sound]) ``` ```swift // showPushAppInForeground == false completionHandler([]) ``` -------------------------------- ### Run All Tests in a Suite with xcodebuild Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Execute all tests within a specified test suite from the command line. Ensure the destination simulator ID is correctly set. ```bash xcodebuild test-without-building -workspace ./.swiftpm/xcode/package.xcworkspace -destination 'platform=iOS Simulator,id=A7D4BD50-572C-491A-9713-4A3B9DB04B44' -scheme Customer.io-Package -only-testing:MessagingPushTests ``` -------------------------------- ### Initialize In-App Messaging Module Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Initialize the In-App Messaging module after the main CustomerIO SDK. Optionally, register a listener for message lifecycle events. ```swift MessagingInApp .initialize(withConfig: MessagingInAppConfigBuilder(siteId: "your-site-id", region: .US).build()) .setEventListener(self) // optional — register lifecycle callbacks ``` -------------------------------- ### Test Single File with Xcodebuild Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md This command allows testing a specific file without rebuilding the entire project. Replace SIMULATOR_ID and TestSuiteName/TestClassName with your specific values. ```bash xcodebuild test-without-building -workspace ./.swiftpm/xcode/package.xcworkspace -destination 'platform=iOS Simulator,id=SIMULATOR_ID' -scheme Customer.io-Package -only-testing:TestSuiteName/TestClassName ``` -------------------------------- ### Configure In-App Messaging Behavior Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Set screen view tracking and automatic UIKit screen view detection during SDK initialization. Use `.inApp` for route matching without analytics, and `true` to automatically track UIKit screen views. ```swift CustomerIO.initialize(withConfig: SDKConfigBuilder(cdpApiKey: "...") .screenViewUse(screenView: .inApp) // route matching only, no analytics tracking .autoTrackUIKitScreenViews(enabled: true) // automatic UIKit route detection .build() ) ``` -------------------------------- ### Deep Link Resolution Strategy Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEEPLINKS-AND-NOTIFICATIONS.md Illustrates the three-tier strategy for handling incoming deep links. It shows the flow from checking registered callbacks to using UIKit wrappers and finally UIApplication.shared.open. ```text URL received │ ▼ Tier 1: deepLinkCallback registered? ├── Yes → call deepLinkCallback(url) │ ├── returns true → handled; stop ✓ │ └── returns false → continue to Tier 2 └── No → continue to Tier 2 │ ▼ Tier 2: UIKitWrapper.continueNSUserActivity(webpageURL: url) ├── Calls application(_:continue:restorationHandler:) on the host app ├── Only works for http/https URLs (NSUserActivity.webpageURL constraint) ├── host app returns true → handled; stop ✓ └── host app returns false → continue to Tier 3 │ ▼ Tier 3: UIKitWrapper.open(url: url) └── UIApplication.shared.open(url:) — system call (browser, app scheme, etc.) ✓ ``` -------------------------------- ### Lint Code Source: https://github.com/customerio/customerio-ios/blob/main/CLAUDE.md Execute this command to lint the code, checking for style and potential issues. ```bash make lint ``` -------------------------------- ### Build In-App Messaging Configuration Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Create a configuration object for the In-App Messaging module using the builder pattern. Specify the workspace Site ID and the API region. ```swift let config = MessagingInAppConfigBuilder(siteId: "abc123", region: .US).build() MessagingInApp.initialize(withConfig: config) ``` -------------------------------- ### Initialize In-App Messaging with Dictionary Config Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Initialize the In-App Messaging module using a dictionary-based configuration, commonly used by cross-platform SDKs like React Native and Flutter. Ensure the 'inApp.siteId' and 'region' keys are correctly provided. This method can throw errors for missing site IDs or malformed configurations. ```swift // Keys: "inApp.siteId" and top-level "region" let config = try MessagingInAppConfigBuilder.build(from: [ "region": "EU", "inApp": ["siteId": "abc123"] ]) ``` -------------------------------- ### Upload Events Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md This endpoint is used to upload batched events to Customer.io. It supports JSON content type and requires basic authentication. ```APIDOC ## POST /b ### Description Uploads a batch of events to the Customer.io API. ### Method POST ### Endpoint `https:///b` Where `` can be `cdp.customer.io/v1` (US) or `cdp-eu.customer.io/v1` (EU). ### Parameters #### Request headers - **Content-Type** (string) - Required - `application/json; charset=utf-8` - **User-Agent** (string) - Required - `analytics-ios/` (overridden by CIO's `Context` plugin) - **Accept-Encoding** (string) - Required - `gzip` - **Authorization** (string) - Required - `Basic :")>` #### Request Body - **writeKey** (string) - Required - The CDP API key. - **sentAt** (string) - Required - ISO 8601 timestamp of when the batch was sent. - **batch** (array) - Required - An array of RawEvent objects. ### Request Example ```json { "writeKey": "", "sentAt": "", "batch": [ , ... ] } ``` ### Response #### Success Response (200) Indicates the batch was successfully received and processed. #### Response Example (No specific example provided in source) ### Error Handling - **HTTP 400**: Malformed JSON. Batch is deleted without retry. - **HTTP 429**: Rate limited. Task fails, file remains on disk for retry. - **Other 4xx / 5xx**: Task fails, file remains on disk for retry. - **Network error**: Task fails, file remains on disk for retry. ``` -------------------------------- ### Manually Track Events with String Properties Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Use this method to manually track events with string-based properties. Ensure the properties dictionary is correctly formatted. ```swift CustomerIO.shared.track(name: "Order Placed", properties: ["sku": "ABC-123", "revenue": 9.99]) ``` -------------------------------- ### Manual Notification Handling in UNUserNotificationCenterDelegate Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEEPLINKS-AND-NOTIFICATIONS.md Implement this in your UNUserNotificationCenterDelegate to manually forward push events to the Customer.io SDK. The SDK returns a boolean indicating if it handled the push; the host app must call the completion handler afterward. ```swift func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { let handled = MessagingPush.shared.userNotificationCenter( center, didReceive: response, withCompletionHandler: completionHandler ) if !handled { completionHandler() } } ``` -------------------------------- ### InAppEventListener Protocol Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Implement this protocol to receive lifecycle events for in-app messages, such as when a message is shown, dismissed, or an action is taken. ```APIDOC ## InAppEventListener (callback protocol) Implement this protocol to receive lifecycle events for in-app messages: ```swift public protocol InAppEventListener { func messageShown(message: InAppMessage) func messageDismissed(message: InAppMessage) func errorWithMessage(message: InAppMessage) func messageActionTaken(message: InAppMessage, actionValue: String, actionName: String) } ``` `messageActionTaken` is called for every button/link tap in a message **except** `gist://close` — the close action is treated as a dismissal rather than an actionable tap. ``` -------------------------------- ### In-App Message Queue Processing Flow Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Illustrates the steps involved in processing the in-app message queue, including filtering, selection, and dispatching for embedded or modal display. ```mermaid graph TD Action[".processMessageQueue"] Action --> Middleware[messageQueueProcessorMiddleware] Middleware --> FilterShown["Filter already-shown messages (shownMessageQueueIds)"] FilterShown --> FilterRoute["Filter messages whose page rule doesn't match currentRoute"] FilterRoute --> SelectMessage["Select first eligible message"] SelectMessage --> Embedded{isEmbedded?} Embedded -- Yes --> DispatchEmbed["dispatch(.embedMessages([message]))"] DispatchEmbed --> StateEmbedded["State: embeddedMessagesState[elementId] = .embedded(...)"] Embedded -- No --> Modal["modal"] Modal --> DispatchLoad["dispatch(.loadMessage(message))"] DispatchLoad --> ModalMiddleware[modalMessageDisplayStateMiddleware] ModalMiddleware --> Guard["Guard: no other modal currently displayed"] Guard --> DispatchDisplay["dispatch(.displayMessage(message))"] DispatchDisplay --> Reducer["Reducer: state.modalMessageState = .displayed(message)"] Reducer --> OnDisplayed["ModalMessageManager.onMessageDisplayed()"] OnDisplayed --> ShowModal["ModalViewManager.showModalView() [animated, .top/.center/.bottom]"] ``` -------------------------------- ### Network API - Engine and Renderer Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md URLs for the Engine API, which processes messages, and the Renderer, which displays them in a WKWebView. ```http https://engine.api.gist.build https://renderer.gist.build/3.0 ``` -------------------------------- ### Message Delivery - Polling Logic Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Details the conditions and intervals under which the SDK initiates polling requests to fetch the message queue. ```swift Immediately when a user is identified. When the app route changes (for route-matched messages). After a message is dismissed (to fetch the next eligible message). On a timed interval (default 600 seconds, configurable via X-Gist-Queue-Polling-Interval response header). ``` -------------------------------- ### EngineWebDelegate Methods for WKWebView Communication Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Defines the delegate methods for `EngineWebDelegate` used to communicate between the `WKWebView` and the native iOS application. ```swift func bootstrapped() // WKWebView loaded func tap(name: String, action: String, system: Bool) // button/link tapped func sizeChanged(message:, width:, height:) // content size changed (inline only) func routeChanged(newRoute:) func routeError(route:) func engineBecameVisible() ``` -------------------------------- ### Manually Track Screen Views Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Manually track screen views with a title and optional properties. This is useful for custom screen tracking logic. ```swift CustomerIO.shared.screen(title: "Home", properties: ["referrer": "push"]) ``` -------------------------------- ### Manually Track Events with Codable Properties Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Track events using Codable objects for properties. This is useful for structured data and ensures type safety. ```swift CustomerIO.shared.track(name: "Order Placed", properties: myStruct) ``` -------------------------------- ### EventBus Signaling - Subscriptions Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md The MessagingInAppImplementation subscribes to several EventBus events to manage user profiles, screen views, and state resets. ```swift ProfileIdentifiedEvent AnonymousProfileIdentifiedEvent ScreenViewedEvent ResetEvent ``` -------------------------------- ### InAppEventListener Protocol Definition Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Implement this protocol to receive lifecycle events for in-app messages, including when a message is shown, dismissed, an error occurs, or an action is taken. ```swift public protocol InAppEventListener { func messageShown(message: InAppMessage) func messageDismissed(message: InAppMessage) func errorWithMessage(message: InAppMessage) func messageActionTaken(message: InAppMessage, actionValue: String, actionName: String) } ``` -------------------------------- ### Observe Inbox Changes (Callback) Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Add a listener to observe changes in the inbox messages. The listener will be called on the main thread. ```swift @MainActor func addChangeListener(_ listener: NotificationInboxChangeListener, topic: String?) func removeChangeListener(_ listener: NotificationInboxChangeListener) ``` -------------------------------- ### Network API - Endpoints Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Defines the HTTP methods, paths, and purposes for various Gist API endpoints used by the SDK. ```http POST /api/v4/users POST /api/v1/logs/queue/{queueId} POST /api/v1/logs/message/{messageId} PATCH /api/v1/messages/{queueId} ``` -------------------------------- ### Add Task with Snapshot Data to Background Queue Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/BACKGROUND-QUEUE.md Provide the actual data value when adding a task to the background queue. This ensures the task uses the state of the data at the time the task was created, preventing inconsistencies if the underlying data changes before the task is processed. ```swift let profileId = 5 // the identifier to query our app's local data storage for this profile let newFirstName = "Eddie" database.updateFirstName(id: profileId, firstName: newFirstName) // Do this: backgroundQueue.addTask( "edit_profile", EditProfileQueueTaskData(newFirstName) ) // Do *not* do this: backgroundQueue.addTask( "edit_profile", EditProfileQueueTaskData(profileId) // when "edit_profile" task runs in the background queue, query the database for the first name for id 5 ) ``` -------------------------------- ### Batch JSON Schema for Upload Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Defines the structure of a single JSON object for batch event uploads. Includes write key, timestamp, and an array of raw events. ```json { "writeKey": "", "sentAt": "", "batch": [ , ... ] } ``` -------------------------------- ### MessagingInApp Public Interface Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Access the shared instance, register lifecycle event callbacks, dismiss messages programmatically, and access inbox functionality. ```swift public class MessagingInApp: MessagingInAppInstance { public static var shared: MessagingInApp /// Register lifecycle event callbacks func setEventListener(_ eventListener: InAppEventListener?) /// Programmatically dismiss the currently displayed message func dismissMessage() /// Access inbox functionality var inbox: NotificationInbox } ``` -------------------------------- ### InlineMessageUIView (UIKit) Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md A UIKit view for displaying inline in-app messages. It can be created programmatically or via Storyboard and supports optional action delegates. ```APIDOC ### Inline Views **UIKit:** ```swift // Create and place in a UIKit view hierarchy: let view = InlineMessageUIView(elementId: "home-banner") view.onActionDelegate = self // optional: InlineMessageUIViewDelegate // Or via Storyboard: @IBOutlet weak var inAppView: InlineMessageUIView! inAppView.elementId = "home-banner" ``` The view self-sizes its height via Auto Layout constraints. Height is animated as content renders or changes. ``` -------------------------------- ### Network API - Request Headers Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md All Gist API requests require specific headers for authentication, identification, and versioning. ```http X-Gist-Site-ID: Workspace Site ID X-Gist-Data-Center: "us" or "eu" X-Gist-Client-Version: SDK version string X-Gist-Client-Platform: e.g. "swift-apple" X-Gist-User-Token: Base64-encoded userId or anonymousId X-Gist-Is-Anonymous: "true" / "false" ``` -------------------------------- ### InlineMessage (SwiftUI) Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md A SwiftUI view for displaying inline in-app messages. It can be configured with an element ID and an optional callback for handling button taps. ```APIDOC **SwiftUI:** ```swift InlineMessage(elementId: "home-banner") { message, actionValue, actionName in // optional: handle button taps } ``` Both views display a loading spinner while fetching content and animate height transitions. ``` -------------------------------- ### Rich Push Notification Lifecycle Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/DEEPLINKS-AND-NOTIFICATIONS.md Outlines the sequence of operations within a UNNotificationServiceExtension for handling rich push notifications. It details parsing, event tracking, image downloading, and content modification. ```text NSE receives UNNotificationServiceExtension.didReceive(_:withContentHandler:) │ ▼ MessagingPush.shared.didReceive(request, withContentHandler:) │ ▼ 1. Parse push via UNNotificationWrapper └── Not a CIO push → return false; NSE handles content itself │ ▼ 2. autoTrackPushEvents enabled? └── Yes → trackMetricFromNSE(deliveryID:event:.delivered:deviceToken:) └── RichPushDeliveryTrackerImpl.trackMetric(...) └── POST /track (direct HTTP — no EventBus) │ ▼ 3. RichPushRequestHandler.shared.startRequest(push:) ├── Downloads image from push.cioImage.url via SDK's httpClient ├── Saves file to a local temp URL └── On completion → completionHandler(modifiedContent) (content now has UNNotificationAttachment with local file URL) │ ▼ 4. serviceExtensionTimeWillExpire() called by OS (time budget exceeded)? └── RichPushRequestHandler.shared.stopAll() └── Calls completionHandler immediately with whatever content is available ``` -------------------------------- ### ScreenEvent-Specific Fields Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Outlines the fields specific to a 'screen' event type, such as screen title, category, and properties. ```json { "type": "screen", "name": "", "category": "", "properties": { ... } } ``` -------------------------------- ### Background Queue - Task Added Logs Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/QA.md These log statements indicate that an operation has been added to the background queue and provide details about the task and when the queue is scheduled to run. ```text identify profile dana900 storing identifier on device storage dana900 adding queue task identifyProfile added queue task data IdentifyProfile(identifier: "dana900") processing queue status QueueStatus(numberOfTask: 5) queue timer: scheduled to run queue in 30 seconds ``` -------------------------------- ### Background Queue - Task Failure Logs Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/QA.md These logs indicate a failure during background queue task processing, often due to network issues or SDK errors. They show the error and the update to the task's run history. ```text queue task 38383838-383838383-49490495 fail - queue task 38383838-383838383-49490495 updating run history from numberOfTimesRun: 0 to numberOfTimesRun: 1 queue task 38383838-383838383-49490495 update success true ``` -------------------------------- ### Network API Endpoints Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Details the base URL, required request headers, and available endpoints for interacting with the In-App Messaging API. ```APIDOC ## Network API Endpoints Base URL: `https://consumer.inapp.customer.io` ### Request Headers (all endpoints) | Header | Value | |---|---| | `X-Gist-Site-ID` | Workspace Site ID | | `X-Gist-Data-Center` | `"us"` or `"eu"` | | `X-Gist-Client-Version` | SDK version string | | `X-Gist-Client-Platform` | e.g. `"swift-apple"` | | `X-Gist-User-Token` | Base64-encoded userId or anonymousId | | `X-Gist-Is-Anonymous` | `"true"` / `"false"` | All requests also include `sessionId` as a query parameter. ### Endpoints | Method | Path | Purpose | |---|---|---| | `POST` | `/api/v4/users` | Fetch message queue for current user | | `POST` | `/api/v1/logs/queue/{queueId}` | Log that an identified user viewed a message | | `POST` | `/api/v1/logs/message/{messageId}` | Log that an anonymous user (or test) viewed a message | | `PATCH` | `/api/v1/messages/{queueId}` | Update inbox message opened/unopened state | ### SSE (Server-Sent Events) | Setting | URL | |---|---| | SSE connection | `https://realtime.inapp.customer.io/api/v3/sse` | SSE uses the same user-token headers as the queue API. It delivers real-time message availability events, eliminating the need for polling when active. ``` -------------------------------- ### MessagingInApp Public Interface Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Provides access to the shared instance of the In-App Messaging module, allows setting event listeners, dismissing messages, and accessing inbox functionality. ```APIDOC ## MessagingInApp (module facade) ```swift public class MessagingInApp: MessagingInAppInstance { public static var shared: MessagingInApp /// Register lifecycle event callbacks func setEventListener(_ eventListener: InAppEventListener?) /// Programmatically dismiss the currently displayed message func dismissMessage() /// Access inbox functionality var inbox: NotificationInbox } ``` ``` -------------------------------- ### EventBus Signaling - Publications Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md The module publishes events to track in-app message metrics like 'opened' and 'clicked'. These are consumed by DataPipeline for analytics. ```swift TrackInAppMetricEvent(deliveryID:, event: "opened") TrackInAppMetricEvent(deliveryID:, event: "clicked", params: ["actionName":, "actionValue":]) ``` -------------------------------- ### InlineMessage for SwiftUI Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Use the `InlineMessage` SwiftUI view to display inline in-app messages. Optionally provide a closure to handle button taps. ```swift InlineMessage(elementId: "home-banner") { message, actionValue, actionName in // optional: handle button taps } ``` -------------------------------- ### TrackEvent-Specific Fields Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Details the fields specific to a 'track' event type, including the event name and custom properties. ```json { "type": "track", "event": "", "properties": { ... } } ``` -------------------------------- ### NotificationInbox API Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Provides methods to fetch, observe, and mutate messages within the user's inbox. It allows for topic-based filtering and asynchronous observation of changes. ```APIDOC ## NotificationInbox API ### Description Provides methods to fetch, observe, and mutate messages within the user's inbox. It allows for topic-based filtering and asynchronous observation of changes. ### Methods #### Fetch Messages ```swift func getMessages(topic: String?) async -> [InboxMessage] ``` Fetches the current list of inbox messages, optionally filtered by a specific topic. #### Observe Changes (Callback-based) ```swift @MainActor func addChangeListener(_ listener: NotificationInboxChangeListener, topic: String?) func removeChangeListener(_ listener: NotificationInboxChangeListener) ``` Adds or removes a listener to observe changes in the inbox messages, with optional topic filtering. Listeners are called on the main thread. #### Observe Changes (Swift Concurrency) ```swift func messages(topic: String?) -> AsyncStream<[InboxMessage]> ``` Returns an `AsyncStream` that emits the latest list of inbox messages, with optional topic filtering. This is the preferred method for observing changes using Swift Concurrency. #### Mutations ```swift func markMessageOpened(message: InboxMessage) func markMessageUnopened(message: InboxMessage) func markMessageDeleted(message: InboxMessage) func trackMessageClicked(message: InboxMessage, actionName: String?) ``` These methods allow for mutating the state of an `InboxMessage`, such as marking it as opened, unopened, deleted, or tracking a click event with an optional action name. ``` -------------------------------- ### Message Delivery - SSE Conditions Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Specifies the three required conditions for enabling Server-Sent Events (SSE) for message delivery. ```swift App is foregrounded [useSse] flag is true in state (set from server header) User is identified (has a non-empty userId) — anonymous users always use polling ``` -------------------------------- ### InlineMessageUIView for UIKit Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Create and place an `InlineMessageUIView` in a UIKit view hierarchy, optionally setting an `onActionDelegate`. The view self-sizes its height and animates height transitions. ```swift // Create and place in a UIKit view hierarchy: let view = InlineMessageUIView(elementId: "home-banner") view.onActionDelegate = self // optional: InlineMessageUIViewDelegate // Or via Storyboard: @IBOutlet weak var inAppView: InlineMessageUIView! inAppView.elementId = "home-banner" ``` -------------------------------- ### Observe Inbox Changes (Swift Concurrency) Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Observe changes to inbox messages using Swift Concurrency's AsyncStream. This provides a modern, asynchronous way to react to inbox updates. ```swift func messages(topic: String?) -> AsyncStream<[InboxMessage]> ``` -------------------------------- ### Common Fields for RawEvent Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/EVENT-TRACKING.md Specifies the common fields present in every raw event object, such as type, IDs, timestamps, and context. ```json { "type": "track" | "identify" | "screen" | "group" | "alias", "messageId": "", "anonymousId": "", "userId": "", "timestamp": "", "context": { "app": { "name": "", "version": "", "build": "", "namespace": "" }, "device": { "manufacturer": "", "type": "ios", "model": "", "name": "", "id": "", "token": "" }, "os": { "name": "", "version": "" }, "screen": { "width": , "height": }, "network": { "bluetooth": , "cellular": , "wifi": }, "locale": "", "timezone": "", "userAgent": "", "instanceId": "" }, "integrations": {} } ``` -------------------------------- ### Network API - SSE Connection Source: https://github.com/customerio/customerio-ios/blob/main/docs/dev-notes/IN-APP-MESSAGING.md Configuration for the Server-Sent Events (SSE) connection URL used for real-time message delivery. ```http https://realtime.inapp.customer.io/api/v3/sse ```