@repliqo/sdk-react-native 0.3.8 → 0.4.0
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/INTEGRATION_GUIDE.md +1384 -1312
- package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +242 -242
- package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -94
- package/dist/components/RepliqoFlatList.d.ts +5 -0
- package/dist/components/RepliqoFlatList.js +39 -0
- package/dist/components/RepliqoScrollView.d.ts +3 -0
- package/dist/components/RepliqoScrollView.js +16 -265
- package/dist/components/useRepliqoScrollTracking.d.ts +33 -0
- package/dist/components/useRepliqoScrollTracking.js +304 -0
- package/dist/core/client.d.ts +52 -0
- package/dist/core/client.js +304 -16
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +5 -1
- package/dist/core/storage.d.ts +29 -0
- package/dist/core/storage.js +33 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/snapshot/capture.d.ts +7 -0
- package/dist/snapshot/capture.js +17 -5
- package/dist/trackers/error.tracker.d.ts +25 -0
- package/dist/trackers/error.tracker.js +114 -37
- package/dist/trackers/navigation.tracker.js +11 -0
- package/dist/transport/api.client.d.ts +34 -1
- package/dist/transport/api.client.js +102 -19
- package/dist/transport/batch-queue.d.ts +53 -1
- package/dist/transport/batch-queue.js +126 -19
- package/dist/types/events.d.ts +12 -0
- package/package.json +68 -64
- package/src/components/RepliqoFlatList.tsx +64 -0
- package/src/components/RepliqoScrollView.tsx +53 -302
- package/src/components/useRepliqoScrollTracking.ts +366 -0
- package/src/core/client.ts +953 -628
- package/src/core/config.ts +38 -30
- package/src/core/storage.ts +51 -0
- package/src/index.ts +54 -50
- package/src/snapshot/NativeScreenCapture.ts +62 -62
- package/src/snapshot/capture.ts +154 -142
- package/src/trackers/error.tracker.ts +211 -136
- package/src/trackers/navigation.tracker.ts +107 -98
- package/src/transport/api.client.ts +430 -327
- package/src/transport/batch-queue.ts +212 -95
- package/src/types/events.ts +72 -60
|
@@ -1,136 +1,211 @@
|
|
|
1
|
-
import { CrashReport } from '../types/crash';
|
|
2
|
-
|
|
3
|
-
type CrashCallback = (crash: CrashReport) => void;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1
|
+
import { CrashReport } from '../types/crash';
|
|
2
|
+
|
|
3
|
+
type CrashCallback = (crash: CrashReport) => void;
|
|
4
|
+
|
|
5
|
+
/* Dynamic require for React Native's optional promise internals. */
|
|
6
|
+
declare const require: any;
|
|
7
|
+
|
|
8
|
+
export class ErrorTracker {
|
|
9
|
+
private onCrash: CrashCallback;
|
|
10
|
+
private sessionId: string | null = null;
|
|
11
|
+
private currentScreen: string | null = null;
|
|
12
|
+
private originalHandler:
|
|
13
|
+
| ((error: Error, isFatal?: boolean) => void)
|
|
14
|
+
| null = null;
|
|
15
|
+
private wrapperHandler:
|
|
16
|
+
| ((error: Error, isFatal?: boolean) => void)
|
|
17
|
+
| null = null;
|
|
18
|
+
private isActive = false;
|
|
19
|
+
|
|
20
|
+
constructor(onCrash: CrashCallback) {
|
|
21
|
+
this.onCrash = onCrash;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setSessionId(id: string | null): void {
|
|
25
|
+
this.sessionId = id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setCurrentScreen(screen: string | null): void {
|
|
29
|
+
this.currentScreen = screen;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start(): void {
|
|
33
|
+
if (this.isActive) return;
|
|
34
|
+
this.isActive = true;
|
|
35
|
+
this.setupGlobalErrorHandler();
|
|
36
|
+
this.setupUnhandledRejectionHandler();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
stop(): void {
|
|
40
|
+
if (!this.isActive) return;
|
|
41
|
+
// Flip the flag FIRST — the rejection trackers can't always be
|
|
42
|
+
// unregistered (Hermes), so their callbacks guard on isActive.
|
|
43
|
+
this.isActive = false;
|
|
44
|
+
this.restoreGlobalErrorHandler();
|
|
45
|
+
this.teardownUnhandledRejectionHandler();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Manual error reporting - allows consumers to report caught errors.
|
|
50
|
+
*/
|
|
51
|
+
reportError(error: Error, metadata?: Record<string, any>): void {
|
|
52
|
+
try {
|
|
53
|
+
const crash: CrashReport = {
|
|
54
|
+
sessionId: this.sessionId || undefined,
|
|
55
|
+
type: 'js_error',
|
|
56
|
+
message: error.message,
|
|
57
|
+
stackTrace: error.stack || undefined,
|
|
58
|
+
screenName: this.currentScreen || undefined,
|
|
59
|
+
metadata,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
this.onCrash(crash);
|
|
63
|
+
} catch (_e) {
|
|
64
|
+
// Never crash the app due to error reporting
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private setupGlobalErrorHandler(): void {
|
|
69
|
+
try {
|
|
70
|
+
const ErrorUtils = (global as any).ErrorUtils;
|
|
71
|
+
if (ErrorUtils) {
|
|
72
|
+
this.originalHandler = ErrorUtils.getGlobalHandler();
|
|
73
|
+
this.wrapperHandler = (error: Error, isFatal?: boolean) => {
|
|
74
|
+
if (this.isActive) {
|
|
75
|
+
try {
|
|
76
|
+
const crash: CrashReport = {
|
|
77
|
+
sessionId: this.sessionId || undefined,
|
|
78
|
+
type: isFatal ? 'native_crash' : 'js_error',
|
|
79
|
+
message: error.message,
|
|
80
|
+
stackTrace: error.stack || undefined,
|
|
81
|
+
screenName: this.currentScreen || undefined,
|
|
82
|
+
metadata: { isFatal },
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
this.onCrash(crash);
|
|
86
|
+
} catch (_e) {
|
|
87
|
+
// Never crash the app due to error reporting
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Always call original handler so RN can handle the error
|
|
92
|
+
if (this.originalHandler) {
|
|
93
|
+
this.originalHandler(error, isFatal);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
ErrorUtils.setGlobalHandler(this.wrapperHandler);
|
|
97
|
+
}
|
|
98
|
+
} catch (_e) {
|
|
99
|
+
// Never crash the app due to error handler setup
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle an unhandled promise rejection. Guarded on isActive because the
|
|
105
|
+
* underlying trackers can't always be unregistered.
|
|
106
|
+
*/
|
|
107
|
+
private handleUnhandledRejection = (_id: number, error: any): void => {
|
|
108
|
+
if (!this.isActive) return;
|
|
109
|
+
try {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
112
|
+
|
|
113
|
+
const crash: CrashReport = {
|
|
114
|
+
sessionId: this.sessionId || undefined,
|
|
115
|
+
type: 'unhandled_rejection',
|
|
116
|
+
message,
|
|
117
|
+
stackTrace: stack || undefined,
|
|
118
|
+
screenName: this.currentScreen || undefined,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
this.onCrash(crash);
|
|
122
|
+
} catch (_e) {
|
|
123
|
+
// Never crash the app due to error reporting
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Register with the mechanisms React Native ACTUALLY uses to surface
|
|
129
|
+
* unhandled promise rejections:
|
|
130
|
+
*
|
|
131
|
+
* 1. Hermes' native promise rejection tracker (Hermes engine).
|
|
132
|
+
* 2. The `promise` polyfill's rejection-tracking module (JSC and older
|
|
133
|
+
* RN versions).
|
|
134
|
+
*
|
|
135
|
+
* A bare global callback (the old approach) is never invoked by RN — it
|
|
136
|
+
* silently captured nothing.
|
|
137
|
+
*
|
|
138
|
+
* KNOWN LIMITATION: these trackers hold a single handler — registering
|
|
139
|
+
* REPLACES whatever was installed before (RN's dev LogBox warning, or
|
|
140
|
+
* another SDK like Sentry if it initialized first), and there is no
|
|
141
|
+
* unregister API, so after stop() an inert handler remains installed.
|
|
142
|
+
* If you run multiple error-tracking SDKs, initialize Repliqo FIRST so
|
|
143
|
+
* the other SDK's handler wins, or disable enableCrashTracking.
|
|
144
|
+
*/
|
|
145
|
+
private setupUnhandledRejectionHandler(): void {
|
|
146
|
+
// Kept for manual invocation / test hooks.
|
|
147
|
+
try {
|
|
148
|
+
(global as any).__analyticsUnhandledRejection =
|
|
149
|
+
this.handleUnhandledRejection;
|
|
150
|
+
} catch (_e) {
|
|
151
|
+
/* ignore */
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Hermes: native promise rejection tracking.
|
|
155
|
+
try {
|
|
156
|
+
const hermes = (global as any).HermesInternal;
|
|
157
|
+
if (hermes?.hasPromise?.() && hermes?.enablePromiseRejectionTracker) {
|
|
158
|
+
hermes.enablePromiseRejectionTracker({
|
|
159
|
+
allRejections: true,
|
|
160
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
161
|
+
onHandled: () => {},
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
} catch (_e) {
|
|
166
|
+
/* ignore — fall through to the polyfill */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// JSC / promise polyfill path.
|
|
170
|
+
try {
|
|
171
|
+
const rejectionTracking = require('promise/setimmediate/rejection-tracking');
|
|
172
|
+
rejectionTracking.enable({
|
|
173
|
+
allRejections: true,
|
|
174
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
175
|
+
onHandled: () => {},
|
|
176
|
+
});
|
|
177
|
+
} catch (_e) {
|
|
178
|
+
// Module unavailable (e.g. jest without the polyfill) — the global
|
|
179
|
+
// hook above still allows manual reporting.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private teardownUnhandledRejectionHandler(): void {
|
|
184
|
+
try {
|
|
185
|
+
delete (global as any).__analyticsUnhandledRejection;
|
|
186
|
+
} catch (_e) {
|
|
187
|
+
/* ignore */
|
|
188
|
+
}
|
|
189
|
+
// Hermes offers no unregister API and disabling the polyfill tracker
|
|
190
|
+
// could clobber another library's handler — the isActive guard in
|
|
191
|
+
// handleUnhandledRejection makes lingering registrations inert.
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private restoreGlobalErrorHandler(): void {
|
|
195
|
+
try {
|
|
196
|
+
const ErrorUtils = (global as any).ErrorUtils;
|
|
197
|
+
if (ErrorUtils && this.originalHandler) {
|
|
198
|
+
// Only restore if WE are still the installed handler — another
|
|
199
|
+
// library may have wrapped us after start(), and restoring would
|
|
200
|
+
// clobber its handler.
|
|
201
|
+
if (ErrorUtils.getGlobalHandler() === this.wrapperHandler) {
|
|
202
|
+
ErrorUtils.setGlobalHandler(this.originalHandler);
|
|
203
|
+
}
|
|
204
|
+
this.originalHandler = null;
|
|
205
|
+
this.wrapperHandler = null;
|
|
206
|
+
}
|
|
207
|
+
} catch (_e) {
|
|
208
|
+
// Never crash the app due to handler restoration
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -1,98 +1,107 @@
|
|
|
1
|
-
import { useRef, useCallback } from 'react';
|
|
2
|
-
import { AppAnalytics } from '../core/client';
|
|
3
|
-
|
|
4
|
-
interface NavigationState {
|
|
5
|
-
routes: Array<{ name: string; key: string; state?: NavigationState }>;
|
|
6
|
-
index: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function getActiveRouteName(state: NavigationState | undefined): string | null {
|
|
10
|
-
if (!state) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const route = state.routes[state.index];
|
|
15
|
-
if (!route) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Dive into nested navigators
|
|
20
|
-
if (route.state) {
|
|
21
|
-
return getActiveRouteName(route.state as NavigationState);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return route.name;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function useNavigationTracker() {
|
|
28
|
-
const routeNameRef = useRef<string | null>(null);
|
|
29
|
-
const navigationRef = useRef<any>(null);
|
|
30
|
-
|
|
31
|
-
const onReady = useCallback(() => {
|
|
32
|
-
if (navigationRef.current) {
|
|
33
|
-
const state = navigationRef.current.getRootState() as
|
|
34
|
-
| NavigationState
|
|
35
|
-
| undefined;
|
|
36
|
-
const currentRouteName = getActiveRouteName(state);
|
|
37
|
-
routeNameRef.current = currentRouteName;
|
|
38
|
-
|
|
39
|
-
if (currentRouteName) {
|
|
40
|
-
try {
|
|
41
|
-
const analytics = AppAnalytics.getInstance();
|
|
42
|
-
analytics.onScreenEnter(currentRouteName);
|
|
43
|
-
} catch {
|
|
44
|
-
// AppAnalytics not initialized, silently ignore
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}, []);
|
|
49
|
-
|
|
50
|
-
const onStateChange = useCallback(() => {
|
|
51
|
-
if (!navigationRef.current) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const state = navigationRef.current.getRootState() as
|
|
56
|
-
| NavigationState
|
|
57
|
-
| undefined;
|
|
58
|
-
const currentRouteName = getActiveRouteName(state);
|
|
59
|
-
const previousRouteName = routeNameRef.current;
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
currentRouteName &&
|
|
63
|
-
previousRouteName &&
|
|
64
|
-
currentRouteName !== previousRouteName
|
|
65
|
-
) {
|
|
66
|
-
try {
|
|
67
|
-
const analytics = AppAnalytics.getInstance();
|
|
68
|
-
analytics.trackNavigation(previousRouteName, currentRouteName);
|
|
69
|
-
analytics.onScreenExit(previousRouteName);
|
|
70
|
-
analytics.onScreenEnter(currentRouteName);
|
|
71
|
-
} catch {
|
|
72
|
-
// AppAnalytics not initialized, silently ignore
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
import { useRef, useCallback } from 'react';
|
|
2
|
+
import { AppAnalytics } from '../core/client';
|
|
3
|
+
|
|
4
|
+
interface NavigationState {
|
|
5
|
+
routes: Array<{ name: string; key: string; state?: NavigationState }>;
|
|
6
|
+
index: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getActiveRouteName(state: NavigationState | undefined): string | null {
|
|
10
|
+
if (!state) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const route = state.routes[state.index];
|
|
15
|
+
if (!route) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Dive into nested navigators
|
|
20
|
+
if (route.state) {
|
|
21
|
+
return getActiveRouteName(route.state as NavigationState);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return route.name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useNavigationTracker() {
|
|
28
|
+
const routeNameRef = useRef<string | null>(null);
|
|
29
|
+
const navigationRef = useRef<any>(null);
|
|
30
|
+
|
|
31
|
+
const onReady = useCallback(() => {
|
|
32
|
+
if (navigationRef.current) {
|
|
33
|
+
const state = navigationRef.current.getRootState() as
|
|
34
|
+
| NavigationState
|
|
35
|
+
| undefined;
|
|
36
|
+
const currentRouteName = getActiveRouteName(state);
|
|
37
|
+
routeNameRef.current = currentRouteName;
|
|
38
|
+
|
|
39
|
+
if (currentRouteName) {
|
|
40
|
+
try {
|
|
41
|
+
const analytics = AppAnalytics.getInstance();
|
|
42
|
+
analytics.onScreenEnter(currentRouteName);
|
|
43
|
+
} catch {
|
|
44
|
+
// AppAnalytics not initialized, silently ignore
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const onStateChange = useCallback(() => {
|
|
51
|
+
if (!navigationRef.current) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const state = navigationRef.current.getRootState() as
|
|
56
|
+
| NavigationState
|
|
57
|
+
| undefined;
|
|
58
|
+
const currentRouteName = getActiveRouteName(state);
|
|
59
|
+
const previousRouteName = routeNameRef.current;
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
currentRouteName &&
|
|
63
|
+
previousRouteName &&
|
|
64
|
+
currentRouteName !== previousRouteName
|
|
65
|
+
) {
|
|
66
|
+
try {
|
|
67
|
+
const analytics = AppAnalytics.getInstance();
|
|
68
|
+
analytics.trackNavigation(previousRouteName, currentRouteName);
|
|
69
|
+
analytics.onScreenExit(previousRouteName);
|
|
70
|
+
analytics.onScreenEnter(currentRouteName);
|
|
71
|
+
} catch {
|
|
72
|
+
// AppAnalytics not initialized, silently ignore
|
|
73
|
+
}
|
|
74
|
+
} else if (currentRouteName && !previousRouteName) {
|
|
75
|
+
// First route seen without onReady having fired (some apps only wire
|
|
76
|
+
// onStateChange). Without this, currentScreen stays null until the
|
|
77
|
+
// SECOND navigation and early touches/visits go unattributed.
|
|
78
|
+
try {
|
|
79
|
+
AppAnalytics.getInstance().onScreenEnter(currentRouteName);
|
|
80
|
+
} catch {
|
|
81
|
+
// AppAnalytics not initialized, silently ignore
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
routeNameRef.current = currentRouteName;
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
navigationRef,
|
|
90
|
+
onStateChange,
|
|
91
|
+
onReady,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function trackScreenChange(
|
|
96
|
+
fromScreen: string,
|
|
97
|
+
toScreen: string,
|
|
98
|
+
): void {
|
|
99
|
+
try {
|
|
100
|
+
const analytics = AppAnalytics.getInstance();
|
|
101
|
+
analytics.trackNavigation(fromScreen, toScreen);
|
|
102
|
+
analytics.onScreenExit(fromScreen);
|
|
103
|
+
analytics.onScreenEnter(toScreen);
|
|
104
|
+
} catch {
|
|
105
|
+
// AppAnalytics not initialized, silently ignore
|
|
106
|
+
}
|
|
107
|
+
}
|