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