@formo/analytics 1.11.5 → 1.11.6-alpha.1
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 +164 -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 +165 -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 +164 -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,65 @@ 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
|
+
console.log(sessionData);
|
|
265
|
+
|
|
266
|
+
const parsedData = JSON.parse(sessionData);
|
|
267
|
+
const sessionExpiry = 30 * 60 * 1000; // 30 minutes
|
|
268
|
+
const currentTime = Date.now();
|
|
269
|
+
|
|
270
|
+
if (currentTime - parsedData.timestamp > sessionExpiry) {
|
|
271
|
+
console.warn('Session expired. Ignoring wallet address.');
|
|
272
|
+
sessionStorage.removeItem(this.walletAddressSessionKey); // Clear expired session data
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.onAddressConnected(parsedData.address);
|
|
277
|
+
return parsedData.address || '';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private async getCurrentChainId(): Promise<string> {
|
|
281
|
+
if (!this.provider) {
|
|
282
|
+
console.error('FormoAnalytics::getCurrentChainId: provider not set');
|
|
283
|
+
}
|
|
284
|
+
const chainIdHex = await this.provider?.request<string>({
|
|
285
|
+
method: 'eth_chainId',
|
|
286
|
+
});
|
|
287
|
+
// Because we're connected, the chainId cannot be null
|
|
288
|
+
if (!chainIdHex) {
|
|
289
|
+
console.error(
|
|
290
|
+
`FormoAnalytics::getCurrentChainId: chainIdHex is: ${chainIdHex}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return parseInt(chainIdHex as string, 16).toString();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private registerAddressChangedListener() {
|
|
298
|
+
const listener = (...args: unknown[]) =>
|
|
299
|
+
this.onAddressChanged(args[0] as string[]);
|
|
300
|
+
|
|
301
|
+
this._provider?.on('accountsChanged', listener);
|
|
302
|
+
this._registeredProviderListeners['accountsChanged'] = listener;
|
|
303
|
+
|
|
304
|
+
const onAddressDisconnected = this.onAddressDisconnected.bind(this);
|
|
305
|
+
this._provider?.on('disconnect', onAddressDisconnected);
|
|
306
|
+
this._registeredProviderListeners['disconnect'] = onAddressDisconnected;
|
|
307
|
+
}
|
|
308
|
+
|
|
320
309
|
private registerChainChangedListener() {
|
|
321
310
|
const listener = (...args: unknown[]) =>
|
|
322
311
|
this.onChainChanged(args[0] as string);
|
|
@@ -324,25 +313,50 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
324
313
|
this._registeredProviderListeners['chainChanged'] = listener;
|
|
325
314
|
}
|
|
326
315
|
|
|
327
|
-
private
|
|
328
|
-
if (
|
|
316
|
+
private async onAddressChanged(addresses: string[]) {
|
|
317
|
+
if (addresses.length > 0) {
|
|
318
|
+
const newAccount = addresses[0];
|
|
319
|
+
if (newAccount !== this.currentConnectedAddress) {
|
|
320
|
+
this.onAddressConnected(newAccount);
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
this.onAddressDisconnected();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async onAddressConnected(address: string) {
|
|
328
|
+
if (address === this.currentConnectedAddress) {
|
|
329
|
+
// We have already reported this address
|
|
330
|
+
return;
|
|
331
|
+
} else {
|
|
332
|
+
this.currentConnectedAddress = address;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.currentChainId = await this.getCurrentChainId();
|
|
336
|
+
|
|
337
|
+
this.connect({ chainId: this.currentChainId, address });
|
|
338
|
+
this.storeWalletAddress(address);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private onAddressDisconnected() {
|
|
342
|
+
if (!this.currentConnectedAddress) {
|
|
329
343
|
return;
|
|
330
344
|
}
|
|
331
345
|
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
346
|
+
const payload = {
|
|
347
|
+
chain_id: this.currentChainId,
|
|
348
|
+
address: this.currentConnectedAddress,
|
|
335
349
|
};
|
|
336
350
|
this.currentChainId = undefined;
|
|
337
|
-
this.
|
|
351
|
+
this.currentConnectedAddress = undefined;
|
|
338
352
|
this.clearWalletAddress();
|
|
339
353
|
|
|
340
|
-
return this.trackEvent(Event.DISCONNECT,
|
|
354
|
+
return this.trackEvent(Event.DISCONNECT, payload);
|
|
341
355
|
}
|
|
342
356
|
|
|
343
357
|
private async onChainChanged(chainIdHex: string) {
|
|
344
358
|
this.currentChainId = parseInt(chainIdHex).toString();
|
|
345
|
-
if (!this.
|
|
359
|
+
if (!this.currentConnectedAddress) {
|
|
346
360
|
if (!this.provider) {
|
|
347
361
|
console.error(
|
|
348
362
|
'error',
|
|
@@ -363,7 +377,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
363
377
|
return;
|
|
364
378
|
}
|
|
365
379
|
|
|
366
|
-
this.
|
|
380
|
+
this.currentConnectedAddress = res[0];
|
|
367
381
|
} catch (err) {
|
|
368
382
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
369
383
|
if ((err as any).code !== 4001) {
|
|
@@ -380,90 +394,10 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
380
394
|
|
|
381
395
|
return this.chain({
|
|
382
396
|
chainId: this.currentChainId,
|
|
383
|
-
|
|
397
|
+
address: this.currentConnectedAddress,
|
|
384
398
|
});
|
|
385
399
|
}
|
|
386
400
|
|
|
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',
|
|
417
|
-
});
|
|
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
|
-
}
|
|
466
|
-
|
|
467
401
|
/**
|
|
468
402
|
* Stores the wallet address in session storage when connected.
|
|
469
403
|
* @param address - The wallet address to store.
|
|
@@ -479,82 +413,68 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
479
413
|
timestamp: Date.now(),
|
|
480
414
|
};
|
|
481
415
|
|
|
482
|
-
sessionStorage.setItem(
|
|
416
|
+
sessionStorage.setItem(
|
|
417
|
+
this.walletAddressSessionKey,
|
|
418
|
+
JSON.stringify(sessionData)
|
|
419
|
+
);
|
|
483
420
|
}
|
|
484
421
|
|
|
485
422
|
/**
|
|
486
423
|
* Clears the wallet address from session storage when disconnected.
|
|
487
424
|
*/
|
|
488
425
|
private clearWalletAddress(): void {
|
|
489
|
-
sessionStorage.removeItem(this.
|
|
426
|
+
sessionStorage.removeItem(this.walletAddressSessionKey);
|
|
490
427
|
}
|
|
491
428
|
|
|
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';
|
|
429
|
+
init(apiKey: string, options: Options): Promise<FormoAnalytics> {
|
|
430
|
+
const instance = new FormoAnalytics(apiKey, options);
|
|
431
|
+
return Promise.resolve(instance);
|
|
508
432
|
}
|
|
509
433
|
|
|
510
|
-
connect({
|
|
434
|
+
connect({ chainId, address }: { chainId: ChainID; address: string }) {
|
|
511
435
|
if (!chainId) {
|
|
512
|
-
throw new Error('FormoAnalytics::connect:
|
|
436
|
+
throw new Error('FormoAnalytics::connect: chain ID cannot be empty');
|
|
513
437
|
}
|
|
514
|
-
if (!
|
|
515
|
-
throw new Error('FormoAnalytics::connect:
|
|
438
|
+
if (!address) {
|
|
439
|
+
throw new Error('FormoAnalytics::connect: address cannot be empty');
|
|
516
440
|
}
|
|
517
441
|
|
|
518
442
|
this.currentChainId = chainId.toString();
|
|
519
|
-
this.
|
|
443
|
+
this.currentConnectedAddress = address;
|
|
520
444
|
|
|
521
445
|
return this.trackEvent(Event.CONNECT, {
|
|
522
|
-
chainId,
|
|
523
|
-
address:
|
|
446
|
+
chain_id: chainId,
|
|
447
|
+
address: address,
|
|
524
448
|
});
|
|
525
449
|
}
|
|
526
450
|
|
|
527
|
-
disconnect(
|
|
528
|
-
const
|
|
529
|
-
if (!
|
|
451
|
+
disconnect(params?: { chainId?: ChainID; address?: string }) {
|
|
452
|
+
const address = params?.address || this.currentConnectedAddress;
|
|
453
|
+
if (!address) {
|
|
530
454
|
// We have most likely already reported this disconnection with the automatic
|
|
531
455
|
// `disconnect` detection
|
|
532
456
|
return;
|
|
533
457
|
}
|
|
534
458
|
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
...(chainId && { chainId }),
|
|
459
|
+
const payload = {
|
|
460
|
+
chain_id: params?.chainId || this.currentChainId,
|
|
461
|
+
address,
|
|
539
462
|
};
|
|
540
|
-
|
|
541
463
|
this.currentChainId = undefined;
|
|
542
|
-
this.
|
|
464
|
+
this.currentConnectedAddress = undefined;
|
|
543
465
|
|
|
544
|
-
return this.trackEvent(Event.DISCONNECT,
|
|
466
|
+
return this.trackEvent(Event.DISCONNECT, payload);
|
|
545
467
|
}
|
|
546
468
|
|
|
547
|
-
chain({ chainId,
|
|
469
|
+
chain({ chainId, address }: { chainId: ChainID; address?: string }) {
|
|
548
470
|
if (!chainId || Number(chainId) === 0) {
|
|
549
471
|
throw new Error('FormoAnalytics::chain: chainId cannot be empty or 0');
|
|
550
472
|
}
|
|
551
|
-
|
|
552
|
-
if (!account && !this.currentConnectedAccount) {
|
|
473
|
+
if (!address && !this.currentConnectedAddress) {
|
|
553
474
|
throw new Error(
|
|
554
|
-
'FormoAnalytics::chain:
|
|
475
|
+
'FormoAnalytics::chain: address was empty and no previous address has been recorded. You can either pass an address or call connect() first'
|
|
555
476
|
);
|
|
556
477
|
}
|
|
557
|
-
|
|
558
478
|
if (isNaN(Number(chainId))) {
|
|
559
479
|
throw new Error(
|
|
560
480
|
'FormoAnalytics::chain: chainId must be a valid hex or decimal number'
|
|
@@ -564,21 +484,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
564
484
|
this.currentChainId = chainId.toString();
|
|
565
485
|
|
|
566
486
|
return this.trackEvent(Event.CHAIN_CHANGED, {
|
|
567
|
-
chainId,
|
|
568
|
-
|
|
487
|
+
chain_id: chainId,
|
|
488
|
+
address: address || this.currentConnectedAddress,
|
|
569
489
|
});
|
|
570
490
|
}
|
|
571
491
|
|
|
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
492
|
page() {
|
|
583
493
|
this.trackPageHit();
|
|
584
494
|
}
|
|
@@ -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