@datalyr/react-native 1.7.0 → 1.7.1

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 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 does not expose a `NavigationContainer`, so automatic tracking is not available. Use `datalyr.screen()` manually in your layout files instead:
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
- import { datalyr } from '@datalyr/react-native/expo';
552
- import { usePathname } from 'expo-router';
553
- import { useEffect } from 'react';
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
- const pathname = usePathname();
556
+ useDatalyrScreenTracking();
557
+ return <Stack />;
558
+ }
559
+ ```
557
560
 
558
- useEffect(() => {
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
- return <Slot />;
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
@@ -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 to attach session info.
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
  /**
@@ -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 to attach session info.
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
  }
@@ -177,7 +177,7 @@ export class DatalyrSDK {
177
177
  const installData = await attributionManager.trackInstall();
178
178
  await this.track('app_install', {
179
179
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
180
- sdk_version: '1.7.0',
180
+ sdk_version: '1.7.1',
181
181
  ...installData,
182
182
  });
183
183
  }
@@ -908,7 +908,7 @@ export class DatalyrSDK {
908
908
  carrier: deviceInfo.carrier,
909
909
  network_type: getNetworkType(),
910
910
  timestamp: Date.now(),
911
- sdk_version: '1.7.0',
911
+ sdk_version: '1.7.1',
912
912
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
913
913
  ...(advertiserInfo ? {
914
914
  idfa: advertiserInfo.idfa,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalyr/react-native",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Datalyr SDK for React Native & Expo - Server-side attribution tracking for iOS and Android",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -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 to attach session info.
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
  }
@@ -196,7 +196,7 @@ export class DatalyrSDKExpo {
196
196
  const installData = await attributionManager.trackInstall();
197
197
  await this.track('app_install', {
198
198
  platform: Platform.OS,
199
- sdk_version: '1.7.0',
199
+ sdk_version: '1.7.1',
200
200
  sdk_variant: 'expo',
201
201
  ...installData,
202
202
  });
@@ -792,7 +792,7 @@ export class DatalyrSDKExpo {
792
792
  carrier: deviceInfo.carrier,
793
793
  network_type: networkType,
794
794
  timestamp: Date.now(),
795
- sdk_version: '1.7.0',
795
+ sdk_version: '1.7.1',
796
796
  sdk_variant: 'expo',
797
797
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
798
798
  ...(advertiserInfo ? {
@@ -228,7 +228,7 @@ export class DatalyrSDK {
228
228
  const installData = await attributionManager.trackInstall();
229
229
  await this.track('app_install', {
230
230
  platform: Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'android',
231
- sdk_version: '1.7.0',
231
+ sdk_version: '1.7.1',
232
232
  ...installData,
233
233
  });
234
234
  }
@@ -1086,7 +1086,7 @@ export class DatalyrSDK {
1086
1086
  carrier: deviceInfo.carrier,
1087
1087
  network_type: getNetworkType(),
1088
1088
  timestamp: Date.now(),
1089
- sdk_version: '1.7.0',
1089
+ sdk_version: '1.7.1',
1090
1090
  // Advertiser data (IDFA/GAID, ATT status) for server-side postback
1091
1091
  ...(advertiserInfo ? {
1092
1092
  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';