@rejourneyco/react-native 1.0.7 → 1.0.8

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 (29) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
  3. package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
  4. package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
  5. package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
  6. package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
  7. package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
  8. package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
  9. package/ios/Engine/RejourneyImpl.swift +3 -18
  10. package/ios/Recording/InteractionRecorder.swift +28 -0
  11. package/ios/Recording/ReplayOrchestrator.swift +50 -17
  12. package/ios/Recording/SegmentDispatcher.swift +147 -13
  13. package/ios/Recording/SpecialCases.swift +614 -0
  14. package/ios/Recording/StabilityMonitor.swift +2 -2
  15. package/ios/Recording/TelemetryPipeline.swift +21 -3
  16. package/ios/Recording/VisualCapture.swift +50 -20
  17. package/lib/commonjs/index.js +4 -5
  18. package/lib/commonjs/sdk/constants.js +2 -2
  19. package/lib/commonjs/sdk/utils.js +1 -1
  20. package/lib/module/index.js +4 -5
  21. package/lib/module/sdk/constants.js +2 -2
  22. package/lib/module/sdk/utils.js +1 -1
  23. package/lib/typescript/sdk/constants.d.ts +2 -2
  24. package/lib/typescript/types/index.d.ts +1 -6
  25. package/package.json +2 -2
  26. package/src/index.ts +9 -10
  27. package/src/sdk/constants.ts +2 -2
  28. package/src/sdk/utils.ts +1 -1
  29. package/src/types/index.ts +1 -6
@@ -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,19 @@ 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
+ completion: @escaping (Bool) -> Void
177
+ ) {
152
178
  guard let url = URL(string: "\(endpoint)/api/ingest/session/end") else {
153
179
  completion(false)
154
180
  return
155
181
  }
182
+ ingestFinalizeMetrics(metrics)
156
183
 
157
184
  var req = URLRequest(url: url)
158
185
  req.httpMethod = "POST"
@@ -162,6 +189,7 @@ final class SegmentDispatcher {
162
189
  var body: [String: Any] = ["sessionId": replayId, "endedAt": concludedAt]
163
190
  if backgroundDurationMs > 0 { body["totalBackgroundTimeMs"] = backgroundDurationMs }
164
191
  if let m = metrics { body["metrics"] = m }
192
+ body["sdkTelemetry"] = sdkTelemetrySnapshot(currentQueueDepth: currentQueueDepth)
165
193
 
166
194
  do {
167
195
  req.httpBody = try JSONSerialization.data(withJSONObject: body)
@@ -222,7 +250,17 @@ final class SegmentDispatcher {
222
250
 
223
251
  private func registerFailure() {
224
252
  consecutiveFailures += 1
253
+ metricsLock.lock()
254
+ uploadFailureCount += 1
255
+ metricsLock.unlock()
256
+
225
257
  if consecutiveFailures >= circuitBreakerThreshold {
258
+ metricsLock.lock()
259
+ if !circuitOpen {
260
+ circuitBreakerOpenCount += 1
261
+ }
262
+ metricsLock.unlock()
263
+
226
264
  circuitOpen = true
227
265
  circuitOpenTime = Date().timeIntervalSince1970
228
266
  }
@@ -230,6 +268,10 @@ final class SegmentDispatcher {
230
268
 
231
269
  private func registerSuccess() {
232
270
  consecutiveFailures = 0
271
+ metricsLock.lock()
272
+ uploadSuccessCount += 1
273
+ lastUploadTime = Self.nowMs()
274
+ metricsLock.unlock()
233
275
  }
234
276
 
235
277
  private func scheduleUpload(_ upload: PendingUpload, completion: ((Bool) -> Void)?) {
@@ -260,7 +302,7 @@ final class SegmentDispatcher {
260
302
  return
261
303
  }
262
304
 
263
- self.uploadToS3(url: presign.presignedUrl, payload: upload.payload, contentType: upload.contentType) { s3ok in
305
+ self.uploadToS3(url: presign.presignedUrl, payload: upload.payload) { s3ok in
264
306
  guard s3ok else {
265
307
  self.registerFailure()
266
308
  self.scheduleRetryIfNeeded(upload, completion: completion)
@@ -284,6 +326,11 @@ final class SegmentDispatcher {
284
326
  retryLock.lock()
285
327
  retryQueue.append(retry)
286
328
  retryLock.unlock()
329
+
330
+ metricsLock.lock()
331
+ retryAttemptCount += 1
332
+ lastRetryTime = Self.nowMs()
333
+ metricsLock.unlock()
287
334
  }
288
335
  completion?(false)
289
336
  }
@@ -316,7 +363,7 @@ final class SegmentDispatcher {
316
363
 
317
364
  if upload.contentType == "events" {
318
365
  body["contentType"] = "events"
319
- body["batchNumber"] = batchSeqNumber
366
+ body["batchNumber"] = upload.batchNumber
320
367
  body["isSampledIn"] = isSampledIn // Server-side enforcement
321
368
  } else {
322
369
  body["kind"] = upload.contentType
@@ -368,7 +415,7 @@ final class SegmentDispatcher {
368
415
  }.resume()
369
416
  }
370
417
 
371
- private func uploadToS3(url: String, payload: Data, contentType: String, completion: @escaping (Bool) -> Void) {
418
+ private func uploadToS3(url: String, payload: Data, completion: @escaping (Bool) -> Void) {
372
419
  guard let uploadUrl = URL(string: url) else {
373
420
  completion(false)
374
421
  return
@@ -377,16 +424,25 @@ final class SegmentDispatcher {
377
424
  var req = URLRequest(url: uploadUrl)
378
425
  req.httpMethod = "PUT"
379
426
 
380
- switch contentType {
381
- case "video": req.setValue("video/mp4", forHTTPHeaderField: "Content-Type")
382
- default: req.setValue("application/gzip", forHTTPHeaderField: "Content-Type")
383
- }
427
+ req.setValue("application/gzip", forHTTPHeaderField: "Content-Type")
384
428
 
385
429
  req.httpBody = payload
430
+ let startMs = Date().timeIntervalSince1970 * 1000
386
431
 
387
432
  httpSession.dataTask(with: req) { _, resp, _ in
388
433
  let status = (resp as? HTTPURLResponse)?.statusCode ?? 0
389
- completion(status >= 200 && status < 300)
434
+ let succeeded = status >= 200 && status < 300
435
+ let durationMs = (Date().timeIntervalSince1970 * 1000) - startMs
436
+
437
+ self.metricsLock.lock()
438
+ self.uploadDurationSampleCount += 1
439
+ self.totalUploadDurationMs += durationMs
440
+ if succeeded {
441
+ self.totalBytesUploaded += Int64(payload.count)
442
+ }
443
+ self.metricsLock.unlock()
444
+
445
+ completion(succeeded)
390
446
  }.resume()
391
447
  }
392
448
 
@@ -407,6 +463,7 @@ final class SegmentDispatcher {
407
463
  "actualSizeBytes": upload.payload.count,
408
464
  "timestamp": Date().timeIntervalSince1970 * 1000
409
465
  ]
466
+ body["sdkTelemetry"] = sdkTelemetrySnapshot(currentQueueDepth: 0)
410
467
 
411
468
  if upload.contentType == "events" {
412
469
  body["batchId"] = batchId
@@ -436,7 +493,8 @@ final class SegmentDispatcher {
436
493
  rangeStart: 0,
437
494
  rangeEnd: 0,
438
495
  itemCount: eventCount,
439
- attempt: 0
496
+ attempt: 0,
497
+ batchNumber: batchNum
440
498
  )
441
499
 
442
500
  requestPresignedUrl(upload: upload) { [weak self] presignResponse in
@@ -446,7 +504,7 @@ final class SegmentDispatcher {
446
504
  return
447
505
  }
448
506
 
449
- self.uploadToS3(url: presign.presignedUrl, payload: payload, contentType: "events") { s3ok in
507
+ self.uploadToS3(url: presign.presignedUrl, payload: payload) { s3ok in
450
508
  guard s3ok else {
451
509
  self.registerFailure()
452
510
  completion?(false)
@@ -474,6 +532,81 @@ final class SegmentDispatcher {
474
532
  req.setValue(sid, forHTTPHeaderField: "x-session-id")
475
533
  }
476
534
  }
535
+
536
+ private func ingestFinalizeMetrics(_ metrics: [String: Any]?) {
537
+ guard let crashes = (metrics?["crashCount"] as? NSNumber)?.intValue else { return }
538
+ metricsLock.lock()
539
+ crashCount = max(crashCount, crashes)
540
+ metricsLock.unlock()
541
+ }
542
+
543
+ func sdkTelemetrySnapshot(currentQueueDepth: Int = 0) -> [String: Any] {
544
+ retryLock.lock()
545
+ let retryDepth = retryQueue.count
546
+ retryLock.unlock()
547
+
548
+ metricsLock.lock()
549
+ let successCount = uploadSuccessCount
550
+ let failureCount = uploadFailureCount
551
+ let retryCount = retryAttemptCount
552
+ let breakerCount = circuitBreakerOpenCount
553
+ let memoryEvictions = memoryEvictionCount
554
+ let offlinePersists = offlinePersistCount
555
+ let starts = sessionStartCount
556
+ let crashes = crashCount
557
+ let uploadedBytes = totalBytesUploaded
558
+ let evictedBytes = totalBytesEvicted
559
+ let avgUploadDurationMs = uploadDurationSampleCount > 0
560
+ ? totalUploadDurationMs / Double(uploadDurationSampleCount)
561
+ : 0
562
+ let uploadTs = lastUploadTime
563
+ let retryTs = lastRetryTime
564
+ metricsLock.unlock()
565
+
566
+ let totalUploads = successCount + failureCount
567
+ let successRate = totalUploads > 0 ? Double(successCount) / Double(totalUploads) : 1.0
568
+
569
+ return [
570
+ "uploadSuccessCount": successCount,
571
+ "uploadFailureCount": failureCount,
572
+ "retryAttemptCount": retryCount,
573
+ "circuitBreakerOpenCount": breakerCount,
574
+ "memoryEvictionCount": memoryEvictions,
575
+ "offlinePersistCount": offlinePersists,
576
+ "sessionStartCount": starts,
577
+ "crashCount": crashes,
578
+ "uploadSuccessRate": successRate,
579
+ "avgUploadDurationMs": avgUploadDurationMs,
580
+ "currentQueueDepth": currentQueueDepth + retryDepth,
581
+ "lastUploadTime": uploadTs.map { NSNumber(value: $0) } ?? NSNull(),
582
+ "lastRetryTime": retryTs.map { NSNumber(value: $0) } ?? NSNull(),
583
+ "totalBytesUploaded": uploadedBytes,
584
+ "totalBytesEvicted": evictedBytes
585
+ ]
586
+ }
587
+
588
+ private func resetSessionTelemetry() {
589
+ metricsLock.lock()
590
+ uploadSuccessCount = 0
591
+ uploadFailureCount = 0
592
+ retryAttemptCount = 0
593
+ circuitBreakerOpenCount = 0
594
+ memoryEvictionCount = 0
595
+ offlinePersistCount = 0
596
+ sessionStartCount = 1
597
+ crashCount = 0
598
+ totalBytesUploaded = 0
599
+ totalBytesEvicted = 0
600
+ totalUploadDurationMs = 0
601
+ uploadDurationSampleCount = 0
602
+ lastUploadTime = nil
603
+ lastRetryTime = nil
604
+ metricsLock.unlock()
605
+ }
606
+
607
+ private static func nowMs() -> Int64 {
608
+ Int64(Date().timeIntervalSince1970 * 1000)
609
+ }
477
610
  }
478
611
 
479
612
  private struct PendingUpload {
@@ -484,6 +617,7 @@ private struct PendingUpload {
484
617
  let rangeEnd: UInt64
485
618
  let itemCount: Int
486
619
  var attempt: Int
620
+ let batchNumber: Int
487
621
  }
488
622
 
489
623
  private struct PresignResponse {