@rejourneyco/react-native 1.0.7 → 1.0.9

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 (52) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +109 -26
  3. package/android/src/main/java/com/rejourney/engine/DeviceRegistrar.kt +18 -3
  4. package/android/src/main/java/com/rejourney/engine/RejourneyImpl.kt +69 -17
  5. package/android/src/main/java/com/rejourney/recording/AnrSentinel.kt +27 -2
  6. package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +30 -0
  7. package/android/src/main/java/com/rejourney/recording/RejourneyNetworkInterceptor.kt +100 -0
  8. package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +260 -174
  9. package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +246 -34
  10. package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
  11. package/android/src/main/java/com/rejourney/recording/StabilityMonitor.kt +3 -0
  12. package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +19 -4
  13. package/android/src/main/java/com/rejourney/recording/ViewHierarchyScanner.kt +8 -0
  14. package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +251 -85
  15. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
  16. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +18 -0
  17. package/ios/Engine/DeviceRegistrar.swift +13 -3
  18. package/ios/Engine/RejourneyImpl.swift +202 -133
  19. package/ios/Recording/AnrSentinel.swift +58 -25
  20. package/ios/Recording/InteractionRecorder.swift +29 -0
  21. package/ios/Recording/RejourneyURLProtocol.swift +168 -0
  22. package/ios/Recording/ReplayOrchestrator.swift +241 -147
  23. package/ios/Recording/SegmentDispatcher.swift +155 -13
  24. package/ios/Recording/SpecialCases.swift +614 -0
  25. package/ios/Recording/StabilityMonitor.swift +42 -34
  26. package/ios/Recording/TelemetryPipeline.swift +38 -3
  27. package/ios/Recording/ViewHierarchyScanner.swift +1 -0
  28. package/ios/Recording/VisualCapture.swift +104 -28
  29. package/ios/Rejourney.mm +27 -8
  30. package/ios/Utility/ImageBlur.swift +0 -1
  31. package/lib/commonjs/index.js +32 -20
  32. package/lib/commonjs/sdk/autoTracking.js +162 -11
  33. package/lib/commonjs/sdk/constants.js +2 -2
  34. package/lib/commonjs/sdk/networkInterceptor.js +84 -4
  35. package/lib/commonjs/sdk/utils.js +1 -1
  36. package/lib/module/index.js +32 -20
  37. package/lib/module/sdk/autoTracking.js +162 -11
  38. package/lib/module/sdk/constants.js +2 -2
  39. package/lib/module/sdk/networkInterceptor.js +84 -4
  40. package/lib/module/sdk/utils.js +1 -1
  41. package/lib/typescript/NativeRejourney.d.ts +5 -2
  42. package/lib/typescript/sdk/autoTracking.d.ts +3 -1
  43. package/lib/typescript/sdk/constants.d.ts +2 -2
  44. package/lib/typescript/types/index.d.ts +15 -8
  45. package/package.json +4 -4
  46. package/src/NativeRejourney.ts +8 -5
  47. package/src/index.ts +46 -29
  48. package/src/sdk/autoTracking.ts +176 -11
  49. package/src/sdk/constants.ts +2 -2
  50. package/src/sdk/networkInterceptor.ts +110 -1
  51. package/src/sdk/utils.ts +1 -1
  52. package/src/types/index.ts +16 -9
@@ -57,6 +57,22 @@ final class SegmentDispatcher {
57
57
  private let retryLock = NSLock()
58
58
  private var active = true
59
59
 
60
+ private let metricsLock = NSLock()
61
+ private var uploadSuccessCount = 0
62
+ private var uploadFailureCount = 0
63
+ private var retryAttemptCount = 0
64
+ private var circuitBreakerOpenCount = 0
65
+ private var memoryEvictionCount = 0
66
+ private var offlinePersistCount = 0
67
+ private var sessionStartCount = 0
68
+ private var crashCount = 0
69
+ private var totalBytesUploaded: Int64 = 0
70
+ private var totalBytesEvicted: Int64 = 0
71
+ private var totalUploadDurationMs: Double = 0
72
+ private var uploadDurationSampleCount = 0
73
+ private var lastUploadTime: Int64?
74
+ private var lastRetryTime: Int64?
75
+
60
76
  private init() {}
61
77
 
62
78
  func configure(replayId: String, apiToken: String?, credential: String?, projectId: String?, isSampledIn: Bool = true) {
@@ -68,6 +84,7 @@ final class SegmentDispatcher {
68
84
  batchSeqNumber = 0
69
85
  billingBlocked = false
70
86
  consecutiveFailures = 0
87
+ resetSessionTelemetry()
71
88
  }
72
89
 
73
90
  /// Reactivate the dispatcher for a new session
@@ -100,7 +117,8 @@ final class SegmentDispatcher {
100
117
  rangeStart: startMs,
101
118
  rangeEnd: endMs,
102
119
  itemCount: frameCount,
103
- attempt: 0
120
+ attempt: 0,
121
+ batchNumber: 0
104
122
  )
105
123
  scheduleUpload(upload, completion: completion)
106
124
  }
@@ -118,7 +136,8 @@ final class SegmentDispatcher {
118
136
  rangeStart: timestampMs,
119
137
  rangeEnd: timestampMs,
120
138
  itemCount: 1,
121
- attempt: 0
139
+ attempt: 0,
140
+ batchNumber: 0
122
141
  )
123
142
  scheduleUpload(upload, completion: completion)
124
143
  }
@@ -148,11 +167,21 @@ final class SegmentDispatcher {
148
167
  }
149
168
  }
150
169
 
151
- func concludeReplay(replayId: String, concludedAt: UInt64, backgroundDurationMs: UInt64, metrics: [String: Any]?, completion: @escaping (Bool) -> Void) {
170
+ func concludeReplay(
171
+ replayId: String,
172
+ concludedAt: UInt64,
173
+ backgroundDurationMs: UInt64,
174
+ metrics: [String: Any]?,
175
+ currentQueueDepth: Int = 0,
176
+ endReason: String? = nil,
177
+ lifecycleVersion: Int? = nil,
178
+ completion: @escaping (Bool) -> Void
179
+ ) {
152
180
  guard let url = URL(string: "\(endpoint)/api/ingest/session/end") else {
153
181
  completion(false)
154
182
  return
155
183
  }
184
+ ingestFinalizeMetrics(metrics)
156
185
 
157
186
  var req = URLRequest(url: url)
158
187
  req.httpMethod = "POST"
@@ -162,6 +191,13 @@ final class SegmentDispatcher {
162
191
  var body: [String: Any] = ["sessionId": replayId, "endedAt": concludedAt]
163
192
  if backgroundDurationMs > 0 { body["totalBackgroundTimeMs"] = backgroundDurationMs }
164
193
  if let m = metrics { body["metrics"] = m }
194
+ body["sdkTelemetry"] = sdkTelemetrySnapshot(currentQueueDepth: currentQueueDepth)
195
+ if let endReason, !endReason.isEmpty {
196
+ body["endReason"] = endReason
197
+ }
198
+ if let lifecycleVersion, lifecycleVersion > 0 {
199
+ body["lifecycleVersion"] = lifecycleVersion
200
+ }
165
201
 
166
202
  do {
167
203
  req.httpBody = try JSONSerialization.data(withJSONObject: body)
@@ -222,7 +258,17 @@ final class SegmentDispatcher {
222
258
 
223
259
  private func registerFailure() {
224
260
  consecutiveFailures += 1
261
+ metricsLock.lock()
262
+ uploadFailureCount += 1
263
+ metricsLock.unlock()
264
+
225
265
  if consecutiveFailures >= circuitBreakerThreshold {
266
+ metricsLock.lock()
267
+ if !circuitOpen {
268
+ circuitBreakerOpenCount += 1
269
+ }
270
+ metricsLock.unlock()
271
+
226
272
  circuitOpen = true
227
273
  circuitOpenTime = Date().timeIntervalSince1970
228
274
  }
@@ -230,6 +276,10 @@ final class SegmentDispatcher {
230
276
 
231
277
  private func registerSuccess() {
232
278
  consecutiveFailures = 0
279
+ metricsLock.lock()
280
+ uploadSuccessCount += 1
281
+ lastUploadTime = Self.nowMs()
282
+ metricsLock.unlock()
233
283
  }
234
284
 
235
285
  private func scheduleUpload(_ upload: PendingUpload, completion: ((Bool) -> Void)?) {
@@ -260,7 +310,7 @@ final class SegmentDispatcher {
260
310
  return
261
311
  }
262
312
 
263
- self.uploadToS3(url: presign.presignedUrl, payload: upload.payload, contentType: upload.contentType) { s3ok in
313
+ self.uploadToS3(url: presign.presignedUrl, payload: upload.payload) { s3ok in
264
314
  guard s3ok else {
265
315
  self.registerFailure()
266
316
  self.scheduleRetryIfNeeded(upload, completion: completion)
@@ -284,6 +334,11 @@ final class SegmentDispatcher {
284
334
  retryLock.lock()
285
335
  retryQueue.append(retry)
286
336
  retryLock.unlock()
337
+
338
+ metricsLock.lock()
339
+ retryAttemptCount += 1
340
+ lastRetryTime = Self.nowMs()
341
+ metricsLock.unlock()
287
342
  }
288
343
  completion?(false)
289
344
  }
@@ -316,7 +371,7 @@ final class SegmentDispatcher {
316
371
 
317
372
  if upload.contentType == "events" {
318
373
  body["contentType"] = "events"
319
- body["batchNumber"] = batchSeqNumber
374
+ body["batchNumber"] = upload.batchNumber
320
375
  body["isSampledIn"] = isSampledIn // Server-side enforcement
321
376
  } else {
322
377
  body["kind"] = upload.contentType
@@ -368,7 +423,7 @@ final class SegmentDispatcher {
368
423
  }.resume()
369
424
  }
370
425
 
371
- private func uploadToS3(url: String, payload: Data, contentType: String, completion: @escaping (Bool) -> Void) {
426
+ private func uploadToS3(url: String, payload: Data, completion: @escaping (Bool) -> Void) {
372
427
  guard let uploadUrl = URL(string: url) else {
373
428
  completion(false)
374
429
  return
@@ -377,16 +432,25 @@ final class SegmentDispatcher {
377
432
  var req = URLRequest(url: uploadUrl)
378
433
  req.httpMethod = "PUT"
379
434
 
380
- switch contentType {
381
- case "video": req.setValue("video/mp4", forHTTPHeaderField: "Content-Type")
382
- default: req.setValue("application/gzip", forHTTPHeaderField: "Content-Type")
383
- }
435
+ req.setValue("application/gzip", forHTTPHeaderField: "Content-Type")
384
436
 
385
437
  req.httpBody = payload
438
+ let startMs = Date().timeIntervalSince1970 * 1000
386
439
 
387
440
  httpSession.dataTask(with: req) { _, resp, _ in
388
441
  let status = (resp as? HTTPURLResponse)?.statusCode ?? 0
389
- completion(status >= 200 && status < 300)
442
+ let succeeded = status >= 200 && status < 300
443
+ let durationMs = (Date().timeIntervalSince1970 * 1000) - startMs
444
+
445
+ self.metricsLock.lock()
446
+ self.uploadDurationSampleCount += 1
447
+ self.totalUploadDurationMs += durationMs
448
+ if succeeded {
449
+ self.totalBytesUploaded += Int64(payload.count)
450
+ }
451
+ self.metricsLock.unlock()
452
+
453
+ completion(succeeded)
390
454
  }.resume()
391
455
  }
392
456
 
@@ -407,6 +471,7 @@ final class SegmentDispatcher {
407
471
  "actualSizeBytes": upload.payload.count,
408
472
  "timestamp": Date().timeIntervalSince1970 * 1000
409
473
  ]
474
+ body["sdkTelemetry"] = sdkTelemetrySnapshot(currentQueueDepth: 0)
410
475
 
411
476
  if upload.contentType == "events" {
412
477
  body["batchId"] = batchId
@@ -436,7 +501,8 @@ final class SegmentDispatcher {
436
501
  rangeStart: 0,
437
502
  rangeEnd: 0,
438
503
  itemCount: eventCount,
439
- attempt: 0
504
+ attempt: 0,
505
+ batchNumber: batchNum
440
506
  )
441
507
 
442
508
  requestPresignedUrl(upload: upload) { [weak self] presignResponse in
@@ -446,7 +512,7 @@ final class SegmentDispatcher {
446
512
  return
447
513
  }
448
514
 
449
- self.uploadToS3(url: presign.presignedUrl, payload: payload, contentType: "events") { s3ok in
515
+ self.uploadToS3(url: presign.presignedUrl, payload: payload) { s3ok in
450
516
  guard s3ok else {
451
517
  self.registerFailure()
452
518
  completion?(false)
@@ -474,6 +540,81 @@ final class SegmentDispatcher {
474
540
  req.setValue(sid, forHTTPHeaderField: "x-session-id")
475
541
  }
476
542
  }
543
+
544
+ private func ingestFinalizeMetrics(_ metrics: [String: Any]?) {
545
+ guard let crashes = (metrics?["crashCount"] as? NSNumber)?.intValue else { return }
546
+ metricsLock.lock()
547
+ crashCount = max(crashCount, crashes)
548
+ metricsLock.unlock()
549
+ }
550
+
551
+ func sdkTelemetrySnapshot(currentQueueDepth: Int = 0) -> [String: Any] {
552
+ retryLock.lock()
553
+ let retryDepth = retryQueue.count
554
+ retryLock.unlock()
555
+
556
+ metricsLock.lock()
557
+ let successCount = uploadSuccessCount
558
+ let failureCount = uploadFailureCount
559
+ let retryCount = retryAttemptCount
560
+ let breakerCount = circuitBreakerOpenCount
561
+ let memoryEvictions = memoryEvictionCount
562
+ let offlinePersists = offlinePersistCount
563
+ let starts = sessionStartCount
564
+ let crashes = crashCount
565
+ let uploadedBytes = totalBytesUploaded
566
+ let evictedBytes = totalBytesEvicted
567
+ let avgUploadDurationMs = uploadDurationSampleCount > 0
568
+ ? totalUploadDurationMs / Double(uploadDurationSampleCount)
569
+ : 0
570
+ let uploadTs = lastUploadTime
571
+ let retryTs = lastRetryTime
572
+ metricsLock.unlock()
573
+
574
+ let totalUploads = successCount + failureCount
575
+ let successRate = totalUploads > 0 ? Double(successCount) / Double(totalUploads) : 1.0
576
+
577
+ return [
578
+ "uploadSuccessCount": successCount,
579
+ "uploadFailureCount": failureCount,
580
+ "retryAttemptCount": retryCount,
581
+ "circuitBreakerOpenCount": breakerCount,
582
+ "memoryEvictionCount": memoryEvictions,
583
+ "offlinePersistCount": offlinePersists,
584
+ "sessionStartCount": starts,
585
+ "crashCount": crashes,
586
+ "uploadSuccessRate": successRate,
587
+ "avgUploadDurationMs": avgUploadDurationMs,
588
+ "currentQueueDepth": currentQueueDepth + retryDepth,
589
+ "lastUploadTime": uploadTs.map { NSNumber(value: $0) } ?? NSNull(),
590
+ "lastRetryTime": retryTs.map { NSNumber(value: $0) } ?? NSNull(),
591
+ "totalBytesUploaded": uploadedBytes,
592
+ "totalBytesEvicted": evictedBytes
593
+ ]
594
+ }
595
+
596
+ private func resetSessionTelemetry() {
597
+ metricsLock.lock()
598
+ uploadSuccessCount = 0
599
+ uploadFailureCount = 0
600
+ retryAttemptCount = 0
601
+ circuitBreakerOpenCount = 0
602
+ memoryEvictionCount = 0
603
+ offlinePersistCount = 0
604
+ sessionStartCount = 1
605
+ crashCount = 0
606
+ totalBytesUploaded = 0
607
+ totalBytesEvicted = 0
608
+ totalUploadDurationMs = 0
609
+ uploadDurationSampleCount = 0
610
+ lastUploadTime = nil
611
+ lastRetryTime = nil
612
+ metricsLock.unlock()
613
+ }
614
+
615
+ private static func nowMs() -> Int64 {
616
+ Int64(Date().timeIntervalSince1970 * 1000)
617
+ }
477
618
  }
478
619
 
479
620
  private struct PendingUpload {
@@ -484,6 +625,7 @@ private struct PendingUpload {
484
625
  let rangeEnd: UInt64
485
626
  let itemCount: Int
486
627
  var attempt: Int
628
+ let batchNumber: Int
487
629
  }
488
630
 
489
631
  private struct PresignResponse {