@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.
- package/README.md +302 -0
- package/lib/commonjs/FormoAnalytics.js +526 -0
- package/lib/commonjs/FormoAnalytics.js.map +1 -0
- package/lib/commonjs/FormoAnalyticsProvider.js +265 -0
- package/lib/commonjs/FormoAnalyticsProvider.js.map +1 -0
- package/lib/commonjs/constants/config.js +69 -0
- package/lib/commonjs/constants/config.js.map +1 -0
- package/lib/commonjs/constants/events.js +30 -0
- package/lib/commonjs/constants/events.js.map +1 -0
- package/lib/commonjs/constants/index.js +39 -0
- package/lib/commonjs/constants/index.js.map +1 -0
- package/lib/commonjs/constants/storage.js +23 -0
- package/lib/commonjs/constants/storage.js.map +1 -0
- package/lib/commonjs/index.js +65 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/lib/consent/index.js +56 -0
- package/lib/commonjs/lib/consent/index.js.map +1 -0
- package/lib/commonjs/lib/event/EventFactory.js +493 -0
- package/lib/commonjs/lib/event/EventFactory.js.map +1 -0
- package/lib/commonjs/lib/event/EventManager.js +46 -0
- package/lib/commonjs/lib/event/EventManager.js.map +1 -0
- package/lib/commonjs/lib/event/EventQueue.js +290 -0
- package/lib/commonjs/lib/event/EventQueue.js.map +1 -0
- package/lib/commonjs/lib/event/index.js +50 -0
- package/lib/commonjs/lib/event/index.js.map +1 -0
- package/lib/commonjs/lib/event/types.js +6 -0
- package/lib/commonjs/lib/event/types.js.map +1 -0
- package/lib/commonjs/lib/lifecycle/index.js +196 -0
- package/lib/commonjs/lib/lifecycle/index.js.map +1 -0
- package/lib/commonjs/lib/logger/index.js +48 -0
- package/lib/commonjs/lib/logger/index.js.map +1 -0
- package/lib/commonjs/lib/session/index.js +109 -0
- package/lib/commonjs/lib/session/index.js.map +1 -0
- package/lib/commonjs/lib/storage/AsyncStorageAdapter.js +164 -0
- package/lib/commonjs/lib/storage/AsyncStorageAdapter.js.map +1 -0
- package/lib/commonjs/lib/storage/MemoryStorage.js +41 -0
- package/lib/commonjs/lib/storage/MemoryStorage.js.map +1 -0
- package/lib/commonjs/lib/storage/StorageBlueprint.js +24 -0
- package/lib/commonjs/lib/storage/StorageBlueprint.js.map +1 -0
- package/lib/commonjs/lib/storage/StorageManager.js +126 -0
- package/lib/commonjs/lib/storage/StorageManager.js.map +1 -0
- package/lib/commonjs/lib/storage/index.js +49 -0
- package/lib/commonjs/lib/storage/index.js.map +1 -0
- package/lib/commonjs/lib/storage/types.js +2 -0
- package/lib/commonjs/lib/storage/types.js.map +1 -0
- package/lib/commonjs/lib/wagmi/WagmiEventHandler.js +445 -0
- package/lib/commonjs/lib/wagmi/WagmiEventHandler.js.map +1 -0
- package/lib/commonjs/lib/wagmi/index.js +28 -0
- package/lib/commonjs/lib/wagmi/index.js.map +1 -0
- package/lib/commonjs/lib/wagmi/types.js +2 -0
- package/lib/commonjs/lib/wagmi/types.js.map +1 -0
- package/lib/commonjs/types/base.js +6 -0
- package/lib/commonjs/types/base.js.map +1 -0
- package/lib/commonjs/types/events.js +22 -0
- package/lib/commonjs/types/events.js.map +1 -0
- package/lib/commonjs/types/index.js +28 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/utils/address.js +82 -0
- package/lib/commonjs/utils/address.js.map +1 -0
- package/lib/commonjs/utils/hash.js +30 -0
- package/lib/commonjs/utils/hash.js.map +1 -0
- package/lib/commonjs/utils/helpers.js +116 -0
- package/lib/commonjs/utils/helpers.js.map +1 -0
- package/lib/commonjs/utils/index.js +61 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/commonjs/utils/timestamp.js +34 -0
- package/lib/commonjs/utils/timestamp.js.map +1 -0
- package/lib/commonjs/utils/trafficSource.js +147 -0
- package/lib/commonjs/utils/trafficSource.js.map +1 -0
- package/lib/commonjs/version.js +10 -0
- package/lib/commonjs/version.js.map +1 -0
- package/lib/module/FormoAnalytics.js +519 -0
- package/lib/module/FormoAnalytics.js.map +1 -0
- package/lib/module/FormoAnalyticsProvider.js +256 -0
- package/lib/module/FormoAnalyticsProvider.js.map +1 -0
- package/lib/module/constants/config.js +62 -0
- package/lib/module/constants/config.js.map +1 -0
- package/lib/module/constants/events.js +24 -0
- package/lib/module/constants/events.js.map +1 -0
- package/lib/module/constants/index.js +4 -0
- package/lib/module/constants/index.js.map +1 -0
- package/lib/module/constants/storage.js +17 -0
- package/lib/module/constants/storage.js.map +1 -0
- package/lib/module/index.js +51 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/consent/index.js +49 -0
- package/lib/module/lib/consent/index.js.map +1 -0
- package/lib/module/lib/event/EventFactory.js +488 -0
- package/lib/module/lib/event/EventFactory.js.map +1 -0
- package/lib/module/lib/event/EventManager.js +41 -0
- package/lib/module/lib/event/EventManager.js.map +1 -0
- package/lib/module/lib/event/EventQueue.js +283 -0
- package/lib/module/lib/event/EventQueue.js.map +1 -0
- package/lib/module/lib/event/index.js +5 -0
- package/lib/module/lib/event/index.js.map +1 -0
- package/lib/module/lib/event/types.js +2 -0
- package/lib/module/lib/event/types.js.map +1 -0
- package/lib/module/lib/lifecycle/index.js +190 -0
- package/lib/module/lib/lifecycle/index.js.map +1 -0
- package/lib/module/lib/logger/index.js +42 -0
- package/lib/module/lib/logger/index.js.map +1 -0
- package/lib/module/lib/session/index.js +92 -0
- package/lib/module/lib/session/index.js.map +1 -0
- package/lib/module/lib/storage/AsyncStorageAdapter.js +158 -0
- package/lib/module/lib/storage/AsyncStorageAdapter.js.map +1 -0
- package/lib/module/lib/storage/MemoryStorage.js +35 -0
- package/lib/module/lib/storage/MemoryStorage.js.map +1 -0
- package/lib/module/lib/storage/StorageBlueprint.js +18 -0
- package/lib/module/lib/storage/StorageBlueprint.js.map +1 -0
- package/lib/module/lib/storage/StorageManager.js +115 -0
- package/lib/module/lib/storage/StorageManager.js.map +1 -0
- package/lib/module/lib/storage/index.js +5 -0
- package/lib/module/lib/storage/index.js.map +1 -0
- package/lib/module/lib/storage/types.js +2 -0
- package/lib/module/lib/storage/types.js.map +1 -0
- package/lib/module/lib/wagmi/WagmiEventHandler.js +439 -0
- package/lib/module/lib/wagmi/WagmiEventHandler.js.map +1 -0
- package/lib/module/lib/wagmi/index.js +3 -0
- package/lib/module/lib/wagmi/index.js.map +1 -0
- package/lib/module/lib/wagmi/types.js +2 -0
- package/lib/module/lib/wagmi/types.js.map +1 -0
- package/lib/module/types/base.js +2 -0
- package/lib/module/types/base.js.map +1 -0
- package/lib/module/types/events.js +17 -0
- package/lib/module/types/events.js.map +1 -0
- package/lib/module/types/index.js +3 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/utils/address.js +74 -0
- package/lib/module/utils/address.js.map +1 -0
- package/lib/module/utils/hash.js +24 -0
- package/lib/module/utils/hash.js.map +1 -0
- package/lib/module/utils/helpers.js +105 -0
- package/lib/module/utils/helpers.js.map +1 -0
- package/lib/module/utils/index.js +6 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/timestamp.js +26 -0
- package/lib/module/utils/timestamp.js.map +1 -0
- package/lib/module/utils/trafficSource.js +137 -0
- package/lib/module/utils/trafficSource.js.map +1 -0
- package/lib/module/version.js +4 -0
- package/lib/module/version.js.map +1 -0
- package/lib/typescript/FormoAnalytics.d.ts +163 -0
- package/lib/typescript/FormoAnalytics.d.ts.map +1 -0
- package/lib/typescript/FormoAnalyticsProvider.d.ts +29 -0
- package/lib/typescript/FormoAnalyticsProvider.d.ts.map +1 -0
- package/lib/typescript/constants/config.d.ts +8 -0
- package/lib/typescript/constants/config.d.ts.map +1 -0
- package/lib/typescript/constants/events.d.ts +23 -0
- package/lib/typescript/constants/events.d.ts.map +1 -0
- package/lib/typescript/constants/index.d.ts +4 -0
- package/lib/typescript/constants/index.d.ts.map +1 -0
- package/lib/typescript/constants/storage.d.ts +10 -0
- package/lib/typescript/constants/storage.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +44 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/lib/consent/index.d.ts +13 -0
- package/lib/typescript/lib/consent/index.d.ts.map +1 -0
- package/lib/typescript/lib/event/EventFactory.d.ts +61 -0
- package/lib/typescript/lib/event/EventFactory.d.ts.map +1 -0
- package/lib/typescript/lib/event/EventManager.d.ts +17 -0
- package/lib/typescript/lib/event/EventManager.d.ts.map +1 -0
- package/lib/typescript/lib/event/EventQueue.d.ts +74 -0
- package/lib/typescript/lib/event/EventQueue.d.ts.map +1 -0
- package/lib/typescript/lib/event/index.d.ts +5 -0
- package/lib/typescript/lib/event/index.d.ts.map +1 -0
- package/lib/typescript/lib/event/types.d.ts +23 -0
- package/lib/typescript/lib/event/types.d.ts.map +1 -0
- package/lib/typescript/lib/lifecycle/index.d.ts +46 -0
- package/lib/typescript/lib/lifecycle/index.d.ts.map +1 -0
- package/lib/typescript/lib/logger/index.d.ts +19 -0
- package/lib/typescript/lib/logger/index.d.ts.map +1 -0
- package/lib/typescript/lib/session/index.d.ts +41 -0
- package/lib/typescript/lib/session/index.d.ts.map +1 -0
- package/lib/typescript/lib/storage/AsyncStorageAdapter.d.ts +48 -0
- package/lib/typescript/lib/storage/AsyncStorageAdapter.d.ts.map +1 -0
- package/lib/typescript/lib/storage/MemoryStorage.d.ts +18 -0
- package/lib/typescript/lib/storage/MemoryStorage.d.ts.map +1 -0
- package/lib/typescript/lib/storage/StorageBlueprint.d.ts +21 -0
- package/lib/typescript/lib/storage/StorageBlueprint.d.ts.map +1 -0
- package/lib/typescript/lib/storage/StorageManager.d.ts +45 -0
- package/lib/typescript/lib/storage/StorageManager.d.ts.map +1 -0
- package/lib/typescript/lib/storage/index.d.ts +5 -0
- package/lib/typescript/lib/storage/index.d.ts.map +1 -0
- package/lib/typescript/lib/storage/types.d.ts +22 -0
- package/lib/typescript/lib/storage/types.d.ts.map +1 -0
- package/lib/typescript/lib/wagmi/WagmiEventHandler.d.ts +104 -0
- package/lib/typescript/lib/wagmi/WagmiEventHandler.d.ts.map +1 -0
- package/lib/typescript/lib/wagmi/index.d.ts +3 -0
- package/lib/typescript/lib/wagmi/index.d.ts.map +1 -0
- package/lib/typescript/lib/wagmi/types.d.ts +54 -0
- package/lib/typescript/lib/wagmi/types.d.ts.map +1 -0
- package/lib/typescript/types/base.d.ts +219 -0
- package/lib/typescript/types/base.d.ts.map +1 -0
- package/lib/typescript/types/events.d.ts +111 -0
- package/lib/typescript/types/events.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +3 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/utils/address.d.ts +25 -0
- package/lib/typescript/utils/address.d.ts.map +1 -0
- package/lib/typescript/utils/hash.d.ts +10 -0
- package/lib/typescript/utils/hash.d.ts.map +1 -0
- package/lib/typescript/utils/helpers.d.ts +26 -0
- package/lib/typescript/utils/helpers.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +6 -0
- package/lib/typescript/utils/index.d.ts.map +1 -0
- package/lib/typescript/utils/timestamp.d.ts +13 -0
- package/lib/typescript/utils/timestamp.d.ts.map +1 -0
- package/lib/typescript/utils/trafficSource.d.ts +30 -0
- package/lib/typescript/utils/trafficSource.d.ts.map +1 -0
- package/lib/typescript/version.d.ts +2 -0
- package/lib/typescript/version.d.ts.map +1 -0
- package/package.json +143 -0
- package/src/FormoAnalytics.ts +685 -0
- package/src/FormoAnalyticsProvider.tsx +296 -0
- package/src/constants/config.ts +62 -0
- package/src/constants/events.ts +26 -0
- package/src/constants/index.ts +3 -0
- package/src/constants/storage.ts +16 -0
- package/src/index.ts +55 -0
- package/src/lib/consent/index.ts +52 -0
- package/src/lib/event/EventFactory.ts +682 -0
- package/src/lib/event/EventManager.ts +50 -0
- package/src/lib/event/EventQueue.ts +371 -0
- package/src/lib/event/index.ts +4 -0
- package/src/lib/event/types.ts +107 -0
- package/src/lib/lifecycle/index.ts +215 -0
- package/src/lib/logger/index.ts +56 -0
- package/src/lib/session/index.ts +103 -0
- package/src/lib/storage/AsyncStorageAdapter.ts +173 -0
- package/src/lib/storage/MemoryStorage.ts +43 -0
- package/src/lib/storage/StorageBlueprint.ts +30 -0
- package/src/lib/storage/StorageManager.ts +121 -0
- package/src/lib/storage/index.ts +4 -0
- package/src/lib/storage/types.ts +23 -0
- package/src/lib/wagmi/WagmiEventHandler.ts +574 -0
- package/src/lib/wagmi/index.ts +2 -0
- package/src/lib/wagmi/types.ts +71 -0
- package/src/types/base.ts +287 -0
- package/src/types/events.ts +140 -0
- package/src/types/index.ts +2 -0
- package/src/utils/address.ts +84 -0
- package/src/utils/hash.ts +23 -0
- package/src/utils/helpers.ts +139 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/timestamp.ts +25 -0
- package/src/utils/trafficSource.ts +153 -0
- 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;
|