@drakkar.software/sunglasses-react-native 0.2.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/dist/index.d.mts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +213 -0
- package/dist/index.mjs +186 -0
- package/package.json +49 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
import { ISunglassesClient, ScreenTrackingOptions } from '@drakkar.software/sunglasses-core';
|
|
3
|
+
export { ConsentStatus, ISunglassesClient, ScreenTrackingOptions, SunglassesConfig, SunglassesCore, SunglassesEvent } from '@drakkar.software/sunglasses-core';
|
|
4
|
+
|
|
5
|
+
interface SunglassesProviderProps {
|
|
6
|
+
/** An initialized ISunglassesClient (from SunglassesCore.create()). */
|
|
7
|
+
client: ISunglassesClient;
|
|
8
|
+
children: React$1.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Provides a SunGlasses client to the React Native component tree.
|
|
12
|
+
*
|
|
13
|
+
* Place this at the root of your Expo/RN application.
|
|
14
|
+
*
|
|
15
|
+
* For screen tracking, use one of these hooks inside your layout:
|
|
16
|
+
* - `useExpoRouterScreenTracking(client)` — for Expo Router
|
|
17
|
+
* - `useNavigationScreenTracking(client, navigationRef)` — for React Navigation
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // App.tsx (React Navigation)
|
|
22
|
+
* const client = await SunglassesCore.create({ ... });
|
|
23
|
+
*
|
|
24
|
+
* function App() {
|
|
25
|
+
* return (
|
|
26
|
+
* <SunglassesProvider client={client}>
|
|
27
|
+
* <NavigationContainer>
|
|
28
|
+
* <Stack.Navigator />
|
|
29
|
+
* </NavigationContainer>
|
|
30
|
+
* </SunglassesProvider>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // app/_layout.tsx (Expo Router)
|
|
38
|
+
* export default function RootLayout() {
|
|
39
|
+
* const client = useSunglasses();
|
|
40
|
+
* useExpoRouterScreenTracking(client);
|
|
41
|
+
* return <Stack />;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function SunglassesProvider({ client, children, }: SunglassesProviderProps): React$1.ReactElement;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Access the SunGlasses client from React context (React Native).
|
|
49
|
+
* Throws if called outside of a SunglassesProvider.
|
|
50
|
+
*/
|
|
51
|
+
declare function useSunglasses(): ISunglassesClient;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Expo Router screen tracking hook.
|
|
55
|
+
*
|
|
56
|
+
* Tracks screen changes by observing the `pathname` from Expo Router.
|
|
57
|
+
* Place this hook inside your root `_layout.tsx`.
|
|
58
|
+
*
|
|
59
|
+
* **Requires**: `expo-router` to be installed in your project.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```tsx
|
|
63
|
+
* // app/_layout.tsx
|
|
64
|
+
* import { useExpoRouterScreenTracking } from '@drakkar.software/sunglasses-react-native';
|
|
65
|
+
*
|
|
66
|
+
* export default function RootLayout() {
|
|
67
|
+
* const client = useSunglasses();
|
|
68
|
+
* useExpoRouterScreenTracking(client);
|
|
69
|
+
* return <Stack />;
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function useExpoRouterScreenTracking(client: ISunglassesClient, options?: Pick<ScreenTrackingOptions, 'screenNameMapper'>): void;
|
|
74
|
+
|
|
75
|
+
interface NavigationState {
|
|
76
|
+
index: number;
|
|
77
|
+
routes: Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
state?: NavigationState;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
interface NavigationContainerRef {
|
|
83
|
+
addListener(event: string, callback: () => void): {
|
|
84
|
+
remove: () => void;
|
|
85
|
+
};
|
|
86
|
+
getRootState(): NavigationState | undefined;
|
|
87
|
+
isReady(): boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* React Navigation screen tracking hook.
|
|
91
|
+
*
|
|
92
|
+
* Pass the `ref` from `<NavigationContainer ref={navigationRef}>`.
|
|
93
|
+
* Tracks screen changes via the `state` listener.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* const navigationRef = useRef(null);
|
|
98
|
+
*
|
|
99
|
+
* <NavigationContainer ref={navigationRef}>
|
|
100
|
+
* ...
|
|
101
|
+
* </NavigationContainer>
|
|
102
|
+
*
|
|
103
|
+
* // In a component:
|
|
104
|
+
* useNavigationScreenTracking(client, navigationRef);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function useNavigationScreenTracking(client: ISunglassesClient, navigationRef: React.RefObject<NavigationContainerRef | null>, options?: Pick<ScreenTrackingOptions, 'screenNameMapper'>): void;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract UTM attribution parameters from a deep link URL and register them
|
|
111
|
+
* as super properties on the client.
|
|
112
|
+
*
|
|
113
|
+
* Works with both HTTPS universal links (`https://myapp.com/home?utm_source=email`)
|
|
114
|
+
* and custom scheme deep links (`myapp://home?utm_source=email`).
|
|
115
|
+
*
|
|
116
|
+
* No-op if the URL contains no UTM parameters or if the URL cannot be parsed.
|
|
117
|
+
*
|
|
118
|
+
* @param client - The SunGlasses client instance.
|
|
119
|
+
* @param url - The deep link URL to extract UTM params from.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* // Handle a deep link manually
|
|
124
|
+
* Linking.getInitialURL().then((url) => {
|
|
125
|
+
* if (url) captureDeepLinkUtmParams(client, url);
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function captureDeepLinkUtmParams(client: ISunglassesClient, url: string): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Hook that captures UTM attribution from deep links via React Native's Linking API.
|
|
133
|
+
*
|
|
134
|
+
* Handles two cases:
|
|
135
|
+
* - **Cold start**: reads the initial URL that launched the app via `Linking.getInitialURL()`.
|
|
136
|
+
* - **Re-open**: subscribes to new deep links while the app is running via
|
|
137
|
+
* `Linking.addEventListener('url', ...)` — useful for re-attribution when the
|
|
138
|
+
* user taps a new campaign link while the app is already open.
|
|
139
|
+
*
|
|
140
|
+
* UTM params are registered as super properties via `client.register()`, so they
|
|
141
|
+
* are attached to all subsequent events automatically.
|
|
142
|
+
*
|
|
143
|
+
* Place this hook in your root component or app entry point.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* // App.tsx
|
|
148
|
+
* import { useLinkingUtmCapture } from '@drakkar.software/sunglasses-react-native';
|
|
149
|
+
*
|
|
150
|
+
* export default function App() {
|
|
151
|
+
* const client = useSunglasses();
|
|
152
|
+
* useLinkingUtmCapture(client);
|
|
153
|
+
* return <NavigationContainer>...</NavigationContainer>;
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
declare function useLinkingUtmCapture(client: ISunglassesClient): void;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Expo Router UTM capture hook.
|
|
161
|
+
*
|
|
162
|
+
* Reads UTM attribution params from the current Expo Router URL via
|
|
163
|
+
* `useGlobalSearchParams()` and registers any found params as super properties
|
|
164
|
+
* on the client. Re-runs whenever the URL params change, so new campaign links
|
|
165
|
+
* opened while the app is running are also captured.
|
|
166
|
+
*
|
|
167
|
+
* Uses `useGlobalSearchParams()` rather than `useLocalSearchParams()` so the
|
|
168
|
+
* hook works correctly from the root `_layout.tsx` — it sees params from any
|
|
169
|
+
* nested route, not just the current layout segment.
|
|
170
|
+
*
|
|
171
|
+
* **Requires**: `expo-router` to be installed in your project.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* // app/_layout.tsx
|
|
176
|
+
* import { useExpoRouterUtmCapture } from '@drakkar.software/sunglasses-react-native';
|
|
177
|
+
*
|
|
178
|
+
* export default function RootLayout() {
|
|
179
|
+
* const client = useSunglasses();
|
|
180
|
+
* useExpoRouterUtmCapture(client);
|
|
181
|
+
* return <Stack />;
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare function useExpoRouterUtmCapture(client: ISunglassesClient): void;
|
|
186
|
+
|
|
187
|
+
export { SunglassesProvider, type SunglassesProviderProps, captureDeepLinkUtmParams, useExpoRouterScreenTracking, useExpoRouterUtmCapture, useLinkingUtmCapture, useNavigationScreenTracking, useSunglasses };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
import { ISunglassesClient, ScreenTrackingOptions } from '@drakkar.software/sunglasses-core';
|
|
3
|
+
export { ConsentStatus, ISunglassesClient, ScreenTrackingOptions, SunglassesConfig, SunglassesCore, SunglassesEvent } from '@drakkar.software/sunglasses-core';
|
|
4
|
+
|
|
5
|
+
interface SunglassesProviderProps {
|
|
6
|
+
/** An initialized ISunglassesClient (from SunglassesCore.create()). */
|
|
7
|
+
client: ISunglassesClient;
|
|
8
|
+
children: React$1.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Provides a SunGlasses client to the React Native component tree.
|
|
12
|
+
*
|
|
13
|
+
* Place this at the root of your Expo/RN application.
|
|
14
|
+
*
|
|
15
|
+
* For screen tracking, use one of these hooks inside your layout:
|
|
16
|
+
* - `useExpoRouterScreenTracking(client)` — for Expo Router
|
|
17
|
+
* - `useNavigationScreenTracking(client, navigationRef)` — for React Navigation
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // App.tsx (React Navigation)
|
|
22
|
+
* const client = await SunglassesCore.create({ ... });
|
|
23
|
+
*
|
|
24
|
+
* function App() {
|
|
25
|
+
* return (
|
|
26
|
+
* <SunglassesProvider client={client}>
|
|
27
|
+
* <NavigationContainer>
|
|
28
|
+
* <Stack.Navigator />
|
|
29
|
+
* </NavigationContainer>
|
|
30
|
+
* </SunglassesProvider>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // app/_layout.tsx (Expo Router)
|
|
38
|
+
* export default function RootLayout() {
|
|
39
|
+
* const client = useSunglasses();
|
|
40
|
+
* useExpoRouterScreenTracking(client);
|
|
41
|
+
* return <Stack />;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function SunglassesProvider({ client, children, }: SunglassesProviderProps): React$1.ReactElement;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Access the SunGlasses client from React context (React Native).
|
|
49
|
+
* Throws if called outside of a SunglassesProvider.
|
|
50
|
+
*/
|
|
51
|
+
declare function useSunglasses(): ISunglassesClient;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Expo Router screen tracking hook.
|
|
55
|
+
*
|
|
56
|
+
* Tracks screen changes by observing the `pathname` from Expo Router.
|
|
57
|
+
* Place this hook inside your root `_layout.tsx`.
|
|
58
|
+
*
|
|
59
|
+
* **Requires**: `expo-router` to be installed in your project.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```tsx
|
|
63
|
+
* // app/_layout.tsx
|
|
64
|
+
* import { useExpoRouterScreenTracking } from '@drakkar.software/sunglasses-react-native';
|
|
65
|
+
*
|
|
66
|
+
* export default function RootLayout() {
|
|
67
|
+
* const client = useSunglasses();
|
|
68
|
+
* useExpoRouterScreenTracking(client);
|
|
69
|
+
* return <Stack />;
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function useExpoRouterScreenTracking(client: ISunglassesClient, options?: Pick<ScreenTrackingOptions, 'screenNameMapper'>): void;
|
|
74
|
+
|
|
75
|
+
interface NavigationState {
|
|
76
|
+
index: number;
|
|
77
|
+
routes: Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
state?: NavigationState;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
interface NavigationContainerRef {
|
|
83
|
+
addListener(event: string, callback: () => void): {
|
|
84
|
+
remove: () => void;
|
|
85
|
+
};
|
|
86
|
+
getRootState(): NavigationState | undefined;
|
|
87
|
+
isReady(): boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* React Navigation screen tracking hook.
|
|
91
|
+
*
|
|
92
|
+
* Pass the `ref` from `<NavigationContainer ref={navigationRef}>`.
|
|
93
|
+
* Tracks screen changes via the `state` listener.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* const navigationRef = useRef(null);
|
|
98
|
+
*
|
|
99
|
+
* <NavigationContainer ref={navigationRef}>
|
|
100
|
+
* ...
|
|
101
|
+
* </NavigationContainer>
|
|
102
|
+
*
|
|
103
|
+
* // In a component:
|
|
104
|
+
* useNavigationScreenTracking(client, navigationRef);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function useNavigationScreenTracking(client: ISunglassesClient, navigationRef: React.RefObject<NavigationContainerRef | null>, options?: Pick<ScreenTrackingOptions, 'screenNameMapper'>): void;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract UTM attribution parameters from a deep link URL and register them
|
|
111
|
+
* as super properties on the client.
|
|
112
|
+
*
|
|
113
|
+
* Works with both HTTPS universal links (`https://myapp.com/home?utm_source=email`)
|
|
114
|
+
* and custom scheme deep links (`myapp://home?utm_source=email`).
|
|
115
|
+
*
|
|
116
|
+
* No-op if the URL contains no UTM parameters or if the URL cannot be parsed.
|
|
117
|
+
*
|
|
118
|
+
* @param client - The SunGlasses client instance.
|
|
119
|
+
* @param url - The deep link URL to extract UTM params from.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* // Handle a deep link manually
|
|
124
|
+
* Linking.getInitialURL().then((url) => {
|
|
125
|
+
* if (url) captureDeepLinkUtmParams(client, url);
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function captureDeepLinkUtmParams(client: ISunglassesClient, url: string): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Hook that captures UTM attribution from deep links via React Native's Linking API.
|
|
133
|
+
*
|
|
134
|
+
* Handles two cases:
|
|
135
|
+
* - **Cold start**: reads the initial URL that launched the app via `Linking.getInitialURL()`.
|
|
136
|
+
* - **Re-open**: subscribes to new deep links while the app is running via
|
|
137
|
+
* `Linking.addEventListener('url', ...)` — useful for re-attribution when the
|
|
138
|
+
* user taps a new campaign link while the app is already open.
|
|
139
|
+
*
|
|
140
|
+
* UTM params are registered as super properties via `client.register()`, so they
|
|
141
|
+
* are attached to all subsequent events automatically.
|
|
142
|
+
*
|
|
143
|
+
* Place this hook in your root component or app entry point.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* // App.tsx
|
|
148
|
+
* import { useLinkingUtmCapture } from '@drakkar.software/sunglasses-react-native';
|
|
149
|
+
*
|
|
150
|
+
* export default function App() {
|
|
151
|
+
* const client = useSunglasses();
|
|
152
|
+
* useLinkingUtmCapture(client);
|
|
153
|
+
* return <NavigationContainer>...</NavigationContainer>;
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
declare function useLinkingUtmCapture(client: ISunglassesClient): void;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Expo Router UTM capture hook.
|
|
161
|
+
*
|
|
162
|
+
* Reads UTM attribution params from the current Expo Router URL via
|
|
163
|
+
* `useGlobalSearchParams()` and registers any found params as super properties
|
|
164
|
+
* on the client. Re-runs whenever the URL params change, so new campaign links
|
|
165
|
+
* opened while the app is running are also captured.
|
|
166
|
+
*
|
|
167
|
+
* Uses `useGlobalSearchParams()` rather than `useLocalSearchParams()` so the
|
|
168
|
+
* hook works correctly from the root `_layout.tsx` — it sees params from any
|
|
169
|
+
* nested route, not just the current layout segment.
|
|
170
|
+
*
|
|
171
|
+
* **Requires**: `expo-router` to be installed in your project.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* // app/_layout.tsx
|
|
176
|
+
* import { useExpoRouterUtmCapture } from '@drakkar.software/sunglasses-react-native';
|
|
177
|
+
*
|
|
178
|
+
* export default function RootLayout() {
|
|
179
|
+
* const client = useSunglasses();
|
|
180
|
+
* useExpoRouterUtmCapture(client);
|
|
181
|
+
* return <Stack />;
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare function useExpoRouterUtmCapture(client: ISunglassesClient): void;
|
|
186
|
+
|
|
187
|
+
export { SunglassesProvider, type SunglassesProviderProps, captureDeepLinkUtmParams, useExpoRouterScreenTracking, useExpoRouterUtmCapture, useLinkingUtmCapture, useNavigationScreenTracking, useSunglasses };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SunglassesCore: () => import_sunglasses_core.SunglassesCore,
|
|
24
|
+
SunglassesProvider: () => SunglassesProvider,
|
|
25
|
+
captureDeepLinkUtmParams: () => captureDeepLinkUtmParams,
|
|
26
|
+
useExpoRouterScreenTracking: () => useExpoRouterScreenTracking,
|
|
27
|
+
useExpoRouterUtmCapture: () => useExpoRouterUtmCapture,
|
|
28
|
+
useLinkingUtmCapture: () => useLinkingUtmCapture,
|
|
29
|
+
useNavigationScreenTracking: () => useNavigationScreenTracking,
|
|
30
|
+
useSunglasses: () => useSunglasses
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/SunglassesProvider.tsx
|
|
35
|
+
var import_react2 = require("react");
|
|
36
|
+
var import_react_native = require("react-native");
|
|
37
|
+
|
|
38
|
+
// src/context.ts
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var SunglassesContext = (0, import_react.createContext)(null);
|
|
41
|
+
function useSunglasses() {
|
|
42
|
+
const client = (0, import_react.useContext)(SunglassesContext);
|
|
43
|
+
if (client === null) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"[SunGlasses] useSunglasses() must be called inside a <SunglassesProvider>."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return client;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/SunglassesProvider.tsx
|
|
52
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
53
|
+
function SunglassesProvider({
|
|
54
|
+
client,
|
|
55
|
+
children
|
|
56
|
+
}) {
|
|
57
|
+
(0, import_react2.useEffect)(() => {
|
|
58
|
+
return () => {
|
|
59
|
+
client.shutdown().catch(() => {
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}, [client]);
|
|
63
|
+
(0, import_react2.useEffect)(() => {
|
|
64
|
+
const subscription = import_react_native.AppState.addEventListener("change", (nextState) => {
|
|
65
|
+
if (nextState === "background") {
|
|
66
|
+
client.flush().catch(() => {
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return () => subscription.remove();
|
|
71
|
+
}, [client]);
|
|
72
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SunglassesContext.Provider, { value: client, children });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/useExpoRouterScreenTracking.ts
|
|
76
|
+
var import_react3 = require("react");
|
|
77
|
+
function useExpoRouterScreenTracking(client, options = {}) {
|
|
78
|
+
let pathname;
|
|
79
|
+
try {
|
|
80
|
+
const { usePathname } = require("expo-router");
|
|
81
|
+
pathname = usePathname();
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const { screenNameMapper } = options;
|
|
86
|
+
(0, import_react3.useEffect)(() => {
|
|
87
|
+
if (!pathname) return;
|
|
88
|
+
const name = screenNameMapper ? screenNameMapper(pathname) : pathname;
|
|
89
|
+
client.screen(name, { $path: pathname });
|
|
90
|
+
}, [pathname, screenNameMapper, client]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/useNavigationScreenTracking.ts
|
|
94
|
+
var import_react4 = require("react");
|
|
95
|
+
function getActiveRouteName(state) {
|
|
96
|
+
if (!state) return "";
|
|
97
|
+
const route = state.routes[state.index];
|
|
98
|
+
if (!route) return "";
|
|
99
|
+
if (route.state) return getActiveRouteName(route.state);
|
|
100
|
+
return route.name;
|
|
101
|
+
}
|
|
102
|
+
function useNavigationScreenTracking(client, navigationRef, options = {}) {
|
|
103
|
+
const clientRef = (0, import_react4.useRef)(client);
|
|
104
|
+
clientRef.current = client;
|
|
105
|
+
const { screenNameMapper } = options;
|
|
106
|
+
(0, import_react4.useEffect)(() => {
|
|
107
|
+
const ref = navigationRef.current;
|
|
108
|
+
if (!ref) return;
|
|
109
|
+
const handleStateChange = () => {
|
|
110
|
+
const state = ref.getRootState();
|
|
111
|
+
const routeName = getActiveRouteName(state);
|
|
112
|
+
if (!routeName) return;
|
|
113
|
+
const name = screenNameMapper ? screenNameMapper(routeName) : routeName;
|
|
114
|
+
clientRef.current.screen(name, { $route: routeName });
|
|
115
|
+
};
|
|
116
|
+
const stateListener = ref.addListener("state", handleStateChange);
|
|
117
|
+
return () => {
|
|
118
|
+
stateListener.remove();
|
|
119
|
+
};
|
|
120
|
+
}, [navigationRef, screenNameMapper]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/captureDeepLinkUtmParams.ts
|
|
124
|
+
var UTM_PARAMS = [
|
|
125
|
+
"utm_source",
|
|
126
|
+
"utm_medium",
|
|
127
|
+
"utm_campaign",
|
|
128
|
+
"utm_content",
|
|
129
|
+
"utm_term"
|
|
130
|
+
];
|
|
131
|
+
function captureDeepLinkUtmParams(client, url) {
|
|
132
|
+
const params = {};
|
|
133
|
+
try {
|
|
134
|
+
const queryStart = url.indexOf("?");
|
|
135
|
+
if (queryStart === -1) return;
|
|
136
|
+
let qs = url.slice(queryStart + 1);
|
|
137
|
+
const hashIdx = qs.indexOf("#");
|
|
138
|
+
if (hashIdx !== -1) qs = qs.slice(0, hashIdx);
|
|
139
|
+
const searchParams = new URLSearchParams(qs);
|
|
140
|
+
for (const key of UTM_PARAMS) {
|
|
141
|
+
const value = searchParams.get(key);
|
|
142
|
+
if (value) params[key] = value;
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (Object.keys(params).length > 0) {
|
|
148
|
+
client.register(params);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/useLinkingUtmCapture.ts
|
|
153
|
+
var import_react5 = require("react");
|
|
154
|
+
var import_react_native2 = require("react-native");
|
|
155
|
+
function useLinkingUtmCapture(client) {
|
|
156
|
+
(0, import_react5.useEffect)(() => {
|
|
157
|
+
import_react_native2.Linking.getInitialURL().then((url) => {
|
|
158
|
+
if (url) captureDeepLinkUtmParams(client, url);
|
|
159
|
+
}).catch(() => {
|
|
160
|
+
});
|
|
161
|
+
const subscription = import_react_native2.Linking.addEventListener("url", ({ url }) => {
|
|
162
|
+
captureDeepLinkUtmParams(client, url);
|
|
163
|
+
});
|
|
164
|
+
return () => {
|
|
165
|
+
subscription.remove();
|
|
166
|
+
};
|
|
167
|
+
}, [client]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/useExpoRouterUtmCapture.ts
|
|
171
|
+
var import_react6 = require("react");
|
|
172
|
+
|
|
173
|
+
// src/expoRouterCompat.ts
|
|
174
|
+
var _useGlobalSearchParams = null;
|
|
175
|
+
try {
|
|
176
|
+
({ useGlobalSearchParams: _useGlobalSearchParams } = require("expo-router"));
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
var useGlobalSearchParams = _useGlobalSearchParams;
|
|
180
|
+
|
|
181
|
+
// src/useExpoRouterUtmCapture.ts
|
|
182
|
+
function useExpoRouterUtmCapture(client) {
|
|
183
|
+
if (!useGlobalSearchParams) return;
|
|
184
|
+
const params = useGlobalSearchParams();
|
|
185
|
+
(0, import_react6.useEffect)(() => {
|
|
186
|
+
const utmParams = {};
|
|
187
|
+
for (const key of UTM_PARAMS) {
|
|
188
|
+
const value = params[key];
|
|
189
|
+
if (typeof value === "string" && value) {
|
|
190
|
+
utmParams[key] = value;
|
|
191
|
+
} else if (Array.isArray(value) && value[0]) {
|
|
192
|
+
utmParams[key] = value[0];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (Object.keys(utmParams).length > 0) {
|
|
196
|
+
client.register(utmParams);
|
|
197
|
+
}
|
|
198
|
+
}, [params, client]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/index.ts
|
|
202
|
+
var import_sunglasses_core = require("@drakkar.software/sunglasses-core");
|
|
203
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
204
|
+
0 && (module.exports = {
|
|
205
|
+
SunglassesCore,
|
|
206
|
+
SunglassesProvider,
|
|
207
|
+
captureDeepLinkUtmParams,
|
|
208
|
+
useExpoRouterScreenTracking,
|
|
209
|
+
useExpoRouterUtmCapture,
|
|
210
|
+
useLinkingUtmCapture,
|
|
211
|
+
useNavigationScreenTracking,
|
|
212
|
+
useSunglasses
|
|
213
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/SunglassesProvider.tsx
|
|
9
|
+
import { useEffect } from "react";
|
|
10
|
+
import { AppState } from "react-native";
|
|
11
|
+
|
|
12
|
+
// src/context.ts
|
|
13
|
+
import { createContext, useContext } from "react";
|
|
14
|
+
var SunglassesContext = createContext(null);
|
|
15
|
+
function useSunglasses() {
|
|
16
|
+
const client = useContext(SunglassesContext);
|
|
17
|
+
if (client === null) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"[SunGlasses] useSunglasses() must be called inside a <SunglassesProvider>."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/SunglassesProvider.tsx
|
|
26
|
+
import { jsx } from "react/jsx-runtime";
|
|
27
|
+
function SunglassesProvider({
|
|
28
|
+
client,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
return () => {
|
|
33
|
+
client.shutdown().catch(() => {
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}, [client]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const subscription = AppState.addEventListener("change", (nextState) => {
|
|
39
|
+
if (nextState === "background") {
|
|
40
|
+
client.flush().catch(() => {
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return () => subscription.remove();
|
|
45
|
+
}, [client]);
|
|
46
|
+
return /* @__PURE__ */ jsx(SunglassesContext.Provider, { value: client, children });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/useExpoRouterScreenTracking.ts
|
|
50
|
+
import { useEffect as useEffect2 } from "react";
|
|
51
|
+
function useExpoRouterScreenTracking(client, options = {}) {
|
|
52
|
+
let pathname;
|
|
53
|
+
try {
|
|
54
|
+
const { usePathname } = __require("expo-router");
|
|
55
|
+
pathname = usePathname();
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { screenNameMapper } = options;
|
|
60
|
+
useEffect2(() => {
|
|
61
|
+
if (!pathname) return;
|
|
62
|
+
const name = screenNameMapper ? screenNameMapper(pathname) : pathname;
|
|
63
|
+
client.screen(name, { $path: pathname });
|
|
64
|
+
}, [pathname, screenNameMapper, client]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/useNavigationScreenTracking.ts
|
|
68
|
+
import { useEffect as useEffect3, useRef } from "react";
|
|
69
|
+
function getActiveRouteName(state) {
|
|
70
|
+
if (!state) return "";
|
|
71
|
+
const route = state.routes[state.index];
|
|
72
|
+
if (!route) return "";
|
|
73
|
+
if (route.state) return getActiveRouteName(route.state);
|
|
74
|
+
return route.name;
|
|
75
|
+
}
|
|
76
|
+
function useNavigationScreenTracking(client, navigationRef, options = {}) {
|
|
77
|
+
const clientRef = useRef(client);
|
|
78
|
+
clientRef.current = client;
|
|
79
|
+
const { screenNameMapper } = options;
|
|
80
|
+
useEffect3(() => {
|
|
81
|
+
const ref = navigationRef.current;
|
|
82
|
+
if (!ref) return;
|
|
83
|
+
const handleStateChange = () => {
|
|
84
|
+
const state = ref.getRootState();
|
|
85
|
+
const routeName = getActiveRouteName(state);
|
|
86
|
+
if (!routeName) return;
|
|
87
|
+
const name = screenNameMapper ? screenNameMapper(routeName) : routeName;
|
|
88
|
+
clientRef.current.screen(name, { $route: routeName });
|
|
89
|
+
};
|
|
90
|
+
const stateListener = ref.addListener("state", handleStateChange);
|
|
91
|
+
return () => {
|
|
92
|
+
stateListener.remove();
|
|
93
|
+
};
|
|
94
|
+
}, [navigationRef, screenNameMapper]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/captureDeepLinkUtmParams.ts
|
|
98
|
+
var UTM_PARAMS = [
|
|
99
|
+
"utm_source",
|
|
100
|
+
"utm_medium",
|
|
101
|
+
"utm_campaign",
|
|
102
|
+
"utm_content",
|
|
103
|
+
"utm_term"
|
|
104
|
+
];
|
|
105
|
+
function captureDeepLinkUtmParams(client, url) {
|
|
106
|
+
const params = {};
|
|
107
|
+
try {
|
|
108
|
+
const queryStart = url.indexOf("?");
|
|
109
|
+
if (queryStart === -1) return;
|
|
110
|
+
let qs = url.slice(queryStart + 1);
|
|
111
|
+
const hashIdx = qs.indexOf("#");
|
|
112
|
+
if (hashIdx !== -1) qs = qs.slice(0, hashIdx);
|
|
113
|
+
const searchParams = new URLSearchParams(qs);
|
|
114
|
+
for (const key of UTM_PARAMS) {
|
|
115
|
+
const value = searchParams.get(key);
|
|
116
|
+
if (value) params[key] = value;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (Object.keys(params).length > 0) {
|
|
122
|
+
client.register(params);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/useLinkingUtmCapture.ts
|
|
127
|
+
import { useEffect as useEffect4 } from "react";
|
|
128
|
+
import { Linking } from "react-native";
|
|
129
|
+
function useLinkingUtmCapture(client) {
|
|
130
|
+
useEffect4(() => {
|
|
131
|
+
Linking.getInitialURL().then((url) => {
|
|
132
|
+
if (url) captureDeepLinkUtmParams(client, url);
|
|
133
|
+
}).catch(() => {
|
|
134
|
+
});
|
|
135
|
+
const subscription = Linking.addEventListener("url", ({ url }) => {
|
|
136
|
+
captureDeepLinkUtmParams(client, url);
|
|
137
|
+
});
|
|
138
|
+
return () => {
|
|
139
|
+
subscription.remove();
|
|
140
|
+
};
|
|
141
|
+
}, [client]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/useExpoRouterUtmCapture.ts
|
|
145
|
+
import { useEffect as useEffect5 } from "react";
|
|
146
|
+
|
|
147
|
+
// src/expoRouterCompat.ts
|
|
148
|
+
var _useGlobalSearchParams = null;
|
|
149
|
+
try {
|
|
150
|
+
({ useGlobalSearchParams: _useGlobalSearchParams } = __require("expo-router"));
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
var useGlobalSearchParams = _useGlobalSearchParams;
|
|
154
|
+
|
|
155
|
+
// src/useExpoRouterUtmCapture.ts
|
|
156
|
+
function useExpoRouterUtmCapture(client) {
|
|
157
|
+
if (!useGlobalSearchParams) return;
|
|
158
|
+
const params = useGlobalSearchParams();
|
|
159
|
+
useEffect5(() => {
|
|
160
|
+
const utmParams = {};
|
|
161
|
+
for (const key of UTM_PARAMS) {
|
|
162
|
+
const value = params[key];
|
|
163
|
+
if (typeof value === "string" && value) {
|
|
164
|
+
utmParams[key] = value;
|
|
165
|
+
} else if (Array.isArray(value) && value[0]) {
|
|
166
|
+
utmParams[key] = value[0];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (Object.keys(utmParams).length > 0) {
|
|
170
|
+
client.register(utmParams);
|
|
171
|
+
}
|
|
172
|
+
}, [params, client]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/index.ts
|
|
176
|
+
import { SunglassesCore } from "@drakkar.software/sunglasses-core";
|
|
177
|
+
export {
|
|
178
|
+
SunglassesCore,
|
|
179
|
+
SunglassesProvider,
|
|
180
|
+
captureDeepLinkUtmParams,
|
|
181
|
+
useExpoRouterScreenTracking,
|
|
182
|
+
useExpoRouterUtmCapture,
|
|
183
|
+
useLinkingUtmCapture,
|
|
184
|
+
useNavigationScreenTracking,
|
|
185
|
+
useSunglasses
|
|
186
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drakkar.software/sunglasses-react-native",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "React Native / Expo provider and hooks for SunGlasses event tracking",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@drakkar.software/sunglasses-core": "0.2.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"expo-router": ">=3.0.0",
|
|
23
|
+
"react": ">=18.0.0",
|
|
24
|
+
"react-native": ">=0.73.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"expo-router": {
|
|
28
|
+
"optional": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^18.3.12",
|
|
33
|
+
"happy-dom": "^15.11.7",
|
|
34
|
+
"react": "^18.3.1",
|
|
35
|
+
"react-dom": "^18.3.1",
|
|
36
|
+
"tsup": "^8.3.5",
|
|
37
|
+
"typescript": "^5.7.2",
|
|
38
|
+
"vitest": "^2.1.8",
|
|
39
|
+
"@drakkar.software/sunglasses-tsconfig": "0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
43
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"lint": "eslint src/",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
48
|
+
}
|
|
49
|
+
}
|