diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml index 7398a8e833..a36fb53525 100644 --- a/.github/workflows/benchmarking.yml +++ b/.github/workflows/benchmarking.yml @@ -134,7 +134,7 @@ jobs: name: raw-build-output-build-xcframework path: | build-xcframework.log - + - name: Build test app with sentry run: bundle exec fastlane build_perf_test_app_sentry env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed7cd68b4..248c303827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Speed up HTTP tracking for multiple requests in parallel (#4366) - Slightly speed up SentryInAppLogic (#4370) +- Rename session replay `redact` options and APIs to `mask` (#4373) - Stop canceling timer for manual transactions (#4380) ### Fixes diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m index a46b3a19b6..1b1ea1dac5 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m @@ -29,8 +29,8 @@ - (BOOL)application:(UIApplication *)application options.failedRequestStatusCodes = @[ httpStatusCodeRange ]; options.experimental.sessionReplay.quality = SentryReplayQualityMedium; - options.experimental.sessionReplay.redactAllText = true; - options.experimental.sessionReplay.redactAllImages = true; + options.experimental.sessionReplay.maskAllText = true; + options.experimental.sessionReplay.maskAllImages = true; options.experimental.sessionReplay.sessionSampleRate = 0; options.experimental.sessionReplay.onErrorSampleRate = 1; diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index 8345293395..ff01f04398 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -44,7 +44,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { options.debug = true if #available(iOS 16.0, *), !args.contains("--disable-session-replay") { - options.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1, redactAllText: true, redactAllImages: true) + options.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1, maskAllText: true, maskAllImages: true) options.experimental.sessionReplay.quality = .high } diff --git a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift index e7a23a5e85..d14416638d 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift @@ -12,6 +12,6 @@ class SRRedactSampleViewController: UIViewController { notRedactedView.backgroundColor = .green notRedactedView.transform = CGAffineTransform(rotationAngle: 45 * .pi / 180.0) - SentrySDK.replay.ignoreView(notRedactedView) + SentrySDK.replay.maskView(notRedactedView) } } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 280f2c20ef..2767e1574f 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -199,7 +199,7 @@ struct ContentView: View { Text("Form Screen") } } - .sentryReplayRedact() + .sentryReplayMask() } SecondView() } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift index 502e719f66..5a490cd286 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift @@ -11,8 +11,8 @@ struct SwiftUIApp: App { options.tracesSampleRate = 1.0 options.profilesSampleRate = 1.0 options.experimental.sessionReplay.sessionSampleRate = 1.0 - options.experimental.sessionReplay.redactAllImages = false - options.experimental.sessionReplay.redactAllText = false + options.experimental.sessionReplay.maskAllImages = false + options.experimental.sessionReplay.maskAllText = false options.initialScope = { scope in scope.injectGitInformation() return scope diff --git a/Sources/Sentry/Public/SentryReplayApi.h b/Sources/Sentry/Public/SentryReplayApi.h index ecbb58872a..800dbb0c97 100644 --- a/Sources/Sentry/Public/SentryReplayApi.h +++ b/Sources/Sentry/Public/SentryReplayApi.h @@ -15,19 +15,18 @@ NS_ASSUME_NONNULL_BEGIN @interface SentryReplayApi : NSObject /** - * Marks this view to be redacted during replays. + * Marks this view to be masked during replays. * * @warning This is an experimental feature and may still have bugs. */ -- (void)redactView:(UIView *)view NS_SWIFT_NAME(redactView(_:)); +- (void)maskView:(UIView *)view NS_SWIFT_NAME(maskView(_:)); /** - * Marks this view to be ignored during redact step of session replay. - * All its content will be visible in the replay. + * Marks this view to not be masked during redact step of session replay. * * @warning This is an experimental feature and may still have bugs. */ -- (void)ignoreView:(UIView *)view NS_SWIFT_NAME(ignoreView(_:)); +- (void)unmaskView:(UIView *)view NS_SWIFT_NAME(unmaskView(_:)); /** * Pauses the replay. diff --git a/Sources/Sentry/SentryReplayApi.m b/Sources/Sentry/SentryReplayApi.m index e9ce39b34a..9d2e7ba4b1 100644 --- a/Sources/Sentry/SentryReplayApi.m +++ b/Sources/Sentry/SentryReplayApi.m @@ -10,14 +10,14 @@ @implementation SentryReplayApi -- (void)redactView:(UIView *)view +- (void)maskView:(UIView *)view { - [SentryRedactViewHelper redactView:view]; + [SentryRedactViewHelper maskView:view]; } -- (void)ignoreView:(UIView *)view +- (void)unmaskView:(UIView *)view { - [SentryRedactViewHelper ignoreView:view]; + [SentryRedactViewHelper unmaskView:view]; } - (void)pause diff --git a/Sources/SentrySwiftUI/SentryReplayView.swift b/Sources/SentrySwiftUI/SentryReplayView.swift index fa24d3f4e2..e35db36e51 100644 --- a/Sources/SentrySwiftUI/SentryReplayView.swift +++ b/Sources/SentrySwiftUI/SentryReplayView.swift @@ -10,7 +10,7 @@ struct SentryReplayView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let result = SentryRedactView() - result.sentryReplayRedact() + result.sentryReplayMask() return result } @@ -29,15 +29,15 @@ struct SentryReplayModifier: ViewModifier { @available(iOS 13, macOS 10.15, tvOS 13, *) public extension View { - /// Marks the view as containing sensitive information that should be redacted during replays. + /// Marks the view as containing sensitive information that should be masked during replays. /// - /// When this modifier is applied, any sensitive content within the view will be hidden or masked + /// When this modifier is applied, any sensitive content within the view will be masked /// during session replays to ensure user privacy. This is useful for views containing personal /// data or confidential information that shouldn't be visible when the replay is reviewed. /// /// - Returns: A modifier that redacts sensitive information during session replays. /// - Experiment: This is an experimental feature and may still have bugs. - func sentryReplayRedact() -> some View { + func sentryReplayMask() -> some View { modifier(SentryReplayModifier()) } } diff --git a/Sources/Swift/Extensions/UIViewExtensions.swift b/Sources/Swift/Extensions/UIViewExtensions.swift index 61ca12e2b2..83bf0ade79 100644 --- a/Sources/Swift/Extensions/UIViewExtensions.swift +++ b/Sources/Swift/Extensions/UIViewExtensions.swift @@ -9,8 +9,8 @@ public extension UIView { * Marks this view to be redacted during replays. * - experiment: This is an experimental feature and may still have bugs. */ - func sentryReplayRedact() { - SentryRedactViewHelper.redactView(self) + func sentryReplayMask() { + SentryRedactViewHelper.maskView(self) } /** @@ -18,8 +18,8 @@ public extension UIView { * of session replay. All its content will be visible in the replay. * - experiment: This is an experimental feature and may still have bugs. */ - func sentryReplayIgnore() { - SentryRedactViewHelper.ignoreView(self) + func sentryReplayUnmask() { + SentryRedactViewHelper.unmaskView(self) } } diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift index 35b20553ee..a29b3a050b 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift @@ -51,7 +51,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * * - note: The default is true */ - public var redactAllText = true + public var maskAllText = true /** * Indicates whether session replay should redact all non-bundled image @@ -59,7 +59,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * * - note: The default is true */ - public var redactAllImages = true + public var maskAllImages = true /** * Indicates the quality of the replay. @@ -73,7 +73,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * By default Sentry already mask text and image elements from UIKit * Every child of a view that is redacted will also be redacted. */ - public var redactViewClasses = [AnyClass]() + public var maskedViewClasses = [AnyClass]() /** * A list of custom UIView subclasses to be ignored @@ -81,7 +81,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * The views of given classes will not be redacted but their children may be. * This property has precedence over `redactViewTypes`. */ - public var ignoreViewClasses = [AnyClass]() + public var unmaskedViewClasses = [AnyClass]() /** * Defines the quality of the session replay. @@ -139,18 +139,18 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * - errorSampleRate Indicates the percentage in which a 30 seconds replay will be send with * error events. */ - public init(sessionSampleRate: Float = 0, onErrorSampleRate: Float = 0, redactAllText: Bool = true, redactAllImages: Bool = true) { + public init(sessionSampleRate: Float = 0, onErrorSampleRate: Float = 0, maskAllText: Bool = true, maskAllImages: Bool = true) { self.sessionSampleRate = sessionSampleRate self.onErrorSampleRate = onErrorSampleRate - self.redactAllText = redactAllText - self.redactAllImages = redactAllImages + self.maskAllText = maskAllText + self.maskAllImages = maskAllImages } convenience init(dictionary: [String: Any]) { let sessionSampleRate = (dictionary["sessionSampleRate"] as? NSNumber)?.floatValue ?? 0 let onErrorSampleRate = (dictionary["errorSampleRate"] as? NSNumber)?.floatValue ?? 0 - let redactAllText = (dictionary["redactAllText"] as? NSNumber)?.boolValue ?? true - let redactAllImages = (dictionary["redactAllImages"] as? NSNumber)?.boolValue ?? true - self.init(sessionSampleRate: sessionSampleRate, onErrorSampleRate: onErrorSampleRate, redactAllText: redactAllText, redactAllImages: redactAllImages) + let maskAllText = (dictionary["maskAllText"] as? NSNumber)?.boolValue ?? true + let maskAllImages = (dictionary["maskAllImages"] as? NSNumber)?.boolValue ?? true + self.init(sessionSampleRate: sessionSampleRate, onErrorSampleRate: onErrorSampleRate, maskAllText: maskAllText, maskAllImages: maskAllImages) } } diff --git a/Sources/Swift/Protocol/SentryRedactOptions.swift b/Sources/Swift/Protocol/SentryRedactOptions.swift index dc0e05c973..24560dddea 100644 --- a/Sources/Swift/Protocol/SentryRedactOptions.swift +++ b/Sources/Swift/Protocol/SentryRedactOptions.swift @@ -2,8 +2,8 @@ import Foundation @objc protocol SentryRedactOptions { - var redactAllText: Bool { get } - var redactAllImages: Bool { get } - var redactViewClasses: [AnyClass] { get } - var ignoreViewClasses: [AnyClass] { get } + var maskAllText: Bool { get } + var maskAllImages: Bool { get } + var maskedViewClasses: [AnyClass] { get } + var unmaskedViewClasses: [AnyClass] { get } } diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 2ec380441d..01b99a3e29 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -52,21 +52,21 @@ class UIRedactBuilder { - parameter options: A `SentryRedactOptions` object that specifies the configuration for the redaction process. - - If `options.redactAllText` is `true`, common text-related views such as `UILabel`, `UITextView`, and `UITextField` are redacted. - - If `options.redactAllImages` is `true`, common image-related views such as `UIImageView` and various internal `SwiftUI` image views are redacted. - - The `options.ignoreRedactViewTypes` allows specifying custom view types to be ignored during the redaction process. - - The `options.redactViewTypes` allows specifying additional custom view types to be redacted. + - If `options.maskAllText` is `true`, common text-related views such as `UILabel`, `UITextView`, and `UITextField` are redacted. + - If `options.maskAllImages` is `true`, common image-related views such as `UIImageView` and various internal `SwiftUI` image views are redacted. + - The `options.unmaskViewTypes` allows specifying custom view types to be ignored during the redaction process. + - The `options.maskViewTypes` allows specifying additional custom view types to be redacted. - note: On iOS, views such as `WKWebView` and `UIWebView` are automatically redacted, and controls like `UISlider` and `UISwitch` are ignored. */ init(options: SentryRedactOptions) { var redactClasses = [AnyClass]() - if options.redactAllText { + if options.maskAllText { redactClasses += [ UILabel.self, UITextView.self, UITextField.self ] } - if options.redactAllImages { + if options.maskAllImages { //this classes are used by SwiftUI to display images. redactClasses += ["_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView", "_TtC7SwiftUIP33_A34643117F00277B93DEBAB70EC0697122_UIShapeHitTestingView", @@ -89,11 +89,11 @@ class UIRedactBuilder { redactClassesIdentifiers = Set(redactClasses.map({ ObjectIdentifier($0) })) - for type in options.ignoreViewClasses { + for type in options.unmaskedViewClasses { self.ignoreClassesIdentifiers.insert(ObjectIdentifier(type)) } - for type in options.redactViewClasses { + for type in options.maskedViewClasses { self.redactClassesIdentifiers.insert(ObjectIdentifier(type)) } } @@ -133,15 +133,15 @@ class UIRedactBuilder { This function identifies and returns the regions within a given UIView that need to be redacted, based on the specified redaction options. - Parameter view: The root UIView for which redaction regions are to be calculated. - - Parameter options: A `SentryRedactOptions` object specifying whether to redact all text (`redactAllText`) or all images (`redactAllImages`). If `options` is nil, defaults are used (redacting all text and images). + - Parameter options: A `SentryRedactOptions` object specifying whether to redact all text (`maskAllText`) or all images (`maskAllImages`). If `options` is nil, defaults are used (redacting all text and images). - Returns: An array of `RedactRegion` objects representing areas of the view (and its subviews) that require redaction, based on the current visibility, opacity, and content (text or images). The method recursively traverses the view hierarchy, collecting redaction areas from the view and all its subviews. Each redaction area is calculated based on the view’s presentation layer, size, transformation matrix, and other attributes. The redaction process considers several key factors: - 1. **Text Redaction**: If `redactAllText` is set to true, regions containing text within the view or its subviews are marked for redaction. - 2. **Image Redaction**: If `redactAllImages` is set to true, image-containing regions are also marked for redaction. + 1. **Text Redaction**: If `maskAllText` is set to true, regions containing text within the view or its subviews are marked for redaction. + 2. **Image Redaction**: If `maskAllImages` is set to true, image-containing regions are also marked for redaction. 3. **Opaque View Handling**: If an opaque view covers the entire area, obfuscating views beneath it, those hidden views are excluded from processing, and we can remove them from the result. 4. **Clip Area Creation**: If a smaller opaque view blocks another view, we create a clip area to avoid drawing a redact mask on top of a view that does not require redaction. @@ -159,11 +159,11 @@ class UIRedactBuilder { } private func shouldIgnore(view: UIView) -> Bool { - return SentryRedactViewHelper.shouldIgnoreView(view) || containsIgnoreClass(type(of: view)) + return SentryRedactViewHelper.shouldUnmask(view) || containsIgnoreClass(type(of: view)) } private func shouldRedact(view: UIView) -> Bool { - if SentryRedactViewHelper.shouldRedactView(view) { + if SentryRedactViewHelper.shouldMaskView(view) { return true } if let imageView = view as? UIImageView, containsRedactClass(UIImageView.self) { @@ -257,19 +257,19 @@ class SentryRedactViewHelper: NSObject { private static var associatedRedactObjectHandle: UInt8 = 0 private static var associatedIgnoreObjectHandle: UInt8 = 0 - static func shouldRedactView(_ view: UIView) -> Bool { + static func shouldMaskView(_ view: UIView) -> Bool { (objc_getAssociatedObject(view, &associatedRedactObjectHandle) as? NSNumber)?.boolValue ?? false } - static func shouldIgnoreView(_ view: UIView) -> Bool { + static func shouldUnmask(_ view: UIView) -> Bool { (objc_getAssociatedObject(view, &associatedIgnoreObjectHandle) as? NSNumber)?.boolValue ?? false } - static func redactView(_ view: UIView) { + static func maskView(_ view: UIView) { objc_setAssociatedObject(view, &associatedRedactObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) } - static func ignoreView(_ view: UIView) { + static func unmaskView(_ view: UIView) { objc_setAssociatedObject(view, &associatedIgnoreObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) } } diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 2b2b2a8aeb..a308c8eb2e 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -288,7 +288,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } startSDK(sessionSampleRate: 1, errorSampleRate: 1) { options in - options.experimental.sessionReplay.redactViewClasses = [AnotherLabel.self] + options.experimental.sessionReplay.maskedViewClasses = [AnotherLabel.self] } let sut = try getSut() @@ -301,7 +301,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } startSDK(sessionSampleRate: 1, errorSampleRate: 1) { options in - options.experimental.sessionReplay.ignoreViewClasses = [AnotherLabel.self] + options.experimental.sessionReplay.unmaskedViewClasses = [AnotherLabel.self] } let sut = try getSut() diff --git a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift index a6023705e5..d2ae74fc7e 100644 --- a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift +++ b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift @@ -8,7 +8,7 @@ class SentryRedactModifierTests: XCTestCase { func testViewRedacted() throws { let text = Text("Hello, World!") - let redactedText = text.sentryReplayRedact() + let redactedText = text.sentryReplayMask() XCTAssertTrue(redactedText is ModifiedContent) } diff --git a/Tests/SentryTests/UIRedactBuilderTests.swift b/Tests/SentryTests/UIRedactBuilderTests.swift index 1711973ca9..0261485d7b 100644 --- a/Tests/SentryTests/UIRedactBuilderTests.swift +++ b/Tests/SentryTests/UIRedactBuilderTests.swift @@ -6,16 +6,16 @@ import UIKit import XCTest class RedactOptions: SentryRedactOptions { - var redactViewClasses: [AnyClass] - var ignoreViewClasses: [AnyClass] - var redactAllText: Bool - var redactAllImages: Bool + var maskedViewClasses: [AnyClass] + var unmaskedViewClasses: [AnyClass] + var maskAllText: Bool + var maskAllImages: Bool - init(redactAllText: Bool = true, redactAllImages: Bool = true) { - self.redactAllText = redactAllText - self.redactAllImages = redactAllImages - redactViewClasses = [] - ignoreViewClasses = [] + init(maskAllText: Bool = true, maskAllImages: Bool = true) { + self.maskAllText = maskAllText + self.maskAllImages = maskAllImages + maskedViewClasses = [] + unmaskedViewClasses = [] } } @@ -52,7 +52,7 @@ class UIRedactBuilderTests: XCTestCase { } func testDontRedactALabelOptionDisabled() { - let sut = getSut(RedactOptions(redactAllText: false)) + let sut = getSut(RedactOptions(maskAllText: false)) let label = UILabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) label.textColor = .purple rootView.addSubview(label) @@ -81,7 +81,7 @@ class UIRedactBuilderTests: XCTestCase { } func testDontRedactAImageOptionDisabled() { - let sut = getSut(RedactOptions(redactAllImages: false)) + let sut = getSut(RedactOptions(maskAllImages: false)) let image = UIGraphicsImageRenderer(size: CGSize(width: 40, height: 40)).image { context in context.fill(CGRect(x: 0, y: 0, width: 40, height: 40)) @@ -197,7 +197,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let label = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - SentrySDK.replay.ignoreView(label) + SentrySDK.replay.unmaskView(label) rootView.addSubview(label) let result = sut.redactRegionsFor(view: rootView) @@ -210,7 +210,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let view = AnotherView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - SentrySDK.replay.redactView(view) + SentrySDK.replay.maskView(view) rootView.addSubview(view) let result = sut.redactRegionsFor(view: rootView) @@ -223,7 +223,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let label = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - label.sentryReplayIgnore() + label.sentryReplayUnmask() rootView.addSubview(label) let result = sut.redactRegionsFor(view: rootView) @@ -236,7 +236,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let view = AnotherView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - view.sentryReplayRedact() + view.sentryReplayMask() rootView.addSubview(view) let result = sut.redactRegionsFor(view: rootView)