@formo/analytics 1.11.9 → 1.11.11

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.9",
3
+ "version": "1.11.11",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/getformo/sdk.git"
@@ -12,11 +12,7 @@ interface IFormoAnalytics {
12
12
  /**
13
13
  * Initializes the FormoAnalytics instance with the provided API key and project ID.
14
14
  */
15
- init(
16
- apiKey: string,
17
- projectId: string,
18
- options?: Options
19
- ): Promise<FormoAnalytics>;
15
+ init(apiKey: string, options?: Options): Promise<FormoAnalytics>;
20
16
 
21
17
  /**
22
18
  * Tracks page visit events.
@@ -63,7 +59,6 @@ export class FormoAnalytics implements IFormoAnalytics {
63
59
 
64
60
  private constructor(
65
61
  public readonly apiKey: string,
66
- public readonly projectId: string,
67
62
  public options: Options = {}
68
63
  ) {
69
64
  this.config = {
@@ -79,13 +74,12 @@ export class FormoAnalytics implements IFormoAnalytics {
79
74
 
80
75
  static async init(
81
76
  apiKey: string,
82
- projectId: string,
83
77
  options?: Options
84
78
  ): Promise<FormoAnalytics> {
85
79
  const config = {
86
80
  token: apiKey,
87
81
  };
88
- const instance = new FormoAnalytics(apiKey, projectId, options);
82
+ const instance = new FormoAnalytics(apiKey, options);
89
83
  instance.config = config;
90
84
 
91
85
  return instance;
@@ -197,39 +191,76 @@ export class FormoAnalytics implements IFormoAnalytics {
197
191
 
198
192
  // Function to track page hits
199
193
  private trackPageHit() {
200
- if (window.__nightmare || window.navigator.webdriver || window.Cypress)
201
- return;
194
+ if (this.isAutomationEnvironment()) return;
202
195
 
203
- let location: string | undefined;
204
- let language: string;
196
+ const location = this.getUserLocation();
197
+ const language = this.getUserLanguage();
198
+
199
+ setTimeout(async () => {
200
+ const eventData = await this.buildPageEventData(location, language);
201
+ this.trackEvent(Event.PAGE, eventData);
202
+ }, 300);
203
+ }
204
+
205
+ private isAutomationEnvironment(): boolean {
206
+ return (
207
+ window.__nightmare ||
208
+ window.navigator.webdriver ||
209
+ window.Cypress ||
210
+ false
211
+ );
212
+ }
213
+
214
+ private getUserLocation(): string | undefined {
205
215
  try {
206
216
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
207
- location = this.timezoneToCountry[timezone];
208
- language =
209
- navigator.languages && navigator.languages.length
217
+ return this.timezoneToCountry[timezone];
218
+ } catch (error) {
219
+ console.error('Error resolving timezone:', error);
220
+ return undefined;
221
+ }
222
+ }
223
+
224
+ private getUserLanguage(): string {
225
+ try {
226
+ return (
227
+ (navigator.languages && navigator.languages.length
210
228
  ? navigator.languages[0]
211
- : navigator.language || 'en';
229
+ : navigator.language) || 'en'
230
+ );
212
231
  } catch (error) {
213
- console.error('Error resolving timezone or language:', error);
232
+ console.error('Error resolving language:', error);
233
+ return 'en';
214
234
  }
235
+ }
215
236
 
216
- setTimeout(() => {
217
- const url = new URL(window.location.href);
218
- const params = new URLSearchParams(url.search);
219
- this.trackEvent(Event.PAGE, {
220
- 'user-agent': window.navigator.userAgent,
221
- address: this.currentConnectedAddress,
222
- locale: language,
223
- location: location,
224
- referrer: document.referrer,
225
- pathname: window.location.pathname,
226
- href: window.location.href,
227
- utm_source: params.get('utm_source'),
228
- utm_medium: params.get('utm_medium'),
229
- utm_campaign: params.get('utm_campaign'),
230
- ref: params.get('ref'),
231
- });
232
- }, 300);
237
+ async buildPageEventData(location: string | undefined, language: string): Promise<Record<string, unknown>> {
238
+ const url = new URL(window.location.href);
239
+ const params = new URLSearchParams(url.search);
240
+
241
+ const address = await this.getAndStoreConnectedAddress();
242
+ if (address === null) {
243
+ console.warn('Wallet address could not be retrieved.');
244
+ }
245
+
246
+ const eventData: Record<string, unknown> = {
247
+ 'user-agent': window.navigator.userAgent,
248
+ locale: language,
249
+ location,
250
+ referrer: document.referrer,
251
+ pathname: window.location.pathname,
252
+ href: window.location.href,
253
+ utm_source: params.get('utm_source'),
254
+ utm_medium: params.get('utm_medium'),
255
+ utm_campaign: params.get('utm_campaign'),
256
+ ref: params.get('ref'),
257
+ };
258
+
259
+ if (address !== null) {
260
+ eventData['address'] = address;
261
+ }
262
+
263
+ return eventData;
233
264
  }
234
265
 
235
266
  private trackProvider(provider: EIP1193Provider) {
@@ -260,6 +291,23 @@ export class FormoAnalytics implements IFormoAnalytics {
260
291
  this.registerChainChangedListener();
261
292
  }
262
293
 
294
+ private async getAndStoreConnectedAddress(): Promise<string | null> {
295
+ console.warn(
296
+ 'Session data missing. Attempting to fetch address from provider.'
297
+ );
298
+ try {
299
+ const accounts = await this.fetchAccounts();
300
+ if (accounts && accounts.length > 0) {
301
+ const address = accounts[0];
302
+ this.storeWalletAddress(address);
303
+ return address;
304
+ }
305
+ } catch (err) {
306
+ console.error('Failed to fetch accounts from provider:', err);
307
+ }
308
+ return null;
309
+ }
310
+
263
311
  private async getCurrentWallet() {
264
312
  if (!this.provider) {
265
313
  console.warn('FormoAnalytics::getCurrentWallet: the provider is not set');
@@ -269,22 +317,7 @@ export class FormoAnalytics implements IFormoAnalytics {
269
317
  const sessionData = sessionStorage.getItem(this.walletAddressSessionKey);
270
318
 
271
319
  if (!sessionData) {
272
- console.warn(
273
- 'Session data missing. Attempting to fetch address from provider.'
274
- );
275
- try {
276
- const accounts = await this.provider.request<string[]>({
277
- method: 'eth_accounts',
278
- });
279
- if (accounts && accounts.length > 0) {
280
- const address = accounts[0];
281
- this.storeWalletAddress(address);
282
- return address;
283
- }
284
- } catch (err) {
285
- console.error('Failed to fetch accounts from provider:', err);
286
- }
287
- return null;
320
+ return await this.getAndStoreConnectedAddress();
288
321
  }
289
322
 
290
323
  const parsedData = JSON.parse(sessionData);
@@ -301,13 +334,61 @@ export class FormoAnalytics implements IFormoAnalytics {
301
334
  return parsedData.address || '';
302
335
  }
303
336
 
337
+ // Utility to fetch accounts
338
+ private async fetchAccounts(): Promise<string[] | null> {
339
+ try {
340
+ const res: string[] | null | undefined = await this.provider?.request({
341
+ method: 'eth_accounts',
342
+ });
343
+ if (!res || res.length === 0) {
344
+ console.error(
345
+ 'error',
346
+ 'FormoAnalytics::fetchAccounts: unable to get account. eth_accounts returned empty'
347
+ );
348
+ return null;
349
+ }
350
+ return res;
351
+ } catch (err) {
352
+ if ((err as any).code !== 4001) {
353
+ console.error(
354
+ 'error',
355
+ 'FormoAnalytics::fetchAccounts: eth_accounts threw an error',
356
+ err
357
+ );
358
+ }
359
+ return null;
360
+ }
361
+ }
362
+
363
+ // Utility to fetch chain ID
364
+ private async fetchChainId(): Promise<string | null> {
365
+ try {
366
+ const chainIdHex = await this.provider?.request<string>({
367
+ method: 'eth_chainId',
368
+ });
369
+ if (!chainIdHex) {
370
+ console.error(
371
+ 'FormoAnalytics::fetchChainId: chainIdHex is null or undefined'
372
+ );
373
+ return null;
374
+ }
375
+ return chainIdHex;
376
+ } catch (err) {
377
+ console.error(
378
+ 'error',
379
+ 'FormoAnalytics::fetchChainId: eth_chainId threw an error',
380
+ err
381
+ );
382
+ return null;
383
+ }
384
+ }
385
+
304
386
  private async getCurrentChainId(): Promise<string> {
305
387
  if (!this.provider) {
306
388
  console.error('FormoAnalytics::getCurrentChainId: provider not set');
307
389
  }
308
- const chainIdHex = await this.provider?.request<string>({
309
- method: 'eth_chainId',
310
- });
390
+
391
+ const chainIdHex = await this.fetchChainId();
311
392
  // Because we're connected, the chainId cannot be null
312
393
  if (!chainIdHex) {
313
394
  console.error(
@@ -369,7 +450,7 @@ export class FormoAnalytics implements IFormoAnalytics {
369
450
 
370
451
  const payload = {
371
452
  chain_id: this.currentChainId,
372
- address: this.currentConnectedAddress,
453
+ address: this.getAndStoreConnectedAddress(),
373
454
  };
374
455
  this.currentChainId = undefined;
375
456
  this.currentConnectedAddress = undefined;
@@ -389,37 +470,31 @@ export class FormoAnalytics implements IFormoAnalytics {
389
470
  return;
390
471
  }
391
472
 
392
- try {
393
- const res: string[] | null | undefined = await this.provider.request({
394
- method: 'eth_accounts',
395
- });
396
- if (!res || res.length === 0) {
397
- console.error(
398
- 'error',
399
- 'FormoAnalytics::onChainChanged: unable to get account. eth_accounts returned empty'
400
- );
401
- return;
402
- }
403
-
404
- this.currentConnectedAddress = res[0];
405
- } catch (err) {
406
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
- if ((err as any).code !== 4001) {
408
- // 4001: The request is rejected by the user , see https://docs.metamask.io/wallet/reference/provider-api/#errors
409
- console.error(
410
- 'error',
411
- `FormoAnalytics::onChainChanged: unable to get account. eth_accounts threw an error`,
412
- err
413
- );
414
- return;
415
- }
473
+ // Attempt to fetch and store the connected address
474
+ const address = await this.getAndStoreConnectedAddress();
475
+ if (!address) {
476
+ console.error(
477
+ 'error',
478
+ 'FormoAnalytics::onChainChanged: Unable to fetch or store connected address'
479
+ );
480
+ return;
416
481
  }
482
+
483
+ this.currentConnectedAddress = address[0];
417
484
  }
418
485
 
419
- return this.chain({
420
- chainId: this.currentChainId,
421
- address: this.currentConnectedAddress,
422
- });
486
+ // Proceed only if the address exists
487
+ if (this.currentConnectedAddress) {
488
+ return this.chain({
489
+ chainId: this.currentChainId,
490
+ address: this.currentConnectedAddress,
491
+ });
492
+ } else {
493
+ console.error(
494
+ 'error',
495
+ 'FormoAnalytics::onChainChanged: currentConnectedAddress is null despite fetch attempt'
496
+ );
497
+ }
423
498
  }
424
499
 
425
500
  /**
@@ -450,8 +525,8 @@ export class FormoAnalytics implements IFormoAnalytics {
450
525
  sessionStorage.removeItem(this.walletAddressSessionKey);
451
526
  }
452
527
 
453
- init(apiKey: string, projectId: string, options: Options): Promise<FormoAnalytics> {
454
- const instance = new FormoAnalytics(apiKey, projectId, options);
528
+ init(apiKey: string, options: Options): Promise<FormoAnalytics> {
529
+ const instance = new FormoAnalytics(apiKey, options);
455
530
  return Promise.resolve(instance);
456
531
  }
457
532
 
@@ -19,7 +19,6 @@ export const FormoAnalyticsContext = createContext<FormoAnalytics | undefined>(
19
19
  export const FormoAnalyticsProvider = ({
20
20
  apiKey,
21
21
  options,
22
- projectId,
23
22
  disabled,
24
23
  children,
25
24
  }: FormoAnalyticsProviderProps) => {
@@ -62,7 +61,7 @@ export const FormoAnalyticsProvider = ({
62
61
 
63
62
  // Initialize FormoAnalytics
64
63
  try {
65
- const sdkInstance = await FormoAnalytics.init(apiKey, projectId, options);
64
+ const sdkInstance = await FormoAnalytics.init(apiKey, options);
66
65
  setSdk(sdkInstance);
67
66
  console.log('FormoAnalytics SDK initialized successfully');
68
67
  } catch (error) {
package/src/types/base.ts CHANGED
@@ -8,7 +8,6 @@ export interface Options {
8
8
 
9
9
  export interface FormoAnalyticsProviderProps {
10
10
  apiKey: string;
11
- projectId: string;
12
11
  options?: Options;
13
12
  disabled?: boolean;
14
13
  children: React.ReactNode;