Skip to content

Commit 89f1233

Browse files
committed
feat: add hooks for session replay
1 parent b31ebf8 commit 89f1233

4 files changed

Lines changed: 53 additions & 3 deletions

File tree

Samples/iOS-Swift/iOS-Swift/SentrySDKWrapper.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@ struct SentrySDKWrapper {
2121
if #available(iOS 16.0, *), enableSessionReplay {
2222
options.sessionReplay = SentryReplayOptions(sessionSampleRate: 0, onErrorSampleRate: 1, maskAllText: true, maskAllImages: true)
2323
options.sessionReplay.quality = .high
24+
options.sessionReplay.onRenderedScreenshot = { image in
25+
guard let data = image.pngData() else {
26+
return image
27+
}
28+
do {
29+
try data.write(to: URL(fileURLWithPath: "/tmp/session-replay-0-rendered.png"))
30+
} catch {
31+
print("Failed to write in onRenderedScreenshot, reason: \(error)")
32+
}
33+
return image
34+
}
35+
options.sessionReplay.onMaskedScreenshot = { image in
36+
guard let data = image.pngData() else {
37+
return image
38+
}
39+
do {
40+
try data.write(to: URL(fileURLWithPath: "/tmp/session-replay-1-masked.png"))
41+
} catch {
42+
print("Failed to write screenshot in onMaskingScreenshot, reason: \(error)")
43+
}
44+
return image
45+
}
46+
2447
}
2548

2649
if #available(iOS 15.0, *), enableMetricKit {

Sources/Sentry/SentrySessionReplayIntegration.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions
111111
[[SentryViewPhotographer alloc] initWithRenderer:viewRenderer
112112
redactOptions:replayOptions
113113
enableExperimentalMaskRenderer:enableExperimentalRenderer];
114+
[_viewPhotographer setOnRenderScreenshot:replayOptions.onRenderedScreenshot];
115+
[_viewPhotographer setOnMaskScreenshot:replayOptions.onMaskedScreenshot];
114116

115117
if (touchTracker) {
116118
_touchTracker = [[SentryTouchTracker alloc]

Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@_implementationOnly import _SentryPrivate
22
import Foundation
33

4+
#if canImport(UIKit)
5+
import UIKit
6+
#endif
7+
48
@objcMembers
59
public class SentryReplayOptions: NSObject, SentryRedactOptions {
610

@@ -106,6 +110,22 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions {
106110
*/
107111
public var unmaskedViewClasses = [AnyClass]()
108112

113+
#if canImport(UIKit)
114+
/**
115+
* Callback to modify the screenshot after rendered from the view hierarchy.
116+
*
117+
* This can be used to apply custom modifications to the screenshot before it is masked.
118+
*/
119+
public var onRenderedScreenshot: ((_ screenshot: UIImage) -> UIImage)?
120+
121+
/**
122+
* Callback to modify the screenshot after masking.
123+
*
124+
* This can be used to apply custom modifications to the screenshot after it is masked.
125+
*/
126+
public var onMaskedScreenshot: ((_ screenshot: UIImage) -> UIImage)?
127+
#endif
128+
109129
/**
110130
* Enables the up to 5x faster experimental view renderer used by the Session Replay integration.
111131
*

Sources/Swift/Tools/ViewCapture/SentryViewPhotographer.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider {
1414

1515
var renderer: SentryViewRenderer
1616

17+
var onRenderScreenshot: ((UIImage) -> UIImage)?
18+
var onMaskScreenshot: ((UIImage) -> UIImage)?
19+
1720
/// Creates a view photographer used to convert a view hierarchy to an image.
1821
///
1922
/// - Parameters:
@@ -39,13 +42,15 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider {
3942
// The render method is synchronous and must be called on the main thread.
4043
// This is because the render method accesses the view hierarchy which is managed from the main thread.
4144
let renderedScreenshot = renderer.render(view: view)
45+
let processedRenderedScreenshot = onRenderScreenshot?(renderedScreenshot) ?? renderedScreenshot
4246

43-
dispatchQueue.dispatchAsync { [maskRenderer] in
47+
dispatchQueue.dispatchAsync { [maskRenderer, onMaskScreenshot] in
4448
// The mask renderer does not need to be on the main thread.
4549
// Moving it to a background thread to avoid blocking the main thread, therefore reducing the performance
4650
// impact/lag of the user interface.
47-
let maskedScreenshot = maskRenderer.maskScreenshot(screenshot: renderedScreenshot, size: viewSize, masking: redact)
48-
onComplete(maskedScreenshot)
51+
let maskedScreenshot = maskRenderer.maskScreenshot(screenshot: processedRenderedScreenshot, size: viewSize, masking: redact)
52+
let processedMaskedScreenshot = onMaskScreenshot?(maskedScreenshot) ?? maskedScreenshot
53+
onComplete(processedMaskedScreenshot)
4954
}
5055
}
5156

0 commit comments

Comments
 (0)