@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,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
|
+
}
|