@litemetrics/react-native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +224 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +200 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
LitemetricsProvider: () => LitemetricsProvider,
|
|
24
|
+
createRNTracker: () => createRNTracker,
|
|
25
|
+
useAppStateTracking: () => useAppStateTracking,
|
|
26
|
+
useLitemetrics: () => useLitemetrics,
|
|
27
|
+
useNavigationTracking: () => useNavigationTracking
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/context.tsx
|
|
32
|
+
var import_react = require("react");
|
|
33
|
+
|
|
34
|
+
// src/tracker.ts
|
|
35
|
+
var sessionId = null;
|
|
36
|
+
var visitorId = null;
|
|
37
|
+
var userId = null;
|
|
38
|
+
function generateId() {
|
|
39
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
40
|
+
const r = Math.random() * 16 | 0;
|
|
41
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
42
|
+
return v.toString(16);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function createRNTracker(config) {
|
|
46
|
+
const { siteId, endpoint, debug = false } = config;
|
|
47
|
+
if (!sessionId) sessionId = generateId();
|
|
48
|
+
if (!visitorId) visitorId = generateId().slice(0, 16);
|
|
49
|
+
const queue = [];
|
|
50
|
+
let flushTimer = null;
|
|
51
|
+
function getContext() {
|
|
52
|
+
return {
|
|
53
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function flush() {
|
|
57
|
+
if (queue.length === 0) return;
|
|
58
|
+
const events = queue.splice(0);
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log("[litemetrics:rn] sending", events.length, "events");
|
|
61
|
+
}
|
|
62
|
+
fetch(endpoint, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({ events })
|
|
66
|
+
}).catch((err) => {
|
|
67
|
+
if (debug) console.warn("[litemetrics:rn] send failed", err);
|
|
68
|
+
queue.unshift(...events);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function send(event) {
|
|
72
|
+
queue.push(event);
|
|
73
|
+
if (queue.length >= (config.batchSize ?? 10)) {
|
|
74
|
+
flush();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
flushTimer = setInterval(flush, config.flushInterval ?? 5e3);
|
|
78
|
+
return {
|
|
79
|
+
track(name, properties) {
|
|
80
|
+
const event = {
|
|
81
|
+
type: "event",
|
|
82
|
+
siteId,
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
sessionId,
|
|
85
|
+
visitorId,
|
|
86
|
+
name,
|
|
87
|
+
properties,
|
|
88
|
+
...getContext()
|
|
89
|
+
};
|
|
90
|
+
if (userId) event.userId = userId;
|
|
91
|
+
send(event);
|
|
92
|
+
},
|
|
93
|
+
identify(id, traits) {
|
|
94
|
+
userId = id;
|
|
95
|
+
const event = {
|
|
96
|
+
type: "identify",
|
|
97
|
+
siteId,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
sessionId,
|
|
100
|
+
visitorId,
|
|
101
|
+
userId: id,
|
|
102
|
+
traits,
|
|
103
|
+
...getContext()
|
|
104
|
+
};
|
|
105
|
+
send(event);
|
|
106
|
+
},
|
|
107
|
+
page(screenName) {
|
|
108
|
+
const event = {
|
|
109
|
+
type: "pageview",
|
|
110
|
+
siteId,
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
sessionId,
|
|
113
|
+
visitorId,
|
|
114
|
+
url: screenName,
|
|
115
|
+
title: screenName,
|
|
116
|
+
...getContext()
|
|
117
|
+
};
|
|
118
|
+
if (userId) event.userId = userId;
|
|
119
|
+
send(event);
|
|
120
|
+
},
|
|
121
|
+
reset() {
|
|
122
|
+
sessionId = generateId();
|
|
123
|
+
visitorId = generateId().slice(0, 16);
|
|
124
|
+
userId = null;
|
|
125
|
+
},
|
|
126
|
+
destroy() {
|
|
127
|
+
flush();
|
|
128
|
+
if (flushTimer) {
|
|
129
|
+
clearInterval(flushTimer);
|
|
130
|
+
flushTimer = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/context.tsx
|
|
137
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
138
|
+
var LitemetricsRNContext = (0, import_react.createContext)(null);
|
|
139
|
+
function LitemetricsProvider({ children, ...config }) {
|
|
140
|
+
const trackerRef = (0, import_react.useRef)(null);
|
|
141
|
+
if (!trackerRef.current) {
|
|
142
|
+
trackerRef.current = createRNTracker(config);
|
|
143
|
+
}
|
|
144
|
+
(0, import_react.useEffect)(() => {
|
|
145
|
+
return () => {
|
|
146
|
+
trackerRef.current?.destroy();
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
const value = (0, import_react.useMemo)(
|
|
150
|
+
() => ({
|
|
151
|
+
tracker: trackerRef.current,
|
|
152
|
+
siteId: config.siteId
|
|
153
|
+
}),
|
|
154
|
+
[config.siteId]
|
|
155
|
+
);
|
|
156
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LitemetricsRNContext.Provider, { value, children });
|
|
157
|
+
}
|
|
158
|
+
function useLitemetricsRNContext() {
|
|
159
|
+
const ctx = (0, import_react.useContext)(LitemetricsRNContext);
|
|
160
|
+
if (!ctx) {
|
|
161
|
+
throw new Error("useLitemetrics must be used within <LitemetricsProvider>");
|
|
162
|
+
}
|
|
163
|
+
return ctx;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/hooks.ts
|
|
167
|
+
var import_react2 = require("react");
|
|
168
|
+
function useLitemetrics() {
|
|
169
|
+
const { tracker } = useLitemetricsRNContext();
|
|
170
|
+
return {
|
|
171
|
+
track: tracker.track.bind(tracker),
|
|
172
|
+
identify: tracker.identify.bind(tracker),
|
|
173
|
+
page: tracker.page.bind(tracker),
|
|
174
|
+
reset: tracker.reset.bind(tracker)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function useNavigationTracking() {
|
|
178
|
+
const { tracker } = useLitemetricsRNContext();
|
|
179
|
+
const routeNameRef = (0, import_react2.useRef)(void 0);
|
|
180
|
+
const navigationRef = (0, import_react2.useRef)(null);
|
|
181
|
+
const onStateChange = (0, import_react2.useCallback)(() => {
|
|
182
|
+
const currentRoute = navigationRef.current?.getCurrentRoute?.();
|
|
183
|
+
const currentRouteName = currentRoute?.name;
|
|
184
|
+
if (currentRouteName && currentRouteName !== routeNameRef.current) {
|
|
185
|
+
routeNameRef.current = currentRouteName;
|
|
186
|
+
tracker.page(currentRouteName);
|
|
187
|
+
}
|
|
188
|
+
}, [tracker]);
|
|
189
|
+
return { onStateChange, navigationRef };
|
|
190
|
+
}
|
|
191
|
+
function useAppStateTracking() {
|
|
192
|
+
const { tracker } = useLitemetricsRNContext();
|
|
193
|
+
const appStateRef = (0, import_react2.useRef)("active");
|
|
194
|
+
(0, import_react2.useEffect)(() => {
|
|
195
|
+
let AppState;
|
|
196
|
+
let subscription;
|
|
197
|
+
try {
|
|
198
|
+
AppState = require("react-native").AppState;
|
|
199
|
+
} catch {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
appStateRef.current = AppState.currentState;
|
|
203
|
+
subscription = AppState.addEventListener("change", (nextState) => {
|
|
204
|
+
if (appStateRef.current.match(/inactive|background/) && nextState === "active") {
|
|
205
|
+
tracker.track("app_foreground");
|
|
206
|
+
} else if (nextState.match(/inactive|background/)) {
|
|
207
|
+
tracker.track("app_background");
|
|
208
|
+
}
|
|
209
|
+
appStateRef.current = nextState;
|
|
210
|
+
});
|
|
211
|
+
return () => {
|
|
212
|
+
subscription?.remove();
|
|
213
|
+
};
|
|
214
|
+
}, [tracker]);
|
|
215
|
+
}
|
|
216
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
217
|
+
0 && (module.exports = {
|
|
218
|
+
LitemetricsProvider,
|
|
219
|
+
createRNTracker,
|
|
220
|
+
useAppStateTracking,
|
|
221
|
+
useLitemetrics,
|
|
222
|
+
useNavigationTracking
|
|
223
|
+
});
|
|
224
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/tracker.ts","../src/hooks.ts"],"sourcesContent":["export { LitemetricsProvider } from './context';\nexport type { LitemetricsProviderProps } from './context';\nexport { useLitemetrics, useNavigationTracking, useAppStateTracking } from './hooks';\nexport { createRNTracker } from './tracker';\nexport type { RNTrackerInstance } from './tracker';\n","import { createContext, useContext, useEffect, useRef, useMemo } from \"react\";\nimport type { TrackerConfig } from \"@litemetrics/core\";\nimport { createRNTracker, type RNTrackerInstance } from \"./tracker\";\n\ninterface LitemetricsRNContextValue {\n tracker: RNTrackerInstance;\n siteId: string;\n}\n\nconst LitemetricsRNContext = createContext<LitemetricsRNContextValue | null>(null);\n\nexport interface LitemetricsProviderProps\n extends Omit<TrackerConfig, \"autoTrack\" | \"autoSpa\"> {\n children: React.ReactNode;\n}\n\nexport function LitemetricsProvider({ children, ...config }: LitemetricsProviderProps) {\n const trackerRef = useRef<RNTrackerInstance | null>(null);\n\n if (!trackerRef.current) {\n trackerRef.current = createRNTracker(config);\n }\n\n useEffect(() => {\n return () => {\n trackerRef.current?.destroy();\n };\n }, []);\n\n const value = useMemo<LitemetricsRNContextValue>(\n () => ({\n tracker: trackerRef.current!,\n siteId: config.siteId,\n }),\n [config.siteId]\n );\n\n return (\n <LitemetricsRNContext.Provider value={value}>\n {children}\n </LitemetricsRNContext.Provider>\n );\n}\n\nexport function useLitemetricsRNContext(): LitemetricsRNContextValue {\n const ctx = useContext(LitemetricsRNContext);\n if (!ctx) {\n throw new Error(\"useLitemetrics must be used within <LitemetricsProvider>\");\n }\n return ctx;\n}\n","import type {\n TrackerConfig,\n ClientEvent,\n PageviewEvent,\n CustomEvent,\n IdentifyEvent,\n ClientContext,\n} from '@litemetrics/core';\n\nexport interface RNTrackerInstance {\n track(name: string, properties?: Record<string, unknown>): void;\n identify(userId: string, traits?: Record<string, unknown>): void;\n page(screenName: string): void;\n reset(): void;\n destroy(): void;\n}\n\nlet sessionId: string | null = null;\nlet visitorId: string | null = null;\nlet userId: string | null = null;\n\nfunction generateId(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function createRNTracker(config: TrackerConfig): RNTrackerInstance {\n const { siteId, endpoint, debug = false } = config;\n\n if (!sessionId) sessionId = generateId();\n if (!visitorId) visitorId = generateId().slice(0, 16);\n\n const queue: ClientEvent[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n function getContext(): ClientContext {\n return {\n timezone:\n typeof Intl !== 'undefined'\n ? Intl.DateTimeFormat().resolvedOptions().timeZone\n : undefined,\n };\n }\n\n function flush() {\n if (queue.length === 0) return;\n const events = queue.splice(0);\n\n if (debug) {\n console.log('[litemetrics:rn] sending', events.length, 'events');\n }\n\n fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ events }),\n }).catch((err) => {\n if (debug) console.warn('[litemetrics:rn] send failed', err);\n // Push back to queue for retry\n queue.unshift(...events);\n });\n }\n\n function send(event: ClientEvent) {\n queue.push(event);\n if (queue.length >= (config.batchSize ?? 10)) {\n flush();\n }\n }\n\n // Start flush timer\n flushTimer = setInterval(flush, config.flushInterval ?? 5000);\n\n return {\n track(name: string, properties?: Record<string, unknown>) {\n const event: CustomEvent & ClientContext = {\n type: 'event',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n name,\n properties,\n ...getContext(),\n };\n if (userId) (event as any).userId = userId;\n send(event);\n },\n\n identify(id: string, traits?: Record<string, unknown>) {\n userId = id;\n const event: IdentifyEvent & ClientContext = {\n type: 'identify',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n userId: id,\n traits,\n ...getContext(),\n };\n send(event);\n },\n\n page(screenName: string) {\n const event: PageviewEvent & ClientContext = {\n type: 'pageview',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n url: screenName,\n title: screenName,\n ...getContext(),\n };\n if (userId) (event as any).userId = userId;\n send(event);\n },\n\n reset() {\n sessionId = generateId();\n visitorId = generateId().slice(0, 16);\n userId = null;\n },\n\n destroy() {\n flush();\n if (flushTimer) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n },\n };\n}\n","import { useEffect, useRef, useCallback } from 'react';\nimport { useLitemetricsRNContext } from './context';\n\n/**\n * Access the Litemetrics tracker instance in React Native.\n *\n * @example\n * ```tsx\n * function Button() {\n * const { track } = useLitemetrics();\n * return <Pressable onPress={() => track('tap', { screen: 'home' })}><Text>Tap</Text></Pressable>;\n * }\n * ```\n */\nexport function useLitemetrics() {\n const { tracker } = useLitemetricsRNContext();\n\n return {\n track: tracker.track.bind(tracker),\n identify: tracker.identify.bind(tracker),\n page: tracker.page.bind(tracker),\n reset: tracker.reset.bind(tracker),\n };\n}\n\n/**\n * Track React Navigation screen changes.\n * Returns `onStateChange` and `ref` props for NavigationContainer.\n *\n * @example\n * ```tsx\n * import { NavigationContainer } from '@react-navigation/native';\n *\n * function App() {\n * const { onStateChange, navigationRef } = useNavigationTracking();\n * return (\n * <LitemetricsProvider siteId=\"xxx\" endpoint=\"https://api.example.com/collect\">\n * <NavigationContainer ref={navigationRef} onStateChange={onStateChange}>\n * <Stack.Navigator>...</Stack.Navigator>\n * </NavigationContainer>\n * </LitemetricsProvider>\n * );\n * }\n * ```\n */\nexport function useNavigationTracking() {\n const { tracker } = useLitemetricsRNContext();\n const routeNameRef = useRef<string | undefined>(undefined);\n const navigationRef = useRef<any>(null);\n\n const onStateChange = useCallback(() => {\n const currentRoute = navigationRef.current?.getCurrentRoute?.();\n const currentRouteName = currentRoute?.name;\n\n if (currentRouteName && currentRouteName !== routeNameRef.current) {\n routeNameRef.current = currentRouteName;\n tracker.page(currentRouteName);\n }\n }, [tracker]);\n\n return { onStateChange, navigationRef };\n}\n\n/**\n * Track app foreground/background state changes.\n * Sends events when the app goes to background and comes back.\n *\n * @example\n * ```tsx\n * function App() {\n * useAppStateTracking();\n * return <MainScreen />;\n * }\n * ```\n */\nexport function useAppStateTracking() {\n const { tracker } = useLitemetricsRNContext();\n const appStateRef = useRef<string>('active');\n\n useEffect(() => {\n let AppState: any;\n let subscription: any;\n\n // Dynamic import to avoid crash if not in RN environment\n try {\n AppState = require('react-native').AppState;\n } catch {\n return;\n }\n\n appStateRef.current = AppState.currentState;\n\n subscription = AppState.addEventListener('change', (nextState: string) => {\n if (appStateRef.current.match(/inactive|background/) && nextState === 'active') {\n tracker.track('app_foreground');\n } else if (nextState.match(/inactive|background/)) {\n tracker.track('app_background');\n }\n appStateRef.current = nextState;\n });\n\n return () => {\n subscription?.remove();\n };\n }, [tracker]);\n}\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { TrackerConfig } from '@litemetrics/core';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
|
|
5
|
+
interface RNTrackerInstance {
|
|
6
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
7
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
8
|
+
page(screenName: string): void;
|
|
9
|
+
reset(): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
}
|
|
12
|
+
declare function createRNTracker(config: TrackerConfig): RNTrackerInstance;
|
|
13
|
+
|
|
14
|
+
interface LitemetricsProviderProps extends Omit<TrackerConfig, "autoTrack" | "autoSpa"> {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
declare function LitemetricsProvider({ children, ...config }: LitemetricsProviderProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Access the Litemetrics tracker instance in React Native.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* function Button() {
|
|
25
|
+
* const { track } = useLitemetrics();
|
|
26
|
+
* return <Pressable onPress={() => track('tap', { screen: 'home' })}><Text>Tap</Text></Pressable>;
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function useLitemetrics(): {
|
|
31
|
+
track: (name: string, properties?: Record<string, unknown>) => void;
|
|
32
|
+
identify: (userId: string, traits?: Record<string, unknown>) => void;
|
|
33
|
+
page: (screenName: string) => void;
|
|
34
|
+
reset: () => void;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Track React Navigation screen changes.
|
|
38
|
+
* Returns `onStateChange` and `ref` props for NavigationContainer.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* import { NavigationContainer } from '@react-navigation/native';
|
|
43
|
+
*
|
|
44
|
+
* function App() {
|
|
45
|
+
* const { onStateChange, navigationRef } = useNavigationTracking();
|
|
46
|
+
* return (
|
|
47
|
+
* <LitemetricsProvider siteId="xxx" endpoint="https://api.example.com/collect">
|
|
48
|
+
* <NavigationContainer ref={navigationRef} onStateChange={onStateChange}>
|
|
49
|
+
* <Stack.Navigator>...</Stack.Navigator>
|
|
50
|
+
* </NavigationContainer>
|
|
51
|
+
* </LitemetricsProvider>
|
|
52
|
+
* );
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function useNavigationTracking(): {
|
|
57
|
+
onStateChange: () => void;
|
|
58
|
+
navigationRef: react.RefObject<any>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Track app foreground/background state changes.
|
|
62
|
+
* Sends events when the app goes to background and comes back.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* function App() {
|
|
67
|
+
* useAppStateTracking();
|
|
68
|
+
* return <MainScreen />;
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function useAppStateTracking(): void;
|
|
73
|
+
|
|
74
|
+
export { LitemetricsProvider, type LitemetricsProviderProps, type RNTrackerInstance, createRNTracker, useAppStateTracking, useLitemetrics, useNavigationTracking };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { TrackerConfig } from '@litemetrics/core';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
|
|
5
|
+
interface RNTrackerInstance {
|
|
6
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
7
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
8
|
+
page(screenName: string): void;
|
|
9
|
+
reset(): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
}
|
|
12
|
+
declare function createRNTracker(config: TrackerConfig): RNTrackerInstance;
|
|
13
|
+
|
|
14
|
+
interface LitemetricsProviderProps extends Omit<TrackerConfig, "autoTrack" | "autoSpa"> {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
declare function LitemetricsProvider({ children, ...config }: LitemetricsProviderProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Access the Litemetrics tracker instance in React Native.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* function Button() {
|
|
25
|
+
* const { track } = useLitemetrics();
|
|
26
|
+
* return <Pressable onPress={() => track('tap', { screen: 'home' })}><Text>Tap</Text></Pressable>;
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function useLitemetrics(): {
|
|
31
|
+
track: (name: string, properties?: Record<string, unknown>) => void;
|
|
32
|
+
identify: (userId: string, traits?: Record<string, unknown>) => void;
|
|
33
|
+
page: (screenName: string) => void;
|
|
34
|
+
reset: () => void;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Track React Navigation screen changes.
|
|
38
|
+
* Returns `onStateChange` and `ref` props for NavigationContainer.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* import { NavigationContainer } from '@react-navigation/native';
|
|
43
|
+
*
|
|
44
|
+
* function App() {
|
|
45
|
+
* const { onStateChange, navigationRef } = useNavigationTracking();
|
|
46
|
+
* return (
|
|
47
|
+
* <LitemetricsProvider siteId="xxx" endpoint="https://api.example.com/collect">
|
|
48
|
+
* <NavigationContainer ref={navigationRef} onStateChange={onStateChange}>
|
|
49
|
+
* <Stack.Navigator>...</Stack.Navigator>
|
|
50
|
+
* </NavigationContainer>
|
|
51
|
+
* </LitemetricsProvider>
|
|
52
|
+
* );
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function useNavigationTracking(): {
|
|
57
|
+
onStateChange: () => void;
|
|
58
|
+
navigationRef: react.RefObject<any>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Track app foreground/background state changes.
|
|
62
|
+
* Sends events when the app goes to background and comes back.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* function App() {
|
|
67
|
+
* useAppStateTracking();
|
|
68
|
+
* return <MainScreen />;
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function useAppStateTracking(): void;
|
|
73
|
+
|
|
74
|
+
export { LitemetricsProvider, type LitemetricsProviderProps, type RNTrackerInstance, createRNTracker, useAppStateTracking, useLitemetrics, useNavigationTracking };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
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/context.tsx
|
|
9
|
+
import { createContext, useContext, useEffect, useRef, useMemo } from "react";
|
|
10
|
+
|
|
11
|
+
// src/tracker.ts
|
|
12
|
+
var sessionId = null;
|
|
13
|
+
var visitorId = null;
|
|
14
|
+
var userId = null;
|
|
15
|
+
function generateId() {
|
|
16
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
17
|
+
const r = Math.random() * 16 | 0;
|
|
18
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
19
|
+
return v.toString(16);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function createRNTracker(config) {
|
|
23
|
+
const { siteId, endpoint, debug = false } = config;
|
|
24
|
+
if (!sessionId) sessionId = generateId();
|
|
25
|
+
if (!visitorId) visitorId = generateId().slice(0, 16);
|
|
26
|
+
const queue = [];
|
|
27
|
+
let flushTimer = null;
|
|
28
|
+
function getContext() {
|
|
29
|
+
return {
|
|
30
|
+
timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function flush() {
|
|
34
|
+
if (queue.length === 0) return;
|
|
35
|
+
const events = queue.splice(0);
|
|
36
|
+
if (debug) {
|
|
37
|
+
console.log("[litemetrics:rn] sending", events.length, "events");
|
|
38
|
+
}
|
|
39
|
+
fetch(endpoint, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body: JSON.stringify({ events })
|
|
43
|
+
}).catch((err) => {
|
|
44
|
+
if (debug) console.warn("[litemetrics:rn] send failed", err);
|
|
45
|
+
queue.unshift(...events);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function send(event) {
|
|
49
|
+
queue.push(event);
|
|
50
|
+
if (queue.length >= (config.batchSize ?? 10)) {
|
|
51
|
+
flush();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
flushTimer = setInterval(flush, config.flushInterval ?? 5e3);
|
|
55
|
+
return {
|
|
56
|
+
track(name, properties) {
|
|
57
|
+
const event = {
|
|
58
|
+
type: "event",
|
|
59
|
+
siteId,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
sessionId,
|
|
62
|
+
visitorId,
|
|
63
|
+
name,
|
|
64
|
+
properties,
|
|
65
|
+
...getContext()
|
|
66
|
+
};
|
|
67
|
+
if (userId) event.userId = userId;
|
|
68
|
+
send(event);
|
|
69
|
+
},
|
|
70
|
+
identify(id, traits) {
|
|
71
|
+
userId = id;
|
|
72
|
+
const event = {
|
|
73
|
+
type: "identify",
|
|
74
|
+
siteId,
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
sessionId,
|
|
77
|
+
visitorId,
|
|
78
|
+
userId: id,
|
|
79
|
+
traits,
|
|
80
|
+
...getContext()
|
|
81
|
+
};
|
|
82
|
+
send(event);
|
|
83
|
+
},
|
|
84
|
+
page(screenName) {
|
|
85
|
+
const event = {
|
|
86
|
+
type: "pageview",
|
|
87
|
+
siteId,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
sessionId,
|
|
90
|
+
visitorId,
|
|
91
|
+
url: screenName,
|
|
92
|
+
title: screenName,
|
|
93
|
+
...getContext()
|
|
94
|
+
};
|
|
95
|
+
if (userId) event.userId = userId;
|
|
96
|
+
send(event);
|
|
97
|
+
},
|
|
98
|
+
reset() {
|
|
99
|
+
sessionId = generateId();
|
|
100
|
+
visitorId = generateId().slice(0, 16);
|
|
101
|
+
userId = null;
|
|
102
|
+
},
|
|
103
|
+
destroy() {
|
|
104
|
+
flush();
|
|
105
|
+
if (flushTimer) {
|
|
106
|
+
clearInterval(flushTimer);
|
|
107
|
+
flushTimer = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/context.tsx
|
|
114
|
+
import { jsx } from "react/jsx-runtime";
|
|
115
|
+
var LitemetricsRNContext = createContext(null);
|
|
116
|
+
function LitemetricsProvider({ children, ...config }) {
|
|
117
|
+
const trackerRef = useRef(null);
|
|
118
|
+
if (!trackerRef.current) {
|
|
119
|
+
trackerRef.current = createRNTracker(config);
|
|
120
|
+
}
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
return () => {
|
|
123
|
+
trackerRef.current?.destroy();
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
126
|
+
const value = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
tracker: trackerRef.current,
|
|
129
|
+
siteId: config.siteId
|
|
130
|
+
}),
|
|
131
|
+
[config.siteId]
|
|
132
|
+
);
|
|
133
|
+
return /* @__PURE__ */ jsx(LitemetricsRNContext.Provider, { value, children });
|
|
134
|
+
}
|
|
135
|
+
function useLitemetricsRNContext() {
|
|
136
|
+
const ctx = useContext(LitemetricsRNContext);
|
|
137
|
+
if (!ctx) {
|
|
138
|
+
throw new Error("useLitemetrics must be used within <LitemetricsProvider>");
|
|
139
|
+
}
|
|
140
|
+
return ctx;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/hooks.ts
|
|
144
|
+
import { useEffect as useEffect2, useRef as useRef2, useCallback } from "react";
|
|
145
|
+
function useLitemetrics() {
|
|
146
|
+
const { tracker } = useLitemetricsRNContext();
|
|
147
|
+
return {
|
|
148
|
+
track: tracker.track.bind(tracker),
|
|
149
|
+
identify: tracker.identify.bind(tracker),
|
|
150
|
+
page: tracker.page.bind(tracker),
|
|
151
|
+
reset: tracker.reset.bind(tracker)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function useNavigationTracking() {
|
|
155
|
+
const { tracker } = useLitemetricsRNContext();
|
|
156
|
+
const routeNameRef = useRef2(void 0);
|
|
157
|
+
const navigationRef = useRef2(null);
|
|
158
|
+
const onStateChange = useCallback(() => {
|
|
159
|
+
const currentRoute = navigationRef.current?.getCurrentRoute?.();
|
|
160
|
+
const currentRouteName = currentRoute?.name;
|
|
161
|
+
if (currentRouteName && currentRouteName !== routeNameRef.current) {
|
|
162
|
+
routeNameRef.current = currentRouteName;
|
|
163
|
+
tracker.page(currentRouteName);
|
|
164
|
+
}
|
|
165
|
+
}, [tracker]);
|
|
166
|
+
return { onStateChange, navigationRef };
|
|
167
|
+
}
|
|
168
|
+
function useAppStateTracking() {
|
|
169
|
+
const { tracker } = useLitemetricsRNContext();
|
|
170
|
+
const appStateRef = useRef2("active");
|
|
171
|
+
useEffect2(() => {
|
|
172
|
+
let AppState;
|
|
173
|
+
let subscription;
|
|
174
|
+
try {
|
|
175
|
+
AppState = __require("react-native").AppState;
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
appStateRef.current = AppState.currentState;
|
|
180
|
+
subscription = AppState.addEventListener("change", (nextState) => {
|
|
181
|
+
if (appStateRef.current.match(/inactive|background/) && nextState === "active") {
|
|
182
|
+
tracker.track("app_foreground");
|
|
183
|
+
} else if (nextState.match(/inactive|background/)) {
|
|
184
|
+
tracker.track("app_background");
|
|
185
|
+
}
|
|
186
|
+
appStateRef.current = nextState;
|
|
187
|
+
});
|
|
188
|
+
return () => {
|
|
189
|
+
subscription?.remove();
|
|
190
|
+
};
|
|
191
|
+
}, [tracker]);
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
LitemetricsProvider,
|
|
195
|
+
createRNTracker,
|
|
196
|
+
useAppStateTracking,
|
|
197
|
+
useLitemetrics,
|
|
198
|
+
useNavigationTracking
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/tracker.ts","../src/hooks.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useRef, useMemo } from \"react\";\nimport type { TrackerConfig } from \"@litemetrics/core\";\nimport { createRNTracker, type RNTrackerInstance } from \"./tracker\";\n\ninterface LitemetricsRNContextValue {\n tracker: RNTrackerInstance;\n siteId: string;\n}\n\nconst LitemetricsRNContext = createContext<LitemetricsRNContextValue | null>(null);\n\nexport interface LitemetricsProviderProps\n extends Omit<TrackerConfig, \"autoTrack\" | \"autoSpa\"> {\n children: React.ReactNode;\n}\n\nexport function LitemetricsProvider({ children, ...config }: LitemetricsProviderProps) {\n const trackerRef = useRef<RNTrackerInstance | null>(null);\n\n if (!trackerRef.current) {\n trackerRef.current = createRNTracker(config);\n }\n\n useEffect(() => {\n return () => {\n trackerRef.current?.destroy();\n };\n }, []);\n\n const value = useMemo<LitemetricsRNContextValue>(\n () => ({\n tracker: trackerRef.current!,\n siteId: config.siteId,\n }),\n [config.siteId]\n );\n\n return (\n <LitemetricsRNContext.Provider value={value}>\n {children}\n </LitemetricsRNContext.Provider>\n );\n}\n\nexport function useLitemetricsRNContext(): LitemetricsRNContextValue {\n const ctx = useContext(LitemetricsRNContext);\n if (!ctx) {\n throw new Error(\"useLitemetrics must be used within <LitemetricsProvider>\");\n }\n return ctx;\n}\n","import type {\n TrackerConfig,\n ClientEvent,\n PageviewEvent,\n CustomEvent,\n IdentifyEvent,\n ClientContext,\n} from '@litemetrics/core';\n\nexport interface RNTrackerInstance {\n track(name: string, properties?: Record<string, unknown>): void;\n identify(userId: string, traits?: Record<string, unknown>): void;\n page(screenName: string): void;\n reset(): void;\n destroy(): void;\n}\n\nlet sessionId: string | null = null;\nlet visitorId: string | null = null;\nlet userId: string | null = null;\n\nfunction generateId(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function createRNTracker(config: TrackerConfig): RNTrackerInstance {\n const { siteId, endpoint, debug = false } = config;\n\n if (!sessionId) sessionId = generateId();\n if (!visitorId) visitorId = generateId().slice(0, 16);\n\n const queue: ClientEvent[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n\n function getContext(): ClientContext {\n return {\n timezone:\n typeof Intl !== 'undefined'\n ? Intl.DateTimeFormat().resolvedOptions().timeZone\n : undefined,\n };\n }\n\n function flush() {\n if (queue.length === 0) return;\n const events = queue.splice(0);\n\n if (debug) {\n console.log('[litemetrics:rn] sending', events.length, 'events');\n }\n\n fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ events }),\n }).catch((err) => {\n if (debug) console.warn('[litemetrics:rn] send failed', err);\n // Push back to queue for retry\n queue.unshift(...events);\n });\n }\n\n function send(event: ClientEvent) {\n queue.push(event);\n if (queue.length >= (config.batchSize ?? 10)) {\n flush();\n }\n }\n\n // Start flush timer\n flushTimer = setInterval(flush, config.flushInterval ?? 5000);\n\n return {\n track(name: string, properties?: Record<string, unknown>) {\n const event: CustomEvent & ClientContext = {\n type: 'event',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n name,\n properties,\n ...getContext(),\n };\n if (userId) (event as any).userId = userId;\n send(event);\n },\n\n identify(id: string, traits?: Record<string, unknown>) {\n userId = id;\n const event: IdentifyEvent & ClientContext = {\n type: 'identify',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n userId: id,\n traits,\n ...getContext(),\n };\n send(event);\n },\n\n page(screenName: string) {\n const event: PageviewEvent & ClientContext = {\n type: 'pageview',\n siteId,\n timestamp: Date.now(),\n sessionId: sessionId!,\n visitorId: visitorId!,\n url: screenName,\n title: screenName,\n ...getContext(),\n };\n if (userId) (event as any).userId = userId;\n send(event);\n },\n\n reset() {\n sessionId = generateId();\n visitorId = generateId().slice(0, 16);\n userId = null;\n },\n\n destroy() {\n flush();\n if (flushTimer) {\n clearInterval(flushTimer);\n flushTimer = null;\n }\n },\n };\n}\n","import { useEffect, useRef, useCallback } from 'react';\nimport { useLitemetricsRNContext } from './context';\n\n/**\n * Access the Litemetrics tracker instance in React Native.\n *\n * @example\n * ```tsx\n * function Button() {\n * const { track } = useLitemetrics();\n * return <Pressable onPress={() => track('tap', { screen: 'home' })}><Text>Tap</Text></Pressable>;\n * }\n * ```\n */\nexport function useLitemetrics() {\n const { tracker } = useLitemetricsRNContext();\n\n return {\n track: tracker.track.bind(tracker),\n identify: tracker.identify.bind(tracker),\n page: tracker.page.bind(tracker),\n reset: tracker.reset.bind(tracker),\n };\n}\n\n/**\n * Track React Navigation screen changes.\n * Returns `onStateChange` and `ref` props for NavigationContainer.\n *\n * @example\n * ```tsx\n * import { NavigationContainer } from '@react-navigation/native';\n *\n * function App() {\n * const { onStateChange, navigationRef } = useNavigationTracking();\n * return (\n * <LitemetricsProvider siteId=\"xxx\" endpoint=\"https://api.example.com/collect\">\n * <NavigationContainer ref={navigationRef} onStateChange={onStateChange}>\n * <Stack.Navigator>...</Stack.Navigator>\n * </NavigationContainer>\n * </LitemetricsProvider>\n * );\n * }\n * ```\n */\nexport function useNavigationTracking() {\n const { tracker } = useLitemetricsRNContext();\n const routeNameRef = useRef<string | undefined>(undefined);\n const navigationRef = useRef<any>(null);\n\n const onStateChange = useCallback(() => {\n const currentRoute = navigationRef.current?.getCurrentRoute?.();\n const currentRouteName = currentRoute?.name;\n\n if (currentRouteName && currentRouteName !== routeNameRef.current) {\n routeNameRef.current = currentRouteName;\n tracker.page(currentRouteName);\n }\n }, [tracker]);\n\n return { onStateChange, navigationRef };\n}\n\n/**\n * Track app foreground/background state changes.\n * Sends events when the app goes to background and comes back.\n *\n * @example\n * ```tsx\n * function App() {\n * useAppStateTracking();\n * return <MainScreen />;\n * }\n * ```\n */\nexport function useAppStateTracking() {\n const { tracker } = useLitemetricsRNContext();\n const appStateRef = useRef<string>('active');\n\n useEffect(() => {\n let AppState: any;\n let subscription: any;\n\n // Dynamic import to avoid crash if not in RN environment\n try {\n AppState = require('react-native').AppState;\n } catch {\n return;\n }\n\n appStateRef.current = AppState.currentState;\n\n subscription = AppState.addEventListener('change', (nextState: string) => {\n if (appStateRef.current.match(/inactive|background/) && nextState === 'active') {\n tracker.track('app_foreground');\n } else if (nextState.match(/inactive|background/)) {\n tracker.track('app_background');\n }\n appStateRef.current = nextState;\n });\n\n return () => {\n subscription?.remove();\n };\n }, [tracker]);\n}\n"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@litemetrics/react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native / Expo bindings for Litemetrics analytics",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Metehan Kurucu",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/metehankurucu/litemetrics",
|
|
10
|
+
"directory": "packages/react-native"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["analytics", "litemetrics", "react-native", "expo", "tracking"],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist"],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"clean": "rm -rf dist"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@litemetrics/core": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18",
|
|
39
|
+
"react-native": ">=0.72"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/react": "^19",
|
|
43
|
+
"react": "^19",
|
|
44
|
+
"tsup": "^8",
|
|
45
|
+
"typescript": "^5.7"
|
|
46
|
+
}
|
|
47
|
+
}
|