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