user-analytics-tracker 2.0.0 → 2.1.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 +31 -0
- package/README.md +11 -2
- package/dist/index.cjs.js +219 -192
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +47 -14
- package/dist/index.d.ts +47 -14
- package/dist/index.esm.js +219 -192
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -549,6 +549,191 @@ function checkAndSetLocationConsent(msisdn) {
|
|
|
549
549
|
return false;
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
/**
|
|
553
|
+
* IP Geolocation Service
|
|
554
|
+
* Fetches location data (country, region, city) from user's IP address
|
|
555
|
+
* Uses ipwho.is API (no API key required)
|
|
556
|
+
*
|
|
557
|
+
* Stores all keys dynamically from the API response, including nested objects
|
|
558
|
+
* This ensures we capture all available data and any new fields added by the API
|
|
559
|
+
*/
|
|
560
|
+
/**
|
|
561
|
+
* Get public IP address using ipwho.is API
|
|
562
|
+
* No API key required
|
|
563
|
+
*
|
|
564
|
+
* @returns Promise<string | null> - The public IP address, or null if unavailable
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* ```typescript
|
|
568
|
+
* const ip = await getPublicIP();
|
|
569
|
+
* console.log('Your IP:', ip); // e.g., "203.0.113.42"
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
async function getPublicIP() {
|
|
573
|
+
// Skip if we're in an environment without fetch (SSR)
|
|
574
|
+
if (typeof fetch === 'undefined') {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
// Call ipwho.is without IP parameter - it auto-detects user's IP
|
|
579
|
+
// Using HTTPS endpoint for better security
|
|
580
|
+
const response = await fetch('https://ipwho.is/', {
|
|
581
|
+
method: 'GET',
|
|
582
|
+
headers: {
|
|
583
|
+
Accept: 'application/json',
|
|
584
|
+
},
|
|
585
|
+
// Add timeout to prevent hanging
|
|
586
|
+
signal: AbortSignal.timeout(5000),
|
|
587
|
+
});
|
|
588
|
+
if (!response.ok) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
const data = await response.json();
|
|
592
|
+
// ipwho.is returns success field
|
|
593
|
+
if (data.success === false) {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
return data.ip || null;
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
// Silently fail - don't break user experience
|
|
600
|
+
if (error.name !== 'AbortError') {
|
|
601
|
+
console.warn('[IP Geolocation] Error fetching public IP:', error.message);
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get location from IP address using ipwho.is API
|
|
608
|
+
* Free tier: No API key required
|
|
609
|
+
*
|
|
610
|
+
* Stores all keys dynamically from the API response, including nested objects
|
|
611
|
+
* This ensures we capture all available data and any new fields added by the API
|
|
612
|
+
*/
|
|
613
|
+
async function getIPLocation(ip) {
|
|
614
|
+
// Skip localhost/private IPs (these can't be geolocated)
|
|
615
|
+
if (!ip ||
|
|
616
|
+
ip === '0.0.0.0' ||
|
|
617
|
+
ip === '::1' ||
|
|
618
|
+
ip.startsWith('127.') ||
|
|
619
|
+
ip.startsWith('192.168.') ||
|
|
620
|
+
ip.startsWith('10.') ||
|
|
621
|
+
ip.startsWith('172.') ||
|
|
622
|
+
ip.startsWith('::ffff:127.')) {
|
|
623
|
+
console.log(`[IP Geolocation] Skipping localhost/private IP: ${ip} (geolocation not available for local IPs)`);
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
try {
|
|
627
|
+
// Using ipwho.is API (no API key required)
|
|
628
|
+
const response = await fetch(`https://ipwho.is/${ip}`, {
|
|
629
|
+
method: 'GET',
|
|
630
|
+
headers: {
|
|
631
|
+
Accept: 'application/json',
|
|
632
|
+
},
|
|
633
|
+
// Add timeout to prevent hanging
|
|
634
|
+
signal: AbortSignal.timeout(5000),
|
|
635
|
+
});
|
|
636
|
+
if (!response.ok) {
|
|
637
|
+
console.warn(`[IP Geolocation] Failed to fetch location for IP ${ip}: ${response.status}`);
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const data = await response.json();
|
|
641
|
+
// ipwho.is returns success field
|
|
642
|
+
if (data.success === false) {
|
|
643
|
+
console.warn(`[IP Geolocation] API error for IP ${ip}: ${data.message || 'Unknown error'}`);
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
// Store all keys dynamically from the response
|
|
647
|
+
// This ensures we capture all fields, including nested objects and any new fields
|
|
648
|
+
const locationData = {
|
|
649
|
+
ip: data.ip || ip,
|
|
650
|
+
// Map all fields from the API response dynamically
|
|
651
|
+
...Object.keys(data).reduce((acc, key) => {
|
|
652
|
+
// Store all keys and their values, preserving nested objects
|
|
653
|
+
acc[key] = data[key];
|
|
654
|
+
return acc;
|
|
655
|
+
}, {}),
|
|
656
|
+
};
|
|
657
|
+
// Add backward compatibility mappings for existing code
|
|
658
|
+
if (data.latitude !== undefined) {
|
|
659
|
+
locationData.lat = data.latitude;
|
|
660
|
+
}
|
|
661
|
+
if (data.longitude !== undefined) {
|
|
662
|
+
locationData.lon = data.longitude;
|
|
663
|
+
}
|
|
664
|
+
if (data.country_code !== undefined) {
|
|
665
|
+
locationData.countryCode = data.country_code;
|
|
666
|
+
}
|
|
667
|
+
if (data.region !== undefined) {
|
|
668
|
+
locationData.regionName = data.region;
|
|
669
|
+
}
|
|
670
|
+
if (data.connection?.isp !== undefined) {
|
|
671
|
+
locationData.isp = data.connection.isp;
|
|
672
|
+
}
|
|
673
|
+
if (data.connection?.org !== undefined) {
|
|
674
|
+
locationData.org = data.connection.org;
|
|
675
|
+
}
|
|
676
|
+
if (data.connection?.asn !== undefined) {
|
|
677
|
+
locationData.as = `AS${data.connection.asn}`;
|
|
678
|
+
}
|
|
679
|
+
if (data.timezone?.id !== undefined) {
|
|
680
|
+
locationData.timezone = data.timezone.id;
|
|
681
|
+
}
|
|
682
|
+
locationData.query = data.ip || ip;
|
|
683
|
+
return locationData;
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
// Silently fail - don't break user experience
|
|
687
|
+
if (error.name !== 'AbortError') {
|
|
688
|
+
console.warn(`[IP Geolocation] Error fetching location for IP ${ip}:`, error.message);
|
|
689
|
+
}
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get IP address from request headers
|
|
695
|
+
* Handles various proxy headers (x-forwarded-for, x-real-ip, etc.)
|
|
696
|
+
*/
|
|
697
|
+
function getIPFromRequest(req) {
|
|
698
|
+
// Try various headers that proxies/load balancers use
|
|
699
|
+
const forwardedFor = req.headers?.get?.('x-forwarded-for') ||
|
|
700
|
+
req.headers?.['x-forwarded-for'] ||
|
|
701
|
+
req.headers?.['X-Forwarded-For'];
|
|
702
|
+
if (forwardedFor) {
|
|
703
|
+
// x-forwarded-for can contain multiple IPs, take the first one
|
|
704
|
+
const ips = forwardedFor.split(',').map((ip) => ip.trim());
|
|
705
|
+
const ip = ips[0];
|
|
706
|
+
if (ip && ip !== '0.0.0.0') {
|
|
707
|
+
return ip;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const realIP = req.headers?.get?.('x-real-ip') ||
|
|
711
|
+
req.headers?.['x-real-ip'] ||
|
|
712
|
+
req.headers?.['X-Real-IP'];
|
|
713
|
+
if (realIP && realIP !== '0.0.0.0') {
|
|
714
|
+
return realIP.trim();
|
|
715
|
+
}
|
|
716
|
+
// Try req.ip (from Express/Next.js)
|
|
717
|
+
if (req.ip && req.ip !== '0.0.0.0') {
|
|
718
|
+
return req.ip;
|
|
719
|
+
}
|
|
720
|
+
// For localhost, detect if we're running locally
|
|
721
|
+
if (typeof window === 'undefined') {
|
|
722
|
+
const hostname = req.headers?.get?.('host') || req.headers?.['host'];
|
|
723
|
+
if (hostname &&
|
|
724
|
+
(hostname.includes('localhost') ||
|
|
725
|
+
hostname.includes('127.0.0.1') ||
|
|
726
|
+
hostname.startsWith('192.168.'))) {
|
|
727
|
+
return '127.0.0.1'; // Localhost IP
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// If no IP found and we're in development, return localhost
|
|
731
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
|
|
732
|
+
return '127.0.0.1'; // Localhost for development
|
|
733
|
+
}
|
|
734
|
+
return '0.0.0.0';
|
|
735
|
+
}
|
|
736
|
+
|
|
552
737
|
/**
|
|
553
738
|
* Location Detector
|
|
554
739
|
* Detects GPS location with consent management, falls back to IP-based location API
|
|
@@ -813,7 +998,8 @@ class LocationDetector {
|
|
|
813
998
|
/**
|
|
814
999
|
* Get location from IP-based public API (client-side)
|
|
815
1000
|
* Works without user permission, good fallback when GPS is unavailable
|
|
816
|
-
* Uses
|
|
1001
|
+
* Uses ipwho.is API (no API key required)
|
|
1002
|
+
* Stores all keys dynamically from the API response
|
|
817
1003
|
*/
|
|
818
1004
|
static async getIPBasedLocation() {
|
|
819
1005
|
// Return cached IP location if available
|
|
@@ -849,51 +1035,47 @@ class LocationDetector {
|
|
|
849
1035
|
}
|
|
850
1036
|
this.ipLocationFetchingRef.current = true;
|
|
851
1037
|
try {
|
|
852
|
-
//
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
headers: {
|
|
857
|
-
Accept: 'application/json',
|
|
858
|
-
},
|
|
859
|
-
// Add timeout to prevent hanging
|
|
860
|
-
signal: AbortSignal.timeout(5000),
|
|
861
|
-
});
|
|
862
|
-
if (!response.ok) {
|
|
863
|
-
throw new Error(`HTTP ${response.status}`);
|
|
1038
|
+
// Get public IP first, then get location
|
|
1039
|
+
const publicIP = await getPublicIP();
|
|
1040
|
+
if (!publicIP) {
|
|
1041
|
+
throw new Error('Could not determine public IP address');
|
|
864
1042
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
const fallback = {
|
|
870
|
-
source: 'unknown',
|
|
871
|
-
permission: 'granted',
|
|
872
|
-
};
|
|
873
|
-
this.lastIPLocationRef.current = fallback;
|
|
874
|
-
return fallback;
|
|
1043
|
+
// Get location from IP using ipwho.is API
|
|
1044
|
+
const ipLocation = await getIPLocation(publicIP);
|
|
1045
|
+
if (!ipLocation) {
|
|
1046
|
+
throw new Error('Could not fetch location data');
|
|
875
1047
|
}
|
|
876
1048
|
// Convert IP location to LocationInfo format
|
|
1049
|
+
// Map all available fields from the IP location response
|
|
1050
|
+
// Handle timezone which can be either a string or an object
|
|
1051
|
+
const timezoneValue = typeof ipLocation.timezone === 'string'
|
|
1052
|
+
? ipLocation.timezone
|
|
1053
|
+
: ipLocation.timezone?.id || undefined;
|
|
877
1054
|
const locationResult = {
|
|
878
|
-
lat:
|
|
879
|
-
lon:
|
|
1055
|
+
lat: ipLocation.latitude ?? ipLocation.lat ?? null,
|
|
1056
|
+
lon: ipLocation.longitude ?? ipLocation.lon ?? null,
|
|
880
1057
|
accuracy: null, // IP-based location has no accuracy metric
|
|
881
1058
|
permission: 'granted', // IP location doesn't require permission
|
|
882
1059
|
source: 'ip',
|
|
883
1060
|
ts: new Date().toISOString(),
|
|
884
|
-
ip:
|
|
885
|
-
country:
|
|
886
|
-
countryCode:
|
|
887
|
-
city:
|
|
888
|
-
region:
|
|
889
|
-
timezone:
|
|
1061
|
+
ip: ipLocation.ip || publicIP,
|
|
1062
|
+
country: ipLocation.country || undefined,
|
|
1063
|
+
countryCode: ipLocation.country_code || ipLocation.countryCode || undefined,
|
|
1064
|
+
city: ipLocation.city || undefined,
|
|
1065
|
+
region: ipLocation.region || ipLocation.regionName || undefined,
|
|
1066
|
+
timezone: timezoneValue,
|
|
890
1067
|
};
|
|
1068
|
+
// Store the full IP location data in a custom field for access to all keys
|
|
1069
|
+
// This preserves all dynamic keys from the API response
|
|
1070
|
+
locationResult.ipLocationData = ipLocation;
|
|
891
1071
|
console.log('[Location] IP-based location obtained:', {
|
|
892
1072
|
ip: locationResult.ip,
|
|
893
1073
|
lat: locationResult.lat,
|
|
894
1074
|
lon: locationResult.lon,
|
|
895
1075
|
city: locationResult.city,
|
|
896
1076
|
country: locationResult.country,
|
|
1077
|
+
continent: ipLocation.continent,
|
|
1078
|
+
timezone: locationResult.timezone,
|
|
897
1079
|
});
|
|
898
1080
|
this.lastIPLocationRef.current = locationResult;
|
|
899
1081
|
return locationResult;
|
|
@@ -2376,6 +2558,8 @@ function useAnalytics(options = {}) {
|
|
|
2376
2558
|
if (autoSend) {
|
|
2377
2559
|
// Send after idle to not block paint
|
|
2378
2560
|
const send = async () => {
|
|
2561
|
+
// Extract IP location data if available (stored in ipLocationData field)
|
|
2562
|
+
const ipLocationData = loc?.ipLocationData;
|
|
2379
2563
|
await AnalyticsService.trackUserJourney({
|
|
2380
2564
|
sessionId: getOrCreateUserId(),
|
|
2381
2565
|
pageUrl: typeof window !== 'undefined' ? window.location.href : '',
|
|
@@ -2383,6 +2567,7 @@ function useAnalytics(options = {}) {
|
|
|
2383
2567
|
deviceInfo: dev,
|
|
2384
2568
|
location: loc,
|
|
2385
2569
|
attribution: attr,
|
|
2570
|
+
ipLocation: ipLocationData,
|
|
2386
2571
|
customData: config?.enableLocation ? { locationEnabled: true } : undefined,
|
|
2387
2572
|
});
|
|
2388
2573
|
};
|
|
@@ -2398,6 +2583,8 @@ function useAnalytics(options = {}) {
|
|
|
2398
2583
|
const logEvent = useCallback(async (customData) => {
|
|
2399
2584
|
if (!sessionId || !networkInfo || !deviceInfo)
|
|
2400
2585
|
return;
|
|
2586
|
+
// Extract IP location data if available (stored in ipLocationData field)
|
|
2587
|
+
const ipLocationData = location ? location?.ipLocationData : undefined;
|
|
2401
2588
|
await AnalyticsService.trackUserJourney({
|
|
2402
2589
|
sessionId,
|
|
2403
2590
|
pageUrl: typeof window !== 'undefined' ? window.location.href : '',
|
|
@@ -2405,6 +2592,7 @@ function useAnalytics(options = {}) {
|
|
|
2405
2592
|
deviceInfo,
|
|
2406
2593
|
location: location ?? undefined,
|
|
2407
2594
|
attribution: attribution ?? undefined,
|
|
2595
|
+
ipLocation: ipLocationData,
|
|
2408
2596
|
userId: sessionId,
|
|
2409
2597
|
customData,
|
|
2410
2598
|
});
|
|
@@ -2542,166 +2730,5 @@ function useAnalytics(options = {}) {
|
|
|
2542
2730
|
]);
|
|
2543
2731
|
}
|
|
2544
2732
|
|
|
2545
|
-
/**
|
|
2546
|
-
* IP Geolocation Service
|
|
2547
|
-
* Fetches location data (country, region, city) from user's IP address
|
|
2548
|
-
* Uses free tier of ip-api.com (no API key required, 45 requests/minute)
|
|
2549
|
-
*/
|
|
2550
|
-
/**
|
|
2551
|
-
* Get public IP address using ip-api.com
|
|
2552
|
-
* Free tier: 45 requests/minute, no API key required
|
|
2553
|
-
*
|
|
2554
|
-
* @returns Promise<string | null> - The public IP address, or null if unavailable
|
|
2555
|
-
*
|
|
2556
|
-
* @example
|
|
2557
|
-
* ```typescript
|
|
2558
|
-
* const ip = await getPublicIP();
|
|
2559
|
-
* console.log('Your IP:', ip); // e.g., "203.0.113.42"
|
|
2560
|
-
* ```
|
|
2561
|
-
*/
|
|
2562
|
-
async function getPublicIP() {
|
|
2563
|
-
// Skip if we're in an environment without fetch (SSR)
|
|
2564
|
-
if (typeof fetch === 'undefined') {
|
|
2565
|
-
return null;
|
|
2566
|
-
}
|
|
2567
|
-
try {
|
|
2568
|
-
// Call ip-api.com without IP parameter - it auto-detects user's IP
|
|
2569
|
-
// Using HTTPS endpoint for better security
|
|
2570
|
-
const response = await fetch('https://ip-api.com/json/?fields=status,message,query', {
|
|
2571
|
-
method: 'GET',
|
|
2572
|
-
headers: {
|
|
2573
|
-
Accept: 'application/json',
|
|
2574
|
-
},
|
|
2575
|
-
// Add timeout to prevent hanging
|
|
2576
|
-
signal: AbortSignal.timeout(5000),
|
|
2577
|
-
});
|
|
2578
|
-
if (!response.ok) {
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
const data = await response.json();
|
|
2582
|
-
// ip-api.com returns status field
|
|
2583
|
-
if (data.status === 'fail') {
|
|
2584
|
-
return null;
|
|
2585
|
-
}
|
|
2586
|
-
return data.query || null;
|
|
2587
|
-
}
|
|
2588
|
-
catch (error) {
|
|
2589
|
-
// Silently fail - don't break user experience
|
|
2590
|
-
if (error.name !== 'AbortError') {
|
|
2591
|
-
console.warn('[IP Geolocation] Error fetching public IP:', error.message);
|
|
2592
|
-
}
|
|
2593
|
-
return null;
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
/**
|
|
2597
|
-
* Get location from IP address using ip-api.com
|
|
2598
|
-
* Free tier: 45 requests/minute, no API key required
|
|
2599
|
-
*
|
|
2600
|
-
* Alternative services:
|
|
2601
|
-
* - ipapi.co (requires API key for production)
|
|
2602
|
-
* - ipgeolocation.io (requires API key)
|
|
2603
|
-
* - ip-api.com (free tier available)
|
|
2604
|
-
*/
|
|
2605
|
-
async function getIPLocation(ip) {
|
|
2606
|
-
// Skip localhost/private IPs (these can't be geolocated)
|
|
2607
|
-
if (!ip ||
|
|
2608
|
-
ip === '0.0.0.0' ||
|
|
2609
|
-
ip === '::1' ||
|
|
2610
|
-
ip.startsWith('127.') ||
|
|
2611
|
-
ip.startsWith('192.168.') ||
|
|
2612
|
-
ip.startsWith('10.') ||
|
|
2613
|
-
ip.startsWith('172.') ||
|
|
2614
|
-
ip.startsWith('::ffff:127.')) {
|
|
2615
|
-
console.log(`[IP Geolocation] Skipping localhost/private IP: ${ip} (geolocation not available for local IPs)`);
|
|
2616
|
-
return null;
|
|
2617
|
-
}
|
|
2618
|
-
try {
|
|
2619
|
-
// Using ip-api.com free tier (JSON format)
|
|
2620
|
-
const response = await fetch(`http://ip-api.com/json/${ip}?fields=status,message,country,countryCode,region,regionName,city,lat,lon,timezone,isp,org,as,query`, {
|
|
2621
|
-
method: 'GET',
|
|
2622
|
-
headers: {
|
|
2623
|
-
Accept: 'application/json',
|
|
2624
|
-
},
|
|
2625
|
-
// Add timeout to prevent hanging
|
|
2626
|
-
signal: AbortSignal.timeout(3000),
|
|
2627
|
-
});
|
|
2628
|
-
if (!response.ok) {
|
|
2629
|
-
console.warn(`[IP Geolocation] Failed to fetch location for IP ${ip}: ${response.status}`);
|
|
2630
|
-
return null;
|
|
2631
|
-
}
|
|
2632
|
-
const data = await response.json();
|
|
2633
|
-
// ip-api.com returns status field
|
|
2634
|
-
if (data.status === 'fail') {
|
|
2635
|
-
console.warn(`[IP Geolocation] API error for IP ${ip}: ${data.message}`);
|
|
2636
|
-
return null;
|
|
2637
|
-
}
|
|
2638
|
-
return {
|
|
2639
|
-
ip: data.query || ip,
|
|
2640
|
-
country: data.country || undefined,
|
|
2641
|
-
countryCode: data.countryCode || undefined,
|
|
2642
|
-
region: data.region || undefined,
|
|
2643
|
-
regionName: data.regionName || undefined,
|
|
2644
|
-
city: data.city || undefined,
|
|
2645
|
-
lat: data.lat || undefined,
|
|
2646
|
-
lon: data.lon || undefined,
|
|
2647
|
-
timezone: data.timezone || undefined,
|
|
2648
|
-
isp: data.isp || undefined,
|
|
2649
|
-
org: data.org || undefined,
|
|
2650
|
-
as: data.as || undefined,
|
|
2651
|
-
query: data.query || ip,
|
|
2652
|
-
};
|
|
2653
|
-
}
|
|
2654
|
-
catch (error) {
|
|
2655
|
-
// Silently fail - don't break user experience
|
|
2656
|
-
if (error.name !== 'AbortError') {
|
|
2657
|
-
console.warn(`[IP Geolocation] Error fetching location for IP ${ip}:`, error.message);
|
|
2658
|
-
}
|
|
2659
|
-
return null;
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
/**
|
|
2663
|
-
* Get IP address from request headers
|
|
2664
|
-
* Handles various proxy headers (x-forwarded-for, x-real-ip, etc.)
|
|
2665
|
-
*/
|
|
2666
|
-
function getIPFromRequest(req) {
|
|
2667
|
-
// Try various headers that proxies/load balancers use
|
|
2668
|
-
const forwardedFor = req.headers?.get?.('x-forwarded-for') ||
|
|
2669
|
-
req.headers?.['x-forwarded-for'] ||
|
|
2670
|
-
req.headers?.['X-Forwarded-For'];
|
|
2671
|
-
if (forwardedFor) {
|
|
2672
|
-
// x-forwarded-for can contain multiple IPs, take the first one
|
|
2673
|
-
const ips = forwardedFor.split(',').map((ip) => ip.trim());
|
|
2674
|
-
const ip = ips[0];
|
|
2675
|
-
if (ip && ip !== '0.0.0.0') {
|
|
2676
|
-
return ip;
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
const realIP = req.headers?.get?.('x-real-ip') ||
|
|
2680
|
-
req.headers?.['x-real-ip'] ||
|
|
2681
|
-
req.headers?.['X-Real-IP'];
|
|
2682
|
-
if (realIP && realIP !== '0.0.0.0') {
|
|
2683
|
-
return realIP.trim();
|
|
2684
|
-
}
|
|
2685
|
-
// Try req.ip (from Express/Next.js)
|
|
2686
|
-
if (req.ip && req.ip !== '0.0.0.0') {
|
|
2687
|
-
return req.ip;
|
|
2688
|
-
}
|
|
2689
|
-
// For localhost, detect if we're running locally
|
|
2690
|
-
if (typeof window === 'undefined') {
|
|
2691
|
-
const hostname = req.headers?.get?.('host') || req.headers?.['host'];
|
|
2692
|
-
if (hostname &&
|
|
2693
|
-
(hostname.includes('localhost') ||
|
|
2694
|
-
hostname.includes('127.0.0.1') ||
|
|
2695
|
-
hostname.startsWith('192.168.'))) {
|
|
2696
|
-
return '127.0.0.1'; // Localhost IP
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
// If no IP found and we're in development, return localhost
|
|
2700
|
-
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
|
|
2701
|
-
return '127.0.0.1'; // Localhost for development
|
|
2702
|
-
}
|
|
2703
|
-
return '0.0.0.0';
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
2733
|
export { AnalyticsService, AttributionDetector, DeviceDetector, LocationDetector, NetworkDetector, QueueManager, checkAndSetLocationConsent, clearLocationConsent, clearSession, useAnalytics as default, getIPFromRequest, getIPLocation, getLocationConsentTimestamp, getOrCreateSession, getOrCreateUserId, getPublicIP, getSession, hasLocationConsent, initDebug, loadJSON, loadSessionJSON, logger, saveJSON, saveSessionJSON, setLocationConsentGranted, trackPageVisit, updateSessionActivity, useAnalytics };
|
|
2707
2734
|
//# sourceMappingURL=index.esm.js.map
|