@formo/analytics 1.13.0-alpha.1 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +91 -24
  2. package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
  3. package/dist/cjs/src/FormoAnalytics.js +325 -37
  4. package/dist/cjs/src/FormoAnalytics.js.map +1 -1
  5. package/dist/cjs/src/constants/config.d.ts +522 -522
  6. package/dist/cjs/src/constants/config.d.ts.map +1 -1
  7. package/dist/cjs/src/constants/config.js +557 -557
  8. package/dist/cjs/src/constants/config.js.map +1 -1
  9. package/dist/cjs/src/constants/events.d.ts +1 -1
  10. package/dist/cjs/src/constants/events.d.ts.map +1 -1
  11. package/dist/cjs/src/constants/events.js +1 -1
  12. package/dist/cjs/src/constants/events.js.map +1 -1
  13. package/dist/cjs/src/types/base.d.ts +1 -1
  14. package/dist/cjs/src/types/base.d.ts.map +1 -1
  15. package/dist/cjs/src/types/events.d.ts +11 -0
  16. package/dist/cjs/src/types/events.d.ts.map +1 -0
  17. package/dist/cjs/src/types/events.js +16 -0
  18. package/dist/cjs/src/types/events.js.map +1 -0
  19. package/dist/cjs/src/types/index.d.ts +2 -1
  20. package/dist/cjs/src/types/index.d.ts.map +1 -1
  21. package/dist/cjs/src/types/index.js +2 -1
  22. package/dist/cjs/src/types/index.js.map +1 -1
  23. package/dist/cjs/src/types/{wallet.d.ts → provider.d.ts} +5 -1
  24. package/dist/cjs/src/types/provider.d.ts.map +1 -0
  25. package/dist/cjs/src/types/{wallet.js → provider.js} +1 -1
  26. package/dist/cjs/src/types/provider.js.map +1 -0
  27. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  28. package/dist/esm/src/FormoAnalytics.d.ts +91 -24
  29. package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
  30. package/dist/esm/src/FormoAnalytics.js +326 -38
  31. package/dist/esm/src/FormoAnalytics.js.map +1 -1
  32. package/dist/esm/src/constants/config.d.ts +522 -522
  33. package/dist/esm/src/constants/config.d.ts.map +1 -1
  34. package/dist/esm/src/constants/config.js +556 -556
  35. package/dist/esm/src/constants/config.js.map +1 -1
  36. package/dist/esm/src/constants/events.d.ts +1 -1
  37. package/dist/esm/src/constants/events.d.ts.map +1 -1
  38. package/dist/esm/src/constants/events.js +1 -1
  39. package/dist/esm/src/constants/events.js.map +1 -1
  40. package/dist/esm/src/types/base.d.ts +1 -1
  41. package/dist/esm/src/types/base.d.ts.map +1 -1
  42. package/dist/esm/src/types/events.d.ts +11 -0
  43. package/dist/esm/src/types/events.d.ts.map +1 -0
  44. package/dist/esm/src/types/events.js +13 -0
  45. package/dist/esm/src/types/events.js.map +1 -0
  46. package/dist/esm/src/types/index.d.ts +2 -1
  47. package/dist/esm/src/types/index.d.ts.map +1 -1
  48. package/dist/esm/src/types/index.js +2 -1
  49. package/dist/esm/src/types/index.js.map +1 -1
  50. package/dist/esm/src/types/{wallet.d.ts → provider.d.ts} +5 -1
  51. package/dist/esm/src/types/provider.d.ts.map +1 -0
  52. package/dist/esm/src/types/provider.js +2 -0
  53. package/dist/esm/src/types/provider.js.map +1 -0
  54. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  55. package/dist/index.umd.min.js +1 -1
  56. package/dist/index.umd.min.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/FormoAnalytics.ts +418 -69
  59. package/src/constants/config.ts +556 -556
  60. package/src/constants/events.ts +1 -1
  61. package/src/types/base.ts +1 -1
  62. package/src/types/events.ts +11 -0
  63. package/src/types/index.ts +2 -1
  64. package/src/types/{wallet.ts → provider.ts} +5 -0
  65. package/dist/cjs/src/types/wallet.d.ts.map +0 -1
  66. package/dist/cjs/src/types/wallet.js.map +0 -1
  67. package/dist/esm/src/types/wallet.d.ts.map +0 -1
  68. package/dist/esm/src/types/wallet.js +0 -2
  69. package/dist/esm/src/types/wallet.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formo/analytics",
3
- "version": "1.13.0-alpha.1",
3
+ "version": "1.13.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/getformo/sdk.git"
@@ -1,45 +1,66 @@
1
1
  import axios from "axios";
2
2
  import {
3
3
  COUNTRY_LIST,
4
+ CURRENT_URL_KEY,
4
5
  EVENTS_API_URL,
5
6
  Event,
6
7
  } from "./constants";
7
8
  import { H } from "highlight.run";
8
- import { ChainID, Address, EIP1193Provider, Options, Config } from "./types";
9
+ import {
10
+ ChainID,
11
+ Address,
12
+ EIP1193Provider,
13
+ Options,
14
+ Config,
15
+ RequestArguments,
16
+ RPCError,
17
+ SignatureStatus,
18
+ TransactionStatus,
19
+ } from "./types";
9
20
 
10
21
  interface IFormoAnalytics {
11
- /**
12
- * Tracks page visit events.
13
- */
14
22
  page(): void;
15
-
16
- /**
17
- * Connects to a wallet with the specified chain ID and address.
18
- */
19
- connect(params: { chainId: ChainID; address: string }): Promise<void>;
20
-
21
- /**
22
- * Disconnects the current wallet.
23
- */
24
- disconnect(params?: { chainId?: ChainID; address?: string }): Promise<void>;
25
-
26
- /**
27
- * Switches the blockchain chain context and optionally logs additional params.
28
- */
29
- chain(params: { chainId: ChainID; address?: string }): Promise<void>;
30
-
31
- /**
32
- * Tracks a specific event with a name and associated data.
33
- */
34
- track(eventName: string, eventData: Record<string, any>): Promise<void>;
23
+ // trackPagesChange(): void;
24
+ connect(params: { chainId: ChainID; address: Address }): Promise<void>;
25
+ disconnect(params?: { chainId?: ChainID; address?: Address }): Promise<void>;
26
+ chain(params: { chainId: ChainID; address?: Address }): Promise<void>;
27
+ signature({
28
+ status,
29
+ chainId,
30
+ address,
31
+ message,
32
+ signatureHash,
33
+ }: {
34
+ status: SignatureStatus;
35
+ chainId?: ChainID;
36
+ address: Address;
37
+ message: string;
38
+ signatureHash?: string;
39
+ }): Promise<void>;
40
+ transaction({
41
+ status,
42
+ chainId,
43
+ address,
44
+ data,
45
+ to,
46
+ value,
47
+ transactionHash,
48
+ }: {
49
+ status: TransactionStatus;
50
+ chainId: ChainID;
51
+ address: Address;
52
+ data?: string;
53
+ to?: string;
54
+ value?: string;
55
+ transactionHash?: string;
56
+ }): Promise<void>;
57
+ identify(params: { address: Address }): Promise<void>;
58
+ track(action: string, payload: Record<string, any>): Promise<void>;
35
59
  }
36
60
 
37
61
  export class FormoAnalytics implements IFormoAnalytics {
38
62
  private _provider?: EIP1193Provider;
39
- private _providerListeners: Record<
40
- string,
41
- (...args: unknown[]) => void
42
- > = {};
63
+ private _providerListeners: Record<string, (...args: unknown[]) => void> = {};
43
64
 
44
65
  config: Config;
45
66
  currentChainId?: ChainID;
@@ -53,11 +74,15 @@ export class FormoAnalytics implements IFormoAnalytics {
53
74
  apiKey: apiKey,
54
75
  };
55
76
 
77
+ // TODO: replace with eip6963
56
78
  const provider =
57
79
  window?.ethereum || window.web3?.currentProvider || options?.provider;
58
80
  if (provider) {
59
81
  this.trackProvider(provider);
60
82
  }
83
+
84
+ this.trackFirstPageVisit();
85
+ this.trackPagesChange();
61
86
  }
62
87
 
63
88
  static async init(
@@ -66,14 +91,40 @@ export class FormoAnalytics implements IFormoAnalytics {
66
91
  ): Promise<FormoAnalytics> {
67
92
  // May be needed for delayed loading
68
93
  // https://github.com/segmentio/analytics-next/tree/master/packages/browser#lazy--delayed-loading
69
- return new FormoAnalytics(apiKey, options);
94
+ const analytics = new FormoAnalytics(apiKey, options);
95
+
96
+ // Identify current user on init (TODO: make this toggleable)
97
+ analytics.identify();
98
+
99
+ return analytics;
70
100
  }
71
101
 
72
102
  /*
73
103
  Public SDK functions
74
104
  */
75
105
 
76
- async connect({ chainId, address }: { chainId: ChainID; address: Address }): Promise<void> {
106
+ /**
107
+ * Emits a page visit event with the current URL information, fire on page change.
108
+ * @returns {Promise<void>}
109
+ */
110
+ async page(): Promise<void> {
111
+ await this.trackPageHit();
112
+ }
113
+
114
+ /**
115
+ * Emits a wallet connect event.
116
+ * @param {ChainID} params.chainId
117
+ * @param {Address} params.address
118
+ * @throws {Error} If chainId or address is empty
119
+ * @returns {Promise<void>}
120
+ */
121
+ async connect({
122
+ chainId,
123
+ address,
124
+ }: {
125
+ chainId: ChainID;
126
+ address: Address;
127
+ }): Promise<void> {
77
128
  if (!chainId) {
78
129
  throw new Error("FormoAnalytics::connect: chain ID cannot be empty");
79
130
  }
@@ -90,24 +141,48 @@ export class FormoAnalytics implements IFormoAnalytics {
90
141
  });
91
142
  }
92
143
 
93
- async disconnect(params?: { chainId?: ChainID; address?: Address }): Promise<void> {
144
+ /**
145
+ * Emits a wallet disconnect event.
146
+ * @param {ChainID} params.chainId
147
+ * @param {Address} params.address
148
+ * @returns {Promise<void>}
149
+ */
150
+ async disconnect(params?: {
151
+ chainId?: ChainID;
152
+ address?: Address;
153
+ }): Promise<void> {
94
154
  const address = params?.address || this.currentConnectedAddress;
95
155
  const chainId = params?.chainId || this.currentChainId;
156
+
96
157
  await this.handleDisconnect(chainId, address);
97
158
  }
98
159
 
99
- async chain({ chainId, address }: { chainId: ChainID; address?: Address }): Promise<void> {
160
+ /**
161
+ * Emits a chain network change event.
162
+ * @param {ChainID} params.chainId
163
+ * @param {Address} params.address
164
+ * @throws {Error} If chainId is empty, zero, or not a valid number
165
+ * @throws {Error} If no address is provided and no previous address is recorded
166
+ * @returns {Promise<void>}
167
+ */
168
+ async chain({
169
+ chainId,
170
+ address,
171
+ }: {
172
+ chainId: ChainID;
173
+ address?: Address;
174
+ }): Promise<void> {
100
175
  if (!chainId || Number(chainId) === 0) {
101
176
  throw new Error("FormoAnalytics::chain: chainId cannot be empty or 0");
102
177
  }
103
- if (!address && !this.currentConnectedAddress) {
178
+ if (isNaN(Number(chainId))) {
104
179
  throw new Error(
105
- "FormoAnalytics::chain: address was empty and no previous address has been recorded. You can either pass an address or call connect() first"
180
+ "FormoAnalytics::chain: chainId must be a valid decimal number"
106
181
  );
107
182
  }
108
- if (isNaN(Number(chainId))) {
183
+ if (!address && !this.currentConnectedAddress) {
109
184
  throw new Error(
110
- "FormoAnalytics::chain: chainId must be a valid decimal number"
185
+ "FormoAnalytics::chain: address was empty and no previous address has been recorded"
111
186
  );
112
187
  }
113
188
 
@@ -119,13 +194,77 @@ export class FormoAnalytics implements IFormoAnalytics {
119
194
  });
120
195
  }
121
196
 
122
- // TODO: allow custom url as input
123
- async page(): Promise<void> {
124
- await this.trackPageHit();
197
+ async signature({
198
+ status,
199
+ chainId,
200
+ address,
201
+ message,
202
+ signatureHash,
203
+ }: {
204
+ status: SignatureStatus;
205
+ chainId?: ChainID;
206
+ address: Address;
207
+ message: string;
208
+ signatureHash?: string;
209
+ }): Promise<void> {
210
+ await this.trackEvent(Event.SIGNATURE, {
211
+ status,
212
+ chainId,
213
+ address,
214
+ message,
215
+ ...(signatureHash && { signatureHash }),
216
+ });
125
217
  }
126
218
 
127
- async track(eventName: string, eventData: Record<string, any>): Promise<void> {
128
- await this.trackEvent(eventName, eventData);
219
+ async transaction({
220
+ status,
221
+ chainId,
222
+ address,
223
+ data,
224
+ to,
225
+ value,
226
+ transactionHash,
227
+ }: {
228
+ status: TransactionStatus;
229
+ chainId: ChainID;
230
+ address: Address;
231
+ data?: string;
232
+ to?: string;
233
+ value?: string;
234
+ transactionHash?: string;
235
+ }): Promise<void> {
236
+ await this.trackEvent(Event.TRANSACTION, {
237
+ status,
238
+ chainId,
239
+ address,
240
+ data,
241
+ to,
242
+ value,
243
+ ...(transactionHash && { transactionHash }),
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Emits a identify event with current wallet address.
249
+ * @param {Address} params.address
250
+ * @returns {Promise<void>}
251
+ */
252
+ public async identify(params?: { address: Address }): Promise<void> {
253
+ const address = params?.address || (await this.getAddress());
254
+ await this.trackEvent(Event.IDENTIFY, {
255
+ address,
256
+ // TODO: detect wallet type https://linear.app/getformo/issue/P-837/sdk-detect-user-wallet-type-in-identify-call
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Emits a custom event with custom data.
262
+ * @param {string} action
263
+ * @param {Record<string, any>} payload
264
+ * @returns {Promise<void>}
265
+ */
266
+ async track(action: string, payload: Record<string, any>): Promise<void> {
267
+ await this.trackEvent(action, payload);
129
268
  }
130
269
 
131
270
  /*
@@ -142,24 +281,20 @@ export class FormoAnalytics implements IFormoAnalytics {
142
281
  this.currentConnectedAddress = undefined;
143
282
 
144
283
  if (this._provider) {
145
- const eventNames = Object.keys(this._providerListeners);
146
- for (const eventName of eventNames) {
147
- this._provider.removeListener(
148
- eventName,
149
- this._providerListeners[eventName]
150
- );
151
- delete this._providerListeners[eventName];
284
+ const actions = Object.keys(this._providerListeners);
285
+ for (const action of actions) {
286
+ this._provider.removeListener(action, this._providerListeners[action]);
287
+ delete this._providerListeners[action];
152
288
  }
153
289
  }
154
290
 
155
- console.log("Tracking new provider:", provider);
156
291
  this._provider = provider;
157
292
 
158
- this.getAddress();
293
+ // Register listeners for web3 provider events
159
294
  this.registerAddressChangedListener();
160
295
  this.registerChainChangedListener();
161
- // TODO: track signing and transactions
162
- // https://linear.app/getformo/issue/P-607/sdk-support-signature-and-transaction-events
296
+ this.registerSignatureListener();
297
+ this.registerTransactionListener();
163
298
  }
164
299
 
165
300
  private registerAddressChangedListener(): void {
@@ -181,6 +316,120 @@ export class FormoAnalytics implements IFormoAnalytics {
181
316
  this._providerListeners["chainChanged"] = listener;
182
317
  }
183
318
 
319
+ private registerSignatureListener(): void {
320
+ if (!this.provider) {
321
+ console.error("_trackSigning: provider not found");
322
+ return;
323
+ }
324
+ if (
325
+ Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
326
+ false
327
+ ) {
328
+ console.warn("_trackSigning: provider.request is not writable");
329
+ return;
330
+ }
331
+
332
+ const request = this.provider.request.bind(this.provider);
333
+ this.provider.request = async <T>({
334
+ method,
335
+ params,
336
+ }: RequestArguments): Promise<T | null | undefined> => {
337
+ if (
338
+ Array.isArray(params) &&
339
+ ["eth_signTypedData_v4", "personal_sign"].includes(method)
340
+ ) {
341
+ // Emit signature request event
342
+ this.signature({
343
+ status: SignatureStatus.REQUESTED,
344
+ ...this.buildSignatureEventPayload(method, params),
345
+ });
346
+
347
+ try {
348
+ const response = (await request({ method, params })) as T;
349
+ if (response) {
350
+ // Emit signature confirmed event
351
+ this.signature({
352
+ status: SignatureStatus.CONFIRMED,
353
+ ...this.buildSignatureEventPayload(method, params, response),
354
+ });
355
+ }
356
+ return response;
357
+ } catch (error) {
358
+ const rpcError = error as RPCError;
359
+ if (rpcError && rpcError?.code === 4001) {
360
+ // Emit signature rejected event
361
+ this.signature({
362
+ status: SignatureStatus.REJECTED,
363
+ ...this.buildSignatureEventPayload(method, params),
364
+ });
365
+ }
366
+ throw error;
367
+ }
368
+ }
369
+ return request({ method, params });
370
+ };
371
+ return;
372
+ }
373
+
374
+ private registerTransactionListener(): void {
375
+ if (!this.provider) {
376
+ console.error("_trackTransactions: provider not found");
377
+ return;
378
+ }
379
+ if (
380
+ Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
381
+ false
382
+ ) {
383
+ console.warn("_trackTransactions: provider.request is not writable");
384
+ return;
385
+ }
386
+ const request = this.provider.request.bind(this.provider);
387
+ this.provider.request = async <T>({
388
+ method,
389
+ params,
390
+ }: RequestArguments): Promise<T | null | undefined> => {
391
+ if (
392
+ Array.isArray(params) &&
393
+ method === "eth_sendTransaction" &&
394
+ params[0]
395
+ ) {
396
+ // Track transaction start
397
+ const payload = await this.buildTransactionEventPayload(params);
398
+ this.transaction({ status: TransactionStatus.STARTED, ...payload });
399
+
400
+ try {
401
+ // Wait for the transaction hash
402
+ const transactionHash = (await request({ method, params })) as string;
403
+
404
+ // Track transaction broadcast
405
+ this.transaction({
406
+ status: TransactionStatus.BROADCASTED,
407
+ ...payload,
408
+ transactionHash,
409
+ });
410
+
411
+ return;
412
+ } catch (error) {
413
+ console.log("transaction listener catch");
414
+ console.log(error);
415
+ const rpcError = error as RPCError;
416
+ if (rpcError && rpcError?.code === 4001) {
417
+ // Emit transaction rejected event
418
+ this.transaction({
419
+ status: TransactionStatus.REJECTED,
420
+ ...payload,
421
+ });
422
+ }
423
+ throw error;
424
+ }
425
+ }
426
+
427
+ return request({ method, params });
428
+ };
429
+
430
+ return;
431
+ }
432
+
184
433
  private async onAddressChanged(addresses: Address[]): Promise<void> {
185
434
  if (addresses.length > 0) {
186
435
  this.onAddressConnected(addresses[0]);
@@ -201,18 +450,25 @@ export class FormoAnalytics implements IFormoAnalytics {
201
450
  this.connect({ chainId: this.currentChainId, address });
202
451
  }
203
452
 
204
- private async handleDisconnect(chainId?: ChainID, address?: Address): Promise<void> {
453
+ private async handleDisconnect(
454
+ chainId?: ChainID,
455
+ address?: Address
456
+ ): Promise<void> {
205
457
  const payload = {
206
458
  chain_id: chainId || this.currentChainId,
207
459
  address: address || this.currentConnectedAddress,
208
460
  };
209
461
  this.currentChainId = undefined;
210
462
  this.currentConnectedAddress = undefined;
463
+
211
464
  await this.trackEvent(Event.DISCONNECT, payload);
212
465
  }
213
466
 
214
467
  private async onAddressDisconnected(): Promise<void> {
215
- await this.handleDisconnect(this.currentChainId, this.currentConnectedAddress);
468
+ await this.handleDisconnect(
469
+ this.currentChainId,
470
+ this.currentConnectedAddress
471
+ );
216
472
  }
217
473
 
218
474
  private async onChainChanged(chainIdHex: string): Promise<void> {
@@ -225,7 +481,6 @@ export class FormoAnalytics implements IFormoAnalytics {
225
481
  return Promise.resolve();
226
482
  }
227
483
 
228
- // Attempt to fetch and store the connected address
229
484
  const address = await this.getAddress();
230
485
  if (!address) {
231
486
  console.log(
@@ -234,7 +489,7 @@ export class FormoAnalytics implements IFormoAnalytics {
234
489
  return Promise.resolve();
235
490
  }
236
491
 
237
- this.currentConnectedAddress = address[0];
492
+ this.currentConnectedAddress = address;
238
493
  }
239
494
 
240
495
  // Proceed only if the address exists
@@ -250,22 +505,60 @@ export class FormoAnalytics implements IFormoAnalytics {
250
505
  }
251
506
  }
252
507
 
253
- // TOFIX: support multiple page hit events
508
+ private async trackFirstPageVisit(): Promise<void> {
509
+ if (sessionStorage.getItem(CURRENT_URL_KEY) === null) {
510
+ sessionStorage.setItem(CURRENT_URL_KEY, window.location.href);
511
+ }
512
+
513
+ return this.trackPageHit();
514
+ }
515
+
516
+ private async trackPagesChange(): Promise<void> {
517
+ const oldPushState = history.pushState;
518
+ history.pushState = function pushState(...args) {
519
+ const ret = oldPushState.apply(this, args);
520
+ window.dispatchEvent(new window.Event("locationchange"));
521
+ return ret;
522
+ };
523
+
524
+ const oldReplaceState = history.replaceState;
525
+ history.replaceState = function replaceState(...args) {
526
+ const ret = oldReplaceState.apply(this, args);
527
+ window.dispatchEvent(new window.Event("locationchange"));
528
+ return ret;
529
+ };
530
+
531
+ window.addEventListener("popstate", () => this.onLocationChange());
532
+
533
+ window.addEventListener("locationchange", () => this.onLocationChange());
534
+ }
535
+
536
+ private async onLocationChange(): Promise<void> {
537
+ const currentUrl = sessionStorage.getItem(CURRENT_URL_KEY);
538
+
539
+ if (currentUrl !== window.location.href) {
540
+ sessionStorage.setItem(CURRENT_URL_KEY, window.location.href);
541
+ this.trackPageHit();
542
+ }
543
+ }
544
+
254
545
  // TODO: Add event listener and support for SPA and hash-based navigation
255
546
  // https://linear.app/getformo/issue/P-800/sdk-support-spa-and-hash-based-routing
256
547
  private trackPageHit(): void {
257
548
  const pathname = window.location.pathname;
258
549
  const href = window.location.href;
550
+ const hash = window.location.hash;
259
551
 
260
552
  setTimeout(async () => {
261
553
  this.trackEvent(Event.PAGE, {
262
554
  pathname,
263
555
  href,
556
+ hash,
264
557
  });
265
558
  }, 300);
266
559
  }
267
560
 
268
- // TODO: refactor this with event queue and flushing
561
+ // TODO: refactor this with event queue and flushing
269
562
  // https://linear.app/getformo/issue/P-835/sdk-refactor-retries-with-event-queue-and-batching
270
563
  private async trackEvent(action: string, payload: any): Promise<void> {
271
564
  const address = await this.getAddress();
@@ -291,7 +584,12 @@ export class FormoAnalytics implements IFormoAnalytics {
291
584
  );
292
585
 
293
586
  if (response.status >= 200 && response.status < 300) {
294
- console.log("Event sent successfully:", action);
587
+ console.log(
588
+ `Event sent successfully: ${this.getActionDescriptor(
589
+ action,
590
+ payload
591
+ )}`
592
+ );
295
593
  } else {
296
594
  throw new Error(`Failed with status: ${response.status}`);
297
595
  }
@@ -310,9 +608,10 @@ export class FormoAnalytics implements IFormoAnalytics {
310
608
 
311
609
  get provider(): EIP1193Provider | undefined {
312
610
  return this._provider;
313
- }
611
+ }
314
612
 
315
613
  private async getAddress(): Promise<Address | null> {
614
+ if (this.currentConnectedAddress) return this.currentConnectedAddress;
316
615
  if (!this.provider) {
317
616
  console.log("FormoAnalytics::getAddress: the provider is not set");
318
617
  return null;
@@ -321,11 +620,9 @@ export class FormoAnalytics implements IFormoAnalytics {
321
620
  try {
322
621
  const accounts = await this.getAccounts();
323
622
  if (accounts && accounts.length > 0) {
324
- const address = accounts[0];
623
+ return accounts[0];
325
624
  // TODO: how to handle multiple addresses? Should we emit a connect event here? Since the user has not manually connected
326
625
  // https://linear.app/getformo/issue/P-691/sdk-detect-multiple-wallets-using-eip6963
327
- this.onAddressConnected(address);
328
- return address;
329
626
  }
330
627
  } catch (err) {
331
628
  console.log("Failed to fetch accounts from provider:", err);
@@ -368,9 +665,7 @@ export class FormoAnalytics implements IFormoAnalytics {
368
665
  method: "eth_chainId",
369
666
  });
370
667
  if (!chainIdHex) {
371
- console.log(
372
- "FormoAnalytics::fetchChainId: chain id not found"
373
- );
668
+ console.log("FormoAnalytics::fetchChainId: chain id not found");
374
669
  return 0;
375
670
  }
376
671
  return parseInt(chainIdHex as string, 16);
@@ -386,10 +681,12 @@ export class FormoAnalytics implements IFormoAnalytics {
386
681
  private getLocation(): string | undefined {
387
682
  try {
388
683
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
389
- return COUNTRY_LIST[timezone as keyof typeof COUNTRY_LIST];
684
+ if (timezone in COUNTRY_LIST)
685
+ return COUNTRY_LIST[timezone as keyof typeof COUNTRY_LIST];
686
+ return timezone;
390
687
  } catch (error) {
391
688
  console.error("Error resolving timezone:", error);
392
- return undefined;
689
+ return "";
393
690
  }
394
691
  }
395
692
 
@@ -430,5 +727,57 @@ export class FormoAnalytics implements IFormoAnalytics {
430
727
  ref: params.get("ref"),
431
728
  ...eventSpecificPayload,
432
729
  };
433
- }
730
+ }
731
+
732
+ private buildSignatureEventPayload(
733
+ method: string,
734
+ params: unknown[],
735
+ response?: unknown
736
+ ) {
737
+ const basePayload = {
738
+ chainId: this.currentChainId,
739
+ address:
740
+ method === "personal_sign"
741
+ ? (params[1] as Address)
742
+ : (params[0] as Address),
743
+ };
744
+
745
+ if (method === "personal_sign") {
746
+ const message = Buffer.from(
747
+ (params[0] as string).slice(2),
748
+ "hex"
749
+ ).toString("utf8");
750
+ return {
751
+ ...basePayload,
752
+ message,
753
+ ...(response ? { signatureHash: response as string } : {}),
754
+ };
755
+ }
756
+
757
+ return {
758
+ ...basePayload,
759
+ message: params[1] as string,
760
+ ...(response ? { signatureHash: response as string } : {}),
761
+ };
762
+ }
763
+
764
+ private async buildTransactionEventPayload(params: unknown[]) {
765
+ const { data, from, to, value } = params[0] as {
766
+ data: string;
767
+ from: string;
768
+ to: string;
769
+ value: string;
770
+ };
771
+ return {
772
+ chainId: this.currentChainId || (await this.getCurrentChainId()),
773
+ data,
774
+ address: from,
775
+ to,
776
+ value,
777
+ };
778
+ }
779
+
780
+ private getActionDescriptor(action: string, payload: any): string {
781
+ return `${action}${payload.status ? ` ${payload.status}` : ""}`;
782
+ }
434
783
  }