@rejourneyco/react-native 1.0.8 → 1.0.10
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 +77 -3
- package/android/src/main/AndroidManifest.xml +6 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +143 -8
- package/android/src/main/java/com/rejourney/RejourneyOkHttpInitProvider.kt +68 -0
- package/android/src/main/java/com/rejourney/engine/DeviceRegistrar.kt +21 -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 +3 -1
- package/android/src/main/java/com/rejourney/recording/RejourneyNetworkInterceptor.kt +93 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +226 -146
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +7 -0
- package/android/src/main/java/com/rejourney/recording/StabilityMonitor.kt +3 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +39 -0
- package/android/src/main/java/com/rejourney/recording/ViewHierarchyScanner.kt +8 -0
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +95 -21
- package/android/src/main/java/com/rejourney/utility/DataCompression.kt +14 -2
- 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 +204 -115
- package/ios/Recording/AnrSentinel.swift +58 -25
- package/ios/Recording/InteractionRecorder.swift +1 -0
- package/ios/Recording/RejourneyURLProtocol.swift +216 -0
- package/ios/Recording/ReplayOrchestrator.swift +207 -144
- package/ios/Recording/SegmentDispatcher.swift +8 -0
- package/ios/Recording/StabilityMonitor.swift +40 -32
- package/ios/Recording/TelemetryPipeline.swift +45 -2
- package/ios/Recording/ViewHierarchyScanner.swift +1 -0
- package/ios/Recording/VisualCapture.swift +79 -29
- package/ios/Rejourney.mm +27 -8
- package/ios/Utility/DataCompression.swift +2 -2
- package/ios/Utility/ImageBlur.swift +0 -1
- package/lib/commonjs/expoRouterTracking.js +137 -0
- package/lib/commonjs/index.js +204 -34
- package/lib/commonjs/sdk/autoTracking.js +262 -100
- package/lib/commonjs/sdk/networkInterceptor.js +84 -4
- package/lib/module/expoRouterTracking.js +135 -0
- package/lib/module/index.js +203 -28
- package/lib/module/sdk/autoTracking.js +260 -100
- package/lib/module/sdk/networkInterceptor.js +84 -4
- package/lib/typescript/NativeRejourney.d.ts +5 -2
- package/lib/typescript/expoRouterTracking.d.ts +14 -0
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/sdk/autoTracking.d.ts +14 -1
- package/lib/typescript/types/index.d.ts +56 -5
- package/package.json +23 -3
- package/src/NativeRejourney.ts +8 -5
- package/src/expoRouterTracking.ts +167 -0
- package/src/index.ts +221 -35
- package/src/sdk/autoTracking.ts +286 -114
- package/src/sdk/networkInterceptor.ts +110 -1
- package/src/types/index.ts +58 -6
|
@@ -59,6 +59,94 @@ const config = {
|
|
|
59
59
|
|
|
60
60
|
const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_token', 'api_key'];
|
|
61
61
|
|
|
62
|
+
function getUtf8Size(text: string): number {
|
|
63
|
+
if (!text) return 0;
|
|
64
|
+
if (typeof TextEncoder !== 'undefined') {
|
|
65
|
+
return new TextEncoder().encode(text).length;
|
|
66
|
+
}
|
|
67
|
+
return text.length;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getBodySize(body: unknown): number {
|
|
71
|
+
if (body == null) return 0;
|
|
72
|
+
|
|
73
|
+
if (typeof body === 'string') return getUtf8Size(body);
|
|
74
|
+
|
|
75
|
+
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) {
|
|
76
|
+
return body.byteLength;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(body as any)) {
|
|
80
|
+
return (body as ArrayBufferView).byteLength;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob) {
|
|
84
|
+
return body.size;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
|
|
88
|
+
return getUtf8Size(body.toString());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function getFetchResponseSize(response: Response): Promise<number> {
|
|
95
|
+
const contentLength = response.headers?.get?.('content-length');
|
|
96
|
+
if (contentLength) {
|
|
97
|
+
const parsed = parseInt(contentLength, 10);
|
|
98
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const cloned = response.clone();
|
|
103
|
+
const buffer = await cloned.arrayBuffer();
|
|
104
|
+
return buffer.byteLength;
|
|
105
|
+
} catch {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getXhrResponseSize(xhr: XMLHttpRequest): number {
|
|
111
|
+
try {
|
|
112
|
+
const contentLength = xhr.getResponseHeader('content-length');
|
|
113
|
+
if (contentLength) {
|
|
114
|
+
const parsed = parseInt(contentLength, 10);
|
|
115
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Ignore header access errors and fall through to body inspection.
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const responseType = xhr.responseType;
|
|
122
|
+
|
|
123
|
+
if (responseType === '' || responseType === 'text') {
|
|
124
|
+
return getUtf8Size(xhr.responseText || '');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (responseType === 'arraybuffer') {
|
|
128
|
+
return typeof ArrayBuffer !== 'undefined' && xhr.response instanceof ArrayBuffer
|
|
129
|
+
? xhr.response.byteLength
|
|
130
|
+
: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (responseType === 'blob') {
|
|
134
|
+
return typeof Blob !== 'undefined' && xhr.response instanceof Blob
|
|
135
|
+
? xhr.response.size
|
|
136
|
+
: 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (responseType === 'json') {
|
|
140
|
+
try {
|
|
141
|
+
return getUtf8Size(JSON.stringify(xhr.response ?? ''));
|
|
142
|
+
} catch {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
62
150
|
/**
|
|
63
151
|
* Scrub sensitive data from URL
|
|
64
152
|
*/
|
|
@@ -225,8 +313,15 @@ function interceptFetch(): void {
|
|
|
225
313
|
|
|
226
314
|
const startTime = Date.now();
|
|
227
315
|
const method = ((init?.method || 'GET').toUpperCase()) as NetworkRequestParams['method'];
|
|
316
|
+
|
|
317
|
+
const requestBodySize = config.captureSizes ? getBodySize(init?.body) : 0;
|
|
318
|
+
|
|
228
319
|
return originalFetch!(input, init).then(
|
|
229
|
-
(response) => {
|
|
320
|
+
async (response) => {
|
|
321
|
+
const responseBodySize = config.captureSizes
|
|
322
|
+
? await getFetchResponseSize(response)
|
|
323
|
+
: 0;
|
|
324
|
+
|
|
230
325
|
queueRequest({
|
|
231
326
|
requestId: `f${startTime}`,
|
|
232
327
|
method,
|
|
@@ -236,6 +331,8 @@ function interceptFetch(): void {
|
|
|
236
331
|
startTimestamp: startTime,
|
|
237
332
|
endTimestamp: Date.now(),
|
|
238
333
|
success: response.ok,
|
|
334
|
+
requestBodySize,
|
|
335
|
+
responseBodySize,
|
|
239
336
|
});
|
|
240
337
|
return response;
|
|
241
338
|
},
|
|
@@ -250,6 +347,7 @@ function interceptFetch(): void {
|
|
|
250
347
|
endTimestamp: Date.now(),
|
|
251
348
|
success: false,
|
|
252
349
|
errorMessage: error?.message || 'Network error',
|
|
350
|
+
requestBodySize,
|
|
253
351
|
});
|
|
254
352
|
throw error;
|
|
255
353
|
}
|
|
@@ -296,10 +394,19 @@ function interceptXHR(): void {
|
|
|
296
394
|
return originalXHRSend!.call(this, body);
|
|
297
395
|
}
|
|
298
396
|
|
|
397
|
+
if (config.captureSizes && body) {
|
|
398
|
+
data.reqSize = getBodySize(body);
|
|
399
|
+
} else {
|
|
400
|
+
data.reqSize = 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
299
403
|
data.t = Date.now();
|
|
300
404
|
|
|
301
405
|
const onComplete = () => {
|
|
302
406
|
const endTime = Date.now();
|
|
407
|
+
|
|
408
|
+
const responseBodySize = config.captureSizes ? getXhrResponseSize(this) : 0;
|
|
409
|
+
|
|
303
410
|
queueRequest({
|
|
304
411
|
requestId: `x${data.t}`,
|
|
305
412
|
method: data.m as NetworkRequestParams['method'],
|
|
@@ -310,6 +417,8 @@ function interceptXHR(): void {
|
|
|
310
417
|
endTimestamp: endTime,
|
|
311
418
|
success: this.status >= 200 && this.status < 400,
|
|
312
419
|
errorMessage: this.status === 0 ? 'Network error' : undefined,
|
|
420
|
+
requestBodySize: data.reqSize,
|
|
421
|
+
responseBodySize,
|
|
313
422
|
});
|
|
314
423
|
};
|
|
315
424
|
|
package/src/types/index.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface RejourneyConfig {
|
|
|
31
31
|
maxStorageSize?: number;
|
|
32
32
|
/** Enable automatic screen name detection with React Navigation (default: true) */
|
|
33
33
|
autoScreenTracking?: boolean;
|
|
34
|
+
/** Enable automatic screen name detection with Expo Router (default: true) */
|
|
35
|
+
autoTrackExpoRouter?: boolean;
|
|
34
36
|
/** Enable automatic gesture detection (default: true) */
|
|
35
37
|
autoGestureTracking?: boolean;
|
|
36
38
|
/** Enable privacy occlusion for text inputs (default: true) */
|
|
@@ -102,8 +104,12 @@ export interface RejourneyConfig {
|
|
|
102
104
|
* Disable if you want minimal network tracking overhead.
|
|
103
105
|
*/
|
|
104
106
|
networkCaptureSizes?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Automatically intercept console.log/info/warn/error and include them in session recordings.
|
|
109
|
+
* Useful for debugging sessions. Capped at 1,000 logs per session. (default: true)
|
|
110
|
+
*/
|
|
111
|
+
trackConsoleLogs?: boolean;
|
|
105
112
|
}
|
|
106
|
-
|
|
107
113
|
export type GestureType =
|
|
108
114
|
| 'tap'
|
|
109
115
|
| 'double_tap'
|
|
@@ -517,7 +523,17 @@ export interface RejourneyNativeModule {
|
|
|
517
523
|
export interface RejourneyAPI {
|
|
518
524
|
/** SDK version */
|
|
519
525
|
readonly version: string;
|
|
520
|
-
/**
|
|
526
|
+
/**
|
|
527
|
+
* Initialize Rejourney SDK
|
|
528
|
+
* @param publicRouteKey - Your public route key from the Rejourney dashboard
|
|
529
|
+
* @param options - Optional configuration options
|
|
530
|
+
*/
|
|
531
|
+
init(publicRouteKey: string, options?: Omit<RejourneyConfig, 'publicRouteKey'>): void;
|
|
532
|
+
/** Start recording (call after user consent) */
|
|
533
|
+
start(): void;
|
|
534
|
+
/** Stop recording */
|
|
535
|
+
stop(): void;
|
|
536
|
+
/** Internal method to start recording session (called by start() / startRejourney()) */
|
|
521
537
|
_startSession(): Promise<boolean>;
|
|
522
538
|
/** Internal method to stop recording session (called by stopRejourney) */
|
|
523
539
|
_stopSession(): Promise<void>;
|
|
@@ -527,8 +543,18 @@ export interface RejourneyAPI {
|
|
|
527
543
|
setUserIdentity(userId: string): void;
|
|
528
544
|
/** Clear user identity */
|
|
529
545
|
clearUserIdentity(): void;
|
|
530
|
-
/**
|
|
531
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Set custom session metadata.
|
|
548
|
+
* Can be called with a single key-value pair or an object of properties.
|
|
549
|
+
* Useful for filtering sessions later (e.g., plan: 'premium', role: 'admin').
|
|
550
|
+
* Caps at 100 properties per session.
|
|
551
|
+
*
|
|
552
|
+
* @param keyOrProperties Property name string, or an object containing key-value pairs
|
|
553
|
+
* @param value Property value (if first argument is a string)
|
|
554
|
+
*/
|
|
555
|
+
setMetadata(keyOrProperties: string | Record<string, string | number | boolean>, value?: string | number | boolean): void;
|
|
556
|
+
/** Track current screen (manual) */
|
|
557
|
+
trackScreen(screenName: string, params?: Record<string, unknown>): void;
|
|
532
558
|
/** Mark a view as sensitive (will be occluded in recording) */
|
|
533
559
|
setOccluded(viewRef: { current: any }, occluded?: boolean): void;
|
|
534
560
|
/** Add a tag to current session */
|
|
@@ -577,16 +603,24 @@ export interface RejourneyAPI {
|
|
|
577
603
|
/** Get storage usage */
|
|
578
604
|
getStorageUsage(): Promise<{ used: number; max: number }>;
|
|
579
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Log customer feedback (e.g. from an in-app survey or NPS widget).
|
|
608
|
+
*
|
|
609
|
+
* @param rating - Numeric rating (e.g. 1 to 5)
|
|
610
|
+
* @param message - Associated feedback text or comment
|
|
611
|
+
*/
|
|
612
|
+
logFeedback(rating: number, message: string): void;
|
|
613
|
+
|
|
580
614
|
/**
|
|
581
615
|
* Get SDK telemetry metrics for observability
|
|
616
|
+
|
|
582
617
|
* Returns metrics about SDK health including upload success rates,
|
|
583
618
|
* retry attempts, circuit breaker events, and memory pressure.
|
|
584
619
|
*/
|
|
585
620
|
getSDKMetrics(): Promise<SDKMetrics>;
|
|
586
621
|
|
|
587
622
|
/**
|
|
588
|
-
* Trigger
|
|
589
|
-
* Blocks the main thread for the specified duration
|
|
623
|
+
* Trigger an ANR test by blocking the main thread for the specified duration.
|
|
590
624
|
*/
|
|
591
625
|
debugTriggerANR(durationMs: number): void;
|
|
592
626
|
|
|
@@ -616,6 +650,22 @@ export interface RejourneyAPI {
|
|
|
616
650
|
* @param nativeID - The nativeID prop of the view to unmask
|
|
617
651
|
*/
|
|
618
652
|
unmaskView(nativeID: string): void;
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Hook for automatic React Navigation tracking.
|
|
656
|
+
* Pass the returned object to your NavigationContainer props.
|
|
657
|
+
*
|
|
658
|
+
* @example
|
|
659
|
+
* ```tsx
|
|
660
|
+
* const navigationTracking = Rejourney.useNavigationTracking();
|
|
661
|
+
* <NavigationContainer {...navigationTracking}>
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
useNavigationTracking(): {
|
|
665
|
+
ref: any;
|
|
666
|
+
onReady: () => void;
|
|
667
|
+
onStateChange: (state: any) => void;
|
|
668
|
+
};
|
|
619
669
|
}
|
|
620
670
|
|
|
621
671
|
/**
|
|
@@ -686,6 +736,8 @@ export interface UseRejourneyResult {
|
|
|
686
736
|
stopRecording: () => Promise<void>;
|
|
687
737
|
/** Log custom event */
|
|
688
738
|
logEvent: (name: string, properties?: Record<string, unknown>) => void;
|
|
739
|
+
/** Set custom session metadata */
|
|
740
|
+
setMetadata: (keyOrProperties: string | Record<string, string | number | boolean>, value?: string | number | boolean) => void;
|
|
689
741
|
/** Error if any */
|
|
690
742
|
error: Error | null;
|
|
691
743
|
}
|