@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.
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 +181 -102
  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 +179 -100
  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 +176 -89
  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.2",
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, {
@@ -319,29 +353,36 @@ export class FormoAnalytics implements IFormoAnalytics {
319
353
  */
320
354
 
321
355
  private trackProvider(provider: EIP1193Provider): void {
322
- if (provider === this._provider) {
323
- console.warn("FormoAnalytics::trackProvider: Provider already tracked.");
324
- return;
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
- if (this._provider) {
331
- const actions = Object.keys(this._providerListeners);
332
- for (const action of actions) {
333
- this._provider.removeListener(action, this._providerListeners[action]);
334
- delete this._providerListeners[action];
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
- this._provider = provider;
376
+ this._provider = provider;
339
377
 
340
- // Register listeners for web3 provider events
341
- this.registerAddressChangedListener();
342
- this.registerChainChangedListener();
343
- this.registerSignatureListener();
344
- this.registerTransactionListener();
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
- console.error("_trackSigning: provider not found");
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
- console.warn("_trackSigning: provider.request is not writable");
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
- console.error("_trackTransactions: provider not found");
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
- console.warn("_trackTransactions: provider.request is not writable");
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
- console.log("transaction listener catch");
461
- console.log(error);
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
- } else {
493
- this.currentConnectedAddress = address;
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
- console.log(
526
- "FormoAnalytics::onChainChanged: provider not found. CHAIN_CHANGED not reported"
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
- console.log(
534
- "FormoAnalytics::onChainChanged: Unable to fetch or store connected address"
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
- console.log(
550
- "FormoAnalytics::onChainChanged: currentConnectedAddress is null despite fetch attempt"
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(CURRENT_URL_KEY) === null) {
557
- 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);
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(CURRENT_URL_KEY);
624
+ const currentUrl = session.get(SESSION_CURRENT_URL_KEY);
584
625
 
585
626
  if (currentUrl !== window.location.href) {
586
- session.set(CURRENT_URL_KEY, window.location.href);
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 console.warn(
597
- "FormoAnalytics::trackPageHit: Ignoring event because website is running locally"
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
- const address = await this.getAddress();
611
-
612
- const requestData: RequestEvent = {
613
- address,
614
- timestamp: new Date().toISOString(),
615
- action,
616
- version: "1",
617
- payload: await this.buildEventPayload(toSnakeCase(payload)),
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
- await this.eventQueue.enqueue(requestData, (err, _, data) => {
621
- if (err) {
622
- console.error(err);
623
- } else console.log(`Events sent successfully: ${data.length} events`);
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
- const providers = [...store.getProviders()];
681
+ let providers = store.getProviders();
634
682
  // TODO: consider using store.subscribe to detect changes to providers list
635
- // store.subscribe(providers => (state.providers = providers))
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 [window?.ethereum];
687
+ return window?.ethereum ? [window.ethereum] : [];
640
688
  }
641
689
  return providers;
642
690
  }
643
691
 
644
- private async identifyAll(providers: EIP6963ProviderDetail[]): Promise<void> {
692
+ private async identifyAll(
693
+ providers: readonly EIP6963ProviderDetail[]
694
+ ): Promise<void> {
645
695
  try {
646
- for (const { provider, info } of providers) {
647
- const accounts = await this.getAccounts(provider);
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
- console.log("identifying all => err", err);
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
- console.log("FormoAnalytics::getAddress: the provider is not set");
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
- 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
+ }
686
776
  }
687
777
  } catch (err) {
688
- console.log("Failed to fetch accounts from provider:", err);
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
- console.log(
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
- console.error("FormoAnalytics::getCurrentChainId: provider not set");
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
- console.log("FormoAnalytics::fetchChainId: chain id not found");
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
- console.log(
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
- console.error("Error resolving timezone:", error);
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
- console.error("Error resolving language:", error);
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
- 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";