Session Replay

A newer version of this documentation is available. Switch to the latest version docs.

As session data collection will start at the 1st screenview event, it is required to have screen tracking implemented. Make sure to follow the React Native Track screens sections.

Updating to latest SDK version

Section titled Updating to latest SDK version

In order to enable Session Replay in your app and get the most stable version, it is required to upgrade the SDK to its latest version.

If you are in the process of implementing the SDK for the 1st time (or choose to take this update as an opportunity to review your Privacy related implementation), make sure to follow the React Native Privacy sections.

WebView events can be collected as part Session Replay under the following conditions:

  • The app is a React Native app
  • The WebView is registered to the SDK
  • The web page implements the Web Tracking Tag

For the full guide to implementation of Webview Tracking, see 📚 Mobile Apps Webview Tracking.

WebView personal data masking is entirely handled on the web side — see 📚 Personal Data handling in WebView.

Enable Session Replay on your device

Section titled Enable Session Replay on your device

Since not all sessions are collected (depending on the percentage set in the Contentsquare back office), we have implemented an option to force replay collection on your device for testing and debugging purposes. This option can be enabled from the in-app features settings:

  1. Enable in-app features
  2. Open in-app features settings with a long press on the snapshot button
  3. Under “Session Replay”, toggle “Enable Session Replay” on
  4. Kill the app
  5. Start app, a new session is starting with Session Replay enabled

How do I know if Session Replay is enabled?

Section titled How do I know if Session Replay is enabled?

There are 2 places where you can check if Session Replay is enabled:

In the logs: The log I/CSLIB|SessionReplay: Starting Session Replay. will confirm that Session Replay is enabled.

In in-app features settings: Below the “Enable “will start at next app start” (see below Access the replay), you will either see:

  • No replay link available which means Session Replay is not running for the current session
  • Get Replay link which means Session Replay is running for the current session

The replay can be accessed by tapping on Get replay link button from the in-app features settings:

The replay will be available within 5 minutes. Only the “ended screen views” are processed (we know a screenview is ended when we start receiving data for the next screenview). This means that you will be able to replay your session up to the previous screenview if the session is still running.

Provide custom fonts (iOS only)

Section titled Provide custom fonts (iOS only)

It is required to send us the custom fonts you use in your app so we can use them to render text properly.

Upload the fonts in otf, ttf, woff or woff2 format directly from the player as described in Managing iOS Fonts In The Player on the Help Center.

The Session Replay feature collects every user interaction within your app. In order 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 and un-masking component.

Although the implementation is done once for both iOS and Android on React Native, the visual appearance of the masking itself will be different for each platform:

Every single UI element is converted into a highly pixelized image to reach a very low resolution. Text and images will appear very blurry so that the content cannot be identified.

Original VS Replay fully masked

Section titled Original VS Replay fully masked

All components are initially masked by default. To modify this default masking state, utilize the setDefaultMasking API, preferably within a useEffect in the root file (such as App.js). This ensures proper initialization and handling when the component mounts.

If you choose to unmask the entire app using the setDefaultMasking API, be aware that you will need to individually mask all personal data within the app. Additionally, ensure that you consistently mask personal data on new screens in the future, if necessary.

/**
* Set the default masking of all the views.
* @param isMasking true mask all views by default.
* false unmask all views by default.
* The default value is true.
*/
setDefaultMasking(boolean isMasking)

Masking/Un-masking by instances

Section titled Masking/Un-masking by instances

Use <CSMask isMasking={boolean}> component to mask or unmask one or multiple specific children view components. By default isMasking props is true, so it’s optional to explicitly write it.

In this example below, all children views inside <CSMask> will follow the rule of the closest parent props isMasking.

// Masks or Unmasks all its children views
<CSMask isMasking={true | false}>
<View>
<Text style={styles.title}>Buy my amazing merch!</Text>
<Text>
Hurry up! This offer won't be available tomorrow. Only
$99.98 to get hands on a wonderful brandnew life changing
item
</Text>
</View>
</CSMask>

You have the ability to create nested structures with <CSMask> components, which means you can include a <CSMask> component within another <CSMask> component. For example if you want to unmask components inside a parent masked view layout.

Implementation recommendations

Section titled Implementation recommendations

<CSMask> should not wrap the Root Navigator it could cause unwanted results. If you want to set the default masking behavior for the whole application you should use setDefaultMasking(boolean isMasking). <CSMask> should be used at component level.

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 of it, you probably will have to write your specific wrapper.

Advanced features for Experience Monitoring

Section titled Advanced features for Experience Monitoring

Contentsquare provides the ability to search for session(s) associated with a specific visitor, based on an identifier: email, phone number, customer ID… As these values are typically personal data, from the moment the SDK is collecting the User Identifier, we immediately encode the value using a hashing algorithm so that the information is hidden and can never be accessed.

Use the following code to send a user identifier:

import Contentsquare from '@contentsquare/react-native-bridge';
Contentsquare.sendUserIdentifier("any_identifier");

When called, the SDK will log:

I/CSLIB: User identifier hashed sent {value}

Sending a user identifier for each session

Section titled Sending a user identifier for each session

You may want to send the user identifier for each session. While triggering the user identifier at app launch will cover most cases, it will not be enough. A session can start when the app is put in foreground, after staying in background for more than 30 minutes. See Session definition section for more information. That is why we also recommend sending the user identifier every time the app enters foreground. You can use AppState API to detect foreground and trigger a sendUserIdentifier().

  • User identifier max length is 100 characters (if the limit is exceeded, the user identifier will not be handled and you will see an error message in the Console/Logcat)
  • Only the first 15 user identifiers per view will be processed on server side
  • The SDK will trim and lowercase user identifier
  • User identifier event are not visible in Log visualizer

Contentsquare provides the ability to retrieve the link of the replay to be attached to other vendors such as Voice of Customer or Crash reporting tools.

Section titled Subscribe to Session Replay Link changes

Use onSessionReplayLinkChange to subscribe to Session Replay link updates (1). Then use the returned subscriber to remove the subscription when is not needed anymore. (2)

useEffect(() => {
const subscriber = Contentsquare.onSessionReplayLinkChange(link => { // 1
currentSessionReplaylink = link
});
return () => {
if (subscriber) { // 2
subscriber.remove();
}
};
}, []);
I/CSLIB: SessionReplay link: https://app.contentsquare.com/quick-playback/index.html?pid={projectId}&uu={userId}&sn={sessionNumber}&recordingType=cs

When the callback is called, the SDK will log:

If you have a Contentsquare account, you can use this link to directly watch your current Session Replay on the Contentsquare Platform.

The replay will be available within 5 minutes. Only the “ended screen views” are processed (we know a screenview is ended when we start receiving data for the next screenview). This means that you will be able to replay the session up to the previous screenview if the session is still running.

const link = await Contentsquare.getCurrentSessionReplayLink();

Sessions can be collected for Session Replay if the Session Replay feature has been enabled for your project and the session matches the collection criteria.

The following conditions will have an impact on which sessions will be collected:

  • User consent: The users have given their consent (if required)
  • Collection rate: The session is being drawn for collection (see Collection Rate below)
  • Compatibility: The OS version is supported.
  • App version: The app version is not part of the block list (see App version block list below)

Note that, when Session Replay is turned off, no content specific to Session Replay is collected whatsoever. Also note that the standard Contentsquare analytics tracking remain unaffected by this.

Session Replay collection is based on a percentage of the total sessions. By default Session Replay collection is disabled.

During the early access phase, the percentage of collected sessions will be set to 1% at the beginning. It will then be adjusted according to:

  • The traffic on your app
  • The volume of collected sessions set in the contract

See compatibility.

The Contentsquare team can add versions of your app in the block list to make sure Session Replay does not start on these versions. This is done when a problem is discovered on a specific version of your app, such as a Personal Data for which the masking was forgotten. This allows to keep Session Replay working on the other app versions (especially the new ones with the fix).

The SDK monitors the application lifecycle events and the view hierarchy, and generates Session Replay data from the behavior of the app, the content of the screen and the interaction of the user. These events are then locally stored, and eventually sent to our servers in batches. We then aggregate that data to create usable visual information into our Web Application, which you use to gather insights.

By default, Session Replay data can be sent over cellular network. If the particular context of your app or users requires a very limited impact on cellular data consumption, sending data over cellular network can be completely disabled. Once disabled, data will be sent when the device is using Wi-Fi.

Reach out to your Contentsquare contact that will make the adjustment in your project’s configuration.

Before being sent, data is stored in local files on disk up to 30MB on iOS and 20MB on Android. If the limit is reached, Session Replay is stopped. It will restart at the next app launch, once the SDK has been able to send the data.

The maximum request size is 1Mbyte.

Android

Requests are sent:

iOS

Requests are sent:

Most operations related to Session Replay are performed on dedicated background threads, for a minimal impact on the main thread. If CPU usage is too high for background tasks, some expensive calculations will be discarded. To make sure of this, we run performance tests on testing applications, along with the Android profiler.

If too much time is spent on the main thread, the quality level will decrease automatically: from high to medium to low to a complete stop if required. Once conditions are back to normal, quality level will be changed back to the default value set in the configuration.

We defined our network strategy to have the lesser impact on CPU, memory and data consumption of the users devices. We measure these impacts, using the Android profiler and dedicated logging system, for each release of our SDK.

You can reduce the quality level if you want to favor performance impact over quality.

We always strive to be non-intrusive, and transparent to the developers of the client app. We apply this rule on the performance as well. These are the technical specifics we can share on performance, if you have any questions feel free to reach out to us.

The performance results were obtained under the following conditions:

ConditionValue
Device modelPixel 5
Android version33 (Android 13)
Quality levelHigh
Default Masking StateDisabled
PropertyValue
Total % on main thread (CPU Profiling)< 10 %
Memory overhead10 MB
Session Replay data transmitted over network during 1 minute of use:
- with “High quality” level< 300 kB
- with “Medium quality” level< 200 kB
- with “Low quality” level< 100 kB
Additional info on performance test procedure
Section titled Additional info on performance test procedure

CPU Usage Test We are profiling our Demo app with and without the SDK dependency by following the same use case. We are measuring the CPU peak for both versions of the app and we are computing the difference.

RAM Usage Test We are profiling our Demo app with and without the SDK dependency by following the same use case. We are measuring the RAM usage peak for both versions of the app and we are computing the difference.

Memory Leak Test We are profiling our demo app and we are using the ADB Monkey tool to create random user input events. During this time we are monitoring the RAM behavior for any anomaly. We are using the Memory Profiler to identify leaks.

Data consumption We replay a predefined, repeatable and automated user scenario on our demo app and all data sent is measured in kB/min.

JankStats If you use the alpha library from Google to monitor the jank frames and you already have some jank frames in your application (main thread overwhelmed), you can expect an increase of 1% or 2% with Session Replay activated. Session Replay requires to perform tasks on the main thread (screen capture and view hierarchy inspection).

We always strive to be non-intrusive, and transparent to the developers of the client app. We apply this rule on the performance as well. These are the technical specifics we can share on performance, if you have any questions feel free to reach out to us.

Most operations related to Session Replay are performed on background threads, for the impact on the main thread to be minimal. To make sure of this, we run performance tests on testing applications, using XCTMetric and Hitch Time Ratio measures.

We also set up mechanisms that stop Session Replay if we detect the feature is using too much memory.

We defined our network strategy to have the lesser impact on CPU and battery of the users devices. We measure these impacts, using Apple Dev Tools, for each release of our SDK.

Session Replay will also stop if we use up to 30Mbytes of local storage.

You can reduce the quality level if you want to favor performance impact over quality.

The following performance results were obtained under the following conditions:

ConditionValue
Device modeliPhone 7
iOS version15.7.8
Test App built using Xcode version14.3.1
Test App built with Swift version5.8
Quality levelHigh
Default Masking StateDisabled

We conducted the tests using a default Master-Detail app built using AdHoc distribution with no app thinning and with Swift symbols stripped. In the app, the SDK was making calls to the Public APIs, running and collecting data in its default state.

PropertyValue
Max CPU overhead<10%
Max RAM usage<5MB
Session Replay data transmitted over network during 1 minute of use
- with “High quality” level<2Mbytes
- with “Medium quality” level<1Mbytes
- with “Low quality” level<900Kbytes

Discrepancy of the default iOS Font

Section titled Discrepancy of the default iOS Font

There is a discrepancy of the default iOS Font between devices and Session Replay.

Animations/Transitions are unsupported when Masking on Android

Section titled Animations/Transitions are unsupported when Masking on Android

When switching between screens using the react-navigation library, or when there is an animated component on the screen (such as one containing Animated.View or using another animation library), personal data may leak during animations/transitions in the collected session.

To prevent this, mask the entire screen.

Mask the entire Screen

If you are unmasking everything by default using Contentsquare.setDefaultMasking(false), a workaround to prevent personal data leaks on screens with animations is to mask the entire screen.

When the component mounts, apply default masking to the entire screen with setDefaultMasking(true), and when the component unmounts, reset the default masking with setDefaultMasking(false).

import React, { useEffect } from 'react';
import { View } from 'react-native';
import Contentsquare from '@contentsquare/react-native-bridge';
const SomeScreen = () => {
useEffect(() => {
// Set default masking to the entire screen
Contentsquare.setDefaultMasking(true);
// Reset default masking when the component unmounts
return () => {
Contentsquare.setDefaultMasking(false);
};
}, []);
return (
<View>
{/* Code */}
</View>
);
};
export default SomeScreen;

Requests are not sent from an Android emulator / iOS simulator

Section titled Requests are not sent from an Android emulator / iOS simulator

If you struggle to watch a replay collection on an emulator/simulator, it may be due to some network constraints applied on your computer (VPN, company network restrictions, etc.). Check your configuration or use a real device.

See following sections for more information about our endpoints and requests:

Very long session on an Android emulator / iOS simulator

Section titled Very long session on an Android emulator / iOS simulator

It is important to remember that an app kill does not end a session. See Session definition for more information. If you leave the simulator/emulator running with the app in foreground, the session will not end, even if you are inactive. To better reflect actual end user behavior and avoid unusually long sessions (last hours), put the app in background or kill it.

Laggy Session Replays in Scroll Views on iOS

Section titled Laggy Session Replays in Scroll Views on iOS

Following the release of Contentsquare SDK version 4.3.0 we’ve identified a lag in Session Replays on iOS 17, particularly within screens containing scroll views. This lag results in elements on the screen not responding smoothly to gestures. We are actively working on resolving this issue.