Skip to content

Commit

Permalink
Merge 276f608 into e3dbde2
Browse files Browse the repository at this point in the history
  • Loading branch information
brustolin authored Jan 29, 2025
2 parents e3dbde2 + 276f608 commit 2006d0e
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Features

- Add `showMaskPreview` to `SentrySDK.replay` api to debug replay masking (#4761)
- Session replay masking preview for SwiftUI (#4737)

## 8.44.0-beta.1

Expand Down
5 changes: 3 additions & 2 deletions Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ struct ContentView: View {

return DataBag.shared.info["lastSpan"] as? Span
}

var body: some View {
return SentryTracedView("Content View Body", waitForFullDisplay: true) {
NavigationView {
Expand Down Expand Up @@ -235,7 +235,7 @@ struct ContentView: View {
.background(Color.white)
}
SecondView()

Text(TTDInfo)
.accessibilityIdentifier("TTDInfo")
}
Expand All @@ -255,5 +255,6 @@ struct SecondView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.sentryReplayPreviewMask(opacity: 0.3)
}
}
20 changes: 20 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,9 @@
D867063F27C3BC2400048851 /* SentryCoreDataTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D867063C27C3BC2400048851 /* SentryCoreDataTracker.h */; };
D86B6835294348A400B8B1FC /* SentryAttachment+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D86B6834294348A400B8B1FC /* SentryAttachment+Private.h */; };
D86F419827C8FEFA00490520 /* SentryCoreDataTrackerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86F419727C8FEFA00490520 /* SentryCoreDataTrackerExtension.swift */; };
D8709AC42D3E9C63006C491E /* SentryReplayMaskPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8709AC32D3E9C5C006C491E /* SentryReplayMaskPreview.swift */; };
D8709ACB2D3F848E006C491E /* SentryReplayMaskPreviewUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8709ACA2D3F8480006C491E /* SentryReplayMaskPreviewUIView.swift */; };
D8709ACD2D3F84CF006C491E /* PreviewRedactOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8709ACC2D3F84C9006C491E /* PreviewRedactOptions.swift */; };
D8739CF32BECF70F007D2F66 /* SentryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8739CF22BECF70F007D2F66 /* SentryLevel.swift */; };
D8739CF92BECFFB5007D2F66 /* SentryTransactionNameSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8739CF82BECFFB5007D2F66 /* SentryTransactionNameSource.swift */; };
D8739D142BEE5049007D2F66 /* SentryRRWebSpanEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8739D132BEE5049007D2F66 /* SentryRRWebSpanEvent.swift */; };
Expand Down Expand Up @@ -1985,6 +1988,9 @@
D86B6820293F39E000B8B1FC /* TestSentryViewHierarchy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestSentryViewHierarchy.h; sourceTree = "<group>"; };
D86B6834294348A400B8B1FC /* SentryAttachment+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryAttachment+Private.h"; path = "include/SentryAttachment+Private.h"; sourceTree = "<group>"; };
D86F419727C8FEFA00490520 /* SentryCoreDataTrackerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCoreDataTrackerExtension.swift; sourceTree = "<group>"; };
D8709AC32D3E9C5C006C491E /* SentryReplayMaskPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayMaskPreview.swift; sourceTree = "<group>"; };
D8709ACA2D3F8480006C491E /* SentryReplayMaskPreviewUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayMaskPreviewUIView.swift; sourceTree = "<group>"; };
D8709ACC2D3F84C9006C491E /* PreviewRedactOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewRedactOptions.swift; sourceTree = "<group>"; };
D8739CF22BECF70F007D2F66 /* SentryLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLevel.swift; sourceTree = "<group>"; };
D8739CF82BECFFB5007D2F66 /* SentryTransactionNameSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionNameSource.swift; sourceTree = "<group>"; };
D8739D132BEE5049007D2F66 /* SentryRRWebSpanEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRRWebSpanEvent.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3779,6 +3785,7 @@
isa = PBXGroup;
children = (
D8199DB429376ECC0074249E /* SentryInternal */,
D8709AC92D3F83A6006C491E /* Preview */,
D8199DB529376ECC0074249E /* SentrySwiftUI.h */,
D88D25E92B8E0BAC0073C3D5 /* module.modulemap */,
D8199DB629376ECC0074249E /* SentryTracedView.swift */,
Expand Down Expand Up @@ -3876,6 +3883,16 @@
name = CoreData;
sourceTree = "<group>";
};
D8709AC92D3F83A6006C491E /* Preview */ = {
isa = PBXGroup;
children = (
D8709ACA2D3F8480006C491E /* SentryReplayMaskPreviewUIView.swift */,
D8709AC32D3E9C5C006C491E /* SentryReplayMaskPreview.swift */,
D8709ACC2D3F84C9006C491E /* PreviewRedactOptions.swift */,
);
path = Preview;
sourceTree = "<group>";
};
D8739CF62BECFF86007D2F66 /* Log */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5323,7 +5340,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D8709AC42D3E9C63006C491E /* SentryReplayMaskPreview.swift in Sources */,
D8199DC129376EEC0074249E /* SentryTracedView.swift in Sources */,
D8709ACD2D3F84CF006C491E /* PreviewRedactOptions.swift in Sources */,
D8709ACB2D3F848E006C491E /* SentryReplayMaskPreviewUIView.swift in Sources */,
D48E8B9D2D3E82AC0032E35E /* SentrySpanOperation.swift in Sources */,
D8199DBF29376EE20074249E /* SentryInternal.m in Sources */,
D48E8B8B2D3E79610032E35E /* SentryTraceOrigin.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion SentrySwiftUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ Pod::Spec.new do |s|
s.watchos.framework = 'WatchKit'

s.source_files = "Sources/SentrySwiftUI/**/*.{swift,h,m}"
s.dependency 'Sentry', "8.44.0-beta.1"
s.dependency 'Sentry/HybridSDK', "8.44.0-beta.1"
end
6 changes: 6 additions & 0 deletions Sources/Sentry/PrivateSentrySDKOnly.mm
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,12 @@ + (SentryBreadcrumb *)breadcrumbWithDictionary:(NSDictionary *)dictionary
}

#if SENTRY_TARGET_REPLAY_SUPPORTED

+ (UIView *)sessionReplayMaskingOverlay:(id<SentryRedactOptions>)options
{
return [[SentryMaskingPreviewView alloc] initWithRedactOptions:options];
}

+ (nullable SentrySessionReplayIntegration *)getReplayIntegration
{

Expand Down
4 changes: 3 additions & 1 deletion Sources/Sentry/SentrySDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,16 @@ + (void)setStartTimestamp:(NSDate *)value

+ (void)startWithOptions:(SentryOptions *)options
{
// We save the options before checking for Xcode preview because
// we will use this options in the preview
startOption = options;
if ([SentryDependencyContainer.sharedInstance.processInfoWrapper
.environment[SENTRY_XCODE_PREVIEW_ENVIRONMENT_KEY] isEqualToString:@"1"]) {
// Using NSLog because SentryLog was not initialized yet.
NSLog(@"[SENTRY] [WARNING] SentrySDK not started. Running from Xcode preview.");
return;
}

startOption = options;
[SentryLog configure:options.debug diagnosticLevel:options.diagnosticLevel];

// We accept the tradeoff that the SDK might not be fully initialized directly after
Expand Down
9 changes: 9 additions & 0 deletions Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
@class SentryEnvelope;
@class SentryId;
@class SentrySessionReplayIntegration;
@class UIView;

@protocol SentryReplayBreadcrumbConverter;
@protocol SentryViewScreenshotProvider;
@protocol SentryRedactOptions;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -184,6 +186,13 @@ typedef void (^SentryOnAppStartMeasurementAvailable)(

#if SENTRY_TARGET_REPLAY_SUPPORTED

/**
* Return an instance of SentryRedactOptions with given option
* To be used from SentrySwiftUI, which cannot access the private
* `SentryRedactOptions` class.
*/
+ (UIView *)sessionReplayMaskingOverlay:(id<SentryRedactOptions>)options;

/**
* Configure session replay with different breadcrumb converter
* and screeshot provider. Used by the Hybrid SDKs.
Expand Down
18 changes: 18 additions & 0 deletions Sources/SentrySwiftUI/Preview/PreviewRedactOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS)
import Sentry

public class PreviewRedactOptions: SentryRedactOptions {
public let maskAllText: Bool
public let maskAllImages: Bool
public let maskedViewClasses: [AnyClass]
public let unmaskedViewClasses: [AnyClass]

public init(maskAllText: Bool = true, maskAllImages: Bool = true, maskedViewClasses: [AnyClass] = [], unmaskedViewClasses: [AnyClass] = []) {
self.maskAllText = maskAllText
self.maskAllImages = maskAllImages
self.maskedViewClasses = maskedViewClasses
self.unmaskedViewClasses = unmaskedViewClasses
}
}

#endif
41 changes: 41 additions & 0 deletions Sources/SentrySwiftUI/Preview/SentryReplayMaskPreview.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS)
import Sentry
import SwiftUI
import UIKit

#if CARTHAGE || SWIFT_PACKAGE
@_implementationOnly import SentryInternal
#endif

@available(iOS 13, macOS 10.15, tvOS 13, *)
struct SentryReplayMaskPreview: ViewModifier {
let redactOptions: SentryRedactOptions
let opacity: Float
func body(content: Content) -> some View {
content.overlay(SentryReplayPreviewView(redactOptions: redactOptions, opacity: opacity).disabled(true))
}
}

@available(iOS 13, macOS 10.15, tvOS 13, *)
public extension View {
func sentryReplayPreviewMask(redactOptions: SentryRedactOptions? = nil, opacity: Float = 1) -> some View {
let options = redactOptions ?? SentrySDK.options?.sessionReplay ?? PreviewRedactOptions()
return modifier(SentryReplayMaskPreview(redactOptions: options, opacity: opacity))
}
}

@available(iOS 13, macOS 10.15, tvOS 13, *)
struct SentryReplayPreviewView: UIViewRepresentable {
let redactOptions: SentryRedactOptions
let opacity: Float

func makeUIView(context: Context) -> SentryReplayMaskPreviewUIView {
return SentryReplayMaskPreviewUIView(redactOptions: redactOptions)
}

func updateUIView(_ uiView: SentryReplayMaskPreviewUIView, context: Context) {
uiView.opacity = CGFloat(opacity)
}
}

#endif
36 changes: 36 additions & 0 deletions Sources/SentrySwiftUI/Preview/SentryReplayMaskPreviewUIView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS)
import Sentry
import UIKit

#if CARTHAGE || SWIFT_PACKAGE
import Sentry._Hybrid
@_implementationOnly import SentryInternal
#endif

class SentryReplayMaskPreviewUIView: UIView {

private let maskingOverlay: UIView

var opacity: CGFloat {
get { maskingOverlay.alpha }
set { maskingOverlay.alpha = newValue }
}

init(redactOptions: SentryRedactOptions) {
maskingOverlay = PrivateSentrySDKOnly.sessionReplayMaskingOverlay(redactOptions)
super.init(frame: .zero)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func didMoveToWindow() {
super.didMoveToWindow()
guard let window = self.window else { return }
maskingOverlay.frame = window.bounds
window.addSubview(maskingOverlay)
}
}

#endif
2 changes: 2 additions & 0 deletions Sources/SentrySwiftUI/SentryInternal/SentryInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

NS_ASSUME_NONNULL_BEGIN

extern NSString *const SENTRY_XCODE_PREVIEW_ENVIRONMENT_KEY;

typedef NS_ENUM(NSInteger, SentryTransactionNameSource);

@class SentrySpanId;
Expand Down
18 changes: 14 additions & 4 deletions Tests/SentryTests/SentrySDKTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,7 @@ class SentrySDKTests: XCTestCase {
}

func testDontStartInsideXcodePreview() {
let testProcessInfoWrapper = TestSentryNSProcessInfoWrapper()
testProcessInfoWrapper.overrides.environment = ["XCODE_RUNNING_FOR_PREVIEWS": "1"]

SentryDependencyContainer.sharedInstance().processInfoWrapper = testProcessInfoWrapper
startprocessInfoWrapperForPreview()

SentrySDK.start { options in
options.debug = true
Expand Down Expand Up @@ -561,6 +558,13 @@ class SentrySDKTests: XCTestCase {
SentrySDK.start(options: fixture.options)
XCTAssertEqual(SentrySDK.options, fixture.options)
}

func testGlobalOptionsForPreview() {
startprocessInfoWrapperForPreview()

SentrySDK.start(options: fixture.options)
XCTAssertEqual(SentrySDK.options, fixture.options)
}

#if SENTRY_HAS_UIKIT
func testSetAppStartMeasurement_CallsPrivateSDKCallback() {
Expand Down Expand Up @@ -974,6 +978,12 @@ class SentrySDKTests: XCTestCase {
private func advanceTime(bySeconds: TimeInterval) {
fixture.currentDate.setDate(date: SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(bySeconds))
}

private func startprocessInfoWrapperForPreview() {
let testProcessInfoWrapper = TestSentryNSProcessInfoWrapper()
testProcessInfoWrapper.overrides.environment = ["XCODE_RUNNING_FOR_PREVIEWS": "1"]
SentryDependencyContainer.sharedInstance().processInfoWrapper = testProcessInfoWrapper
}
}

/// Tests in this class aren't part of SentrySDKTests because we need would need to undo a bunch of operations
Expand Down

0 comments on commit 2006d0e

Please sign in to comment.