Skip to content

Commit 05c674c

Browse files
Merge branch 'main' into replay-link-events
2 parents 6f848a3 + a04a10a commit 05c674c

25 files changed

+341
-20
lines changed

.github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,11 @@ jobs:
112112
run: dotnet restore Sentry-CI-Build-${{ runner.os }}.slnf --nologo
113113

114114
- name: Build
115+
id: build
115116
run: dotnet build Sentry-CI-Build-${{ runner.os }}.slnf -c Release --no-restore --nologo -v:minimal -flp:logfile=build.log -p:CopyLocalLockFileAssemblies=true -bl:build.binlog
116117

117118
- name: Upload build logs
119+
if: ${{ steps.build.outcome != 'skipped' }}
118120
uses: actions/upload-artifact@v4
119121
with:
120122
name: ${{ runner.os }}-build-logs

CHANGELOG.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@
44

55
### Features
66

7-
- Option to disable the SentryNative integration ([#4107](https://github.com/getsentry/sentry-dotnet/pull/4107))
7+
- Custom SessionReplay masks in MAUI Android apps ([#4121](https://github.com/getsentry/sentry-dotnet/pull/4121))
8+
9+
### Fixes
10+
11+
- Work around iOS SHA1 bug ([#4143](https://github.com/getsentry/sentry-dotnet/pull/4143))
12+
13+
## 5.6.0
14+
15+
### Features
16+
17+
- Option to disable the SentryNative integration ([#4107](https://github.com/getsentry/sentry-dotnet/pull/4107), [#4134](https://github.com/getsentry/sentry-dotnet/pull/4134))
18+
- To disable it, add this msbuild property: `<SentryNative>false</SentryNative>`
819
- Reintroduced experimental support for Session Replay on Android ([#4097](https://github.com/getsentry/sentry-dotnet/pull/4097))
920
- Associate replays with errors and traces on Android ([#4133](https://github.com/getsentry/sentry-dotnet/pull/4133))
1021

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<VersionPrefix>5.5.1</VersionPrefix>
4+
<VersionPrefix>5.6.0</VersionPrefix>
55
<LangVersion>13</LangVersion>
66
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
77
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

samples/Sentry.Samples.Maui/MainPage.xaml

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
33
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:sentry="http://schemas.sentry.io/maui"
45
x:Class="Sentry.Samples.Maui.MainPage">
56

67
<ScrollView>
@@ -11,6 +12,7 @@
1112

1213
<Image
1314
Source="dotnet_bot.png"
15+
sentry:SessionReplay.Mask="Unmask"
1416
SemanticProperties.Description="Cute dot net bot waving hi to you!"
1517
HeightRequest="200"
1618
HorizontalOptions="Center" />

samples/Sentry.Samples.Maui/MauiProgram.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@ public static MauiApp CreateMauiApp()
2525
options.Debug = true;
2626
options.SampleRate = 1.0F;
2727

28-
#if ANDROID
28+
#if __ANDROID__
2929
// Currently experimental support is only available on Android
3030
options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0;
3131
options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0;
32-
options.Native.ExperimentalOptions.SessionReplay.MaskAllImages = false;
33-
options.Native.ExperimentalOptions.SessionReplay.MaskAllText = false;
32+
// Mask all images and text by default. This can be overridden for individual view elements via the
33+
// sentry:SessionReplay.Mask XML attribute (see MainPage.xaml for an example)
34+
options.Native.ExperimentalOptions.SessionReplay.MaskAllImages = true;
35+
options.Native.ExperimentalOptions.SessionReplay.MaskAllText = true;
36+
// Alternatively the masking behaviour for entire classes of VisualElements can be configured here as
37+
// an exception to the default behaviour.
38+
options.Native.ExperimentalOptions.SessionReplay.UnmaskControlsOfType<Button>();
3439
#endif
3540

3641
options.SetBeforeScreenshotCapture((@event, hint) =>

samples/Sentry.Samples.Maui/Platforms/Android/MainActivity.cs

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#if __ANDROID__
12
using Android.App;
23
using Android.Content.PM;
4+
using Android.OS;
5+
#endif
36

47
namespace Sentry.Samples.Maui;
58

src/Sentry.Maui/AssemblyInfo.cs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// XML namespaces for custom XAML elements defined in the Sentry.Maui assembly (e.g. Bindable Properties)
2+
[assembly: XmlnsDefinition("http://schemas.sentry.io/maui", "Sentry.Maui")]
3+
[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://schemas.sentry.io/maui", "sentry")]

src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public void UnBind(VisualElement element)
2929
}
3030
}
3131

32-
3332
private void OnButtonOnClicked(object? sender, EventArgs _)
3433
=> addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Clicked)));
3534

src/Sentry.Maui/Internal/MauiEventsBinder.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Microsoft.Extensions.Options;
2+
using Microsoft.Maui.Platform;
3+
using Sentry.Extensibility;
24

35
namespace Sentry.Maui.Internal;
46

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Microsoft.Extensions.Options;
2+
using Sentry.Extensibility;
3+
#if __ANDROID__
4+
using View = Android.Views.View;
5+
#endif
6+
7+
namespace Sentry.Maui.Internal;
8+
9+
/// <summary>
10+
/// Masks or unmasks visual elements for session replay recordings
11+
/// </summary>
12+
internal class MauiVisualElementEventsBinder : IMauiElementEventBinder
13+
{
14+
private readonly SentryMauiOptions _options;
15+
16+
public MauiVisualElementEventsBinder(IOptions<SentryMauiOptions> options)
17+
{
18+
_options = options.Value;
19+
}
20+
21+
/// <inheritdoc />
22+
public void Bind(VisualElement element, Action<BreadcrumbEvent> _)
23+
{
24+
element.Loaded += OnElementLoaded;
25+
}
26+
27+
/// <inheritdoc />
28+
public void UnBind(VisualElement element)
29+
{
30+
element.Loaded -= OnElementLoaded;
31+
}
32+
33+
internal void OnElementLoaded(object? sender, EventArgs _)
34+
{
35+
if (sender is not VisualElement element)
36+
{
37+
_options.LogDebug("OnElementLoaded: sender is not a VisualElement");
38+
return;
39+
}
40+
41+
var handler = element.Handler;
42+
if (handler is null)
43+
{
44+
_options.LogDebug("OnElementLoaded: element.Handler is null");
45+
return;
46+
}
47+
48+
#if __ANDROID__
49+
if (element.Handler?.PlatformView is not View nativeView)
50+
{
51+
return;
52+
}
53+
54+
if (_options.Native.ExperimentalOptions.SessionReplay.MaskedControls.FirstOrDefault(maskType => element.GetType().IsAssignableFrom(maskType)) is not null)
55+
{
56+
nativeView.Tag = SessionReplayMaskMode.Mask.ToNativeTag();
57+
_options.LogDebug("OnElementLoaded: Successfully set sentry-mask tag on native view");
58+
}
59+
else if (_options.Native.ExperimentalOptions.SessionReplay.UnmaskedControls.FirstOrDefault(unmaskType => element.GetType().IsAssignableFrom(unmaskType)) is not null)
60+
{
61+
nativeView.Tag = SessionReplayMaskMode.Unmask.ToNativeTag();
62+
_options.LogDebug("OnElementLoaded: Successfully set sentry-unmask tag on native view");
63+
}
64+
#endif
65+
}
66+
}

src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder,
5757

5858
services.AddSingleton<IMauiElementEventBinder, MauiButtonEventsBinder>();
5959
services.AddSingleton<IMauiElementEventBinder, MauiImageButtonEventsBinder>();
60+
services.AddSingleton<IMauiElementEventBinder, MauiVisualElementEventsBinder>();
6061
services.TryAddSingleton<IMauiEventsBinder, MauiEventsBinder>();
6162

6263
services.AddSentry<SentryMauiOptions>();

src/Sentry.Maui/SentryMauiOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public SentryMauiOptions()
8181
/// if this callback return false the capture will not take place
8282
/// </remarks>
8383
/// <code>
84-
///
84+
///
8585
///options.SetBeforeCapture((@event, hint) =>
8686
///{
8787
/// // Return true to capture or false to prevent the capture

src/Sentry.Maui/SessionReplay.cs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
using Sentry.Infrastructure;
3+
#if __ANDROID__
4+
using View = Android.Views.View;
5+
using Android.Views;
6+
using Java.Lang;
7+
using Microsoft.Maui.Handlers;
8+
using Microsoft.Maui.Platform;
9+
#endif
10+
11+
namespace Sentry.Maui;
12+
13+
/// <summary>
14+
/// Contains custom <see cref="BindableProperty"/> definitions used to control the behaviour of the Sentry SessionReplay
15+
/// feature in MAUI apps.
16+
/// <remarks>
17+
/// NOTE: Session Replay is currently an experimental feature for MAUI and is subject to change.
18+
/// </remarks>
19+
/// </summary>
20+
public static class SessionReplay
21+
{
22+
/// <summary>
23+
/// Mask can be used to either unmask or mask a view.
24+
/// </summary>
25+
public static readonly BindableProperty MaskProperty =
26+
BindableProperty.CreateAttached(
27+
"Mask",
28+
typeof(SessionReplayMaskMode),
29+
typeof(SessionReplay),
30+
defaultValue: SessionReplayMaskMode.Mask,
31+
propertyChanged: OnMaskChanged);
32+
33+
/// <summary>
34+
/// Gets the value of the Mask property for a view.
35+
/// </summary>
36+
public static SessionReplayMaskMode GetMask(BindableObject view) => (SessionReplayMaskMode)view.GetValue(MaskProperty);
37+
38+
/// <summary>
39+
/// Sets the value of the Mask property for a view.
40+
/// </summary>
41+
/// <param name="view">The view element to mask or unmask</param>
42+
/// <param name="value">The value to assign. Can be either "sentry-mask" or "sentry-unmask".</param>
43+
public static void SetMask(BindableObject view, SessionReplayMaskMode value) => view.SetValue(MaskProperty, value);
44+
45+
private static void OnMaskChanged(BindableObject bindable, object oldValue, object newValue)
46+
{
47+
#if __ANDROID__
48+
if (bindable is not VisualElement ve || newValue is not SessionReplayMaskMode maskSetting)
49+
{
50+
return;
51+
}
52+
53+
// This code looks pretty funky... just matching how funky MAUI is though.
54+
// See https://github.com/getsentry/sentry-dotnet/pull/4121#discussion_r2054129378
55+
ve.HandlerChanged -= OnMaskedElementHandlerChanged;
56+
ve.HandlerChanged -= OnUnmaskedElementHandlerChanged;
57+
58+
if (maskSetting == SessionReplayMaskMode.Mask)
59+
{
60+
ve.HandlerChanged += OnMaskedElementHandlerChanged;
61+
}
62+
else if (maskSetting == SessionReplayMaskMode.Unmask)
63+
{
64+
ve.HandlerChanged += OnUnmaskedElementHandlerChanged;
65+
}
66+
#endif
67+
}
68+
69+
#if __ANDROID__
70+
private static void OnMaskedElementHandlerChanged(object? sender, EventArgs _)
71+
{
72+
if ((sender as VisualElement)?.Handler?.PlatformView is not View nativeView)
73+
{
74+
return;
75+
}
76+
77+
nativeView.Tag = SessionReplayMaskMode.Mask.ToNativeTag();
78+
}
79+
80+
private static void OnUnmaskedElementHandlerChanged(object? sender, EventArgs _)
81+
{
82+
if ((sender as VisualElement)?.Handler?.PlatformView is not View nativeView)
83+
{
84+
return;
85+
}
86+
87+
nativeView.Tag = SessionReplayMaskMode.Unmask.ToNativeTag();
88+
}
89+
#endif
90+
}
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace Sentry.Maui;
2+
3+
/// <summary>
4+
/// Controls the masking behaviour of the Session Replay feature.
5+
/// </summary>
6+
public enum SessionReplayMaskMode
7+
{
8+
/// <summary>
9+
/// Masks the view
10+
/// </summary>
11+
Mask,
12+
/// <summary>
13+
/// Unmasks the view
14+
/// </summary>
15+
Unmask
16+
}
17+
18+
internal static class SessionReplayMaskModeExtensions
19+
{
20+
#if __ANDROID__
21+
/// <summary>
22+
/// Maps from <see cref="SessionReplayMaskMode"/> to the native tag values used by the JavaSDK to mask and unmask
23+
/// views. See https://docs.sentry.io/platforms/android/session-replay/privacy/#mask-by-view-instance
24+
/// </summary>
25+
public static string ToNativeTag(this SessionReplayMaskMode maskMode) => maskMode switch
26+
{
27+
SessionReplayMaskMode.Mask => "sentry-mask",
28+
SessionReplayMaskMode.Unmask => "sentry-unmask",
29+
_ => throw new ArgumentOutOfRangeException(nameof(maskMode), maskMode, null)
30+
};
31+
#endif
32+
}

src/Sentry/Platforms/Android/NativeOptions.cs

+12
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,18 @@ public class NativeSentryReplayOptions
272272
public double? SessionSampleRate { get; set; }
273273
public bool MaskAllImages { get; set; } = true;
274274
public bool MaskAllText { get; set; } = true;
275+
internal HashSet<Type> MaskedControls { get; } = [];
276+
internal HashSet<Type> UnmaskedControls { get; } = [];
277+
278+
public void MaskControlsOfType<T>()
279+
{
280+
MaskedControls.Add(typeof(T));
281+
}
282+
283+
public void UnmaskControlsOfType<T>()
284+
{
285+
UnmaskedControls.Add(typeof(T));
286+
}
275287
}
276288

277289
/// <summary>

src/Sentry/Platforms/Native/buildTransitive/Sentry.Native.targets

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
<Project>
1010

1111
<ItemGroup>
12-
<!-- When user sets <DisableSentryNative>true</DisableSentryNative> in their project -->
12+
<!-- When user sets <SentryNative>false</SentryNative> or <SentryNative>disable</SentryNative> in their project -->
1313
<!-- SentryNative.IsEnabled should result in compile-time constant for trimmed applications -->
1414
<!-- Effectively disabling native library -->
1515
<RuntimeHostConfigurationOption Include="Sentry.Native.IsEnabled"
16-
Condition="'$(DisableSentryNative)' != 'true'"
16+
Condition="'$(SentryNative)' != 'false' and '$(SentryNative)' != 'disable'"
1717
Value="true"
1818
Trim="true" />
1919
</ItemGroup>
@@ -22,7 +22,7 @@
2222
<!-- net8.0 or greater -->
2323
<FrameworkSupportsNative Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0')) and ('$(OutputType)' == 'Exe' or '$(OutputType)' == 'WinExe')">true</FrameworkSupportsNative>
2424
<!-- Make it opt-out -->
25-
<FrameworkSupportsNative Condition="'$(DisableSentryNative)' == 'true'">false</FrameworkSupportsNative>
25+
<FrameworkSupportsNative Condition="'$(SentryNative)' == 'false' or '$(SentryNative)' == 'disable'">false</FrameworkSupportsNative>
2626
</PropertyGroup>
2727

2828
<ItemGroup Condition="'$(FrameworkSupportsNative)' == 'true' and '$(RuntimeIdentifier)' == 'win-x64'">

src/Sentry/Sentry.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
<ItemGroup Condition="'$(SolutionName)' == 'Sentry.Unity'">
2424
<InternalsVisibleTo Include="Sentry.Unity" />
25+
<InternalsVisibleTo Include="Sentry.Unity.Tests" />
2526
</ItemGroup>
2627

2728
<!-- Platform-specific props included here -->

src/Sentry/SentryOptions.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1816,8 +1816,11 @@ internal void SetupLogging()
18161816
{
18171817
return null;
18181818
}
1819-
1819+
#if IOS || ANDROID // on iOS or Android the app is already sandboxed so there's no risk of sending data from 1 app to another Sentry's DSN
1820+
return Path.Combine(CacheDirectoryPath, "Sentry");
1821+
#else
18201822
return Path.Combine(CacheDirectoryPath, "Sentry", Dsn.GetHashString());
1823+
#endif
18211824
}
18221825

18231826
internal string? TryGetProcessSpecificCacheDirectoryPath()

src/Sentry/SentrySdk.cs

-7
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,6 @@ internal static IDisposable UseHub(IHub hub)
209209
return new DisposeHandle(hub);
210210
}
211211

212-
/// <summary>
213-
/// Allows to set the trace
214-
/// </summary>
215-
internal static void SetTrace(SentryId traceId, SpanId parentSpanId) =>
216-
CurrentHub.ConfigureScope(scope =>
217-
scope.SetPropagationContext(new SentryPropagationContext(traceId, parentSpanId)));
218-
219212
/// <summary>
220213
/// Flushes the queue of captured events until the timeout set in <see cref="SentryOptions.FlushTimeout"/>
221214
/// is reached.

0 commit comments

Comments
 (0)