@formo/analytics 1.14.3 → 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 +130 -56
- 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 +128 -54
- 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 +121 -52
- 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, {
|
|
@@ -321,9 +355,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
321
355
|
private trackProvider(provider: EIP1193Provider): void {
|
|
322
356
|
try {
|
|
323
357
|
if (provider === this._provider) {
|
|
324
|
-
|
|
325
|
-
"FormoAnalytics::trackProvider: Provider already tracked."
|
|
326
|
-
);
|
|
358
|
+
logger.warn("TrackProvider: Provider already tracked.");
|
|
327
359
|
return;
|
|
328
360
|
}
|
|
329
361
|
|
|
@@ -349,7 +381,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
349
381
|
this.registerSignatureListener();
|
|
350
382
|
this.registerTransactionListener();
|
|
351
383
|
} catch (error) {
|
|
352
|
-
|
|
384
|
+
logger.error("Error tracking provider:", error);
|
|
353
385
|
}
|
|
354
386
|
}
|
|
355
387
|
|
|
@@ -374,14 +406,14 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
374
406
|
|
|
375
407
|
private registerSignatureListener(): void {
|
|
376
408
|
if (!this.provider) {
|
|
377
|
-
|
|
409
|
+
logger.error("Provider not found for signature tracking");
|
|
378
410
|
return;
|
|
379
411
|
}
|
|
380
412
|
if (
|
|
381
413
|
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
382
414
|
false
|
|
383
415
|
) {
|
|
384
|
-
|
|
416
|
+
logger.warn("Provider.request is not writable");
|
|
385
417
|
return;
|
|
386
418
|
}
|
|
387
419
|
|
|
@@ -429,14 +461,14 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
429
461
|
|
|
430
462
|
private registerTransactionListener(): void {
|
|
431
463
|
if (!this.provider) {
|
|
432
|
-
|
|
464
|
+
logger.error("Provider not found for transaction tracking");
|
|
433
465
|
return;
|
|
434
466
|
}
|
|
435
467
|
if (
|
|
436
468
|
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
437
469
|
false
|
|
438
470
|
) {
|
|
439
|
-
|
|
471
|
+
logger.warn("Provider.request is not writable");
|
|
440
472
|
return;
|
|
441
473
|
}
|
|
442
474
|
const request = this.provider.request.bind(this.provider);
|
|
@@ -466,8 +498,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
466
498
|
|
|
467
499
|
return;
|
|
468
500
|
} catch (error) {
|
|
469
|
-
|
|
470
|
-
|
|
501
|
+
logger.info("Transaction listener catch");
|
|
502
|
+
logger.error("Transaction error:", error);
|
|
471
503
|
const rpcError = error as RPCError;
|
|
472
504
|
if (rpcError && rpcError?.code === 4001) {
|
|
473
505
|
// Emit transaction rejected event
|
|
@@ -495,12 +527,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
495
527
|
}
|
|
496
528
|
|
|
497
529
|
private async onAddressConnected(address: Address): Promise<void> {
|
|
498
|
-
if (address === this.currentConnectedAddress)
|
|
530
|
+
if (address === this.currentConnectedAddress)
|
|
499
531
|
// We have already reported this address
|
|
500
532
|
return;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
533
|
+
|
|
534
|
+
this.currentConnectedAddress = address;
|
|
504
535
|
|
|
505
536
|
this.currentChainId = await this.getCurrentChainId();
|
|
506
537
|
this.connect({ chainId: this.currentChainId, address });
|
|
@@ -516,6 +547,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
516
547
|
};
|
|
517
548
|
this.currentChainId = undefined;
|
|
518
549
|
this.currentConnectedAddress = undefined;
|
|
550
|
+
session.remove(SESSION_USER_ID_KEY);
|
|
519
551
|
|
|
520
552
|
await this.trackEvent(Event.DISCONNECT, payload);
|
|
521
553
|
}
|
|
@@ -531,21 +563,21 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
531
563
|
this.currentChainId = parseInt(chainIdHex);
|
|
532
564
|
if (!this.currentConnectedAddress) {
|
|
533
565
|
if (!this.provider) {
|
|
534
|
-
|
|
535
|
-
"
|
|
566
|
+
logger.info(
|
|
567
|
+
"OnChainChanged: Provider not found. CHAIN_CHANGED not reported"
|
|
536
568
|
);
|
|
537
569
|
return Promise.resolve();
|
|
538
570
|
}
|
|
539
571
|
|
|
540
572
|
const address = await this.getAddress();
|
|
541
573
|
if (!address) {
|
|
542
|
-
|
|
543
|
-
"
|
|
574
|
+
logger.info(
|
|
575
|
+
"OnChainChanged: Unable to fetch or store connected address"
|
|
544
576
|
);
|
|
545
577
|
return Promise.resolve();
|
|
546
578
|
}
|
|
547
|
-
|
|
548
579
|
this.currentConnectedAddress = address;
|
|
580
|
+
this.userId = await this.getUserId(address);
|
|
549
581
|
}
|
|
550
582
|
|
|
551
583
|
// Proceed only if the address exists
|
|
@@ -555,15 +587,15 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
555
587
|
address: this.currentConnectedAddress,
|
|
556
588
|
});
|
|
557
589
|
} else {
|
|
558
|
-
|
|
559
|
-
"
|
|
590
|
+
logger.info(
|
|
591
|
+
"OnChainChanged: Current connected address is null despite fetch attempt"
|
|
560
592
|
);
|
|
561
593
|
}
|
|
562
594
|
}
|
|
563
595
|
|
|
564
596
|
private async trackFirstPageHit(): Promise<void> {
|
|
565
|
-
if (session.get(
|
|
566
|
-
session.set(
|
|
597
|
+
if (session.get(SESSION_CURRENT_URL_KEY) === null) {
|
|
598
|
+
session.set(SESSION_CURRENT_URL_KEY, window.location.href);
|
|
567
599
|
}
|
|
568
600
|
|
|
569
601
|
return this.trackPageHit();
|
|
@@ -589,10 +621,10 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
589
621
|
}
|
|
590
622
|
|
|
591
623
|
private async onLocationChange(): Promise<void> {
|
|
592
|
-
const currentUrl = session.get(
|
|
624
|
+
const currentUrl = session.get(SESSION_CURRENT_URL_KEY);
|
|
593
625
|
|
|
594
626
|
if (currentUrl !== window.location.href) {
|
|
595
|
-
session.set(
|
|
627
|
+
session.set(SESSION_CURRENT_URL_KEY, window.location.href);
|
|
596
628
|
this.trackPageHit();
|
|
597
629
|
}
|
|
598
630
|
}
|
|
@@ -602,8 +634,8 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
602
634
|
const hash = window.location.hash;
|
|
603
635
|
|
|
604
636
|
if (!this.config.trackLocalhost && isLocalhost()) {
|
|
605
|
-
return
|
|
606
|
-
"
|
|
637
|
+
return logger.warn(
|
|
638
|
+
"Track page hit: Ignoring event because website is running locally"
|
|
607
639
|
);
|
|
608
640
|
}
|
|
609
641
|
|
|
@@ -618,8 +650,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
618
650
|
private async trackEvent(action: string, payload: any): Promise<void> {
|
|
619
651
|
try {
|
|
620
652
|
const address = await this.getAddress();
|
|
653
|
+
const user_id = await this.getUserId(address);
|
|
621
654
|
|
|
622
655
|
const requestData: RequestEvent = {
|
|
656
|
+
anonymous_id: this.anonymousId as UUID,
|
|
657
|
+
user_id,
|
|
623
658
|
address,
|
|
624
659
|
timestamp: new Date().toISOString(),
|
|
625
660
|
action,
|
|
@@ -629,11 +664,11 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
629
664
|
|
|
630
665
|
await this.eventQueue.enqueue(requestData, (err, _, data) => {
|
|
631
666
|
if (err) {
|
|
632
|
-
|
|
633
|
-
} else
|
|
667
|
+
logger.error("Error sending events:", err);
|
|
668
|
+
} else logger.info(`Events sent successfully: ${data.length} events`);
|
|
634
669
|
});
|
|
635
670
|
} catch (error) {
|
|
636
|
-
|
|
671
|
+
logger.error("Error tracking event:", error);
|
|
637
672
|
}
|
|
638
673
|
}
|
|
639
674
|
|
|
@@ -682,7 +717,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
682
717
|
}
|
|
683
718
|
}
|
|
684
719
|
} catch (err) {
|
|
685
|
-
|
|
720
|
+
logger.error("Error identifying all:", err);
|
|
686
721
|
}
|
|
687
722
|
}
|
|
688
723
|
|
|
@@ -690,20 +725,57 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
690
725
|
return this._provider;
|
|
691
726
|
}
|
|
692
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
|
+
|
|
693
762
|
private async getAddress(): Promise<Address | null> {
|
|
694
763
|
if (this.currentConnectedAddress) return this.currentConnectedAddress;
|
|
695
764
|
if (!this?.provider) {
|
|
696
|
-
|
|
765
|
+
logger.info("The provider is not set");
|
|
697
766
|
return null;
|
|
698
767
|
}
|
|
699
768
|
|
|
700
769
|
try {
|
|
701
770
|
const accounts = await this.getAccounts();
|
|
702
771
|
if (accounts && accounts.length > 0) {
|
|
703
|
-
|
|
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
|
+
}
|
|
704
776
|
}
|
|
705
777
|
} catch (err) {
|
|
706
|
-
|
|
778
|
+
logger.error("Failed to fetch accounts from provider:", err);
|
|
707
779
|
return null;
|
|
708
780
|
}
|
|
709
781
|
return null;
|
|
@@ -721,7 +793,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
721
793
|
return res.filter(isAddress);
|
|
722
794
|
} catch (err) {
|
|
723
795
|
if ((err as any).code !== 4001) {
|
|
724
|
-
|
|
796
|
+
logger.error(
|
|
725
797
|
"FormoAnalytics::getAccounts: eth_accounts threw an error",
|
|
726
798
|
err
|
|
727
799
|
);
|
|
@@ -732,7 +804,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
732
804
|
|
|
733
805
|
private async getCurrentChainId(): Promise<number> {
|
|
734
806
|
if (!this.provider) {
|
|
735
|
-
|
|
807
|
+
logger.error("Provider not set for chain ID");
|
|
736
808
|
}
|
|
737
809
|
|
|
738
810
|
let chainIdHex;
|
|
@@ -741,15 +813,12 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
741
813
|
method: "eth_chainId",
|
|
742
814
|
});
|
|
743
815
|
if (!chainIdHex) {
|
|
744
|
-
|
|
816
|
+
logger.info("Chain id not found");
|
|
745
817
|
return 0;
|
|
746
818
|
}
|
|
747
819
|
return parseInt(chainIdHex as string, 16);
|
|
748
820
|
} catch (err) {
|
|
749
|
-
|
|
750
|
-
"FormoAnalytics::fetchChainId: eth_chainId threw an error",
|
|
751
|
-
err
|
|
752
|
-
);
|
|
821
|
+
logger.error("eth_chainId threw an error:", err);
|
|
753
822
|
return 0;
|
|
754
823
|
}
|
|
755
824
|
}
|
|
@@ -761,7 +830,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
761
830
|
return COUNTRY_LIST[timezone as keyof typeof COUNTRY_LIST];
|
|
762
831
|
return timezone;
|
|
763
832
|
} catch (error) {
|
|
764
|
-
|
|
833
|
+
logger.error("Error resolving timezone:", error);
|
|
765
834
|
return "";
|
|
766
835
|
}
|
|
767
836
|
}
|
|
@@ -774,7 +843,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
774
843
|
: navigator.language) || "en"
|
|
775
844
|
);
|
|
776
845
|
} catch (error) {
|
|
777
|
-
|
|
846
|
+
logger.error("Error resolving language:", error);
|
|
778
847
|
return "en";
|
|
779
848
|
}
|
|
780
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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "trace";
|
|
2
|
+
|
|
3
|
+
export class Logger {
|
|
4
|
+
private static instance: Logger;
|
|
5
|
+
private enabledLevels: Set<LogLevel>;
|
|
6
|
+
private enabled: boolean;
|
|
7
|
+
|
|
8
|
+
private constructor(enabled: boolean = true, enabledLevels: LogLevel[] = []) {
|
|
9
|
+
this.enabled = enabled;
|
|
10
|
+
this.enabledLevels = new Set(enabledLevels);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static getInstance(
|
|
14
|
+
enabled: boolean = false,
|
|
15
|
+
enabledLevels: LogLevel[] = []
|
|
16
|
+
): Logger {
|
|
17
|
+
if (!Logger.instance) {
|
|
18
|
+
Logger.instance = new Logger(enabled, enabledLevels);
|
|
19
|
+
}
|
|
20
|
+
return Logger.instance;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public setEnabled(enabled: boolean): void {
|
|
24
|
+
this.enabled = enabled;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public isLoggingEnabled(): boolean {
|
|
28
|
+
return this.enabled;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public setEnabledLevels(levels: LogLevel[]): void {
|
|
32
|
+
this.enabledLevels = new Set(levels);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public getEnabledLevels(): LogLevel[] {
|
|
36
|
+
return Array.from(this.enabledLevels);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private shouldLog(level: LogLevel): boolean {
|
|
40
|
+
if (!this.enabled) return false;
|
|
41
|
+
return this.enabledLevels.has(level);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private formatMessage(message: string): string {
|
|
45
|
+
const timestamp = new Date().toLocaleString("en-US", {
|
|
46
|
+
year: "numeric",
|
|
47
|
+
month: "2-digit",
|
|
48
|
+
day: "2-digit",
|
|
49
|
+
hour: "2-digit",
|
|
50
|
+
minute: "2-digit",
|
|
51
|
+
second: "2-digit",
|
|
52
|
+
hour12: false,
|
|
53
|
+
});
|
|
54
|
+
return `[Formo Analytics][${timestamp}] ${message}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public debug(message: string, ...args: any[]): void {
|
|
58
|
+
if (this.shouldLog("debug")) {
|
|
59
|
+
console.debug(this.formatMessage(message), ...args);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public info(message: string, ...args: any[]): void {
|
|
64
|
+
if (this.shouldLog("info")) {
|
|
65
|
+
console.info(this.formatMessage(message), ...args);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public warn(message: string, ...args: any[]): void {
|
|
70
|
+
if (this.shouldLog("warn")) {
|
|
71
|
+
console.warn(this.formatMessage(message), ...args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public error(message: string, ...args: any[]): void {
|
|
76
|
+
if (this.shouldLog("error")) {
|
|
77
|
+
console.error(this.formatMessage(message), ...args);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public trace(message: string, ...args: any[]): void {
|
|
82
|
+
if (this.shouldLog("trace")) {
|
|
83
|
+
console.trace(this.formatMessage(message), ...args);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public log(message: string, ...args: any[]): void {
|
|
88
|
+
this.info(message, ...args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Export a default instance for easy use
|
|
93
|
+
export const logger = Logger.getInstance();
|