@datalyr/react-native 1.7.0 → 1.7.2
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 +31 -10
- package/lib/auto-events.d.ts +2 -1
- package/lib/auto-events.js +3 -2
- package/lib/datalyr-sdk.d.ts +3 -0
- package/lib/datalyr-sdk.js +22 -3
- package/package.json +1 -1
- package/src/auto-events.ts +3 -2
- package/src/datalyr-sdk-expo.ts +21 -3
- package/src/datalyr-sdk.ts +22 -3
- package/src/expo-router-tracking.ts +108 -0
- package/src/expo.ts +43 -0
package/README.md
CHANGED
|
@@ -545,22 +545,43 @@ The API is identical to the React Navigation example above.
|
|
|
545
545
|
|
|
546
546
|
### Expo Router
|
|
547
547
|
|
|
548
|
-
Expo Router
|
|
548
|
+
For Expo Router apps, use the `useDatalyrScreenTracking` hook in your root layout. It automatically tracks every route change as a `pageview` event:
|
|
549
549
|
|
|
550
550
|
```tsx
|
|
551
|
-
|
|
552
|
-
import {
|
|
553
|
-
import {
|
|
551
|
+
// app/_layout.tsx
|
|
552
|
+
import { useDatalyrScreenTracking } from '@datalyr/react-native/expo';
|
|
553
|
+
import { Stack } from 'expo-router';
|
|
554
554
|
|
|
555
555
|
export default function RootLayout() {
|
|
556
|
-
|
|
556
|
+
useDatalyrScreenTracking();
|
|
557
|
+
return <Stack />;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
557
560
|
|
|
558
|
-
|
|
559
|
-
datalyr.screen(pathname);
|
|
560
|
-
}, [pathname]);
|
|
561
|
+
Screen names are raw pathnames (e.g. `/onboarding/paywall`, `/(app)/chat`). You can map specific paths to friendly names:
|
|
561
562
|
|
|
562
|
-
|
|
563
|
-
|
|
563
|
+
```tsx
|
|
564
|
+
useDatalyrScreenTracking({
|
|
565
|
+
screenNames: {
|
|
566
|
+
'/onboarding/paywall': 'Paywall',
|
|
567
|
+
'/(app)/chat': 'Chat',
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Additional options:
|
|
573
|
+
|
|
574
|
+
```tsx
|
|
575
|
+
useDatalyrScreenTracking({
|
|
576
|
+
// Map specific paths to friendly names (checked first)
|
|
577
|
+
screenNames: { '/': 'Home' },
|
|
578
|
+
|
|
579
|
+
// Transform all other pathnames (e.g. strip route groups)
|
|
580
|
+
transformPathname: (path) => path.replace(/\(.*?\)\//g, ''),
|
|
581
|
+
|
|
582
|
+
// Skip tracking for certain paths
|
|
583
|
+
shouldTrackPath: (path) => !path.startsWith('/modal'),
|
|
584
|
+
});
|
|
564
585
|
```
|
|
565
586
|
|
|
566
587
|
### Configuration
|
package/lib/auto-events.d.ts
CHANGED
|
@@ -52,7 +52,8 @@ export declare class AutoEventsManager {
|
|
|
52
52
|
recordScreenView(screenName: string): Promise<void>;
|
|
53
53
|
/**
|
|
54
54
|
* Get session data to enrich a pageview event.
|
|
55
|
-
* Called by the SDK's screen() method
|
|
55
|
+
* Called by the SDK's screen() method *before* recordScreenView(),
|
|
56
|
+
* so we add 1 to account for the current view being tracked.
|
|
56
57
|
*/
|
|
57
58
|
getScreenViewEnrichment(): Record<string, any> | null;
|
|
58
59
|
/**
|
package/lib/auto-events.js
CHANGED
|
@@ -180,14 +180,15 @@ export class AutoEventsManager {
|
|
|
180
180
|
}
|
|
181
181
|
/**
|
|
182
182
|
* Get session data to enrich a pageview event.
|
|
183
|
-
* Called by the SDK's screen() method
|
|
183
|
+
* Called by the SDK's screen() method *before* recordScreenView(),
|
|
184
|
+
* so we add 1 to account for the current view being tracked.
|
|
184
185
|
*/
|
|
185
186
|
getScreenViewEnrichment() {
|
|
186
187
|
if (!this.currentSession)
|
|
187
188
|
return null;
|
|
188
189
|
return {
|
|
189
190
|
session_id: this.currentSession.sessionId,
|
|
190
|
-
pageviews_in_session: this.currentSession.screenViews,
|
|
191
|
+
pageviews_in_session: this.currentSession.screenViews + 1,
|
|
191
192
|
previous_screen: this.lastScreenName,
|
|
192
193
|
};
|
|
193
194
|
}
|
package/lib/datalyr-sdk.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export declare class DatalyrSDK {
|
|
|
12
12
|
private cachedAdvertiserInfo;
|
|
13
13
|
private static conversionEncoder?;
|
|
14
14
|
private static debugEnabled;
|
|
15
|
+
/** Events that arrived before initialize() completed. Flushed once init finishes. */
|
|
16
|
+
private preInitQueue;
|
|
17
|
+
private static readonly PRE_INIT_QUEUE_MAX;
|
|
15
18
|
constructor();
|
|
16
19
|
/**
|
|
17
20
|
* Initialize the SDK with configuration
|
package/lib/datalyr-sdk.js
CHANGED
|
@@ -16,6 +16,8 @@ export class DatalyrSDK {
|
|
|
16
16
|
this.appStateSubscription = null;
|
|
17
17
|
this.networkStatusUnsubscribe = null;
|
|
18
18
|
this.cachedAdvertiserInfo = null;
|
|
19
|
+
/** Events that arrived before initialize() completed. Flushed once init finishes. */
|
|
20
|
+
this.preInitQueue = [];
|
|
19
21
|
// Initialize state with defaults
|
|
20
22
|
this.state = {
|
|
21
23
|
initialized: false,
|
|
@@ -167,6 +169,15 @@ export class DatalyrSDK {
|
|
|
167
169
|
});
|
|
168
170
|
// SDK initialized successfully - set state before tracking install event
|
|
169
171
|
this.state.initialized = true;
|
|
172
|
+
// Flush any events that were queued before init completed (e.g. screen tracking)
|
|
173
|
+
if (this.preInitQueue.length > 0) {
|
|
174
|
+
debugLog(`Flushing ${this.preInitQueue.length} pre-init event(s)`);
|
|
175
|
+
const queued = [...this.preInitQueue];
|
|
176
|
+
this.preInitQueue = [];
|
|
177
|
+
for (const { eventName, eventData } of queued) {
|
|
178
|
+
await this.track(eventName, eventData);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
170
181
|
// Check for app install (after SDK is marked as initialized)
|
|
171
182
|
if (attributionManager.isInstall()) {
|
|
172
183
|
// iOS: Attempt deferred web-to-app attribution via IP matching before tracking install
|
|
@@ -177,7 +188,7 @@ export class DatalyrSDK {
|
|
|
177
188
|
const installData = await attributionManager.trackInstall();
|
|
178
189
|
await this.track('app_install', {
|
|
179
190
|
platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
|
|
180
|
-
sdk_version: '1.7.
|
|
191
|
+
sdk_version: '1.7.2',
|
|
181
192
|
...installData,
|
|
182
193
|
});
|
|
183
194
|
}
|
|
@@ -199,7 +210,14 @@ export class DatalyrSDK {
|
|
|
199
210
|
async track(eventName, eventData) {
|
|
200
211
|
try {
|
|
201
212
|
if (!this.state.initialized) {
|
|
202
|
-
|
|
213
|
+
// Queue events that arrive before init completes instead of dropping them
|
|
214
|
+
if (this.preInitQueue.length < DatalyrSDK.PRE_INIT_QUEUE_MAX) {
|
|
215
|
+
debugLog(`Queuing pre-init event: ${eventName}`);
|
|
216
|
+
this.preInitQueue.push({ eventName, eventData });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
errorLog('Pre-init event queue full, dropping event:', eventName);
|
|
220
|
+
}
|
|
203
221
|
return;
|
|
204
222
|
}
|
|
205
223
|
if (!validateEventName(eventName)) {
|
|
@@ -908,7 +926,7 @@ export class DatalyrSDK {
|
|
|
908
926
|
carrier: deviceInfo.carrier,
|
|
909
927
|
network_type: getNetworkType(),
|
|
910
928
|
timestamp: Date.now(),
|
|
911
|
-
sdk_version: '1.7.
|
|
929
|
+
sdk_version: '1.7.2',
|
|
912
930
|
// Advertiser data (IDFA/GAID, ATT status) for server-side postback
|
|
913
931
|
...(advertiserInfo ? {
|
|
914
932
|
idfa: advertiserInfo.idfa,
|
|
@@ -1089,6 +1107,7 @@ export class DatalyrSDK {
|
|
|
1089
1107
|
}
|
|
1090
1108
|
}
|
|
1091
1109
|
DatalyrSDK.debugEnabled = false;
|
|
1110
|
+
DatalyrSDK.PRE_INIT_QUEUE_MAX = 50;
|
|
1092
1111
|
// Create singleton instance
|
|
1093
1112
|
const datalyr = new DatalyrSDK();
|
|
1094
1113
|
// Export enhanced Datalyr class with static methods
|
package/package.json
CHANGED
package/src/auto-events.ts
CHANGED
|
@@ -222,13 +222,14 @@ export class AutoEventsManager {
|
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
224
|
* Get session data to enrich a pageview event.
|
|
225
|
-
* Called by the SDK's screen() method
|
|
225
|
+
* Called by the SDK's screen() method *before* recordScreenView(),
|
|
226
|
+
* so we add 1 to account for the current view being tracked.
|
|
226
227
|
*/
|
|
227
228
|
getScreenViewEnrichment(): Record<string, any> | null {
|
|
228
229
|
if (!this.currentSession) return null;
|
|
229
230
|
return {
|
|
230
231
|
session_id: this.currentSession.sessionId,
|
|
231
|
-
pageviews_in_session: this.currentSession.screenViews,
|
|
232
|
+
pageviews_in_session: this.currentSession.screenViews + 1,
|
|
232
233
|
previous_screen: this.lastScreenName,
|
|
233
234
|
};
|
|
234
235
|
}
|
package/src/datalyr-sdk-expo.ts
CHANGED
|
@@ -47,6 +47,9 @@ export class DatalyrSDKExpo {
|
|
|
47
47
|
private cachedAdvertiserInfo: any = null;
|
|
48
48
|
private static conversionEncoder?: ConversionValueEncoder;
|
|
49
49
|
private static debugEnabled = false;
|
|
50
|
+
/** Events that arrived before initialize() completed. Flushed once init finishes. */
|
|
51
|
+
private preInitQueue: Array<{ eventName: string; eventData?: EventData }> = [];
|
|
52
|
+
private static readonly PRE_INIT_QUEUE_MAX = 50;
|
|
50
53
|
|
|
51
54
|
constructor() {
|
|
52
55
|
this.state = {
|
|
@@ -192,11 +195,21 @@ export class DatalyrSDKExpo {
|
|
|
192
195
|
|
|
193
196
|
this.state.initialized = true;
|
|
194
197
|
|
|
198
|
+
// Flush any events that were queued before init completed (e.g. screen tracking)
|
|
199
|
+
if (this.preInitQueue.length > 0) {
|
|
200
|
+
debugLog(`Flushing ${this.preInitQueue.length} pre-init event(s)`);
|
|
201
|
+
const queued = [...this.preInitQueue];
|
|
202
|
+
this.preInitQueue = [];
|
|
203
|
+
for (const { eventName, eventData } of queued) {
|
|
204
|
+
await this.track(eventName, eventData);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
195
208
|
if (attributionManager.isInstall()) {
|
|
196
209
|
const installData = await attributionManager.trackInstall();
|
|
197
210
|
await this.track('app_install', {
|
|
198
211
|
platform: Platform.OS,
|
|
199
|
-
sdk_version: '1.7.
|
|
212
|
+
sdk_version: '1.7.2',
|
|
200
213
|
sdk_variant: 'expo',
|
|
201
214
|
...installData,
|
|
202
215
|
});
|
|
@@ -218,7 +231,12 @@ export class DatalyrSDKExpo {
|
|
|
218
231
|
async track(eventName: string, eventData?: EventData): Promise<void> {
|
|
219
232
|
try {
|
|
220
233
|
if (!this.state.initialized) {
|
|
221
|
-
|
|
234
|
+
if (this.preInitQueue.length < DatalyrSDKExpo.PRE_INIT_QUEUE_MAX) {
|
|
235
|
+
debugLog(`Queuing pre-init event: ${eventName}`);
|
|
236
|
+
this.preInitQueue.push({ eventName, eventData });
|
|
237
|
+
} else {
|
|
238
|
+
errorLog('Pre-init event queue full, dropping event:', eventName as unknown as Error);
|
|
239
|
+
}
|
|
222
240
|
return;
|
|
223
241
|
}
|
|
224
242
|
|
|
@@ -792,7 +810,7 @@ export class DatalyrSDKExpo {
|
|
|
792
810
|
carrier: deviceInfo.carrier,
|
|
793
811
|
network_type: networkType,
|
|
794
812
|
timestamp: Date.now(),
|
|
795
|
-
sdk_version: '1.7.
|
|
813
|
+
sdk_version: '1.7.2',
|
|
796
814
|
sdk_variant: 'expo',
|
|
797
815
|
// Advertiser data (IDFA/GAID, ATT status) for server-side postback
|
|
798
816
|
...(advertiserInfo ? {
|
package/src/datalyr-sdk.ts
CHANGED
|
@@ -45,6 +45,9 @@ export class DatalyrSDK {
|
|
|
45
45
|
private cachedAdvertiserInfo: any = null;
|
|
46
46
|
private static conversionEncoder?: ConversionValueEncoder;
|
|
47
47
|
private static debugEnabled = false;
|
|
48
|
+
/** Events that arrived before initialize() completed. Flushed once init finishes. */
|
|
49
|
+
private preInitQueue: Array<{ eventName: string; eventData?: EventData }> = [];
|
|
50
|
+
private static readonly PRE_INIT_QUEUE_MAX = 50;
|
|
48
51
|
|
|
49
52
|
constructor() {
|
|
50
53
|
// Initialize state with defaults
|
|
@@ -217,6 +220,16 @@ export class DatalyrSDK {
|
|
|
217
220
|
// SDK initialized successfully - set state before tracking install event
|
|
218
221
|
this.state.initialized = true;
|
|
219
222
|
|
|
223
|
+
// Flush any events that were queued before init completed (e.g. screen tracking)
|
|
224
|
+
if (this.preInitQueue.length > 0) {
|
|
225
|
+
debugLog(`Flushing ${this.preInitQueue.length} pre-init event(s)`);
|
|
226
|
+
const queued = [...this.preInitQueue];
|
|
227
|
+
this.preInitQueue = [];
|
|
228
|
+
for (const { eventName, eventData } of queued) {
|
|
229
|
+
await this.track(eventName, eventData);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
220
233
|
// Check for app install (after SDK is marked as initialized)
|
|
221
234
|
if (attributionManager.isInstall()) {
|
|
222
235
|
// iOS: Attempt deferred web-to-app attribution via IP matching before tracking install
|
|
@@ -228,7 +241,7 @@ export class DatalyrSDK {
|
|
|
228
241
|
const installData = await attributionManager.trackInstall();
|
|
229
242
|
await this.track('app_install', {
|
|
230
243
|
platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
|
|
231
|
-
sdk_version: '1.7.
|
|
244
|
+
sdk_version: '1.7.2',
|
|
232
245
|
...installData,
|
|
233
246
|
});
|
|
234
247
|
}
|
|
@@ -252,7 +265,13 @@ export class DatalyrSDK {
|
|
|
252
265
|
async track(eventName: string, eventData?: EventData): Promise<void> {
|
|
253
266
|
try {
|
|
254
267
|
if (!this.state.initialized) {
|
|
255
|
-
|
|
268
|
+
// Queue events that arrive before init completes instead of dropping them
|
|
269
|
+
if (this.preInitQueue.length < DatalyrSDK.PRE_INIT_QUEUE_MAX) {
|
|
270
|
+
debugLog(`Queuing pre-init event: ${eventName}`);
|
|
271
|
+
this.preInitQueue.push({ eventName, eventData });
|
|
272
|
+
} else {
|
|
273
|
+
errorLog('Pre-init event queue full, dropping event:', eventName as unknown as Error);
|
|
274
|
+
}
|
|
256
275
|
return;
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -1086,7 +1105,7 @@ export class DatalyrSDK {
|
|
|
1086
1105
|
carrier: deviceInfo.carrier,
|
|
1087
1106
|
network_type: getNetworkType(),
|
|
1088
1107
|
timestamp: Date.now(),
|
|
1089
|
-
sdk_version: '1.7.
|
|
1108
|
+
sdk_version: '1.7.2',
|
|
1090
1109
|
// Advertiser data (IDFA/GAID, ATT status) for server-side postback
|
|
1091
1110
|
...(advertiserInfo ? {
|
|
1092
1111
|
idfa: advertiserInfo.idfa,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic screen tracking for Expo Router.
|
|
3
|
+
*
|
|
4
|
+
* Expo Router uses file-based routing and does not expose a
|
|
5
|
+
* NavigationContainerRef, so we use the `usePathname()` hook to
|
|
6
|
+
* detect route changes and fire pageview events automatically.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // app/_layout.tsx
|
|
10
|
+
* import { useDatalyrScreenTracking } from '@datalyr/react-native/expo';
|
|
11
|
+
*
|
|
12
|
+
* export default function RootLayout() {
|
|
13
|
+
* useDatalyrScreenTracking();
|
|
14
|
+
* return <Stack />;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Screen names are the raw pathname (e.g. "/onboarding/paywall", "/(app)/chat").
|
|
19
|
+
* These are consistent and easy to filter in the Datalyr dashboard.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useEffect, useRef } from 'react';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Configuration
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export interface ExpoRouterTrackingConfig {
|
|
29
|
+
/**
|
|
30
|
+
* Map specific pathnames to friendly screen names.
|
|
31
|
+
* Paths not in this map use the raw pathname (or `transformPathname` if set).
|
|
32
|
+
* @example { '/onboarding/paywall': 'Paywall', '/(app)/chat': 'Chat' }
|
|
33
|
+
*/
|
|
34
|
+
screenNames?: Record<string, string>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Transform the pathname before tracking.
|
|
38
|
+
* Applied only when the path is NOT in `screenNames`.
|
|
39
|
+
* @example (path) => path.replace(/\(.*?\)\//g, '') // strip route groups
|
|
40
|
+
*/
|
|
41
|
+
transformPathname?: (pathname: string) => string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Filter which paths should be tracked.
|
|
45
|
+
* Return false to skip tracking for a given path.
|
|
46
|
+
* @example (path) => !path.startsWith('/modal')
|
|
47
|
+
*/
|
|
48
|
+
shouldTrackPath?: (pathname: string) => boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Core hook
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* React hook that automatically tracks screen views when the Expo Router
|
|
57
|
+
* pathname changes. Drop this into your root `_layout.tsx`.
|
|
58
|
+
*
|
|
59
|
+
* @param trackScreen The function that records a screen event.
|
|
60
|
+
* Receives `(pathname, properties)`.
|
|
61
|
+
* @param usePathname The `usePathname` hook from `expo-router`.
|
|
62
|
+
* Passed in to avoid a hard dependency on expo-router.
|
|
63
|
+
* @param config Optional filtering / transform config.
|
|
64
|
+
*/
|
|
65
|
+
export function useExpoRouterTracking(
|
|
66
|
+
trackScreen: (screenName: string, properties?: Record<string, any>) => Promise<void>,
|
|
67
|
+
usePathname: () => string,
|
|
68
|
+
config?: ExpoRouterTrackingConfig,
|
|
69
|
+
): void {
|
|
70
|
+
const pathname = usePathname();
|
|
71
|
+
const previousPathname = useRef<string | undefined>(undefined);
|
|
72
|
+
|
|
73
|
+
// Keep mutable refs so the effect always sees the latest values
|
|
74
|
+
// without needing them in the dependency array (which would re-fire on every render).
|
|
75
|
+
const trackScreenRef = useRef(trackScreen);
|
|
76
|
+
trackScreenRef.current = trackScreen;
|
|
77
|
+
const configRef = useRef(config);
|
|
78
|
+
configRef.current = config;
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!pathname) return;
|
|
82
|
+
if (previousPathname.current === pathname) return;
|
|
83
|
+
|
|
84
|
+
const prevPath = previousPathname.current;
|
|
85
|
+
previousPathname.current = pathname;
|
|
86
|
+
|
|
87
|
+
const cfg = configRef.current;
|
|
88
|
+
|
|
89
|
+
// Apply filter
|
|
90
|
+
if (cfg?.shouldTrackPath && !cfg.shouldTrackPath(pathname)) return;
|
|
91
|
+
|
|
92
|
+
// Resolve screen name: screenNames map → transformPathname → raw pathname
|
|
93
|
+
const resolve = (path: string): string =>
|
|
94
|
+
cfg?.screenNames?.[path]
|
|
95
|
+
?? (cfg?.transformPathname ? cfg.transformPathname(path) : path);
|
|
96
|
+
|
|
97
|
+
const screenName = resolve(pathname);
|
|
98
|
+
|
|
99
|
+
const properties: Record<string, any> = { source: 'auto_expo_router' };
|
|
100
|
+
if (prevPath) {
|
|
101
|
+
properties.previous_screen = resolve(prevPath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
trackScreenRef.current(screenName, properties).catch(() => {
|
|
105
|
+
// Silently ignore — SDK logs internally
|
|
106
|
+
});
|
|
107
|
+
}, [pathname]);
|
|
108
|
+
}
|
package/src/expo.ts
CHANGED
|
@@ -89,6 +89,49 @@ export function datalyrScreenTracking(
|
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Export automatic screen tracking for Expo Router
|
|
93
|
+
export { useExpoRouterTracking } from './expo-router-tracking';
|
|
94
|
+
export type { ExpoRouterTrackingConfig } from './expo-router-tracking';
|
|
95
|
+
|
|
96
|
+
import { useExpoRouterTracking as _useExpoRouterTracking } from './expo-router-tracking';
|
|
97
|
+
import type { ExpoRouterTrackingConfig as _ExpoRouterConfig } from './expo-router-tracking';
|
|
98
|
+
|
|
99
|
+
// Lazy-resolved expo-router hook. Resolved once at first call, not inside the
|
|
100
|
+
// hook body, so that hook call count stays stable across renders.
|
|
101
|
+
let _usePathname: (() => string) | null = null;
|
|
102
|
+
function getUsePathname(): () => string {
|
|
103
|
+
if (!_usePathname) {
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
105
|
+
_usePathname = (require('expo-router') as { usePathname: () => string }).usePathname;
|
|
106
|
+
}
|
|
107
|
+
return _usePathname;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Drop-in screen tracking for Expo Router — wires to the Expo Datalyr singleton.
|
|
112
|
+
*
|
|
113
|
+
* ```tsx
|
|
114
|
+
* // app/_layout.tsx
|
|
115
|
+
* import { useDatalyrScreenTracking } from '@datalyr/react-native/expo';
|
|
116
|
+
*
|
|
117
|
+
* export default function RootLayout() {
|
|
118
|
+
* useDatalyrScreenTracking();
|
|
119
|
+
* return <Stack />;
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* Screen names are raw pathnames (e.g. "/onboarding/paywall").
|
|
124
|
+
*
|
|
125
|
+
* @param config Optional path transforms and filters.
|
|
126
|
+
*/
|
|
127
|
+
export function useDatalyrScreenTracking(config?: _ExpoRouterConfig): void {
|
|
128
|
+
_useExpoRouterTracking(
|
|
129
|
+
(screenName, properties) => datalyrExpo.screen(screenName, properties),
|
|
130
|
+
getUsePathname(),
|
|
131
|
+
config,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
92
135
|
// Export platform integrations
|
|
93
136
|
export { appleSearchAdsIntegration } from './integrations';
|
|
94
137
|
export type { AppleSearchAdsAttribution } from './native/DatalyrNativeBridge';
|