@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.
@@ -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 };
@@ -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
+ }