@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.
Files changed (178) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +10 -0
  2. package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
  3. package/dist/cjs/src/FormoAnalytics.js +130 -56
  4. package/dist/cjs/src/FormoAnalytics.js.map +1 -1
  5. package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
  6. package/dist/cjs/src/FormoAnalyticsProvider.js +6 -5
  7. package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -1
  8. package/dist/cjs/src/constants/base.d.ts +3 -1
  9. package/dist/cjs/src/constants/base.d.ts.map +1 -1
  10. package/dist/cjs/src/constants/base.js +7 -3
  11. package/dist/cjs/src/constants/base.js.map +1 -1
  12. package/dist/cjs/src/constants/config.d.ts +6 -1
  13. package/dist/cjs/src/constants/config.d.ts.map +1 -1
  14. package/dist/cjs/src/constants/config.js +9 -3
  15. package/dist/cjs/src/constants/config.js.map +1 -1
  16. package/dist/cjs/src/lib/fetch.d.ts +3 -0
  17. package/dist/cjs/src/lib/fetch.d.ts.map +1 -0
  18. package/dist/cjs/src/lib/fetch.js +8 -0
  19. package/dist/cjs/src/lib/fetch.js.map +1 -0
  20. package/dist/cjs/src/lib/index.d.ts +4 -2
  21. package/dist/cjs/src/lib/index.d.ts.map +1 -1
  22. package/dist/cjs/src/lib/index.js +6 -4
  23. package/dist/cjs/src/lib/index.js.map +1 -1
  24. package/dist/cjs/src/lib/logger.d.ts +22 -0
  25. package/dist/cjs/src/lib/logger.d.ts.map +1 -0
  26. package/dist/cjs/src/lib/logger.js +114 -0
  27. package/dist/cjs/src/lib/logger.js.map +1 -0
  28. package/dist/cjs/src/lib/queue.d.ts +2 -3
  29. package/dist/cjs/src/lib/queue.d.ts.map +1 -1
  30. package/dist/cjs/src/lib/queue.js +42 -64
  31. package/dist/cjs/src/lib/queue.js.map +1 -1
  32. package/dist/cjs/src/lib/storage/index.d.ts +3 -0
  33. package/dist/cjs/src/lib/storage/index.d.ts.map +1 -0
  34. package/dist/cjs/src/lib/storage/index.js +11 -0
  35. package/dist/cjs/src/lib/storage/index.js.map +1 -0
  36. package/dist/cjs/src/lib/storage/local.d.ts +4 -0
  37. package/dist/cjs/src/lib/storage/local.d.ts.map +1 -0
  38. package/dist/cjs/src/lib/storage/local.js +8 -0
  39. package/dist/cjs/src/lib/storage/local.js.map +1 -0
  40. package/dist/cjs/src/lib/storage/native.d.ts +15 -0
  41. package/dist/cjs/src/lib/storage/native.d.ts.map +1 -0
  42. package/dist/cjs/src/lib/storage/native.js +83 -0
  43. package/dist/cjs/src/lib/storage/native.js.map +1 -0
  44. package/dist/cjs/src/lib/storage/session.d.ts +4 -0
  45. package/dist/cjs/src/lib/storage/session.d.ts.map +1 -0
  46. package/dist/cjs/src/lib/storage/session.js +8 -0
  47. package/dist/cjs/src/lib/storage/session.js.map +1 -0
  48. package/dist/cjs/src/types/base.d.ts +5 -0
  49. package/dist/cjs/src/types/base.d.ts.map +1 -1
  50. package/dist/cjs/src/types/events.d.ts +6 -0
  51. package/dist/cjs/src/types/events.d.ts.map +1 -1
  52. package/dist/cjs/src/types/events.js.map +1 -1
  53. package/dist/cjs/src/{lib/utils.d.ts → utils/base.d.ts} +4 -3
  54. package/dist/cjs/src/utils/base.d.ts.map +1 -0
  55. package/dist/cjs/src/utils/base.js +113 -0
  56. package/dist/cjs/src/utils/base.js.map +1 -0
  57. package/dist/cjs/src/utils/index.d.ts +3 -0
  58. package/dist/cjs/src/utils/index.d.ts.map +1 -0
  59. package/dist/cjs/src/utils/index.js +19 -0
  60. package/dist/cjs/src/utils/index.js.map +1 -0
  61. package/dist/cjs/src/utils/is.d.ts +3 -0
  62. package/dist/cjs/src/utils/is.d.ts.map +1 -0
  63. package/dist/cjs/src/utils/is.js +11 -0
  64. package/dist/cjs/src/utils/is.js.map +1 -0
  65. package/dist/cjs/test/lib.spec.js +5 -5
  66. package/dist/cjs/test/lib.spec.js.map +1 -1
  67. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  68. package/dist/esm/src/FormoAnalytics.d.ts +10 -0
  69. package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
  70. package/dist/esm/src/FormoAnalytics.js +128 -54
  71. package/dist/esm/src/FormoAnalytics.js.map +1 -1
  72. package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
  73. package/dist/esm/src/FormoAnalyticsProvider.js +6 -5
  74. package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -1
  75. package/dist/esm/src/constants/base.d.ts +3 -1
  76. package/dist/esm/src/constants/base.d.ts.map +1 -1
  77. package/dist/esm/src/constants/base.js +6 -2
  78. package/dist/esm/src/constants/base.js.map +1 -1
  79. package/dist/esm/src/constants/config.d.ts +6 -1
  80. package/dist/esm/src/constants/config.d.ts.map +1 -1
  81. package/dist/esm/src/constants/config.js +7 -2
  82. package/dist/esm/src/constants/config.js.map +1 -1
  83. package/dist/esm/src/lib/fetch.d.ts +3 -0
  84. package/dist/esm/src/lib/fetch.d.ts.map +1 -0
  85. package/dist/esm/src/lib/fetch.js +3 -0
  86. package/dist/esm/src/lib/fetch.js.map +1 -0
  87. package/dist/esm/src/lib/index.d.ts +4 -2
  88. package/dist/esm/src/lib/index.d.ts.map +1 -1
  89. package/dist/esm/src/lib/index.js +4 -2
  90. package/dist/esm/src/lib/index.js.map +1 -1
  91. package/dist/esm/src/lib/logger.d.ts +22 -0
  92. package/dist/esm/src/lib/logger.d.ts.map +1 -0
  93. package/dist/esm/src/lib/logger.js +111 -0
  94. package/dist/esm/src/lib/logger.js.map +1 -0
  95. package/dist/esm/src/lib/queue.d.ts +2 -3
  96. package/dist/esm/src/lib/queue.d.ts.map +1 -1
  97. package/dist/esm/src/lib/queue.js +42 -64
  98. package/dist/esm/src/lib/queue.js.map +1 -1
  99. package/dist/esm/src/lib/storage/index.d.ts +3 -0
  100. package/dist/esm/src/lib/storage/index.d.ts.map +1 -0
  101. package/dist/esm/src/lib/storage/index.js +3 -0
  102. package/dist/esm/src/lib/storage/index.js.map +1 -0
  103. package/dist/esm/src/lib/storage/local.d.ts +4 -0
  104. package/dist/esm/src/lib/storage/local.d.ts.map +1 -0
  105. package/dist/esm/src/lib/storage/local.js +3 -0
  106. package/dist/esm/src/lib/storage/local.js.map +1 -0
  107. package/dist/esm/src/lib/storage/native.d.ts +15 -0
  108. package/dist/esm/src/lib/storage/native.d.ts.map +1 -0
  109. package/dist/esm/src/lib/storage/native.js +80 -0
  110. package/dist/esm/src/lib/storage/native.js.map +1 -0
  111. package/dist/esm/src/lib/storage/session.d.ts +4 -0
  112. package/dist/esm/src/lib/storage/session.d.ts.map +1 -0
  113. package/dist/esm/src/lib/storage/session.js +3 -0
  114. package/dist/esm/src/lib/storage/session.js.map +1 -0
  115. package/dist/esm/src/types/base.d.ts +5 -0
  116. package/dist/esm/src/types/base.d.ts.map +1 -1
  117. package/dist/esm/src/types/events.d.ts +6 -0
  118. package/dist/esm/src/types/events.d.ts.map +1 -1
  119. package/dist/esm/src/types/events.js.map +1 -1
  120. package/dist/esm/src/{lib/utils.d.ts → utils/base.d.ts} +4 -3
  121. package/dist/esm/src/utils/base.d.ts.map +1 -0
  122. package/dist/esm/src/{lib/fingerprint.js → utils/base.js} +66 -23
  123. package/dist/esm/src/utils/base.js.map +1 -0
  124. package/dist/esm/src/utils/index.d.ts +3 -0
  125. package/dist/esm/src/utils/index.d.ts.map +1 -0
  126. package/dist/esm/src/utils/index.js +3 -0
  127. package/dist/esm/src/utils/index.js.map +1 -0
  128. package/dist/esm/src/utils/is.d.ts +3 -0
  129. package/dist/esm/src/utils/is.d.ts.map +1 -0
  130. package/dist/esm/src/utils/is.js +6 -0
  131. package/dist/esm/src/utils/is.js.map +1 -0
  132. package/dist/esm/test/lib.spec.js +1 -1
  133. package/dist/esm/test/lib.spec.js.map +1 -1
  134. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  135. package/dist/index.umd.min.js +1 -1
  136. package/dist/index.umd.min.js.map +1 -1
  137. package/package.json +1 -2
  138. package/src/FormoAnalytics.ts +121 -52
  139. package/src/FormoAnalyticsProvider.tsx +6 -5
  140. package/src/constants/base.ts +12 -2
  141. package/src/constants/config.ts +9 -2
  142. package/src/lib/fetch.ts +3 -0
  143. package/src/lib/index.ts +4 -2
  144. package/src/lib/logger.ts +93 -0
  145. package/src/lib/queue.ts +27 -48
  146. package/src/lib/storage/index.ts +2 -0
  147. package/src/lib/storage/local.ts +3 -0
  148. package/src/lib/storage/native.ts +93 -0
  149. package/src/lib/storage/session.ts +3 -0
  150. package/src/types/base.ts +5 -0
  151. package/src/types/events.ts +8 -0
  152. package/src/{lib/utils.ts → utils/base.ts} +16 -8
  153. package/src/utils/index.ts +2 -0
  154. package/src/utils/is.ts +8 -0
  155. package/test/lib.spec.ts +1 -1
  156. package/dist/cjs/src/lib/fingerprint.d.ts +0 -4
  157. package/dist/cjs/src/lib/fingerprint.d.ts.map +0 -1
  158. package/dist/cjs/src/lib/fingerprint.js +0 -63
  159. package/dist/cjs/src/lib/fingerprint.js.map +0 -1
  160. package/dist/cjs/src/lib/session-storage.d.ts +0 -11
  161. package/dist/cjs/src/lib/session-storage.d.ts.map +0 -1
  162. package/dist/cjs/src/lib/session-storage.js +0 -52
  163. package/dist/cjs/src/lib/session-storage.js.map +0 -1
  164. package/dist/cjs/src/lib/utils.d.ts.map +0 -1
  165. package/dist/cjs/src/lib/utils.js +0 -63
  166. package/dist/cjs/src/lib/utils.js.map +0 -1
  167. package/dist/esm/src/lib/fingerprint.d.ts +0 -4
  168. package/dist/esm/src/lib/fingerprint.d.ts.map +0 -1
  169. package/dist/esm/src/lib/fingerprint.js.map +0 -1
  170. package/dist/esm/src/lib/session-storage.d.ts +0 -11
  171. package/dist/esm/src/lib/session-storage.d.ts.map +0 -1
  172. package/dist/esm/src/lib/session-storage.js +0 -49
  173. package/dist/esm/src/lib/session-storage.js.map +0 -1
  174. package/dist/esm/src/lib/utils.d.ts.map +0 -1
  175. package/dist/esm/src/lib/utils.js +0 -53
  176. package/dist/esm/src/lib/utils.js.map +0 -1
  177. package/src/lib/fingerprint.ts +0 -9
  178. 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.14.3",
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"
@@ -1,9 +1,13 @@
1
1
  import { createStore, EIP6963ProviderDetail } from "mipd";
2
2
  import {
3
+ LOCAL_ANONYMOUS_ID_KEY,
3
4
  COUNTRY_LIST,
4
- CURRENT_URL_KEY,
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, isLocalhost, toSnakeCase, isAddress } from "./lib";
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 { EventQueue } from "./lib/queue";
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
- throw new Error("FormoAnalytics::connect: chain ID cannot be empty");
178
+ logger.warn("Connect: Chain ID cannot be empty");
143
179
  }
144
180
  if (!address) {
145
- throw new Error("FormoAnalytics::connect: address cannot be empty");
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 console.warn(
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
- console.warn(
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
- console.error("FormoAnalytics::trackProvider: error:", error);
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
- console.error("_trackSigning: provider not found");
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
- console.warn("_trackSigning: provider.request is not writable");
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
- console.error("_trackTransactions: provider not found");
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
- console.warn("_trackTransactions: provider.request is not writable");
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
- console.log("transaction listener catch");
470
- console.log(error);
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
- } else {
502
- this.currentConnectedAddress = address;
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
- console.log(
535
- "FormoAnalytics::onChainChanged: provider not found. CHAIN_CHANGED not reported"
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
- console.log(
543
- "FormoAnalytics::onChainChanged: Unable to fetch or store connected address"
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
- console.log(
559
- "FormoAnalytics::onChainChanged: currentConnectedAddress is null despite fetch attempt"
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(CURRENT_URL_KEY) === null) {
566
- session.set(CURRENT_URL_KEY, window.location.href);
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(CURRENT_URL_KEY);
624
+ const currentUrl = session.get(SESSION_CURRENT_URL_KEY);
593
625
 
594
626
  if (currentUrl !== window.location.href) {
595
- session.set(CURRENT_URL_KEY, window.location.href);
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 console.warn(
606
- "FormoAnalytics::trackPageHit: Ignoring event because website is running locally"
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
- console.error(err);
633
- } else console.log(`Events sent successfully: ${data.length} events`);
667
+ logger.error("Error sending events:", err);
668
+ } else logger.info(`Events sent successfully: ${data.length} events`);
634
669
  });
635
670
  } catch (error) {
636
- console.error("FormoAnalytics::trackEvent: error:", error);
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
- console.log("identifying all => err", err);
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
- console.log("FormoAnalytics::getAddress: the provider is not set");
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
- return isAddress(accounts[0]) ? accounts[0] : null;
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
- console.log("Failed to fetch accounts from provider:", err);
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
- console.log(
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
- console.error("FormoAnalytics::getCurrentChainId: provider not set");
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
- console.log("FormoAnalytics::fetchChainId: chain id not found");
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
- console.log(
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
- console.error("Error resolving timezone:", error);
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
- console.error("Error resolving language:", error);
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
- console.error("FormoAnalyticsProvider: No Write Key provided");
15
+ logger.error("FormoAnalyticsProvider: No Write Key provided");
15
16
  return children;
16
17
  }
17
18
 
18
19
  if (disabled) {
19
- console.warn("FormoAnalytics is disabled");
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
- console.log("FormoAnalytics SDK initialized successfully");
39
+ logger.log("FormoAnalytics SDK initialized successfully");
39
40
  } catch (error) {
40
- console.error("Failed to initialize FormoAnalytics SDK", error);
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
- console.warn("useFormoAnalytics called without a valid context");
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
@@ -1,2 +1,12 @@
1
- export const SESSION_PREFIX = "f0-";
2
- export const SESSION_IDENTIFIED_KEY = SESSION_PREFIX + "session_identified";
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");
@@ -1,5 +1,12 @@
1
- export const EVENTS_API_URL = "https://events.formo.so/events";
2
- export const CURRENT_URL_KEY = "formo-analytics-current-url";
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",
@@ -0,0 +1,3 @@
1
+ import fetch from "fetch-retry";
2
+
3
+ export default fetch(global.fetch);
package/src/lib/index.ts CHANGED
@@ -1,2 +1,4 @@
1
- export { default as session } from "./session-storage";
2
- export * from "./utils";
1
+ export * from "./logger";
2
+ export * from "./queue";
3
+ export * from "./storage";
4
+ export { default as fetch } from "./fetch";
@@ -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();