Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- recording: improvements to screenshot masking ([#147](https://github.com/PostHog/posthog-ios/pull/147))

## 3.6.0 - 2024-06-18

- recording: screenshot masking ([#146](https://github.com/PostHog/posthog-ios/pull/146))
Expand Down
86 changes: 78 additions & 8 deletions PostHog/Replay/PostHogReplayIntegration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,38 @@
}
}

if view is UILabel {
if isTextInputSensitive(view) {
if let label = view as? UILabel {
if isLabelSensitive(label) {
maskableWidgets.append(view.toAbsoluteRect(parent))
return
}
}

if let webView = view as? WKWebView {
// since we cannot mask the webview content, if masking texts or images are enabled
// we mask the whole webview as well
if isAnyInputSensitive(webView) {
maskableWidgets.append(view.toAbsoluteRect(parent))
return
}
}

if let button = view as? UIButton {
if isButtonSensitive(button) {
maskableWidgets.append(view.toAbsoluteRect(parent))
return
}
}

if let theSwitch = view as? UISwitch {
if isSwitchSensitive(theSwitch) {
maskableWidgets.append(view.toAbsoluteRect(parent))
return
}
}

if let picker = view as? UIPickerView {
if isPickerSensitive(picker) {
maskableWidgets.append(view.toAbsoluteRect(parent))
return
}
Expand Down Expand Up @@ -225,29 +255,63 @@

private func isAssetsImage(_ image: UIImage) -> Bool {
// https://github.com/daydreamboy/lldb_scripts#9-pimage
// do not mask if its an asset image, likely not PII anyway
image.imageAsset?.value(forKey: "_containingBundle") != nil
}

private func isAnyInputSensitive(_ view: UIView) -> Bool {
config.sessionReplayConfig.maskAllTextInputs || config.sessionReplayConfig.maskAllImages || view.isNoCapture()
isTextInputSensitive(view) || config.sessionReplayConfig.maskAllImages
}

private func isTextInputSensitive(_ view: UIView) -> Bool {
config.sessionReplayConfig.maskAllTextInputs || view.isNoCapture()
}

private func isLabelSensitive(_ view: UILabel) -> Bool {
isTextInputSensitive(view) && hasText(view.text)
}

private func isButtonSensitive(_ view: UIButton) -> Bool {
isTextInputSensitive(view) && hasText(view.titleLabel?.text)
}

private func isTextViewSensitive(_ view: UITextView) -> Bool {
isTextInputSensitive(view) || view.isSensitiveText()
(isTextInputSensitive(view) || view.isSensitiveText()) && hasText(view.text)
}

private func isPickerSensitive(_ view: UIPickerView) -> Bool {
isTextInputSensitive(view) && view.dataSource != nil
}

private func isSwitchSensitive(_ view: UISwitch) -> Bool {
var containsText = true
if #available(iOS 14.0, *) {
containsText = hasText(view.title)
}

return isTextInputSensitive(view) && containsText
}

private func isTextFieldSensitive(_ view: UITextField) -> Bool {
isTextInputSensitive(view) || view.isSensitiveText()
(isTextInputSensitive(view) || view.isSensitiveText()) && (hasText(view.text) || hasText(view.placeholder))
}

private func hasText(_ text: String?) -> Bool {
if let text = text, !text.isEmpty {
return true
} else {
// if there's no text, there's nothing to mask
return false
}
}

private func isImageViewSensitive(_ view: UIImageView) -> Bool {
var isAsset = false
if let image = view.image {
isAsset = isAssetsImage(image)
} else {
// if there's no image, there's nothing to mask
return false
Comment on lines +299 to +314

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if masking is enabled, only mask if there's actually any content (text|image)

}
return (config.sessionReplayConfig.maskAllImages && !isAsset) || view.isNoCapture()
}
Expand Down Expand Up @@ -294,15 +358,21 @@
setAlignment(textField.textAlignment, style)
}

if view is UIPickerView {
if let picker = view as? UIPickerView {
wireframe.type = "input"
wireframe.inputType = "select"
// set wireframe.value from selected row
}

if let theSwitch = view as? UISwitch {
wireframe.type = "input"
wireframe.inputType = "toggle"
wireframe.checked = theSwitch.isOn
if #available(iOS 14.0, *) {
if let text = theSwitch.title {
wireframe.label = isSwitchSensitive(theSwitch) ? text.mask() : text
}
}
}

if let imageView = view as? UIImageView {
Expand All @@ -320,14 +390,14 @@
wireframe.disabled = !button.isEnabled

if let text = button.titleLabel?.text {
wireframe.value = (config.sessionReplayConfig.maskAllTextInputs || button.isNoCapture()) ? text.mask() : text
wireframe.value = isButtonSensitive(button) ? text.mask() : text
}
}

if let label = view as? UILabel {
wireframe.type = "text"
if let text = label.text {
wireframe.text = isTextInputSensitive(view) ? text.mask() : text
wireframe.text = isLabelSensitive(label) ? text.mask() : text
}
wireframe.disabled = !label.isEnabled
style.color = label.textColor?.toRGBString()
Expand Down