@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,685 @@
1
+ /**
2
+ * FormoAnalytics for React Native
3
+ *
4
+ * Main SDK class for tracking wallet events and user analytics in mobile dApps
5
+ */
6
+
7
+ import {
8
+ EVENTS_API_HOST,
9
+ EventType,
10
+ LOCAL_ANONYMOUS_ID_KEY,
11
+ SESSION_USER_ID_KEY,
12
+ CONSENT_OPT_OUT_KEY,
13
+ TEventType,
14
+ } from "./constants";
15
+ import { initStorageManager, storage, AsyncStorageInterface } from "./lib/storage";
16
+ import { EventManager, EventQueue, IEventManager } from "./lib/event";
17
+ import { logger, Logger } from "./lib/logger";
18
+ import {
19
+ setConsentFlag,
20
+ getConsentFlag,
21
+ removeConsentFlag,
22
+ } from "./lib/consent";
23
+ import { FormoAnalyticsSession } from "./lib/session";
24
+ import { WagmiEventHandler } from "./lib/wagmi";
25
+ import { AppLifecycleManager } from "./lib/lifecycle";
26
+ import {
27
+ Address,
28
+ ChainID,
29
+ Config,
30
+ IFormoAnalytics,
31
+ IFormoEventContext,
32
+ IFormoEventProperties,
33
+ Options,
34
+ SignatureStatus,
35
+ TrackingOptions,
36
+ TransactionStatus,
37
+ } from "./types";
38
+ import { toChecksumAddress, getValidAddress } from "./utils";
39
+ import { parseTrafficSource, storeTrafficSource } from "./utils/trafficSource";
40
+
41
+ export class FormoAnalytics implements IFormoAnalytics {
42
+ private session: FormoAnalyticsSession;
43
+ private eventManager: IEventManager;
44
+ private eventQueue: EventQueue;
45
+ private wagmiHandler?: WagmiEventHandler;
46
+ private lifecycleManager?: AppLifecycleManager;
47
+
48
+ config: Config;
49
+ currentChainId?: ChainID;
50
+ currentAddress?: Address;
51
+ currentUserId?: string = "";
52
+
53
+ private constructor(
54
+ public readonly writeKey: string,
55
+ public options: Options = {}
56
+ ) {
57
+ this.config = { writeKey };
58
+
59
+ this.session = new FormoAnalyticsSession();
60
+ this.currentUserId =
61
+ (storage().get(SESSION_USER_ID_KEY) as string) || undefined;
62
+
63
+ // Bind methods
64
+ this.identify = this.identify.bind(this);
65
+ this.connect = this.connect.bind(this);
66
+ this.disconnect = this.disconnect.bind(this);
67
+ this.chain = this.chain.bind(this);
68
+ this.signature = this.signature.bind(this);
69
+ this.transaction = this.transaction.bind(this);
70
+ this.detect = this.detect.bind(this);
71
+ this.track = this.track.bind(this);
72
+ this.screen = this.screen.bind(this);
73
+ this.isAutocaptureEnabled = this.isAutocaptureEnabled.bind(this);
74
+
75
+ // Initialize logger
76
+ Logger.init({
77
+ enabled: options.logger?.enabled || false,
78
+ enabledLevels: options.logger?.levels || [],
79
+ });
80
+
81
+ // Initialize event queue
82
+ this.eventQueue = new EventQueue(this.config.writeKey, {
83
+ apiHost: options.apiHost || EVENTS_API_HOST,
84
+ flushAt: options.flushAt,
85
+ retryCount: options.retryCount,
86
+ maxQueueSize: options.maxQueueSize,
87
+ flushInterval: options.flushInterval,
88
+ });
89
+
90
+ // Initialize event manager
91
+ this.eventManager = new EventManager(this.eventQueue, options);
92
+
93
+ // Check consent status
94
+ if (this.hasOptedOutTracking()) {
95
+ logger.info("User has previously opted out of tracking");
96
+ }
97
+
98
+ // Initialize Wagmi handler if provided and config is valid
99
+ if (options.wagmi?.config) {
100
+ logger.info("FormoAnalytics: Initializing in Wagmi mode");
101
+ this.wagmiHandler = new WagmiEventHandler(
102
+ this,
103
+ options.wagmi.config,
104
+ options.wagmi.queryClient
105
+ );
106
+ } else if (options.wagmi) {
107
+ logger.warn("FormoAnalytics: wagmi option provided but config is missing");
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Initialize the SDK
113
+ * @param writeKey - Your Formo write key
114
+ * @param options - Configuration options
115
+ * @param asyncStorage - AsyncStorage instance from @react-native-async-storage/async-storage
116
+ */
117
+ static async init(
118
+ writeKey: string,
119
+ options?: Options,
120
+ asyncStorage?: AsyncStorageInterface
121
+ ): Promise<FormoAnalytics> {
122
+ const storageManager = initStorageManager(writeKey);
123
+
124
+ // Initialize storage with AsyncStorage if provided
125
+ if (asyncStorage) {
126
+ await storageManager.initialize(asyncStorage);
127
+ }
128
+
129
+ const analytics = new FormoAnalytics(writeKey, options);
130
+
131
+ // Initialize lifecycle tracking if enabled
132
+ // Wrapped in try-catch so a transient storage failure doesn't prevent SDK init
133
+ if (analytics.isAutocaptureEnabled("lifecycle")) {
134
+ try {
135
+ analytics.lifecycleManager = new AppLifecycleManager(analytics);
136
+ await analytics.lifecycleManager.start(options?.app);
137
+ } catch (error) {
138
+ logger.error("FormoAnalytics: Failed to initialize lifecycle tracking", error);
139
+ }
140
+ }
141
+
142
+ // Call ready callback
143
+ if (options?.ready) {
144
+ options.ready(analytics);
145
+ }
146
+
147
+ return analytics;
148
+ }
149
+
150
+ /**
151
+ * Track a screen view (mobile equivalent of page view)
152
+ */
153
+ public async screen(
154
+ name: string,
155
+ category?: string,
156
+ properties?: IFormoEventProperties,
157
+ context?: IFormoEventContext,
158
+ callback?: (...args: unknown[]) => void
159
+ ): Promise<void> {
160
+ // Note: shouldTrack() is called in trackEvent() - no need to check here
161
+ await this.trackEvent(
162
+ EventType.SCREEN,
163
+ { name, ...(category && { category }) },
164
+ properties,
165
+ context,
166
+ callback
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Set traffic source from deep link URL
172
+ * Parses UTM parameters and referrer information from URL
173
+ * This is automatically persisted for the session
174
+ *
175
+ * @param url - Deep link URL (e.g., "myapp://product?utm_source=facebook&ref=friend123")
176
+ *
177
+ * @example
178
+ * ```tsx
179
+ * import { Linking } from 'react-native';
180
+ *
181
+ * // Listen for deep links
182
+ * Linking.addEventListener('url', (event) => {
183
+ * formo.setTrafficSourceFromUrl(event.url);
184
+ * });
185
+ *
186
+ * // Or get initial URL
187
+ * Linking.getInitialURL().then((url) => {
188
+ * if (url) formo.setTrafficSourceFromUrl(url);
189
+ * });
190
+ * ```
191
+ */
192
+ public setTrafficSourceFromUrl(url: string): void {
193
+ const trafficSource = parseTrafficSource(
194
+ url,
195
+ this.options.referral?.queryParams,
196
+ this.options.referral?.pathPattern
197
+ );
198
+ storeTrafficSource(trafficSource);
199
+ logger.debug("Traffic source set from URL:", trafficSource);
200
+ }
201
+
202
+ /**
203
+ * Reset the current user session
204
+ */
205
+ public reset(): void {
206
+ this.currentUserId = undefined;
207
+ storage().remove(LOCAL_ANONYMOUS_ID_KEY);
208
+ storage().remove(SESSION_USER_ID_KEY);
209
+ this.session.clear();
210
+ }
211
+
212
+ /**
213
+ * Clean up resources
214
+ */
215
+ public async cleanup(): Promise<void> {
216
+ logger.info("FormoAnalytics: Cleaning up resources");
217
+
218
+ if (this.lifecycleManager) {
219
+ this.lifecycleManager.cleanup();
220
+ this.lifecycleManager = undefined;
221
+ }
222
+
223
+ if (this.wagmiHandler) {
224
+ this.wagmiHandler.cleanup();
225
+ this.wagmiHandler = undefined;
226
+ }
227
+
228
+ if (this.eventQueue) {
229
+ await this.eventQueue.cleanup();
230
+ }
231
+
232
+ logger.info("FormoAnalytics: Cleanup complete");
233
+ }
234
+
235
+ /**
236
+ * Track wallet connect event
237
+ */
238
+ async connect(
239
+ { chainId, address }: { chainId: ChainID; address: Address },
240
+ properties?: IFormoEventProperties,
241
+ context?: IFormoEventContext,
242
+ callback?: (...args: unknown[]) => void
243
+ ): Promise<void> {
244
+ if (chainId === null || chainId === undefined || Number(chainId) === 0) {
245
+ logger.warn("Connect: Chain ID cannot be null, undefined, or 0");
246
+ return;
247
+ }
248
+ if (!address) {
249
+ logger.warn("Connect: Address cannot be empty");
250
+ return;
251
+ }
252
+
253
+ const checksummedAddress = this.validateAndChecksumAddress(address);
254
+ if (!checksummedAddress) {
255
+ logger.warn(`Connect: Invalid address provided ("${address}")`);
256
+ return;
257
+ }
258
+
259
+ // Track event before updating state so connect events TO excluded chains are tracked
260
+ await this.trackEvent(
261
+ EventType.CONNECT,
262
+ { chainId, address: checksummedAddress },
263
+ properties,
264
+ context,
265
+ callback
266
+ );
267
+
268
+ this.currentChainId = chainId;
269
+ this.currentAddress = checksummedAddress;
270
+ }
271
+
272
+ /**
273
+ * Track wallet disconnect event
274
+ */
275
+ async disconnect(
276
+ params?: { chainId?: ChainID; address?: Address },
277
+ properties?: IFormoEventProperties,
278
+ context?: IFormoEventContext,
279
+ callback?: (...args: unknown[]) => void
280
+ ): Promise<void> {
281
+ const chainId = params?.chainId || this.currentChainId;
282
+ const address = params?.address || this.currentAddress;
283
+
284
+ logger.info("Disconnect: Emitting disconnect event with:", {
285
+ chainId,
286
+ address,
287
+ });
288
+
289
+ await this.trackEvent(
290
+ EventType.DISCONNECT,
291
+ {
292
+ ...(chainId && { chainId }),
293
+ ...(address && { address }),
294
+ },
295
+ properties,
296
+ context,
297
+ callback
298
+ );
299
+
300
+ this.currentAddress = undefined;
301
+ this.currentChainId = undefined;
302
+ }
303
+
304
+ /**
305
+ * Track chain change event
306
+ */
307
+ async chain(
308
+ { chainId, address }: { chainId: ChainID; address?: Address },
309
+ properties?: IFormoEventProperties,
310
+ context?: IFormoEventContext,
311
+ callback?: (...args: unknown[]) => void
312
+ ): Promise<void> {
313
+ if (!chainId || Number(chainId) === 0) {
314
+ logger.warn("FormoAnalytics::chain: chainId cannot be empty or 0");
315
+ return;
316
+ }
317
+ if (isNaN(Number(chainId))) {
318
+ logger.warn("FormoAnalytics::chain: chainId must be a valid number");
319
+ return;
320
+ }
321
+ if (!address && !this.currentAddress) {
322
+ logger.warn("FormoAnalytics::chain: address was empty and no previous address recorded");
323
+ return;
324
+ }
325
+
326
+ // Track event before updating currentChainId so shouldTrack uses the previous chain
327
+ // This ensures chain change events TO excluded chains are still tracked
328
+ await this.trackEvent(
329
+ EventType.CHAIN,
330
+ { chainId, address: address || this.currentAddress },
331
+ properties,
332
+ context,
333
+ callback
334
+ );
335
+
336
+ this.currentChainId = chainId;
337
+ }
338
+
339
+ /**
340
+ * Track signature event
341
+ */
342
+ async signature(
343
+ {
344
+ status,
345
+ chainId,
346
+ address,
347
+ message,
348
+ signatureHash,
349
+ }: {
350
+ status: SignatureStatus;
351
+ chainId?: ChainID;
352
+ address: Address;
353
+ message: string;
354
+ signatureHash?: string;
355
+ },
356
+ properties?: IFormoEventProperties,
357
+ context?: IFormoEventContext,
358
+ callback?: (...args: unknown[]) => void
359
+ ): Promise<void> {
360
+ if (!address) {
361
+ logger.warn("Signature: Address cannot be empty");
362
+ return;
363
+ }
364
+ await this.trackEvent(
365
+ EventType.SIGNATURE,
366
+ {
367
+ status,
368
+ ...(chainId !== undefined && chainId !== null && { chainId }),
369
+ address,
370
+ message,
371
+ ...(signatureHash && { signatureHash }),
372
+ },
373
+ properties,
374
+ context,
375
+ callback
376
+ );
377
+ }
378
+
379
+ /**
380
+ * Track transaction event
381
+ */
382
+ async transaction(
383
+ {
384
+ status,
385
+ chainId,
386
+ address,
387
+ data,
388
+ to,
389
+ value,
390
+ transactionHash,
391
+ function_name,
392
+ function_args,
393
+ }: {
394
+ status: TransactionStatus;
395
+ chainId: ChainID;
396
+ address: Address;
397
+ data?: string;
398
+ to?: string;
399
+ value?: string;
400
+ transactionHash?: string;
401
+ function_name?: string;
402
+ function_args?: Record<string, unknown>;
403
+ },
404
+ properties?: IFormoEventProperties,
405
+ context?: IFormoEventContext,
406
+ callback?: (...args: unknown[]) => void
407
+ ): Promise<void> {
408
+ if (chainId === null || chainId === undefined || Number(chainId) === 0) {
409
+ logger.warn("Transaction: Chain ID cannot be null, undefined, or 0");
410
+ return;
411
+ }
412
+ if (!address) {
413
+ logger.warn("Transaction: Address cannot be empty");
414
+ return;
415
+ }
416
+ await this.trackEvent(
417
+ EventType.TRANSACTION,
418
+ {
419
+ status,
420
+ chainId,
421
+ address,
422
+ data,
423
+ to,
424
+ value,
425
+ ...(transactionHash && { transactionHash }),
426
+ ...(function_name && { function_name }),
427
+ ...(function_args && { function_args }),
428
+ },
429
+ properties,
430
+ context,
431
+ callback
432
+ );
433
+ }
434
+
435
+ /**
436
+ * Track identify event
437
+ */
438
+ async identify(
439
+ params: {
440
+ address: Address;
441
+ providerName?: string;
442
+ userId?: string;
443
+ rdns?: string;
444
+ },
445
+ properties?: IFormoEventProperties,
446
+ context?: IFormoEventContext,
447
+ callback?: (...args: unknown[]) => void
448
+ ): Promise<void> {
449
+ try {
450
+ const { userId, address, providerName, rdns } = params;
451
+ logger.info("Identify", address, userId, providerName, rdns);
452
+
453
+ let validAddress: Address | undefined = undefined;
454
+ if (address) {
455
+ validAddress = this.validateAndChecksumAddress(address);
456
+ if (!validAddress) {
457
+ logger.warn(`Identify: Invalid address provided ("${address}")`);
458
+ return;
459
+ }
460
+ this.currentAddress = validAddress;
461
+ } else {
462
+ this.currentAddress = undefined;
463
+ }
464
+
465
+ if (userId) {
466
+ this.currentUserId = userId;
467
+ storage().set(SESSION_USER_ID_KEY, userId);
468
+ }
469
+
470
+ // Check for duplicate identify
471
+ const isAlreadyIdentified = validAddress
472
+ ? this.session.isWalletIdentified(validAddress, rdns || "")
473
+ : false;
474
+
475
+ if (isAlreadyIdentified) {
476
+ logger.info(
477
+ `Identify: Wallet ${providerName || "Unknown"} with address ${validAddress} already identified`
478
+ );
479
+ return;
480
+ }
481
+
482
+ // Mark as identified
483
+ if (validAddress) {
484
+ this.session.markWalletIdentified(validAddress, rdns || "");
485
+ }
486
+
487
+ await this.trackEvent(
488
+ EventType.IDENTIFY,
489
+ { address: validAddress, providerName, userId, rdns },
490
+ properties,
491
+ context,
492
+ callback
493
+ );
494
+ } catch (e) {
495
+ logger.log("identify error", e);
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Track detect wallet event
501
+ */
502
+ async detect(
503
+ { providerName, rdns }: { providerName: string; rdns: string },
504
+ properties?: IFormoEventProperties,
505
+ context?: IFormoEventContext,
506
+ callback?: (...args: unknown[]) => void
507
+ ): Promise<void> {
508
+ if (this.session.isWalletDetected(rdns)) {
509
+ logger.warn(`Detect: Wallet ${providerName} already detected in this session`);
510
+ return;
511
+ }
512
+
513
+ this.session.markWalletDetected(rdns);
514
+ await this.trackEvent(
515
+ EventType.DETECT,
516
+ { providerName, rdns },
517
+ properties,
518
+ context,
519
+ callback
520
+ );
521
+ }
522
+
523
+ /**
524
+ * Track custom event
525
+ */
526
+ async track(
527
+ event: string,
528
+ properties?: IFormoEventProperties,
529
+ context?: IFormoEventContext,
530
+ callback?: (...args: unknown[]) => void
531
+ ): Promise<void> {
532
+ await this.trackEvent(
533
+ EventType.TRACK,
534
+ { event },
535
+ properties,
536
+ context,
537
+ callback
538
+ );
539
+ }
540
+
541
+ /**
542
+ * Opt out of tracking
543
+ */
544
+ public optOutTracking(): void {
545
+ logger.info("Opting out of tracking");
546
+ setConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY, "true");
547
+ this.eventQueue.clear();
548
+ this.reset();
549
+ logger.info("Successfully opted out of tracking");
550
+ }
551
+
552
+ /**
553
+ * Opt back into tracking
554
+ */
555
+ public optInTracking(): void {
556
+ logger.info("Opting back into tracking");
557
+ removeConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY);
558
+ logger.info("Successfully opted back into tracking");
559
+ }
560
+
561
+ /**
562
+ * Check if user has opted out
563
+ */
564
+ public hasOptedOutTracking(): boolean {
565
+ return getConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY) === "true";
566
+ }
567
+
568
+ /**
569
+ * Check if autocapture is enabled for event type
570
+ */
571
+ public isAutocaptureEnabled(
572
+ eventType: "connect" | "disconnect" | "signature" | "transaction" | "chain" | "lifecycle"
573
+ ): boolean {
574
+ if (this.options.autocapture === undefined) {
575
+ return true;
576
+ }
577
+
578
+ if (typeof this.options.autocapture === "boolean") {
579
+ return this.options.autocapture;
580
+ }
581
+
582
+ if (
583
+ this.options.autocapture !== null &&
584
+ typeof this.options.autocapture === "object"
585
+ ) {
586
+ const eventConfig = this.options.autocapture[eventType];
587
+ return eventConfig !== false;
588
+ }
589
+
590
+ return true;
591
+ }
592
+
593
+ /**
594
+ * Internal method to track events
595
+ * This is the single enforcement point for shouldTrack() - all public tracking
596
+ * methods (track, screen, connect, etc.) route through here
597
+ */
598
+ private async trackEvent(
599
+ type: TEventType,
600
+ payload?: Record<string, unknown>,
601
+ properties?: IFormoEventProperties,
602
+ context?: IFormoEventContext,
603
+ callback?: (...args: unknown[]) => void
604
+ ): Promise<void> {
605
+ try {
606
+ if (!this.shouldTrack()) {
607
+ logger.info(`Skipping ${type} event due to tracking configuration`);
608
+ return;
609
+ }
610
+
611
+ await this.eventManager.addEvent(
612
+ {
613
+ type,
614
+ ...payload,
615
+ properties,
616
+ context,
617
+ callback,
618
+ } as any,
619
+ this.currentAddress,
620
+ this.currentUserId
621
+ );
622
+ } catch (error) {
623
+ logger.error("Error tracking event:", error);
624
+ if (this.options.errorHandler) {
625
+ try {
626
+ this.options.errorHandler(error instanceof Error ? error : new Error(String(error)));
627
+ } catch (handlerError) {
628
+ logger.error("Error in errorHandler callback:", handlerError);
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Check if tracking should be enabled
636
+ */
637
+ private shouldTrack(): boolean {
638
+ // Check consent
639
+ if (this.hasOptedOutTracking()) {
640
+ return false;
641
+ }
642
+
643
+ // Check tracking option
644
+ if (typeof this.options.tracking === "boolean") {
645
+ return this.options.tracking;
646
+ }
647
+
648
+ // Handle object configuration
649
+ if (
650
+ this.options.tracking !== null &&
651
+ typeof this.options.tracking === "object" &&
652
+ !Array.isArray(this.options.tracking)
653
+ ) {
654
+ const { excludeChains = [] } = this.options.tracking as TrackingOptions;
655
+
656
+ if (
657
+ excludeChains.length > 0 &&
658
+ this.currentChainId &&
659
+ excludeChains.includes(this.currentChainId)
660
+ ) {
661
+ return false;
662
+ }
663
+
664
+ return true;
665
+ }
666
+
667
+ // Default: track
668
+ return true;
669
+ }
670
+
671
+ /**
672
+ * Validate and checksum address
673
+ */
674
+ private validateAndChecksumAddress(address: string): Address | undefined {
675
+ const validAddress = getValidAddress(address);
676
+ return validAddress ? toChecksumAddress(validAddress) : undefined;
677
+ }
678
+
679
+ /**
680
+ * Flush pending events
681
+ */
682
+ public async flush(): Promise<void> {
683
+ await this.eventQueue.flush();
684
+ }
685
+ }