Skip to content

Commit b2b1aed

Browse files
committed
Merge branch 'main' into andrey/double-masking
* main: doc: Add using ldMask in readme. (#311) chore: release main (#312) feat: take transformed coordinates, which are more precise in animation (#309) chore: release main (#307) fix(SEC-7530): update react-server-dom-webpack to 19.0.1 (#310) # Conflicts: # sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/capture/CaptureSource.kt # sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/ComposeMaskTarget.kt # sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/Mask.kt # sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/NativeMaskTarget.kt
2 parents 8b2351a + 50723a3 commit b2b1aed

File tree

10 files changed

+135
-57
lines changed

10 files changed

+135
-57
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"go": "0.4.0",
33
"sdk/@launchdarkly/observability": "0.4.9",
4-
"sdk/@launchdarkly/observability-android": "0.17.0",
4+
"sdk/@launchdarkly/observability-android": "0.19.0",
55
"sdk/@launchdarkly/observability-dotnet": "0.3.0",
66
"sdk/@launchdarkly/observability-node": "0.3.1",
77
"sdk/@launchdarkly/observability-python": "0.1.1",

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"typescript": "^5.8.3"
4141
},
4242
"resolutions": {
43-
"ansi-color@^0.2.1": "patch:ansi-color@npm%3A0.2.1#./.yarn/patches/ansi-color-npm-0.2.1-f7243d10a4.patch"
43+
"ansi-color@^0.2.1": "patch:ansi-color@npm%3A0.2.1#./.yarn/patches/ansi-color-npm-0.2.1-f7243d10a4.patch",
44+
"react-server-dom-webpack": "19.0.1"
4445
},
4546
"packageManager": "yarn@4.9.1"
4647
}

sdk/@launchdarkly/observability-android/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## [0.19.0](https://github.com/launchdarkly/observability-sdk/compare/launchdarkly-observability-android-0.18.0...launchdarkly-observability-android-0.19.0) (2025-12-04)
4+
5+
6+
### Features
7+
8+
* take transformed coordinates, which are more precise in animation ([#309](https://github.com/launchdarkly/observability-sdk/issues/309)) ([5d669d4](https://github.com/launchdarkly/observability-sdk/commit/5d669d49a7d412b4edce8e5f5bdc7728243bd2c3))
9+
10+
## [0.18.0](https://github.com/launchdarkly/observability-sdk/compare/launchdarkly-observability-android-0.17.0...launchdarkly-observability-android-0.18.0) (2025-12-04)
11+
12+
13+
### Features
14+
15+
* Android SR Do not send duplicate screens ([#304](https://github.com/launchdarkly/observability-sdk/issues/304)) ([f3369bc](https://github.com/launchdarkly/observability-sdk/commit/f3369bc87f7e1293c8bdabf592693b8365600312))
16+
* recursive mask collection ([#308](https://github.com/launchdarkly/observability-sdk/issues/308)) ([ee9f061](https://github.com/launchdarkly/observability-sdk/commit/ee9f0610d199378b368cd5a91aa259254b27511a))
17+
* support non-standard windows added by WindowManager ([#306](https://github.com/launchdarkly/observability-sdk/issues/306)) ([199374a](https://github.com/launchdarkly/observability-sdk/commit/199374a30c67da7d8151cbb65c8eb1a50545006c))
18+
319
## [0.17.0](https://github.com/launchdarkly/observability-sdk/compare/launchdarkly-observability-android-0.16.0...launchdarkly-observability-android-0.17.0) (2025-11-26)
420

521

sdk/@launchdarkly/observability-android/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,82 @@ span.makeCurrent().use {
167167
span.end()
168168
```
169169

170+
### Session Replay
170171

172+
#### Masking sensitive UI
173+
174+
Use `ldMask()` to mark views that should be masked in session replay. There are helpers for both XML-based Views and Jetpack Compose.
175+
176+
##### XML Views
177+
178+
Import the masking API and call `ldMask()` on any `View` (for example, after inflating the layout in an `Activity` or `Fragment`).
179+
180+
```kotlin
181+
import android.os.Bundle
182+
import android.widget.EditText
183+
import androidx.appcompat.app.AppCompatActivity
184+
import com.launchdarkly.observability.api.ldMask
185+
186+
class LoginActivity : AppCompatActivity() {
187+
override fun onCreate(savedInstanceState: Bundle?) {
188+
super.onCreate(savedInstanceState)
189+
setContentView(R.layout.activity_login)
190+
191+
val password = findViewById<EditText>(R.id.password)
192+
password.ldMask() // mask this field in session replay
193+
}
194+
}
195+
```
196+
197+
With View Binding or Data Binding:
198+
199+
```kotlin
200+
import android.os.Bundle
201+
import android.view.View
202+
import androidx.fragment.app.Fragment
203+
import com.launchdarkly.observability.api.ldMask
204+
205+
class CheckoutFragment : Fragment(R.layout.fragment_checkout) {
206+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
207+
super.onViewCreated(view, savedInstanceState)
208+
val binding = FragmentCheckoutBinding.bind(view)
209+
binding.creditCardNumber.ldMask()
210+
binding.cvv.ldMask()
211+
}
212+
}
213+
```
214+
215+
Optional: use `ldUnmask()` to explicitly clear masking on a view you previously masked.
216+
217+
##### Jetpack Compose
218+
219+
Add the masking `Modifier` to any composable you want masked in session replay.
220+
221+
```kotlin
222+
import androidx.compose.foundation.layout.fillMaxWidth
223+
import androidx.compose.material3.TextField
224+
import androidx.compose.runtime.*
225+
import androidx.compose.ui.Modifier
226+
import com.launchdarkly.observability.api.ldMask
227+
228+
@Composable
229+
fun CreditCardField() {
230+
var number by remember { mutableStateOf("") }
231+
TextField(
232+
value = number,
233+
onValueChange = { number = it },
234+
modifier = Modifier
235+
.fillMaxWidth()
236+
.ldMask() // mask this composable in session replay
237+
)
238+
}
239+
```
240+
241+
Optional: use `Modifier.ldUnmask()` to explicitly clear masking on a composable you previously masked.
242+
243+
Notes:
244+
- Masking marks elements so their contents are obscured in recorded sessions.
245+
- You can apply masking to any `View` or composable where sensitive data may appear.
171246

172247
## Contributing
173248

sdk/@launchdarkly/observability-android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
android.useAndroidX=true
55

66
#x-release-please-start-version
7-
version=0.17.0
7+
version=0.19.0
88
#x-release-please-end

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/capture/CaptureSource.kt

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import kotlin.coroutines.resume
3434
import androidx.core.graphics.withTranslation
3535
import com.launchdarkly.observability.replay.masking.Mask
3636
import androidx.core.graphics.createBitmap
37-
import com.launchdarkly.observability.replay.masking.draw
38-
import java.util.Dictionary
3937
import kotlin.collections.mutableMapOf
4038
import kotlin.math.abs
4139
import kotlin.math.max
@@ -321,30 +319,42 @@ class CaptureSource(
321319
* @param canvas The canvas to mask
322320
* @param afterMasks areas that will be masked
323321
*/
324-
// private fun drawMasks(canvas: Canvas, captureResult: CaptureResult) {
325-
//// val path = Path()
326-
//// captureResult.beforeMasks.forEach { mask ->
327-
//// mask.draw(path, canvas, beforeMasksPaint)
328-
//// }
329-
//// captureResult.afterMasks.forEach { mask ->
330-
//// mask.draw(path, canvas, afterMaskPaint)
331-
//// }
332-
// }
333322

334323
private fun drawMasks(canvas: Canvas, beforeMasks: List<Mask>?, afterMasks: List<Mask>?) {
335324
if (afterMasks == null && beforeMasks == null) return
336325

337326
val path = Path()
338327
beforeMasks?.forEach { mask ->
339-
mask.draw(path, canvas, beforeMaskPaint)
328+
drawMask(mask, path, canvas, beforeMaskPaint)
340329
}
341330
afterMasks?.forEach { mask ->
342-
mask.draw(path, canvas, afterMaskPaint)
331+
drawMask(mask, path, canvas, afterMaskPaint)
343332
}
344333
}
345334

346-
fun areMasksMapsStable(
335+
private val maskIntRect = Rect()
336+
private fun drawMask(mask: Mask, path: Path, canvas: Canvas, paint: Paint) {
337+
if (mask.points != null) {
338+
val pts = mask.points
339+
340+
path.reset()
341+
path.moveTo(pts[0], pts[1])
342+
path.lineTo(pts[2], pts[3])
343+
path.lineTo(pts[4], pts[5])
344+
path.lineTo(pts[6], pts[7])
345+
path.close()
346+
347+
canvas.drawPath(path, paint)
348+
} else {
349+
maskIntRect.left = mask.rect.left.toInt()
350+
maskIntRect.top = mask.rect.top.toInt()
351+
maskIntRect.right = mask.rect.right.toInt()
352+
maskIntRect.bottom = mask.rect.bottom.toInt()
353+
canvas.drawRect(maskIntRect, paint)
354+
}
355+
}
347356

357+
fun areMasksMapsStable(
348358
beforeMasksMap: Map<Int, List<Mask>>,
349359
afterMasksMap: Map<Int, List<Mask>>
350360
): Map<Int, List<Mask>>? {

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/ComposeMaskTarget.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ data class ComposeMaskTarget(
108108
}
109109

110110
// return 4 points of polygon under transformations
111-
fun points(context: MaskContext): FloatArray? {
111+
private fun points(context: MaskContext): FloatArray? {
112112
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
113113
return null
114114
}
Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package com.launchdarkly.observability.replay.masking
2-
import android.graphics.Path
3-
import android.graphics.Rect
2+
43
import android.graphics.RectF
5-
import android.graphics.Canvas
6-
import android.graphics.Paint
74

85
data class Mask(
96
val rect: RectF,
107
val viewId: Int,
11-
val points: FloatArray? = null) {
8+
val points: FloatArray? = null
9+
) {
1210
// Implemented to suppress warning
1311
override fun equals(other: Any?): Boolean {
1412
if (this === other) return true
@@ -25,27 +23,4 @@ data class Mask(
2523
result = 31 * result + points.contentHashCode()
2624
return result
2725
}
28-
}
29-
30-
fun Mask.draw(path: Path, canvas: Canvas, paint: Paint) {
31-
if (points != null) {
32-
val pts = points
33-
34-
path.reset()
35-
path.moveTo(pts[0], pts[1])
36-
path.lineTo(pts[2], pts[3])
37-
path.lineTo(pts[4], pts[5])
38-
path.lineTo(pts[6], pts[7])
39-
path.close()
40-
41-
canvas.drawPath(path, paint)
42-
} else {
43-
val intRect = Rect(
44-
rect.left.toInt(),
45-
rect.top.toInt(),
46-
rect.right.toInt(),
47-
rect.bottom.toInt()
48-
)
49-
canvas.drawRect(intRect, paint)
50-
}
5126
}

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/NativeMaskTarget.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ data class NativeMaskTarget(
6969
}
7070

7171
// return 4 points of polygon under transformations
72-
fun points(context: MaskContext): FloatArray? {
72+
private fun points(context: MaskContext): FloatArray? {
7373
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
7474
return null
7575
}

yarn.lock

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39290,17 +39290,18 @@ __metadata:
3929039290
languageName: unknown
3929139291
linkType: soft
3929239292

39293-
"react-server-dom-webpack@npm:19.0.0-rc-6230622a1a-20240610":
39294-
version: 19.0.0-rc-6230622a1a-20240610
39295-
resolution: "react-server-dom-webpack@npm:19.0.0-rc-6230622a1a-20240610"
39293+
"react-server-dom-webpack@npm:19.0.1":
39294+
version: 19.0.1
39295+
resolution: "react-server-dom-webpack@npm:19.0.1"
3929639296
dependencies:
3929739297
acorn-loose: "npm:^8.3.0"
3929839298
neo-async: "npm:^2.6.1"
39299+
webpack-sources: "npm:^3.2.0"
3929939300
peerDependencies:
39300-
react: 19.0.0-rc-6230622a1a-20240610
39301-
react-dom: 19.0.0-rc-6230622a1a-20240610
39301+
react: ^19.0.1
39302+
react-dom: ^19.0.1
3930239303
webpack: ^5.59.0
39303-
checksum: 10/66ae78f359bcd09400d39f25341c9f799b33cbdb4c7a1134c07ee34d7d142c240bd0dea83942c9260c3e3be6de0bc2bf0ed1343e4f044eeabcf6e5d3c1172c79
39304+
checksum: 10/bf457509c1201b2bd5080beee6ffa50a2dff70e0ff8130e95bb39d75e51a2bbfad4b72acd732ac4f8dd7f91ec202986b44f68f78a81d1acd266279e1ce63721c
3930439305
languageName: node
3930539306
linkType: hard
3930639307

@@ -46330,10 +46331,10 @@ __metadata:
4633046331
languageName: node
4633146332
linkType: hard
4633246333

46333-
"webpack-sources@npm:^3.0.0, webpack-sources@npm:^3.2.3":
46334-
version: 3.2.3
46335-
resolution: "webpack-sources@npm:3.2.3"
46336-
checksum: 10/a661f41795d678b7526ae8a88cd1b3d8ce71a7d19b6503da8149b2e667fc7a12f9b899041c1665d39e38245ed3a59ab68de648ea31040c3829aa695a5a45211d
46334+
"webpack-sources@npm:^3.0.0, webpack-sources@npm:^3.2.0, webpack-sources@npm:^3.2.3":
46335+
version: 3.3.3
46336+
resolution: "webpack-sources@npm:3.3.3"
46337+
checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91
4633746338
languageName: node
4633846339
linkType: hard
4633946340

0 commit comments

Comments
 (0)