@datadog/mobile-react-native-session-replay 2.4.3-alpha.0 → 2.5.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DatadogSDKReactNativeSessionReplay.podspec +1 -1
- package/android/build.gradle +16 -6
- package/android/consumer-proguard-rules.pro +3 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +34 -14
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +15 -4
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +8 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayPrivacySettings.kt +90 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +14 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +10 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt +190 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +84 -10
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt +106 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +4 -5
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/resources/ReactDrawableCopier.kt +34 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +10 -23
- package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +28 -4
- package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +35 -3
- package/android/src/{main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt → rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt} +6 -8
- package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +13 -0
- package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
- package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +88 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +67 -9
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +7 -3
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +2 -7
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +2 -1
- package/ios/Sources/DdSessionReplay.mm +46 -4
- package/ios/Sources/DdSessionReplayImplementation.swift +83 -11
- package/ios/Sources/RCTTextViewRecorder.swift +1 -1
- package/lib/commonjs/SessionReplay.js +78 -10
- package/lib/commonjs/SessionReplay.js.map +1 -1
- package/lib/commonjs/index.js +18 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/module/SessionReplay.js +77 -9
- package/lib/module/SessionReplay.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NativeDdSessionReplay.js.map +1 -1
- package/lib/typescript/SessionReplay.d.ts +73 -4
- package/lib/typescript/SessionReplay.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/nativeModulesTypes.d.ts +18 -3
- package/lib/typescript/nativeModulesTypes.d.ts.map +1 -1
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -2
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/SessionReplay.ts +170 -23
- package/src/__tests__/SessionReplay.test.ts +94 -8
- package/src/index.ts +14 -2
- package/src/nativeModulesTypes.ts +27 -4
- package/src/specs/NativeDdSessionReplay.ts +21 -3
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +0 -66
|
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
|
|
|
19
19
|
s.dependency "React-Core"
|
|
20
20
|
|
|
21
21
|
# /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec
|
|
22
|
-
s.dependency 'DatadogSessionReplay', '~> 2.
|
|
22
|
+
s.dependency 'DatadogSessionReplay', '~> 2.18.0'
|
|
23
23
|
s.dependency 'DatadogSDKReactNative'
|
|
24
24
|
|
|
25
25
|
s.test_spec 'Tests' do |test_spec|
|
package/android/build.gradle
CHANGED
|
@@ -12,7 +12,7 @@ buildscript {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
dependencies {
|
|
15
|
-
classpath 'com.android.tools.build:gradle:7.
|
|
15
|
+
classpath 'com.android.tools.build:gradle:7.3.1'
|
|
16
16
|
// noinspection DifferentKotlinGradleVersion
|
|
17
17
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
18
18
|
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.5.1"
|
|
@@ -96,15 +96,19 @@ def getExtOrIntegerDefault(name) {
|
|
|
96
96
|
android {
|
|
97
97
|
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
|
98
98
|
buildToolsVersion getExtOrDefault('buildToolsVersion')
|
|
99
|
+
|
|
99
100
|
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
101
|
+
|
|
100
102
|
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
|
|
101
103
|
namespace = "com.datadog.reactnative.sessionreplay"
|
|
102
104
|
}
|
|
105
|
+
|
|
103
106
|
if (agpVersion.tokenize('.')[0].toInteger() >= 8) {
|
|
104
107
|
buildFeatures {
|
|
105
108
|
buildConfig = true
|
|
106
109
|
}
|
|
107
110
|
}
|
|
111
|
+
|
|
108
112
|
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
|
109
113
|
compileOptions {
|
|
110
114
|
sourceCompatibility JavaVersion.VERSION_11
|
|
@@ -130,6 +134,14 @@ android {
|
|
|
130
134
|
} else {
|
|
131
135
|
java.srcDirs += ['src/oldarch/kotlin']
|
|
132
136
|
}
|
|
137
|
+
|
|
138
|
+
if (reactNativeMinorVersion >= 76) {
|
|
139
|
+
java.srcDirs += ['src/rn76/kotlin']
|
|
140
|
+
} else if (reactNativeMinorVersion >= 75) {
|
|
141
|
+
java.srcDirs += ['src/rn75/kotlin']
|
|
142
|
+
} else {
|
|
143
|
+
java.srcDirs += ['src/rnlegacy/kotlin']
|
|
144
|
+
}
|
|
133
145
|
}
|
|
134
146
|
test {
|
|
135
147
|
java.srcDir("src/test/kotlin")
|
|
@@ -145,15 +157,12 @@ android {
|
|
|
145
157
|
buildTypes {
|
|
146
158
|
release {
|
|
147
159
|
minifyEnabled false
|
|
160
|
+
consumerProguardFiles 'consumer-proguard-rules.pro'
|
|
148
161
|
}
|
|
149
162
|
}
|
|
150
163
|
lintOptions {
|
|
151
164
|
disable 'GradleCompatible'
|
|
152
165
|
}
|
|
153
|
-
compileOptions {
|
|
154
|
-
sourceCompatibility JavaVersion.VERSION_11
|
|
155
|
-
targetCompatibility JavaVersion.VERSION_11
|
|
156
|
-
}
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
repositories {
|
|
@@ -188,7 +197,8 @@ dependencies {
|
|
|
188
197
|
api "com.facebook.react:react-android:$reactNativeVersion"
|
|
189
198
|
}
|
|
190
199
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
191
|
-
implementation "com.datadoghq:dd-sdk-android-session-replay:2.
|
|
200
|
+
implementation "com.datadoghq:dd-sdk-android-session-replay:2.16.1"
|
|
201
|
+
implementation "com.datadoghq:dd-sdk-android-internal:2.16.1"
|
|
192
202
|
implementation project(path: ':datadog_mobile-react-native')
|
|
193
203
|
|
|
194
204
|
testImplementation "org.junit.platform:junit-platform-launcher:1.6.2"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
DatadogSDKReactNativeSessionReplay_kotlinVersion=1.
|
|
1
|
+
DatadogSDKReactNativeSessionReplay_kotlinVersion=1.8.21
|
|
2
2
|
DatadogSDKReactNativeSessionReplay_compileSdkVersion=33
|
|
3
3
|
DatadogSDKReactNativeSessionReplay_buildToolsVersion=33.0.0
|
|
4
4
|
DatadogSDKReactNativeSessionReplay_targetSdkVersion=33
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay
|
|
8
8
|
|
|
9
|
-
import com.datadog.android.Datadog
|
|
10
9
|
import com.datadog.android.api.feature.FeatureSdkCore
|
|
10
|
+
import com.datadog.android.sessionreplay.ImagePrivacy
|
|
11
11
|
import com.datadog.android.sessionreplay.SessionReplayConfiguration
|
|
12
|
-
import com.datadog.android.sessionreplay.
|
|
12
|
+
import com.datadog.android.sessionreplay.TextAndInputPrivacy
|
|
13
|
+
import com.datadog.android.sessionreplay.TouchPrivacy
|
|
13
14
|
import com.datadog.reactnative.DatadogSDKWrapperStorage
|
|
14
15
|
import com.facebook.react.bridge.Promise
|
|
15
16
|
import com.facebook.react.bridge.ReactContext
|
|
@@ -27,14 +28,25 @@ class DdSessionReplayImplementation(
|
|
|
27
28
|
/**
|
|
28
29
|
* Enable session replay and start recording session.
|
|
29
30
|
* @param replaySampleRate The sample rate applied for session replay.
|
|
30
|
-
* @param defaultPrivacyLevel The privacy level used for replay.
|
|
31
31
|
* @param customEndpoint Custom server url for sending replay data.
|
|
32
|
+
* @param privacySettings Defines the way visual elements should be masked.
|
|
33
|
+
* @param customEndpoint Custom server url for sending replay data.
|
|
34
|
+
* @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
|
|
32
35
|
*/
|
|
33
|
-
fun enable(
|
|
36
|
+
fun enable(
|
|
37
|
+
replaySampleRate: Double,
|
|
38
|
+
customEndpoint: String,
|
|
39
|
+
privacySettings: SessionReplayPrivacySettings,
|
|
40
|
+
startRecordingImmediately: Boolean,
|
|
41
|
+
promise: Promise
|
|
42
|
+
) {
|
|
34
43
|
val sdkCore = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
35
44
|
val logger = sdkCore.internalLogger
|
|
36
45
|
val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
|
|
37
|
-
.
|
|
46
|
+
.startRecordingImmediately(startRecordingImmediately)
|
|
47
|
+
.setImagePrivacy(privacySettings.imagePrivacyLevel)
|
|
48
|
+
.setTouchPrivacy(privacySettings.touchPrivacyLevel)
|
|
49
|
+
.setTextAndInputPrivacy(privacySettings.textAndInputPrivacyLevel)
|
|
38
50
|
.addExtensionSupport(ReactNativeSessionReplayExtensionSupport(reactContext, logger))
|
|
39
51
|
|
|
40
52
|
if (customEndpoint != "") {
|
|
@@ -45,16 +57,24 @@ class DdSessionReplayImplementation(
|
|
|
45
57
|
promise.resolve(null)
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Manually start recording the current session.
|
|
62
|
+
*/
|
|
63
|
+
fun startRecording(promise: Promise) {
|
|
64
|
+
sessionReplayProvider().startRecording(
|
|
65
|
+
DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
66
|
+
)
|
|
67
|
+
promise.resolve(null)
|
|
68
|
+
}
|
|
57
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Manually stop recording the current session.
|
|
72
|
+
*/
|
|
73
|
+
fun stopRecording(promise: Promise) {
|
|
74
|
+
sessionReplayProvider().stopRecording(
|
|
75
|
+
DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
|
|
76
|
+
)
|
|
77
|
+
promise.resolve(null)
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
companion object {
|
|
@@ -11,11 +11,14 @@ import com.datadog.android.api.InternalLogger
|
|
|
11
11
|
import com.datadog.android.sessionreplay.ExtensionSupport
|
|
12
12
|
import com.datadog.android.sessionreplay.MapperTypeWrapper
|
|
13
13
|
import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
|
|
14
|
+
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
|
|
14
15
|
import com.datadog.reactnative.sessionreplay.mappers.ReactEditTextMapper
|
|
16
|
+
import com.datadog.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper
|
|
15
17
|
import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
|
|
16
18
|
import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
|
|
17
19
|
import com.facebook.react.bridge.ReactContext
|
|
18
20
|
import com.facebook.react.uimanager.UIManagerModule
|
|
21
|
+
import com.facebook.react.views.image.ReactImageView
|
|
19
22
|
import com.facebook.react.views.text.ReactTextView
|
|
20
23
|
import com.facebook.react.views.textinput.ReactEditText
|
|
21
24
|
import com.facebook.react.views.view.ReactViewGroup
|
|
@@ -24,21 +27,21 @@ internal class ReactNativeSessionReplayExtensionSupport(
|
|
|
24
27
|
private val reactContext: ReactContext,
|
|
25
28
|
private val logger: InternalLogger
|
|
26
29
|
) : ExtensionSupport {
|
|
30
|
+
override fun name(): String {
|
|
31
|
+
return ReactNativeSessionReplayExtensionSupport::class.java.simpleName
|
|
32
|
+
}
|
|
27
33
|
|
|
28
34
|
override fun getCustomViewMappers(): List<MapperTypeWrapper<*>> {
|
|
29
35
|
val uiManagerModule = getUiManagerModule()
|
|
30
36
|
|
|
31
37
|
return listOf(
|
|
38
|
+
MapperTypeWrapper(ReactImageView::class.java, ReactNativeImageViewMapper()),
|
|
32
39
|
MapperTypeWrapper(ReactViewGroup::class.java, ReactViewGroupMapper()),
|
|
33
40
|
MapperTypeWrapper(ReactTextView::class.java, ReactTextMapper(reactContext, uiManagerModule)),
|
|
34
41
|
MapperTypeWrapper(ReactEditText::class.java, ReactEditTextMapper(reactContext, uiManagerModule)),
|
|
35
42
|
)
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
|
|
39
|
-
return listOf()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
45
|
@VisibleForTesting
|
|
43
46
|
internal fun getUiManagerModule(): UIManagerModule? {
|
|
44
47
|
return try {
|
|
@@ -54,6 +57,14 @@ internal class ReactNativeSessionReplayExtensionSupport(
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
|
|
61
|
+
return listOf()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
|
|
65
|
+
return emptyList()
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
internal companion object {
|
|
58
69
|
internal const val RESOLVE_UIMANAGERMODULE_ERROR = "Unable to resolve UIManagerModule"
|
|
59
70
|
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt
CHANGED
|
@@ -6,28 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
package com.datadog.reactnative.sessionreplay
|
|
8
8
|
|
|
9
|
+
import ReactViewBackgroundDrawableUtils
|
|
9
10
|
import android.view.Gravity
|
|
10
11
|
import android.widget.TextView
|
|
11
12
|
import androidx.annotation.VisibleForTesting
|
|
13
|
+
import com.datadog.android.internal.utils.densityNormalized
|
|
12
14
|
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
13
|
-
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
|
|
14
15
|
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
15
|
-
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
16
16
|
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
|
|
17
17
|
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
18
18
|
import com.facebook.react.bridge.ReactContext
|
|
19
19
|
import com.facebook.react.uimanager.UIManagerModule
|
|
20
20
|
import com.facebook.react.views.text.TextAttributes
|
|
21
|
-
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
22
21
|
import java.util.Locale
|
|
23
22
|
|
|
24
23
|
internal class ReactTextPropertiesResolver(
|
|
25
24
|
private val reactContext: ReactContext,
|
|
26
25
|
private val uiManagerModule: UIManagerModule,
|
|
27
26
|
private val reflectionUtils: ReflectionUtils = ReflectionUtils(),
|
|
28
|
-
private val
|
|
29
|
-
ReactViewBackgroundDrawableUtils()
|
|
30
|
-
private val drawableUtils: DrawableUtils = DrawableUtils()
|
|
27
|
+
private val drawableUtils: DrawableUtils =
|
|
28
|
+
ReactViewBackgroundDrawableUtils()
|
|
31
29
|
): TextPropertiesResolver {
|
|
32
30
|
override fun addReactNativeProperties(
|
|
33
31
|
originalWireframe: MobileSegment.Wireframe.TextWireframe,
|
|
@@ -94,14 +92,14 @@ internal class ReactTextPropertiesResolver(
|
|
|
94
92
|
view: TextView,
|
|
95
93
|
pixelDensity: Float,
|
|
96
94
|
): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>? {
|
|
97
|
-
val backgroundDrawable
|
|
98
|
-
|
|
95
|
+
val backgroundDrawable = drawableUtils
|
|
96
|
+
.getReactBackgroundFromDrawable(view.background) ?: return null
|
|
99
97
|
|
|
100
98
|
// view.alpha is the value of the opacity prop on the js side
|
|
101
99
|
val opacity = view.alpha
|
|
102
100
|
|
|
103
101
|
val (shapeStyle, border) =
|
|
104
|
-
|
|
102
|
+
drawableUtils
|
|
105
103
|
.resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
|
|
106
104
|
|
|
107
105
|
return shapeStyle to border
|
|
@@ -136,7 +134,7 @@ internal class ReactTextPropertiesResolver(
|
|
|
136
134
|
val fontFamily = getFontFamily(shadowNodeWrapper)
|
|
137
135
|
?: textWireframe.textStyle.family
|
|
138
136
|
val fontSize = getFontSize(shadowNodeWrapper)
|
|
139
|
-
?.
|
|
137
|
+
?.densityNormalized(pixelsDensity)
|
|
140
138
|
?: textWireframe.textStyle.size
|
|
141
139
|
val fontColor = getTextColor(shadowNodeWrapper)
|
|
142
140
|
?: textWireframe.textStyle.color
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package com.datadog.reactnative.sessionreplay
|
|
8
|
+
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import com.datadog.android.sessionreplay.ImagePrivacy
|
|
11
|
+
import com.datadog.android.sessionreplay.TextAndInputPrivacy
|
|
12
|
+
import com.datadog.android.sessionreplay.TouchPrivacy
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A utility class to store Session Replay privacy settings, and convert them from string to
|
|
16
|
+
* enum values.
|
|
17
|
+
*
|
|
18
|
+
* @param imagePrivacyLevel Defines the way images should be masked.
|
|
19
|
+
* @param touchPrivacyLevel Defines the way user touches should be masked.
|
|
20
|
+
* @param textAndInputPrivacyLevel Defines the way text and input should be masked.
|
|
21
|
+
*/
|
|
22
|
+
class SessionReplayPrivacySettings(
|
|
23
|
+
imagePrivacyLevel: String,
|
|
24
|
+
touchPrivacyLevel: String,
|
|
25
|
+
textAndInputPrivacyLevel: String
|
|
26
|
+
){
|
|
27
|
+
/**
|
|
28
|
+
* Defines the way images should be masked.
|
|
29
|
+
*/
|
|
30
|
+
val imagePrivacyLevel = getImagePrivacy(imagePrivacyLevel)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Defines the way user touches should be masked.
|
|
34
|
+
*/
|
|
35
|
+
val touchPrivacyLevel = getTouchPrivacy(touchPrivacyLevel)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Defines the way text and input should be masked.
|
|
39
|
+
*/
|
|
40
|
+
val textAndInputPrivacyLevel = getTextAndInputPrivacy(textAndInputPrivacyLevel)
|
|
41
|
+
|
|
42
|
+
companion object {
|
|
43
|
+
internal fun getImagePrivacy(imagePrivacyLevel: String): ImagePrivacy {
|
|
44
|
+
return when (imagePrivacyLevel) {
|
|
45
|
+
"MASK_NON_BUNDLED_ONLY" -> ImagePrivacy.MASK_LARGE_ONLY
|
|
46
|
+
"MASK_ALL" -> ImagePrivacy.MASK_ALL
|
|
47
|
+
"MASK_NONE" -> ImagePrivacy.MASK_NONE
|
|
48
|
+
else -> {
|
|
49
|
+
Log.w(
|
|
50
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
51
|
+
"Unknown Session Replay Image Privacy Level given: $imagePrivacyLevel, " +
|
|
52
|
+
"using ${ImagePrivacy.MASK_ALL} as default"
|
|
53
|
+
)
|
|
54
|
+
ImagePrivacy.MASK_ALL
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
internal fun getTouchPrivacy(touchPrivacyLevel: String): TouchPrivacy {
|
|
60
|
+
return when (touchPrivacyLevel) {
|
|
61
|
+
"SHOW" -> TouchPrivacy.SHOW
|
|
62
|
+
"HIDE" -> TouchPrivacy.HIDE
|
|
63
|
+
else -> {
|
|
64
|
+
Log.w(
|
|
65
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
66
|
+
"Unknown Session Replay Touch Privacy Level given: $touchPrivacyLevel, " +
|
|
67
|
+
"using ${TouchPrivacy.HIDE} as default"
|
|
68
|
+
)
|
|
69
|
+
TouchPrivacy.HIDE
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
internal fun getTextAndInputPrivacy(textAndInputPrivacyLevel: String): TextAndInputPrivacy {
|
|
75
|
+
return when (textAndInputPrivacyLevel) {
|
|
76
|
+
"MASK_SENSITIVE_INPUTS" -> TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
|
|
77
|
+
"MASK_ALL_INPUTS" -> TextAndInputPrivacy.MASK_ALL_INPUTS
|
|
78
|
+
"MASK_ALL" -> TextAndInputPrivacy.MASK_ALL
|
|
79
|
+
else -> {
|
|
80
|
+
Log.w(
|
|
81
|
+
SessionReplayPrivacySettings::class.java.canonicalName,
|
|
82
|
+
"Unknown Session Replay Text And Input Privacy Level given: $textAndInputPrivacyLevel, " +
|
|
83
|
+
"using ${TextAndInputPrivacy.MASK_ALL} as default"
|
|
84
|
+
)
|
|
85
|
+
TextAndInputPrivacy.MASK_ALL
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt
CHANGED
|
@@ -24,4 +24,18 @@ internal class SessionReplaySDKWrapper : SessionReplayWrapper {
|
|
|
24
24
|
sdkCore,
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Manually start recording the current session.
|
|
30
|
+
*/
|
|
31
|
+
override fun startRecording(sdkCore: SdkCore) {
|
|
32
|
+
SessionReplay.startRecording(sdkCore)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Manually stop recording the current session.
|
|
37
|
+
*/
|
|
38
|
+
override fun stopRecording(sdkCore: SdkCore) {
|
|
39
|
+
SessionReplay.stopRecording(sdkCore)
|
|
40
|
+
}
|
|
27
41
|
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt
CHANGED
|
@@ -21,4 +21,14 @@ interface SessionReplayWrapper {
|
|
|
21
21
|
sessionReplayConfiguration: SessionReplayConfiguration,
|
|
22
22
|
sdkCore: SdkCore
|
|
23
23
|
)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manually start recording the current session.
|
|
27
|
+
*/
|
|
28
|
+
fun startRecording(sdkCore: SdkCore)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Manually stop recording the current session.
|
|
32
|
+
*/
|
|
33
|
+
fun stopRecording(sdkCore: SdkCore)
|
|
24
34
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
|
|
3
|
+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
|
|
4
|
+
* Copyright 2016-Present Datadog, Inc.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package com.datadog.reactnative.sessionreplay.extensions
|
|
8
|
+
|
|
9
|
+
import android.content.res.Resources
|
|
10
|
+
import android.graphics.Bitmap
|
|
11
|
+
import android.graphics.Bitmap.Config
|
|
12
|
+
import android.graphics.Canvas
|
|
13
|
+
import android.graphics.drawable.BitmapDrawable
|
|
14
|
+
import android.graphics.drawable.Drawable
|
|
15
|
+
import android.graphics.drawable.ShapeDrawable
|
|
16
|
+
import android.graphics.drawable.VectorDrawable
|
|
17
|
+
import android.widget.ImageView
|
|
18
|
+
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
|
|
19
|
+
import com.facebook.drawee.drawable.ArrayDrawable
|
|
20
|
+
import com.facebook.drawee.drawable.ForwardingDrawable
|
|
21
|
+
import com.facebook.drawee.drawable.RoundedBitmapDrawable
|
|
22
|
+
import com.facebook.drawee.drawable.ScaleTypeDrawable
|
|
23
|
+
import com.facebook.drawee.drawable.ScalingUtils
|
|
24
|
+
|
|
25
|
+
internal fun ScaleTypeDrawable.imageViewScaleType(): ImageView.ScaleType? {
|
|
26
|
+
return when (scaleType) {
|
|
27
|
+
ScalingUtils.ScaleType.CENTER -> ImageView.ScaleType.CENTER
|
|
28
|
+
ScalingUtils.ScaleType.CENTER_CROP -> ImageView.ScaleType.CENTER_CROP
|
|
29
|
+
ScalingUtils.ScaleType.CENTER_INSIDE -> ImageView.ScaleType.CENTER_INSIDE
|
|
30
|
+
ScalingUtils.ScaleType.FIT_CENTER -> ImageView.ScaleType.FIT_CENTER
|
|
31
|
+
ScalingUtils.ScaleType.FIT_START -> ImageView.ScaleType.FIT_START
|
|
32
|
+
ScalingUtils.ScaleType.FIT_END -> ImageView.ScaleType.FIT_END
|
|
33
|
+
ScalingUtils.ScaleType.FIT_XY -> ImageView.ScaleType.FIT_XY
|
|
34
|
+
else -> null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
internal fun ArrayDrawable.getScaleTypeDrawable(): ScaleTypeDrawable? {
|
|
39
|
+
for (i in 0 until numberOfLayers) {
|
|
40
|
+
val drawable = getDrawableOrNull(i)
|
|
41
|
+
if (drawable is ScaleTypeDrawable) return drawable
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
internal fun ArrayDrawable.getDrawableOrNull(index: Int): Drawable? {
|
|
48
|
+
return try {
|
|
49
|
+
getDrawable(index)
|
|
50
|
+
} catch (_: IllegalArgumentException) {
|
|
51
|
+
null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
internal fun ForwardingDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
56
|
+
val forwardedDrawable = drawable
|
|
57
|
+
return if (forwardedDrawable != null) {
|
|
58
|
+
forwardedDrawable.tryToExtractBitmap(resources)
|
|
59
|
+
} else {
|
|
60
|
+
toBitmapOrNull(
|
|
61
|
+
intrinsicWidth,
|
|
62
|
+
intrinsicHeight,
|
|
63
|
+
Config.ARGB_8888
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
internal fun RoundedBitmapDrawable.tryToExtractBitmap(): Bitmap? {
|
|
69
|
+
val privateBitmap = try {
|
|
70
|
+
val field = RoundedBitmapDrawable::class.java.getDeclaredField("mBitmap")
|
|
71
|
+
field.isAccessible = true
|
|
72
|
+
field.get(this) as? Bitmap
|
|
73
|
+
} catch (_: NoSuchFieldException) {
|
|
74
|
+
null
|
|
75
|
+
} catch (_: IllegalAccessException) {
|
|
76
|
+
null
|
|
77
|
+
} catch (_: Exception) {
|
|
78
|
+
null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return privateBitmap ?: toBitmapOrNull(
|
|
82
|
+
intrinsicWidth,
|
|
83
|
+
intrinsicHeight,
|
|
84
|
+
Config.ARGB_8888
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
internal fun BitmapDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
89
|
+
if (bitmap != null) {
|
|
90
|
+
return bitmap
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (constantState != null) {
|
|
94
|
+
val copy = constantState?.newDrawable(resources)
|
|
95
|
+
return (copy as? BitmapDrawable)?.bitmap ?: copy?.toBitmapOrNull(
|
|
96
|
+
intrinsicWidth,
|
|
97
|
+
intrinsicHeight,
|
|
98
|
+
Config.ARGB_8888
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
internal fun ArrayDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
|
|
106
|
+
var width = 0
|
|
107
|
+
var height = 0
|
|
108
|
+
for (index in 0 until numberOfLayers) {
|
|
109
|
+
val drawable = getDrawableOrNull(index) ?: continue
|
|
110
|
+
|
|
111
|
+
if (drawable is ScaleTypeDrawable) {
|
|
112
|
+
return drawable.tryToExtractBitmap(resources)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (drawable.intrinsicWidth * drawable.intrinsicHeight > width * height) {
|
|
116
|
+
width = drawable.intrinsicWidth
|
|
117
|
+
height = drawable.intrinsicHeight
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return if (width > 0 && height > 0)
|
|
122
|
+
toBitmapOrNull(width, height, Config.ARGB_8888)
|
|
123
|
+
else
|
|
124
|
+
null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
internal fun Drawable.tryToExtractBitmap(
|
|
128
|
+
resources: Resources
|
|
129
|
+
): Bitmap? {
|
|
130
|
+
when (this) {
|
|
131
|
+
is ArrayDrawable -> {
|
|
132
|
+
return tryToExtractBitmap(resources)
|
|
133
|
+
}
|
|
134
|
+
is ForwardingDrawable -> {
|
|
135
|
+
return tryToExtractBitmap(resources)
|
|
136
|
+
}
|
|
137
|
+
is RoundedBitmapDrawable -> {
|
|
138
|
+
return tryToExtractBitmap()
|
|
139
|
+
}
|
|
140
|
+
is BitmapDrawable -> {
|
|
141
|
+
return tryToExtractBitmap(resources)
|
|
142
|
+
}
|
|
143
|
+
is VectorDrawable, is ShapeDrawable, is DrawerArrowDrawable -> {
|
|
144
|
+
return toBitmapOrNull(
|
|
145
|
+
intrinsicWidth,
|
|
146
|
+
intrinsicHeight,
|
|
147
|
+
Config.ARGB_8888
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
else -> return null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
internal fun Drawable.toBitmapOrNull(
|
|
155
|
+
width: Int = intrinsicWidth,
|
|
156
|
+
height: Int = intrinsicHeight,
|
|
157
|
+
config: Config? = null
|
|
158
|
+
): Bitmap? {
|
|
159
|
+
if (this is BitmapDrawable && bitmap == null) {
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
return toBitmap(width, height, config)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
internal fun Drawable.toBitmap(
|
|
166
|
+
width: Int = intrinsicWidth,
|
|
167
|
+
height: Int = intrinsicHeight,
|
|
168
|
+
config: Config? = null
|
|
169
|
+
): Bitmap {
|
|
170
|
+
if (this is BitmapDrawable) {
|
|
171
|
+
if (bitmap == null) {
|
|
172
|
+
return Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
|
|
173
|
+
}
|
|
174
|
+
if (config == null || bitmap.config == config) {
|
|
175
|
+
// Fast-path to return original. Bitmap.createScaledBitmap will do this check, but it
|
|
176
|
+
// involves allocation and two jumps into native code so we perform the check ourselves.
|
|
177
|
+
if (width == bitmap.width && height == bitmap.height) {
|
|
178
|
+
return bitmap
|
|
179
|
+
}
|
|
180
|
+
return Bitmap.createScaledBitmap(bitmap, width, height, true)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
val bitmap = Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
|
|
185
|
+
setBounds(0, 0, width, height)
|
|
186
|
+
draw(Canvas(bitmap))
|
|
187
|
+
|
|
188
|
+
setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom)
|
|
189
|
+
return bitmap
|
|
190
|
+
}
|