Enable privacy protection in the Connect iOS SDK

When capturing user behavior data, you must take measures to exclude personally identifiable information (PII) such as credit card numbers or home addresses.

Before you begin

We recommend assigning accessibility labels (accessibilityLabel) or accessibility IDs (accessibilityIdentifier) to all UI elements that require privacy protection. You can do it programmatically or with the help of the Attribute Inspector in Xcode.

Accessibility label and ID in an iOS app

Important:

  • Using the same accessibility labels across the iOS and Android versions of an app creates consistency and is considered an industry standard. For more information, see Mobile Accessibility at W3C.
  • Accessibility labels are audible. Accessibility IDs are inaccessible for end users, which makes them useful for internal purposes.
  • If a UI element isn't assigned an accessibility label, accessibility ID or static ID, the library uses its XPath. You can enable privacy protection for the XPath, but it will be necessary to convert it to a regular expression first.

Instructions

  1. Download ConnectLayoutConfig.json from our sample app.
  2. Add the file to your project bundle and select an appropriate target for it.
Layout configuration file copied to bundle resources
  1. In ConnectLayoutConfig.json, enter the accessibility labels and IDs of the UI elements to exclude from capturing. See the How it works section below for the names of parameters.
  2. (recommended) Run the file through a JSON validator.
  3. Add a new section to the AppDelegate file. It will instruct the library to use the settings from your ConnectLayoutConfig.json. Here is a sample AppDelegate.swift for your reference.
Customized AppDelegate.swift

Customized AppDelegate.swift

Examples of AppDelegate files

class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let connectApplicationHelperObj = ConnectApplicationHelper() // Read the new ConnectLayoutConfig settings let tlfAdvFilePath: String? = Bundle.main.path(forResource: "ConnectLayoutConfig", ofType: "json") var layoutConfigDict: [AnyHashable : Any] = [:] // read data into layoutConfigDict loadJson(filePath: tlfAdvFilePath!, jsonDict: &layoutConfigDict) // Update values in configuration for both json objects "AutoLayout" & "AppendMapIds" in ConnectLayoutConfig.json to values in the frameworks. EOApplicationHelper.sharedInstance().setConfigItem("AutoLayout", value:layoutConfigDict["AutoLayout"], forModuleName:kTLFCoreModule) EOApplicationHelper.sharedInstance().setConfigItem("AppendMapIds", value:layoutConfigDict["AppendMapIds"], forModuleName:kTLFCoreModule) return true } func loadJson(filePath: String, jsonDict: inout [AnyHashable : Any]) { let jsonData = NSData(contentsOfFile: filePath) as Data? if let jsonData = jsonData, let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable : Any] { jsonDict = json } print("\(filePath):") print("\(jsonDict)") let error: Error? = nil if error != nil { print("Error: was not able to load for \(filePath)") } } }
#import "AppDelegate.h" @import Connect; @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Enable library to load configuration settings NSString *appKey = @"app_key"; NSString *postMessageURL = @"endoint_url"; [[ConnectApplicationHelper sharedInstance] enableFramework:appKey withPostMessageUrl:postMessageURL]; // Read the new ConnectLayoutConfig settings NSString *tlfAdvFilePath = [[NSBundle mainBundle] pathForResource:@"ConnectLayoutConfig" ofType:@"json"]; NSMutableDictionary *layoutConfigDict = [NSMutableDictionary dictionary]; // read data into layoutConfigDict [self loadJson:filePath:tlfAdvFilePath jsonDict:&layoutConfigDict]; // Update values in configuration for both json objects "AutoLayout" & "AppendMapIds" [[EOApplicationHelper sharedInstance] setConfigItem:@"AutoLayout" value:layoutConfigDict[@"AutoLayout"] forModuleName:kTLFCoreModule]; [[EOApplicationHelper sharedInstance] setConfigItem:@"AppendMapIds" value:layoutConfigDict[@"AppendMapIds"] forModuleName:kTLFCoreModule]; return YES; } - (void)loadJson:(NSString *)filePath jsonDict:(NSMutableDictionary **)jsonDict { NSData *jsonData = [NSData dataWithContentsOfFile:filePath]; if (jsonData) { NSError *error = nil; id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if ([json isKindOfClass:[NSDictionary class]]) { *jsonDict = [json mutableCopy]; } } NSLog(@"%@:", filePath); NSLog(@"%@", *jsonDict); if (error) { NSLog(@"Error: was not able to load for %@", filePath); } } @end

How it works

The masking object in ConnectLayoutConfig.json contains all properties related to PII protection.

PropertyValuesDescription
HasMaskingBooleanSet the value to true to allow masking.
HasCustomMasktrueProtected text is replaced with a fixed string (xxxxx).

⚠️ In the current version, the default mask cannot be disabled and customized.
MaskAccessibilityIdListArray of stringThe accessibility ID (accessibilityIdentifier) assigned to the UI element
MaskAccessibilityLabelListArray of stringThe accessibility label (accessibilityLabel) assigned to the UI element
MaskIdListArray of stringThe IDs of UI elements.

All XPath IDs must be converted to a regular expressions . For example, you can set the regular expression as ^9[0-9][0-9][0-9]$ to match all the controls whose ID has 4 characters.

You can create a general privacy rule that applies to all screens of your app. In that case, use the AutoLayout.GlobalScreenSettings object. To create a rule for a particular screen, add the AutoLayout.ScreenName object where ScreenName is the name of the screen. Feel free to combine general and screen-specific rules within the same configuration file.

{
  "AutoLayout": {
    "GlobalScreenSettings": {
      "Masking": {
        "HasMasking": true,
        "HasCustomMask": true,
        "Sensitive": {
          "capitalCaseAlphabet": "X",
          "number": "9",
          "smallCaseAlphabet": "x",
          "symbol": "#"
        },
        "MaskIdList": [
          "^9[0-9][0-9][0-9]$"
        ],
        "MaskAccessibilityIdList": [
          "loginName",
          "securityQuestion"
        ],
        "MaskAccessibilityLabelList": [
          "Health conditions",
          "Medications taken"
        ]
      }
    }
  },
  "PaymentViewController": {
    "Masking": {
      "HasMasking": true,
      "HasCustomMask": true,
      "Sensitive": {
        "capitalCaseAlphabet": "X",
        "number": "9",
        "smallCaseAlphabet": "x",
        "symbol": "#"
      },
      "MaskIdList": [],
      "MaskAccessibilityIdList": "pii",
      "MaskAccessibilityLabelList": [
        "Card Number",
        "Billing Address"
      ]
    }
  }
}

Test the settings

To make sure your new rule is working correctly, run the app and enter something into the fields you have masked. Then find the session in Connect and check how the input is displayed.

Masked input example

Protected text input in session replay