@rejourneyco/react-native 1.0.10 → 1.0.12
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 +13 -6
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +4 -0
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +8 -3
- package/ios/Recording/RejourneyURLProtocol.swift +68 -18
- package/ios/Recording/ReplayOrchestrator.swift +3 -0
- package/ios/Recording/SegmentDispatcher.swift +8 -1
- package/ios/Recording/VisualCapture.swift +9 -2
- package/ios/Rejourney.h +4 -0
- package/ios/Rejourney.mm +3 -15
- package/lib/commonjs/sdk/autoTracking.js +3 -0
- package/lib/commonjs/sdk/metricsTracking.js +3 -0
- package/lib/commonjs/sdk/networkInterceptor.js +7 -7
- package/lib/module/sdk/autoTracking.js +3 -0
- package/lib/module/sdk/metricsTracking.js +3 -0
- package/lib/module/sdk/networkInterceptor.js +7 -7
- package/package.json +1 -1
- package/rejourney.podspec +11 -2
- package/src/sdk/autoTracking.ts +3 -0
- package/src/sdk/metricsTracking.ts +3 -0
- package/src/sdk/networkInterceptor.ts +7 -7
package/README.md
CHANGED
|
@@ -64,22 +64,29 @@ Rejourney.trackScreen('Custom Screen Name');
|
|
|
64
64
|
|
|
65
65
|
## Custom Events & Metadata
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Track user actions and attach session-level context for filtering and segmentation in the dashboard.
|
|
68
68
|
|
|
69
69
|
```typescript
|
|
70
70
|
import { Rejourney } from '@rejourneyco/react-native';
|
|
71
71
|
|
|
72
|
-
// Log custom events
|
|
73
|
-
Rejourney.logEvent('
|
|
72
|
+
// Log custom events with optional properties
|
|
73
|
+
Rejourney.logEvent('signup_completed');
|
|
74
|
+
Rejourney.logEvent('purchase_completed', {
|
|
75
|
+
plan: 'pro',
|
|
76
|
+
amount: 29.99
|
|
77
|
+
});
|
|
74
78
|
|
|
75
|
-
//
|
|
79
|
+
// Attach session-level metadata (key-value context)
|
|
76
80
|
Rejourney.setMetadata('plan', 'premium');
|
|
77
81
|
Rejourney.setMetadata({
|
|
78
|
-
role: '
|
|
79
|
-
|
|
82
|
+
role: 'admin',
|
|
83
|
+
ab_variant: 'checkout_v2'
|
|
80
84
|
});
|
|
81
85
|
```
|
|
82
86
|
|
|
87
|
+
**Events** = things that happened (actions, timestamped, can occur multiple times)
|
|
88
|
+
**Metadata** = who the user is / what state they're in (session-level, one value per key)
|
|
89
|
+
|
|
83
90
|
## API Reference & Compatibility
|
|
84
91
|
|
|
85
92
|
Rejourney supports both a standardized `Rejourney.` namespace and standalone function exports (AKA calls). Both are fully supported.
|
|
@@ -483,6 +483,10 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
483
483
|
|
|
484
484
|
fun logScreenView(screenId: String) {
|
|
485
485
|
if (screenId.isEmpty()) return
|
|
486
|
+
if (visitedScreens.size >= 500) {
|
|
487
|
+
val excess = visitedScreens.size - 250
|
|
488
|
+
repeat(excess) { visitedScreens.removeAt(0) }
|
|
489
|
+
}
|
|
486
490
|
visitedScreens.add(screenId)
|
|
487
491
|
currentScreenName = screenId
|
|
488
492
|
if (hierarchyCaptureEnabled) captureHierarchy()
|
|
@@ -125,15 +125,17 @@ class SegmentDispatcher private constructor() {
|
|
|
125
125
|
private val scope = CoroutineScope(workerExecutor.asCoroutineDispatcher() + SupervisorJob())
|
|
126
126
|
|
|
127
127
|
private val httpClient: OkHttpClient = OkHttpClient.Builder()
|
|
128
|
-
.connectTimeout(5, TimeUnit.SECONDS)
|
|
128
|
+
.connectTimeout(5, TimeUnit.SECONDS)
|
|
129
129
|
.readTimeout(10, TimeUnit.SECONDS)
|
|
130
130
|
.writeTimeout(10, TimeUnit.SECONDS)
|
|
131
|
-
//
|
|
132
|
-
|
|
131
|
+
// Intentionally NO RejourneyNetworkInterceptor here: intercepting our
|
|
132
|
+
// own upload traffic creates redundant network events, wastes bandwidth,
|
|
133
|
+
// and can cause circular upload→intercept→upload chains.
|
|
133
134
|
.build()
|
|
134
135
|
|
|
135
136
|
private val retryQueue = mutableListOf<PendingUpload>()
|
|
136
137
|
private val retryLock = ReentrantLock()
|
|
138
|
+
private val maxRetryQueueSize = 20
|
|
137
139
|
private var active = true
|
|
138
140
|
|
|
139
141
|
fun configure(replayId: String, apiToken: String?, credential: String?, projectId: String?, isSampledIn: Boolean = true) {
|
|
@@ -411,6 +413,9 @@ class SegmentDispatcher private constructor() {
|
|
|
411
413
|
if (upload.attempt < 3) {
|
|
412
414
|
val retry = upload.copy(attempt = upload.attempt + 1)
|
|
413
415
|
retryLock.withLock {
|
|
416
|
+
if (retryQueue.size >= maxRetryQueueSize) {
|
|
417
|
+
retryQueue.removeAt(0)
|
|
418
|
+
}
|
|
414
419
|
retryQueue.add(retry)
|
|
415
420
|
}
|
|
416
421
|
metricsLock.withLock {
|
|
@@ -18,22 +18,26 @@ import Foundation
|
|
|
18
18
|
|
|
19
19
|
/// Intercepts URLSession network traffic globally for Rejourney Session Replay.
|
|
20
20
|
@objc(RejourneyURLProtocol)
|
|
21
|
-
public class RejourneyURLProtocol: URLProtocol
|
|
21
|
+
public class RejourneyURLProtocol: URLProtocol {
|
|
22
22
|
|
|
23
|
-
// We tag requests that we've already handled so we don't intercept them repeatedly.
|
|
24
23
|
private static let _handledKey = "co.rejourney.handled"
|
|
25
24
|
|
|
26
25
|
private var _dataTask: URLSessionDataTask?
|
|
27
26
|
private var _startMs: Int64 = 0
|
|
28
27
|
private var _endMs: Int64 = 0
|
|
29
|
-
private var _responseData: Data?
|
|
30
28
|
private var _response: URLResponse?
|
|
31
29
|
private var _error: Error?
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
/// Shared forwarding session. Uses ephemeral config with protocol classes
|
|
32
|
+
/// stripped to prevent self-interception, and a delegate adapter that routes
|
|
33
|
+
/// callbacks to the correct RejourneyURLProtocol instance via a task map.
|
|
34
|
+
/// This avoids the per-instance URLSession retain cycle that previously
|
|
35
|
+
/// leaked every intercepted request (~1-3MB each).
|
|
36
|
+
private static let _delegateAdapter = SessionDelegateAdapter()
|
|
37
|
+
private static let _sharedSession: URLSession = {
|
|
38
|
+
let cfg = URLSessionConfiguration.ephemeral
|
|
39
|
+
cfg.protocolClasses = []
|
|
40
|
+
return URLSession(configuration: cfg, delegate: _delegateAdapter, delegateQueue: nil)
|
|
37
41
|
}()
|
|
38
42
|
|
|
39
43
|
@objc public static func enable() {
|
|
@@ -130,30 +134,32 @@ public class RejourneyURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessi
|
|
|
130
134
|
URLProtocol.setProperty(true, forKey: RejourneyURLProtocol._handledKey, in: request)
|
|
131
135
|
|
|
132
136
|
_startMs = Int64(Date().timeIntervalSince1970 * 1000)
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
let task = Self._sharedSession.dataTask(with: request as URLRequest)
|
|
138
|
+
Self._delegateAdapter.register(task: task, protocol: self)
|
|
139
|
+
_dataTask = task
|
|
140
|
+
task.resume()
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
public override func stopLoading() {
|
|
138
|
-
_dataTask
|
|
144
|
+
if let task = _dataTask {
|
|
145
|
+
Self._delegateAdapter.unregister(task: task)
|
|
146
|
+
task.cancel()
|
|
147
|
+
}
|
|
139
148
|
_dataTask = nil
|
|
140
149
|
}
|
|
141
150
|
|
|
142
|
-
// MARK: -
|
|
151
|
+
// MARK: - Callbacks from SessionDelegateAdapter
|
|
143
152
|
|
|
144
|
-
|
|
153
|
+
fileprivate func handleDidReceiveData(_ data: Data) {
|
|
145
154
|
client?.urlProtocol(self, didLoad: data)
|
|
146
155
|
}
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
fileprivate func handleDidReceiveResponse(_ response: URLResponse) {
|
|
149
158
|
_response = response
|
|
150
159
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
|
|
151
|
-
completionHandler(.allow)
|
|
152
160
|
}
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
162
|
+
fileprivate func handleDidComplete(error: Error?) {
|
|
157
163
|
_endMs = Int64(Date().timeIntervalSince1970 * 1000)
|
|
158
164
|
_error = error
|
|
159
165
|
|
|
@@ -162,7 +168,9 @@ public class RejourneyURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessi
|
|
|
162
168
|
} else {
|
|
163
169
|
client?.urlProtocolDidFinishLoading(self)
|
|
164
170
|
}
|
|
165
|
-
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fileprivate func handleDidCompleteLogging(task: URLSessionTask) {
|
|
166
174
|
_logRequest(task: task)
|
|
167
175
|
}
|
|
168
176
|
|
|
@@ -214,3 +222,45 @@ public class RejourneyURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessi
|
|
|
214
222
|
TelemetryPipeline.shared.recordNetworkEvent(details: event)
|
|
215
223
|
}
|
|
216
224
|
}
|
|
225
|
+
|
|
226
|
+
/// Routes URLSession delegate callbacks to the correct RejourneyURLProtocol
|
|
227
|
+
/// instance using a task-to-protocol map. Uses weak references to avoid
|
|
228
|
+
/// retaining protocol instances that have been stopped by the URL loading system.
|
|
229
|
+
private final class SessionDelegateAdapter: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
|
|
230
|
+
private let _lock = NSLock()
|
|
231
|
+
private let _taskMap = NSMapTable<URLSessionTask, RejourneyURLProtocol>.strongToWeakObjects()
|
|
232
|
+
|
|
233
|
+
func register(task: URLSessionTask, protocol proto: RejourneyURLProtocol) {
|
|
234
|
+
_lock.lock()
|
|
235
|
+
_taskMap.setObject(proto, forKey: task)
|
|
236
|
+
_lock.unlock()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func unregister(task: URLSessionTask) {
|
|
240
|
+
_lock.lock()
|
|
241
|
+
_taskMap.removeObject(forKey: task)
|
|
242
|
+
_lock.unlock()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private func proto(for task: URLSessionTask) -> RejourneyURLProtocol? {
|
|
246
|
+
_lock.lock()
|
|
247
|
+
defer { _lock.unlock() }
|
|
248
|
+
return _taskMap.object(forKey: task)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
|
252
|
+
proto(for: dataTask)?.handleDidReceiveData(data)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
|
256
|
+
proto(for: dataTask)?.handleDidReceiveResponse(response)
|
|
257
|
+
completionHandler(.allow)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
261
|
+
guard let p = proto(for: task) else { return }
|
|
262
|
+
p.handleDidComplete(error: error)
|
|
263
|
+
p.handleDidCompleteLogging(task: task)
|
|
264
|
+
unregister(task: task)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -429,6 +429,9 @@ public final class ReplayOrchestrator: NSObject {
|
|
|
429
429
|
|
|
430
430
|
@objc public func logScreenView(_ screenId: String) {
|
|
431
431
|
guard !screenId.isEmpty else { return }
|
|
432
|
+
if _visitedScreens.count >= 500 {
|
|
433
|
+
_visitedScreens.removeFirst(_visitedScreens.count - 250)
|
|
434
|
+
}
|
|
432
435
|
_visitedScreens.append(screenId)
|
|
433
436
|
currentScreenName = screenId
|
|
434
437
|
if hierarchyCaptureEnabled { _captureHierarchy() }
|
|
@@ -44,17 +44,21 @@ final class SegmentDispatcher {
|
|
|
44
44
|
}()
|
|
45
45
|
|
|
46
46
|
private let httpSession: URLSession = {
|
|
47
|
-
// Industry standard: Use ephemeral config with explicit connection limits
|
|
48
47
|
let cfg = URLSessionConfiguration.ephemeral
|
|
49
48
|
cfg.httpMaximumConnectionsPerHost = 4
|
|
50
49
|
cfg.waitsForConnectivity = true
|
|
51
50
|
cfg.timeoutIntervalForRequest = 30
|
|
52
51
|
cfg.timeoutIntervalForResource = 60
|
|
52
|
+
// Strip our own protocol to prevent self-interception. Without this,
|
|
53
|
+
// every SDK upload is intercepted by RejourneyURLProtocol which
|
|
54
|
+
// generates redundant network events and wastes resources.
|
|
55
|
+
cfg.protocolClasses = cfg.protocolClasses?.filter { $0 != RejourneyURLProtocol.self } ?? []
|
|
53
56
|
return URLSession(configuration: cfg)
|
|
54
57
|
}()
|
|
55
58
|
|
|
56
59
|
private var retryQueue: [PendingUpload] = []
|
|
57
60
|
private let retryLock = NSLock()
|
|
61
|
+
private let maxRetryQueueSize = 20
|
|
58
62
|
private var active = true
|
|
59
63
|
|
|
60
64
|
private let metricsLock = NSLock()
|
|
@@ -332,6 +336,9 @@ final class SegmentDispatcher {
|
|
|
332
336
|
var retry = upload
|
|
333
337
|
retry.attempt += 1
|
|
334
338
|
retryLock.lock()
|
|
339
|
+
if retryQueue.count >= maxRetryQueueSize {
|
|
340
|
+
retryQueue.removeFirst()
|
|
341
|
+
}
|
|
335
342
|
retryQueue.append(retry)
|
|
336
343
|
retryLock.unlock()
|
|
337
344
|
|
|
@@ -206,9 +206,16 @@ public final class VisualCapture: NSObject {
|
|
|
206
206
|
// Refresh map detection state (very cheap shallow walk)
|
|
207
207
|
SpecialCases.shared.refreshMapState()
|
|
208
208
|
|
|
209
|
-
// Debug-only: confirm capture is running and map state
|
|
210
209
|
if _frameCounter < 5 || _frameCounter % 30 == 0 {
|
|
211
|
-
|
|
210
|
+
var info = mach_task_basic_info()
|
|
211
|
+
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
|
|
212
|
+
let _ = withUnsafeMutablePointer(to: &info) {
|
|
213
|
+
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
|
|
214
|
+
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
let memMB = Double(info.resident_size) / 1_048_576.0
|
|
218
|
+
DiagnosticLog.trace("[VisualCapture] frame#\(_frameCounter) mapVisible=\(SpecialCases.shared.mapVisible) mapIdle=\(SpecialCases.shared.mapIdle) forced=\(forced) residentMB=\(String(format: "%.0f", memMB))")
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
// Map stutter prevention: when a map view is visible and its camera
|
package/ios/Rejourney.h
CHANGED
|
@@ -24,10 +24,14 @@
|
|
|
24
24
|
#import <ReactCommon/RCTTurboModule.h>
|
|
25
25
|
#if __has_include(<RejourneySpec/RejourneySpec.h>)
|
|
26
26
|
#import <RejourneySpec/RejourneySpec.h>
|
|
27
|
+
#define RJ_USE_NEW_ARCH_CODEGEN 1
|
|
27
28
|
#elif __has_include("RejourneySpec.h")
|
|
28
29
|
#import "RejourneySpec.h"
|
|
30
|
+
#define RJ_USE_NEW_ARCH_CODEGEN 1
|
|
31
|
+
#endif
|
|
29
32
|
#endif
|
|
30
33
|
|
|
34
|
+
#if defined(RCT_NEW_ARCH_ENABLED) && defined(RJ_USE_NEW_ARCH_CODEGEN)
|
|
31
35
|
@interface Rejourney : NSObject <NativeRejourneySpec>
|
|
32
36
|
#else
|
|
33
37
|
@interface Rejourney : NSObject <RCTBridgeModule>
|
package/ios/Rejourney.mm
CHANGED
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
#import <ReactCommon/RCTTurboModule.h>
|
|
34
34
|
#if __has_include(<RejourneySpec/RejourneySpec.h>)
|
|
35
35
|
#import <RejourneySpec/RejourneySpec.h>
|
|
36
|
+
#define RJ_USE_NEW_ARCH_CODEGEN 1
|
|
36
37
|
#elif __has_include("RejourneySpec.h")
|
|
37
38
|
#import "RejourneySpec.h"
|
|
39
|
+
#define RJ_USE_NEW_ARCH_CODEGEN 1
|
|
38
40
|
#endif
|
|
39
41
|
#endif
|
|
40
42
|
|
|
@@ -110,7 +112,7 @@ RCT_EXPORT_METHOD(removeListeners : (double)count) {
|
|
|
110
112
|
return _impl;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
#
|
|
115
|
+
#if defined(RCT_NEW_ARCH_ENABLED) && defined(RJ_USE_NEW_ARCH_CODEGEN)
|
|
114
116
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
115
117
|
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
116
118
|
return std::make_shared<facebook::react::NativeRejourneySpecJSI>(params);
|
|
@@ -139,20 +141,6 @@ RCT_EXPORT_METHOD(startSession : (NSString *)userId apiUrl : (NSString *)
|
|
|
139
141
|
reject:reject];
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
RCT_EXPORT_METHOD(startSessionWithOptions : (NSDictionary *)options resolve : (
|
|
143
|
-
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
144
|
-
RejourneyImpl *impl = [self ensureImpl];
|
|
145
|
-
if (!impl) {
|
|
146
|
-
resolve(@{
|
|
147
|
-
@"success" : @NO,
|
|
148
|
-
@"sessionId" : @"",
|
|
149
|
-
@"error" : @"Native module not available"
|
|
150
|
-
});
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
[impl startSessionWithOptions:options resolve:resolve reject:reject];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
144
|
RCT_EXPORT_METHOD(stopSession : (RCTPromiseResolveBlock)
|
|
157
145
|
resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
158
146
|
RejourneyImpl *impl = [self ensureImpl];
|
|
@@ -902,6 +902,9 @@ function trackScreen(screenName) {
|
|
|
902
902
|
}
|
|
903
903
|
const previousScreen = currentScreen;
|
|
904
904
|
currentScreen = screenName;
|
|
905
|
+
if (screensVisited.length >= 500) {
|
|
906
|
+
screensVisited.splice(0, screensVisited.length - 250);
|
|
907
|
+
}
|
|
905
908
|
screensVisited.push(screenName);
|
|
906
909
|
const uniqueScreens = new Set(screensVisited);
|
|
907
910
|
metrics.uniqueScreensCount = uniqueScreens.size;
|
|
@@ -141,6 +141,9 @@ function incrementErrorCount() {
|
|
|
141
141
|
metrics.totalEvents++;
|
|
142
142
|
}
|
|
143
143
|
function addScreenVisited(screenName) {
|
|
144
|
+
if (metrics.screensVisited.length >= 500) {
|
|
145
|
+
metrics.screensVisited.splice(0, metrics.screensVisited.length - 250);
|
|
146
|
+
}
|
|
144
147
|
metrics.screensVisited.push(screenName);
|
|
145
148
|
metrics.uniqueScreensCount = new Set(metrics.screensVisited).size;
|
|
146
149
|
}
|
|
@@ -91,13 +91,10 @@ async function getFetchResponseSize(response) {
|
|
|
91
91
|
const parsed = parseInt(contentLength, 10);
|
|
92
92
|
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} catch {
|
|
99
|
-
return 0;
|
|
100
|
-
}
|
|
94
|
+
|
|
95
|
+
// Don't clone+buffer the full body just to measure size when
|
|
96
|
+
// content-length is missing — this doubles memory for large responses.
|
|
97
|
+
return 0;
|
|
101
98
|
}
|
|
102
99
|
function getXhrResponseSize(xhr) {
|
|
103
100
|
try {
|
|
@@ -347,6 +344,9 @@ function interceptXHR() {
|
|
|
347
344
|
}
|
|
348
345
|
data.t = Date.now();
|
|
349
346
|
const onComplete = () => {
|
|
347
|
+
this.removeEventListener('load', onComplete);
|
|
348
|
+
this.removeEventListener('error', onComplete);
|
|
349
|
+
this.removeEventListener('abort', onComplete);
|
|
350
350
|
const endTime = Date.now();
|
|
351
351
|
const responseBodySize = config.captureSizes ? getXhrResponseSize(this) : 0;
|
|
352
352
|
queueRequest({
|
|
@@ -872,6 +872,9 @@ export function trackScreen(screenName) {
|
|
|
872
872
|
}
|
|
873
873
|
const previousScreen = currentScreen;
|
|
874
874
|
currentScreen = screenName;
|
|
875
|
+
if (screensVisited.length >= 500) {
|
|
876
|
+
screensVisited.splice(0, screensVisited.length - 250);
|
|
877
|
+
}
|
|
875
878
|
screensVisited.push(screenName);
|
|
876
879
|
const uniqueScreens = new Set(screensVisited);
|
|
877
880
|
metrics.uniqueScreensCount = uniqueScreens.size;
|
|
@@ -123,6 +123,9 @@ export function incrementErrorCount() {
|
|
|
123
123
|
metrics.totalEvents++;
|
|
124
124
|
}
|
|
125
125
|
export function addScreenVisited(screenName) {
|
|
126
|
+
if (metrics.screensVisited.length >= 500) {
|
|
127
|
+
metrics.screensVisited.splice(0, metrics.screensVisited.length - 250);
|
|
128
|
+
}
|
|
126
129
|
metrics.screensVisited.push(screenName);
|
|
127
130
|
metrics.uniqueScreensCount = new Set(metrics.screensVisited).size;
|
|
128
131
|
}
|
|
@@ -80,13 +80,10 @@ async function getFetchResponseSize(response) {
|
|
|
80
80
|
const parsed = parseInt(contentLength, 10);
|
|
81
81
|
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} catch {
|
|
88
|
-
return 0;
|
|
89
|
-
}
|
|
83
|
+
|
|
84
|
+
// Don't clone+buffer the full body just to measure size when
|
|
85
|
+
// content-length is missing — this doubles memory for large responses.
|
|
86
|
+
return 0;
|
|
90
87
|
}
|
|
91
88
|
function getXhrResponseSize(xhr) {
|
|
92
89
|
try {
|
|
@@ -336,6 +333,9 @@ function interceptXHR() {
|
|
|
336
333
|
}
|
|
337
334
|
data.t = Date.now();
|
|
338
335
|
const onComplete = () => {
|
|
336
|
+
this.removeEventListener('load', onComplete);
|
|
337
|
+
this.removeEventListener('error', onComplete);
|
|
338
|
+
this.removeEventListener('abort', onComplete);
|
|
339
339
|
const endTime = Date.now();
|
|
340
340
|
const responseBodySize = config.captureSizes ? getXhrResponseSize(this) : 0;
|
|
341
341
|
queueRequest({
|
package/package.json
CHANGED
package/rejourney.podspec
CHANGED
|
@@ -18,6 +18,15 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.exclude_files = "ios/build/**/*"
|
|
19
19
|
s.library = "z"
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
21
|
+
# React Native core dependencies so headers like `React/RCTBridgeModule.h`
|
|
22
|
+
# are always available, regardless of React Native version or architecture.
|
|
23
|
+
# On modern React Native, `React-Core` is the canonical dependency.
|
|
24
|
+
s.dependency "React-Core"
|
|
25
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
26
|
+
|
|
27
|
+
# New Architecture / Codegen integration (RN 0.71+). On older RN versions
|
|
28
|
+
# this helper is not defined, so we guard it.
|
|
29
|
+
if respond_to?(:install_modules_dependencies)
|
|
30
|
+
install_modules_dependencies(s)
|
|
31
|
+
end
|
|
23
32
|
end
|
package/src/sdk/autoTracking.ts
CHANGED
|
@@ -1035,6 +1035,9 @@ export function trackScreen(screenName: string): void {
|
|
|
1035
1035
|
|
|
1036
1036
|
const previousScreen = currentScreen;
|
|
1037
1037
|
currentScreen = screenName;
|
|
1038
|
+
if (screensVisited.length >= 500) {
|
|
1039
|
+
screensVisited.splice(0, screensVisited.length - 250);
|
|
1040
|
+
}
|
|
1038
1041
|
screensVisited.push(screenName);
|
|
1039
1042
|
|
|
1040
1043
|
const uniqueScreens = new Set(screensVisited);
|
|
@@ -153,6 +153,9 @@ export function incrementErrorCount(): void {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
export function addScreenVisited(screenName: string): void {
|
|
156
|
+
if (metrics.screensVisited.length >= 500) {
|
|
157
|
+
metrics.screensVisited.splice(0, metrics.screensVisited.length - 250);
|
|
158
|
+
}
|
|
156
159
|
metrics.screensVisited.push(screenName);
|
|
157
160
|
metrics.uniqueScreensCount = new Set(metrics.screensVisited).size;
|
|
158
161
|
}
|
|
@@ -98,13 +98,9 @@ async function getFetchResponseSize(response: Response): Promise<number> {
|
|
|
98
98
|
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return buffer.byteLength;
|
|
105
|
-
} catch {
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
101
|
+
// Don't clone+buffer the full body just to measure size when
|
|
102
|
+
// content-length is missing — this doubles memory for large responses.
|
|
103
|
+
return 0;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
function getXhrResponseSize(xhr: XMLHttpRequest): number {
|
|
@@ -403,6 +399,10 @@ function interceptXHR(): void {
|
|
|
403
399
|
data.t = Date.now();
|
|
404
400
|
|
|
405
401
|
const onComplete = () => {
|
|
402
|
+
this.removeEventListener('load', onComplete);
|
|
403
|
+
this.removeEventListener('error', onComplete);
|
|
404
|
+
this.removeEventListener('abort', onComplete);
|
|
405
|
+
|
|
406
406
|
const endTime = Date.now();
|
|
407
407
|
|
|
408
408
|
const responseBodySize = config.captureSizes ? getXhrResponseSize(this) : 0;
|