@rejourneyco/react-native 1.0.2 → 1.0.4

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 (43) hide show
  1. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
  2. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
  3. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
  4. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
  5. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
  6. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
  7. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +14 -27
  8. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
  9. package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
  10. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
  11. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
  12. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
  13. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
  14. package/ios/Capture/RJCaptureEngine.m +9 -61
  15. package/ios/Capture/RJViewHierarchyScanner.m +68 -51
  16. package/ios/Core/RJLifecycleManager.m +0 -14
  17. package/ios/Core/Rejourney.mm +24 -37
  18. package/ios/Network/RJDeviceAuthManager.m +0 -2
  19. package/ios/Network/RJUploadManager.h +8 -0
  20. package/ios/Network/RJUploadManager.m +45 -0
  21. package/ios/Privacy/RJPrivacyMask.m +5 -31
  22. package/ios/Rejourney.h +0 -14
  23. package/ios/Touch/RJTouchInterceptor.m +21 -15
  24. package/ios/Utils/RJEventBuffer.m +57 -69
  25. package/ios/Utils/RJWindowUtils.m +87 -86
  26. package/lib/commonjs/index.js +44 -31
  27. package/lib/commonjs/sdk/autoTracking.js +0 -3
  28. package/lib/commonjs/sdk/constants.js +1 -1
  29. package/lib/commonjs/sdk/networkInterceptor.js +0 -11
  30. package/lib/commonjs/sdk/utils.js +73 -14
  31. package/lib/module/index.js +44 -31
  32. package/lib/module/sdk/autoTracking.js +0 -3
  33. package/lib/module/sdk/constants.js +1 -1
  34. package/lib/module/sdk/networkInterceptor.js +0 -11
  35. package/lib/module/sdk/utils.js +73 -14
  36. package/lib/typescript/sdk/constants.d.ts +1 -1
  37. package/lib/typescript/sdk/utils.d.ts +31 -1
  38. package/package.json +16 -4
  39. package/src/index.ts +42 -20
  40. package/src/sdk/autoTracking.ts +0 -2
  41. package/src/sdk/constants.ts +14 -14
  42. package/src/sdk/networkInterceptor.ts +0 -9
  43. 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 // Reset billing blocked state for new session
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 // Keep in sync initially
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 // No-op, return success
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 // Re-throw to propagate cancellation properly
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 // Return success - skip is intentional, not an error
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() // Fallback for unknown types
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") // Don't upload frames on evaluation failure (fail-safe)
1274
+ Pair(false, "exception")
1361
1275
  }
1362
1276
  }
1363
1277
  }