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/CHANGELOG.md +19 -0
- package/README.md +20 -1
- package/dist/index.cjs.js +588 -82
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +184 -58
- package/dist/index.d.ts +184 -58
- package/dist/index.esm.js +585 -83
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
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", "
|
|
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 ["
|
|
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"
|
|
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
|
|
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: {
|
|
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
|
|
648
|
-
*
|
|
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
|
-
*
|
|
652
|
-
*
|
|
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
|
-
*
|
|
656
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
819
|
+
declare function extractRequiredFields(event: AnalyticsEvent): RequiredEventFields;
|
|
684
820
|
/**
|
|
685
|
-
*
|
|
686
|
-
*
|
|
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
|
|
824
|
+
declare function validateRequiredFields(fields: RequiredEventFields): boolean;
|
|
694
825
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
826
|
+
* Validate and filter event
|
|
827
|
+
* Returns null if event should be filtered out, otherwise returns the event
|
|
697
828
|
*/
|
|
698
|
-
declare function
|
|
699
|
-
|
|
829
|
+
declare function validateEvent(event: AnalyticsEvent): AnalyticsEvent | null;
|
|
700
830
|
/**
|
|
701
|
-
*
|
|
702
|
-
*
|
|
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
|
|
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 };
|