@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,682 @@
1
+ import { Platform, NativeModules, Dimensions } from "react-native";
2
+ import NetInfo from "@react-native-community/netinfo";
3
+
4
+ // Lazy load device info to handle Expo Go where native modules may not exist
5
+ let DeviceInfo: typeof import("react-native-device-info").default | null = null;
6
+ let ExpoDevice: typeof import("expo-device") | null = null;
7
+ let ExpoApplication: typeof import("expo-application") | null = null;
8
+
9
+ // Try to load react-native-device-info (works in bare RN and dev builds)
10
+ try {
11
+ DeviceInfo = require("react-native-device-info").default;
12
+ } catch {
13
+ // Not available - try Expo alternatives
14
+ }
15
+
16
+ // Try to load Expo modules (works in Expo Go and Expo dev builds)
17
+ try {
18
+ ExpoDevice = require("expo-device");
19
+ } catch {
20
+ // Not available
21
+ }
22
+
23
+ try {
24
+ ExpoApplication = require("expo-application");
25
+ } catch {
26
+ // Not available
27
+ }
28
+ import { COUNTRY_LIST, LOCAL_ANONYMOUS_ID_KEY, CHANNEL, VERSION } from "../../constants";
29
+ import {
30
+ Address,
31
+ APIEvent,
32
+ ChainID,
33
+ IFormoEvent,
34
+ IFormoEventContext,
35
+ IFormoEventProperties,
36
+ Nullable,
37
+ Options,
38
+ SignatureStatus,
39
+ TransactionStatus,
40
+ } from "../../types";
41
+ import {
42
+ toChecksumAddress,
43
+ getValidAddress,
44
+ toSnakeCase,
45
+ mergeDeepRight,
46
+ getStoredTrafficSource,
47
+ } from "../../utils";
48
+ import { getCurrentTimeFormatted } from "../../utils/timestamp";
49
+ import { generateUUID } from "../../utils/hash";
50
+ import { logger } from "../logger";
51
+ import { storage } from "../storage";
52
+ import { IEventFactory } from "./types";
53
+ import { version as SDK_VERSION } from "../../version";
54
+
55
+ /**
56
+ * Generate or retrieve anonymous ID
57
+ */
58
+ function generateAnonymousId(key: string): string {
59
+ const existing = storage().get(key);
60
+ if (existing) {
61
+ return existing;
62
+ }
63
+
64
+ const newId = generateUUID();
65
+ storage().set(key, newId);
66
+ return newId;
67
+ }
68
+
69
+ /**
70
+ * Event factory for React Native
71
+ * Creates event payloads with mobile-specific context
72
+ */
73
+ class EventFactory implements IEventFactory {
74
+ private options?: Options;
75
+
76
+ constructor(options?: Options) {
77
+ this.options = options;
78
+ }
79
+
80
+ /**
81
+ * Get device timezone
82
+ */
83
+ private getTimezone(): string {
84
+ try {
85
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
86
+ } catch (error) {
87
+ logger.error("Error resolving timezone:", error);
88
+ return "";
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Get location from timezone
94
+ */
95
+ private getLocation(): string {
96
+ try {
97
+ const timezone = this.getTimezone();
98
+ if (timezone in COUNTRY_LIST) {
99
+ return COUNTRY_LIST[timezone] ?? timezone;
100
+ }
101
+ return timezone;
102
+ } catch (error) {
103
+ logger.error("Error resolving location:", error);
104
+ return "";
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get device language/locale
110
+ */
111
+ private getLanguage(): string {
112
+ try {
113
+ // Try to get the device locale
114
+ const locale =
115
+ Platform.OS === "ios"
116
+ ? NativeModules.SettingsManager?.settings?.AppleLocale ||
117
+ NativeModules.SettingsManager?.settings?.AppleLanguages?.[0]
118
+ : NativeModules.I18nManager?.localeIdentifier;
119
+
120
+ return locale || "en";
121
+ } catch (error) {
122
+ logger.debug("Error resolving language:", error);
123
+ return "en";
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get screen dimensions
129
+ */
130
+ private getScreen(): {
131
+ screen_width: number;
132
+ screen_height: number;
133
+ screen_density: number;
134
+ } {
135
+ try {
136
+ const { width, height, scale } = Dimensions.get("screen");
137
+ return {
138
+ screen_width: Math.round(width),
139
+ screen_height: Math.round(height),
140
+ screen_density: scale,
141
+ };
142
+ } catch (error) {
143
+ logger.error("Error resolving screen properties:", error);
144
+ return {
145
+ screen_width: 0,
146
+ screen_height: 0,
147
+ screen_density: 1,
148
+ };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get network information
154
+ */
155
+ private async getNetworkInfo(): Promise<{
156
+ network_wifi?: boolean;
157
+ network_cellular?: boolean;
158
+ network_carrier?: string;
159
+ }> {
160
+ try {
161
+ const netState = await NetInfo.fetch();
162
+
163
+ const networkInfo: {
164
+ network_wifi?: boolean;
165
+ network_cellular?: boolean;
166
+ network_carrier?: string;
167
+ } = {};
168
+
169
+ // Set connection type flags
170
+ if (netState.type === "wifi") {
171
+ networkInfo.network_wifi = true;
172
+ networkInfo.network_cellular = false;
173
+ } else if (netState.type === "cellular") {
174
+ networkInfo.network_wifi = false;
175
+ networkInfo.network_cellular = true;
176
+
177
+ // Get carrier name for cellular connections
178
+ if (netState.details && "carrier" in netState.details) {
179
+ networkInfo.network_carrier = netState.details.carrier || undefined;
180
+ }
181
+ } else {
182
+ // Other types (ethernet, bluetooth, wimax, vpn, other, unknown, none)
183
+ networkInfo.network_wifi = false;
184
+ networkInfo.network_cellular = false;
185
+ }
186
+
187
+ return networkInfo;
188
+ } catch (error) {
189
+ logger.debug("Error getting network info:", error);
190
+ return {};
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Get device information
196
+ * Supports both react-native-device-info (bare RN) and expo-device/expo-application (Expo Go)
197
+ */
198
+ private async getDeviceInfo(): Promise<{
199
+ os_name: string;
200
+ os_version: string;
201
+ device_model: string;
202
+ device_manufacturer: string;
203
+ device_name: string;
204
+ device_type: string;
205
+ user_agent: string;
206
+ app_name: string;
207
+ app_version: string;
208
+ app_build: string;
209
+ app_bundle_id: string;
210
+ }> {
211
+ // Try react-native-device-info first (bare RN and Expo dev builds)
212
+ if (DeviceInfo) {
213
+ try {
214
+ const [model, manufacturer, deviceName, userAgent, isTablet] = await Promise.all([
215
+ DeviceInfo.getModel(),
216
+ DeviceInfo.getManufacturer(),
217
+ DeviceInfo.getDeviceName(),
218
+ DeviceInfo.getUserAgent(),
219
+ DeviceInfo.isTablet(),
220
+ ]);
221
+
222
+ return {
223
+ os_name: Platform.OS,
224
+ os_version: DeviceInfo.getSystemVersion(),
225
+ device_model: model,
226
+ device_manufacturer: manufacturer,
227
+ device_name: deviceName,
228
+ device_type: isTablet ? "tablet" : "mobile",
229
+ user_agent: userAgent,
230
+ app_name: DeviceInfo.getApplicationName(),
231
+ app_version: DeviceInfo.getVersion(),
232
+ app_build: DeviceInfo.getBuildNumber(),
233
+ app_bundle_id: DeviceInfo.getBundleId(),
234
+ };
235
+ } catch (error) {
236
+ logger.debug("Error using react-native-device-info, falling back:", error);
237
+ }
238
+ }
239
+
240
+ // Fall back to Expo modules (Expo Go)
241
+ if (ExpoDevice || ExpoApplication) {
242
+ try {
243
+ const isTablet = ExpoDevice?.deviceType === ExpoDevice?.DeviceType?.TABLET;
244
+ return {
245
+ os_name: ExpoDevice?.osName || Platform.OS,
246
+ os_version: ExpoDevice?.osVersion || String(Platform.Version),
247
+ device_model: ExpoDevice?.modelName || "Unknown",
248
+ device_manufacturer: ExpoDevice?.manufacturer || "Unknown",
249
+ device_name: ExpoDevice?.deviceName || "Unknown Device",
250
+ device_type: isTablet ? "tablet" : "mobile",
251
+ user_agent: "",
252
+ app_name: ExpoApplication?.applicationName || "",
253
+ app_version: ExpoApplication?.nativeApplicationVersion || "",
254
+ app_build: ExpoApplication?.nativeBuildVersion || "",
255
+ app_bundle_id: ExpoApplication?.applicationId || "",
256
+ };
257
+ } catch (error) {
258
+ logger.debug("Error using Expo device modules:", error);
259
+ }
260
+ }
261
+
262
+ // Final fallback - minimal info from Platform
263
+ logger.debug("No device info modules available, using Platform defaults");
264
+ return {
265
+ os_name: Platform.OS,
266
+ os_version: String(Platform.Version),
267
+ device_model: "Unknown",
268
+ device_manufacturer: "Unknown",
269
+ device_name: "Unknown Device",
270
+ device_type: "mobile",
271
+ user_agent: "",
272
+ app_name: "",
273
+ app_version: "",
274
+ app_build: "",
275
+ app_bundle_id: "",
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Generate context with mobile-specific metadata
281
+ */
282
+ private async generateContext(
283
+ context?: IFormoEventContext
284
+ ): Promise<IFormoEventContext> {
285
+ const language = this.getLanguage();
286
+ const timezone = this.getTimezone();
287
+ const location = this.getLocation();
288
+ const deviceInfo = await this.getDeviceInfo();
289
+ const networkInfo = await this.getNetworkInfo();
290
+ const screenInfo = this.getScreen();
291
+
292
+ // Get stored traffic source from session (UTM params, referrer from deep links)
293
+ const storedTrafficSource = getStoredTrafficSource();
294
+
295
+ const defaultContext: IFormoEventContext = {
296
+ locale: language,
297
+ timezone,
298
+ location,
299
+ library_name: "Formo React Native SDK",
300
+ library_version: SDK_VERSION,
301
+ ...deviceInfo,
302
+ ...networkInfo,
303
+ ...screenInfo,
304
+ // App info from options (overrides auto-detected values)
305
+ ...(this.options?.app?.name && { app_name: this.options.app.name }),
306
+ ...(this.options?.app?.version && { app_version: this.options.app.version }),
307
+ ...(this.options?.app?.build && { app_build: this.options.app.build }),
308
+ ...(this.options?.app?.bundleId && { app_bundle_id: this.options.app.bundleId }),
309
+ // Traffic source (UTM params, referrer) from session
310
+ ...(storedTrafficSource || {}),
311
+ };
312
+
313
+ const mergedContext = mergeDeepRight(
314
+ defaultContext,
315
+ context || {}
316
+ ) as IFormoEventContext;
317
+
318
+ return mergedContext;
319
+ }
320
+
321
+ /**
322
+ * Create enriched event with common properties
323
+ */
324
+ private async getEnrichedEvent(
325
+ formoEvent: Partial<IFormoEvent>,
326
+ context?: IFormoEventContext
327
+ ): Promise<IFormoEvent> {
328
+ const commonEventData: Partial<IFormoEvent> = {
329
+ context: await this.generateContext(context),
330
+ original_timestamp: getCurrentTimeFormatted(),
331
+ user_id: formoEvent.user_id,
332
+ type: formoEvent.type,
333
+ channel: CHANNEL,
334
+ version: VERSION,
335
+ };
336
+
337
+ commonEventData.anonymous_id = generateAnonymousId(LOCAL_ANONYMOUS_ID_KEY);
338
+
339
+ // Handle address - convert undefined to null for consistency
340
+ const validAddress = getValidAddress(formoEvent.address);
341
+ if (validAddress) {
342
+ commonEventData.address = toChecksumAddress(validAddress);
343
+ } else {
344
+ commonEventData.address = null;
345
+ }
346
+
347
+ const processedEvent = mergeDeepRight(
348
+ formoEvent as Record<string, unknown>,
349
+ commonEventData as Record<string, unknown>
350
+ ) as unknown as IFormoEvent;
351
+
352
+ if (processedEvent.event === undefined) {
353
+ processedEvent.event = null;
354
+ }
355
+
356
+ if (processedEvent.properties === undefined) {
357
+ processedEvent.properties = null;
358
+ }
359
+
360
+ // Extract function_args before snake_case conversion to preserve ABI parameter names
361
+ // (e.g., "tokenId" should not become "token_id" since it's a contract ABI name)
362
+ const functionArgs = (processedEvent.properties as Record<string, unknown>)?.function_args;
363
+
364
+ const converted = toSnakeCase(processedEvent as unknown as Record<string, unknown>) as unknown as IFormoEvent;
365
+
366
+ // Re-attach function_args with original key casing
367
+ if (functionArgs && converted.properties) {
368
+ (converted.properties as Record<string, unknown>).function_args = functionArgs;
369
+ }
370
+
371
+ return converted;
372
+ }
373
+
374
+ /**
375
+ * Generate screen view event (mobile equivalent of page)
376
+ */
377
+ async generateScreenEvent(
378
+ name: string,
379
+ category?: string,
380
+ properties?: IFormoEventProperties,
381
+ context?: IFormoEventContext
382
+ ): Promise<IFormoEvent> {
383
+ const props = { ...(properties ?? {}), name, ...(category && { category }) };
384
+
385
+ const screenEvent: Partial<IFormoEvent> = {
386
+ properties: props,
387
+ type: "screen",
388
+ };
389
+
390
+ return this.getEnrichedEvent(screenEvent, context);
391
+ }
392
+
393
+ async generateDetectWalletEvent(
394
+ providerName: string,
395
+ rdns: string,
396
+ properties?: IFormoEventProperties,
397
+ context?: IFormoEventContext
398
+ ): Promise<IFormoEvent> {
399
+ const detectEvent: Partial<IFormoEvent> = {
400
+ properties: {
401
+ providerName,
402
+ rdns,
403
+ ...properties,
404
+ },
405
+ type: "detect",
406
+ };
407
+
408
+ return this.getEnrichedEvent(detectEvent, context);
409
+ }
410
+
411
+ async generateIdentifyEvent(
412
+ providerName: string,
413
+ rdns: string,
414
+ address: Nullable<Address>,
415
+ userId?: Nullable<string>,
416
+ properties?: IFormoEventProperties,
417
+ context?: IFormoEventContext
418
+ ): Promise<IFormoEvent> {
419
+ const identifyEvent: Partial<IFormoEvent> = {
420
+ properties: {
421
+ providerName,
422
+ rdns,
423
+ ...properties,
424
+ },
425
+ user_id: userId,
426
+ address,
427
+ type: "identify",
428
+ };
429
+
430
+ return this.getEnrichedEvent(identifyEvent, context);
431
+ }
432
+
433
+ async generateConnectEvent(
434
+ chainId: ChainID,
435
+ address: Address,
436
+ properties?: IFormoEventProperties,
437
+ context?: IFormoEventContext
438
+ ): Promise<IFormoEvent> {
439
+ const connectEvent: Partial<IFormoEvent> = {
440
+ properties: {
441
+ chainId,
442
+ ...properties,
443
+ },
444
+ address,
445
+ type: "connect",
446
+ };
447
+
448
+ return this.getEnrichedEvent(connectEvent, context);
449
+ }
450
+
451
+ async generateDisconnectEvent(
452
+ chainId?: ChainID,
453
+ address?: Address,
454
+ properties?: IFormoEventProperties,
455
+ context?: IFormoEventContext
456
+ ): Promise<IFormoEvent> {
457
+ const disconnectEvent: Partial<IFormoEvent> = {
458
+ properties: {
459
+ chainId,
460
+ ...properties,
461
+ },
462
+ address,
463
+ type: "disconnect",
464
+ };
465
+
466
+ return this.getEnrichedEvent(disconnectEvent, context);
467
+ }
468
+
469
+ async generateChainChangedEvent(
470
+ chainId: ChainID,
471
+ address: Address,
472
+ properties?: IFormoEventProperties,
473
+ context?: IFormoEventContext
474
+ ): Promise<IFormoEvent> {
475
+ const chainEvent: Partial<IFormoEvent> = {
476
+ properties: {
477
+ chainId,
478
+ ...properties,
479
+ },
480
+ address,
481
+ type: "chain",
482
+ };
483
+
484
+ return this.getEnrichedEvent(chainEvent, context);
485
+ }
486
+
487
+ async generateSignatureEvent(
488
+ status: SignatureStatus,
489
+ chainId: ChainID | undefined,
490
+ address: Address,
491
+ message: string,
492
+ signatureHash?: string,
493
+ properties?: IFormoEventProperties,
494
+ context?: IFormoEventContext
495
+ ): Promise<IFormoEvent> {
496
+ const signatureEvent: Partial<IFormoEvent> = {
497
+ properties: {
498
+ status,
499
+ ...(chainId !== undefined && chainId !== null && { chainId }),
500
+ message,
501
+ ...(signatureHash && { signatureHash }),
502
+ ...properties,
503
+ },
504
+ address,
505
+ type: "signature",
506
+ };
507
+
508
+ return this.getEnrichedEvent(signatureEvent, context);
509
+ }
510
+
511
+ async generateTransactionEvent(
512
+ status: TransactionStatus,
513
+ chainId: ChainID,
514
+ address: Address,
515
+ data?: string,
516
+ to?: string,
517
+ value?: string,
518
+ transactionHash?: string,
519
+ function_name?: string,
520
+ function_args?: Record<string, unknown>,
521
+ properties?: IFormoEventProperties,
522
+ context?: IFormoEventContext
523
+ ): Promise<IFormoEvent> {
524
+ const transactionEvent: Partial<IFormoEvent> = {
525
+ properties: {
526
+ status,
527
+ chainId,
528
+ data,
529
+ to,
530
+ value,
531
+ ...(transactionHash && { transactionHash }),
532
+ ...(function_name && { function_name }),
533
+ ...(function_args && { function_args }),
534
+ ...properties,
535
+ },
536
+ address,
537
+ type: "transaction",
538
+ };
539
+
540
+ return this.getEnrichedEvent(transactionEvent, context);
541
+ }
542
+
543
+ async generateTrackEvent(
544
+ event: string,
545
+ properties?: IFormoEventProperties,
546
+ context?: IFormoEventContext
547
+ ): Promise<IFormoEvent> {
548
+ const trackEvent: Partial<IFormoEvent> = {
549
+ properties: {
550
+ ...properties,
551
+ ...(properties?.revenue !== undefined && {
552
+ revenue: Number(properties.revenue),
553
+ currency: (typeof properties?.currency === "string"
554
+ ? properties.currency
555
+ : "USD"
556
+ ).toLowerCase(),
557
+ }),
558
+ ...(properties?.points !== undefined && {
559
+ points: Number(properties.points),
560
+ }),
561
+ ...(properties?.volume !== undefined && {
562
+ volume: Number(properties.volume),
563
+ }),
564
+ },
565
+ event,
566
+ type: "track",
567
+ };
568
+
569
+ return this.getEnrichedEvent(trackEvent, context);
570
+ }
571
+
572
+ /**
573
+ * Create event from API event type
574
+ */
575
+ async create(
576
+ event: APIEvent,
577
+ address?: Address,
578
+ userId?: string
579
+ ): Promise<IFormoEvent> {
580
+ let formoEvent: Partial<IFormoEvent> = {};
581
+
582
+ switch (event.type) {
583
+ case "screen":
584
+ formoEvent = await this.generateScreenEvent(
585
+ event.name,
586
+ event.category,
587
+ event.properties,
588
+ event.context
589
+ );
590
+ break;
591
+ case "detect":
592
+ formoEvent = await this.generateDetectWalletEvent(
593
+ event.providerName,
594
+ event.rdns,
595
+ event.properties,
596
+ event.context
597
+ );
598
+ break;
599
+ case "identify":
600
+ formoEvent = await this.generateIdentifyEvent(
601
+ event.providerName,
602
+ event.rdns,
603
+ event.address,
604
+ event.userId,
605
+ event.properties,
606
+ event.context
607
+ );
608
+ break;
609
+ case "chain":
610
+ formoEvent = await this.generateChainChangedEvent(
611
+ event.chainId,
612
+ event.address,
613
+ event.properties,
614
+ event.context
615
+ );
616
+ break;
617
+ case "connect":
618
+ formoEvent = await this.generateConnectEvent(
619
+ event.chainId,
620
+ event.address,
621
+ event.properties,
622
+ event.context
623
+ );
624
+ break;
625
+ case "disconnect":
626
+ formoEvent = await this.generateDisconnectEvent(
627
+ event.chainId,
628
+ event.address,
629
+ event.properties,
630
+ event.context
631
+ );
632
+ break;
633
+ case "signature":
634
+ formoEvent = await this.generateSignatureEvent(
635
+ event.status,
636
+ event.chainId,
637
+ event.address,
638
+ event.message,
639
+ event.signatureHash,
640
+ event.properties,
641
+ event.context
642
+ );
643
+ break;
644
+ case "transaction":
645
+ formoEvent = await this.generateTransactionEvent(
646
+ event.status,
647
+ event.chainId,
648
+ event.address,
649
+ event.data,
650
+ event.to,
651
+ event.value,
652
+ event.transactionHash,
653
+ event.function_name,
654
+ event.function_args,
655
+ event.properties,
656
+ event.context
657
+ );
658
+ break;
659
+ case "track":
660
+ default:
661
+ formoEvent = await this.generateTrackEvent(
662
+ event.event,
663
+ event.properties,
664
+ event.context
665
+ );
666
+ break;
667
+ }
668
+
669
+ // Set address if not already set by the specific event generator
670
+ if (formoEvent.address === undefined || formoEvent.address === null) {
671
+ const validAddress = getValidAddress(address);
672
+ formoEvent.address = validAddress
673
+ ? toChecksumAddress(validAddress)
674
+ : null;
675
+ }
676
+ formoEvent.user_id = userId || null;
677
+
678
+ return formoEvent as IFormoEvent;
679
+ }
680
+ }
681
+
682
+ export { EventFactory };