@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.
- package/README.md +1 -1
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
- package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
- package/ios/Engine/RejourneyImpl.swift +3 -18
- package/ios/Recording/InteractionRecorder.swift +28 -0
- package/ios/Recording/ReplayOrchestrator.swift +50 -17
- package/ios/Recording/SegmentDispatcher.swift +147 -13
- package/ios/Recording/SpecialCases.swift +614 -0
- package/ios/Recording/StabilityMonitor.swift +2 -2
- package/ios/Recording/TelemetryPipeline.swift +21 -3
- package/ios/Recording/VisualCapture.swift +50 -20
- package/lib/commonjs/index.js +4 -5
- package/lib/commonjs/sdk/constants.js +2 -2
- package/lib/commonjs/sdk/utils.js +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/sdk/constants.js +2 -2
- package/lib/module/sdk/utils.js +1 -1
- package/lib/typescript/sdk/constants.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +1 -6
- package/package.json +2 -2
- package/src/index.ts +9 -10
- package/src/sdk/constants.ts +2 -2
- package/src/sdk/utils.ts +1 -1
- 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(
|
|
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
|
|
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"] =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|