@formo/analytics 1.11.13 → 1.11.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formo/analytics",
3
- "version": "1.11.13",
3
+ "version": "1.11.14",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/getformo/sdk.git"
@@ -63,7 +63,7 @@
63
63
  "sinon-chai": "^3.7.0",
64
64
  "ts-loader": "^9.3.1",
65
65
  "ts-node": "^10.8.2",
66
- "typescript": "^5.6.3",
66
+ "typescript": "^5.7.2",
67
67
  "webpack": "^5.74.0",
68
68
  "webpack-cli": "^4.10.0"
69
69
  },
@@ -12,7 +12,7 @@ interface IFormoAnalytics {
12
12
  /**
13
13
  * Initializes the FormoAnalytics instance with the provided API key and project ID.
14
14
  */
15
- init(apiKey: string, options: Options): Promise<FormoAnalytics>;
15
+ init(apiKey: string, options?: Options): Promise<FormoAnalytics>;
16
16
 
17
17
  /**
18
18
  * Tracks page visit events.
@@ -72,7 +72,10 @@ export class FormoAnalytics implements IFormoAnalytics {
72
72
  }
73
73
  }
74
74
 
75
- static async init(apiKey: string, options: Options): Promise<FormoAnalytics> {
75
+ static async init(
76
+ apiKey: string,
77
+ options?: Options
78
+ ): Promise<FormoAnalytics> {
76
79
  const config = {
77
80
  token: apiKey,
78
81
  };
@@ -139,11 +142,7 @@ export class FormoAnalytics implements IFormoAnalytics {
139
142
  timestamp: new Date().toISOString(),
140
143
  action,
141
144
  version: "1",
142
- payload: {
143
- // common fields
144
- ...this.getCommonTrackingFields(),
145
- ...payload,
146
- },
145
+ payload: await this.buildEventPayload(...payload),
147
146
  };
148
147
 
149
148
  const sendRequest = async (): Promise<void> => {
@@ -190,24 +189,71 @@ export class FormoAnalytics implements IFormoAnalytics {
190
189
  await sendRequest();
191
190
  }
192
191
 
193
- private getCommonTrackingFields() {
194
- let location: string | undefined;
195
- let language: string = "en";
192
+ // Function to track page hits
193
+ private trackPageHit() {
194
+ if (this.isAutomationEnvironment()) return;
195
+
196
+ const pathname = window.location.pathname;
197
+ const href = window.location.href;
198
+
199
+ setTimeout(async () => {
200
+ this.trackEvent(Event.PAGE, {
201
+ pathname,
202
+ href,
203
+ });
204
+ }, 300);
205
+ }
206
+
207
+ private isAutomationEnvironment(): boolean {
208
+ return (
209
+ window.__nightmare ||
210
+ window.navigator.webdriver ||
211
+ window.Cypress ||
212
+ false
213
+ );
214
+ }
215
+
216
+ private getUserLocation(): string | undefined {
196
217
  try {
197
218
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
198
- location = this.timezoneToCountry[timezone];
199
- language =
200
- navigator.languages && navigator.languages.length
219
+ return this.timezoneToCountry[timezone];
220
+ } catch (error) {
221
+ console.error("Error resolving timezone:", error);
222
+ return undefined;
223
+ }
224
+ }
225
+
226
+ private getUserLanguage(): string {
227
+ try {
228
+ return (
229
+ (navigator.languages && navigator.languages.length
201
230
  ? navigator.languages[0]
202
- : navigator.language || "en";
231
+ : navigator.language) || "en"
232
+ );
203
233
  } catch (error) {
204
- console.error("Error resolving timezone or language:", error);
234
+ console.error("Error resolving language:", error);
235
+ return "en";
205
236
  }
237
+ }
206
238
 
239
+ async buildEventPayload(
240
+ eventSpecificPayload: Record<string, unknown> = {}
241
+ ): Promise<Record<string, unknown>> {
207
242
  const url = new URL(window.location.href);
208
243
  const params = new URLSearchParams(url.search);
244
+
245
+ const location = this.getUserLocation();
246
+ const language = this.getUserLanguage();
247
+
248
+ const address = await this.getAndStoreConnectedAddress();
249
+ if (address === null) {
250
+ console.warn("Wallet address could not be retrieved.");
251
+ }
252
+
253
+ // common fields
209
254
  return {
210
255
  "user-agent": window.navigator.userAgent,
256
+ address,
211
257
  locale: language,
212
258
  location,
213
259
  referrer: document.referrer,
@@ -215,24 +261,13 @@ export class FormoAnalytics implements IFormoAnalytics {
215
261
  utm_medium: params.get("utm_medium"),
216
262
  utm_campaign: params.get("utm_campaign"),
217
263
  ref: params.get("ref"),
264
+ ...eventSpecificPayload,
218
265
  };
219
266
  }
220
267
 
221
- // Function to track page hits
222
- private trackPageHit() {
223
- if (window.__nightmare || window.navigator.webdriver || window.Cypress)
224
- return;
225
-
226
- setTimeout(() => {
227
- this.trackEvent(Event.PAGE, {
228
- pathname: window.location.pathname,
229
- href: window.location.href,
230
- });
231
- }, 300);
232
- }
233
-
234
268
  private trackProvider(provider: EIP1193Provider) {
235
269
  if (provider === this._provider) {
270
+ console.log("Provider already tracked.");
236
271
  return;
237
272
  }
238
273
 
@@ -250,6 +285,7 @@ export class FormoAnalytics implements IFormoAnalytics {
250
285
  }
251
286
  }
252
287
 
288
+ console.log("Tracking new provider:", provider);
253
289
  this._provider = provider;
254
290
 
255
291
  this.getCurrentWallet();
@@ -257,14 +293,33 @@ export class FormoAnalytics implements IFormoAnalytics {
257
293
  this.registerChainChangedListener();
258
294
  }
259
295
 
296
+ private async getAndStoreConnectedAddress(): Promise<string | null> {
297
+ console.log(
298
+ "Session data missing. Attempting to fetch address from provider."
299
+ );
300
+ try {
301
+ const accounts = await this.fetchAccounts();
302
+ if (accounts && accounts.length > 0) {
303
+ const address = accounts[0];
304
+ this.storeWalletAddress(address);
305
+ return address;
306
+ }
307
+ } catch (err) {
308
+ console.error("Failed to fetch accounts from provider:", err);
309
+ }
310
+ return null;
311
+ }
312
+
260
313
  private async getCurrentWallet() {
261
314
  if (!this.provider) {
262
315
  console.warn("FormoAnalytics::getCurrentWallet: the provider is not set");
263
316
  return;
264
317
  }
318
+
265
319
  const sessionData = sessionStorage.getItem(this.walletAddressSessionKey);
320
+
266
321
  if (!sessionData) {
267
- return null;
322
+ return await this.getAndStoreConnectedAddress();
268
323
  }
269
324
 
270
325
  const parsedData = JSON.parse(sessionData);
@@ -281,13 +336,61 @@ export class FormoAnalytics implements IFormoAnalytics {
281
336
  return parsedData.address || "";
282
337
  }
283
338
 
339
+ // Utility to fetch accounts
340
+ private async fetchAccounts(): Promise<string[] | null> {
341
+ try {
342
+ const res: string[] | null | undefined = await this.provider?.request({
343
+ method: "eth_accounts",
344
+ });
345
+ if (!res || res.length === 0) {
346
+ console.error(
347
+ "error",
348
+ "FormoAnalytics::fetchAccounts: unable to get account. eth_accounts returned empty"
349
+ );
350
+ return null;
351
+ }
352
+ return res;
353
+ } catch (err) {
354
+ if ((err as any).code !== 4001) {
355
+ console.error(
356
+ "error",
357
+ "FormoAnalytics::fetchAccounts: eth_accounts threw an error",
358
+ err
359
+ );
360
+ }
361
+ return null;
362
+ }
363
+ }
364
+
365
+ // Utility to fetch chain ID
366
+ private async fetchChainId(): Promise<string | null> {
367
+ try {
368
+ const chainIdHex = await this.provider?.request<string>({
369
+ method: "eth_chainId",
370
+ });
371
+ if (!chainIdHex) {
372
+ console.error(
373
+ "FormoAnalytics::fetchChainId: chainIdHex is null or undefined"
374
+ );
375
+ return null;
376
+ }
377
+ return chainIdHex;
378
+ } catch (err) {
379
+ console.error(
380
+ "error",
381
+ "FormoAnalytics::fetchChainId: eth_chainId threw an error",
382
+ err
383
+ );
384
+ return null;
385
+ }
386
+ }
387
+
284
388
  private async getCurrentChainId(): Promise<string> {
285
389
  if (!this.provider) {
286
390
  console.error("FormoAnalytics::getCurrentChainId: provider not set");
287
391
  }
288
- const chainIdHex = await this.provider?.request<string>({
289
- method: "eth_chainId",
290
- });
392
+
393
+ const chainIdHex = await this.fetchChainId();
291
394
  // Because we're connected, the chainId cannot be null
292
395
  if (!chainIdHex) {
293
396
  console.error(
@@ -349,7 +452,7 @@ export class FormoAnalytics implements IFormoAnalytics {
349
452
 
350
453
  const payload = {
351
454
  chain_id: this.currentChainId,
352
- address: this.currentConnectedAddress,
455
+ address: this.getAndStoreConnectedAddress(),
353
456
  };
354
457
  this.currentChainId = undefined;
355
458
  this.currentConnectedAddress = undefined;
@@ -369,37 +472,31 @@ export class FormoAnalytics implements IFormoAnalytics {
369
472
  return;
370
473
  }
371
474
 
372
- try {
373
- const res: string[] | null | undefined = await this.provider.request({
374
- method: "eth_accounts",
375
- });
376
- if (!res || res.length === 0) {
377
- console.error(
378
- "error",
379
- "FormoAnalytics::onChainChanged: unable to get account. eth_accounts returned empty"
380
- );
381
- return;
382
- }
383
-
384
- this.currentConnectedAddress = res[0];
385
- } catch (err) {
386
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
387
- if ((err as any).code !== 4001) {
388
- // 4001: The request is rejected by the user , see https://docs.metamask.io/wallet/reference/provider-api/#errors
389
- console.error(
390
- "error",
391
- `FormoAnalytics::onChainChanged: unable to get account. eth_accounts threw an error`,
392
- err
393
- );
394
- return;
395
- }
475
+ // Attempt to fetch and store the connected address
476
+ const address = await this.getAndStoreConnectedAddress();
477
+ if (!address) {
478
+ console.error(
479
+ "error",
480
+ "FormoAnalytics::onChainChanged: Unable to fetch or store connected address"
481
+ );
482
+ return;
396
483
  }
484
+
485
+ this.currentConnectedAddress = address[0];
397
486
  }
398
487
 
399
- return this.chain({
400
- chainId: this.currentChainId,
401
- address: this.currentConnectedAddress,
402
- });
488
+ // Proceed only if the address exists
489
+ if (this.currentConnectedAddress) {
490
+ return this.chain({
491
+ chainId: this.currentChainId,
492
+ address: this.currentConnectedAddress,
493
+ });
494
+ } else {
495
+ console.error(
496
+ "error",
497
+ "FormoAnalytics::onChainChanged: currentConnectedAddress is null despite fetch attempt"
498
+ );
499
+ }
403
500
  }
404
501
 
405
502
  /**
package/src/types/base.ts CHANGED
@@ -8,7 +8,7 @@ export interface Options {
8
8
 
9
9
  export interface FormoAnalyticsProviderProps {
10
10
  apiKey: string;
11
- options: Options;
11
+ options?: Options;
12
12
  disabled?: boolean;
13
13
  children: React.ReactNode;
14
14
  }