@rejourneyco/react-native 1.0.2 → 1.0.3
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/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +3 -26
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
- package/ios/Capture/RJViewHierarchyScanner.m +68 -51
- package/ios/Core/RJLifecycleManager.m +0 -14
- package/ios/Core/Rejourney.mm +24 -37
- package/ios/Network/RJDeviceAuthManager.m +0 -2
- package/ios/Network/RJUploadManager.h +8 -0
- package/ios/Network/RJUploadManager.m +45 -0
- package/ios/Privacy/RJPrivacyMask.m +5 -31
- package/ios/Rejourney.h +0 -14
- package/ios/Touch/RJTouchInterceptor.m +21 -15
- package/ios/Utils/RJEventBuffer.m +57 -69
- package/ios/Utils/RJWindowUtils.m +87 -86
- package/lib/commonjs/index.js +42 -30
- package/lib/commonjs/sdk/autoTracking.js +0 -3
- package/lib/commonjs/sdk/networkInterceptor.js +0 -11
- package/lib/commonjs/sdk/utils.js +73 -14
- package/lib/module/index.js +42 -30
- package/lib/module/sdk/autoTracking.js +0 -3
- package/lib/module/sdk/networkInterceptor.js +0 -11
- package/lib/module/sdk/utils.js +73 -14
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/package.json +16 -4
- package/src/index.ts +40 -19
- package/src/sdk/autoTracking.ts +0 -2
- package/src/sdk/constants.ts +13 -13
- package/src/sdk/networkInterceptor.ts +0 -9
- package/src/sdk/utils.ts +76 -14
|
@@ -41,13 +41,9 @@ class UploadManager(
|
|
|
41
41
|
private val context: Context,
|
|
42
42
|
var apiUrl: String
|
|
43
43
|
) {
|
|
44
|
-
// Configuration
|
|
45
44
|
var publicKey: String = ""
|
|
46
45
|
var deviceHash: String = ""
|
|
47
46
|
|
|
48
|
-
// NUCLEAR FIX: Two session ID fields to prevent recovery from corrupting the current session
|
|
49
|
-
// - sessionId: Used by uploadGzippedContent/uploadContent for recovery operations (can be temporarily changed)
|
|
50
|
-
// - activeSessionId: The REAL current session ID, never modified by recovery - use this for current session operations
|
|
51
47
|
var sessionId: String? = null
|
|
52
48
|
private var activeSessionId: String? = null
|
|
53
49
|
|
|
@@ -55,37 +51,29 @@ class UploadManager(
|
|
|
55
51
|
var sessionStartTime: Long = 0
|
|
56
52
|
var projectId: String? = null
|
|
57
53
|
|
|
58
|
-
// Background time tracking for billing exclusion
|
|
59
54
|
var totalBackgroundTimeMs: Long = 0
|
|
60
55
|
|
|
61
|
-
// Billing blocked flag - set when 402 is received
|
|
62
56
|
var billingBlocked: Boolean = false
|
|
63
57
|
|
|
64
58
|
private var batchNumber = AtomicInteger(0)
|
|
65
59
|
|
|
66
|
-
// Video segment uploader
|
|
67
60
|
private var segmentUploader: SegmentUploader? = null
|
|
68
61
|
|
|
69
|
-
// Circuit breaker state
|
|
70
62
|
private var consecutiveFailures = AtomicInteger(0)
|
|
71
63
|
private var circuitOpen = false
|
|
72
64
|
private var circuitOpenTime: Long = 0
|
|
73
65
|
private val circuitBreakerThreshold = 5
|
|
74
66
|
private val circuitResetTimeMs = 60_000L
|
|
75
67
|
|
|
76
|
-
// Retry configuration
|
|
77
68
|
private val maxRetries = 3
|
|
78
69
|
private val initialRetryDelayMs = 1000L
|
|
79
70
|
|
|
80
|
-
// Use shared client (SSL pinning removed)
|
|
81
71
|
private val client = HttpClientProvider.shared
|
|
82
72
|
|
|
83
73
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
84
74
|
|
|
85
|
-
// Serialize uploads + endSession so stopSession can't race an in-flight timer upload.
|
|
86
75
|
private val uploadMutex = Mutex()
|
|
87
76
|
|
|
88
|
-
// Crash-safe persistence (disk-backed pending uploads)
|
|
89
77
|
private val pendingRootDir: File by lazy {
|
|
90
78
|
File(context.filesDir, "rejourney/pending_uploads").apply { mkdirs() }
|
|
91
79
|
}
|
|
@@ -96,8 +84,7 @@ class UploadManager(
|
|
|
96
84
|
fun resetForNewSession() {
|
|
97
85
|
batchNumber.set(0)
|
|
98
86
|
totalBackgroundTimeMs = 0
|
|
99
|
-
billingBlocked = false
|
|
100
|
-
// CRITICAL FIX: Clear cached SegmentUploader to prevent stale session data
|
|
87
|
+
billingBlocked = false
|
|
101
88
|
segmentUploader = null
|
|
102
89
|
}
|
|
103
90
|
|
|
@@ -108,7 +95,7 @@ class UploadManager(
|
|
|
108
95
|
*/
|
|
109
96
|
fun setActiveSessionId(newSessionId: String) {
|
|
110
97
|
activeSessionId = newSessionId
|
|
111
|
-
sessionId = newSessionId
|
|
98
|
+
sessionId = newSessionId
|
|
112
99
|
Logger.debug("[UploadManager] sessionId SET to: $newSessionId (activeSessionId=$activeSessionId)")
|
|
113
100
|
}
|
|
114
101
|
|
|
@@ -184,7 +171,7 @@ class UploadManager(
|
|
|
184
171
|
@Deprecated("Use UploadWorker.scheduleRecoveryUpload() instead - this blocks all uploads")
|
|
185
172
|
suspend fun recoverPendingSessions(): Boolean {
|
|
186
173
|
Logger.warning("[UploadManager] recoverPendingSessions is DEPRECATED - use WorkManager recovery instead")
|
|
187
|
-
return true
|
|
174
|
+
return true
|
|
188
175
|
}
|
|
189
176
|
|
|
190
177
|
/**
|
|
@@ -198,13 +185,9 @@ class UploadManager(
|
|
|
198
185
|
events: List<Map<String, Any?>>,
|
|
199
186
|
isFinal: Boolean = false
|
|
200
187
|
): Boolean {
|
|
201
|
-
// CRITICAL FIX: Use activeSessionId for current session operations.
|
|
202
|
-
// sessionId can be temporarily modified by recoverPendingSessions(), causing events
|
|
203
|
-
// to be uploaded to the WRONG session. activeSessionId is protected from recovery.
|
|
204
188
|
val effectiveSessionId = activeSessionId ?: sessionId
|
|
205
189
|
Logger.debug("[UploadManager] uploadBatch: START (eventCount=${events.size}, isFinal=$isFinal, effectiveSessionId=$effectiveSessionId)")
|
|
206
190
|
|
|
207
|
-
// Skip uploads if billing is blocked
|
|
208
191
|
if (billingBlocked) {
|
|
209
192
|
Logger.warning("[UploadManager] uploadBatch: Upload skipped - billing blocked")
|
|
210
193
|
return false
|
|
@@ -223,20 +206,16 @@ class UploadManager(
|
|
|
223
206
|
|
|
224
207
|
Logger.debug("[UploadManager] uploadBatch: batchNumber=$currentBatch, canUploadNow=$canUploadNow, effectiveSessionId=$effectiveSessionId")
|
|
225
208
|
|
|
226
|
-
// If we're online, first flush any previously persisted pending payloads
|
|
227
|
-
// for the *current* session (offline queue drain).
|
|
228
209
|
if (canUploadNow) {
|
|
229
210
|
effectiveSessionId?.takeIf { it.isNotBlank() }?.let { sid ->
|
|
230
211
|
val ok = flushPendingForSession(sid)
|
|
231
212
|
if (!ok) {
|
|
232
|
-
// Don't block new data persistence; just mark as failure.
|
|
233
213
|
success = false
|
|
234
214
|
}
|
|
235
215
|
}
|
|
236
216
|
}
|
|
237
217
|
|
|
238
218
|
try {
|
|
239
|
-
// Upload events if present OR if this is the final batch (to send duration/endTime)
|
|
240
219
|
if (events.isNotEmpty() || isFinal) {
|
|
241
220
|
Logger.debug("[UploadManager] uploadBatch: Building payload (eventCount=${events.size}, isFinal=$isFinal)")
|
|
242
221
|
val payload = buildEventsPayload(events, currentBatch, isFinal)
|
|
@@ -288,17 +267,14 @@ class UploadManager(
|
|
|
288
267
|
onUploadFailure()
|
|
289
268
|
}
|
|
290
269
|
} catch (e: CancellationException) {
|
|
291
|
-
// Normal cancellation (e.g., app going to background) - not an error
|
|
292
|
-
// The data is persisted on disk and WorkManager will handle the upload
|
|
293
270
|
Logger.debug("[UploadManager] uploadBatch: Batch upload cancelled - WorkManager will handle upload")
|
|
294
|
-
throw e
|
|
271
|
+
throw e
|
|
295
272
|
} catch (e: Exception) {
|
|
296
273
|
Logger.error("[UploadManager] uploadBatch: ❌ Exception during batch upload: ${e.message}", e)
|
|
297
274
|
onUploadFailure()
|
|
298
275
|
success = false
|
|
299
276
|
}
|
|
300
277
|
|
|
301
|
-
// Semantics: return true when data is either uploaded (online) or safely persisted (offline).
|
|
302
278
|
val result = if (canUploadNow) {
|
|
303
279
|
success
|
|
304
280
|
} else {
|
|
@@ -319,18 +295,11 @@ class UploadManager(
|
|
|
319
295
|
endTime: Long,
|
|
320
296
|
frameCount: Int
|
|
321
297
|
): Boolean {
|
|
322
|
-
// CRITICAL FIX: Extract session ID from segment filename instead of using this.sessionId
|
|
323
|
-
// The segment filename format is: seg_<sessionId>_<timestamp>.mp4
|
|
324
|
-
// This ensures we upload to the correct session even when this.sessionId is stale
|
|
325
|
-
// (e.g., from a previous session that wasn't properly cleaned up)
|
|
326
298
|
val sid = extractSessionIdFromFilename(segmentFile.name) ?: sessionId ?: return false
|
|
327
299
|
|
|
328
|
-
// DEBUG: Log to trace stale session ID issue
|
|
329
300
|
Logger.debug("[UploadManager] uploadVideoSegment START (file=${segmentFile.name}, frames=$frameCount)")
|
|
330
301
|
Logger.debug("[UploadManager] sessionId from filename=$sid, this.sessionId=$sessionId")
|
|
331
302
|
|
|
332
|
-
// CRITICAL FIX: Ensure valid token before segment upload
|
|
333
|
-
// Same pattern as UploadWorker fix - token may have expired since uploader was created
|
|
334
303
|
val authManager = DeviceAuthManager.getInstance(context)
|
|
335
304
|
val tokenValid = authManager.ensureValidToken()
|
|
336
305
|
if (!tokenValid) {
|
|
@@ -338,7 +307,6 @@ class UploadManager(
|
|
|
338
307
|
}
|
|
339
308
|
|
|
340
309
|
val uploader = getOrCreateSegmentUploader()
|
|
341
|
-
// Always update token before each upload (may have been refreshed)
|
|
342
310
|
uploader.uploadToken = authManager.getCurrentUploadToken()
|
|
343
311
|
|
|
344
312
|
val result = uploader.uploadVideoSegment(
|
|
@@ -365,15 +333,12 @@ class UploadManager(
|
|
|
365
333
|
* Returns: session_1768582930679_9510E45F
|
|
366
334
|
*/
|
|
367
335
|
private fun extractSessionIdFromFilename(filename: String): String? {
|
|
368
|
-
// Remove prefix and extension
|
|
369
|
-
// Format: seg_session_<timestamp>_<hash>_<segmentTimestamp>.mp4
|
|
370
336
|
if (!filename.startsWith("seg_") || !filename.endsWith(".mp4")) {
|
|
371
337
|
return null
|
|
372
338
|
}
|
|
373
339
|
|
|
374
340
|
val withoutPrefix = filename.removePrefix("seg_").removeSuffix(".mp4")
|
|
375
341
|
|
|
376
|
-
// The session ID is everything except the last underscore-separated component (segment timestamp)
|
|
377
342
|
val lastUnderscore = withoutPrefix.lastIndexOf('_')
|
|
378
343
|
if (lastUnderscore <= 0) {
|
|
379
344
|
return null
|
|
@@ -398,8 +363,6 @@ class UploadManager(
|
|
|
398
363
|
Logger.debug("[UploadManager] uploadHierarchy: size=${hierarchyData.size} bytes, timestamp=$timestamp, sessionId=$sessionId")
|
|
399
364
|
Logger.debug("[UploadManager] uploadHierarchy: this.sessionId=$sessionId, activeSessionId=$activeSessionId")
|
|
400
365
|
|
|
401
|
-
// CRITICAL FIX: Ensure valid token before hierarchy upload
|
|
402
|
-
// Same pattern as uploadVideoSegment - token may have expired since uploader was created
|
|
403
366
|
val authManager = DeviceAuthManager.getInstance(context)
|
|
404
367
|
Logger.debug("[UploadManager] uploadHierarchy: Ensuring valid auth token...")
|
|
405
368
|
val tokenValid = authManager.ensureValidToken()
|
|
@@ -410,7 +373,6 @@ class UploadManager(
|
|
|
410
373
|
}
|
|
411
374
|
|
|
412
375
|
val uploader = getOrCreateSegmentUploader()
|
|
413
|
-
// Always update token before each upload (may have been refreshed)
|
|
414
376
|
uploader.uploadToken = authManager.getCurrentUploadToken()
|
|
415
377
|
Logger.debug("[UploadManager] uploadHierarchy: SegmentUploader ready, apiKey=${uploader.apiKey?.take(8)}..., projectId=${uploader.projectId}")
|
|
416
378
|
|
|
@@ -454,7 +416,6 @@ class UploadManager(
|
|
|
454
416
|
isKeyframe: Boolean
|
|
455
417
|
): Boolean {
|
|
456
418
|
val gzipped = gzipData(content) ?: return false
|
|
457
|
-
// CRITICAL FIX: Use activeSessionId for current session operations
|
|
458
419
|
val sidForPersist = (activeSessionId ?: sessionId ?: "").ifBlank { "unknown" }
|
|
459
420
|
persistPendingUpload(
|
|
460
421
|
sessionId = sidForPersist,
|
|
@@ -514,7 +475,6 @@ class UploadManager(
|
|
|
514
475
|
file.delete()
|
|
515
476
|
metaFile.delete()
|
|
516
477
|
} catch (_: Exception) {
|
|
517
|
-
// ignore
|
|
518
478
|
}
|
|
519
479
|
} else {
|
|
520
480
|
allOk = false
|
|
@@ -535,15 +495,12 @@ class UploadManager(
|
|
|
535
495
|
frameCount: Int,
|
|
536
496
|
isKeyframe: Boolean = false
|
|
537
497
|
): Boolean {
|
|
538
|
-
// Step 1: Gzip the content
|
|
539
498
|
val gzipped = gzipData(content)
|
|
540
499
|
if (gzipped == null) {
|
|
541
500
|
Logger.error("Failed to gzip $contentType data")
|
|
542
501
|
return false
|
|
543
502
|
}
|
|
544
503
|
|
|
545
|
-
// Crash-safe persistence: write pending gzipped payload before any network
|
|
546
|
-
// CRITICAL FIX: Use activeSessionId for current session operations
|
|
547
504
|
val sidForPersist = (activeSessionId ?: sessionId ?: "").ifBlank { "unknown" }
|
|
548
505
|
val pendingFile = persistPendingUpload(
|
|
549
506
|
sessionId = sidForPersist,
|
|
@@ -557,50 +514,41 @@ class UploadManager(
|
|
|
557
514
|
|
|
558
515
|
Logger.debug("$contentType batch $batchNumber: ${content.size} bytes -> ${gzipped.size} gzipped")
|
|
559
516
|
|
|
560
|
-
// Step 2: Request presigned URL
|
|
561
517
|
val presignResult = presignForContentType(contentType, batchNumber, gzipped.size, isKeyframe)
|
|
562
518
|
if (presignResult == null) {
|
|
563
519
|
Logger.error("Failed to get presigned URL for $contentType")
|
|
564
520
|
return false
|
|
565
521
|
}
|
|
566
522
|
|
|
567
|
-
// Check if server says to skip this upload (recording disabled for frames)
|
|
568
523
|
val skipUpload = presignResult.optBoolean("skipUpload", false)
|
|
569
524
|
if (skipUpload) {
|
|
570
525
|
Logger.debug("$contentType upload skipped - recording disabled for project")
|
|
571
|
-
// Clean up pending file since we don't need to upload
|
|
572
526
|
try {
|
|
573
527
|
pendingFile?.delete()
|
|
574
528
|
pendingFile?.let { File(it.parentFile, it.name + ".meta.json").delete() }
|
|
575
529
|
} catch (_: Exception) {}
|
|
576
|
-
return true
|
|
530
|
+
return true
|
|
577
531
|
}
|
|
578
532
|
|
|
579
533
|
val presignedUrl = presignResult.optString("presignedUrl")
|
|
580
534
|
val batchId = presignResult.optString("batchId")
|
|
581
535
|
|
|
582
|
-
// NUCLEAR FIX: Do NOT update this.sessionId from server response!
|
|
583
|
-
// This was corrupting the current session ID when uploading old/recovered sessions.
|
|
584
|
-
// The sessionId should only be set by RejourneyModuleImpl.startSession()
|
|
585
536
|
|
|
586
537
|
if (presignedUrl.isEmpty() || batchId.isEmpty()) {
|
|
587
538
|
Logger.error("Invalid presign response: $presignResult")
|
|
588
539
|
return false
|
|
589
540
|
}
|
|
590
541
|
|
|
591
|
-
// Step 3: Upload to S3
|
|
592
542
|
val uploadSuccess = uploadToS3(presignedUrl, gzipped)
|
|
593
543
|
if (!uploadSuccess) {
|
|
594
544
|
Logger.error("Failed to upload $contentType to S3")
|
|
595
545
|
return false
|
|
596
546
|
}
|
|
597
547
|
|
|
598
|
-
// Step 4: Complete batch
|
|
599
548
|
val completeSuccess = completeBatch(batchId, gzipped.size, eventCount, frameCount)
|
|
600
549
|
if (!completeSuccess) {
|
|
601
550
|
Logger.warning("Failed to complete batch $batchId (data uploaded to S3)")
|
|
602
551
|
} else {
|
|
603
|
-
// Only delete pending file when complete succeeds (safe to retry otherwise)
|
|
604
552
|
try {
|
|
605
553
|
pendingFile?.delete()
|
|
606
554
|
pendingFile?.let { File(it.parentFile, it.name + ".meta.json").delete() }
|
|
@@ -623,7 +571,6 @@ class UploadManager(
|
|
|
623
571
|
}
|
|
624
572
|
|
|
625
573
|
private fun parsePendingFilename(name: String): PendingName? {
|
|
626
|
-
// Format: <contentType>_<batchNumber>_<k|n>.gz
|
|
627
574
|
if (!name.endsWith(".gz")) return null
|
|
628
575
|
val base = name.removeSuffix(".gz")
|
|
629
576
|
val parts = base.split("_")
|
|
@@ -681,14 +628,11 @@ class UploadManager(
|
|
|
681
628
|
|
|
682
629
|
val presignedUrl = presignResult.optString("presignedUrl")
|
|
683
630
|
val batchId = presignResult.optString("batchId")
|
|
684
|
-
// NUCLEAR FIX: Do NOT update this.sessionId from server response!
|
|
685
|
-
// This was corrupting the current session ID when uploading old/recovered sessions.
|
|
686
631
|
if (presignedUrl.isEmpty() || batchId.isEmpty()) return false
|
|
687
632
|
|
|
688
633
|
val uploadSuccess = uploadToS3(presignedUrl, gzipped)
|
|
689
634
|
if (!uploadSuccess) return false
|
|
690
635
|
|
|
691
|
-
// Treat completion as required for deleting pending content
|
|
692
636
|
return completeBatch(batchId, gzipped.size, eventCount, frameCount)
|
|
693
637
|
}
|
|
694
638
|
|
|
@@ -703,7 +647,6 @@ class UploadManager(
|
|
|
703
647
|
): JSONObject? {
|
|
704
648
|
Logger.debug("[UploadManager] presignForContentType START (type=$contentType, batch=$batchNumber, size=$sizeBytes, sessionId=${sessionId?.take(20)}...)")
|
|
705
649
|
|
|
706
|
-
// CRITICAL FIX: Use activeSessionId for current session operations
|
|
707
650
|
val effectiveSessionId = activeSessionId ?: sessionId ?: ""
|
|
708
651
|
val body = JSONObject().apply {
|
|
709
652
|
put("batchNumber", batchNumber)
|
|
@@ -732,7 +675,6 @@ class UploadManager(
|
|
|
732
675
|
Logger.debug("[UploadManager] Presign SUCCESS - got batchId: ${result.optString("batchId", "null")}")
|
|
733
676
|
result
|
|
734
677
|
} else if (it.code == 402) {
|
|
735
|
-
// Payment required - billing blocked, stop uploads
|
|
736
678
|
Logger.warning("[UploadManager] Presign BLOCKED (402) - billing issue, stopping uploads")
|
|
737
679
|
billingBlocked = true
|
|
738
680
|
null
|
|
@@ -748,7 +690,6 @@ class UploadManager(
|
|
|
748
690
|
}
|
|
749
691
|
}
|
|
750
692
|
} catch (e: CancellationException) {
|
|
751
|
-
// Expected during shutdown - don't log as error
|
|
752
693
|
Logger.debug("[UploadManager] Presign request cancelled (shutdown)")
|
|
753
694
|
null
|
|
754
695
|
} catch (e: Exception) {
|
|
@@ -845,7 +786,6 @@ class UploadManager(
|
|
|
845
786
|
): Boolean {
|
|
846
787
|
Logger.debug("[UploadManager] ===== END SESSION START =====")
|
|
847
788
|
|
|
848
|
-
// Use explicit sessionId if provided, otherwise fall back to current sessionId
|
|
849
789
|
val effectiveSessionId = sessionIdOverride ?: sessionId
|
|
850
790
|
Logger.debug("[UploadManager] endSession: sessionId=$effectiveSessionId, totalBackgroundTimeMs=$totalBackgroundTimeMs")
|
|
851
791
|
|
|
@@ -856,7 +796,6 @@ class UploadManager(
|
|
|
856
796
|
|
|
857
797
|
Logger.debug("[UploadManager] endSession: Sending session end for: $effectiveSessionId")
|
|
858
798
|
|
|
859
|
-
// Use provided endedAt if available (for crash recovery), otherwise current time
|
|
860
799
|
val endedAt = endedAtOverride ?: System.currentTimeMillis()
|
|
861
800
|
Logger.debug("[UploadManager] endSession: endedAt=$endedAt (override=${endedAtOverride != null})")
|
|
862
801
|
|
|
@@ -936,7 +875,6 @@ class UploadManager(
|
|
|
936
875
|
return false
|
|
937
876
|
}
|
|
938
877
|
|
|
939
|
-
// Temporarily use crash session ID for presign
|
|
940
878
|
val originalSessionId = sessionId
|
|
941
879
|
sessionId = crashSessionId
|
|
942
880
|
|
|
@@ -965,7 +903,6 @@ class UploadManager(
|
|
|
965
903
|
Logger.debug("Crash report uploaded")
|
|
966
904
|
return true
|
|
967
905
|
} finally {
|
|
968
|
-
// Restore original session ID
|
|
969
906
|
sessionId = originalSessionId
|
|
970
907
|
}
|
|
971
908
|
}
|
|
@@ -997,7 +934,6 @@ class UploadManager(
|
|
|
997
934
|
return false
|
|
998
935
|
}
|
|
999
936
|
|
|
1000
|
-
// Temporarily use ANR session ID for presign
|
|
1001
937
|
val originalSessionId = sessionId
|
|
1002
938
|
sessionId = anrSessionId
|
|
1003
939
|
|
|
@@ -1026,7 +962,6 @@ class UploadManager(
|
|
|
1026
962
|
Logger.debug("ANR report uploaded")
|
|
1027
963
|
return true
|
|
1028
964
|
} finally {
|
|
1029
|
-
// Restore original session ID
|
|
1030
965
|
sessionId = originalSessionId
|
|
1031
966
|
}
|
|
1032
967
|
}
|
|
@@ -1086,8 +1021,6 @@ class UploadManager(
|
|
|
1086
1021
|
|
|
1087
1022
|
val currentTime = System.currentTimeMillis()
|
|
1088
1023
|
|
|
1089
|
-
// CRITICAL FIX: Use activeSessionId for current session operations
|
|
1090
|
-
// sessionId can be temporarily modified by recoverPendingSessions()
|
|
1091
1024
|
val effectiveSessionId = activeSessionId ?: sessionId ?: ""
|
|
1092
1025
|
|
|
1093
1026
|
val payload = JSONObject().apply {
|
|
@@ -1119,7 +1052,6 @@ class UploadManager(
|
|
|
1119
1052
|
val payloadBytes = payload.toString().toByteArray(Charsets.UTF_8)
|
|
1120
1053
|
Logger.debug("[UploadManager] buildEventsPayload: Payload built - size=${payloadBytes.size} bytes, sessionId=${sessionId ?: "null"}, userId=${userId.ifEmpty { "anonymous" }}, eventCount=${events.size}")
|
|
1121
1054
|
|
|
1122
|
-
// Log event types in payload
|
|
1123
1055
|
if (events.isNotEmpty()) {
|
|
1124
1056
|
val eventTypes = events.mapNotNull { it["type"]?.toString() }.groupingBy { it }.eachCount()
|
|
1125
1057
|
Logger.debug("[UploadManager] buildEventsPayload: Event types in payload: ${eventTypes.entries.joinToString(", ") { "${it.key}=${it.value}" }}")
|
|
@@ -1149,7 +1081,7 @@ class UploadManager(
|
|
|
1149
1081
|
}
|
|
1150
1082
|
}
|
|
1151
1083
|
is Number, is String, is Boolean -> value
|
|
1152
|
-
else -> value.toString()
|
|
1084
|
+
else -> value.toString()
|
|
1153
1085
|
}
|
|
1154
1086
|
}
|
|
1155
1087
|
|
|
@@ -1164,12 +1096,6 @@ class UploadManager(
|
|
|
1164
1096
|
put("deviceHash", deviceHash)
|
|
1165
1097
|
put("name", Build.DEVICE)
|
|
1166
1098
|
|
|
1167
|
-
// Screen info
|
|
1168
|
-
// IMPORTANT: Use density-independent pixels (dp) to match touch coordinate format.
|
|
1169
|
-
// Touch coordinates from TouchInterceptor are normalized to dp (divided by density).
|
|
1170
|
-
// Using dp ensures consistent coordinate system across:
|
|
1171
|
-
// - iOS (uses points natively)
|
|
1172
|
-
// - Android (raw pixels divided by density = dp)
|
|
1173
1099
|
val displayMetrics = context.resources.displayMetrics
|
|
1174
1100
|
val density = if (displayMetrics.density > 0f) displayMetrics.density else 1f
|
|
1175
1101
|
var widthPx = displayMetrics.widthPixels
|
|
@@ -1184,16 +1110,13 @@ class UploadManager(
|
|
|
1184
1110
|
put("screenHeight", (heightPx / density).toInt())
|
|
1185
1111
|
put("screenScale", density)
|
|
1186
1112
|
|
|
1187
|
-
// App info
|
|
1188
1113
|
try {
|
|
1189
1114
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
1190
1115
|
put("appVersion", packageInfo.versionName)
|
|
1191
1116
|
put("appId", context.packageName)
|
|
1192
1117
|
} catch (e: Exception) {
|
|
1193
|
-
// Ignore
|
|
1194
1118
|
}
|
|
1195
1119
|
|
|
1196
|
-
// Network info
|
|
1197
1120
|
val networkQuality = NetworkMonitor.getInstance(context).captureNetworkQuality()
|
|
1198
1121
|
val networkMap = networkQuality.toMap()
|
|
1199
1122
|
put("networkType", networkMap["networkType"] ?: "none")
|
|
@@ -1210,7 +1133,6 @@ class UploadManager(
|
|
|
1210
1133
|
val deviceAuthManager = DeviceAuthManager.getInstance(context)
|
|
1211
1134
|
val token = deviceAuthManager.getCurrentUploadToken()
|
|
1212
1135
|
|
|
1213
|
-
// Log token status for debugging
|
|
1214
1136
|
Logger.debug("[UploadManager] buildAuthenticatedRequest: path=$path, token=${if (token != null) "present" else "NULL"}")
|
|
1215
1137
|
|
|
1216
1138
|
if (token == null) {
|
|
@@ -1228,7 +1150,6 @@ class UploadManager(
|
|
|
1228
1150
|
.url("$apiUrl$path")
|
|
1229
1151
|
.post(body.toRequestBody("application/json".toMediaType()))
|
|
1230
1152
|
.apply {
|
|
1231
|
-
// Match iOS header names exactly
|
|
1232
1153
|
if (publicKey.isNotEmpty()) {
|
|
1233
1154
|
addHeader("X-Rejourney-Key", publicKey)
|
|
1234
1155
|
}
|
|
@@ -1259,10 +1180,8 @@ class UploadManager(
|
|
|
1259
1180
|
}
|
|
1260
1181
|
|
|
1261
1182
|
private fun canUpload(): Boolean {
|
|
1262
|
-
// Check circuit breaker
|
|
1263
1183
|
if (circuitOpen) {
|
|
1264
1184
|
if (System.currentTimeMillis() - circuitOpenTime > circuitResetTimeMs) {
|
|
1265
|
-
// Reset circuit breaker
|
|
1266
1185
|
circuitOpen = false
|
|
1267
1186
|
consecutiveFailures.set(0)
|
|
1268
1187
|
Logger.debug("Circuit breaker reset")
|
|
@@ -1271,7 +1190,6 @@ class UploadManager(
|
|
|
1271
1190
|
}
|
|
1272
1191
|
}
|
|
1273
1192
|
|
|
1274
|
-
// Check network
|
|
1275
1193
|
val networkMonitor = NetworkMonitor.getInstance(context)
|
|
1276
1194
|
return networkMonitor.isConnected
|
|
1277
1195
|
}
|
|
@@ -1293,9 +1211,6 @@ class UploadManager(
|
|
|
1293
1211
|
}
|
|
1294
1212
|
}
|
|
1295
1213
|
|
|
1296
|
-
// =============================================================================
|
|
1297
|
-
// Replay Promotion
|
|
1298
|
-
// =============================================================================
|
|
1299
1214
|
|
|
1300
1215
|
/**
|
|
1301
1216
|
* Whether this session has been promoted for replay upload.
|
|
@@ -1317,7 +1232,6 @@ class UploadManager(
|
|
|
1317
1232
|
return Pair(false, "network_unavailable")
|
|
1318
1233
|
}
|
|
1319
1234
|
|
|
1320
|
-
// CRITICAL FIX: Use activeSessionId for current session operations
|
|
1321
1235
|
val effectiveSessionId = activeSessionId ?: sessionId ?: ""
|
|
1322
1236
|
|
|
1323
1237
|
val body = JSONObject().apply {
|
|
@@ -1357,7 +1271,7 @@ class UploadManager(
|
|
|
1357
1271
|
}
|
|
1358
1272
|
} catch (e: Exception) {
|
|
1359
1273
|
Logger.warning("Replay promotion evaluation error: ${e.message}")
|
|
1360
|
-
Pair(false, "exception")
|
|
1274
|
+
Pair(false, "exception")
|
|
1361
1275
|
}
|
|
1362
1276
|
}
|
|
1363
1277
|
}
|