@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,215 @@
1
+ /**
2
+ * Application Lifecycle Event Manager
3
+ *
4
+ * Tracks application lifecycle events following the Segment/RudderStack spec:
5
+ * - Application Installed (first launch)
6
+ * - Application Updated (version/build changed)
7
+ * - Application Opened (every cold start + foreground return)
8
+ * - Application Backgrounded (app goes to background)
9
+ *
10
+ * Detection is JS-side using AsyncStorage (no native modules required).
11
+ */
12
+
13
+ import { AppState, AppStateStatus, Linking } from "react-native";
14
+ import { logger } from "../logger";
15
+ import { storage, getStorageManager } from "../storage";
16
+ import {
17
+ LOCAL_APP_VERSION_KEY,
18
+ LOCAL_APP_BUILD_KEY,
19
+ } from "../../constants/storage";
20
+
21
+ /** Interface for the analytics instance to avoid circular deps */
22
+ interface IAnalyticsInstance {
23
+ track(event: string, properties?: Record<string, unknown>): Promise<void>;
24
+ }
25
+
26
+ /** App version info resolved from device or config */
27
+ interface AppVersionInfo {
28
+ version: string;
29
+ build: string;
30
+ }
31
+
32
+ /**
33
+ * Resolves current app version and build from device info modules or options.
34
+ * Uses the same fallback chain as EventFactory.getDeviceInfo().
35
+ */
36
+ async function resolveAppVersionInfo(
37
+ appOptions?: { version?: string; build?: string }
38
+ ): Promise<AppVersionInfo> {
39
+ // Use explicit options first
40
+ if (appOptions?.version && appOptions?.build) {
41
+ return { version: appOptions.version, build: appOptions.build };
42
+ }
43
+
44
+ // Try react-native-device-info
45
+ try {
46
+ const DeviceInfo = require("react-native-device-info").default;
47
+ return {
48
+ version: appOptions?.version || DeviceInfo.getVersion(),
49
+ build: appOptions?.build || DeviceInfo.getBuildNumber(),
50
+ };
51
+ } catch {
52
+ // Not available
53
+ }
54
+
55
+ // Try expo-application
56
+ try {
57
+ const ExpoApplication = require("expo-application");
58
+ return {
59
+ version: appOptions?.version || ExpoApplication.nativeApplicationVersion || "",
60
+ build: appOptions?.build || ExpoApplication.nativeBuildVersion || "",
61
+ };
62
+ } catch {
63
+ // Not available
64
+ }
65
+
66
+ return {
67
+ version: appOptions?.version || "",
68
+ build: appOptions?.build || "",
69
+ };
70
+ }
71
+
72
+ export class AppLifecycleManager {
73
+ private analytics: IAnalyticsInstance;
74
+ private appStateSubscription: { remove: () => void } | null = null;
75
+ private lastAppState: AppStateStatus = AppState.currentState;
76
+ private appVersionInfo: AppVersionInfo = { version: "", build: "" };
77
+
78
+ constructor(analytics: IAnalyticsInstance) {
79
+ this.analytics = analytics;
80
+ }
81
+
82
+ /**
83
+ * Initialize lifecycle tracking.
84
+ * Detects install/update, fires Application Opened, and sets up AppState listener.
85
+ */
86
+ async start(
87
+ appOptions?: { version?: string; build?: string }
88
+ ): Promise<void> {
89
+ this.appVersionInfo = await resolveAppVersionInfo(appOptions);
90
+
91
+ // Detect install vs update
92
+ await this.detectInstallOrUpdate();
93
+
94
+ // Fire Application Opened (cold start)
95
+ let initialUrl: string | undefined;
96
+ try {
97
+ const url = await Linking.getInitialURL();
98
+ if (url) {
99
+ initialUrl = url;
100
+ }
101
+ } catch {
102
+ // Linking not available
103
+ }
104
+
105
+ await this.analytics.track("Application Opened", {
106
+ version: this.appVersionInfo.version,
107
+ build: this.appVersionInfo.build,
108
+ from_background: false,
109
+ ...(initialUrl && { url: initialUrl }),
110
+ });
111
+
112
+ // Subscribe to AppState changes
113
+ this.appStateSubscription = AppState.addEventListener(
114
+ "change",
115
+ this.handleAppStateChange.bind(this)
116
+ );
117
+
118
+ logger.info("AppLifecycleManager: Started");
119
+ }
120
+
121
+ /**
122
+ * Compare stored version/build with current to detect install or update.
123
+ * Requires persistent storage (AsyncStorage) — skips if only MemoryStorage is available,
124
+ * since MemoryStorage is empty on every cold start and would false-positive as "installed".
125
+ */
126
+ private async detectInstallOrUpdate(): Promise<void> {
127
+ const manager = getStorageManager();
128
+ const hasPersistentStorage = manager?.hasPersistentStorage() ?? false;
129
+
130
+ if (!hasPersistentStorage) {
131
+ logger.warn(
132
+ "AppLifecycleManager: AsyncStorage not available, skipping install/update detection. " +
133
+ "Provide asyncStorage to FormoAnalyticsProvider for accurate lifecycle tracking."
134
+ );
135
+ return;
136
+ }
137
+
138
+ const previousVersion = storage().get(LOCAL_APP_VERSION_KEY) as string | null;
139
+ const previousBuild = storage().get(LOCAL_APP_BUILD_KEY) as string | null;
140
+
141
+ const { version, build } = this.appVersionInfo;
142
+
143
+ if (previousVersion === null && previousBuild === null) {
144
+ // No stored version — first install
145
+ logger.info("AppLifecycleManager: Application Installed");
146
+ await this.analytics.track("Application Installed", {
147
+ version,
148
+ build,
149
+ });
150
+ } else if (previousVersion !== version || previousBuild !== build) {
151
+ // Version or build changed — update
152
+ logger.info("AppLifecycleManager: Application Updated");
153
+ await this.analytics.track("Application Updated", {
154
+ version,
155
+ build,
156
+ previous_version: previousVersion || "",
157
+ previous_build: previousBuild || "",
158
+ });
159
+ }
160
+
161
+ // Persist current version/build for next comparison
162
+ // Use setAsync to ensure data is written to AsyncStorage before continuing,
163
+ // preventing duplicate install events if the app is terminated before persistence completes
164
+ await storage().setAsync(LOCAL_APP_VERSION_KEY, version);
165
+ await storage().setAsync(LOCAL_APP_BUILD_KEY, build);
166
+ }
167
+
168
+ /**
169
+ * Handle AppState transitions for foreground/background events.
170
+ */
171
+ private handleAppStateChange(nextAppState: AppStateStatus): void {
172
+ // Ignore "inactive" (iOS transitional state) and "unknown"
173
+ if (nextAppState === "inactive" || nextAppState === "unknown") {
174
+ return;
175
+ }
176
+
177
+ if (nextAppState === "active" && this.lastAppState === "background") {
178
+ // Returning from background
179
+ this.analytics
180
+ .track("Application Opened", {
181
+ version: this.appVersionInfo.version,
182
+ build: this.appVersionInfo.build,
183
+ from_background: true,
184
+ })
185
+ .catch((error) => {
186
+ logger.error("AppLifecycleManager: Error tracking Application Opened", error);
187
+ });
188
+ }
189
+
190
+ if (nextAppState === "background" && this.lastAppState === "active") {
191
+ // Going to background
192
+ this.analytics
193
+ .track("Application Backgrounded", {
194
+ version: this.appVersionInfo.version,
195
+ build: this.appVersionInfo.build,
196
+ })
197
+ .catch((error) => {
198
+ logger.error("AppLifecycleManager: Error tracking Application Backgrounded", error);
199
+ });
200
+ }
201
+
202
+ this.lastAppState = nextAppState;
203
+ }
204
+
205
+ /**
206
+ * Clean up AppState listener.
207
+ */
208
+ cleanup(): void {
209
+ if (this.appStateSubscription) {
210
+ this.appStateSubscription.remove();
211
+ this.appStateSubscription = null;
212
+ }
213
+ logger.info("AppLifecycleManager: Cleaned up");
214
+ }
215
+ }
@@ -0,0 +1,56 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "log";
2
+
3
+ interface LoggerConfig {
4
+ enabled?: boolean;
5
+ enabledLevels?: LogLevel[];
6
+ }
7
+
8
+ class LoggerClass {
9
+ private config: LoggerConfig = {
10
+ enabled: false,
11
+ enabledLevels: [],
12
+ };
13
+
14
+ init(config: LoggerConfig) {
15
+ this.config = config;
16
+ }
17
+
18
+ private shouldLog(level: LogLevel): boolean {
19
+ if (!this.config.enabled) return false;
20
+ if (this.config.enabledLevels?.length === 0) return true;
21
+ return this.config.enabledLevels?.includes(level) ?? false;
22
+ }
23
+
24
+ debug(...args: unknown[]) {
25
+ if (this.shouldLog("debug")) {
26
+ console.debug("[Formo RN]", ...args);
27
+ }
28
+ }
29
+
30
+ info(...args: unknown[]) {
31
+ if (this.shouldLog("info")) {
32
+ console.info("[Formo RN]", ...args);
33
+ }
34
+ }
35
+
36
+ warn(...args: unknown[]) {
37
+ if (this.shouldLog("warn")) {
38
+ console.warn("[Formo RN]", ...args);
39
+ }
40
+ }
41
+
42
+ error(...args: unknown[]) {
43
+ if (this.shouldLog("error")) {
44
+ console.error("[Formo RN]", ...args);
45
+ }
46
+ }
47
+
48
+ log(...args: unknown[]) {
49
+ if (this.shouldLog("log")) {
50
+ console.log("[Formo RN]", ...args);
51
+ }
52
+ }
53
+ }
54
+
55
+ export const Logger = new LoggerClass();
56
+ export const logger = Logger;
@@ -0,0 +1,103 @@
1
+ import {
2
+ SESSION_WALLET_DETECTED_KEY,
3
+ SESSION_WALLET_IDENTIFIED_KEY,
4
+ } from "../../constants";
5
+ import { storage } from "../storage";
6
+ import { logger } from "../logger";
7
+
8
+ export { SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY };
9
+
10
+ /**
11
+ * Session manager for tracking wallet detection and identification
12
+ * Persists to session storage to avoid duplicate detection/identification events
13
+ * within the same session
14
+ */
15
+ export class FormoAnalyticsSession {
16
+ private detectedWallets: Set<string> = new Set();
17
+ private identifiedWallets: Set<string> = new Set();
18
+
19
+ constructor() {
20
+ this.loadFromStorage();
21
+ }
22
+
23
+ /**
24
+ * Load session state from storage
25
+ */
26
+ private loadFromStorage(): void {
27
+ try {
28
+ const detected = storage().get(SESSION_WALLET_DETECTED_KEY);
29
+ if (detected) {
30
+ const parsed = JSON.parse(detected) as string[];
31
+ this.detectedWallets = new Set(parsed);
32
+ }
33
+
34
+ const identified = storage().get(SESSION_WALLET_IDENTIFIED_KEY);
35
+ if (identified) {
36
+ const parsed = JSON.parse(identified) as string[];
37
+ this.identifiedWallets = new Set(parsed);
38
+ }
39
+ } catch (error) {
40
+ logger.debug("Session: Failed to load from storage", error);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Save session state to storage
46
+ */
47
+ private saveToStorage(): void {
48
+ try {
49
+ storage().set(
50
+ SESSION_WALLET_DETECTED_KEY,
51
+ JSON.stringify(Array.from(this.detectedWallets))
52
+ );
53
+ storage().set(
54
+ SESSION_WALLET_IDENTIFIED_KEY,
55
+ JSON.stringify(Array.from(this.identifiedWallets))
56
+ );
57
+ } catch (error) {
58
+ logger.debug("Session: Failed to save to storage", error);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Check if a wallet has been detected in this session
64
+ */
65
+ public isWalletDetected(rdns: string): boolean {
66
+ return this.detectedWallets.has(rdns);
67
+ }
68
+
69
+ /**
70
+ * Mark a wallet as detected
71
+ */
72
+ public markWalletDetected(rdns: string): void {
73
+ this.detectedWallets.add(rdns);
74
+ this.saveToStorage();
75
+ }
76
+
77
+ /**
78
+ * Check if a wallet + address combination has been identified
79
+ */
80
+ public isWalletIdentified(address: string, rdns: string): boolean {
81
+ const key = `${address.toLowerCase()}:${rdns}`;
82
+ return this.identifiedWallets.has(key);
83
+ }
84
+
85
+ /**
86
+ * Mark a wallet + address combination as identified
87
+ */
88
+ public markWalletIdentified(address: string, rdns: string): void {
89
+ const key = `${address.toLowerCase()}:${rdns}`;
90
+ this.identifiedWallets.add(key);
91
+ this.saveToStorage();
92
+ }
93
+
94
+ /**
95
+ * Clear all session data
96
+ */
97
+ public clear(): void {
98
+ this.detectedWallets.clear();
99
+ this.identifiedWallets.clear();
100
+ storage().remove(SESSION_WALLET_DETECTED_KEY);
101
+ storage().remove(SESSION_WALLET_IDENTIFIED_KEY);
102
+ }
103
+ }
@@ -0,0 +1,173 @@
1
+ import StorageBlueprint from "./StorageBlueprint";
2
+ import { AsyncStorageInterface } from "./types";
3
+ import { logger } from "../logger";
4
+
5
+ /**
6
+ * AsyncStorage adapter for React Native
7
+ * Provides persistent storage across app restarts
8
+ */
9
+ class AsyncStorageAdapter extends StorageBlueprint {
10
+ private asyncStorage: AsyncStorageInterface | null = null;
11
+ private cache: Map<string, string> = new Map();
12
+
13
+ constructor(writeKey: string, asyncStorage?: AsyncStorageInterface) {
14
+ super(writeKey);
15
+ if (asyncStorage) {
16
+ this.asyncStorage = asyncStorage;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Initialize with AsyncStorage instance and preload all Formo keys
22
+ * This ensures consent flags and other critical data are available synchronously
23
+ */
24
+ public async initialize(asyncStorage: AsyncStorageInterface): Promise<void> {
25
+ this.asyncStorage = asyncStorage;
26
+
27
+ // Preload all Formo keys into cache for synchronous access
28
+ // This is critical for consent checks on cold start (GDPR compliance)
29
+ try {
30
+ const allKeys = await asyncStorage.getAllKeys();
31
+ // getKey("") returns "formo_rn_{writeKey}_" - use this exact prefix
32
+ // to avoid matching keys from other instances (e.g., "abc" matching "abc123")
33
+ const formoPrefix = this.getKey("");
34
+
35
+ // Filter to only our keys (exact prefix match including trailing underscore)
36
+ const formoKeys = allKeys.filter((key) => key.startsWith(formoPrefix));
37
+
38
+ if (formoKeys.length > 0) {
39
+ const pairs = await asyncStorage.multiGet(formoKeys);
40
+ for (const [key, value] of pairs) {
41
+ if (value !== null) {
42
+ this.cache.set(key, value);
43
+ }
44
+ }
45
+ logger.debug(
46
+ `AsyncStorageAdapter: Preloaded ${formoKeys.length} keys into cache`
47
+ );
48
+ }
49
+ } catch (error) {
50
+ logger.error("AsyncStorageAdapter: Failed to preload keys", error);
51
+ }
52
+
53
+ logger.debug("AsyncStorageAdapter: Initialized");
54
+ }
55
+
56
+ public isAvailable(): boolean {
57
+ return this.asyncStorage !== null;
58
+ }
59
+
60
+ /**
61
+ * Synchronous get from cache (may return stale data)
62
+ * Use getAsync for guaranteed fresh data
63
+ */
64
+ public get(key: string): string | null {
65
+ const cachedValue = this.cache.get(this.getKey(key));
66
+ if (cachedValue !== undefined) {
67
+ return cachedValue;
68
+ }
69
+
70
+ // Trigger async fetch to populate cache
71
+ this.getAsync(key).catch(() => {
72
+ // Ignore errors in background fetch
73
+ });
74
+
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Async get from storage
80
+ */
81
+ public async getAsync(key: string): Promise<string | null> {
82
+ if (!this.asyncStorage) {
83
+ return this.cache.get(this.getKey(key)) ?? null;
84
+ }
85
+
86
+ try {
87
+ const fullKey = this.getKey(key);
88
+ const value = await this.asyncStorage.getItem(fullKey);
89
+
90
+ if (value !== null) {
91
+ this.cache.set(fullKey, value);
92
+ }
93
+
94
+ return value;
95
+ } catch (error) {
96
+ logger.error("AsyncStorageAdapter: Failed to get item", error);
97
+ return this.cache.get(this.getKey(key)) ?? null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Synchronous set (writes to cache immediately, persists async)
103
+ */
104
+ public set(key: string, value: string): void {
105
+ const fullKey = this.getKey(key);
106
+ this.cache.set(fullKey, value);
107
+
108
+ // Persist asynchronously
109
+ this.setAsync(key, value).catch((error) => {
110
+ logger.error("AsyncStorageAdapter: Failed to persist item", error);
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Async set to storage
116
+ */
117
+ public async setAsync(key: string, value: string): Promise<void> {
118
+ const fullKey = this.getKey(key);
119
+ this.cache.set(fullKey, value);
120
+
121
+ if (!this.asyncStorage) {
122
+ return;
123
+ }
124
+
125
+ try {
126
+ await this.asyncStorage.setItem(fullKey, value);
127
+ } catch (error) {
128
+ logger.error("AsyncStorageAdapter: Failed to set item", error);
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Synchronous remove (removes from cache immediately, persists async)
135
+ */
136
+ public remove(key: string): void {
137
+ const fullKey = this.getKey(key);
138
+ this.cache.delete(fullKey);
139
+
140
+ // Persist asynchronously
141
+ this.removeAsync(key).catch((error) => {
142
+ logger.error("AsyncStorageAdapter: Failed to remove item", error);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Async remove from storage
148
+ */
149
+ public async removeAsync(key: string): Promise<void> {
150
+ const fullKey = this.getKey(key);
151
+ this.cache.delete(fullKey);
152
+
153
+ if (!this.asyncStorage) {
154
+ return;
155
+ }
156
+
157
+ try {
158
+ await this.asyncStorage.removeItem(fullKey);
159
+ } catch (error) {
160
+ logger.error("AsyncStorageAdapter: Failed to remove item", error);
161
+ throw error;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Clear all cached data
167
+ */
168
+ public clearCache(): void {
169
+ this.cache.clear();
170
+ }
171
+ }
172
+
173
+ export default AsyncStorageAdapter;
@@ -0,0 +1,43 @@
1
+ import StorageBlueprint from "./StorageBlueprint";
2
+
3
+ /**
4
+ * In-memory storage fallback
5
+ * Data is lost when the app is closed
6
+ */
7
+ class MemoryStorage extends StorageBlueprint {
8
+ private storage: Map<string, string> = new Map();
9
+
10
+ public isAvailable(): boolean {
11
+ return true;
12
+ }
13
+
14
+ public get(key: string): string | null {
15
+ return this.storage.get(this.getKey(key)) ?? null;
16
+ }
17
+
18
+ public async getAsync(key: string): Promise<string | null> {
19
+ return this.get(key);
20
+ }
21
+
22
+ public set(key: string, value: string): void {
23
+ this.storage.set(this.getKey(key), value);
24
+ }
25
+
26
+ public async setAsync(key: string, value: string): Promise<void> {
27
+ this.set(key, value);
28
+ }
29
+
30
+ public remove(key: string): void {
31
+ this.storage.delete(this.getKey(key));
32
+ }
33
+
34
+ public async removeAsync(key: string): Promise<void> {
35
+ this.remove(key);
36
+ }
37
+
38
+ public clear(): void {
39
+ this.storage.clear();
40
+ }
41
+ }
42
+
43
+ export default MemoryStorage;
@@ -0,0 +1,30 @@
1
+ import { STORAGE_PREFIX } from "../../constants";
2
+ import { IStorage } from "./types";
3
+
4
+ /**
5
+ * Base storage class with key prefixing
6
+ */
7
+ abstract class StorageBlueprint implements IStorage {
8
+ protected writeKey: string;
9
+
10
+ constructor(writeKey: string) {
11
+ this.writeKey = writeKey;
12
+ }
13
+
14
+ /**
15
+ * Generate storage key with prefix
16
+ */
17
+ public getKey(key: string): string {
18
+ return `${STORAGE_PREFIX}${this.writeKey}_${key}`;
19
+ }
20
+
21
+ abstract isAvailable(): boolean;
22
+ abstract get(key: string): string | null;
23
+ abstract getAsync(key: string): Promise<string | null>;
24
+ abstract set(key: string, value: string): void;
25
+ abstract setAsync(key: string, value: string): Promise<void>;
26
+ abstract remove(key: string): void;
27
+ abstract removeAsync(key: string): Promise<void>;
28
+ }
29
+
30
+ export default StorageBlueprint;