@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.
Files changed (152) hide show
  1. package/android/build.gradle.kts +135 -0
  2. package/android/consumer-rules.pro +10 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/AndroidManifest.xml +15 -0
  5. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
  6. package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
  7. package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
  8. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
  9. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
  10. package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
  11. package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
  12. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
  13. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
  14. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
  15. package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
  16. package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
  17. package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
  18. package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
  19. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
  20. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
  21. package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
  22. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
  23. package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
  24. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
  25. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
  26. package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
  27. package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
  28. package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
  29. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
  30. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
  31. package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
  32. package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
  33. package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
  34. package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
  35. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
  36. package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
  37. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
  38. package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
  39. package/ios/Capture/RJANRHandler.h +42 -0
  40. package/ios/Capture/RJANRHandler.m +328 -0
  41. package/ios/Capture/RJCaptureEngine.h +275 -0
  42. package/ios/Capture/RJCaptureEngine.m +2062 -0
  43. package/ios/Capture/RJCaptureHeuristics.h +80 -0
  44. package/ios/Capture/RJCaptureHeuristics.m +903 -0
  45. package/ios/Capture/RJCrashHandler.h +46 -0
  46. package/ios/Capture/RJCrashHandler.m +313 -0
  47. package/ios/Capture/RJMotionEvent.h +183 -0
  48. package/ios/Capture/RJMotionEvent.m +183 -0
  49. package/ios/Capture/RJPerformanceManager.h +100 -0
  50. package/ios/Capture/RJPerformanceManager.m +373 -0
  51. package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
  52. package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
  53. package/ios/Capture/RJSegmentUploader.h +146 -0
  54. package/ios/Capture/RJSegmentUploader.m +778 -0
  55. package/ios/Capture/RJVideoEncoder.h +247 -0
  56. package/ios/Capture/RJVideoEncoder.m +1036 -0
  57. package/ios/Capture/RJViewControllerTracker.h +73 -0
  58. package/ios/Capture/RJViewControllerTracker.m +508 -0
  59. package/ios/Capture/RJViewHierarchyScanner.h +215 -0
  60. package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
  61. package/ios/Capture/RJViewSerializer.h +119 -0
  62. package/ios/Capture/RJViewSerializer.m +498 -0
  63. package/ios/Core/RJConstants.h +124 -0
  64. package/ios/Core/RJConstants.m +88 -0
  65. package/ios/Core/RJLifecycleManager.h +85 -0
  66. package/ios/Core/RJLifecycleManager.m +308 -0
  67. package/ios/Core/RJLogger.h +61 -0
  68. package/ios/Core/RJLogger.m +211 -0
  69. package/ios/Core/RJTypes.h +176 -0
  70. package/ios/Core/RJTypes.m +66 -0
  71. package/ios/Core/Rejourney.h +64 -0
  72. package/ios/Core/Rejourney.mm +2495 -0
  73. package/ios/Network/RJDeviceAuthManager.h +94 -0
  74. package/ios/Network/RJDeviceAuthManager.m +967 -0
  75. package/ios/Network/RJNetworkMonitor.h +68 -0
  76. package/ios/Network/RJNetworkMonitor.m +267 -0
  77. package/ios/Network/RJRetryManager.h +73 -0
  78. package/ios/Network/RJRetryManager.m +325 -0
  79. package/ios/Network/RJUploadManager.h +267 -0
  80. package/ios/Network/RJUploadManager.m +2296 -0
  81. package/ios/Privacy/RJPrivacyMask.h +163 -0
  82. package/ios/Privacy/RJPrivacyMask.m +922 -0
  83. package/ios/Rejourney.h +63 -0
  84. package/ios/Touch/RJGestureClassifier.h +130 -0
  85. package/ios/Touch/RJGestureClassifier.m +333 -0
  86. package/ios/Touch/RJTouchInterceptor.h +169 -0
  87. package/ios/Touch/RJTouchInterceptor.m +772 -0
  88. package/ios/Utils/RJEventBuffer.h +112 -0
  89. package/ios/Utils/RJEventBuffer.m +358 -0
  90. package/ios/Utils/RJGzipUtils.h +33 -0
  91. package/ios/Utils/RJGzipUtils.m +89 -0
  92. package/ios/Utils/RJKeychainManager.h +48 -0
  93. package/ios/Utils/RJKeychainManager.m +111 -0
  94. package/ios/Utils/RJPerfTiming.h +209 -0
  95. package/ios/Utils/RJPerfTiming.m +264 -0
  96. package/ios/Utils/RJTelemetry.h +92 -0
  97. package/ios/Utils/RJTelemetry.m +320 -0
  98. package/ios/Utils/RJWindowUtils.h +66 -0
  99. package/ios/Utils/RJWindowUtils.m +133 -0
  100. package/lib/commonjs/NativeRejourney.js +40 -0
  101. package/lib/commonjs/components/Mask.js +79 -0
  102. package/lib/commonjs/index.js +1381 -0
  103. package/lib/commonjs/sdk/autoTracking.js +1259 -0
  104. package/lib/commonjs/sdk/constants.js +151 -0
  105. package/lib/commonjs/sdk/errorTracking.js +199 -0
  106. package/lib/commonjs/sdk/index.js +50 -0
  107. package/lib/commonjs/sdk/metricsTracking.js +204 -0
  108. package/lib/commonjs/sdk/navigation.js +151 -0
  109. package/lib/commonjs/sdk/networkInterceptor.js +412 -0
  110. package/lib/commonjs/sdk/utils.js +363 -0
  111. package/lib/commonjs/types/expo-router.d.js +2 -0
  112. package/lib/commonjs/types/index.js +2 -0
  113. package/lib/module/NativeRejourney.js +38 -0
  114. package/lib/module/components/Mask.js +72 -0
  115. package/lib/module/index.js +1284 -0
  116. package/lib/module/sdk/autoTracking.js +1233 -0
  117. package/lib/module/sdk/constants.js +145 -0
  118. package/lib/module/sdk/errorTracking.js +189 -0
  119. package/lib/module/sdk/index.js +12 -0
  120. package/lib/module/sdk/metricsTracking.js +187 -0
  121. package/lib/module/sdk/navigation.js +143 -0
  122. package/lib/module/sdk/networkInterceptor.js +401 -0
  123. package/lib/module/sdk/utils.js +342 -0
  124. package/lib/module/types/expo-router.d.js +2 -0
  125. package/lib/module/types/index.js +2 -0
  126. package/lib/typescript/NativeRejourney.d.ts +147 -0
  127. package/lib/typescript/components/Mask.d.ts +39 -0
  128. package/lib/typescript/index.d.ts +117 -0
  129. package/lib/typescript/sdk/autoTracking.d.ts +204 -0
  130. package/lib/typescript/sdk/constants.d.ts +120 -0
  131. package/lib/typescript/sdk/errorTracking.d.ts +32 -0
  132. package/lib/typescript/sdk/index.d.ts +9 -0
  133. package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
  134. package/lib/typescript/sdk/navigation.d.ts +33 -0
  135. package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
  136. package/lib/typescript/sdk/utils.d.ts +148 -0
  137. package/lib/typescript/types/index.d.ts +624 -0
  138. package/package.json +102 -0
  139. package/rejourney.podspec +21 -0
  140. package/src/NativeRejourney.ts +165 -0
  141. package/src/components/Mask.tsx +80 -0
  142. package/src/index.ts +1459 -0
  143. package/src/sdk/autoTracking.ts +1373 -0
  144. package/src/sdk/constants.ts +134 -0
  145. package/src/sdk/errorTracking.ts +231 -0
  146. package/src/sdk/index.ts +11 -0
  147. package/src/sdk/metricsTracking.ts +232 -0
  148. package/src/sdk/navigation.ts +157 -0
  149. package/src/sdk/networkInterceptor.ts +440 -0
  150. package/src/sdk/utils.ts +369 -0
  151. package/src/types/expo-router.d.ts +7 -0
  152. 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
+ )