@formo/analytics 1.14.2 → 1.15.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/dist/cjs/src/FormoAnalytics.d.ts +10 -0
- package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalytics.js +181 -102
- package/dist/cjs/src/FormoAnalytics.js.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.js +6 -5
- package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/cjs/src/constants/base.d.ts +3 -1
- package/dist/cjs/src/constants/base.d.ts.map +1 -1
- package/dist/cjs/src/constants/base.js +7 -3
- package/dist/cjs/src/constants/base.js.map +1 -1
- package/dist/cjs/src/constants/config.d.ts +6 -1
- package/dist/cjs/src/constants/config.d.ts.map +1 -1
- package/dist/cjs/src/constants/config.js +9 -3
- package/dist/cjs/src/constants/config.js.map +1 -1
- package/dist/cjs/src/lib/fetch.d.ts +3 -0
- package/dist/cjs/src/lib/fetch.d.ts.map +1 -0
- package/dist/cjs/src/lib/fetch.js +8 -0
- package/dist/cjs/src/lib/fetch.js.map +1 -0
- package/dist/cjs/src/lib/index.d.ts +4 -2
- package/dist/cjs/src/lib/index.d.ts.map +1 -1
- package/dist/cjs/src/lib/index.js +6 -4
- package/dist/cjs/src/lib/index.js.map +1 -1
- package/dist/cjs/src/lib/logger.d.ts +22 -0
- package/dist/cjs/src/lib/logger.d.ts.map +1 -0
- package/dist/cjs/src/lib/logger.js +114 -0
- package/dist/cjs/src/lib/logger.js.map +1 -0
- package/dist/cjs/src/lib/queue.d.ts +2 -3
- package/dist/cjs/src/lib/queue.d.ts.map +1 -1
- package/dist/cjs/src/lib/queue.js +42 -64
- package/dist/cjs/src/lib/queue.js.map +1 -1
- package/dist/cjs/src/lib/storage/index.d.ts +3 -0
- package/dist/cjs/src/lib/storage/index.d.ts.map +1 -0
- package/dist/cjs/src/lib/storage/index.js +11 -0
- package/dist/cjs/src/lib/storage/index.js.map +1 -0
- package/dist/cjs/src/lib/storage/local.d.ts +4 -0
- package/dist/cjs/src/lib/storage/local.d.ts.map +1 -0
- package/dist/cjs/src/lib/storage/local.js +8 -0
- package/dist/cjs/src/lib/storage/local.js.map +1 -0
- package/dist/cjs/src/lib/storage/native.d.ts +15 -0
- package/dist/cjs/src/lib/storage/native.d.ts.map +1 -0
- package/dist/cjs/src/lib/storage/native.js +83 -0
- package/dist/cjs/src/lib/storage/native.js.map +1 -0
- package/dist/cjs/src/lib/storage/session.d.ts +4 -0
- package/dist/cjs/src/lib/storage/session.d.ts.map +1 -0
- package/dist/cjs/src/lib/storage/session.js +8 -0
- package/dist/cjs/src/lib/storage/session.js.map +1 -0
- package/dist/cjs/src/types/base.d.ts +5 -0
- package/dist/cjs/src/types/base.d.ts.map +1 -1
- package/dist/cjs/src/types/events.d.ts +6 -0
- package/dist/cjs/src/types/events.d.ts.map +1 -1
- package/dist/cjs/src/types/events.js.map +1 -1
- package/dist/cjs/src/{lib/utils.d.ts → utils/base.d.ts} +4 -3
- package/dist/cjs/src/utils/base.d.ts.map +1 -0
- package/dist/cjs/src/utils/base.js +113 -0
- package/dist/cjs/src/utils/base.js.map +1 -0
- package/dist/cjs/src/utils/index.d.ts +3 -0
- package/dist/cjs/src/utils/index.d.ts.map +1 -0
- package/dist/cjs/src/utils/index.js +19 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/cjs/src/utils/is.d.ts +3 -0
- package/dist/cjs/src/utils/is.d.ts.map +1 -0
- package/dist/cjs/src/utils/is.js +11 -0
- package/dist/cjs/src/utils/is.js.map +1 -0
- package/dist/cjs/test/lib.spec.js +5 -5
- package/dist/cjs/test/lib.spec.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +10 -0
- package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalytics.js +179 -100
- package/dist/esm/src/FormoAnalytics.js.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.js +6 -5
- package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/esm/src/constants/base.d.ts +3 -1
- package/dist/esm/src/constants/base.d.ts.map +1 -1
- package/dist/esm/src/constants/base.js +6 -2
- package/dist/esm/src/constants/base.js.map +1 -1
- package/dist/esm/src/constants/config.d.ts +6 -1
- package/dist/esm/src/constants/config.d.ts.map +1 -1
- package/dist/esm/src/constants/config.js +7 -2
- package/dist/esm/src/constants/config.js.map +1 -1
- package/dist/esm/src/lib/fetch.d.ts +3 -0
- package/dist/esm/src/lib/fetch.d.ts.map +1 -0
- package/dist/esm/src/lib/fetch.js +3 -0
- package/dist/esm/src/lib/fetch.js.map +1 -0
- package/dist/esm/src/lib/index.d.ts +4 -2
- package/dist/esm/src/lib/index.d.ts.map +1 -1
- package/dist/esm/src/lib/index.js +4 -2
- package/dist/esm/src/lib/index.js.map +1 -1
- package/dist/esm/src/lib/logger.d.ts +22 -0
- package/dist/esm/src/lib/logger.d.ts.map +1 -0
- package/dist/esm/src/lib/logger.js +111 -0
- package/dist/esm/src/lib/logger.js.map +1 -0
- package/dist/esm/src/lib/queue.d.ts +2 -3
- package/dist/esm/src/lib/queue.d.ts.map +1 -1
- package/dist/esm/src/lib/queue.js +42 -64
- package/dist/esm/src/lib/queue.js.map +1 -1
- package/dist/esm/src/lib/storage/index.d.ts +3 -0
- package/dist/esm/src/lib/storage/index.d.ts.map +1 -0
- package/dist/esm/src/lib/storage/index.js +3 -0
- package/dist/esm/src/lib/storage/index.js.map +1 -0
- package/dist/esm/src/lib/storage/local.d.ts +4 -0
- package/dist/esm/src/lib/storage/local.d.ts.map +1 -0
- package/dist/esm/src/lib/storage/local.js +3 -0
- package/dist/esm/src/lib/storage/local.js.map +1 -0
- package/dist/esm/src/lib/storage/native.d.ts +15 -0
- package/dist/esm/src/lib/storage/native.d.ts.map +1 -0
- package/dist/esm/src/lib/storage/native.js +80 -0
- package/dist/esm/src/lib/storage/native.js.map +1 -0
- package/dist/esm/src/lib/storage/session.d.ts +4 -0
- package/dist/esm/src/lib/storage/session.d.ts.map +1 -0
- package/dist/esm/src/lib/storage/session.js +3 -0
- package/dist/esm/src/lib/storage/session.js.map +1 -0
- package/dist/esm/src/types/base.d.ts +5 -0
- package/dist/esm/src/types/base.d.ts.map +1 -1
- package/dist/esm/src/types/events.d.ts +6 -0
- package/dist/esm/src/types/events.d.ts.map +1 -1
- package/dist/esm/src/types/events.js.map +1 -1
- package/dist/esm/src/{lib/utils.d.ts → utils/base.d.ts} +4 -3
- package/dist/esm/src/utils/base.d.ts.map +1 -0
- package/dist/esm/src/{lib/fingerprint.js → utils/base.js} +66 -23
- package/dist/esm/src/utils/base.js.map +1 -0
- package/dist/esm/src/utils/index.d.ts +3 -0
- package/dist/esm/src/utils/index.d.ts.map +1 -0
- package/dist/esm/src/utils/index.js +3 -0
- package/dist/esm/src/utils/index.js.map +1 -0
- package/dist/esm/src/utils/is.d.ts +3 -0
- package/dist/esm/src/utils/is.d.ts.map +1 -0
- package/dist/esm/src/utils/is.js +6 -0
- package/dist/esm/src/utils/is.js.map +1 -0
- package/dist/esm/test/lib.spec.js +1 -1
- package/dist/esm/test/lib.spec.js.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 -2
- package/src/FormoAnalytics.ts +176 -89
- package/src/FormoAnalyticsProvider.tsx +6 -5
- package/src/constants/base.ts +12 -2
- package/src/constants/config.ts +9 -2
- package/src/lib/fetch.ts +3 -0
- package/src/lib/index.ts +4 -2
- package/src/lib/logger.ts +93 -0
- package/src/lib/queue.ts +27 -48
- package/src/lib/storage/index.ts +2 -0
- package/src/lib/storage/local.ts +3 -0
- package/src/lib/storage/native.ts +93 -0
- package/src/lib/storage/session.ts +3 -0
- package/src/types/base.ts +5 -0
- package/src/types/events.ts +8 -0
- package/src/{lib/utils.ts → utils/base.ts} +16 -8
- package/src/utils/index.ts +2 -0
- package/src/utils/is.ts +8 -0
- package/test/lib.spec.ts +1 -1
- package/dist/cjs/src/lib/fingerprint.d.ts +0 -4
- package/dist/cjs/src/lib/fingerprint.d.ts.map +0 -1
- package/dist/cjs/src/lib/fingerprint.js +0 -63
- package/dist/cjs/src/lib/fingerprint.js.map +0 -1
- package/dist/cjs/src/lib/session-storage.d.ts +0 -11
- package/dist/cjs/src/lib/session-storage.d.ts.map +0 -1
- package/dist/cjs/src/lib/session-storage.js +0 -52
- package/dist/cjs/src/lib/session-storage.js.map +0 -1
- package/dist/cjs/src/lib/utils.d.ts.map +0 -1
- package/dist/cjs/src/lib/utils.js +0 -63
- package/dist/cjs/src/lib/utils.js.map +0 -1
- package/dist/esm/src/lib/fingerprint.d.ts +0 -4
- package/dist/esm/src/lib/fingerprint.d.ts.map +0 -1
- package/dist/esm/src/lib/fingerprint.js.map +0 -1
- package/dist/esm/src/lib/session-storage.d.ts +0 -11
- package/dist/esm/src/lib/session-storage.d.ts.map +0 -1
- package/dist/esm/src/lib/session-storage.js +0 -49
- package/dist/esm/src/lib/session-storage.js.map +0 -1
- package/dist/esm/src/lib/utils.d.ts.map +0 -1
- package/dist/esm/src/lib/utils.js +0 -53
- package/dist/esm/src/lib/utils.js.map +0 -1
- package/src/lib/fingerprint.ts +0 -9
- package/src/lib/session-storage.ts +0 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formo/analytics",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/getformo/sdk.git"
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@fingerprintjs/fingerprintjs": "^4.6.0",
|
|
25
24
|
"fetch-retry": "^6.0.0",
|
|
26
25
|
"is-network-error": "^1.1.0",
|
|
27
26
|
"mipd": "^0.0.7"
|
package/src/FormoAnalytics.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createStore, EIP6963ProviderDetail } from "mipd";
|
|
2
2
|
import {
|
|
3
|
+
LOCAL_ANONYMOUS_ID_KEY,
|
|
3
4
|
COUNTRY_LIST,
|
|
4
|
-
|
|
5
|
+
SESSION_CURRENT_URL_KEY,
|
|
5
6
|
EVENTS_API_URL,
|
|
6
7
|
Event,
|
|
8
|
+
SESSION_USER_ID_KEY,
|
|
9
|
+
EVENTS_API_REQUEST_HEADER,
|
|
10
|
+
USER_API_URL,
|
|
7
11
|
} from "./constants";
|
|
8
12
|
import {
|
|
9
13
|
ChainID,
|
|
@@ -17,12 +21,19 @@ import {
|
|
|
17
21
|
TransactionStatus,
|
|
18
22
|
RequestEvent,
|
|
19
23
|
} from "./types";
|
|
20
|
-
import { session,
|
|
24
|
+
import { session, local, logger, EventQueue, fetch } from "./lib";
|
|
25
|
+
import {
|
|
26
|
+
isLocalhost,
|
|
27
|
+
isAddress,
|
|
28
|
+
toSnakeCase,
|
|
29
|
+
generateNativeUUID,
|
|
30
|
+
} from "./utils";
|
|
21
31
|
import { SESSION_IDENTIFIED_KEY } from "./constants";
|
|
22
|
-
import {
|
|
32
|
+
import { UUID } from "crypto";
|
|
23
33
|
|
|
24
34
|
interface IFormoAnalytics {
|
|
25
35
|
page(): void;
|
|
36
|
+
reset(): void;
|
|
26
37
|
connect(params: { chainId: ChainID; address: Address }): Promise<void>;
|
|
27
38
|
disconnect(params?: { chainId?: ChainID; address?: Address }): Promise<void>;
|
|
28
39
|
chain(params: { chainId: ChainID; address?: Address }): Promise<void>;
|
|
@@ -65,6 +76,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
65
76
|
private _providerListeners: Record<string, (...args: unknown[]) => void> = {};
|
|
66
77
|
private session: FormoAnalyticsSession;
|
|
67
78
|
private eventQueue: EventQueue;
|
|
79
|
+
private anonymousId: UUID | null = null;
|
|
80
|
+
private userId: UUID | null = null;
|
|
68
81
|
|
|
69
82
|
config: Config;
|
|
70
83
|
currentChainId?: ChainID;
|
|
@@ -81,6 +94,15 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
81
94
|
|
|
82
95
|
this.session = new FormoAnalyticsSession();
|
|
83
96
|
|
|
97
|
+
// Initialize logger with configuration from options
|
|
98
|
+
const loggerConfig = options.logger || {
|
|
99
|
+
enabled: false,
|
|
100
|
+
};
|
|
101
|
+
logger.setEnabled(loggerConfig.enabled);
|
|
102
|
+
if (loggerConfig.levels) {
|
|
103
|
+
logger.setEnabledLevels(loggerConfig.levels);
|
|
104
|
+
}
|
|
105
|
+
|
|
84
106
|
this.eventQueue = new EventQueue(this.config.writeKey, {
|
|
85
107
|
url: EVENTS_API_URL,
|
|
86
108
|
flushAt: options.flushAt,
|
|
@@ -89,6 +111,9 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
89
111
|
flushInterval: options.flushInterval,
|
|
90
112
|
});
|
|
91
113
|
|
|
114
|
+
this.anonymousId = this.getAnonymousId();
|
|
115
|
+
this.getUserId(null).then((userId) => (this.userId = userId));
|
|
116
|
+
|
|
92
117
|
// TODO: replace with eip6963
|
|
93
118
|
const provider = options.provider || window?.ethereum;
|
|
94
119
|
if (provider) {
|
|
@@ -120,10 +145,21 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
120
145
|
* Emits a page visit event with the current URL information, fire on page change.
|
|
121
146
|
* @returns {Promise<void>}
|
|
122
147
|
*/
|
|
123
|
-
async page(): Promise<void> {
|
|
148
|
+
public async page(): Promise<void> {
|
|
124
149
|
await this.trackPageHit();
|
|
125
150
|
}
|
|
126
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Reset the current user session.
|
|
154
|
+
* @returns {void}
|
|
155
|
+
*/
|
|
156
|
+
public reset(): void {
|
|
157
|
+
this.anonymousId = this.getAnonymousId();
|
|
158
|
+
this.userId = null;
|
|
159
|
+
local.remove(LOCAL_ANONYMOUS_ID_KEY);
|
|
160
|
+
session.remove(SESSION_USER_ID_KEY);
|
|
161
|
+
}
|
|
162
|
+
|
|
127
163
|
/**
|
|
128
164
|
* Emits a wallet connect event.
|
|
129
165
|
* @param {ChainID} params.chainId
|
|
@@ -139,10 +175,10 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
139
175
|
address: Address;
|
|
140
176
|
}): Promise<void> {
|
|
141
177
|
if (!chainId) {
|
|
142
|
-
|
|
178
|
+
logger.warn("Connect: Chain ID cannot be empty");
|
|
143
179
|
}
|
|
144
180
|
if (!address) {
|
|
145
|
-
|
|
181
|
+
logger.warn("Connect: Address cannot be empty");
|
|
146
182
|
}
|
|
147
183
|
|
|
148
184
|
this.currentChainId = chainId;
|
|
@@ -292,9 +328,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
292
328
|
rdns?: string;
|
|
293
329
|
}): Promise<void> {
|
|
294
330
|
if (this.session.isIdentified())
|
|
295
|
-
return
|
|
296
|
-
"FormoAnalytics::identify: Wallet already identified in this session"
|
|
297
|
-
);
|
|
331
|
+
return logger.warn("Identify: Wallet already identified in this session");
|
|
298
332
|
|
|
299
333
|
this.session.identify();
|
|
300
334
|
await this.trackEvent(Event.IDENTIFY, {
|
|
@@ -319,29 +353,36 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
319
353
|
*/
|
|
320
354
|
|
|
321
355
|
private trackProvider(provider: EIP1193Provider): void {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
this.currentChainId = undefined;
|
|
328
|
-
this.currentConnectedAddress = undefined;
|
|
356
|
+
try {
|
|
357
|
+
if (provider === this._provider) {
|
|
358
|
+
logger.warn("TrackProvider: Provider already tracked.");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
329
361
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
362
|
+
this.currentChainId = undefined;
|
|
363
|
+
this.currentConnectedAddress = undefined;
|
|
364
|
+
|
|
365
|
+
if (this._provider) {
|
|
366
|
+
const actions = Object.keys(this._providerListeners);
|
|
367
|
+
for (const action of actions) {
|
|
368
|
+
this._provider.removeListener(
|
|
369
|
+
action,
|
|
370
|
+
this._providerListeners[action]
|
|
371
|
+
);
|
|
372
|
+
delete this._providerListeners[action];
|
|
373
|
+
}
|
|
335
374
|
}
|
|
336
|
-
}
|
|
337
375
|
|
|
338
|
-
|
|
376
|
+
this._provider = provider;
|
|
339
377
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
378
|
+
// Register listeners for web3 provider events
|
|
379
|
+
this.registerAddressChangedListener();
|
|
380
|
+
this.registerChainChangedListener();
|
|
381
|
+
this.registerSignatureListener();
|
|
382
|
+
this.registerTransactionListener();
|
|
383
|
+
} catch (error) {
|
|
384
|
+
logger.error("Error tracking provider:", error);
|
|
385
|
+
}
|
|
345
386
|
}
|
|
346
387
|
|
|
347
388
|
private registerAddressChangedListener(): void {
|
|
@@ -365,14 +406,14 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
365
406
|
|
|
366
407
|
private registerSignatureListener(): void {
|
|
367
408
|
if (!this.provider) {
|
|
368
|
-
|
|
409
|
+
logger.error("Provider not found for signature tracking");
|
|
369
410
|
return;
|
|
370
411
|
}
|
|
371
412
|
if (
|
|
372
413
|
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
373
414
|
false
|
|
374
415
|
) {
|
|
375
|
-
|
|
416
|
+
logger.warn("Provider.request is not writable");
|
|
376
417
|
return;
|
|
377
418
|
}
|
|
378
419
|
|
|
@@ -420,14 +461,14 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
420
461
|
|
|
421
462
|
private registerTransactionListener(): void {
|
|
422
463
|
if (!this.provider) {
|
|
423
|
-
|
|
464
|
+
logger.error("Provider not found for transaction tracking");
|
|
424
465
|
return;
|
|
425
466
|
}
|
|
426
467
|
if (
|
|
427
468
|
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
428
469
|
false
|
|
429
470
|
) {
|
|
430
|
-
|
|
471
|
+
logger.warn("Provider.request is not writable");
|
|
431
472
|
return;
|
|
432
473
|
}
|
|
433
474
|
const request = this.provider.request.bind(this.provider);
|
|
@@ -457,8 +498,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
457
498
|
|
|
458
499
|
return;
|
|
459
500
|
} catch (error) {
|
|
460
|
-
|
|
461
|
-
|
|
501
|
+
logger.info("Transaction listener catch");
|
|
502
|
+
logger.error("Transaction error:", error);
|
|
462
503
|
const rpcError = error as RPCError;
|
|
463
504
|
if (rpcError && rpcError?.code === 4001) {
|
|
464
505
|
// Emit transaction rejected event
|
|
@@ -486,12 +527,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
486
527
|
}
|
|
487
528
|
|
|
488
529
|
private async onAddressConnected(address: Address): Promise<void> {
|
|
489
|
-
if (address === this.currentConnectedAddress)
|
|
530
|
+
if (address === this.currentConnectedAddress)
|
|
490
531
|
// We have already reported this address
|
|
491
532
|
return;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
533
|
+
|
|
534
|
+
this.currentConnectedAddress = address;
|
|
495
535
|
|
|
496
536
|
this.currentChainId = await this.getCurrentChainId();
|
|
497
537
|
this.connect({ chainId: this.currentChainId, address });
|
|
@@ -507,6 +547,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
507
547
|
};
|
|
508
548
|
this.currentChainId = undefined;
|
|
509
549
|
this.currentConnectedAddress = undefined;
|
|
550
|
+
session.remove(SESSION_USER_ID_KEY);
|
|
510
551
|
|
|
511
552
|
await this.trackEvent(Event.DISCONNECT, payload);
|
|
512
553
|
}
|
|
@@ -522,21 +563,21 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
522
563
|
this.currentChainId = parseInt(chainIdHex);
|
|
523
564
|
if (!this.currentConnectedAddress) {
|
|
524
565
|
if (!this.provider) {
|
|
525
|
-
|
|
526
|
-
"
|
|
566
|
+
logger.info(
|
|
567
|
+
"OnChainChanged: Provider not found. CHAIN_CHANGED not reported"
|
|
527
568
|
);
|
|
528
569
|
return Promise.resolve();
|
|
529
570
|
}
|
|
530
571
|
|
|
531
572
|
const address = await this.getAddress();
|
|
532
573
|
if (!address) {
|
|
533
|
-
|
|
534
|
-
"
|
|
574
|
+
logger.info(
|
|
575
|
+
"OnChainChanged: Unable to fetch or store connected address"
|
|
535
576
|
);
|
|
536
577
|
return Promise.resolve();
|
|
537
578
|
}
|
|
538
|
-
|
|
539
579
|
this.currentConnectedAddress = address;
|
|
580
|
+
this.userId = await this.getUserId(address);
|
|
540
581
|
}
|
|
541
582
|
|
|
542
583
|
// Proceed only if the address exists
|
|
@@ -546,15 +587,15 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
546
587
|
address: this.currentConnectedAddress,
|
|
547
588
|
});
|
|
548
589
|
} else {
|
|
549
|
-
|
|
550
|
-
"
|
|
590
|
+
logger.info(
|
|
591
|
+
"OnChainChanged: Current connected address is null despite fetch attempt"
|
|
551
592
|
);
|
|
552
593
|
}
|
|
553
594
|
}
|
|
554
595
|
|
|
555
596
|
private async trackFirstPageHit(): Promise<void> {
|
|
556
|
-
if (session.get(
|
|
557
|
-
session.set(
|
|
597
|
+
if (session.get(SESSION_CURRENT_URL_KEY) === null) {
|
|
598
|
+
session.set(SESSION_CURRENT_URL_KEY, window.location.href);
|
|
558
599
|
}
|
|
559
600
|
|
|
560
601
|
return this.trackPageHit();
|
|
@@ -580,10 +621,10 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
580
621
|
}
|
|
581
622
|
|
|
582
623
|
private async onLocationChange(): Promise<void> {
|
|
583
|
-
const currentUrl = session.get(
|
|
624
|
+
const currentUrl = session.get(SESSION_CURRENT_URL_KEY);
|
|
584
625
|
|
|
585
626
|
if (currentUrl !== window.location.href) {
|
|
586
|
-
session.set(
|
|
627
|
+
session.set(SESSION_CURRENT_URL_KEY, window.location.href);
|
|
587
628
|
this.trackPageHit();
|
|
588
629
|
}
|
|
589
630
|
}
|
|
@@ -593,8 +634,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
593
634
|
const hash = window.location.hash;
|
|
594
635
|
|
|
595
636
|
if (!this.config.trackLocalhost && isLocalhost()) {
|
|
596
|
-
return
|
|
597
|
-
"
|
|
637
|
+
return logger.warn(
|
|
638
|
+
"Track page hit: Ignoring event because website is running locally"
|
|
598
639
|
);
|
|
599
640
|
}
|
|
600
641
|
|
|
@@ -607,64 +648,76 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
607
648
|
}
|
|
608
649
|
|
|
609
650
|
private async trackEvent(action: string, payload: any): Promise<void> {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
651
|
+
try {
|
|
652
|
+
const address = await this.getAddress();
|
|
653
|
+
const user_id = await this.getUserId(address);
|
|
654
|
+
|
|
655
|
+
const requestData: RequestEvent = {
|
|
656
|
+
anonymous_id: this.anonymousId as UUID,
|
|
657
|
+
user_id,
|
|
658
|
+
address,
|
|
659
|
+
timestamp: new Date().toISOString(),
|
|
660
|
+
action,
|
|
661
|
+
version: "1",
|
|
662
|
+
payload: await this.buildEventPayload(toSnakeCase(payload)),
|
|
663
|
+
};
|
|
619
664
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
665
|
+
await this.eventQueue.enqueue(requestData, (err, _, data) => {
|
|
666
|
+
if (err) {
|
|
667
|
+
logger.error("Error sending events:", err);
|
|
668
|
+
} else logger.info(`Events sent successfully: ${data.length} events`);
|
|
669
|
+
});
|
|
670
|
+
} catch (error) {
|
|
671
|
+
logger.error("Error tracking event:", error);
|
|
672
|
+
}
|
|
625
673
|
}
|
|
626
674
|
|
|
627
675
|
/*
|
|
628
676
|
Utility functions
|
|
629
677
|
*/
|
|
630
678
|
|
|
631
|
-
private async getProviders(): Promise<EIP6963ProviderDetail[]> {
|
|
679
|
+
private async getProviders(): Promise<readonly EIP6963ProviderDetail[]> {
|
|
632
680
|
const store = createStore();
|
|
633
|
-
|
|
681
|
+
let providers = store.getProviders();
|
|
634
682
|
// TODO: consider using store.subscribe to detect changes to providers list
|
|
635
|
-
|
|
683
|
+
store.subscribe((providerDetails) => (providers = providerDetails));
|
|
636
684
|
|
|
637
685
|
// Fallback to injected provider if no providers are found
|
|
638
686
|
if (providers.length === 0) {
|
|
639
|
-
return
|
|
687
|
+
return window?.ethereum ? [window.ethereum] : [];
|
|
640
688
|
}
|
|
641
689
|
return providers;
|
|
642
690
|
}
|
|
643
691
|
|
|
644
|
-
private async identifyAll(
|
|
692
|
+
private async identifyAll(
|
|
693
|
+
providers: readonly EIP6963ProviderDetail[]
|
|
694
|
+
): Promise<void> {
|
|
645
695
|
try {
|
|
646
|
-
for (const
|
|
647
|
-
|
|
696
|
+
for (const eip6963ProviderDetail of providers) {
|
|
697
|
+
if (!eip6963ProviderDetail) continue;
|
|
698
|
+
const accounts = await this.getAccounts(
|
|
699
|
+
eip6963ProviderDetail?.provider
|
|
700
|
+
);
|
|
648
701
|
// Identify with accounts
|
|
649
702
|
if (accounts && accounts.length > 0) {
|
|
650
703
|
for (const address of accounts) {
|
|
651
704
|
await this.identify({
|
|
652
705
|
address,
|
|
653
|
-
providerName: info.name,
|
|
654
|
-
rdns: info.rdns,
|
|
706
|
+
providerName: eip6963ProviderDetail?.info.name,
|
|
707
|
+
rdns: eip6963ProviderDetail?.info.rdns,
|
|
655
708
|
});
|
|
656
709
|
}
|
|
657
710
|
} else {
|
|
658
711
|
// Identify without accounts
|
|
659
712
|
await this.identify({
|
|
660
713
|
address: null,
|
|
661
|
-
providerName: info.name,
|
|
662
|
-
rdns: info.rdns,
|
|
714
|
+
providerName: eip6963ProviderDetail?.info.name,
|
|
715
|
+
rdns: eip6963ProviderDetail?.info.rdns,
|
|
663
716
|
});
|
|
664
717
|
}
|
|
665
718
|
}
|
|
666
719
|
} catch (err) {
|
|
667
|
-
|
|
720
|
+
logger.error("Error identifying all:", err);
|
|
668
721
|
}
|
|
669
722
|
}
|
|
670
723
|
|
|
@@ -672,20 +725,57 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
672
725
|
return this._provider;
|
|
673
726
|
}
|
|
674
727
|
|
|
728
|
+
private getAnonymousId(): UUID {
|
|
729
|
+
const storedAnonymousId = local.get(LOCAL_ANONYMOUS_ID_KEY);
|
|
730
|
+
if (storedAnonymousId && typeof storedAnonymousId === "string")
|
|
731
|
+
return storedAnonymousId as UUID;
|
|
732
|
+
const newAnonymousId = generateNativeUUID();
|
|
733
|
+
local.set(LOCAL_ANONYMOUS_ID_KEY, newAnonymousId);
|
|
734
|
+
return newAnonymousId;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private async getUserId(address: string | null): Promise<UUID | null> {
|
|
738
|
+
const storedUserId = session.get(SESSION_USER_ID_KEY);
|
|
739
|
+
if (storedUserId && typeof storedUserId === "string")
|
|
740
|
+
return storedUserId as UUID;
|
|
741
|
+
|
|
742
|
+
if (address) {
|
|
743
|
+
const res = await fetch(`${USER_API_URL}?address=${address}`, {
|
|
744
|
+
headers: EVENTS_API_REQUEST_HEADER(this.writeKey),
|
|
745
|
+
method: "GET",
|
|
746
|
+
});
|
|
747
|
+
const data = await res.json();
|
|
748
|
+
const userId = data?.data?.[0]?.user_id;
|
|
749
|
+
if (userId) {
|
|
750
|
+
session.set(SESSION_USER_ID_KEY, userId);
|
|
751
|
+
return userId;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const newUserId = generateNativeUUID();
|
|
755
|
+
session.set(SESSION_USER_ID_KEY, newUserId);
|
|
756
|
+
return newUserId;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
|
|
675
762
|
private async getAddress(): Promise<Address | null> {
|
|
676
763
|
if (this.currentConnectedAddress) return this.currentConnectedAddress;
|
|
677
764
|
if (!this?.provider) {
|
|
678
|
-
|
|
765
|
+
logger.info("The provider is not set");
|
|
679
766
|
return null;
|
|
680
767
|
}
|
|
681
768
|
|
|
682
769
|
try {
|
|
683
770
|
const accounts = await this.getAccounts();
|
|
684
771
|
if (accounts && accounts.length > 0) {
|
|
685
|
-
|
|
772
|
+
// TODO: fetch userId if account is valid, generate userId if no userId matches address from tinybird
|
|
773
|
+
if (isAddress(accounts[0])) {
|
|
774
|
+
return accounts[0];
|
|
775
|
+
}
|
|
686
776
|
}
|
|
687
777
|
} catch (err) {
|
|
688
|
-
|
|
778
|
+
logger.error("Failed to fetch accounts from provider:", err);
|
|
689
779
|
return null;
|
|
690
780
|
}
|
|
691
781
|
return null;
|
|
@@ -703,7 +793,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
703
793
|
return res.filter(isAddress);
|
|
704
794
|
} catch (err) {
|
|
705
795
|
if ((err as any).code !== 4001) {
|
|
706
|
-
|
|
796
|
+
logger.error(
|
|
707
797
|
"FormoAnalytics::getAccounts: eth_accounts threw an error",
|
|
708
798
|
err
|
|
709
799
|
);
|
|
@@ -714,7 +804,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
714
804
|
|
|
715
805
|
private async getCurrentChainId(): Promise<number> {
|
|
716
806
|
if (!this.provider) {
|
|
717
|
-
|
|
807
|
+
logger.error("Provider not set for chain ID");
|
|
718
808
|
}
|
|
719
809
|
|
|
720
810
|
let chainIdHex;
|
|
@@ -723,15 +813,12 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
723
813
|
method: "eth_chainId",
|
|
724
814
|
});
|
|
725
815
|
if (!chainIdHex) {
|
|
726
|
-
|
|
816
|
+
logger.info("Chain id not found");
|
|
727
817
|
return 0;
|
|
728
818
|
}
|
|
729
819
|
return parseInt(chainIdHex as string, 16);
|
|
730
820
|
} catch (err) {
|
|
731
|
-
|
|
732
|
-
"FormoAnalytics::fetchChainId: eth_chainId threw an error",
|
|
733
|
-
err
|
|
734
|
-
);
|
|
821
|
+
logger.error("eth_chainId threw an error:", err);
|
|
735
822
|
return 0;
|
|
736
823
|
}
|
|
737
824
|
}
|
|
@@ -743,7 +830,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
743
830
|
return COUNTRY_LIST[timezone as keyof typeof COUNTRY_LIST];
|
|
744
831
|
return timezone;
|
|
745
832
|
} catch (error) {
|
|
746
|
-
|
|
833
|
+
logger.error("Error resolving timezone:", error);
|
|
747
834
|
return "";
|
|
748
835
|
}
|
|
749
836
|
}
|
|
@@ -756,7 +843,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
756
843
|
: navigator.language) || "en"
|
|
757
844
|
);
|
|
758
845
|
} catch (error) {
|
|
759
|
-
|
|
846
|
+
logger.error("Error resolving language:", error);
|
|
760
847
|
return "en";
|
|
761
848
|
}
|
|
762
849
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext, useEffect, useState, useRef } from "react";
|
|
2
2
|
import { FormoAnalytics } from "./FormoAnalytics";
|
|
3
3
|
import { FormoAnalyticsProviderProps } from "./types";
|
|
4
|
+
import { logger } from "./lib";
|
|
4
5
|
|
|
5
6
|
export const FormoAnalyticsContext = createContext<FormoAnalytics | undefined>(
|
|
6
7
|
undefined
|
|
@@ -11,12 +12,12 @@ export const FormoAnalyticsProvider = (props: FormoAnalyticsProviderProps) => {
|
|
|
11
12
|
|
|
12
13
|
// Keep the app running without analytics if no Write Key is provided or disabled
|
|
13
14
|
if (!writeKey) {
|
|
14
|
-
|
|
15
|
+
logger.error("FormoAnalyticsProvider: No Write Key provided");
|
|
15
16
|
return children;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
if (disabled) {
|
|
19
|
-
|
|
20
|
+
logger.warn("FormoAnalytics is disabled");
|
|
20
21
|
return children;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -35,9 +36,9 @@ const InitializedAnalytics = ({
|
|
|
35
36
|
try {
|
|
36
37
|
const sdkInstance = await FormoAnalytics.init(writeKey, options);
|
|
37
38
|
setSdk(sdkInstance);
|
|
38
|
-
|
|
39
|
+
logger.log("FormoAnalytics SDK initialized successfully");
|
|
39
40
|
} catch (error) {
|
|
40
|
-
|
|
41
|
+
logger.error("Failed to initialize FormoAnalytics SDK", error);
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
44
|
|
|
@@ -63,7 +64,7 @@ export const useFormoAnalytics = () => {
|
|
|
63
64
|
const context = useContext(FormoAnalyticsContext);
|
|
64
65
|
|
|
65
66
|
if (!context) {
|
|
66
|
-
|
|
67
|
+
logger.warn("useFormoAnalytics called without a valid context");
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
return context; // Return undefined if SDK is not initialized, handle accordingly in consumer
|
package/src/constants/base.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const STORAGE_PREFIX = "formo-";
|
|
2
|
+
|
|
3
|
+
const generateStoragePrefix = (prefix: string) => `${STORAGE_PREFIX}${prefix}`;
|
|
4
|
+
|
|
5
|
+
export const SESSION_IDENTIFIED_KEY =
|
|
6
|
+
generateStoragePrefix("session-identified");
|
|
7
|
+
export const SESSION_CURRENT_URL_KEY = generateStoragePrefix(
|
|
8
|
+
"analytics-current-url"
|
|
9
|
+
);
|
|
10
|
+
export const SESSION_USER_ID_KEY = generateStoragePrefix("user-id");
|
|
11
|
+
|
|
12
|
+
export const LOCAL_ANONYMOUS_ID_KEY = generateStoragePrefix("anonymous-id");
|
package/src/constants/config.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
1
|
+
export const EVENTS_API_HOST = "https://events.formo.so";
|
|
2
|
+
export const EVENTS_API_URL = `${EVENTS_API_HOST}/events`;
|
|
3
|
+
export const USER_API_URL = `${EVENTS_API_HOST}/user-id`;
|
|
4
|
+
|
|
5
|
+
export const EVENTS_API_REQUEST_HEADER = (writeKey: string) => ({
|
|
6
|
+
"Content-Type": "application/json",
|
|
7
|
+
Authorization: `Basic ${writeKey}`,
|
|
8
|
+
});
|
|
9
|
+
|
|
3
10
|
export const COUNTRY_LIST = {
|
|
4
11
|
// Africa
|
|
5
12
|
"Africa/Abidjan": "CI",
|
package/src/lib/fetch.ts
ADDED
package/src/lib/index.ts
CHANGED