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.
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
- Download ConnectLayoutConfig.json from our sample app.
- Add the file to your project bundle and select an appropriate target for it.
- 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.
- (recommended) Run the file through a JSON validator.
- 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.
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 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.
Property | Values | Description |
---|---|---|
HasMasking | Boolean | Set the value to true to allow masking. |
HasCustomMask | true | Protected text is replaced with a fixed string (xxxxx ).⚠️ In the current version, the default mask cannot be disabled and customized. |
MaskAccessibilityIdList | Array of string | The accessibility ID (accessibilityIdentifier ) assigned to the UI element |
MaskAccessibilityLabelList | Array of string | The accessibility label (accessibilityLabel ) assigned to the UI element |
MaskIdList | Array of string | The 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.
Updated about 1 month ago