Mediation

Creating a Module

Core SDK Dependency ๐Ÿ”—

All modules in the Chartboost ecosystem must depend upon the Core SDK. This is to ensure that the module you develop can be initialized by the Core SDK. Include the following dependency to your module:

ChartboostCoreSDK

Required Module API Implementations ๐Ÿ”—

Module ๐Ÿ”—

The moduleโ€™s initialization entry point must conform to the Module interface so that the module is initializable by the Core SDK.

Your module implementation should gracefully handle situations where multiple initialization attempts are made. If your module is a single initialization type of implementation, it is recommended to synchronize your initialization method to prevent multithreaded access and to maintain an initialization state.

The interfaces are listed in the example below, however, we recommended that you double check the Core SDK API Reference.

/// The protocol to which all Chartboost Core modules must conform to.
@objc(CBCModule)
public protocol Module: AnyObject {
    /// The module identifier.
    var moduleID: String { get }

    /// The version of the module.
    var moduleVersion: String { get }

    /// The designated initializer for the module.
    /// The Chartboost Core SDK will invoke this initializer when instantiating modules defined on
    /// the dashboard through reflection.
    /// - parameter credentials: A dictionary containing all the information required to initialize
    /// this module, as defined on the Chartboost Core's dashboard.
    ///
    /// - note: Modules should not perform costly operations on this initializer.
    /// Chartboost Core SDK may instantiate and discard several instances of the same module.
    /// Chartboost Core SDK keeps strong references to modules that are successfully initialized.
    init(credentials: [String: Any]?)

    /// Sets up the module to make it ready to be used.
    /// - parameter configuration: A ``ModuleConfiguration`` for configuring the module.
    /// - parameter completion: A completion handler to be executed when the module is done initializing.
    /// An error should be passed if the initialization failed, whereas `nil` should be passed if it succeeded.
    @objc(initializeWithConfiguration:completion:)
    func initialize(
        configuration: ModuleConfiguration,
        completion: @escaping (Error?) -> Void
    )
}

/// Configuration with parameters needed by ``Module`` on initialization.
@objc(CBCModuleConfiguration)
@objcMembers
public final class ModuleConfiguration: NSObject {
    /// The Chartboost App ID which should match the value on the Chartboost dashboard.
    public let chartboostAppID: String

    /// Initializes a configuration object.
    /// - parameter chartboostAppID: The Chartboost App ID which should match the value on the Chartboost dashboard.
    init(sdkConfiguration: SDKConfiguration) {}
}

ConsentAdapter ๐Ÿ”—

For custom CMP adapters, the moduleโ€™s initialization entry point must conform to the ConsentAdapter interface so that the module is recognized as a CMP adapter and initializable by the Core SDK.

In addition to conforming to the Module interface, the module must also conform to all of the CMP-specific methods, properties, and callbacks.

The interfaces are listed in the example below, however, we recommended that you double check the Core SDK API Reference.

/// The protocol to which a CMP adapter module must conform to.
///
/// Core SDK identifies a CMP adapter from the list of modules it initializes based on conformance to this protocol.
/// When the publisher uses Core's CMP APIs (the ``ChartboostCore/consent``) method calls are forwarded to a Core module
/// that conforms to ``ConsentAdapter``, and such a module is also expected to report consent info changes back to the
/// Core SDK via its ``delegate`` property.
@objc(CBCConsentAdapter)
public protocol ConsentAdapter: Module {
    /// Indicates whether the CMP has determined that consent should be collected from the user.
    var shouldCollectConsent: Bool { get }

    /// Current user consent info as determined by the CMP.
    ///
    /// Consent info may include IAB strings, like TCF or GPP, and parsed boolean-like signals like "CCPA Opt In Sale"
    /// and partner-specific signals.
    ///
    /// Predefined consent key constants, such as ``ConsentKeys/tcf`` and ``ConsentKeys/usp``, are provided
    /// by Core. Adapters should use them when reporting the status of a common standard.
    /// Custom keys should only be used by adapters when a corresponding constant is not provided by the Core.
    ///
    /// Predefined consent value constants are also proivded, but are only applicable to non-IAB string keys, like
    /// ``ConsentKeys/ccpaOptIn`` and ``ConsentKeys/gdprConsentGiven``.
    var consents: [ConsentKey: ConsentValue] { get }

    /// The delegate to be notified whenever any change happens in the CMP consent info.
    /// This delegate is set by Core SDK and is an essential communication channel between Core and the CMP.
    /// Adapters should not set it themselves.
    var delegate: ConsentAdapterDelegate? { get set }

    /// Instructs the CMP to present a consent dialog to the user for the purpose of collecting consent.
    /// - parameter type: The type of consent dialog to present. See the ``ConsentDialogType`` documentation for more info.
    /// If the CMP does not support a given type, it should default to whatever type it does support.
    /// - parameter viewController: The view controller to present the consent dialog from.
    /// - parameter completion: This handler is called to indicate whether the consent dialog was successfully presented or not.
    /// Note that this is called at the moment the dialog is presented, **not when it is dismissed**.
    func showConsentDialog(
        _ type: ConsentDialogType,
        from viewController: UIViewController,
        completion: @escaping (_ succeeded: Bool) -> Void
    )

    /// Informs the CMP that the user has granted consent.
    /// This method should be used only when a custom consent dialog is presented to the user, thereby making the publisher
    /// responsible for the UI-side of collecting consent. In most cases ``showConsentDialog(_:from:completion:)`` should
    /// be used instead.
    /// If the CMP does not support custom consent dialogs or the operation fails for any other reason, the completion
    /// handler is executed with a `false` parameter.
    /// - parameter source: The source of the new consent. See the ``ConsentSource`` documentation for more info.
    /// - parameter completion: Handler called to indicate if the operation went through successfully or not.
    func grantConsent(source: ConsentSource, completion: @escaping (_ succeeded: Bool) -> Void)

    /// Informs the CMP that the user has denied consent.
    /// This method should be used only when a custom consent dialog is presented to the user, thereby making the publisher
    /// responsible for the UI-side of collecting consent. In most cases ``showConsentDialog(_:from:completion:)`` should
    /// be used instead.
    /// If the CMP does not support custom consent dialogs or the operation fails for any other reason, the completion
    /// handler is executed with a `false` parameter.
    /// - parameter source: The source of the new consent. See the ``ConsentSource`` documentation for more info.
    /// - parameter completion: Handler called to indicate if the operation went through successfully or not.
    func denyConsent(source: ConsentSource, completion: @escaping (_ succeeded: Bool) -> Void)

    /// Informs the CMP that the given consent should be reset.
    /// If the CMP does not support the `reset()` function or the operation fails for any other reason, the completion
    /// handler is executed with a `false` parameter.
    /// - parameter completion: Handler called to indicate if the operation went through successfully or not.
    func resetConsent(completion: @escaping (_ succeeded: Bool) -> Void)
}

// Extension to provide convenience logging methods.
extension ConsentAdapter {
    /// Logs a message using the Core SDK's internal logging system.
    /// - parameter message: The string to log.
    /// A prefix is added to identify the adapter when printing out the message to the console.
    /// - parameter level: The log severity level.
    public func log(_ message: String, level: LogLevel) { ... }
}

// Extension to provide convenience IAB string methods.
extension ConsentAdapter {
    /// Obtains the IAB consent strings currently stored in UserDefaults.
    /// If the CMP follows the IAB standard this can be used to obtain all the relevant IAB strings already mapped
    /// to Core's ``ConsentKey`` constants.
    public func userDefaultsIABStrings() -> [ConsentKey: ConsentValue] { ... }

    /// Adds an observer to get notified whenever IAB strings stored in UserDefaults change.
    /// If the CMP follows the IAB standard this can be used to start observing consent changes
    /// on initialization and have chages automatically trigger the appropriate ``ConsentAdapterDelegate``
    /// methods.
    ///
    /// Just make sure to call this method on a successful initialization and keep the returned observer object
    /// alive in your adapter instance.
    ///
    /// - returns: An observer object that must be kept alive in the adapter.
    public func startObservingUserDefaultsIABStrings() -> Any { ... }
}

/// The delegate of a ``ConsentAdapter`` module.
/// It should get notified whenever any change happens in the CMP consent.
@objc(CBCConsentAdapterDelegate)
public protocol ConsentAdapterDelegate: AnyObject {
    /// Called whenever the ``ConsentAdapter/consents`` value changed.
    /// - parameter key: The key that changed.
    func onConsentChange(key: ConsentKey)
}

/// A key in a Core consents dictionary.
public typealias ConsentKey = String

/// A namespace for Core's predefined ``ConsentKey`` constants.
@objc(CBCConsentKeys)
@objcMembers
public final class ConsentKeys: NSObject {
    /// CCPA opt-in key.
    /// Possible values are: ``ConsentValue/denied``, ``ConsentValue/granted``, ``ConsentValue/doesNotApply``.
    public static let ccpaOptIn: ConsentKey = "ccpa_opt_in"

    /// GDPR consent given key.
    /// Possible values are: ``ConsentValue/denied``, ``ConsentValue/granted``, ``ConsentValue/doesNotApply``.
    public static let gdprConsentGiven: ConsentKey = "gdpr_consent_given"

    /// GPP key.
    /// Possible values are IAB's GPP strings, as defined in https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/Consent%20String%20Specification.md
    public static let gpp: ConsentKey = "IABGPP_HDR_GppString"

    /// TCF key.
    /// Possible values are IAB's TCF strings, as defined in https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20Consent%20string%20and%20vendor%20list%20formats%20v2.md
    public static let tcf: ConsentKey = "IABTCF_TCString"

    /// USP key.
    /// Possible values are IAB's USP strings, as defined in https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md
    public static let usp: ConsentKey = "IABUSPrivacy_String"

    @available(*, unavailable)
    override init() {
        fatalError("Instantiation is disallowed because this class is intended only to act as a namespace.")
    }
}

/// A value in a Core consents dictionary.
public typealias ConsentValue = String

/// A namespace for Core's predefined ``ConsentValue`` constants.
@objc(CBCConsentValues)
@objcMembers
public final class ConsentValues: NSObject {
    /// Indicates the user denied consent.
    public static let denied: ConsentValue = "denied"

    /// Indicates the standard does not apply.
    public static let doesNotApply: ConsentValue = "does_not_apply"

    /// Indicates the user granted consent.
    public static let granted: ConsentValue = "granted"

    @available(*, unavailable)
    override init() {
        fatalError("Instantiation is disallowed because this class is intended only to act as a namespace.")
    }
}

Using Environment Information ๐Ÿ”—

The ChartboostCore public API provides three predefined environment information objects for different purposes:

  • AdvertisingEnvironment
  • AnalyticsEnvironment
  • AttributionEnvironment

These environments provide information based upon an intended purpose and are grouped to provide module creators guidance on what information is permissible for a given purpose. These environments are static, and can be accessed before the Core SDK is initialized.

The environment properties located in the ChartboostCore class are listed below.

/// An environment that contains information intended solely for analytics purposes.
/// - warning: Make sure to access UI-related properties from the main thread.
@objc(CBCAnalyticsEnvironment)
public protocol AnalyticsEnvironment {
    /// The tracking authorization status, as determined by the system's AppTrackingTransparency
    /// framework.
    @available(iOS 14.0, *)
    var appTrackingTransparencyStatus: ATTrackingManager.AuthorizationStatus { get }

    /// The session duration, or `0` if the ``ChartboostCore/initializeSDK(configuration:moduleObserver:)``
    /// method has not been called yet.
    /// A session starts the moment ``ChartboostCore/initializeSDK(configuration:moduleObserver:)`` is
    /// called for the first time.
    var appSessionDuration: TimeInterval { get }

    /// The session identifier, or `nil` if the ``ChartboostCore/initializeSDK(configuration:moduleObserver:)``
    /// method has not been called yet.
    /// A session starts the moment ``ChartboostCore/initializeSDK(configuration:moduleObserver:)`` is
    /// called for the first time.
    var appSessionID: String? { get }

    /// The version of the app.
    var appVersion: String? { get }

    /// The system advertising identifier (IFA).
    var advertisingID: String? { get }

    /// The app bundle identifier.
    var bundleID: String? { get }

    /// The device locale string, e.g. "en-US".
    var deviceLocale: String? { get }

    /// The device make, e.g. "Apple".
    var deviceMake: String { get }

    /// The device model, e.g. "iPhone11,2".
    var deviceModel: String { get }

    /// The framework name set by the publisher through a call to ``PublisherMetadata/setFrameworkName(_:)``.
    var frameworkName: String? { get }

    /// The framework version set by the publisher through a call to ``PublisherMetadata/setFrameworkVersion(_:)``.
    var frameworkVersion: String? { get }

    /// Indicates wheter the user has limited ad tracking enabled, as determined by a
    /// call to the system framework `ASIdentifierManager.shared().isAdvertisingTrackingEnabled`.
    /// Please note the system API is deprecated: https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614148-isadvertisingtrackingenabled
    var isLimitAdTrackingEnabled: Bool { get }

    /// Indicates whether the user is underage. This is determined by the latest value set by the publisher through
    /// a call to ``PublisherMetadata/setIsUserUnderage(_:)``, as well as by the "child-directed" option defined on
    /// the Chartboost Core dashboard. The more restrictive option is used.
    var isUserUnderage: Bool { get }

    /// The current network connection type, e.g. `wifi`.
    var networkConnectionType: NetworkConnectionType { get }

    /// The OS name, e.g. "iOS" or "iPadOS".
    var osName: String { get }

    /// The OS version, e.g. "17.0".
    var osVersion: String { get }

    /// The player identifier set by the publisher through a call to ``PublisherMetadata/setPlayerID(_:)``.
    var playerID: String? { get }

    /// The publisher-defined application identifier set by the publisher through a call to ``PublisherMetadata/setPublisherAppID(_:)``.
    var publisherAppID: String? { get }

    /// The publisher-defined session identifier set by the publisher through a call to ``PublisherMetadata/setPublisherSessionID(_:)``.
    var publisherSessionID: String? { get }

    /// The height of the screen in pixels.
    var screenHeightPixels: Double { get }

    /// The screen scale.
    var screenScale: Double { get }

    /// The width of the screen in pixels.
    var screenWidthPixels: Double { get }

    /// The system identifier for vendor (IFV).
    var vendorID: String? { get }

    /// The scope of the identifier for vendor.
    /// Currently the only value possible is ``VendorIDScope/developer``.
    var vendorIDScope: VendorIDScope { get }

    /// The device volume level.
    var volume: Double { get }

    /// Obtain the device user agent asynchronously.
    /// - parameter completion: Handler executed at the end of the user agent fetch operation.
    /// It returns either a valid string value or `nil` if the fetch fails.
    /// If a valid value is already cached, the completion executes immediately.
    func userAgent(completion: @escaping (_ userAgent: String?) -> Void)

    // MARK: Observers

    /// Adds an observer to receive notifications whenever an environment property changes.
    /// The Core SDK automatically notifies modules that conform to ``EnvironmentObserver`` about these changes,
    /// so there is no need to manually add Core modules as observers.
    func addObserver(_ observer: EnvironmentObserver)

    /// Removes a previously-added observer.
    func removeObserver(_ observer: EnvironmentObserver)
}

/// An environment that contains information intended solely for advertising purposes.
/// - warning: Make sure to access UI-related properties from the main thread.
@objc(CBCAdvertisingEnvironment)
public protocol AdvertisingEnvironment {
    /// The system advertising identifier (IFA).
    var advertisingID: String? { get }

    /// The app bundle identifier.
    var bundleID: String? { get }

    /// The device locale string, e.g. "en-US".
    var deviceLocale: String? { get }

    /// The device make, e.g. "Apple".
    var deviceMake: String { get }

    /// The device model, e.g. "iPhone11,2".
    var deviceModel: String { get }

    /// Indicates wheter the user has limited ad tracking enabled, as determined by a
    /// call to the system framework `ASIdentifierManager.shared().isAdvertisingTrackingEnabled`.
    /// Please note the system API is deprecated: https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614148-isadvertisingtrackingenabled
    var isLimitAdTrackingEnabled: Bool { get }

    /// The OS name, e.g. "iOS" or "iPadOS".
    var osName: String { get }

    /// The OS version, e.g. "17.0".
    var osVersion: String { get }

    /// The height of the screen in pixels.
    var screenHeightPixels: Double { get }

    /// The screen scale.
    var screenScale: Double { get }

    /// The width of the screen in pixels.
    var screenWidthPixels: Double { get }
}

/// An environment that contains information intended solely for attribution purposes.
/// - warning: Make sure to access UI-related properties from the main thread.
@objc(CBCAttributionEnvironment)
public protocol AttributionEnvironment {
    /// The system advertising identifier (IFA).
    var advertisingID: String? { get }

    /// Obtain the device user agent asynchronously.
    /// - parameter completion: Handler executed at the end of the user agent fetch operation.
    /// It returns either a valid string value or `nil` if the fetch fails.
    /// If a valid value is already cached, the completion executes immediately.
    func userAgent(completion: @escaping (_ userAgent: String?) -> Void)
}

Subscribing to Publisher Metadata Updates ๐Ÿ”—

Publisher metadata can be set or updated by the Publisher at any point in the appโ€™s lifecycle. If your module requires reacting to changes in these values in real time, you can subscribe to these changes by calling the AnalyticsEnvironment.addObserver() method with your EnvironmentObserver object.

Modules do not need to handle their own user consent collection, and can instead rely upon the Core SDKโ€™s consent mediation to provide that information.

To query the consent values from the currently initialized CMP, access the ConsentManagementPlatform interface from the ChartboostCore.consent property.

Consent information is valid only if a CMP adapter was successfully initialized by the Core SDK.

In the event that no CMP adapter was successfully initialized, the current consent information will be unavailable, and modules should assume that consent has not been given.

Handling Underage Users ๐Ÿ”—

CMPs do not provide COPPA related or child-directed APIs as COPPA relates to whether a user is underage or an app is child-directed, not whether the user has given consent for various use cases.

This information is captured by the isUserUnderage property in AnalyticsEnvironment, and is set by the Publisher via PublisherMetadata.setIsUserUnderage().

To obtain the consent, query the ConsentManagementPlatform.consents property for the map of current consent states.

You can then use one of the following keys in the table below to access the desired consent value if it exists. Each platform will have its own convenience constants as well.

Note that if a consent value does not exist for the specified key, it likely means that the CMP does not support that consent standard.

Consent Standard Key Description
ccpa_opt_in CCPA opt-in
gdpr_consent_given GDPR consent
IABGPP_HDR_GppString IAB GPP string
IABTCF_TCString IAB TCF string
IABUSPrivacy_String IAB USP string

Consent may change at any given time, and it is recommended to subscribe to consent changes if your module needs to react in real time to those consent changes.

To subscribe to consent changes, register your observer using the ConsentManagementPlatform.addObserver() call.

The Core SDK also provides a 500ms debouncing logic for CMP consent changes so that observers are not inundated with callbacks in a short period of time.

Usage Example ๐Ÿ”—

/import ChartboostCoreSDK

// Querying TCF, USP, GPP strings from ChartboostCore.consent.
let tcfString = ChartboostCore.consent.consents[ConsentKeys.tcf]
let uspString = ChartboostCore.consent.consents[ConsentKeys.usp]
let gppString = ChartboostCore.consent.consents[ConsentKeys.gpp]

// Querying isUserUnderage from AnalyticsEnvironment.
let isUserUnderage = ChartboostCore.consent.analyticsEnvironment.isUserUnderage

// Subscribing to consent changes.
class CustomConsentObserver: ConsentObserver {
    func onConsentModuleReady(initialConsents: [ConsentKey: ConsentValue]) {
    // The initial consent info is now ready to be queried from Core
    print("Consent module is ready. Initial consents dictionary: \(initialConsents)")
  }

  func onConsentChange(fullConsents: [ConsentKey: ConsentValue], modifiedKeys: Set<ConsentKey>) {
    // React to consent changes
    print("Consent changed. Affected keys: \(modifiedKeys). Full consents dictionary: \(fullConsents)")
  }
}

let consentObserver = CustomConsentObserver()
ChartboostCore.consent.addObserver(consentObserver)

Handling Multiple Initializations ๐Ÿ”—

It is possible that the moduleโ€™s initialization method may be invoked multiple times.

This usually occurs when the Core SDK attempts to initialize the module, and the module reports back a failure. The Core SDK will attempt to retry module initialization on an exponential backoff up to the maximum number of attempts.

As a module developer, it is good practice to add safeguards to your module initialization implementation so that you can guard against the multiple initialization edge cases such as:

  • Initializing an already initialized module.
  • Cleaning up state after failed module initializations.

Module API Access Before Module Initialization ๐Ÿ”—

Any public API may be called by a Publisher before the module has initialized.

It is good practice to identify which public APIs can function without the module being initialized vs APIs that cannot function without being initialized. For the public APIs that cannot function without being initialized, be sure to put in a fast fail initialization check.