@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.
- package/README.md +1 -1
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +109 -26
- package/android/src/main/java/com/rejourney/engine/DeviceRegistrar.kt +18 -3
- package/android/src/main/java/com/rejourney/engine/RejourneyImpl.kt +69 -17
- package/android/src/main/java/com/rejourney/recording/AnrSentinel.kt +27 -2
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +30 -0
- package/android/src/main/java/com/rejourney/recording/RejourneyNetworkInterceptor.kt +100 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +260 -174
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +246 -34
- package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
- package/android/src/main/java/com/rejourney/recording/StabilityMonitor.kt +3 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +19 -4
- package/android/src/main/java/com/rejourney/recording/ViewHierarchyScanner.kt +8 -0
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +251 -85
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +18 -0
- package/ios/Engine/DeviceRegistrar.swift +13 -3
- package/ios/Engine/RejourneyImpl.swift +202 -133
- package/ios/Recording/AnrSentinel.swift +58 -25
- package/ios/Recording/InteractionRecorder.swift +29 -0
- package/ios/Recording/RejourneyURLProtocol.swift +168 -0
- package/ios/Recording/ReplayOrchestrator.swift +241 -147
- package/ios/Recording/SegmentDispatcher.swift +155 -13
- package/ios/Recording/SpecialCases.swift +614 -0
- package/ios/Recording/StabilityMonitor.swift +42 -34
- package/ios/Recording/TelemetryPipeline.swift +38 -3
- package/ios/Recording/ViewHierarchyScanner.swift +1 -0
- package/ios/Recording/VisualCapture.swift +104 -28
- package/ios/Rejourney.mm +27 -8
- package/ios/Utility/ImageBlur.swift +0 -1
- package/lib/commonjs/index.js +32 -20
- package/lib/commonjs/sdk/autoTracking.js +162 -11
- package/lib/commonjs/sdk/constants.js +2 -2
- package/lib/commonjs/sdk/networkInterceptor.js +84 -4
- package/lib/commonjs/sdk/utils.js +1 -1
- package/lib/module/index.js +32 -20
- package/lib/module/sdk/autoTracking.js +162 -11
- package/lib/module/sdk/constants.js +2 -2
- package/lib/module/sdk/networkInterceptor.js +84 -4
- package/lib/module/sdk/utils.js +1 -1
- package/lib/typescript/NativeRejourney.d.ts +5 -2
- package/lib/typescript/sdk/autoTracking.d.ts +3 -1
- package/lib/typescript/sdk/constants.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +15 -8
- package/package.json +4 -4
- package/src/NativeRejourney.ts +8 -5
- package/src/index.ts +46 -29
- package/src/sdk/autoTracking.ts +176 -11
- package/src/sdk/constants.ts +2 -2
- package/src/sdk/networkInterceptor.ts +110 -1
- package/src/sdk/utils.ts +1 -1
- 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(
|
|
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
|
|
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"] =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|