@rejourneyco/react-native 1.0.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/android/build.gradle.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
- package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
- package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
- package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
- package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
- package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
- package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
- package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
- package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
- package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
- package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
- package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
- package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
- package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
- package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
- package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
- package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Capture/RJANRHandler.h +42 -0
- package/ios/Capture/RJANRHandler.m +328 -0
- package/ios/Capture/RJCaptureEngine.h +275 -0
- package/ios/Capture/RJCaptureEngine.m +2062 -0
- package/ios/Capture/RJCaptureHeuristics.h +80 -0
- package/ios/Capture/RJCaptureHeuristics.m +903 -0
- package/ios/Capture/RJCrashHandler.h +46 -0
- package/ios/Capture/RJCrashHandler.m +313 -0
- package/ios/Capture/RJMotionEvent.h +183 -0
- package/ios/Capture/RJMotionEvent.m +183 -0
- package/ios/Capture/RJPerformanceManager.h +100 -0
- package/ios/Capture/RJPerformanceManager.m +373 -0
- package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
- package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
- package/ios/Capture/RJSegmentUploader.h +146 -0
- package/ios/Capture/RJSegmentUploader.m +778 -0
- package/ios/Capture/RJVideoEncoder.h +247 -0
- package/ios/Capture/RJVideoEncoder.m +1036 -0
- package/ios/Capture/RJViewControllerTracker.h +73 -0
- package/ios/Capture/RJViewControllerTracker.m +508 -0
- package/ios/Capture/RJViewHierarchyScanner.h +215 -0
- package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
- package/ios/Capture/RJViewSerializer.h +119 -0
- package/ios/Capture/RJViewSerializer.m +498 -0
- package/ios/Core/RJConstants.h +124 -0
- package/ios/Core/RJConstants.m +88 -0
- package/ios/Core/RJLifecycleManager.h +85 -0
- package/ios/Core/RJLifecycleManager.m +308 -0
- package/ios/Core/RJLogger.h +61 -0
- package/ios/Core/RJLogger.m +211 -0
- package/ios/Core/RJTypes.h +176 -0
- package/ios/Core/RJTypes.m +66 -0
- package/ios/Core/Rejourney.h +64 -0
- package/ios/Core/Rejourney.mm +2495 -0
- package/ios/Network/RJDeviceAuthManager.h +94 -0
- package/ios/Network/RJDeviceAuthManager.m +967 -0
- package/ios/Network/RJNetworkMonitor.h +68 -0
- package/ios/Network/RJNetworkMonitor.m +267 -0
- package/ios/Network/RJRetryManager.h +73 -0
- package/ios/Network/RJRetryManager.m +325 -0
- package/ios/Network/RJUploadManager.h +267 -0
- package/ios/Network/RJUploadManager.m +2296 -0
- package/ios/Privacy/RJPrivacyMask.h +163 -0
- package/ios/Privacy/RJPrivacyMask.m +922 -0
- package/ios/Rejourney.h +63 -0
- package/ios/Touch/RJGestureClassifier.h +130 -0
- package/ios/Touch/RJGestureClassifier.m +333 -0
- package/ios/Touch/RJTouchInterceptor.h +169 -0
- package/ios/Touch/RJTouchInterceptor.m +772 -0
- package/ios/Utils/RJEventBuffer.h +112 -0
- package/ios/Utils/RJEventBuffer.m +358 -0
- package/ios/Utils/RJGzipUtils.h +33 -0
- package/ios/Utils/RJGzipUtils.m +89 -0
- package/ios/Utils/RJKeychainManager.h +48 -0
- package/ios/Utils/RJKeychainManager.m +111 -0
- package/ios/Utils/RJPerfTiming.h +209 -0
- package/ios/Utils/RJPerfTiming.m +264 -0
- package/ios/Utils/RJTelemetry.h +92 -0
- package/ios/Utils/RJTelemetry.m +320 -0
- package/ios/Utils/RJWindowUtils.h +66 -0
- package/ios/Utils/RJWindowUtils.m +133 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +79 -0
- package/lib/commonjs/index.js +1381 -0
- package/lib/commonjs/sdk/autoTracking.js +1259 -0
- package/lib/commonjs/sdk/constants.js +151 -0
- package/lib/commonjs/sdk/errorTracking.js +199 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +204 -0
- package/lib/commonjs/sdk/navigation.js +151 -0
- package/lib/commonjs/sdk/networkInterceptor.js +412 -0
- package/lib/commonjs/sdk/utils.js +363 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +72 -0
- package/lib/module/index.js +1284 -0
- package/lib/module/sdk/autoTracking.js +1233 -0
- package/lib/module/sdk/constants.js +145 -0
- package/lib/module/sdk/errorTracking.js +189 -0
- package/lib/module/sdk/index.js +12 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +143 -0
- package/lib/module/sdk/networkInterceptor.js +401 -0
- package/lib/module/sdk/utils.js +342 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +147 -0
- package/lib/typescript/components/Mask.d.ts +39 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +204 -0
- package/lib/typescript/sdk/constants.d.ts +120 -0
- package/lib/typescript/sdk/errorTracking.d.ts +32 -0
- package/lib/typescript/sdk/index.d.ts +9 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
- package/lib/typescript/sdk/navigation.d.ts +33 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
- package/lib/typescript/sdk/utils.d.ts +148 -0
- package/lib/typescript/types/index.d.ts +624 -0
- package/package.json +102 -0
- package/rejourney.podspec +21 -0
- package/src/NativeRejourney.ts +165 -0
- package/src/components/Mask.tsx +80 -0
- package/src/index.ts +1459 -0
- package/src/sdk/autoTracking.ts +1373 -0
- package/src/sdk/constants.ts +134 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/metricsTracking.ts +232 -0
- package/src/sdk/navigation.ts +157 -0
- package/src/sdk/networkInterceptor.ts +440 -0
- package/src/sdk/utils.ts +369 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +739 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View hierarchy serializer aligned with iOS RJViewSerializer.
|
|
3
|
+
*/
|
|
4
|
+
package com.rejourney.capture
|
|
5
|
+
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.drawable.ColorDrawable
|
|
8
|
+
import android.os.SystemClock
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewGroup
|
|
11
|
+
import android.view.Window
|
|
12
|
+
import android.widget.Button
|
|
13
|
+
import android.widget.CompoundButton
|
|
14
|
+
import android.widget.EditText
|
|
15
|
+
import android.widget.HorizontalScrollView
|
|
16
|
+
import android.widget.ImageView
|
|
17
|
+
import android.widget.ScrollView
|
|
18
|
+
import android.widget.TextView
|
|
19
|
+
import androidx.core.widget.NestedScrollView
|
|
20
|
+
import androidx.recyclerview.widget.RecyclerView
|
|
21
|
+
import com.rejourney.privacy.PrivacyMask
|
|
22
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
23
|
+
import kotlin.math.min
|
|
24
|
+
|
|
25
|
+
class ViewSerializer(private val density: Float) {
|
|
26
|
+
var enabled: Boolean = true
|
|
27
|
+
var maxDepth: Int = 10
|
|
28
|
+
var includeVisualProperties: Boolean = true
|
|
29
|
+
var includeTextContent: Boolean = true
|
|
30
|
+
|
|
31
|
+
private val imageViewClass = ImageView::class.java
|
|
32
|
+
private val buttonClass = Button::class.java
|
|
33
|
+
private val scrollViewClass = ScrollView::class.java
|
|
34
|
+
private val textFieldClass = EditText::class.java
|
|
35
|
+
private val textViewClass = TextView::class.java
|
|
36
|
+
|
|
37
|
+
fun serializeWindow(
|
|
38
|
+
window: Window,
|
|
39
|
+
scanResult: ViewHierarchyScanResult? = null,
|
|
40
|
+
timestamp: Long = System.currentTimeMillis()
|
|
41
|
+
): Map<String, Any?> {
|
|
42
|
+
if (!enabled) return emptyMap()
|
|
43
|
+
|
|
44
|
+
val rootView = window.decorView ?: return emptyMap()
|
|
45
|
+
val width = sanitizeFloat(rootView.width / density)
|
|
46
|
+
val height = sanitizeFloat(rootView.height / density)
|
|
47
|
+
val scale = sanitizeFloat(density)
|
|
48
|
+
|
|
49
|
+
val startTime = SystemClock.elapsedRealtimeNanos()
|
|
50
|
+
val rootNode = serializeViewInternal(rootView, 0, startTime)
|
|
51
|
+
|
|
52
|
+
val result = mutableMapOf<String, Any?>(
|
|
53
|
+
"timestamp" to timestamp,
|
|
54
|
+
"screen" to mapOf(
|
|
55
|
+
"width" to width,
|
|
56
|
+
"height" to height,
|
|
57
|
+
"scale" to scale
|
|
58
|
+
),
|
|
59
|
+
"root" to rootNode
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
scanResult?.layoutSignature?.let { result["layoutSignature"] = it }
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun serializeView(view: View): Map<String, Any?> {
|
|
67
|
+
if (!enabled) return emptyMap()
|
|
68
|
+
val startTime = SystemClock.elapsedRealtimeNanos()
|
|
69
|
+
return serializeViewInternal(view, 0, startTime)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun serializeViewInternal(view: View, depth: Int, startTime: Long): Map<String, Any?> {
|
|
73
|
+
if (depth > maxDepth) return emptyMap()
|
|
74
|
+
|
|
75
|
+
if (SystemClock.elapsedRealtimeNanos() - startTime > VIEW_SERIALIZATION_BUDGET_NS) {
|
|
76
|
+
return mapOf(
|
|
77
|
+
"type" to classNameFor(view.javaClass),
|
|
78
|
+
"bailout" to true
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (depth > 0 && (!view.isShown || view.alpha <= 0.01f)) return emptyMap()
|
|
83
|
+
if (view.width <= 0 || view.height <= 0) return emptyMap()
|
|
84
|
+
|
|
85
|
+
val node = mutableMapOf<String, Any?>()
|
|
86
|
+
|
|
87
|
+
node["type"] = classNameFor(view.javaClass)
|
|
88
|
+
|
|
89
|
+
val frame = mapOf(
|
|
90
|
+
"x" to sanitizeFloat(view.x / density),
|
|
91
|
+
"y" to sanitizeFloat(view.y / density),
|
|
92
|
+
"w" to sanitizeFloat(view.width / density),
|
|
93
|
+
"h" to sanitizeFloat(view.height / density)
|
|
94
|
+
)
|
|
95
|
+
node["frame"] = frame
|
|
96
|
+
|
|
97
|
+
if (view.visibility != View.VISIBLE) {
|
|
98
|
+
node["hidden"] = true
|
|
99
|
+
}
|
|
100
|
+
if (view.alpha < 1.0f) {
|
|
101
|
+
node["alpha"] = view.alpha
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
val nativeId = view.getTag(com.facebook.react.R.id.view_tag_native_id)
|
|
105
|
+
if (nativeId is String && nativeId.isNotEmpty()) {
|
|
106
|
+
node["testID"] = nativeId
|
|
107
|
+
}
|
|
108
|
+
val label = view.contentDescription?.toString()
|
|
109
|
+
if (!label.isNullOrEmpty()) {
|
|
110
|
+
node["label"] = label
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (PrivacyMask.isSensitiveView(view)) {
|
|
114
|
+
node["masked"] = true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (includeVisualProperties) {
|
|
118
|
+
val bgColor = extractBackgroundColor(view)
|
|
119
|
+
if (bgColor != null && Color.alpha(bgColor) > 0) {
|
|
120
|
+
node["bg"] = colorToHex(bgColor)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (includeTextContent) {
|
|
125
|
+
val text = extractTextFromView(view)
|
|
126
|
+
if (!text.isNullOrEmpty()) {
|
|
127
|
+
node["text"] = maskText(text)
|
|
128
|
+
node["textLength"] = text.length
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (imageViewClass.isInstance(view)) {
|
|
133
|
+
node["hasImage"] = true
|
|
134
|
+
val imageLabel = view.contentDescription?.toString()
|
|
135
|
+
if (!imageLabel.isNullOrEmpty()) {
|
|
136
|
+
node["imageLabel"] = imageLabel
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isInteractiveView(view)) {
|
|
141
|
+
node["interactive"] = true
|
|
142
|
+
|
|
143
|
+
if (buttonClass.isInstance(view)) {
|
|
144
|
+
val buttonText = (view as? Button)?.text?.toString()
|
|
145
|
+
if (!buttonText.isNullOrEmpty()) {
|
|
146
|
+
node["buttonTitle"] = buttonText
|
|
147
|
+
}
|
|
148
|
+
node["enabled"] = view.isEnabled
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (view is CompoundButton) {
|
|
152
|
+
node["switchOn"] = view.isChecked
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (scrollViewClass.isInstance(view) || view is HorizontalScrollView || view is NestedScrollView || view is RecyclerView) {
|
|
157
|
+
val (offsetX, offsetY, contentWidth, contentHeight) = scrollMetricsFor(view)
|
|
158
|
+
node["contentOffset"] = mapOf(
|
|
159
|
+
"x" to sanitizeFloat(offsetX / density),
|
|
160
|
+
"y" to sanitizeFloat(offsetY / density)
|
|
161
|
+
)
|
|
162
|
+
node["contentSize"] = mapOf(
|
|
163
|
+
"w" to sanitizeFloat(contentWidth / density),
|
|
164
|
+
"h" to sanitizeFloat(contentHeight / density)
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (view is ViewGroup) {
|
|
169
|
+
val children = mutableListOf<Map<String, Any?>>()
|
|
170
|
+
val parentWidth = view.width
|
|
171
|
+
val parentHeight = view.height
|
|
172
|
+
for (i in view.childCount - 1 downTo 0) {
|
|
173
|
+
val child = view.getChildAt(i)
|
|
174
|
+
if (!child.isShown || child.alpha <= 0.01f) continue
|
|
175
|
+
if (child.width <= 0 || child.height <= 0) continue
|
|
176
|
+
|
|
177
|
+
val childNode = serializeViewInternal(child, depth + 1, startTime)
|
|
178
|
+
if (childNode.isNotEmpty()) {
|
|
179
|
+
children.add(0, childNode)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (child.isOpaque && child.alpha >= 1.0f &&
|
|
183
|
+
child.left == 0 && child.top == 0 &&
|
|
184
|
+
child.width == parentWidth && child.height == parentHeight
|
|
185
|
+
) {
|
|
186
|
+
break
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (children.isNotEmpty()) {
|
|
191
|
+
node["children"] = children
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return node
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private fun isInteractiveView(view: View): Boolean {
|
|
199
|
+
if (view.isClickable || view.isLongClickable) return true
|
|
200
|
+
if (textFieldClass.isInstance(view) || textViewClass.isInstance(view)) return true
|
|
201
|
+
return view.hasOnClickListeners() || view.isFocusable
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun extractTextFromView(view: View): String? {
|
|
205
|
+
return when (view) {
|
|
206
|
+
is TextView -> view.text?.toString()
|
|
207
|
+
is Button -> view.text?.toString()
|
|
208
|
+
else -> null
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private fun maskText(text: String): String {
|
|
213
|
+
if (text.isEmpty()) return ""
|
|
214
|
+
val maskLength = min(text.length, 12)
|
|
215
|
+
return "*".repeat(maskLength)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private fun extractBackgroundColor(view: View): Int? {
|
|
219
|
+
val background = view.background ?: return null
|
|
220
|
+
return when (background) {
|
|
221
|
+
is ColorDrawable -> background.color
|
|
222
|
+
else -> null
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private fun colorToHex(color: Int): String {
|
|
227
|
+
val r = Color.red(color)
|
|
228
|
+
val g = Color.green(color)
|
|
229
|
+
val b = Color.blue(color)
|
|
230
|
+
return String.format("#%02X%02X%02X", r, g, b)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private fun scrollMetricsFor(view: View): ScrollMetrics {
|
|
234
|
+
return when (view) {
|
|
235
|
+
is RecyclerView -> ScrollMetrics(
|
|
236
|
+
view.computeHorizontalScrollOffset(),
|
|
237
|
+
view.computeVerticalScrollOffset(),
|
|
238
|
+
view.computeHorizontalScrollRange(),
|
|
239
|
+
view.computeVerticalScrollRange()
|
|
240
|
+
)
|
|
241
|
+
is ScrollView -> ScrollMetrics(
|
|
242
|
+
view.scrollX,
|
|
243
|
+
view.scrollY,
|
|
244
|
+
view.getChildAt(0)?.width ?: view.width,
|
|
245
|
+
view.getChildAt(0)?.height ?: view.height
|
|
246
|
+
)
|
|
247
|
+
is HorizontalScrollView -> ScrollMetrics(
|
|
248
|
+
view.scrollX,
|
|
249
|
+
view.scrollY,
|
|
250
|
+
view.getChildAt(0)?.width ?: view.width,
|
|
251
|
+
view.getChildAt(0)?.height ?: view.height
|
|
252
|
+
)
|
|
253
|
+
is NestedScrollView -> ScrollMetrics(
|
|
254
|
+
view.scrollX,
|
|
255
|
+
view.scrollY,
|
|
256
|
+
view.getChildAt(0)?.width ?: view.width,
|
|
257
|
+
view.getChildAt(0)?.height ?: view.height
|
|
258
|
+
)
|
|
259
|
+
else -> ScrollMetrics(view.scrollX, view.scrollY, view.width, view.height)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private fun classNameFor(cls: Class<*>): String {
|
|
264
|
+
return classNameCache[cls] ?: run {
|
|
265
|
+
val name = cls.simpleName
|
|
266
|
+
classNameCache[cls] = name
|
|
267
|
+
name
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun sanitizeFloat(value: Float): Float {
|
|
272
|
+
return if (value.isNaN() || value.isInfinite()) 0f else value
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private data class ScrollMetrics(
|
|
276
|
+
val offsetX: Int,
|
|
277
|
+
val offsetY: Int,
|
|
278
|
+
val contentWidth: Int,
|
|
279
|
+
val contentHeight: Int
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
companion object {
|
|
283
|
+
private const val VIEW_SERIALIZATION_BUDGET_NS = 10_000_000L
|
|
284
|
+
private val classNameCache = ConcurrentHashMap<Class<*>, String>()
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-wide constants and configuration defaults.
|
|
3
|
+
* Ported from iOS RJConstants.h/m
|
|
4
|
+
*/
|
|
5
|
+
package com.rejourney.core
|
|
6
|
+
|
|
7
|
+
object Constants {
|
|
8
|
+
// SDK Version
|
|
9
|
+
const val SDK_VERSION = "1.0.0"
|
|
10
|
+
|
|
11
|
+
// Video Capture Configuration (H.264 Segment Mode)
|
|
12
|
+
// These match iOS RJCaptureEngine defaults for performance
|
|
13
|
+
|
|
14
|
+
/** Default capture scale factor (0.35 = 35% of original size) - matches iOS */
|
|
15
|
+
const val DEFAULT_CAPTURE_SCALE = 0.35f
|
|
16
|
+
|
|
17
|
+
/** Target video bitrate in bits per second (1.5 Mbps) - matches iOS */
|
|
18
|
+
const val DEFAULT_VIDEO_BITRATE = 1_500_000
|
|
19
|
+
|
|
20
|
+
/** Maximum video dimension in pixels (longest edge) - matches iOS */
|
|
21
|
+
const val MAX_VIDEO_DIMENSION = 1920
|
|
22
|
+
|
|
23
|
+
/** Target frames per second for video capture (15 FPS) - matches iOS */
|
|
24
|
+
const val DEFAULT_VIDEO_FPS = 15
|
|
25
|
+
|
|
26
|
+
/** Number of frames per segment before auto-rotation (60 frames = 60 seconds at 1 FPS) */
|
|
27
|
+
const val DEFAULT_FRAMES_PER_SEGMENT = 60
|
|
28
|
+
|
|
29
|
+
/** Keyframe interval in seconds (10s for better compression) - matches iOS */
|
|
30
|
+
const val DEFAULT_KEYFRAME_INTERVAL = 10
|
|
31
|
+
|
|
32
|
+
/** Minimum frame interval in seconds (1/15 ~= 0.0667) */
|
|
33
|
+
const val DEFAULT_MIN_FRAME_INTERVAL = 0.0667
|
|
34
|
+
|
|
35
|
+
/** Maximum frames allowed per minute (15 FPS * 60s) */
|
|
36
|
+
const val DEFAULT_MAX_FRAMES_PER_MINUTE = 900
|
|
37
|
+
|
|
38
|
+
/** Segment duration in seconds (for planning purposes) */
|
|
39
|
+
const val DEFAULT_SEGMENT_DURATION_SECONDS = 60
|
|
40
|
+
|
|
41
|
+
// Motion Event Configuration
|
|
42
|
+
/** Minimum interval between motion events (0.016 = 60 FPS motion capture) */
|
|
43
|
+
const val DEFAULT_MOTION_EVENT_INTERVAL = 0.016
|
|
44
|
+
|
|
45
|
+
/** Minimum velocity to record scroll motion (points/second) */
|
|
46
|
+
const val MOTION_VELOCITY_THRESHOLD = 10f
|
|
47
|
+
|
|
48
|
+
/** Scroll distance threshold for motion events (points) */
|
|
49
|
+
const val DEFAULT_SCROLL_THRESHOLD = 5f
|
|
50
|
+
|
|
51
|
+
/** Time after last scroll event to consider scroll ended (seconds) */
|
|
52
|
+
const val SCROLL_END_DELAY = 0.15
|
|
53
|
+
|
|
54
|
+
// Memory Thresholds
|
|
55
|
+
/** Memory warning threshold in bytes (100MB) */
|
|
56
|
+
const val MEMORY_WARNING_THRESHOLD_BYTES = 100L * 1024 * 1024
|
|
57
|
+
|
|
58
|
+
/** Maximum frame data bytes to keep in memory */
|
|
59
|
+
// Frames are sparse but can be large (data URIs). Too low causes aggressive eviction
|
|
60
|
+
// before the upload timer drains, especially during crashes/offline.
|
|
61
|
+
const val MAX_FRAME_BYTES_IN_MEMORY = 4L * 1024 * 1024
|
|
62
|
+
|
|
63
|
+
/** Default maximum frames to keep in memory */
|
|
64
|
+
const val DEFAULT_MAX_FRAMES_IN_MEMORY = 20
|
|
65
|
+
|
|
66
|
+
// Performance Thresholds
|
|
67
|
+
/** Battery level threshold for low-power mode (15%) */
|
|
68
|
+
const val LOW_BATTERY_THRESHOLD = 0.15f
|
|
69
|
+
|
|
70
|
+
/** Maximum consecutive captures before cooldown */
|
|
71
|
+
const val MAX_CONSECUTIVE_CAPTURES = 5
|
|
72
|
+
|
|
73
|
+
/** Cooldown duration after max consecutive captures (seconds) */
|
|
74
|
+
const val CAPTURE_COOLDOWN_SECONDS = 1.0
|
|
75
|
+
|
|
76
|
+
// Upload Configuration
|
|
77
|
+
/** Batch upload interval in seconds */
|
|
78
|
+
const val BATCH_UPLOAD_INTERVAL = 5.0
|
|
79
|
+
|
|
80
|
+
/** Initial upload delay for short sessions (seconds) */
|
|
81
|
+
const val INITIAL_UPLOAD_DELAY = 1.0
|
|
82
|
+
|
|
83
|
+
/** Network request timeout (seconds) */
|
|
84
|
+
const val NETWORK_REQUEST_TIMEOUT = 60.0
|
|
85
|
+
|
|
86
|
+
/** Network resource timeout (seconds) */
|
|
87
|
+
const val NETWORK_RESOURCE_TIMEOUT = 120.0
|
|
88
|
+
|
|
89
|
+
// Session Configuration
|
|
90
|
+
/** Background duration threshold for new session (seconds) - 60 seconds matches iOS */
|
|
91
|
+
const val BACKGROUND_SESSION_TIMEOUT = 60.0
|
|
92
|
+
|
|
93
|
+
// Gesture Detection
|
|
94
|
+
/** Maximum time between taps for double-tap detection (milliseconds) */
|
|
95
|
+
const val DOUBLE_TAP_MAX_INTERVAL = 300.0
|
|
96
|
+
|
|
97
|
+
/** Maximum distance between taps for double-tap detection (points) */
|
|
98
|
+
const val DOUBLE_TAP_MAX_DISTANCE = 50f
|
|
99
|
+
|
|
100
|
+
/** Minimum duration for long press detection (milliseconds) */
|
|
101
|
+
const val LONG_PRESS_MIN_DURATION = 500.0
|
|
102
|
+
|
|
103
|
+
/** Minimum distance for swipe gesture detection (points) */
|
|
104
|
+
const val SWIPE_MIN_DISTANCE = 50f
|
|
105
|
+
|
|
106
|
+
/** Force touch threshold (normalized force value) */
|
|
107
|
+
const val FORCE_TOUCH_THRESHOLD = 2.0f
|
|
108
|
+
|
|
109
|
+
/** Minimum distance for pinch gesture detection (points) */
|
|
110
|
+
const val PINCH_MIN_DISTANCE = 30f
|
|
111
|
+
|
|
112
|
+
/** Minimum distance change percentage for pinch detection */
|
|
113
|
+
const val PINCH_MIN_CHANGE_PERCENT = 0.2f
|
|
114
|
+
|
|
115
|
+
/** Minimum rotation angle for rotation gesture detection (degrees) */
|
|
116
|
+
const val ROTATION_MIN_ANGLE = 15f
|
|
117
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package com.rejourney.core
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.rejourney.BuildConfig
|
|
5
|
+
|
|
6
|
+
enum class LogLevel(val priority: Int) {
|
|
7
|
+
DEBUG(0),
|
|
8
|
+
INFO(1),
|
|
9
|
+
WARNING(2),
|
|
10
|
+
ERROR(3),
|
|
11
|
+
SILENT(4)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
object Logger {
|
|
15
|
+
private const val TAG = "Rejourney"
|
|
16
|
+
private var debugMode = false
|
|
17
|
+
|
|
18
|
+
var minimumLogLevel: LogLevel = if (BuildConfig.DEBUG) LogLevel.ERROR else LogLevel.SILENT
|
|
19
|
+
private set
|
|
20
|
+
|
|
21
|
+
fun setLogLevel(level: LogLevel) {
|
|
22
|
+
minimumLogLevel = level
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun setDebugMode(enabled: Boolean) {
|
|
26
|
+
debugMode = enabled
|
|
27
|
+
minimumLogLevel = if (enabled) {
|
|
28
|
+
LogLevel.DEBUG
|
|
29
|
+
} else if (BuildConfig.DEBUG) {
|
|
30
|
+
LogLevel.ERROR
|
|
31
|
+
} else {
|
|
32
|
+
LogLevel.SILENT
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun debug(message: String) {
|
|
37
|
+
if (minimumLogLevel.priority <= LogLevel.DEBUG.priority) {
|
|
38
|
+
Log.d(TAG, message)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun info(message: String) {
|
|
43
|
+
if (minimumLogLevel.priority <= LogLevel.INFO.priority) {
|
|
44
|
+
Log.i(TAG, message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun warning(message: String) {
|
|
49
|
+
if (minimumLogLevel.priority <= LogLevel.WARNING.priority) {
|
|
50
|
+
Log.w(TAG, message)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fun error(message: String, throwable: Throwable? = null) {
|
|
55
|
+
if (minimumLogLevel.priority <= LogLevel.ERROR.priority) {
|
|
56
|
+
if (throwable != null) {
|
|
57
|
+
Log.e(TAG, message, throwable)
|
|
58
|
+
} else {
|
|
59
|
+
Log.e(TAG, message)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun logInitSuccess(version: String) {
|
|
65
|
+
if (debugMode) {
|
|
66
|
+
info("✓ SDK initialized (v$version)")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun logInitFailure(reason: String) {
|
|
71
|
+
error("✗ Initialization failed: $reason")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun logSessionStart(sessionId: String) {
|
|
75
|
+
if (debugMode) {
|
|
76
|
+
info("Session started: $sessionId")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun logSessionEnd(sessionId: String) {
|
|
81
|
+
if (debugMode) {
|
|
82
|
+
info("Session ended: $sessionId")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fun logArchitectureInfo(isNewArch: Boolean, architectureType: String) {
|
|
87
|
+
if (minimumLogLevel.priority <= LogLevel.DEBUG.priority) {
|
|
88
|
+
debug("React Native Architecture: $architectureType")
|
|
89
|
+
debug("New Architecture Enabled: ${if (isNewArch) "YES" else "NO"}")
|
|
90
|
+
debug("Rejourney SDK Version: ${Constants.SDK_VERSION}")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common type definitions used throughout the SDK.
|
|
3
|
+
* Ported from iOS RJTypes.h/m
|
|
4
|
+
*/
|
|
5
|
+
package com.rejourney.core
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents the importance level of a capture event.
|
|
9
|
+
* Higher importance events are less likely to be skipped during throttling.
|
|
10
|
+
*/
|
|
11
|
+
enum class CaptureImportance(val value: Int) {
|
|
12
|
+
/** Low importance - can be freely skipped (e.g., heartbeat) */
|
|
13
|
+
LOW(0),
|
|
14
|
+
|
|
15
|
+
/** Medium importance - skip only under heavy load (e.g., tap gestures) */
|
|
16
|
+
MEDIUM(1),
|
|
17
|
+
|
|
18
|
+
/** High importance - rarely skip (e.g., scroll events) */
|
|
19
|
+
HIGH(2),
|
|
20
|
+
|
|
21
|
+
/** Critical importance - never skip (e.g., navigation, app lifecycle) */
|
|
22
|
+
CRITICAL(3)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents the current performance level of the capture engine.
|
|
27
|
+
* The engine adjusts its behavior based on system conditions.
|
|
28
|
+
*/
|
|
29
|
+
enum class PerformanceLevel(val value: Int) {
|
|
30
|
+
/** Normal operation - full capture rate */
|
|
31
|
+
NORMAL(0),
|
|
32
|
+
|
|
33
|
+
/** Reduced rate due to low battery or thermal throttling */
|
|
34
|
+
REDUCED(1),
|
|
35
|
+
|
|
36
|
+
/** Minimal captures due to memory pressure */
|
|
37
|
+
MINIMAL(2),
|
|
38
|
+
|
|
39
|
+
/** All non-critical captures paused */
|
|
40
|
+
PAUSED(3)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Represents recognized gesture types.
|
|
45
|
+
*/
|
|
46
|
+
object GestureType {
|
|
47
|
+
const val TAP = "tap"
|
|
48
|
+
const val DOUBLE_TAP = "double_tap"
|
|
49
|
+
const val LONG_PRESS = "long_press"
|
|
50
|
+
const val FORCE_TOUCH = "force_touch"
|
|
51
|
+
const val SWIPE_LEFT = "swipe_left"
|
|
52
|
+
const val SWIPE_RIGHT = "swipe_right"
|
|
53
|
+
const val SWIPE_UP = "swipe_up"
|
|
54
|
+
const val SWIPE_DOWN = "swipe_down"
|
|
55
|
+
const val SCROLL_UP = "scroll_up"
|
|
56
|
+
const val SCROLL_DOWN = "scroll_down"
|
|
57
|
+
const val PINCH_IN = "pinch_in"
|
|
58
|
+
const val PINCH_OUT = "pinch_out"
|
|
59
|
+
const val ROTATE_CW = "rotate_cw"
|
|
60
|
+
const val ROTATE_CCW = "rotate_ccw"
|
|
61
|
+
const val PAN_UP = "pan_up"
|
|
62
|
+
const val PAN_DOWN = "pan_down"
|
|
63
|
+
const val PAN_LEFT = "pan_left"
|
|
64
|
+
const val PAN_RIGHT = "pan_right"
|
|
65
|
+
const val TWO_FINGER_TAP = "two_finger_tap"
|
|
66
|
+
const val THREE_FINGER_GESTURE = "three_finger_gesture"
|
|
67
|
+
const val MULTI_TOUCH = "multi_touch"
|
|
68
|
+
const val KEYBOARD_TAP = "keyboard_tap"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Represents session event types for logging.
|
|
73
|
+
*/
|
|
74
|
+
object EventType {
|
|
75
|
+
const val SESSION_START = "session_start"
|
|
76
|
+
const val SESSION_END = "session_end"
|
|
77
|
+
const val SESSION_TIMEOUT = "session_timeout"
|
|
78
|
+
const val NAVIGATION = "navigation"
|
|
79
|
+
const val GESTURE = "gesture"
|
|
80
|
+
const val VISUAL_CHANGE = "visual_change"
|
|
81
|
+
const val KEYBOARD_SHOW = "keyboard_show"
|
|
82
|
+
const val KEYBOARD_HIDE = "keyboard_hide"
|
|
83
|
+
const val KEYBOARD_TYPING = "keyboard_typing"
|
|
84
|
+
const val APP_BACKGROUND = "app_background"
|
|
85
|
+
const val APP_FOREGROUND = "app_foreground"
|
|
86
|
+
const val APP_TERMINATED = "app_terminated"
|
|
87
|
+
const val EXTERNAL_URL_OPENED = "external_url_opened"
|
|
88
|
+
const val OAUTH_STARTED = "oauth_started"
|
|
89
|
+
const val OAUTH_COMPLETED = "oauth_completed"
|
|
90
|
+
const val OAUTH_RETURNED = "oauth_returned"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Result type for session operations
|
|
95
|
+
*/
|
|
96
|
+
data class SessionResult(
|
|
97
|
+
val success: Boolean,
|
|
98
|
+
val sessionId: String = "",
|
|
99
|
+
val error: String? = null,
|
|
100
|
+
val uploadSuccess: Boolean? = null,
|
|
101
|
+
val warning: String? = null
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* SDK telemetry metrics for observability
|
|
106
|
+
*/
|
|
107
|
+
data class SDKMetrics(
|
|
108
|
+
val uploadSuccessCount: Int = 0,
|
|
109
|
+
val uploadFailureCount: Int = 0,
|
|
110
|
+
val retryAttemptCount: Int = 0,
|
|
111
|
+
val circuitBreakerOpenCount: Int = 0,
|
|
112
|
+
val memoryEvictionCount: Int = 0,
|
|
113
|
+
val offlinePersistCount: Int = 0,
|
|
114
|
+
val sessionStartCount: Int = 0,
|
|
115
|
+
val crashCount: Int = 0,
|
|
116
|
+
val anrCount: Int = 0,
|
|
117
|
+
val uploadSuccessRate: Float = 0f,
|
|
118
|
+
val avgUploadDurationMs: Long = 0,
|
|
119
|
+
val currentQueueDepth: Int = 0,
|
|
120
|
+
val lastUploadTime: Long? = null,
|
|
121
|
+
val lastRetryTime: Long? = null,
|
|
122
|
+
val totalBytesUploaded: Long = 0,
|
|
123
|
+
val totalBytesEvicted: Long = 0
|
|
124
|
+
)
|