@formo/analytics 1.11.5 → 1.11.6-alpha.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 +10 -18
- package/dist/cjs/src/FormoAnalytics.d.ts +30 -37
- package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalytics.js +163 -247
- package/dist/cjs/src/FormoAnalytics.js.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.js +3 -3
- package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/cjs/src/constants/config.d.ts +1 -1
- package/dist/cjs/src/constants/config.d.ts.map +1 -1
- package/dist/cjs/src/constants/config.js +2 -2
- package/dist/cjs/src/constants/config.js.map +1 -1
- package/dist/cjs/src/types/base.d.ts +5 -1
- package/dist/cjs/src/types/base.d.ts.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +30 -37
- package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalytics.js +164 -248
- package/dist/esm/src/FormoAnalytics.js.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.d.ts +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.js +3 -3
- package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/esm/src/constants/config.d.ts +1 -1
- package/dist/esm/src/constants/config.d.ts.map +1 -1
- package/dist/esm/src/constants/config.js +1 -1
- package/dist/esm/src/constants/config.js.map +1 -1
- package/dist/esm/src/types/base.d.ts +5 -1
- package/dist/esm/src/types/base.d.ts.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/FormoAnalytics.ts +162 -254
- package/src/FormoAnalyticsProvider.tsx +3 -6
- package/src/constants/config.ts +1 -1
- package/src/types/base.ts +7 -1
- package/dist/cjs/src/utils/index.d.ts +0 -2
- package/dist/cjs/src/utils/index.d.ts.map +0 -1
- package/dist/cjs/src/utils/index.js +0 -18
- package/dist/cjs/src/utils/index.js.map +0 -1
- package/dist/cjs/src/utils/isNotEmptyObject.d.ts +0 -2
- package/dist/cjs/src/utils/isNotEmptyObject.d.ts.map +0 -1
- package/dist/cjs/src/utils/isNotEmptyObject.js +0 -9
- package/dist/cjs/src/utils/isNotEmptyObject.js.map +0 -1
- package/dist/esm/src/utils/index.d.ts +0 -2
- package/dist/esm/src/utils/index.d.ts.map +0 -1
- package/dist/esm/src/utils/index.js +0 -2
- package/dist/esm/src/utils/index.js.map +0 -1
- package/dist/esm/src/utils/isNotEmptyObject.d.ts +0 -2
- package/dist/esm/src/utils/isNotEmptyObject.d.ts.map +0 -1
- package/dist/esm/src/utils/isNotEmptyObject.js +0 -6
- package/dist/esm/src/utils/isNotEmptyObject.js.map +0 -1
- package/src/utils/index.ts +0 -1
- package/src/utils/isNotEmptyObject.ts +0 -5
package/package.json
CHANGED
package/src/FormoAnalytics.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import {
|
|
3
3
|
COUNTRY_LIST,
|
|
4
|
-
|
|
4
|
+
EVENTS_API_URL,
|
|
5
5
|
SESSION_STORAGE_ID_KEY,
|
|
6
6
|
Event,
|
|
7
7
|
} from './constants';
|
|
8
|
-
import { isNotEmpty } from './utils';
|
|
9
8
|
import { H } from 'highlight.run';
|
|
10
|
-
import { ChainID, EIP1193Provider,
|
|
9
|
+
import { ChainID, EIP1193Provider, Options } from './types';
|
|
11
10
|
|
|
12
11
|
interface IFormoAnalytics {
|
|
13
12
|
/**
|
|
14
13
|
* Initializes the FormoAnalytics instance with the provided API key and project ID.
|
|
15
14
|
*/
|
|
16
|
-
init(apiKey: string,
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Identifies the user with the provided user data.
|
|
20
|
-
*/
|
|
21
|
-
identify(userData: Record<string, any>): void;
|
|
15
|
+
init(apiKey: string, options: Options): Promise<FormoAnalytics>;
|
|
22
16
|
|
|
23
17
|
/**
|
|
24
18
|
* Tracks page visit events.
|
|
@@ -28,22 +22,25 @@ interface IFormoAnalytics {
|
|
|
28
22
|
/**
|
|
29
23
|
* Connects to a wallet with the specified chain ID and address.
|
|
30
24
|
*/
|
|
31
|
-
connect(params: {
|
|
25
|
+
connect(params: { chainId: ChainID; address: string }): Promise<void>;
|
|
32
26
|
|
|
33
27
|
/**
|
|
34
28
|
* Disconnects the current wallet and clears the session information.
|
|
35
29
|
*/
|
|
36
|
-
disconnect(
|
|
30
|
+
disconnect(params?: { chainId?: ChainID; address?: string }): void;
|
|
37
31
|
|
|
38
32
|
/**
|
|
39
|
-
*
|
|
33
|
+
* Switches the blockchain chain context and optionally logs additional params.
|
|
40
34
|
*/
|
|
41
|
-
|
|
35
|
+
chain(params: { chainId: ChainID; address?: string }): void;
|
|
42
36
|
|
|
43
37
|
/**
|
|
44
|
-
*
|
|
38
|
+
* Tracks a specific event with a name and associated data.
|
|
45
39
|
*/
|
|
46
|
-
|
|
40
|
+
track(eventName: string, eventData: Record<string, any>): void;
|
|
41
|
+
}
|
|
42
|
+
interface Config {
|
|
43
|
+
token: string;
|
|
47
44
|
}
|
|
48
45
|
export class FormoAnalytics implements IFormoAnalytics {
|
|
49
46
|
private _provider?: EIP1193Provider;
|
|
@@ -52,36 +49,34 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
52
49
|
(...args: unknown[]) => void
|
|
53
50
|
> = {};
|
|
54
51
|
|
|
55
|
-
private
|
|
56
|
-
private config:
|
|
52
|
+
private walletAddressSessionKey = 'walletAddress';
|
|
53
|
+
private config: Config;
|
|
57
54
|
private sessionIdKey: string = SESSION_STORAGE_ID_KEY;
|
|
58
55
|
private timezoneToCountry: Record<string, string> = COUNTRY_LIST;
|
|
59
56
|
|
|
60
57
|
currentChainId?: string | null;
|
|
61
|
-
|
|
58
|
+
currentConnectedAddress?: string;
|
|
62
59
|
|
|
63
60
|
private constructor(
|
|
64
61
|
public readonly apiKey: string,
|
|
65
|
-
public
|
|
62
|
+
public options: Options = {}
|
|
66
63
|
) {
|
|
67
64
|
this.config = {
|
|
68
65
|
token: this.apiKey,
|
|
69
66
|
};
|
|
70
67
|
|
|
71
|
-
const provider =
|
|
68
|
+
const provider =
|
|
69
|
+
window?.ethereum || window.web3?.currentProvider || options?.provider;
|
|
72
70
|
if (provider) {
|
|
73
71
|
this.trackProvider(provider);
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
static async init(
|
|
78
|
-
apiKey: string,
|
|
79
|
-
projectId: string
|
|
80
|
-
): Promise<FormoAnalytics> {
|
|
75
|
+
static async init(apiKey: string, options: Options): Promise<FormoAnalytics> {
|
|
81
76
|
const config = {
|
|
82
77
|
token: apiKey,
|
|
83
78
|
};
|
|
84
|
-
const instance = new FormoAnalytics(apiKey,
|
|
79
|
+
const instance = new FormoAnalytics(apiKey, options);
|
|
85
80
|
instance.config = config;
|
|
86
81
|
|
|
87
82
|
return instance;
|
|
@@ -91,10 +86,6 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
91
86
|
return this._provider;
|
|
92
87
|
}
|
|
93
88
|
|
|
94
|
-
private identifyUser(userData: any) {
|
|
95
|
-
this.trackEvent(Event.IDENTIFY, userData);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
89
|
private getSessionId() {
|
|
99
90
|
const existingSessionId = this.getCookieValue(this.sessionIdKey);
|
|
100
91
|
|
|
@@ -106,13 +97,16 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
106
97
|
return newSessionId;
|
|
107
98
|
}
|
|
108
99
|
|
|
100
|
+
private getOrigin(): string {
|
|
101
|
+
return window.location.origin || 'ORIGIN_NOT_FOUND';
|
|
102
|
+
}
|
|
103
|
+
|
|
109
104
|
// Function to set the session cookie
|
|
110
|
-
private setSessionCookie(
|
|
105
|
+
private setSessionCookie(): void {
|
|
111
106
|
const sessionId = this.getSessionId();
|
|
112
|
-
let cookieValue = `${
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
107
|
+
let cookieValue = `${
|
|
108
|
+
this.sessionIdKey
|
|
109
|
+
}=${sessionId}; Max-Age=1800; path=/; secure; domain=${this.getOrigin()}`;
|
|
116
110
|
document.cookie = cookieValue;
|
|
117
111
|
}
|
|
118
112
|
|
|
@@ -136,27 +130,32 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
136
130
|
const maxRetries = 3;
|
|
137
131
|
let attempt = 0;
|
|
138
132
|
|
|
139
|
-
this.setSessionCookie(
|
|
140
|
-
const apiUrl = this.buildApiUrl();
|
|
133
|
+
this.setSessionCookie();
|
|
141
134
|
const address = await this.getCurrentWallet();
|
|
142
135
|
|
|
136
|
+
console.log('address:', address);
|
|
137
|
+
|
|
143
138
|
const requestData = {
|
|
144
|
-
project_id: this.projectId,
|
|
145
139
|
address: address,
|
|
146
140
|
session_id: this.getSessionId(),
|
|
147
141
|
timestamp: new Date().toISOString(),
|
|
148
|
-
action
|
|
142
|
+
action,
|
|
149
143
|
version: '1',
|
|
150
|
-
payload
|
|
144
|
+
payload,
|
|
151
145
|
};
|
|
152
146
|
|
|
153
147
|
const sendRequest = async (): Promise<void> => {
|
|
154
148
|
try {
|
|
155
|
-
const response = await axios.post(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
const response = await axios.post(
|
|
150
|
+
EVENTS_API_URL,
|
|
151
|
+
JSON.stringify(requestData),
|
|
152
|
+
{
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
);
|
|
160
159
|
|
|
161
160
|
if (response.status >= 200 && response.status < 300) {
|
|
162
161
|
console.log('Event sent successfully:', action);
|
|
@@ -189,72 +188,6 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
189
188
|
await sendRequest();
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
// Function to mask sensitive data in the payload
|
|
193
|
-
private maskSensitiveData(
|
|
194
|
-
data: string | undefined | null
|
|
195
|
-
): Record<string, any> | null {
|
|
196
|
-
// Check if data is null or undefined
|
|
197
|
-
if (data === null || data === undefined) {
|
|
198
|
-
console.warn('Data is null or undefined, returning null');
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Check if data is a string; if so, parse it to an object
|
|
203
|
-
if (typeof data === 'string') {
|
|
204
|
-
let parsedData: Record<string, any>;
|
|
205
|
-
try {
|
|
206
|
-
parsedData = JSON.parse(data);
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error('Failed to parse JSON:', error);
|
|
209
|
-
return null; // Return null if parsing fails
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const sensitiveFields = [
|
|
213
|
-
'username',
|
|
214
|
-
'user',
|
|
215
|
-
'user_id',
|
|
216
|
-
'password',
|
|
217
|
-
'email',
|
|
218
|
-
'phone',
|
|
219
|
-
];
|
|
220
|
-
|
|
221
|
-
// Create a new object to store masked data
|
|
222
|
-
const maskedData = { ...parsedData };
|
|
223
|
-
|
|
224
|
-
// Mask sensitive fields
|
|
225
|
-
sensitiveFields.forEach((field) => {
|
|
226
|
-
if (field in maskedData) {
|
|
227
|
-
maskedData[field] = '********'; // Replace value with masked string
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
return maskedData; // Return the new object with masked fields
|
|
232
|
-
} else if (typeof data === 'object') {
|
|
233
|
-
// If data is already an object, handle masking directly
|
|
234
|
-
const sensitiveFields = [
|
|
235
|
-
'username',
|
|
236
|
-
'user',
|
|
237
|
-
'user_id',
|
|
238
|
-
'password',
|
|
239
|
-
'email',
|
|
240
|
-
'phone',
|
|
241
|
-
];
|
|
242
|
-
|
|
243
|
-
const maskedData = { ...(data as Record<string, any>) };
|
|
244
|
-
|
|
245
|
-
// Mask sensitive fields
|
|
246
|
-
sensitiveFields.forEach((field) => {
|
|
247
|
-
if (field in maskedData) {
|
|
248
|
-
maskedData[field] = '********'; // Replace value with masked string
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
return maskedData; // Return the new object with masked fields
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return data;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
191
|
// Function to track page hits
|
|
259
192
|
private trackPageHit() {
|
|
260
193
|
if (window.__nightmare || window.navigator.webdriver || window.Cypress)
|
|
@@ -278,6 +211,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
278
211
|
const params = new URLSearchParams(url.search);
|
|
279
212
|
this.trackEvent(Event.PAGE, {
|
|
280
213
|
'user-agent': window.navigator.userAgent,
|
|
214
|
+
address: this.currentConnectedAddress,
|
|
281
215
|
locale: language,
|
|
282
216
|
location: location,
|
|
283
217
|
referrer: document.referrer,
|
|
@@ -297,7 +231,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
297
231
|
}
|
|
298
232
|
|
|
299
233
|
this.currentChainId = undefined;
|
|
300
|
-
this.
|
|
234
|
+
this.currentConnectedAddress = undefined;
|
|
301
235
|
|
|
302
236
|
if (this._provider) {
|
|
303
237
|
const eventNames = Object.keys(this._registeredProviderListeners);
|
|
@@ -313,10 +247,63 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
313
247
|
this._provider = provider;
|
|
314
248
|
|
|
315
249
|
this.getCurrentWallet();
|
|
316
|
-
this.
|
|
250
|
+
this.registerAddressChangedListener();
|
|
317
251
|
this.registerChainChangedListener();
|
|
318
252
|
}
|
|
319
253
|
|
|
254
|
+
private async getCurrentWallet() {
|
|
255
|
+
if (!this.provider) {
|
|
256
|
+
console.warn('FormoAnalytics::getCurrentWallet: the provider is not set');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const sessionData = sessionStorage.getItem(this.walletAddressSessionKey);
|
|
260
|
+
if (!sessionData) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const parsedData = JSON.parse(sessionData);
|
|
265
|
+
const sessionExpiry = 30 * 60 * 1000; // 30 minutes
|
|
266
|
+
const currentTime = Date.now();
|
|
267
|
+
|
|
268
|
+
if (currentTime - parsedData.timestamp > sessionExpiry) {
|
|
269
|
+
console.warn('Session expired. Ignoring wallet address.');
|
|
270
|
+
sessionStorage.removeItem(this.walletAddressSessionKey); // Clear expired session data
|
|
271
|
+
return '';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.onAddressConnected(parsedData.address);
|
|
275
|
+
return parsedData.address || '';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private async getCurrentChainId(): Promise<string> {
|
|
279
|
+
if (!this.provider) {
|
|
280
|
+
console.error('FormoAnalytics::getCurrentChainId: provider not set');
|
|
281
|
+
}
|
|
282
|
+
const chainIdHex = await this.provider?.request<string>({
|
|
283
|
+
method: 'eth_chainId',
|
|
284
|
+
});
|
|
285
|
+
// Because we're connected, the chainId cannot be null
|
|
286
|
+
if (!chainIdHex) {
|
|
287
|
+
console.error(
|
|
288
|
+
`FormoAnalytics::getCurrentChainId: chainIdHex is: ${chainIdHex}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return parseInt(chainIdHex as string, 16).toString();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private registerAddressChangedListener() {
|
|
296
|
+
const listener = (...args: unknown[]) =>
|
|
297
|
+
this.onAddressChanged(args[0] as string[]);
|
|
298
|
+
|
|
299
|
+
this._provider?.on('accountsChanged', listener);
|
|
300
|
+
this._registeredProviderListeners['accountsChanged'] = listener;
|
|
301
|
+
|
|
302
|
+
const onAddressDisconnected = this.onAddressDisconnected.bind(this);
|
|
303
|
+
this._provider?.on('disconnect', onAddressDisconnected);
|
|
304
|
+
this._registeredProviderListeners['disconnect'] = onAddressDisconnected;
|
|
305
|
+
}
|
|
306
|
+
|
|
320
307
|
private registerChainChangedListener() {
|
|
321
308
|
const listener = (...args: unknown[]) =>
|
|
322
309
|
this.onChainChanged(args[0] as string);
|
|
@@ -324,25 +311,50 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
324
311
|
this._registeredProviderListeners['chainChanged'] = listener;
|
|
325
312
|
}
|
|
326
313
|
|
|
327
|
-
private
|
|
328
|
-
if (
|
|
314
|
+
private async onAddressChanged(addresses: string[]) {
|
|
315
|
+
if (addresses.length > 0) {
|
|
316
|
+
const newAccount = addresses[0];
|
|
317
|
+
if (newAccount !== this.currentConnectedAddress) {
|
|
318
|
+
this.onAddressConnected(newAccount);
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
this.onAddressDisconnected();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private async onAddressConnected(address: string) {
|
|
326
|
+
if (address === this.currentConnectedAddress) {
|
|
327
|
+
// We have already reported this address
|
|
329
328
|
return;
|
|
329
|
+
} else {
|
|
330
|
+
this.currentConnectedAddress = address;
|
|
330
331
|
}
|
|
331
332
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
this.currentChainId = await this.getCurrentChainId();
|
|
334
|
+
|
|
335
|
+
this.connect({ chainId: this.currentChainId, address });
|
|
336
|
+
this.storeWalletAddress(address);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private onAddressDisconnected() {
|
|
340
|
+
if (!this.currentConnectedAddress) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const payload = {
|
|
345
|
+
chain_id: this.currentChainId,
|
|
346
|
+
address: this.currentConnectedAddress,
|
|
335
347
|
};
|
|
336
348
|
this.currentChainId = undefined;
|
|
337
|
-
this.
|
|
349
|
+
this.currentConnectedAddress = undefined;
|
|
338
350
|
this.clearWalletAddress();
|
|
339
351
|
|
|
340
|
-
return this.trackEvent(Event.DISCONNECT,
|
|
352
|
+
return this.trackEvent(Event.DISCONNECT, payload);
|
|
341
353
|
}
|
|
342
354
|
|
|
343
355
|
private async onChainChanged(chainIdHex: string) {
|
|
344
356
|
this.currentChainId = parseInt(chainIdHex).toString();
|
|
345
|
-
if (!this.
|
|
357
|
+
if (!this.currentConnectedAddress) {
|
|
346
358
|
if (!this.provider) {
|
|
347
359
|
console.error(
|
|
348
360
|
'error',
|
|
@@ -363,7 +375,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
363
375
|
return;
|
|
364
376
|
}
|
|
365
377
|
|
|
366
|
-
this.
|
|
378
|
+
this.currentConnectedAddress = res[0];
|
|
367
379
|
} catch (err) {
|
|
368
380
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
369
381
|
if ((err as any).code !== 4001) {
|
|
@@ -380,88 +392,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
380
392
|
|
|
381
393
|
return this.chain({
|
|
382
394
|
chainId: this.currentChainId,
|
|
383
|
-
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
private async onAccountsChanged(accounts: string[]) {
|
|
388
|
-
if (accounts.length > 0) {
|
|
389
|
-
const newAccount = accounts[0];
|
|
390
|
-
if (newAccount !== this.currentConnectedAccount) {
|
|
391
|
-
this.handleAccountConnected(newAccount);
|
|
392
|
-
}
|
|
393
|
-
} else {
|
|
394
|
-
this.handleAccountDisconnected();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private registerAccountsChangedListener() {
|
|
399
|
-
const listener = (...args: unknown[]) =>
|
|
400
|
-
this.onAccountsChanged(args[0] as string[]);
|
|
401
|
-
|
|
402
|
-
this._provider?.on('accountsChanged', listener);
|
|
403
|
-
this._registeredProviderListeners['accountsChanged'] = listener;
|
|
404
|
-
|
|
405
|
-
const handleAccountDisconnected = this.handleAccountDisconnected.bind(this);
|
|
406
|
-
this._provider?.on('disconnect', handleAccountDisconnected);
|
|
407
|
-
this._registeredProviderListeners['disconnect'] = handleAccountDisconnected;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private async getCurrentChainId(): Promise<string> {
|
|
411
|
-
if (!this.provider) {
|
|
412
|
-
console.error('FormoAnalytics::getCurrentChainId: provider not set');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const chainIdHex = await this.provider?.request<string>({
|
|
416
|
-
method: 'eth_chainId',
|
|
395
|
+
address: this.currentConnectedAddress,
|
|
417
396
|
});
|
|
418
|
-
// Because we're connected, the chainId cannot be null
|
|
419
|
-
if (!chainIdHex) {
|
|
420
|
-
console.error(
|
|
421
|
-
`FormoAnalytics::getCurrentChainId: chainIdHex is: ${chainIdHex}`
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return parseInt(chainIdHex as string, 16).toString();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private async handleAccountConnected(account: string) {
|
|
429
|
-
if (account === this.currentConnectedAccount) {
|
|
430
|
-
// We have already reported this account
|
|
431
|
-
return;
|
|
432
|
-
} else {
|
|
433
|
-
this.currentConnectedAccount = account;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
this.currentChainId = await this.getCurrentChainId();
|
|
437
|
-
|
|
438
|
-
this.connect({ account, chainId: this.currentChainId });
|
|
439
|
-
this.storeWalletAddress(account);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
private async getCurrentWallet() {
|
|
443
|
-
if (!this.provider) {
|
|
444
|
-
console.warn('FormoAnalytics::getCurrentWallet: the provider is not set');
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
const sessionData = sessionStorage.getItem(this.sessionKey);
|
|
448
|
-
|
|
449
|
-
if (!sessionData) {
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const parsedData = JSON.parse(sessionData);
|
|
454
|
-
const sessionExpiry = 30 * 60 * 1000; // 30 minutes
|
|
455
|
-
const currentTime = Date.now();
|
|
456
|
-
|
|
457
|
-
if (currentTime - parsedData.timestamp > sessionExpiry) {
|
|
458
|
-
console.warn('Session expired. Ignoring wallet address.');
|
|
459
|
-
sessionStorage.removeItem(this.sessionKey); // Clear expired session data
|
|
460
|
-
return '';
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
this.handleAccountConnected(parsedData.address);
|
|
464
|
-
return parsedData.address || '';
|
|
465
397
|
}
|
|
466
398
|
|
|
467
399
|
/**
|
|
@@ -479,82 +411,68 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
479
411
|
timestamp: Date.now(),
|
|
480
412
|
};
|
|
481
413
|
|
|
482
|
-
sessionStorage.setItem(
|
|
414
|
+
sessionStorage.setItem(
|
|
415
|
+
this.walletAddressSessionKey,
|
|
416
|
+
JSON.stringify(sessionData)
|
|
417
|
+
);
|
|
483
418
|
}
|
|
484
419
|
|
|
485
420
|
/**
|
|
486
421
|
* Clears the wallet address from session storage when disconnected.
|
|
487
422
|
*/
|
|
488
423
|
private clearWalletAddress(): void {
|
|
489
|
-
sessionStorage.removeItem(this.
|
|
424
|
+
sessionStorage.removeItem(this.walletAddressSessionKey);
|
|
490
425
|
}
|
|
491
426
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (token) {
|
|
496
|
-
if (proxy) {
|
|
497
|
-
return `${proxy}/api/tracking`;
|
|
498
|
-
}
|
|
499
|
-
if (host) {
|
|
500
|
-
return `${host.replace(
|
|
501
|
-
/\/+$/,
|
|
502
|
-
''
|
|
503
|
-
)}/v0/events?name=${dataSource}&token=${token}`;
|
|
504
|
-
}
|
|
505
|
-
return `${EVENTS_API}?name=${dataSource}&token=${token}`;
|
|
506
|
-
}
|
|
507
|
-
return 'Error: No token provided';
|
|
427
|
+
init(apiKey: string, options: Options): Promise<FormoAnalytics> {
|
|
428
|
+
const instance = new FormoAnalytics(apiKey, options);
|
|
429
|
+
return Promise.resolve(instance);
|
|
508
430
|
}
|
|
509
431
|
|
|
510
|
-
connect({
|
|
432
|
+
connect({ chainId, address }: { chainId: ChainID; address: string }) {
|
|
511
433
|
if (!chainId) {
|
|
512
|
-
throw new Error('FormoAnalytics::connect:
|
|
434
|
+
throw new Error('FormoAnalytics::connect: chain ID cannot be empty');
|
|
513
435
|
}
|
|
514
|
-
if (!
|
|
515
|
-
throw new Error('FormoAnalytics::connect:
|
|
436
|
+
if (!address) {
|
|
437
|
+
throw new Error('FormoAnalytics::connect: address cannot be empty');
|
|
516
438
|
}
|
|
517
439
|
|
|
518
440
|
this.currentChainId = chainId.toString();
|
|
519
|
-
this.
|
|
441
|
+
this.currentConnectedAddress = address;
|
|
520
442
|
|
|
521
443
|
return this.trackEvent(Event.CONNECT, {
|
|
522
|
-
chainId,
|
|
523
|
-
address:
|
|
444
|
+
chain_id: chainId,
|
|
445
|
+
address: address,
|
|
524
446
|
});
|
|
525
447
|
}
|
|
526
448
|
|
|
527
|
-
disconnect(
|
|
528
|
-
const
|
|
529
|
-
if (!
|
|
449
|
+
disconnect(params?: { chainId?: ChainID; address?: string }) {
|
|
450
|
+
const address = params?.address || this.currentConnectedAddress;
|
|
451
|
+
if (!address) {
|
|
530
452
|
// We have most likely already reported this disconnection with the automatic
|
|
531
453
|
// `disconnect` detection
|
|
532
454
|
return;
|
|
533
455
|
}
|
|
534
456
|
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
...(chainId && { chainId }),
|
|
457
|
+
const payload = {
|
|
458
|
+
chain_id: params?.chainId || this.currentChainId,
|
|
459
|
+
address,
|
|
539
460
|
};
|
|
540
|
-
|
|
541
461
|
this.currentChainId = undefined;
|
|
542
|
-
this.
|
|
462
|
+
this.currentConnectedAddress = undefined;
|
|
543
463
|
|
|
544
|
-
return this.trackEvent(Event.DISCONNECT,
|
|
464
|
+
return this.trackEvent(Event.DISCONNECT, payload);
|
|
545
465
|
}
|
|
546
466
|
|
|
547
|
-
chain({ chainId,
|
|
467
|
+
chain({ chainId, address }: { chainId: ChainID; address?: string }) {
|
|
548
468
|
if (!chainId || Number(chainId) === 0) {
|
|
549
469
|
throw new Error('FormoAnalytics::chain: chainId cannot be empty or 0');
|
|
550
470
|
}
|
|
551
|
-
|
|
552
|
-
if (!account && !this.currentConnectedAccount) {
|
|
471
|
+
if (!address && !this.currentConnectedAddress) {
|
|
553
472
|
throw new Error(
|
|
554
|
-
'FormoAnalytics::chain:
|
|
473
|
+
'FormoAnalytics::chain: address was empty and no previous address has been recorded. You can either pass an address or call connect() first'
|
|
555
474
|
);
|
|
556
475
|
}
|
|
557
|
-
|
|
558
476
|
if (isNaN(Number(chainId))) {
|
|
559
477
|
throw new Error(
|
|
560
478
|
'FormoAnalytics::chain: chainId must be a valid hex or decimal number'
|
|
@@ -564,21 +482,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
564
482
|
this.currentChainId = chainId.toString();
|
|
565
483
|
|
|
566
484
|
return this.trackEvent(Event.CHAIN_CHANGED, {
|
|
567
|
-
chainId,
|
|
568
|
-
|
|
485
|
+
chain_id: chainId,
|
|
486
|
+
address: address || this.currentConnectedAddress,
|
|
569
487
|
});
|
|
570
488
|
}
|
|
571
489
|
|
|
572
|
-
init(apiKey: string, projectId: string): Promise<FormoAnalytics> {
|
|
573
|
-
const instance = new FormoAnalytics(apiKey, projectId);
|
|
574
|
-
|
|
575
|
-
return Promise.resolve(instance);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
identify(userData: any) {
|
|
579
|
-
this.identifyUser(userData);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
490
|
page() {
|
|
583
491
|
this.trackPageHit();
|
|
584
492
|
}
|
|
@@ -18,7 +18,7 @@ export const FormoAnalyticsContext = createContext<FormoAnalytics | undefined>(
|
|
|
18
18
|
|
|
19
19
|
export const FormoAnalyticsProvider = ({
|
|
20
20
|
apiKey,
|
|
21
|
-
|
|
21
|
+
options,
|
|
22
22
|
disabled,
|
|
23
23
|
children,
|
|
24
24
|
}: FormoAnalyticsProviderProps) => {
|
|
@@ -32,15 +32,12 @@ export const FormoAnalyticsProvider = ({
|
|
|
32
32
|
console.error('FormoAnalyticsProvider: No API key provided');
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
35
|
if (disabled) {
|
|
37
36
|
console.warn('FormoAnalytics is disabled');
|
|
38
37
|
return;
|
|
39
38
|
}
|
|
40
|
-
|
|
41
39
|
if (initializedStartedRef.current) return;
|
|
42
40
|
initializedStartedRef.current = true;
|
|
43
|
-
|
|
44
41
|
// Initialize Highlight.run if project ID is available
|
|
45
42
|
if (HIGHLIGHT_PROJECT_ID) {
|
|
46
43
|
try {
|
|
@@ -64,7 +61,7 @@ export const FormoAnalyticsProvider = ({
|
|
|
64
61
|
|
|
65
62
|
// Initialize FormoAnalytics
|
|
66
63
|
try {
|
|
67
|
-
const sdkInstance = await FormoAnalytics.init(apiKey,
|
|
64
|
+
const sdkInstance = await FormoAnalytics.init(apiKey, options);
|
|
68
65
|
setSdk(sdkInstance);
|
|
69
66
|
console.log('FormoAnalytics SDK initialized successfully');
|
|
70
67
|
} catch (error) {
|
|
@@ -75,7 +72,7 @@ export const FormoAnalyticsProvider = ({
|
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
initialize();
|
|
78
|
-
}, [apiKey, disabled,
|
|
75
|
+
}, [apiKey, disabled, options]);
|
|
79
76
|
|
|
80
77
|
if (!isInitialized) {
|
|
81
78
|
// Optionally show a loading state until initialization attempt finishes
|
package/src/constants/config.ts
CHANGED
package/src/types/base.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { EIP1193Provider } from "./wallet";
|
|
2
|
+
|
|
1
3
|
export type ChainID = string | number
|
|
2
4
|
|
|
5
|
+
export interface Options {
|
|
6
|
+
provider?: EIP1193Provider;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export interface FormoAnalyticsProviderProps {
|
|
4
10
|
apiKey: string;
|
|
5
|
-
|
|
11
|
+
options: Options;
|
|
6
12
|
disabled?: boolean;
|
|
7
13
|
children: React.ReactNode;
|
|
8
14
|
}
|