@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,297 @@
1
+ /**
2
+ * SDK telemetry and metrics collection.
3
+ * Android implementation aligned with iOS RJTelemetry.
4
+ */
5
+ package com.rejourney.utils
6
+
7
+ import com.rejourney.BuildConfig
8
+ import com.rejourney.core.Logger
9
+ import com.rejourney.core.SDKMetrics
10
+ import java.util.concurrent.locks.ReentrantLock
11
+ import kotlin.concurrent.withLock
12
+
13
+ enum class TelemetryEventType {
14
+ UPLOAD_SUCCESS,
15
+ UPLOAD_FAILURE,
16
+ RETRY_ATTEMPT,
17
+ CIRCUIT_BREAKER_OPEN,
18
+ CIRCUIT_BREAKER_CLOSE,
19
+ MEMORY_PRESSURE_EVICTION,
20
+ OFFLINE_QUEUE_PERSIST,
21
+ OFFLINE_QUEUE_RESTORE,
22
+ SESSION_START,
23
+ SESSION_END,
24
+ CRASH_DETECTED,
25
+ TOKEN_REFRESH
26
+ }
27
+
28
+ class Telemetry private constructor() {
29
+
30
+ companion object {
31
+ @Volatile
32
+ private var instance: Telemetry? = null
33
+
34
+ fun getInstance(): Telemetry {
35
+ return instance ?: synchronized(this) {
36
+ instance ?: Telemetry().also { instance = it }
37
+ }
38
+ }
39
+ }
40
+
41
+ private val lock = ReentrantLock()
42
+
43
+ private var uploadSuccessCount = 0
44
+ private var uploadFailureCount = 0
45
+ private var retryAttemptCount = 0
46
+ private var circuitBreakerOpenCount = 0
47
+ private var memoryEvictionCount = 0
48
+ private var offlinePersistCount = 0
49
+ private var sessionStartCount = 0
50
+ private var crashCount = 0
51
+ private var anrCount = 0
52
+ private var uploadSuccessRate = 1.0
53
+ private var avgUploadDurationMs = 0.0
54
+ private var currentQueueDepth = 0
55
+ private var lastUploadTime: Long? = null
56
+ private var lastRetryTime: Long? = null
57
+
58
+ private var totalUploadCount = 0
59
+ private var totalUploadDurationMs = 0L
60
+ private var totalBytesUploaded = 0L
61
+ private var totalBytesEvicted = 0L
62
+
63
+ fun recordEvent(eventType: TelemetryEventType, metadata: Map<String, Any?>? = null) {
64
+ lock.withLock {
65
+ when (eventType) {
66
+ TelemetryEventType.UPLOAD_SUCCESS -> {
67
+ uploadSuccessCount++
68
+ lastUploadTime = System.currentTimeMillis()
69
+ }
70
+ TelemetryEventType.UPLOAD_FAILURE -> {
71
+ uploadFailureCount++
72
+ }
73
+ TelemetryEventType.RETRY_ATTEMPT -> {
74
+ retryAttemptCount++
75
+ lastRetryTime = System.currentTimeMillis()
76
+ }
77
+ TelemetryEventType.CIRCUIT_BREAKER_OPEN -> {
78
+ circuitBreakerOpenCount++
79
+ Logger.warning("[Telemetry] Circuit breaker opened (total: $circuitBreakerOpenCount)")
80
+ }
81
+ TelemetryEventType.CIRCUIT_BREAKER_CLOSE -> {
82
+ Logger.debug("[Telemetry] Circuit breaker closed")
83
+ }
84
+ TelemetryEventType.MEMORY_PRESSURE_EVICTION -> {
85
+ memoryEvictionCount++
86
+ }
87
+ TelemetryEventType.OFFLINE_QUEUE_PERSIST -> {
88
+ offlinePersistCount++
89
+ Logger.debug("[Telemetry] Offline queue persisted (total: $offlinePersistCount)")
90
+ }
91
+ TelemetryEventType.OFFLINE_QUEUE_RESTORE -> {
92
+ Logger.debug("[Telemetry] Offline queue restored")
93
+ }
94
+ TelemetryEventType.SESSION_START -> {
95
+ sessionStartCount++
96
+ }
97
+ TelemetryEventType.SESSION_END -> {
98
+ logCurrentMetricsInternal()
99
+ }
100
+ TelemetryEventType.CRASH_DETECTED -> {
101
+ crashCount++
102
+ Logger.warning("[Telemetry] Crash detected (total: $crashCount)")
103
+ }
104
+ TelemetryEventType.TOKEN_REFRESH -> {
105
+ Logger.debug("[Telemetry] Token refresh triggered")
106
+ }
107
+ }
108
+
109
+ updateSuccessRate()
110
+ }
111
+
112
+ if (metadata != null) {
113
+ Logger.debug("[Telemetry] Event metadata: $metadata")
114
+ }
115
+ }
116
+
117
+ fun recordUploadDuration(durationMs: Long, success: Boolean, byteCount: Long) {
118
+ lock.withLock {
119
+ totalUploadCount++
120
+ totalUploadDurationMs += durationMs
121
+ avgUploadDurationMs = totalUploadDurationMs.toDouble() / totalUploadCount.toDouble()
122
+
123
+ if (success) {
124
+ totalBytesUploaded += byteCount
125
+ uploadSuccessCount++
126
+ lastUploadTime = System.currentTimeMillis()
127
+ } else {
128
+ uploadFailureCount++
129
+ }
130
+
131
+ updateSuccessRate()
132
+ }
133
+ }
134
+
135
+ fun recordFrameEviction(bytesEvicted: Long, frameCount: Int) {
136
+ lock.withLock {
137
+ memoryEvictionCount += frameCount
138
+ totalBytesEvicted += bytesEvicted
139
+ Logger.warning(
140
+ "[Telemetry] Memory eviction: $frameCount frames, ${totalBytesEvicted / 1024.0} KB total evicted"
141
+ )
142
+ }
143
+ }
144
+
145
+ fun recordQueueDepth(depth: Int) {
146
+ lock.withLock {
147
+ currentQueueDepth = depth
148
+ }
149
+ }
150
+
151
+ fun recordANR() {
152
+ lock.withLock {
153
+ anrCount++
154
+ Logger.warning("[Telemetry] ANR detected (total: $anrCount)")
155
+ }
156
+ }
157
+
158
+ fun currentMetrics(): SDKMetrics {
159
+ return lock.withLock {
160
+ SDKMetrics(
161
+ uploadSuccessCount = uploadSuccessCount,
162
+ uploadFailureCount = uploadFailureCount,
163
+ retryAttemptCount = retryAttemptCount,
164
+ circuitBreakerOpenCount = circuitBreakerOpenCount,
165
+ memoryEvictionCount = memoryEvictionCount,
166
+ offlinePersistCount = offlinePersistCount,
167
+ sessionStartCount = sessionStartCount,
168
+ crashCount = crashCount,
169
+ anrCount = anrCount,
170
+ uploadSuccessRate = uploadSuccessRate.toFloat(),
171
+ avgUploadDurationMs = avgUploadDurationMs.toLong(),
172
+ currentQueueDepth = currentQueueDepth,
173
+ lastUploadTime = lastUploadTime,
174
+ lastRetryTime = lastRetryTime,
175
+ totalBytesUploaded = totalBytesUploaded,
176
+ totalBytesEvicted = totalBytesEvicted
177
+ )
178
+ }
179
+ }
180
+
181
+ fun metricsAsMap(): Map<String, Any?> {
182
+ val metrics = currentMetrics()
183
+ return mapOf(
184
+ "uploadSuccessCount" to metrics.uploadSuccessCount,
185
+ "uploadFailureCount" to metrics.uploadFailureCount,
186
+ "retryAttemptCount" to metrics.retryAttemptCount,
187
+ "circuitBreakerOpenCount" to metrics.circuitBreakerOpenCount,
188
+ "memoryEvictionCount" to metrics.memoryEvictionCount,
189
+ "offlinePersistCount" to metrics.offlinePersistCount,
190
+ "sessionStartCount" to metrics.sessionStartCount,
191
+ "crashCount" to metrics.crashCount,
192
+ "anrCount" to metrics.anrCount,
193
+ "uploadSuccessRate" to metrics.uploadSuccessRate,
194
+ "avgUploadDurationMs" to metrics.avgUploadDurationMs,
195
+ "currentQueueDepth" to metrics.currentQueueDepth,
196
+ "lastUploadTime" to metrics.lastUploadTime,
197
+ "lastRetryTime" to metrics.lastRetryTime,
198
+ "totalBytesUploaded" to metrics.totalBytesUploaded,
199
+ "totalBytesEvicted" to metrics.totalBytesEvicted
200
+ )
201
+ }
202
+
203
+ fun reset() {
204
+ lock.withLock {
205
+ uploadSuccessCount = 0
206
+ uploadFailureCount = 0
207
+ retryAttemptCount = 0
208
+ circuitBreakerOpenCount = 0
209
+ memoryEvictionCount = 0
210
+ offlinePersistCount = 0
211
+ sessionStartCount = 0
212
+ crashCount = 0
213
+ anrCount = 0
214
+ uploadSuccessRate = 1.0
215
+ avgUploadDurationMs = 0.0
216
+ currentQueueDepth = 0
217
+ lastUploadTime = null
218
+ lastRetryTime = null
219
+ totalUploadCount = 0
220
+ totalUploadDurationMs = 0
221
+ totalBytesUploaded = 0
222
+ totalBytesEvicted = 0
223
+ }
224
+ }
225
+
226
+ fun logCurrentMetrics() {
227
+ if (!BuildConfig.DEBUG) {
228
+ return
229
+ }
230
+
231
+ lock.withLock {
232
+ logCurrentMetricsInternal()
233
+ }
234
+ }
235
+
236
+ private fun logCurrentMetricsInternal() {
237
+ Logger.debug("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
238
+ Logger.debug(" SDK Telemetry Summary")
239
+ Logger.debug(
240
+ " Uploads: $uploadSuccessCount success, $uploadFailureCount failed (${uploadSuccessRate * 100}%% success rate)"
241
+ )
242
+ Logger.debug(" Avg upload latency: ${"%.1f".format(avgUploadDurationMs)} ms")
243
+ Logger.debug(" Retries: $retryAttemptCount attempts")
244
+ Logger.debug(" Circuit breaker opens: $circuitBreakerOpenCount")
245
+ Logger.debug(
246
+ " Memory evictions: $memoryEvictionCount frames (${totalBytesEvicted / 1024.0} KB)"
247
+ )
248
+ Logger.debug(" Offline persists: $offlinePersistCount")
249
+ Logger.debug(" Data uploaded: ${totalBytesUploaded / 1024.0} KB")
250
+ Logger.debug("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
251
+ }
252
+
253
+ private fun updateSuccessRate() {
254
+ val total = uploadSuccessCount + uploadFailureCount
255
+ uploadSuccessRate = if (total > 0) {
256
+ uploadSuccessCount.toDouble() / total.toDouble()
257
+ } else {
258
+ 1.0
259
+ }
260
+ }
261
+
262
+ fun recordUploadSuccess(durationMs: Long, bytes: Long = 0) {
263
+ recordUploadDuration(durationMs, true, bytes)
264
+ }
265
+
266
+ fun recordUploadFailure() {
267
+ recordEvent(TelemetryEventType.UPLOAD_FAILURE)
268
+ }
269
+
270
+ fun recordRetryAttempt() {
271
+ recordEvent(TelemetryEventType.RETRY_ATTEMPT)
272
+ }
273
+
274
+ fun recordCircuitBreakerOpen() {
275
+ recordEvent(TelemetryEventType.CIRCUIT_BREAKER_OPEN)
276
+ }
277
+
278
+ fun recordCircuitBreakerClose() {
279
+ recordEvent(TelemetryEventType.CIRCUIT_BREAKER_CLOSE)
280
+ }
281
+
282
+ fun recordOfflinePersist() {
283
+ recordEvent(TelemetryEventType.OFFLINE_QUEUE_PERSIST)
284
+ }
285
+
286
+ fun recordSessionStart() {
287
+ recordEvent(TelemetryEventType.SESSION_START)
288
+ }
289
+
290
+ fun recordCrash() {
291
+ recordEvent(TelemetryEventType.CRASH_DETECTED)
292
+ }
293
+
294
+ fun updateQueueDepth(depth: Int) {
295
+ recordQueueDepth(depth)
296
+ }
297
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Window and view utilities.
3
+ * Android implementation aligned with iOS RJWindowUtils.
4
+ */
5
+ package com.rejourney.utils
6
+
7
+ import android.app.Activity
8
+ import android.content.Context
9
+ import android.view.View
10
+ import android.view.Window
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import java.security.SecureRandom
13
+
14
+ object WindowUtils {
15
+ private val random = SecureRandom()
16
+
17
+ /**
18
+ * Returns the current key window.
19
+ */
20
+ fun keyWindow(context: Context): Window? {
21
+ return try {
22
+ when (context) {
23
+ is ReactApplicationContext -> context.currentActivity?.window
24
+ is Activity -> context.window
25
+ else -> null
26
+ }
27
+ } catch (_: Exception) {
28
+ null
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Get the current activity's window.
34
+ */
35
+ fun getCurrentWindow(context: Context): Window? = keyWindow(context)
36
+
37
+ /**
38
+ * Get the current activity.
39
+ */
40
+ fun getCurrentActivity(context: Context): Activity? {
41
+ return try {
42
+ when (context) {
43
+ is ReactApplicationContext -> context.currentActivity
44
+ is Activity -> context
45
+ else -> null
46
+ }
47
+ } catch (_: Exception) {
48
+ null
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Finds the accessibility label for a view or its ancestors.
54
+ */
55
+ fun accessibilityLabelForView(view: View?): String? {
56
+ var current = view
57
+ while (current != null) {
58
+ val label = current.contentDescription?.toString()?.trim()
59
+ if (!label.isNullOrEmpty()) {
60
+ return label
61
+ }
62
+ val parent = current.parent
63
+ current = if (parent is View) parent else null
64
+ }
65
+ return null
66
+ }
67
+
68
+ /**
69
+ * Generates a unique session ID.
70
+ * Format: session_{timestamp}_{random_hex}
71
+ */
72
+ fun generateSessionId(): String {
73
+ val timestamp = System.currentTimeMillis()
74
+ val bytes = ByteArray(4)
75
+ random.nextBytes(bytes)
76
+ val hex = bytes.joinToString("") { "%02X".format(it) }
77
+ return "session_${timestamp}_$hex"
78
+ }
79
+
80
+ /**
81
+ * Returns the current timestamp in milliseconds.
82
+ */
83
+ fun currentTimestampMillis(): Long = System.currentTimeMillis()
84
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * New Architecture (TurboModules) module wrapper for Rejourney SDK.
3
+ *
4
+ * This thin wrapper extends the Codegen-generated NativeRejourneySpec for the New Architecture
5
+ * and delegates all method implementations to RejourneyModuleImpl.
6
+ *
7
+ * This file is compiled when newArchEnabled=true in gradle.properties.
8
+ */
9
+ package com.rejourney
10
+
11
+ import com.facebook.react.bridge.*
12
+ import com.facebook.react.module.annotations.ReactModule
13
+ import com.facebook.proguard.annotations.DoNotStrip
14
+ import com.rejourney.core.Logger
15
+
16
+ @ReactModule(name = RejourneyModule.NAME)
17
+ class RejourneyModule(reactContext: ReactApplicationContext) :
18
+ NativeRejourneySpec(reactContext) {
19
+
20
+ companion object {
21
+ const val NAME = RejourneyModuleImpl.NAME
22
+ }
23
+
24
+ private var initError: Throwable? = null
25
+
26
+ // Lazy initialization to avoid constructor crashes blocking module creation
27
+ private val impl: RejourneyModuleImpl? by lazy {
28
+ try {
29
+ RejourneyModuleImpl(reactContext, isNewArchitecture = true)
30
+ } catch (e: Throwable) {
31
+ initError = e
32
+ Logger.error("✗ CRITICAL: Failed to create RejourneyModuleImpl", e)
33
+ null
34
+ }
35
+ }
36
+
37
+ private fun getImplOrReject(promise: Promise): RejourneyModuleImpl? {
38
+ val instance = impl
39
+ if (instance == null) {
40
+ val message = initError?.message ?: "Module initialization failed"
41
+ promise.resolve(createErrorMap(message))
42
+ return null
43
+ }
44
+ return instance
45
+ }
46
+
47
+ override fun getName(): String = NAME
48
+
49
+ @ReactMethod
50
+ @DoNotStrip
51
+ override fun debugTriggerANR(durationMs: Double) {
52
+ val instance = impl
53
+ if (instance == null) {
54
+ Logger.error("debugTriggerANR failed: module not initialized")
55
+ return
56
+ }
57
+ instance.debugTriggerANR(durationMs)
58
+ }
59
+
60
+ override fun invalidate() {
61
+ impl?.invalidate()
62
+ super.invalidate()
63
+ }
64
+
65
+ @ReactMethod
66
+ @DoNotStrip
67
+ override fun startSession(userId: String, apiUrl: String, publicKey: String, promise: Promise) {
68
+ val instance = getImplOrReject(promise) ?: return
69
+ instance.startSession(userId, apiUrl, publicKey, promise)
70
+ }
71
+
72
+ @ReactMethod
73
+ @DoNotStrip
74
+ override fun stopSession(promise: Promise) {
75
+ val instance = getImplOrReject(promise) ?: return
76
+ instance.stopSession(promise)
77
+ }
78
+
79
+ @ReactMethod
80
+ @DoNotStrip
81
+ override fun logEvent(eventType: String, details: ReadableMap, promise: Promise) {
82
+ val instance = getImplOrReject(promise) ?: return
83
+ instance.logEvent(eventType, details, promise)
84
+ }
85
+
86
+ @ReactMethod
87
+ @DoNotStrip
88
+ override fun screenChanged(screenName: String, promise: Promise) {
89
+ val instance = getImplOrReject(promise) ?: return
90
+ instance.screenChanged(screenName, promise)
91
+ }
92
+
93
+ @ReactMethod
94
+ @DoNotStrip
95
+ override fun onScroll(offsetY: Double, promise: Promise) {
96
+ val instance = getImplOrReject(promise) ?: return
97
+ instance.onScroll(offsetY, promise)
98
+ }
99
+
100
+ @ReactMethod
101
+ @DoNotStrip
102
+ override fun markVisualChange(reason: String, importance: String, promise: Promise) {
103
+ val instance = getImplOrReject(promise) ?: return
104
+ instance.markVisualChange(reason, importance, promise)
105
+ }
106
+
107
+ @ReactMethod
108
+ @DoNotStrip
109
+ override fun onExternalURLOpened(urlScheme: String, promise: Promise) {
110
+ val instance = getImplOrReject(promise) ?: return
111
+ instance.onExternalURLOpened(urlScheme, promise)
112
+ }
113
+
114
+ @ReactMethod
115
+ @DoNotStrip
116
+ override fun onOAuthStarted(provider: String, promise: Promise) {
117
+ val instance = getImplOrReject(promise) ?: return
118
+ instance.onOAuthStarted(provider, promise)
119
+ }
120
+
121
+ @ReactMethod
122
+ @DoNotStrip
123
+ override fun onOAuthCompleted(provider: String, success: Boolean, promise: Promise) {
124
+ val instance = getImplOrReject(promise) ?: return
125
+ instance.onOAuthCompleted(provider, success, promise)
126
+ }
127
+
128
+ @ReactMethod
129
+ @DoNotStrip
130
+ override fun getSDKMetrics(promise: Promise) {
131
+ val instance = getImplOrReject(promise) ?: return
132
+ instance.getSDKMetrics(promise)
133
+ }
134
+
135
+ @ReactMethod
136
+ @DoNotStrip
137
+ override fun debugCrash() {
138
+ val instance = impl
139
+ if (instance == null) {
140
+ Logger.error("debugCrash failed: module not initialized")
141
+ return
142
+ }
143
+ instance.debugCrash()
144
+ }
145
+
146
+ @ReactMethod
147
+ @DoNotStrip
148
+ override fun getSessionId(promise: Promise) {
149
+ val instance = getImplOrReject(promise) ?: return
150
+ instance.getSessionId(promise)
151
+ }
152
+
153
+ @ReactMethod
154
+ @DoNotStrip
155
+ override fun maskViewByNativeID(nativeID: String, promise: Promise) {
156
+ val instance = getImplOrReject(promise) ?: return
157
+ instance.maskViewByNativeID(nativeID, promise)
158
+ }
159
+
160
+ @ReactMethod
161
+ @DoNotStrip
162
+ override fun unmaskViewByNativeID(nativeID: String, promise: Promise) {
163
+ val instance = getImplOrReject(promise) ?: return
164
+ instance.unmaskViewByNativeID(nativeID, promise)
165
+ }
166
+
167
+ @ReactMethod
168
+ @DoNotStrip
169
+ override fun setUserIdentity(userId: String, promise: Promise) {
170
+ val instance = getImplOrReject(promise) ?: return
171
+ instance.setUserIdentity(userId, promise)
172
+ }
173
+
174
+ @ReactMethod
175
+ @DoNotStrip
176
+ override fun setDebugMode(enabled: Boolean, promise: Promise) {
177
+ val instance = getImplOrReject(promise) ?: return
178
+ instance.setDebugMode(enabled, promise)
179
+ }
180
+
181
+ private fun createErrorMap(error: String): WritableMap {
182
+ return Arguments.createMap().apply {
183
+ putBoolean("success", false)
184
+ putString("error", error)
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * New Architecture (TurboModules) package registration for Rejourney SDK.
3
+ *
4
+ * This package is compiled when newArchEnabled=true in gradle.properties.
5
+ * It uses BaseReactPackage for proper TurboModule registration.
6
+ */
7
+ package com.rejourney
8
+
9
+ import com.facebook.react.BaseReactPackage
10
+ import com.facebook.react.bridge.NativeModule
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import com.facebook.react.module.model.ReactModuleInfo
13
+ import com.facebook.react.module.model.ReactModuleInfoProvider
14
+ import com.facebook.react.uimanager.ViewManager
15
+
16
+ class RejourneyPackage : BaseReactPackage() {
17
+
18
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
19
+ return if (name == RejourneyModuleImpl.NAME) {
20
+ RejourneyModule(reactContext)
21
+ } else {
22
+ null
23
+ }
24
+ }
25
+
26
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
27
+ return ReactModuleInfoProvider {
28
+ mapOf(
29
+ RejourneyModuleImpl.NAME to ReactModuleInfo(
30
+ RejourneyModuleImpl.NAME, // name
31
+ RejourneyModule::class.java.name, // className (full class name)
32
+ false, // canOverrideExistingModule
33
+ false, // needsEagerInit
34
+ false, // isCxxModule - NOT a C++ module
35
+ true // isTurboModule - MUST be true for TurboModules
36
+ )
37
+ )
38
+ }
39
+ }
40
+ }