@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
|
@@ -106,8 +106,7 @@ export interface Spec extends TurboModule {
|
|
|
106
106
|
*/
|
|
107
107
|
debugCrash(): void;
|
|
108
108
|
/**
|
|
109
|
-
* Trigger
|
|
110
|
-
* Blocks the main thread for the specified duration
|
|
109
|
+
* Trigger an ANR test by blocking the main thread for the specified duration.
|
|
111
110
|
*/
|
|
112
111
|
debugTriggerANR(durationMs: number): void;
|
|
113
112
|
/**
|
|
@@ -130,6 +129,10 @@ export interface Spec extends TurboModule {
|
|
|
130
129
|
success: boolean;
|
|
131
130
|
}>;
|
|
132
131
|
getUserIdentity(): Promise<string | null>;
|
|
132
|
+
setAnonymousId(anonymousId: string): Promise<{
|
|
133
|
+
success: boolean;
|
|
134
|
+
}>;
|
|
135
|
+
getAnonymousId(): Promise<string | null>;
|
|
133
136
|
setDebugMode(enabled: boolean): Promise<{
|
|
134
137
|
success: boolean;
|
|
135
138
|
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Expo Router integration for @rejourneyco/react-native
|
|
3
|
+
*
|
|
4
|
+
* This file is only loaded when you import '@rejourneyco/react-native/expo-router'.
|
|
5
|
+
* It contains require('expo-router') and related subpaths. Metro bundles require()
|
|
6
|
+
* at build time, so keeping this in a separate entry ensures apps that use
|
|
7
|
+
* Expo with react-navigation (without expo-router) never pull in expo-router
|
|
8
|
+
* and avoid "Requiring unknown module" crashes.
|
|
9
|
+
*
|
|
10
|
+
* If you use expo-router, add this once (e.g. in your root _layout.tsx):
|
|
11
|
+
* import '@rejourneyco/react-native/expo-router';
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=expoRouterTracking.d.ts.map
|
|
@@ -26,7 +26,7 @@ import type { RejourneyConfig, RejourneyAPI } from './types';
|
|
|
26
26
|
/**
|
|
27
27
|
* Main Rejourney API (Internal)
|
|
28
28
|
*/
|
|
29
|
-
declare const Rejourney: RejourneyAPI;
|
|
29
|
+
export declare const Rejourney: RejourneyAPI;
|
|
30
30
|
/**
|
|
31
31
|
* Initialize Rejourney SDK - STEP 1 of 3
|
|
32
32
|
*
|
|
@@ -81,7 +81,7 @@ export declare function startRejourney(): void;
|
|
|
81
81
|
export declare function stopRejourney(): void;
|
|
82
82
|
export default Rejourney;
|
|
83
83
|
export * from './types';
|
|
84
|
-
export { trackTap, trackScroll, trackGesture, trackInput,
|
|
84
|
+
export { trackTap, trackScroll, trackGesture, trackInput, captureError, getSessionMetrics, } from './sdk/autoTracking';
|
|
85
85
|
export { trackNavigationState, useNavigationTracking } from './sdk/autoTracking';
|
|
86
86
|
export { LogLevel } from './sdk/utils';
|
|
87
87
|
/**
|
|
@@ -64,9 +64,11 @@ export interface AutoTrackingConfig {
|
|
|
64
64
|
trackJSErrors?: boolean;
|
|
65
65
|
trackPromiseRejections?: boolean;
|
|
66
66
|
trackReactNativeErrors?: boolean;
|
|
67
|
+
trackConsoleLogs?: boolean;
|
|
67
68
|
collectDeviceInfo?: boolean;
|
|
68
69
|
maxSessionDurationMs?: number;
|
|
69
70
|
detectDeadTaps?: boolean;
|
|
71
|
+
autoTrackExpoRouter?: boolean;
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
74
|
* Mark a tap as handled.
|
|
@@ -100,6 +102,16 @@ export declare function notifyStateChange(): void;
|
|
|
100
102
|
* Manually track an error (for API errors, etc.)
|
|
101
103
|
*/
|
|
102
104
|
export declare function captureError(message: string, stack?: string, name?: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* Register the polling interval from the optional expo-router entry so we can clear it on cleanup.
|
|
107
|
+
* Used by src/expoRouterTracking.ts (only loaded when app imports '@rejourneyco/react-native/expo-router').
|
|
108
|
+
*/
|
|
109
|
+
export declare function setExpoRouterPollingInterval(id: ReturnType<typeof setInterval> | null): void;
|
|
110
|
+
/**
|
|
111
|
+
* Check if Expo Router auto-tracking is enabled in the current configuration.
|
|
112
|
+
* Used by src/expoRouterTracking.ts.
|
|
113
|
+
*/
|
|
114
|
+
export declare function isExpoRouterTrackingEnabled(): boolean;
|
|
103
115
|
/**
|
|
104
116
|
* Track a navigation state change from React Navigation.
|
|
105
117
|
*
|
|
@@ -198,7 +210,8 @@ export declare function getAnonymousId(): string;
|
|
|
198
210
|
export declare function ensurePersistentAnonymousId(): Promise<string>;
|
|
199
211
|
/**
|
|
200
212
|
* Load anonymous ID from persistent storage
|
|
201
|
-
*
|
|
213
|
+
* Checks native anonymous storage first, then falls back to native getUserIdentity,
|
|
214
|
+
* and finally generates a new ID if nothing is persisted.
|
|
202
215
|
*/
|
|
203
216
|
export declare function loadAnonymousId(): Promise<string>;
|
|
204
217
|
/**
|
|
@@ -25,6 +25,8 @@ export interface RejourneyConfig {
|
|
|
25
25
|
maxStorageSize?: number;
|
|
26
26
|
/** Enable automatic screen name detection with React Navigation (default: true) */
|
|
27
27
|
autoScreenTracking?: boolean;
|
|
28
|
+
/** Enable automatic screen name detection with Expo Router (default: true) */
|
|
29
|
+
autoTrackExpoRouter?: boolean;
|
|
28
30
|
/** Enable automatic gesture detection (default: true) */
|
|
29
31
|
autoGestureTracking?: boolean;
|
|
30
32
|
/** Enable privacy occlusion for text inputs (default: true) */
|
|
@@ -90,6 +92,11 @@ export interface RejourneyConfig {
|
|
|
90
92
|
* Disable if you want minimal network tracking overhead.
|
|
91
93
|
*/
|
|
92
94
|
networkCaptureSizes?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Automatically intercept console.log/info/warn/error and include them in session recordings.
|
|
97
|
+
* Useful for debugging sessions. Capped at 1,000 logs per session. (default: true)
|
|
98
|
+
*/
|
|
99
|
+
trackConsoleLogs?: boolean;
|
|
93
100
|
}
|
|
94
101
|
export type GestureType = 'tap' | 'double_tap' | 'long_press' | 'force_touch' | 'swipe_left' | 'swipe_right' | 'swipe_up' | 'swipe_down' | 'pinch' | 'pinch_in' | 'pinch_out' | 'pan_up' | 'pan_down' | 'pan_left' | 'pan_right' | 'rotate_cw' | 'rotate_ccw' | 'scroll' | 'scroll_up' | 'scroll_down' | 'two_finger_tap' | 'three_finger_gesture' | 'multi_touch' | 'keyboard_tap' | 'rage_tap';
|
|
95
102
|
export type EventType = 'gesture' | 'screen_change' | 'custom' | 'app_state' | 'app_lifecycle' | 'keyboard_show' | 'keyboard_hide' | 'keyboard_typing' | 'oauth_started' | 'oauth_completed' | 'oauth_returned' | 'external_url_opened' | 'session_start' | 'session_timeout' | 'frustration' | 'error';
|
|
@@ -428,7 +435,17 @@ export interface RejourneyNativeModule {
|
|
|
428
435
|
export interface RejourneyAPI {
|
|
429
436
|
/** SDK version */
|
|
430
437
|
readonly version: string;
|
|
431
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* Initialize Rejourney SDK
|
|
440
|
+
* @param publicRouteKey - Your public route key from the Rejourney dashboard
|
|
441
|
+
* @param options - Optional configuration options
|
|
442
|
+
*/
|
|
443
|
+
init(publicRouteKey: string, options?: Omit<RejourneyConfig, 'publicRouteKey'>): void;
|
|
444
|
+
/** Start recording (call after user consent) */
|
|
445
|
+
start(): void;
|
|
446
|
+
/** Stop recording */
|
|
447
|
+
stop(): void;
|
|
448
|
+
/** Internal method to start recording session (called by start() / startRejourney()) */
|
|
432
449
|
_startSession(): Promise<boolean>;
|
|
433
450
|
/** Internal method to stop recording session (called by stopRejourney) */
|
|
434
451
|
_stopSession(): Promise<void>;
|
|
@@ -438,8 +455,18 @@ export interface RejourneyAPI {
|
|
|
438
455
|
setUserIdentity(userId: string): void;
|
|
439
456
|
/** Clear user identity */
|
|
440
457
|
clearUserIdentity(): void;
|
|
441
|
-
/**
|
|
442
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Set custom session metadata.
|
|
460
|
+
* Can be called with a single key-value pair or an object of properties.
|
|
461
|
+
* Useful for filtering sessions later (e.g., plan: 'premium', role: 'admin').
|
|
462
|
+
* Caps at 100 properties per session.
|
|
463
|
+
*
|
|
464
|
+
* @param keyOrProperties Property name string, or an object containing key-value pairs
|
|
465
|
+
* @param value Property value (if first argument is a string)
|
|
466
|
+
*/
|
|
467
|
+
setMetadata(keyOrProperties: string | Record<string, string | number | boolean>, value?: string | number | boolean): void;
|
|
468
|
+
/** Track current screen (manual) */
|
|
469
|
+
trackScreen(screenName: string, params?: Record<string, unknown>): void;
|
|
443
470
|
/** Mark a view as sensitive (will be occluded in recording) */
|
|
444
471
|
setOccluded(viewRef: {
|
|
445
472
|
current: any;
|
|
@@ -492,15 +519,22 @@ export interface RejourneyAPI {
|
|
|
492
519
|
used: number;
|
|
493
520
|
max: number;
|
|
494
521
|
}>;
|
|
522
|
+
/**
|
|
523
|
+
* Log customer feedback (e.g. from an in-app survey or NPS widget).
|
|
524
|
+
*
|
|
525
|
+
* @param rating - Numeric rating (e.g. 1 to 5)
|
|
526
|
+
* @param message - Associated feedback text or comment
|
|
527
|
+
*/
|
|
528
|
+
logFeedback(rating: number, message: string): void;
|
|
495
529
|
/**
|
|
496
530
|
* Get SDK telemetry metrics for observability
|
|
531
|
+
|
|
497
532
|
* Returns metrics about SDK health including upload success rates,
|
|
498
533
|
* retry attempts, circuit breaker events, and memory pressure.
|
|
499
534
|
*/
|
|
500
535
|
getSDKMetrics(): Promise<SDKMetrics>;
|
|
501
536
|
/**
|
|
502
|
-
* Trigger
|
|
503
|
-
* Blocks the main thread for the specified duration
|
|
537
|
+
* Trigger an ANR test by blocking the main thread for the specified duration.
|
|
504
538
|
*/
|
|
505
539
|
debugTriggerANR(durationMs: number): void;
|
|
506
540
|
/**
|
|
@@ -528,6 +562,21 @@ export interface RejourneyAPI {
|
|
|
528
562
|
* @param nativeID - The nativeID prop of the view to unmask
|
|
529
563
|
*/
|
|
530
564
|
unmaskView(nativeID: string): void;
|
|
565
|
+
/**
|
|
566
|
+
* Hook for automatic React Navigation tracking.
|
|
567
|
+
* Pass the returned object to your NavigationContainer props.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```tsx
|
|
571
|
+
* const navigationTracking = Rejourney.useNavigationTracking();
|
|
572
|
+
* <NavigationContainer {...navigationTracking}>
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
useNavigationTracking(): {
|
|
576
|
+
ref: any;
|
|
577
|
+
onReady: () => void;
|
|
578
|
+
onStateChange: (state: any) => void;
|
|
579
|
+
};
|
|
531
580
|
}
|
|
532
581
|
/**
|
|
533
582
|
* SDK telemetry metrics for observability
|
|
@@ -595,6 +644,8 @@ export interface UseRejourneyResult {
|
|
|
595
644
|
stopRecording: () => Promise<void>;
|
|
596
645
|
/** Log custom event */
|
|
597
646
|
logEvent: (name: string, properties?: Record<string, unknown>) => void;
|
|
647
|
+
/** Set custom session metadata */
|
|
648
|
+
setMetadata: (keyOrProperties: string | Record<string, string | number | boolean>, value?: string | number | boolean) => void;
|
|
598
649
|
/** Error if any */
|
|
599
650
|
error: Error | null;
|
|
600
651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rejourneyco/react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Rejourney Session Recording SDK for React Native",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@types/react-native": "*",
|
|
76
76
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
77
77
|
"@typescript-eslint/parser": "^8.15.0",
|
|
78
|
-
"@vitest/coverage-v8": "^
|
|
78
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
79
79
|
"dependency-cruiser": "^16.10.4",
|
|
80
80
|
"@react-navigation/native": "*",
|
|
81
81
|
"expo-router": "*",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-native": "*",
|
|
85
85
|
"react-native-builder-bob": "^0.23.0",
|
|
86
86
|
"typescript": "^5.0.0",
|
|
87
|
-
"vitest": "^
|
|
87
|
+
"vitest": "^4.0.18"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
90
|
"react": "*",
|
|
@@ -92,6 +92,26 @@
|
|
|
92
92
|
"@react-navigation/native": ">=6.0.0",
|
|
93
93
|
"expo-router": ">=3.0.0"
|
|
94
94
|
},
|
|
95
|
+
"peerDependenciesMeta": {
|
|
96
|
+
"expo-router": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"exports": {
|
|
101
|
+
".": {
|
|
102
|
+
"types": "./lib/typescript/index.d.ts",
|
|
103
|
+
"react-native": "./lib/module/index.js",
|
|
104
|
+
"import": "./lib/module/index.js",
|
|
105
|
+
"require": "./lib/commonjs/index.js"
|
|
106
|
+
},
|
|
107
|
+
"./expo-router": {
|
|
108
|
+
"types": "./lib/typescript/expoRouterTracking.d.ts",
|
|
109
|
+
"react-native": "./lib/module/expoRouterTracking.js",
|
|
110
|
+
"import": "./lib/module/expoRouterTracking.js",
|
|
111
|
+
"require": "./lib/commonjs/expoRouterTracking.js",
|
|
112
|
+
"default": "./lib/module/expoRouterTracking.js"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
95
115
|
"codegenConfig": {
|
|
96
116
|
"name": "RejourneySpec",
|
|
97
117
|
"type": "modules",
|
package/src/NativeRejourney.ts
CHANGED
|
@@ -128,8 +128,7 @@ export interface Spec extends TurboModule {
|
|
|
128
128
|
debugCrash(): void;
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Trigger
|
|
132
|
-
* Blocks the main thread for the specified duration
|
|
131
|
+
* Trigger an ANR test by blocking the main thread for the specified duration.
|
|
133
132
|
*/
|
|
134
133
|
debugTriggerANR(durationMs: number): void;
|
|
135
134
|
|
|
@@ -152,11 +151,15 @@ export interface Spec extends TurboModule {
|
|
|
152
151
|
|
|
153
152
|
getUserIdentity(): Promise<string | null>;
|
|
154
153
|
|
|
154
|
+
setAnonymousId(anonymousId: string): Promise<{ success: boolean }>;
|
|
155
|
+
|
|
156
|
+
getAnonymousId(): Promise<string | null>;
|
|
157
|
+
|
|
155
158
|
setDebugMode(enabled: boolean): Promise<{ success: boolean }>;
|
|
156
159
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Set SDK version from JS (called during init with version from package.json)
|
|
162
|
+
*/
|
|
160
163
|
setSDKVersion(version: string): void;
|
|
161
164
|
|
|
162
165
|
/**
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Expo Router integration for @rejourneyco/react-native
|
|
3
|
+
*
|
|
4
|
+
* This file is only loaded when you import '@rejourneyco/react-native/expo-router'.
|
|
5
|
+
* It contains require('expo-router') and related subpaths. Metro bundles require()
|
|
6
|
+
* at build time, so keeping this in a separate entry ensures apps that use
|
|
7
|
+
* Expo with react-navigation (without expo-router) never pull in expo-router
|
|
8
|
+
* and avoid "Requiring unknown module" crashes.
|
|
9
|
+
*
|
|
10
|
+
* If you use expo-router, add this once (e.g. in your root _layout.tsx):
|
|
11
|
+
* import '@rejourneyco/react-native/expo-router';
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { trackScreen, setExpoRouterPollingInterval, isExpoRouterTrackingEnabled } from './sdk/autoTracking';
|
|
15
|
+
import { normalizeScreenName, getScreenNameFromPath } from './sdk/navigation';
|
|
16
|
+
|
|
17
|
+
const MAX_POLLING_ERRORS = 10;
|
|
18
|
+
|
|
19
|
+
function extractScreenNameFromRouterState(
|
|
20
|
+
state: any,
|
|
21
|
+
getScreenNameFromPathFn: (path: string, segments: string[]) => string,
|
|
22
|
+
normalizeScreenNameFn: (name: string) => string,
|
|
23
|
+
accumulatedSegments: string[] = []
|
|
24
|
+
): string | null {
|
|
25
|
+
if (!state?.routes) return null;
|
|
26
|
+
|
|
27
|
+
const route = state.routes[state.index ?? state.routes.length - 1];
|
|
28
|
+
if (!route) return null;
|
|
29
|
+
|
|
30
|
+
const newSegments = [...accumulatedSegments, route.name];
|
|
31
|
+
|
|
32
|
+
if (route.state) {
|
|
33
|
+
return extractScreenNameFromRouterState(
|
|
34
|
+
route.state,
|
|
35
|
+
getScreenNameFromPathFn,
|
|
36
|
+
normalizeScreenNameFn,
|
|
37
|
+
newSegments
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cleanSegments = newSegments.filter((s) => !s.startsWith('(') && !s.endsWith(')'));
|
|
42
|
+
|
|
43
|
+
if (cleanSegments.length === 0) {
|
|
44
|
+
for (let i = newSegments.length - 1; i >= 0; i--) {
|
|
45
|
+
const seg = newSegments[i];
|
|
46
|
+
if (seg && !seg.startsWith('(') && !seg.endsWith(')')) {
|
|
47
|
+
cleanSegments.push(seg);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const pathname = '/' + cleanSegments.join('/');
|
|
54
|
+
return getScreenNameFromPathFn(pathname, newSegments);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setupExpoRouterPolling(): void {
|
|
58
|
+
let lastDetectedScreen = '';
|
|
59
|
+
let pollingErrors = 0;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const EXPO_ROUTER = 'expo-router';
|
|
63
|
+
const expoRouter = require(EXPO_ROUTER);
|
|
64
|
+
const router = expoRouter.router;
|
|
65
|
+
|
|
66
|
+
if (!router) {
|
|
67
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
68
|
+
console.debug('[Rejourney] Expo Router: router object not found');
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const intervalId = setInterval(() => {
|
|
74
|
+
try {
|
|
75
|
+
let state: any = null;
|
|
76
|
+
if (typeof router.getState === 'function') {
|
|
77
|
+
state = router.getState();
|
|
78
|
+
} else if ((router as any).rootState) {
|
|
79
|
+
state = (router as any).rootState;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!state) {
|
|
83
|
+
try {
|
|
84
|
+
const STORE_PATH = 'expo-router/build/global-state/router-store';
|
|
85
|
+
const storeModule = require(STORE_PATH);
|
|
86
|
+
if (storeModule?.store) {
|
|
87
|
+
state = storeModule.store.state;
|
|
88
|
+
if (!state && storeModule.store.navigationRef?.current) {
|
|
89
|
+
state = storeModule.store.navigationRef.current.getRootState?.();
|
|
90
|
+
}
|
|
91
|
+
if (!state) {
|
|
92
|
+
state = storeModule.store.rootState || storeModule.store.initialState;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// Ignore
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!state) {
|
|
101
|
+
try {
|
|
102
|
+
const IMPERATIVE_PATH = 'expo-router/build/imperative-api';
|
|
103
|
+
const imperative = require(IMPERATIVE_PATH);
|
|
104
|
+
if (imperative?.router) {
|
|
105
|
+
state = imperative.router.getState?.();
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// Ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (state) {
|
|
113
|
+
pollingErrors = 0;
|
|
114
|
+
const screenName = extractScreenNameFromRouterState(
|
|
115
|
+
state,
|
|
116
|
+
getScreenNameFromPath,
|
|
117
|
+
normalizeScreenName
|
|
118
|
+
);
|
|
119
|
+
if (screenName && screenName !== lastDetectedScreen) {
|
|
120
|
+
lastDetectedScreen = screenName;
|
|
121
|
+
trackScreen(screenName);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
pollingErrors++;
|
|
125
|
+
if (pollingErrors >= MAX_POLLING_ERRORS) {
|
|
126
|
+
clearInterval(intervalId);
|
|
127
|
+
setExpoRouterPollingInterval(null);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
pollingErrors++;
|
|
132
|
+
if (pollingErrors >= MAX_POLLING_ERRORS) {
|
|
133
|
+
clearInterval(intervalId);
|
|
134
|
+
setExpoRouterPollingInterval(null);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, 500);
|
|
138
|
+
|
|
139
|
+
setExpoRouterPollingInterval(intervalId);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
142
|
+
console.debug('[Rejourney] Expo Router not available:', e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let attempts = 0;
|
|
148
|
+
const maxAttempts = 5;
|
|
149
|
+
|
|
150
|
+
function trySetup(): void {
|
|
151
|
+
attempts++;
|
|
152
|
+
try {
|
|
153
|
+
const EXPO_ROUTER = 'expo-router';
|
|
154
|
+
const expoRouter = require(EXPO_ROUTER);
|
|
155
|
+
if (expoRouter?.router && isExpoRouterTrackingEnabled()) {
|
|
156
|
+
setupExpoRouterPolling();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Not ready or not installed
|
|
161
|
+
}
|
|
162
|
+
if (attempts < maxAttempts) {
|
|
163
|
+
setTimeout(trySetup, 200 * attempts);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setTimeout(trySetup, 200);
|