@formo/analytics-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.
Files changed (247) hide show
  1. package/README.md +302 -0
  2. package/lib/commonjs/FormoAnalytics.js +526 -0
  3. package/lib/commonjs/FormoAnalytics.js.map +1 -0
  4. package/lib/commonjs/FormoAnalyticsProvider.js +265 -0
  5. package/lib/commonjs/FormoAnalyticsProvider.js.map +1 -0
  6. package/lib/commonjs/constants/config.js +69 -0
  7. package/lib/commonjs/constants/config.js.map +1 -0
  8. package/lib/commonjs/constants/events.js +30 -0
  9. package/lib/commonjs/constants/events.js.map +1 -0
  10. package/lib/commonjs/constants/index.js +39 -0
  11. package/lib/commonjs/constants/index.js.map +1 -0
  12. package/lib/commonjs/constants/storage.js +23 -0
  13. package/lib/commonjs/constants/storage.js.map +1 -0
  14. package/lib/commonjs/index.js +65 -0
  15. package/lib/commonjs/index.js.map +1 -0
  16. package/lib/commonjs/lib/consent/index.js +56 -0
  17. package/lib/commonjs/lib/consent/index.js.map +1 -0
  18. package/lib/commonjs/lib/event/EventFactory.js +493 -0
  19. package/lib/commonjs/lib/event/EventFactory.js.map +1 -0
  20. package/lib/commonjs/lib/event/EventManager.js +46 -0
  21. package/lib/commonjs/lib/event/EventManager.js.map +1 -0
  22. package/lib/commonjs/lib/event/EventQueue.js +290 -0
  23. package/lib/commonjs/lib/event/EventQueue.js.map +1 -0
  24. package/lib/commonjs/lib/event/index.js +50 -0
  25. package/lib/commonjs/lib/event/index.js.map +1 -0
  26. package/lib/commonjs/lib/event/types.js +6 -0
  27. package/lib/commonjs/lib/event/types.js.map +1 -0
  28. package/lib/commonjs/lib/lifecycle/index.js +196 -0
  29. package/lib/commonjs/lib/lifecycle/index.js.map +1 -0
  30. package/lib/commonjs/lib/logger/index.js +48 -0
  31. package/lib/commonjs/lib/logger/index.js.map +1 -0
  32. package/lib/commonjs/lib/session/index.js +109 -0
  33. package/lib/commonjs/lib/session/index.js.map +1 -0
  34. package/lib/commonjs/lib/storage/AsyncStorageAdapter.js +164 -0
  35. package/lib/commonjs/lib/storage/AsyncStorageAdapter.js.map +1 -0
  36. package/lib/commonjs/lib/storage/MemoryStorage.js +41 -0
  37. package/lib/commonjs/lib/storage/MemoryStorage.js.map +1 -0
  38. package/lib/commonjs/lib/storage/StorageBlueprint.js +24 -0
  39. package/lib/commonjs/lib/storage/StorageBlueprint.js.map +1 -0
  40. package/lib/commonjs/lib/storage/StorageManager.js +126 -0
  41. package/lib/commonjs/lib/storage/StorageManager.js.map +1 -0
  42. package/lib/commonjs/lib/storage/index.js +49 -0
  43. package/lib/commonjs/lib/storage/index.js.map +1 -0
  44. package/lib/commonjs/lib/storage/types.js +2 -0
  45. package/lib/commonjs/lib/storage/types.js.map +1 -0
  46. package/lib/commonjs/lib/wagmi/WagmiEventHandler.js +445 -0
  47. package/lib/commonjs/lib/wagmi/WagmiEventHandler.js.map +1 -0
  48. package/lib/commonjs/lib/wagmi/index.js +28 -0
  49. package/lib/commonjs/lib/wagmi/index.js.map +1 -0
  50. package/lib/commonjs/lib/wagmi/types.js +2 -0
  51. package/lib/commonjs/lib/wagmi/types.js.map +1 -0
  52. package/lib/commonjs/types/base.js +6 -0
  53. package/lib/commonjs/types/base.js.map +1 -0
  54. package/lib/commonjs/types/events.js +22 -0
  55. package/lib/commonjs/types/events.js.map +1 -0
  56. package/lib/commonjs/types/index.js +28 -0
  57. package/lib/commonjs/types/index.js.map +1 -0
  58. package/lib/commonjs/utils/address.js +82 -0
  59. package/lib/commonjs/utils/address.js.map +1 -0
  60. package/lib/commonjs/utils/hash.js +30 -0
  61. package/lib/commonjs/utils/hash.js.map +1 -0
  62. package/lib/commonjs/utils/helpers.js +116 -0
  63. package/lib/commonjs/utils/helpers.js.map +1 -0
  64. package/lib/commonjs/utils/index.js +61 -0
  65. package/lib/commonjs/utils/index.js.map +1 -0
  66. package/lib/commonjs/utils/timestamp.js +34 -0
  67. package/lib/commonjs/utils/timestamp.js.map +1 -0
  68. package/lib/commonjs/utils/trafficSource.js +147 -0
  69. package/lib/commonjs/utils/trafficSource.js.map +1 -0
  70. package/lib/commonjs/version.js +10 -0
  71. package/lib/commonjs/version.js.map +1 -0
  72. package/lib/module/FormoAnalytics.js +519 -0
  73. package/lib/module/FormoAnalytics.js.map +1 -0
  74. package/lib/module/FormoAnalyticsProvider.js +256 -0
  75. package/lib/module/FormoAnalyticsProvider.js.map +1 -0
  76. package/lib/module/constants/config.js +62 -0
  77. package/lib/module/constants/config.js.map +1 -0
  78. package/lib/module/constants/events.js +24 -0
  79. package/lib/module/constants/events.js.map +1 -0
  80. package/lib/module/constants/index.js +4 -0
  81. package/lib/module/constants/index.js.map +1 -0
  82. package/lib/module/constants/storage.js +17 -0
  83. package/lib/module/constants/storage.js.map +1 -0
  84. package/lib/module/index.js +51 -0
  85. package/lib/module/index.js.map +1 -0
  86. package/lib/module/lib/consent/index.js +49 -0
  87. package/lib/module/lib/consent/index.js.map +1 -0
  88. package/lib/module/lib/event/EventFactory.js +488 -0
  89. package/lib/module/lib/event/EventFactory.js.map +1 -0
  90. package/lib/module/lib/event/EventManager.js +41 -0
  91. package/lib/module/lib/event/EventManager.js.map +1 -0
  92. package/lib/module/lib/event/EventQueue.js +283 -0
  93. package/lib/module/lib/event/EventQueue.js.map +1 -0
  94. package/lib/module/lib/event/index.js +5 -0
  95. package/lib/module/lib/event/index.js.map +1 -0
  96. package/lib/module/lib/event/types.js +2 -0
  97. package/lib/module/lib/event/types.js.map +1 -0
  98. package/lib/module/lib/lifecycle/index.js +190 -0
  99. package/lib/module/lib/lifecycle/index.js.map +1 -0
  100. package/lib/module/lib/logger/index.js +42 -0
  101. package/lib/module/lib/logger/index.js.map +1 -0
  102. package/lib/module/lib/session/index.js +92 -0
  103. package/lib/module/lib/session/index.js.map +1 -0
  104. package/lib/module/lib/storage/AsyncStorageAdapter.js +158 -0
  105. package/lib/module/lib/storage/AsyncStorageAdapter.js.map +1 -0
  106. package/lib/module/lib/storage/MemoryStorage.js +35 -0
  107. package/lib/module/lib/storage/MemoryStorage.js.map +1 -0
  108. package/lib/module/lib/storage/StorageBlueprint.js +18 -0
  109. package/lib/module/lib/storage/StorageBlueprint.js.map +1 -0
  110. package/lib/module/lib/storage/StorageManager.js +115 -0
  111. package/lib/module/lib/storage/StorageManager.js.map +1 -0
  112. package/lib/module/lib/storage/index.js +5 -0
  113. package/lib/module/lib/storage/index.js.map +1 -0
  114. package/lib/module/lib/storage/types.js +2 -0
  115. package/lib/module/lib/storage/types.js.map +1 -0
  116. package/lib/module/lib/wagmi/WagmiEventHandler.js +439 -0
  117. package/lib/module/lib/wagmi/WagmiEventHandler.js.map +1 -0
  118. package/lib/module/lib/wagmi/index.js +3 -0
  119. package/lib/module/lib/wagmi/index.js.map +1 -0
  120. package/lib/module/lib/wagmi/types.js +2 -0
  121. package/lib/module/lib/wagmi/types.js.map +1 -0
  122. package/lib/module/types/base.js +2 -0
  123. package/lib/module/types/base.js.map +1 -0
  124. package/lib/module/types/events.js +17 -0
  125. package/lib/module/types/events.js.map +1 -0
  126. package/lib/module/types/index.js +3 -0
  127. package/lib/module/types/index.js.map +1 -0
  128. package/lib/module/utils/address.js +74 -0
  129. package/lib/module/utils/address.js.map +1 -0
  130. package/lib/module/utils/hash.js +24 -0
  131. package/lib/module/utils/hash.js.map +1 -0
  132. package/lib/module/utils/helpers.js +105 -0
  133. package/lib/module/utils/helpers.js.map +1 -0
  134. package/lib/module/utils/index.js +6 -0
  135. package/lib/module/utils/index.js.map +1 -0
  136. package/lib/module/utils/timestamp.js +26 -0
  137. package/lib/module/utils/timestamp.js.map +1 -0
  138. package/lib/module/utils/trafficSource.js +137 -0
  139. package/lib/module/utils/trafficSource.js.map +1 -0
  140. package/lib/module/version.js +4 -0
  141. package/lib/module/version.js.map +1 -0
  142. package/lib/typescript/FormoAnalytics.d.ts +163 -0
  143. package/lib/typescript/FormoAnalytics.d.ts.map +1 -0
  144. package/lib/typescript/FormoAnalyticsProvider.d.ts +29 -0
  145. package/lib/typescript/FormoAnalyticsProvider.d.ts.map +1 -0
  146. package/lib/typescript/constants/config.d.ts +8 -0
  147. package/lib/typescript/constants/config.d.ts.map +1 -0
  148. package/lib/typescript/constants/events.d.ts +23 -0
  149. package/lib/typescript/constants/events.d.ts.map +1 -0
  150. package/lib/typescript/constants/index.d.ts +4 -0
  151. package/lib/typescript/constants/index.d.ts.map +1 -0
  152. package/lib/typescript/constants/storage.d.ts +10 -0
  153. package/lib/typescript/constants/storage.d.ts.map +1 -0
  154. package/lib/typescript/index.d.ts +44 -0
  155. package/lib/typescript/index.d.ts.map +1 -0
  156. package/lib/typescript/lib/consent/index.d.ts +13 -0
  157. package/lib/typescript/lib/consent/index.d.ts.map +1 -0
  158. package/lib/typescript/lib/event/EventFactory.d.ts +61 -0
  159. package/lib/typescript/lib/event/EventFactory.d.ts.map +1 -0
  160. package/lib/typescript/lib/event/EventManager.d.ts +17 -0
  161. package/lib/typescript/lib/event/EventManager.d.ts.map +1 -0
  162. package/lib/typescript/lib/event/EventQueue.d.ts +74 -0
  163. package/lib/typescript/lib/event/EventQueue.d.ts.map +1 -0
  164. package/lib/typescript/lib/event/index.d.ts +5 -0
  165. package/lib/typescript/lib/event/index.d.ts.map +1 -0
  166. package/lib/typescript/lib/event/types.d.ts +23 -0
  167. package/lib/typescript/lib/event/types.d.ts.map +1 -0
  168. package/lib/typescript/lib/lifecycle/index.d.ts +46 -0
  169. package/lib/typescript/lib/lifecycle/index.d.ts.map +1 -0
  170. package/lib/typescript/lib/logger/index.d.ts +19 -0
  171. package/lib/typescript/lib/logger/index.d.ts.map +1 -0
  172. package/lib/typescript/lib/session/index.d.ts +41 -0
  173. package/lib/typescript/lib/session/index.d.ts.map +1 -0
  174. package/lib/typescript/lib/storage/AsyncStorageAdapter.d.ts +48 -0
  175. package/lib/typescript/lib/storage/AsyncStorageAdapter.d.ts.map +1 -0
  176. package/lib/typescript/lib/storage/MemoryStorage.d.ts +18 -0
  177. package/lib/typescript/lib/storage/MemoryStorage.d.ts.map +1 -0
  178. package/lib/typescript/lib/storage/StorageBlueprint.d.ts +21 -0
  179. package/lib/typescript/lib/storage/StorageBlueprint.d.ts.map +1 -0
  180. package/lib/typescript/lib/storage/StorageManager.d.ts +45 -0
  181. package/lib/typescript/lib/storage/StorageManager.d.ts.map +1 -0
  182. package/lib/typescript/lib/storage/index.d.ts +5 -0
  183. package/lib/typescript/lib/storage/index.d.ts.map +1 -0
  184. package/lib/typescript/lib/storage/types.d.ts +22 -0
  185. package/lib/typescript/lib/storage/types.d.ts.map +1 -0
  186. package/lib/typescript/lib/wagmi/WagmiEventHandler.d.ts +104 -0
  187. package/lib/typescript/lib/wagmi/WagmiEventHandler.d.ts.map +1 -0
  188. package/lib/typescript/lib/wagmi/index.d.ts +3 -0
  189. package/lib/typescript/lib/wagmi/index.d.ts.map +1 -0
  190. package/lib/typescript/lib/wagmi/types.d.ts +54 -0
  191. package/lib/typescript/lib/wagmi/types.d.ts.map +1 -0
  192. package/lib/typescript/types/base.d.ts +219 -0
  193. package/lib/typescript/types/base.d.ts.map +1 -0
  194. package/lib/typescript/types/events.d.ts +111 -0
  195. package/lib/typescript/types/events.d.ts.map +1 -0
  196. package/lib/typescript/types/index.d.ts +3 -0
  197. package/lib/typescript/types/index.d.ts.map +1 -0
  198. package/lib/typescript/utils/address.d.ts +25 -0
  199. package/lib/typescript/utils/address.d.ts.map +1 -0
  200. package/lib/typescript/utils/hash.d.ts +10 -0
  201. package/lib/typescript/utils/hash.d.ts.map +1 -0
  202. package/lib/typescript/utils/helpers.d.ts +26 -0
  203. package/lib/typescript/utils/helpers.d.ts.map +1 -0
  204. package/lib/typescript/utils/index.d.ts +6 -0
  205. package/lib/typescript/utils/index.d.ts.map +1 -0
  206. package/lib/typescript/utils/timestamp.d.ts +13 -0
  207. package/lib/typescript/utils/timestamp.d.ts.map +1 -0
  208. package/lib/typescript/utils/trafficSource.d.ts +30 -0
  209. package/lib/typescript/utils/trafficSource.d.ts.map +1 -0
  210. package/lib/typescript/version.d.ts +2 -0
  211. package/lib/typescript/version.d.ts.map +1 -0
  212. package/package.json +143 -0
  213. package/src/FormoAnalytics.ts +685 -0
  214. package/src/FormoAnalyticsProvider.tsx +296 -0
  215. package/src/constants/config.ts +62 -0
  216. package/src/constants/events.ts +26 -0
  217. package/src/constants/index.ts +3 -0
  218. package/src/constants/storage.ts +16 -0
  219. package/src/index.ts +55 -0
  220. package/src/lib/consent/index.ts +52 -0
  221. package/src/lib/event/EventFactory.ts +682 -0
  222. package/src/lib/event/EventManager.ts +50 -0
  223. package/src/lib/event/EventQueue.ts +371 -0
  224. package/src/lib/event/index.ts +4 -0
  225. package/src/lib/event/types.ts +107 -0
  226. package/src/lib/lifecycle/index.ts +215 -0
  227. package/src/lib/logger/index.ts +56 -0
  228. package/src/lib/session/index.ts +103 -0
  229. package/src/lib/storage/AsyncStorageAdapter.ts +173 -0
  230. package/src/lib/storage/MemoryStorage.ts +43 -0
  231. package/src/lib/storage/StorageBlueprint.ts +30 -0
  232. package/src/lib/storage/StorageManager.ts +121 -0
  233. package/src/lib/storage/index.ts +4 -0
  234. package/src/lib/storage/types.ts +23 -0
  235. package/src/lib/wagmi/WagmiEventHandler.ts +574 -0
  236. package/src/lib/wagmi/index.ts +2 -0
  237. package/src/lib/wagmi/types.ts +71 -0
  238. package/src/types/base.ts +287 -0
  239. package/src/types/events.ts +140 -0
  240. package/src/types/index.ts +2 -0
  241. package/src/utils/address.ts +84 -0
  242. package/src/utils/hash.ts +23 -0
  243. package/src/utils/helpers.ts +139 -0
  244. package/src/utils/index.ts +5 -0
  245. package/src/utils/timestamp.ts +25 -0
  246. package/src/utils/trafficSource.ts +153 -0
  247. package/src/version.ts +3 -0
@@ -0,0 +1,296 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ FC,
9
+ } from "react";
10
+ import { FormoAnalytics } from "./FormoAnalytics";
11
+ import { initStorageManager } from "./lib/storage";
12
+ import { logger } from "./lib/logger";
13
+ import { FormoAnalyticsProviderProps, IFormoAnalytics } from "./types";
14
+
15
+ // Default context with no-op methods
16
+ const defaultContext: IFormoAnalytics = {
17
+ chain: () => Promise.resolve(),
18
+ screen: () => Promise.resolve(),
19
+ reset: () => {},
20
+ cleanup: () => Promise.resolve(),
21
+ detect: () => Promise.resolve(),
22
+ connect: () => Promise.resolve(),
23
+ disconnect: () => Promise.resolve(),
24
+ signature: () => Promise.resolve(),
25
+ transaction: () => Promise.resolve(),
26
+ identify: () => Promise.resolve(),
27
+ track: () => Promise.resolve(),
28
+ flush: () => Promise.resolve(),
29
+ setTrafficSourceFromUrl: () => {},
30
+ optOutTracking: () => {},
31
+ optInTracking: () => {},
32
+ hasOptedOutTracking: () => false,
33
+ };
34
+
35
+ export const FormoAnalyticsContext =
36
+ createContext<IFormoAnalytics>(defaultContext);
37
+
38
+ /**
39
+ * Formo Analytics Provider for React Native
40
+ *
41
+ * Wraps your app to provide analytics context
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * import AsyncStorage from '@react-native-async-storage/async-storage';
46
+ * import { FormoAnalyticsProvider } from '@formo/analytics-react-native';
47
+ *
48
+ * function App() {
49
+ * return (
50
+ * <FormoAnalyticsProvider
51
+ * writeKey="your-write-key"
52
+ * asyncStorage={AsyncStorage}
53
+ * options={{ wagmi: { config, queryClient } }}
54
+ * >
55
+ * <YourApp />
56
+ * </FormoAnalyticsProvider>
57
+ * );
58
+ * }
59
+ * ```
60
+ */
61
+ export const FormoAnalyticsProvider: FC<FormoAnalyticsProviderProps> = (
62
+ props
63
+ ) => {
64
+ const { writeKey, disabled = false, children } = props;
65
+
66
+ if (!writeKey) {
67
+ logger.error("FormoAnalyticsProvider: No Write Key provided");
68
+ return (
69
+ <FormoAnalyticsContext.Provider value={defaultContext}>
70
+ {children}
71
+ </FormoAnalyticsContext.Provider>
72
+ );
73
+ }
74
+
75
+ if (disabled) {
76
+ logger.warn("FormoAnalytics is disabled");
77
+ return (
78
+ <FormoAnalyticsContext.Provider value={defaultContext}>
79
+ {children}
80
+ </FormoAnalyticsContext.Provider>
81
+ );
82
+ }
83
+
84
+ return <InitializedAnalytics {...props} />;
85
+ };
86
+
87
+ const InitializedAnalytics: FC<FormoAnalyticsProviderProps> = ({
88
+ writeKey,
89
+ options,
90
+ asyncStorage,
91
+ onReady,
92
+ onError,
93
+ children,
94
+ }) => {
95
+ const [sdk, setSdk] = useState<IFormoAnalytics>(defaultContext);
96
+ const sdkRef = useRef<IFormoAnalytics>(defaultContext);
97
+ const storageInitKeyRef = useRef<string | null>(null);
98
+ const cleanupPromiseRef = useRef<Promise<void>>(Promise.resolve());
99
+ const initializationIdRef = useRef<number>(0);
100
+
101
+ // Only initialize storage manager when writeKey changes, not on every render
102
+ if (storageInitKeyRef.current !== writeKey) {
103
+ initStorageManager(writeKey);
104
+ storageInitKeyRef.current = writeKey;
105
+ }
106
+
107
+ // Store callbacks and options in refs to avoid re-initialization when references change
108
+ // This fixes the issue where inline arrow functions cause repeated SDK init
109
+ const onReadyRef = useRef(onReady);
110
+ const onErrorRef = useRef(onError);
111
+ const optionsRef = useRef(options);
112
+
113
+ // Update refs when values change (without triggering effect)
114
+ useEffect(() => {
115
+ onReadyRef.current = onReady;
116
+ }, [onReady]);
117
+
118
+ useEffect(() => {
119
+ onErrorRef.current = onError;
120
+ }, [onError]);
121
+
122
+ useEffect(() => {
123
+ optionsRef.current = options;
124
+ }, [options]);
125
+
126
+ // Extract individual option values to avoid reference equality issues with options object
127
+ const tracking = options?.tracking;
128
+ const autocapture = options?.autocapture;
129
+ const apiHost = options?.apiHost;
130
+ const flushAt = options?.flushAt;
131
+ const flushInterval = options?.flushInterval;
132
+ const retryCount = options?.retryCount;
133
+ const maxQueueSize = options?.maxQueueSize;
134
+ const loggerOption = options?.logger;
135
+ const app = options?.app;
136
+ const hasReady = !!options?.ready;
137
+ const wagmiConfig = options?.wagmi?.config;
138
+ const wagmiQueryClient = options?.wagmi?.queryClient;
139
+
140
+ // Create stable key from serializable options
141
+ const optionsKey = useMemo(() => {
142
+ const serializableOptions = {
143
+ tracking,
144
+ autocapture,
145
+ apiHost,
146
+ flushAt,
147
+ flushInterval,
148
+ retryCount,
149
+ maxQueueSize,
150
+ logger: loggerOption,
151
+ app,
152
+ hasReady,
153
+ };
154
+
155
+ try {
156
+ return JSON.stringify(serializableOptions);
157
+ } catch (error) {
158
+ logger.warn("Failed to serialize options, using timestamp", error);
159
+ return Date.now().toString();
160
+ }
161
+ }, [tracking, autocapture, apiHost, flushAt, flushInterval, retryCount, maxQueueSize, loggerOption, app, hasReady]);
162
+
163
+ useEffect(() => {
164
+ // Increment initialization ID to track which initialization is current
165
+ const currentInitId = ++initializationIdRef.current;
166
+ let isCleanedUp = false;
167
+
168
+ const initialize = async () => {
169
+ // Wait for any pending cleanup to complete before re-initializing
170
+ // This prevents race conditions between cleanup and init
171
+ await cleanupPromiseRef.current;
172
+
173
+ // Check if this initialization is still current after awaiting cleanup
174
+ if (currentInitId !== initializationIdRef.current || isCleanedUp) {
175
+ logger.debug("Skipping stale initialization");
176
+ return;
177
+ }
178
+
179
+ // Clean up existing SDK and await flush completion
180
+ if (sdkRef.current && sdkRef.current !== defaultContext) {
181
+ logger.log("Cleaning up existing FormoAnalytics SDK instance");
182
+ const cleanup = sdkRef.current.cleanup();
183
+ cleanupPromiseRef.current = cleanup;
184
+ await cleanup;
185
+ sdkRef.current = defaultContext;
186
+ setSdk(defaultContext);
187
+ }
188
+
189
+ // Check again after cleanup
190
+ if (currentInitId !== initializationIdRef.current || isCleanedUp) {
191
+ logger.debug("Skipping stale initialization after cleanup");
192
+ return;
193
+ }
194
+
195
+ try {
196
+ // Use optionsRef.current to ensure we have the latest options
197
+ const sdkInstance = await FormoAnalytics.init(
198
+ writeKey,
199
+ optionsRef.current,
200
+ asyncStorage
201
+ );
202
+
203
+ // Verify this initialization is still current
204
+ if (currentInitId !== initializationIdRef.current || isCleanedUp) {
205
+ logger.log("Initialization superseded, cleaning up new instance");
206
+ await sdkInstance.cleanup();
207
+ return;
208
+ }
209
+
210
+ setSdk(sdkInstance);
211
+ sdkRef.current = sdkInstance;
212
+ logger.log("Successfully initialized FormoAnalytics SDK");
213
+
214
+ // Call onReady callback using the ref (stable reference)
215
+ onReadyRef.current?.(sdkInstance);
216
+ } catch (error) {
217
+ if (currentInitId === initializationIdRef.current && !isCleanedUp) {
218
+ logger.error("Failed to initialize FormoAnalytics SDK", error);
219
+ // Call onError callback using the ref (stable reference)
220
+ onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
221
+ }
222
+ }
223
+ };
224
+
225
+ initialize();
226
+
227
+ return () => {
228
+ isCleanedUp = true;
229
+
230
+ if (sdkRef.current && sdkRef.current !== defaultContext) {
231
+ logger.log("Cleaning up FormoAnalytics SDK instance");
232
+ // Store cleanup promise so next initialization can await it
233
+ const cleanup = sdkRef.current.cleanup().catch((error) => {
234
+ logger.error("Error during SDK cleanup:", error);
235
+ });
236
+ cleanupPromiseRef.current = cleanup;
237
+ sdkRef.current = defaultContext;
238
+ }
239
+ };
240
+ // Note: onReady and onError are NOT in the dependency array
241
+ // They are accessed via refs to prevent re-initialization
242
+ // wagmiConfig and wagmiQueryClient are tracked separately since they're not serializable
243
+ }, [writeKey, optionsKey, asyncStorage, wagmiConfig, wagmiQueryClient]);
244
+
245
+ return (
246
+ <FormoAnalyticsContext.Provider value={sdk}>
247
+ {children}
248
+ </FormoAnalyticsContext.Provider>
249
+ );
250
+ };
251
+
252
+ /**
253
+ * Hook to access Formo Analytics
254
+ *
255
+ * @example
256
+ * ```tsx
257
+ * import { useFormo } from '@formo/analytics-react-native';
258
+ *
259
+ * function MyScreen() {
260
+ * const formo = useFormo();
261
+ *
262
+ * useEffect(() => {
263
+ * formo.screen('Home');
264
+ * }, []);
265
+ *
266
+ * const handleButtonPress = () => {
267
+ * formo.track('Button Pressed', { buttonName: 'signup' });
268
+ * };
269
+ *
270
+ * return <Button onPress={handleButtonPress}>Sign Up</Button>;
271
+ * }
272
+ * ```
273
+ */
274
+ // Track if the useFormo warning has been logged to avoid console spam
275
+ let hasLoggedUseFormoWarning = false;
276
+
277
+ export const useFormo = (): IFormoAnalytics => {
278
+ const context = useContext(FormoAnalyticsContext);
279
+
280
+ // Check if SDK has been initialized (context will be defaultContext before init completes)
281
+ // Only log the warning once to avoid flooding the console during async initialization
282
+ if (context === defaultContext && !hasLoggedUseFormoWarning) {
283
+ hasLoggedUseFormoWarning = true;
284
+ logger.warn(
285
+ "useFormo called before SDK initialization complete. " +
286
+ "Ensure FormoAnalyticsProvider is mounted and writeKey is provided."
287
+ );
288
+ }
289
+
290
+ // Reset the warning flag when SDK is initialized so it can warn again after a reset
291
+ if (context !== defaultContext) {
292
+ hasLoggedUseFormoWarning = false;
293
+ }
294
+
295
+ return context;
296
+ };
@@ -0,0 +1,62 @@
1
+ export const EVENTS_API_ORIGIN = "https://events.formo.so";
2
+ export const EVENTS_API_HOST = `${EVENTS_API_ORIGIN}/v0/raw_events`;
3
+
4
+ export const EVENTS_API_REQUEST_HEADER = (writeKey: string) => ({
5
+ "Content-Type": "application/json",
6
+ Authorization: `Bearer ${writeKey}`,
7
+ });
8
+
9
+ // Timezone to country mapping
10
+ export const COUNTRY_LIST: Record<string, string> = {
11
+ // Africa
12
+ "Africa/Abidjan": "CI",
13
+ "Africa/Accra": "GH",
14
+ "Africa/Addis_Ababa": "ET",
15
+ "Africa/Algiers": "DZ",
16
+ "Africa/Cairo": "EG",
17
+ "Africa/Casablanca": "MA",
18
+ "Africa/Johannesburg": "ZA",
19
+ "Africa/Lagos": "NG",
20
+ "Africa/Nairobi": "KE",
21
+ // America
22
+ "America/Anchorage": "US",
23
+ "America/Argentina/Buenos_Aires": "AR",
24
+ "America/Bogota": "CO",
25
+ "America/Chicago": "US",
26
+ "America/Denver": "US",
27
+ "America/Los_Angeles": "US",
28
+ "America/Mexico_City": "MX",
29
+ "America/New_York": "US",
30
+ "America/Phoenix": "US",
31
+ "America/Sao_Paulo": "BR",
32
+ "America/Toronto": "CA",
33
+ "America/Vancouver": "CA",
34
+ // Asia
35
+ "Asia/Bangkok": "TH",
36
+ "Asia/Dubai": "AE",
37
+ "Asia/Ho_Chi_Minh": "VN",
38
+ "Asia/Hong_Kong": "HK",
39
+ "Asia/Jakarta": "ID",
40
+ "Asia/Kolkata": "IN",
41
+ "Asia/Manila": "PH",
42
+ "Asia/Seoul": "KR",
43
+ "Asia/Shanghai": "CN",
44
+ "Asia/Singapore": "SG",
45
+ "Asia/Taipei": "TW",
46
+ "Asia/Tokyo": "JP",
47
+ // Australia
48
+ "Australia/Melbourne": "AU",
49
+ "Australia/Sydney": "AU",
50
+ // Europe
51
+ "Europe/Amsterdam": "NL",
52
+ "Europe/Berlin": "DE",
53
+ "Europe/London": "GB",
54
+ "Europe/Madrid": "ES",
55
+ "Europe/Moscow": "RU",
56
+ "Europe/Paris": "FR",
57
+ "Europe/Rome": "IT",
58
+ "Europe/Zurich": "CH",
59
+ // Pacific
60
+ "Pacific/Auckland": "NZ",
61
+ "Pacific/Honolulu": "US",
62
+ };
@@ -0,0 +1,26 @@
1
+ export enum EventType {
2
+ PAGE = "page",
3
+ SCREEN = "screen",
4
+ IDENTIFY = "identify",
5
+ DETECT = "detect",
6
+ CONNECT = "connect",
7
+ DISCONNECT = "disconnect",
8
+ CHAIN = "chain",
9
+ SIGNATURE = "signature",
10
+ TRANSACTION = "transaction",
11
+ TRACK = "track",
12
+ }
13
+
14
+ export enum EventChannel {
15
+ WEB = "web",
16
+ MOBILE = "mobile",
17
+ SERVER = "server",
18
+ SOURCE = "source",
19
+ }
20
+
21
+ export type TEventType = Lowercase<EventType>;
22
+ export type TEventChannel = Lowercase<EventChannel>;
23
+
24
+ // React Native SDK uses mobile channel
25
+ export const CHANNEL: TEventChannel = "mobile";
26
+ export const VERSION = "1";
@@ -0,0 +1,3 @@
1
+ export * from "./events";
2
+ export * from "./config";
3
+ export * from "./storage";
@@ -0,0 +1,16 @@
1
+ // Storage keys for React Native SDK
2
+ export const STORAGE_PREFIX = "formo_rn_";
3
+
4
+ // Local storage keys (persistent)
5
+ export const LOCAL_ANONYMOUS_ID_KEY = "anonymous_id";
6
+ export const LOCAL_APP_VERSION_KEY = "app_version";
7
+ export const LOCAL_APP_BUILD_KEY = "app_build";
8
+
9
+ // Session storage keys (cleared on app restart)
10
+ export const SESSION_USER_ID_KEY = "user_id";
11
+ export const SESSION_TRAFFIC_SOURCE_KEY = "traffic_source";
12
+ export const SESSION_WALLET_DETECTED_KEY = "wallet_detected";
13
+ export const SESSION_WALLET_IDENTIFIED_KEY = "wallet_identified";
14
+
15
+ // Consent keys
16
+ export const CONSENT_OPT_OUT_KEY = "opt_out_tracking";
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Formo Analytics SDK for React Native
3
+ *
4
+ * Track wallet events and user analytics in mobile dApps
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import AsyncStorage from '@react-native-async-storage/async-storage';
9
+ * import { FormoAnalyticsProvider, useFormo } from '@formo/analytics-react-native';
10
+ *
11
+ * // Wrap your app with the provider
12
+ * function App() {
13
+ * return (
14
+ * <FormoAnalyticsProvider
15
+ * writeKey="your-write-key"
16
+ * asyncStorage={AsyncStorage}
17
+ * options={{
18
+ * wagmi: { config, queryClient },
19
+ * app: { name: 'MyApp', version: '1.0.0' },
20
+ * }}
21
+ * >
22
+ * <YourApp />
23
+ * </FormoAnalyticsProvider>
24
+ * );
25
+ * }
26
+ *
27
+ * // Use the hook in your components
28
+ * function MyScreen() {
29
+ * const formo = useFormo();
30
+ *
31
+ * useEffect(() => {
32
+ * formo.screen('Home');
33
+ * }, []);
34
+ *
35
+ * return <View>...</View>;
36
+ * }
37
+ * ```
38
+ */
39
+
40
+ // Main exports
41
+ export { FormoAnalytics } from "./FormoAnalytics";
42
+ export {
43
+ FormoAnalyticsProvider,
44
+ FormoAnalyticsContext,
45
+ useFormo,
46
+ } from "./FormoAnalyticsProvider";
47
+
48
+ // Types
49
+ export * from "./types";
50
+
51
+ // Event types for manual event tracking
52
+ export { SignatureStatus, TransactionStatus } from "./types/events";
53
+
54
+ // Storage types for custom storage implementations
55
+ export type { AsyncStorageInterface } from "./lib/storage";
@@ -0,0 +1,52 @@
1
+ import { storage } from "../storage";
2
+ import { logger } from "../logger";
3
+
4
+ /**
5
+ * Get consent storage key
6
+ * Note: The storage adapter already prefixes keys with formo_rn_{writeKey}_
7
+ * so we just need a simple consent prefix here
8
+ */
9
+ function getConsentKey(_writeKey: string, key: string): string {
10
+ return `consent_${key}`;
11
+ }
12
+
13
+ /**
14
+ * Set a consent flag
15
+ */
16
+ export function setConsentFlag(
17
+ writeKey: string,
18
+ key: string,
19
+ value: string
20
+ ): void {
21
+ try {
22
+ const consentKey = getConsentKey(writeKey, key);
23
+ storage().set(consentKey, value);
24
+ } catch (error) {
25
+ logger.error("Consent: Failed to set flag", error);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Get a consent flag
31
+ */
32
+ export function getConsentFlag(writeKey: string, key: string): string | null {
33
+ try {
34
+ const consentKey = getConsentKey(writeKey, key);
35
+ return storage().get(consentKey);
36
+ } catch (error) {
37
+ logger.error("Consent: Failed to get flag", error);
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Remove a consent flag
44
+ */
45
+ export function removeConsentFlag(writeKey: string, key: string): void {
46
+ try {
47
+ const consentKey = getConsentKey(writeKey, key);
48
+ storage().remove(consentKey);
49
+ } catch (error) {
50
+ logger.error("Consent: Failed to remove flag", error);
51
+ }
52
+ }