Skip to content

Commit 11ece9d

Browse files
authored
Merge branch 'main' into dependabot/github_actions/getsentry/craft-2.21.2
2 parents 6a46705 + f9f2ee3 commit 11ece9d

File tree

7 files changed

+129
-7
lines changed

7 files changed

+129
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
- [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0126)
1313
- [diff](https://github.com/getsentry/sentry-native/compare/0.12.4...0.12.6)
1414

15+
### Internal
16+
17+
- Add integration to track session replay custom masking ([#5070](https://github.com/getsentry/sentry-java/pull/5070))
18+
1519
## 8.32.0
1620

1721
### Features

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ internal object ComposeViewHierarchyNode {
8585
): Boolean {
8686
val sentryPrivacyModifier = this?.getOrNull(SentryReplayModifiers.SentryPrivacy)
8787
if (sentryPrivacyModifier == "unmask") {
88+
options.sessionReplay.trackCustomMasking()
8889
return false
8990
}
9091

9192
if (sentryPrivacyModifier == "mask") {
93+
options.sessionReplay.trackCustomMasking()
9294
return true
9395
}
9496

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,15 @@ internal sealed class ViewHierarchyNode(
291291
(tag as? String)?.lowercase()?.contains(SENTRY_UNMASK_TAG) == true ||
292292
getTag(R.id.sentry_privacy) == "unmask"
293293
) {
294+
options.sessionReplay.trackCustomMasking()
294295
return false
295296
}
296297

297298
if (
298299
(tag as? String)?.lowercase()?.contains(SENTRY_MASK_TAG) == true ||
299300
getTag(R.id.sentry_privacy) == "mask"
300301
) {
302+
options.sessionReplay.trackCustomMasking()
301303
return true
302304
}
303305

sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,5 +256,8 @@
256256
<meta-data
257257
android:name="io.sentry.spotlight.enable"
258258
android:value="true" />
259+
<meta-data
260+
android:name="io.sentry.tombstone.enable"
261+
android:value="true" />
259262
</application>
260263
</manifest>

sentry/api/sentry.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3953,6 +3953,7 @@ public final class io/sentry/SentryReplayOptions {
39533953
public fun setSessionSampleRate (Ljava/lang/Double;)V
39543954
public fun setTrackConfiguration (Z)V
39553955
public fun setUnmaskViewContainerClass (Ljava/lang/String;)V
3956+
public fun trackCustomMasking ()V
39563957
}
39573958

39583959
public final class io/sentry/SentryReplayOptions$SentryReplayQuality : java/lang/Enum {

sentry/src/main/java/io/sentry/SentryReplayOptions.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.sentry;
22

3+
import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;
4+
35
import io.sentry.protocol.SdkVersion;
46
import io.sentry.util.SampleRateUtils;
57
import java.util.ArrayList;
@@ -16,6 +18,9 @@
1618

1719
public final class SentryReplayOptions {
1820

21+
private static final String CUSTOM_MASKING_INTEGRATION_NAME = "ReplayCustomMasking";
22+
private volatile boolean customMaskingTracked = false;
23+
1924
public static final String TEXT_VIEW_CLASS_NAME = "android.widget.TextView";
2025
public static final String IMAGE_VIEW_CLASS_NAME = "android.widget.ImageView";
2126
public static final String WEB_VIEW_CLASS_NAME = "android.webkit.WebView";
@@ -209,8 +214,9 @@ public enum SentryReplayQuality {
209214

210215
public SentryReplayOptions(final boolean empty, final @Nullable SdkVersion sdkVersion) {
211216
if (!empty) {
212-
setMaskAllText(true);
213-
setMaskAllImages(true);
217+
// Add default mask classes directly without setting usingCustomMasking flag
218+
maskViewClasses.add(TEXT_VIEW_CLASS_NAME);
219+
maskViewClasses.add(IMAGE_VIEW_CLASS_NAME);
214220
maskViewClasses.add(WEB_VIEW_CLASS_NAME);
215221
maskViewClasses.add(VIDEO_VIEW_CLASS_NAME);
216222
maskViewClasses.add(ANDROIDX_MEDIA_VIEW_CLASS_NAME);
@@ -276,10 +282,11 @@ public void setSessionSampleRate(final @Nullable Double sessionSampleRate) {
276282
*/
277283
public void setMaskAllText(final boolean maskAllText) {
278284
if (maskAllText) {
279-
addMaskViewClass(TEXT_VIEW_CLASS_NAME);
285+
maskViewClasses.add(TEXT_VIEW_CLASS_NAME);
280286
unmaskViewClasses.remove(TEXT_VIEW_CLASS_NAME);
281287
} else {
282-
addUnmaskViewClass(TEXT_VIEW_CLASS_NAME);
288+
trackCustomMasking();
289+
unmaskViewClasses.add(TEXT_VIEW_CLASS_NAME);
283290
maskViewClasses.remove(TEXT_VIEW_CLASS_NAME);
284291
}
285292
}
@@ -294,10 +301,11 @@ public void setMaskAllText(final boolean maskAllText) {
294301
*/
295302
public void setMaskAllImages(final boolean maskAllImages) {
296303
if (maskAllImages) {
297-
addMaskViewClass(IMAGE_VIEW_CLASS_NAME);
304+
maskViewClasses.add(IMAGE_VIEW_CLASS_NAME);
298305
unmaskViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
299306
} else {
300-
addUnmaskViewClass(IMAGE_VIEW_CLASS_NAME);
307+
trackCustomMasking();
308+
unmaskViewClasses.add(IMAGE_VIEW_CLASS_NAME);
301309
maskViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
302310
}
303311
}
@@ -308,6 +316,7 @@ public Set<String> getMaskViewClasses() {
308316
}
309317

310318
public void addMaskViewClass(final @NotNull String className) {
319+
trackCustomMasking();
311320
this.maskViewClasses.add(className);
312321
}
313322

@@ -317,6 +326,7 @@ public Set<String> getUnmaskViewClasses() {
317326
}
318327

319328
public void addUnmaskViewClass(final @NotNull String className) {
329+
trackCustomMasking();
320330
this.unmaskViewClasses.add(className);
321331
}
322332

@@ -351,7 +361,7 @@ public long getSessionDuration() {
351361

352362
@ApiStatus.Internal
353363
public void setMaskViewContainerClass(@NotNull String containerClass) {
354-
addMaskViewClass(containerClass);
364+
maskViewClasses.add(containerClass);
355365
maskViewContainerClass = containerClass;
356366
}
357367

@@ -370,6 +380,14 @@ public void setUnmaskViewContainerClass(@NotNull String containerClass) {
370380
return unmaskViewContainerClass;
371381
}
372382

383+
@ApiStatus.Internal
384+
public void trackCustomMasking() {
385+
if (!customMaskingTracked) {
386+
customMaskingTracked = true;
387+
addIntegrationToSdkVersion(CUSTOM_MASKING_INTEGRATION_NAME);
388+
}
389+
}
390+
373391
@ApiStatus.Internal
374392
public boolean isTrackConfiguration() {
375393
return trackConfiguration;

sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package io.sentry
22

3+
import kotlin.test.BeforeTest
34
import kotlin.test.Test
45
import kotlin.test.assertEquals
6+
import kotlin.test.assertFalse
57
import kotlin.test.assertTrue
68

79
class SentryReplayOptionsTest {
10+
11+
@BeforeTest
12+
fun setup() {
13+
SentryIntegrationPackageStorage.getInstance().clearStorage()
14+
}
15+
816
@Test
917
fun `uses medium quality as default`() {
1018
val replayOptions = SentryReplayOptions(true, null)
@@ -126,4 +134,88 @@ class SentryReplayOptionsTest {
126134
assertTrue(headers.contains("X-Response-Header"))
127135
assertTrue(headers.contains("X-Debug-Header"))
128136
}
137+
138+
// Custom Masking Integration Tests
139+
140+
private fun hasCustomMaskingIntegration(): Boolean {
141+
return SentryIntegrationPackageStorage.getInstance()
142+
.integrations
143+
.contains("ReplayCustomMasking")
144+
}
145+
146+
@Test
147+
fun `default options does not add ReplayCustomMasking integration`() {
148+
SentryReplayOptions(false, null)
149+
assertFalse(hasCustomMaskingIntegration())
150+
}
151+
152+
@Test
153+
fun `empty options does not add ReplayCustomMasking integration`() {
154+
SentryReplayOptions(true, null)
155+
assertFalse(hasCustomMaskingIntegration())
156+
}
157+
158+
@Test
159+
fun `addUnmaskViewClass adds ReplayCustomMasking integration`() {
160+
val options = SentryReplayOptions(false, null)
161+
options.addUnmaskViewClass("com.example.MyTextView")
162+
assertTrue(hasCustomMaskingIntegration())
163+
}
164+
165+
@Test
166+
fun `setMaskViewContainerClass does not add ReplayCustomMasking integration`() {
167+
val options = SentryReplayOptions(false, null)
168+
options.setMaskViewContainerClass("com.example.MyContainer")
169+
assertFalse(hasCustomMaskingIntegration())
170+
}
171+
172+
@Test
173+
fun `setUnmaskViewContainerClass does not add ReplayCustomMasking integration`() {
174+
val options = SentryReplayOptions(false, null)
175+
options.setUnmaskViewContainerClass("com.example.MyContainer")
176+
assertFalse(hasCustomMaskingIntegration())
177+
}
178+
179+
@Test
180+
fun `setMaskAllText true does not set custom integration`() {
181+
val options = SentryReplayOptions(false, null)
182+
options.setMaskAllText(true)
183+
options.setMaskAllImages(true)
184+
assertFalse(hasCustomMaskingIntegration())
185+
}
186+
187+
@Test
188+
fun `trackCustomMasking only adds integration once`() {
189+
val options = SentryReplayOptions(false, null)
190+
options.setMaskAllText(false)
191+
options.setMaskAllImages(false)
192+
assertTrue(hasCustomMaskingIntegration())
193+
assertEquals(
194+
1,
195+
SentryIntegrationPackageStorage.getInstance().integrations.count {
196+
it == "ReplayCustomMasking"
197+
},
198+
)
199+
}
200+
201+
@Test
202+
fun `addMaskViewClass adds ReplayCustomMasking integration`() {
203+
val options = SentryReplayOptions(false, null)
204+
options.addMaskViewClass("com.example.MySensitiveView")
205+
assertTrue(hasCustomMaskingIntegration())
206+
}
207+
208+
@Test
209+
fun `setMaskAllText adds ReplayCustomMasking integration`() {
210+
val options = SentryReplayOptions(false, null)
211+
options.setMaskAllText(false)
212+
assertTrue(hasCustomMaskingIntegration())
213+
}
214+
215+
@Test
216+
fun `setMaskAllImages adds ReplayCustomMasking integration`() {
217+
val options = SentryReplayOptions(false, null)
218+
options.setMaskAllImages(false)
219+
assertTrue(hasCustomMaskingIntegration())
220+
}
129221
}

0 commit comments

Comments
 (0)