Set up the extension

This page will guide you to set up Mobile Experience Analytics Extension for Contentsquare Product Analytics.

This will supercharge the Product Analytics platform with:

  • Powerful Session Replay
  • Heatmaps
  • Analysis capabilities of Mobile API Errors and Crashes

This guide assumes that you have already implemented the CSQ SDK for Product Analytics.

Enable the extension in the Product Analytics UI

Section titled Enable the extension in the Product Analytics UI
  1. In Product Analytics, navigate to Account > Manage > Replays & Heatmaps.
  1. If this is your first time setting up Session Replay, select the checkbox to accept the terms and conditions.
  2. Click Add web or mobile app to launch the setup wizard. There is one configuration per app.
  3. Select Mobile as platform.
  4. Give your app a name and choose a storage location (US or EU).
  1. Set a sampling rate for how many sessions to record.
  2. Configure replay settings:
    • App ID: enter the bundle ID of your app (for example, com.companyname.appname).
    • Quality Settings: choose the recording fidelity for Wi-Fi and Cellular connections. The default for both is Medium; keep Cellular equal to or lower than Wi-Fi.
    • User Consent: enable this if your app requires explicit opt-in before recording. See Privacy docs for the opt-in API.
  1. Click Save and Continue to finish the wizard.

Enable error and crash collection

Section titled Enable error and crash collection

Error and crash collection is managed separately from the setup wizard. Navigate to Account > Manage > Replays & Heatmaps, click Edit next to your app configuration, then scroll to the relevant section under General Settings:

  • Error Capture: toggles for API Errors and Custom Errors.
  • Crashes Capture: toggle for Crashes.

Click Save when finished.

The Session Replay feature replays every interaction of your users with your app. To respect the user's right to privacy, the Contentsquare SDK:

  • Masks everything by default
  • Allows you to control which part of the user interface is collected via our Masking rules

Masking depends on the type of element:

Element TypeMasking Behavior
UIKit.UIImage and SwiftUI.ImageImage isn't collected, a placeholder is sent instead of the content; the "IMG" placeholder will be displayed in the frame of the element.
UIKit.UILabel and UIKit.UITextViewText is replaced by "la" repeated as many times as needed to equal the original character count. White characters are preserved. For instance the lazy fox is collected as lal lala ala. All other visual properties are collected (text color, background color, alignment, etc.).
UIKit.UITextFieldSame as UIKit.UILabel or UIKit.UITextView unless isSecureTextEntry is set to true. In this case all characters, including whitespaces are replaced with "•".
SwiftUI.TextElement is replaced by a black rectangle.
All other typesNo specific data is collected but all visual properties are collected: size, background color, corner radius, etc.

If you think a specific element can reveal personal data from one of these properties mask it using one of the methods presented below.

An efficient way to check how a view is rendered in the Session Replay is to navigate to the desired view with the SDK running then use the quick replay link.

Original vs Replay fully masked

Section titled Original vs Replay fully masked

Masking rules can be applied in two ways:

  1. Through remote masking configuration in the CSQ Console (for admin users): this is managed directly in the Console and takes effect for all sessions as soon as the app is restarted or brought to the foreground. See How to customize masking rules from the Data Masking tab in the Help Center.

  2. Using public masking APIs in the SDK (for mobile application developers): these APIs require developer implementation and will be applied only after the mobile app has gone through its release cycle.

The SDK determines whether views, images, and texts are masked according to the following rules, ranked from highest to lowest priority. Once a rule is triggered, the state is set, and subsequent rules are not applied.

Rules (highest to lowest priority)Configured via
1. The app or SDK version is fully maskedData Masking tab in the CSQ Console
2. Remote Text or Image masking is definedData Masking tab in the CSQ Console
3. An instance is specifically masked or unmaskedAPI
4. A parent is specifically masked or unmaskedAPI
5. A type is masked or unmaskedAPI
6. A parent type is masked or unmaskedAPI
7. Remote Text or Image unmasking is definedData Masking tab in the CSQ Console
8. Otherwise the default masking state is appliedAPI

UIKit.UITextField, UIKit.UITextView and SwiftUI.TextField text inputs follow specific rules as the risk of leaking personal data is higher with these elements:

Rules (highest to lowest priority)Configured via
1. The app or SDK version is fully maskedData Masking tab in the CSQ Console
2. Remote Text inputs masking is definedData Masking tab in the CSQ Console
3. An instance is specifically masked or unmaskedAPI
4. A type is masked or unmaskedAPI
5. Otherwise a text input field remains maskeddefault

All iOS views and subclasses of UIView are fully masked by default. Change the default masking state with:

/// Change the masking state of all types of views.
/// - Parameter mask: true restores the default masking state.
/// false unmasks every type of views.
static func setDefaultMasking(_ mask: Bool)

Masking/Un-masking by instance

Section titled Masking/Un-masking by instance

Use mask(view:) and unmask(view:) methods to mask or unmask a specific view instance. Masking is applied recursively to all subviews unless specified otherwise (cf. Masking and Unmasking behaviors on a parent view).

Following the previous example on un-masking UIButtons: you are in default masking, the UIButton are unmasked but a screen shows a button, myButton, that contains sensitive information. You can mask it with mask(view: myButton).

Although the SDK allows masking elements by type for convenience but it isn't the recommended masking mechanism because it impacts all screens of your application.

Use this when you have a specific class for presenting user information or for displaying the user profile picture to make sure it always stays masked, for instance.

Use mask(viewsOfType:) and unmask(viewsOfType:) methods if you want to handle a specific type, whether system or custom.

Example: un-masking UIButtons: If default masking is set to true, and you don't want any button to be masked, call unmask(viewsOfType: UIButton.self): to specify that all instances of UIButton and its subclasses should not be masked.

Masking and Unmasking behaviors on a parent view

Section titled Masking and Unmasking behaviors on a parent view

Masking is applied recursively to all children unless a specific rule has been applied to one of them. In this case, this rule also applies recursively.

Examples

Consider the following structure:

(parent view)
------------------------
| "Parent Label" |
| |
| (child view) |
| -------------------- |
| | "Child Label" | |
| | | |
| -------------------- |
| |
------------------------

Here's how the masking rules applied affect the parent and child labels:

Rules appliedParent LabelChild Label
No rule appliedMaskedMasked
Parent view unmaskedUnmaskedUnmasked
Parent view unmasked and child view maskedUnmaskedMasked
Default masking = falseUnmaskedUnmasked
Default masking = false and parent view maskedMaskedMasked

See Masking rules.

Unmasking an instance when its type is masked

If you've masked UILabel types specifically or are in default masking, call unmask(view: myLabel) so that all UILabel instances are masked except myLabel.

Mask an instance when its type is unmasked

If you've unmasked the UILabel type specifically or are using default masking, call mask(view: myLabel) so that no UILabel is masked except myLabel.

Implementation recommendations

Section titled Implementation recommendations

Masking operations should always be performed before the target view is added to the window to avoid any Personal Data leak.

For instance, you can set up masking in the didFinishLaunching callback of your app or if you need to change masking behavior while your app is launched: this can be done in loadView, viewDidLoad or viewWillAppear if you use a ViewController.

Keeping track of what is masked

Section titled Keeping track of what is masked

The SDK doesn't provide a list of what is currently masked, if you need to keep track, you probably will have to write your specific wrapper.

Masking operations performance impact

Section titled Masking operations performance impact

Repeating the same operation has little to no impact. For example, you can call unmask(viewsOfType: UIButton.self) multiple times without impacting the SDK or your app performance.