user-analytics-tracker 4.1.2 → 4.3.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.
package/dist/index.d.cts CHANGED
@@ -133,6 +133,11 @@ interface AnalyticsConfig {
133
133
  sessionTimeout?: number;
134
134
  logLevel?: LogLevel$1;
135
135
  enableMetrics?: boolean;
136
+ ipGeolocation?: {
137
+ apiKey?: string;
138
+ baseUrl?: string;
139
+ timeout?: number;
140
+ };
136
141
  fieldStorage?: {
137
142
  ipLocation?: FieldStorageConfig$1;
138
143
  deviceInfo?: FieldStorageConfig$1;
@@ -160,11 +165,12 @@ type IPLocationFieldConfig = FieldStorageConfig$1;
160
165
  * Default essential fields for IP location storage
161
166
  * These fields are stored when mode is 'essential' (default)
162
167
  */
163
- declare const DEFAULT_ESSENTIAL_IP_FIELDS: readonly ["ip", "country", "countryCode", "region", "city", "lat", "lon", "continent", "continentCode", "type", "isEu", "isp", "connection", "connection.asn", "connection.org", "connection.isp", "connection.domain", "timezone", "timezoneDetails", "timezoneDetails.id", "timezoneDetails.abbr", "timezoneDetails.utc", "flag.emoji"];
168
+ declare const DEFAULT_ESSENTIAL_IP_FIELDS: readonly ["ip", "countryCode", "city", "lat", "lon", "type", "isEu", "isp", "connection", "connection.asn", "connection.org", "connection.isp", "connection.domain"];
164
169
  /**
165
170
  * Default essential fields for Device Info storage
171
+ * In essential mode, only OS and browser are stored
166
172
  */
167
- declare const DEFAULT_ESSENTIAL_DEVICE_FIELDS: readonly ["type", "os", "osVersion", "browser", "browserVersion", "deviceModel", "deviceBrand", "userAgent"];
173
+ declare const DEFAULT_ESSENTIAL_DEVICE_FIELDS: readonly ["os", "browser"];
168
174
  /**
169
175
  * Default essential fields for Network Info storage
170
176
  *
@@ -180,7 +186,7 @@ declare const DEFAULT_ESSENTIAL_LOCATION_FIELDS: readonly ["lat", "lon", "source
180
186
  /**
181
187
  * Default essential fields for Attribution Info storage
182
188
  */
183
- declare const DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS: readonly ["landingUrl", "path", "hostname", "referrerUrl", "referrerDomain", "navigationType", "isReload", "isBackForward", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
189
+ declare const DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS: readonly ["landingUrl", "path"];
184
190
  interface AnalyticsEvent {
185
191
  sessionId: string;
186
192
  pageUrl: string;
@@ -236,6 +242,100 @@ declare class DeviceDetector {
236
242
  private static getDefaultDeviceInfo;
237
243
  }
238
244
 
245
+ /**
246
+ * IP Geolocation Service
247
+ * Fetches location data (country, region, city) from user's IP address
248
+ * Uses ipwho.is API (supports optional API key for higher rate limits)
249
+ *
250
+ * Stores all keys dynamically from the API response, including nested objects
251
+ * This ensures we capture all available data and any new fields added by the API
252
+ *
253
+ * --- Where does the IP come from? (secure usage) ---
254
+ *
255
+ * 1) Browser / client-side:
256
+ * - Do NOT pass an IP. Call getCompleteIPLocation(config) with no IP.
257
+ * - The request goes: user's browser → ipwho.is; ipwho.is sees the visitor's
258
+ * public IP and returns it. The IP is never stored or sent by your code.
259
+ * - For config.apiKey: avoid hardcoding. Use a build-time env var or omit (free tier).
260
+ *
261
+ * 2) Server-side (Node/Express/Next etc.):
262
+ * - Get the visitor's IP from the incoming request with getIPFromRequest(req).
263
+ * - Then call getIPLocation(userIp, config) to geolocate that IP.
264
+ * - Keep apiKey in process.env (e.g. IPWHOIS_API_KEY); never commit it.
265
+ */
266
+ /**
267
+ * IP Geolocation configuration interface
268
+ */
269
+ interface IPGeolocationConfig {
270
+ apiKey?: string;
271
+ baseUrl?: string;
272
+ timeout?: number;
273
+ }
274
+ /**
275
+ * Get complete IP location data from ipwho.is API (HIGH PRIORITY)
276
+ * This is the primary method - gets IP, location, connection, and all data in one call
277
+ *
278
+ * @param config - Optional configuration for API key and base URL
279
+ * @returns Promise<IPLocation | null> - Complete IP location data, or null if unavailable
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * // Without API key (free tier)
284
+ * const location = await getCompleteIPLocation();
285
+ *
286
+ * // With API key (for higher rate limits)
287
+ * const location = await getCompleteIPLocation({
288
+ * apiKey: '<your-api-key>',
289
+ * baseUrl: 'https://ipwho.is'
290
+ * });
291
+ * ```
292
+ */
293
+ declare function getCompleteIPLocation(config?: IPGeolocationConfig): Promise<IPLocation | null>;
294
+ /**
295
+ * Get public IP address using ipwho.is API (FALLBACK - lower priority)
296
+ * This is kept for backward compatibility and as a fallback
297
+ * Prefer getCompleteIPLocation() which gets everything in one call
298
+ *
299
+ * @param config - Optional configuration for API key and base URL
300
+ * @returns Promise<string | null> - The public IP address, or null if unavailable
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const ip = await getPublicIP();
305
+ * console.log('Your IP:', ip); // e.g., "203.0.113.42"
306
+ * ```
307
+ */
308
+ declare function getPublicIP(config?: IPGeolocationConfig): Promise<string | null>;
309
+ /**
310
+ * Get location from IP address using ipwho.is API (HIGH PRIORITY)
311
+ *
312
+ * Stores all keys dynamically from the API response, including nested objects
313
+ * This ensures we capture all available data and any new fields added by the API
314
+ *
315
+ * @param ip - IP address to geolocate
316
+ * @param config - Optional configuration for API key and base URL
317
+ * @returns Promise<IPLocation | null> - IP location data, or null if unavailable
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // Without API key
322
+ * const location = await getIPLocation('203.0.113.42');
323
+ *
324
+ * // With API key
325
+ * const location = await getIPLocation('203.0.113.42', {
326
+ * apiKey: '<your-api-key>'
327
+ * });
328
+ * ```
329
+ *
330
+ * Note: If you don't have an IP yet, use getCompleteIPLocation() which gets everything in one call
331
+ */
332
+ declare function getIPLocation(ip: string, config?: IPGeolocationConfig): Promise<IPLocation | null>;
333
+ /**
334
+ * Get IP address from request headers
335
+ * Handles various proxy headers (x-forwarded-for, x-real-ip, etc.)
336
+ */
337
+ declare function getIPFromRequest(req: Request | any): string;
338
+
239
339
  /**
240
340
  * Location Detector
241
341
  * Detects GPS location with consent management, falls back to IP-based location API
@@ -247,6 +347,22 @@ declare class LocationDetector {
247
347
  private static locationConsentLoggedRef;
248
348
  private static ipLocationFetchingRef;
249
349
  private static lastIPLocationRef;
350
+ private static ipGeolocationConfig;
351
+ /**
352
+ * Configure IP geolocation settings (API key, base URL, timeout)
353
+ *
354
+ * @param config - IP geolocation configuration
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * LocationDetector.configureIPGeolocation({
359
+ * apiKey: '<your-ipwho-is-api-key>',
360
+ * baseUrl: 'https://ipwho.is',
361
+ * timeout: 5000
362
+ * });
363
+ * ```
364
+ */
365
+ static configureIPGeolocation(config: IPGeolocationConfig | null): void;
250
366
  /**
251
367
  * Detect location using IP-based API only (no GPS, no permission needed)
252
368
  * Fast and automatic - works immediately without user interaction
@@ -400,6 +516,8 @@ declare class AnalyticsService {
400
516
  private static queueManager;
401
517
  private static config;
402
518
  private static isInitialized;
519
+ private static seenEventKeys;
520
+ private static readonly DEDUPE_CACHE_SIZE;
403
521
  /**
404
522
  * Configure the analytics service
405
523
  *
@@ -446,6 +564,7 @@ declare class AnalyticsService {
446
564
  private static generateEventId;
447
565
  /**
448
566
  * Send a batch of events with retry logic
567
+ * Filters out invalid and duplicate events before sending
449
568
  */
450
569
  private static sendBatch;
451
570
  /**
@@ -455,12 +574,13 @@ declare class AnalyticsService {
455
574
  /**
456
575
  * Track user journey/analytics event
457
576
  * Events are automatically queued and batched
577
+ * Duplicate events and events with null values are filtered out
458
578
  */
459
579
  static trackEvent(event: Omit<AnalyticsEvent, 'eventId' | 'timestamp'>): Promise<void>;
460
580
  /**
461
581
  * Track user journey with full context
462
582
  */
463
- static trackUserJourney({ sessionId, pageUrl, networkInfo, deviceInfo, location, attribution, ipLocation, userId, customData, pageVisits, interactions, }: {
583
+ static trackUserJourney({ sessionId, pageUrl, networkInfo, deviceInfo, location, attribution, ipLocation, userId, customData, pageVisits: _pageVisits, interactions: _interactions, }: {
464
584
  sessionId: string;
465
585
  pageUrl: string;
466
586
  networkInfo?: NetworkInfo;
@@ -506,6 +626,7 @@ declare class AnalyticsService {
506
626
  location?: any;
507
627
  attribution?: AttributionInfo;
508
628
  userId?: string;
629
+ ipLocation?: any;
509
630
  }): Promise<void>;
510
631
  /**
511
632
  * Track a page view event (Firebase/GA-style)
@@ -526,7 +647,16 @@ declare class AnalyticsService {
526
647
  * });
527
648
  * ```
528
649
  */
529
- static trackPageView(pageName?: string, parameters?: Record<string, any>): Promise<void>;
650
+ static trackPageView(pageName?: string, parameters?: Record<string, any>, context?: {
651
+ sessionId?: string;
652
+ pageUrl?: string;
653
+ networkInfo?: NetworkInfo;
654
+ deviceInfo?: DeviceInfo;
655
+ location?: any;
656
+ attribution?: AttributionInfo;
657
+ userId?: string;
658
+ ipLocation?: any;
659
+ }): Promise<void>;
530
660
  /**
531
661
  * Manually flush the event queue
532
662
  * Useful for ensuring events are sent before page unload
@@ -560,11 +690,17 @@ interface UseAnalyticsOptions {
560
690
  /**
561
691
  * React hook for analytics tracking
562
692
  *
693
+ * To use your own ipwho.is API key (higher rate limits), pass ipGeolocation in config.
694
+ * Use an env var so the key is not committed (e.g. VITE_IPWHOIS_API_KEY, REACT_APP_IPWHOIS_API_KEY).
695
+ *
563
696
  * @example
564
697
  * ```tsx
565
698
  * const { sessionId, networkInfo, deviceInfo, logEvent } = useAnalytics({
566
699
  * autoSend: true,
567
- * config: { apiEndpoint: '/api/analytics' }
700
+ * config: {
701
+ * apiEndpoint: '/api/analytics',
702
+ * ipGeolocation: { apiKey: import.meta.env.VITE_IPWHOIS_API_KEY }, // optional; omit for free tier
703
+ * },
568
704
  * });
569
705
  * ```
570
706
  */
@@ -644,68 +780,58 @@ declare function clearLocationConsent(): void;
644
780
  declare function checkAndSetLocationConsent(msisdn?: string | null): boolean;
645
781
 
646
782
  /**
647
- * IP Geolocation Service
648
- * Fetches location data (country, region, city) from user's IP address
649
- * Uses ipwho.is API (no API key required)
783
+ * Transform IP location data from API format (snake_case) to backend-expected format (camelCase)
784
+ * Supports configurable field storage to optimize storage capacity
650
785
  *
651
- * Stores all keys dynamically from the API response, including nested objects
652
- * This ensures we capture all available data and any new fields added by the API
786
+ * @param ipLocation - Raw IP location data from ipwho.is API
787
+ * @param config - Optional configuration for which fields to store
788
+ * @returns Transformed IP location data matching backend schema (only includes configured fields)
653
789
  */
790
+ declare function transformIPLocationForBackend(ipLocation: IPLocation | null | any, config?: FieldStorageConfig$1): Record<string, any> | null;
791
+
654
792
  /**
655
- * Get complete IP location data from ipwho.is API (HIGH PRIORITY)
656
- * This is the primary method - gets IP, location, connection, and all data in one call
657
- * No API key required
658
- *
659
- * @returns Promise<IPLocation | null> - Complete IP location data, or null if unavailable
660
- *
661
- * @example
662
- * ```typescript
663
- * const location = await getCompleteIPLocation();
664
- * console.log('IP:', location?.ip);
665
- * console.log('Country:', location?.country);
666
- * console.log('ISP:', location?.connection?.isp);
667
- * ```
793
+ * Required fields for analytics events
794
+ * Events missing these fields will be filtered out
668
795
  */
669
- declare function getCompleteIPLocation(): Promise<IPLocation | null>;
796
+ interface RequiredEventFields {
797
+ ip?: string | null;
798
+ lat?: number | null;
799
+ lon?: number | null;
800
+ mobile?: boolean | null;
801
+ location?: string | null;
802
+ msisdn?: string | null;
803
+ session?: string | null;
804
+ operators?: string | null;
805
+ page?: string | null;
806
+ pageUrl?: string | null;
807
+ eventType?: string | null;
808
+ companyName?: string | null;
809
+ eventId?: string | null;
810
+ timestamp?: Date | string | null;
811
+ gps?: boolean | null;
812
+ os?: string | null;
813
+ browser?: string | null;
814
+ serviceId?: string | null;
815
+ }
670
816
  /**
671
- * Get public IP address using ipwho.is API (FALLBACK - lower priority)
672
- * This is kept for backward compatibility and as a fallback
673
- * Prefer getCompleteIPLocation() which gets everything in one call
674
- *
675
- * @returns Promise<string | null> - The public IP address, or null if unavailable
676
- *
677
- * @example
678
- * ```typescript
679
- * const ip = await getPublicIP();
680
- * console.log('Your IP:', ip); // e.g., "203.0.113.42"
681
- * ```
817
+ * Extract required fields from analytics event
682
818
  */
683
- declare function getPublicIP(): Promise<string | null>;
819
+ declare function extractRequiredFields(event: AnalyticsEvent): RequiredEventFields;
684
820
  /**
685
- * Get location from IP address using ipwho.is API (HIGH PRIORITY)
686
- * Free tier: No API key required
687
- *
688
- * Stores all keys dynamically from the API response, including nested objects
689
- * This ensures we capture all available data and any new fields added by the API
690
- *
691
- * Note: If you don't have an IP yet, use getCompleteIPLocation() which gets everything in one call
821
+ * Validate that event has minimum required fields
822
+ * Returns true if valid, false if should be filtered out
692
823
  */
693
- declare function getIPLocation(ip: string): Promise<IPLocation | null>;
824
+ declare function validateRequiredFields(fields: RequiredEventFields): boolean;
694
825
  /**
695
- * Get IP address from request headers
696
- * Handles various proxy headers (x-forwarded-for, x-real-ip, etc.)
826
+ * Validate and filter event
827
+ * Returns null if event should be filtered out, otherwise returns the event
697
828
  */
698
- declare function getIPFromRequest(req: Request | any): string;
699
-
829
+ declare function validateEvent(event: AnalyticsEvent): AnalyticsEvent | null;
700
830
  /**
701
- * Transform IP location data from API format (snake_case) to backend-expected format (camelCase)
702
- * Supports configurable field storage to optimize storage capacity
703
- *
704
- * @param ipLocation - Raw IP location data from ipwho.is API
705
- * @param config - Optional configuration for which fields to store
706
- * @returns Transformed IP location data matching backend schema (only includes configured fields)
831
+ * Generate a unique key for deduplication
832
+ * Based on: sessionId + pageUrl + eventName + timestamp (rounded to nearest second)
707
833
  */
708
- declare function transformIPLocationForBackend(ipLocation: IPLocation | null, config?: FieldStorageConfig$1): Record<string, any> | null;
834
+ declare function generateDeduplicationKey(event: AnalyticsEvent): string;
709
835
 
710
836
  /**
711
837
  * Generic field storage transformer
@@ -745,5 +871,5 @@ declare class Logger {
745
871
  }
746
872
  declare const logger: Logger;
747
873
 
748
- export { AnalyticsService, AttributionDetector, DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS, DEFAULT_ESSENTIAL_DEVICE_FIELDS, DEFAULT_ESSENTIAL_IP_FIELDS, DEFAULT_ESSENTIAL_LOCATION_FIELDS, DEFAULT_ESSENTIAL_NETWORK_FIELDS, DeviceDetector, LocationDetector, NetworkDetector, QueueManager, checkAndSetLocationConsent, clearLocationConsent, clearSession, useAnalytics as default, filterFieldsByConfig, getCompleteIPLocation, getIPFromRequest, getIPLocation, getLocationConsentTimestamp, getOrCreateSession, getOrCreateUserId, getPublicIP, getSession, hasLocationConsent, initDebug, loadJSON, loadSessionJSON, logger, saveJSON, saveSessionJSON, setLocationConsentGranted, trackPageVisit, transformIPLocationForBackend, updateSessionActivity, useAnalytics };
749
- export type { AnalyticsConfig, AnalyticsEvent, AttributionInfo, DeviceInfo, DeviceType, FieldStorageConfig$1 as FieldStorageConfig, FieldStorageConfig as FieldStorageConfigType, IPLocation, IPLocationFieldConfig, LocationInfo, LogLevel$1 as LogLevel, NetworkInfo, NetworkType, QueueConfig, QueuedEvent, SessionInfo, UseAnalyticsOptions, UseAnalyticsReturn };
874
+ export { AnalyticsService, AttributionDetector, DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS, DEFAULT_ESSENTIAL_DEVICE_FIELDS, DEFAULT_ESSENTIAL_IP_FIELDS, DEFAULT_ESSENTIAL_LOCATION_FIELDS, DEFAULT_ESSENTIAL_NETWORK_FIELDS, DeviceDetector, LocationDetector, NetworkDetector, QueueManager, checkAndSetLocationConsent, clearLocationConsent, clearSession, useAnalytics as default, extractRequiredFields, filterFieldsByConfig, generateDeduplicationKey, getCompleteIPLocation, getIPFromRequest, getIPLocation, getLocationConsentTimestamp, getOrCreateSession, getOrCreateUserId, getPublicIP, getSession, hasLocationConsent, initDebug, loadJSON, loadSessionJSON, logger, saveJSON, saveSessionJSON, setLocationConsentGranted, trackPageVisit, transformIPLocationForBackend, updateSessionActivity, useAnalytics, validateEvent, validateRequiredFields };
875
+ export type { AnalyticsConfig, AnalyticsEvent, AttributionInfo, DeviceInfo, DeviceType, FieldStorageConfig$1 as FieldStorageConfig, FieldStorageConfig as FieldStorageConfigType, IPGeolocationConfig, IPLocation, IPLocationFieldConfig, LocationInfo, LogLevel$1 as LogLevel, NetworkInfo, NetworkType, QueueConfig, QueuedEvent, RequiredEventFields, SessionInfo, UseAnalyticsOptions, UseAnalyticsReturn };