user-analytics-tracker 2.2.0 → 3.0.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 +38 -0
- package/README.md +15 -0
- package/dist/index.cjs.js +364 -19
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +68 -5
- package/dist/index.d.ts +68 -5
- package/dist/index.esm.js +359 -20
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Core types for the analytics tracker package
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Default essential fields for IP location storage
|
|
8
|
+
* These fields are stored when mode is 'essential' (default)
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_ESSENTIAL_IP_FIELDS = [
|
|
11
|
+
// Core identification
|
|
12
|
+
'ip',
|
|
13
|
+
'country',
|
|
14
|
+
'countryCode',
|
|
15
|
+
'region',
|
|
16
|
+
'city',
|
|
17
|
+
// Geographic coordinates (stored here, not duplicated in location)
|
|
18
|
+
'lat',
|
|
19
|
+
'lon',
|
|
20
|
+
// Additional geographic info
|
|
21
|
+
'continent',
|
|
22
|
+
'continentCode',
|
|
23
|
+
// Network info
|
|
24
|
+
'type',
|
|
25
|
+
'isEu',
|
|
26
|
+
'isp',
|
|
27
|
+
'connection',
|
|
28
|
+
'connection.asn',
|
|
29
|
+
'connection.org',
|
|
30
|
+
'connection.isp',
|
|
31
|
+
'connection.domain',
|
|
32
|
+
// Timezone (stored here, not duplicated in location)
|
|
33
|
+
'timezone',
|
|
34
|
+
'timezoneDetails',
|
|
35
|
+
'timezoneDetails.id',
|
|
36
|
+
'timezoneDetails.abbr',
|
|
37
|
+
'timezoneDetails.utc',
|
|
38
|
+
// Flag (only emoji in essential mode)
|
|
39
|
+
'flag.emoji',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Default essential fields for Device Info storage
|
|
43
|
+
*/
|
|
44
|
+
const DEFAULT_ESSENTIAL_DEVICE_FIELDS = [
|
|
45
|
+
'type',
|
|
46
|
+
'os',
|
|
47
|
+
'osVersion',
|
|
48
|
+
'browser',
|
|
49
|
+
'browserVersion',
|
|
50
|
+
'deviceModel',
|
|
51
|
+
'deviceBrand',
|
|
52
|
+
'userAgent',
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Default essential fields for Network Info storage
|
|
56
|
+
*/
|
|
57
|
+
const DEFAULT_ESSENTIAL_NETWORK_FIELDS = [
|
|
58
|
+
'type',
|
|
59
|
+
'effectiveType',
|
|
60
|
+
'downlink',
|
|
61
|
+
'rtt',
|
|
62
|
+
'saveData',
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Default essential fields for Location Info storage
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_ESSENTIAL_LOCATION_FIELDS = [
|
|
68
|
+
// Minimal location fields - only coordinates and source
|
|
69
|
+
// All IP-related data (ip, country, city, etc.) is stored in customData.ipLocation to avoid duplication
|
|
70
|
+
'lat',
|
|
71
|
+
'lon',
|
|
72
|
+
'source',
|
|
73
|
+
'ts',
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Default essential fields for Attribution Info storage
|
|
77
|
+
*/
|
|
78
|
+
const DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS = [
|
|
79
|
+
'landingUrl',
|
|
80
|
+
'path',
|
|
81
|
+
'hostname',
|
|
82
|
+
'referrerUrl',
|
|
83
|
+
'referrerDomain',
|
|
84
|
+
'navigationType',
|
|
85
|
+
'isReload',
|
|
86
|
+
'isBackForward',
|
|
87
|
+
'utm_source',
|
|
88
|
+
'utm_medium',
|
|
89
|
+
'utm_campaign',
|
|
90
|
+
'utm_term',
|
|
91
|
+
'utm_content',
|
|
92
|
+
];
|
|
93
|
+
|
|
3
94
|
/**
|
|
4
95
|
* Network Type Detector
|
|
5
96
|
* Detects WiFi, Mobile Data (Cellular), Hotspot, Ethernet, or Unknown
|
|
@@ -2006,18 +2097,206 @@ class MetricsCollector {
|
|
|
2006
2097
|
// Global metrics collector instance
|
|
2007
2098
|
const metricsCollector = new MetricsCollector();
|
|
2008
2099
|
|
|
2100
|
+
/**
|
|
2101
|
+
* Generic field storage transformer
|
|
2102
|
+
* Filters object fields based on storage configuration
|
|
2103
|
+
*/
|
|
2104
|
+
/**
|
|
2105
|
+
* Filter object fields based on storage configuration
|
|
2106
|
+
*
|
|
2107
|
+
* @param data - The data object to filter
|
|
2108
|
+
* @param config - Storage configuration
|
|
2109
|
+
* @param defaultEssentialFields - Default essential fields for this data type
|
|
2110
|
+
* @returns Filtered data object with only configured fields
|
|
2111
|
+
*/
|
|
2112
|
+
function filterFieldsByConfig(data, config, defaultEssentialFields) {
|
|
2113
|
+
if (!data) {
|
|
2114
|
+
return null;
|
|
2115
|
+
}
|
|
2116
|
+
const mode = config?.mode || 'essential';
|
|
2117
|
+
let fieldsToInclude = [];
|
|
2118
|
+
if (mode === 'essential') {
|
|
2119
|
+
// Use default essential fields
|
|
2120
|
+
fieldsToInclude = [...defaultEssentialFields];
|
|
2121
|
+
}
|
|
2122
|
+
else if (mode === 'all') {
|
|
2123
|
+
// Include all fields, then exclude specified ones
|
|
2124
|
+
fieldsToInclude = ['*']; // Special marker for "all fields"
|
|
2125
|
+
}
|
|
2126
|
+
else if (mode === 'custom' && config) {
|
|
2127
|
+
// Use custom field list
|
|
2128
|
+
fieldsToInclude = config.fields || [];
|
|
2129
|
+
}
|
|
2130
|
+
// If mode is 'all', just exclude specified fields
|
|
2131
|
+
if (mode === 'all') {
|
|
2132
|
+
const filtered = { ...data };
|
|
2133
|
+
if (config?.exclude && config.exclude.length > 0) {
|
|
2134
|
+
const excludeSet = new Set(config.exclude);
|
|
2135
|
+
Object.keys(filtered).forEach(key => {
|
|
2136
|
+
if (excludeSet.has(key)) {
|
|
2137
|
+
delete filtered[key];
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
// Handle nested exclusions
|
|
2141
|
+
if (filtered.connection && excludeSet.has('connection')) {
|
|
2142
|
+
delete filtered.connection;
|
|
2143
|
+
}
|
|
2144
|
+
if (filtered.timezoneDetails && excludeSet.has('timezoneDetails')) {
|
|
2145
|
+
delete filtered.timezoneDetails;
|
|
2146
|
+
}
|
|
2147
|
+
if (filtered.flag && excludeSet.has('flag')) {
|
|
2148
|
+
delete filtered.flag;
|
|
2149
|
+
}
|
|
2150
|
+
if (filtered.firstTouch && excludeSet.has('firstTouch')) {
|
|
2151
|
+
delete filtered.firstTouch;
|
|
2152
|
+
}
|
|
2153
|
+
if (filtered.lastTouch && excludeSet.has('lastTouch')) {
|
|
2154
|
+
delete filtered.lastTouch;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return filtered;
|
|
2158
|
+
}
|
|
2159
|
+
// For 'essential' or 'custom' mode, only include specified fields
|
|
2160
|
+
const filtered = {};
|
|
2161
|
+
const includeSet = new Set(fieldsToInclude);
|
|
2162
|
+
// Helper to check if a field path should be included
|
|
2163
|
+
const shouldInclude = (fieldPath) => {
|
|
2164
|
+
// Direct match - most specific
|
|
2165
|
+
if (includeSet.has(fieldPath))
|
|
2166
|
+
return true;
|
|
2167
|
+
// For nested fields (e.g., 'flag.emoji'), only include if explicitly listed
|
|
2168
|
+
// Don't auto-include all children just because parent is included
|
|
2169
|
+
const parts = fieldPath.split('.');
|
|
2170
|
+
if (parts.length > 1) {
|
|
2171
|
+
// For nested fields, require explicit inclusion
|
|
2172
|
+
// This prevents 'flag' from including all 'flag.*' fields
|
|
2173
|
+
return includeSet.has(fieldPath);
|
|
2174
|
+
}
|
|
2175
|
+
// For top-level fields only, check if parent path is included
|
|
2176
|
+
// This allows 'connection' to work when all connection.* fields are listed
|
|
2177
|
+
return false;
|
|
2178
|
+
};
|
|
2179
|
+
// Helper to check if a parent object should be created (for nested objects)
|
|
2180
|
+
const shouldIncludeParent = (parentPath) => {
|
|
2181
|
+
// Check if any child of this parent is included
|
|
2182
|
+
for (const field of fieldsToInclude) {
|
|
2183
|
+
if (field.startsWith(parentPath + '.')) {
|
|
2184
|
+
return true;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
// Also check if parent itself is explicitly included
|
|
2188
|
+
return includeSet.has(parentPath);
|
|
2189
|
+
};
|
|
2190
|
+
// Filter top-level fields
|
|
2191
|
+
Object.keys(data).forEach(key => {
|
|
2192
|
+
if (shouldInclude(key)) {
|
|
2193
|
+
filtered[key] = data[key];
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
// Handle nested objects - only create if at least one child field is included
|
|
2197
|
+
if (data.connection && shouldIncludeParent('connection')) {
|
|
2198
|
+
filtered.connection = {};
|
|
2199
|
+
if (shouldInclude('connection.asn'))
|
|
2200
|
+
filtered.connection.asn = data.connection.asn;
|
|
2201
|
+
if (shouldInclude('connection.org'))
|
|
2202
|
+
filtered.connection.org = data.connection.org;
|
|
2203
|
+
if (shouldInclude('connection.isp'))
|
|
2204
|
+
filtered.connection.isp = data.connection.isp;
|
|
2205
|
+
if (shouldInclude('connection.domain'))
|
|
2206
|
+
filtered.connection.domain = data.connection.domain;
|
|
2207
|
+
// If no connection fields were included, remove the object
|
|
2208
|
+
if (Object.keys(filtered.connection).length === 0) {
|
|
2209
|
+
delete filtered.connection;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if (data.timezoneDetails && shouldIncludeParent('timezoneDetails')) {
|
|
2213
|
+
filtered.timezoneDetails = {};
|
|
2214
|
+
if (shouldInclude('timezoneDetails.id'))
|
|
2215
|
+
filtered.timezoneDetails.id = data.timezoneDetails.id;
|
|
2216
|
+
if (shouldInclude('timezoneDetails.abbr'))
|
|
2217
|
+
filtered.timezoneDetails.abbr = data.timezoneDetails.abbr;
|
|
2218
|
+
if (shouldInclude('timezoneDetails.utc'))
|
|
2219
|
+
filtered.timezoneDetails.utc = data.timezoneDetails.utc;
|
|
2220
|
+
if (shouldInclude('timezoneDetails.isDst'))
|
|
2221
|
+
filtered.timezoneDetails.isDst = data.timezoneDetails.isDst;
|
|
2222
|
+
if (shouldInclude('timezoneDetails.offset'))
|
|
2223
|
+
filtered.timezoneDetails.offset = data.timezoneDetails.offset;
|
|
2224
|
+
if (shouldInclude('timezoneDetails.currentTime'))
|
|
2225
|
+
filtered.timezoneDetails.currentTime = data.timezoneDetails.currentTime;
|
|
2226
|
+
// If no timezoneDetails fields were included, remove the object
|
|
2227
|
+
if (Object.keys(filtered.timezoneDetails).length === 0) {
|
|
2228
|
+
delete filtered.timezoneDetails;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
if (data.flag && shouldIncludeParent('flag')) {
|
|
2232
|
+
filtered.flag = {};
|
|
2233
|
+
// Only include specific flag fields if they're explicitly in the include list
|
|
2234
|
+
if (shouldInclude('flag.emoji'))
|
|
2235
|
+
filtered.flag.emoji = data.flag.emoji;
|
|
2236
|
+
if (shouldInclude('flag.img'))
|
|
2237
|
+
filtered.flag.img = data.flag.img;
|
|
2238
|
+
if (shouldInclude('flag.emojiUnicode'))
|
|
2239
|
+
filtered.flag.emojiUnicode = data.flag.emojiUnicode;
|
|
2240
|
+
// If no specific flag fields are included, don't add the flag object
|
|
2241
|
+
if (Object.keys(filtered.flag).length === 0) {
|
|
2242
|
+
delete filtered.flag;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (data.firstTouch && shouldInclude('firstTouch')) {
|
|
2246
|
+
filtered.firstTouch = data.firstTouch;
|
|
2247
|
+
}
|
|
2248
|
+
if (data.lastTouch && shouldInclude('lastTouch')) {
|
|
2249
|
+
filtered.lastTouch = data.lastTouch;
|
|
2250
|
+
}
|
|
2251
|
+
// Remove null and undefined values to reduce payload size
|
|
2252
|
+
const cleanValue = (val) => {
|
|
2253
|
+
if (val === null || val === undefined) {
|
|
2254
|
+
return undefined; // Will be filtered out
|
|
2255
|
+
}
|
|
2256
|
+
// For objects, recursively clean nested null/undefined values
|
|
2257
|
+
if (typeof val === 'object' && !Array.isArray(val) && val !== null) {
|
|
2258
|
+
const cleaned = {};
|
|
2259
|
+
let hasAnyValue = false;
|
|
2260
|
+
Object.keys(val).forEach(key => {
|
|
2261
|
+
const cleanedChild = cleanValue(val[key]);
|
|
2262
|
+
if (cleanedChild !== undefined) {
|
|
2263
|
+
cleaned[key] = cleanedChild;
|
|
2264
|
+
hasAnyValue = true;
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
return hasAnyValue ? cleaned : undefined;
|
|
2268
|
+
}
|
|
2269
|
+
// For arrays, clean each element
|
|
2270
|
+
if (Array.isArray(val)) {
|
|
2271
|
+
const cleaned = val.map(cleanValue).filter(item => item !== undefined);
|
|
2272
|
+
return cleaned.length > 0 ? cleaned : undefined;
|
|
2273
|
+
}
|
|
2274
|
+
return val;
|
|
2275
|
+
};
|
|
2276
|
+
const cleaned = {};
|
|
2277
|
+
Object.keys(filtered).forEach(key => {
|
|
2278
|
+
const cleanedValue = cleanValue(filtered[key]);
|
|
2279
|
+
if (cleanedValue !== undefined) {
|
|
2280
|
+
cleaned[key] = cleanedValue;
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
return cleaned;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2009
2286
|
/**
|
|
2010
2287
|
* Transform IP location data from API format (snake_case) to backend-expected format (camelCase)
|
|
2011
|
-
*
|
|
2288
|
+
* Supports configurable field storage to optimize storage capacity
|
|
2012
2289
|
*
|
|
2013
2290
|
* @param ipLocation - Raw IP location data from ipwho.is API
|
|
2014
|
-
* @
|
|
2291
|
+
* @param config - Optional configuration for which fields to store
|
|
2292
|
+
* @returns Transformed IP location data matching backend schema (only includes configured fields)
|
|
2015
2293
|
*/
|
|
2016
|
-
function transformIPLocationForBackend(ipLocation) {
|
|
2294
|
+
function transformIPLocationForBackend(ipLocation, config) {
|
|
2017
2295
|
if (!ipLocation) {
|
|
2018
2296
|
return null;
|
|
2019
2297
|
}
|
|
2020
2298
|
// Transform to match backend expected format (camelCase)
|
|
2299
|
+
// Build complete object first, then filter based on configuration
|
|
2021
2300
|
const transformed = {
|
|
2022
2301
|
// Basic fields
|
|
2023
2302
|
ip: ipLocation.ip,
|
|
@@ -2052,9 +2331,11 @@ function transformIPLocationForBackend(ipLocation) {
|
|
|
2052
2331
|
timezoneDetails: ipLocation.timezone && typeof ipLocation.timezone === 'object' ? {
|
|
2053
2332
|
id: ipLocation.timezone.id,
|
|
2054
2333
|
abbr: ipLocation.timezone.abbr,
|
|
2334
|
+
utc: ipLocation.timezone.utc,
|
|
2335
|
+
// Exclude these in essential mode: isDst, offset, currentTime
|
|
2336
|
+
// They will be filtered out by filterFieldsByConfig if not in essential fields
|
|
2055
2337
|
isDst: ipLocation.timezone.is_dst,
|
|
2056
2338
|
offset: ipLocation.timezone.offset,
|
|
2057
|
-
utc: ipLocation.timezone.utc,
|
|
2058
2339
|
currentTime: ipLocation.timezone.current_time,
|
|
2059
2340
|
} : undefined,
|
|
2060
2341
|
// Flag - transform to camelCase
|
|
@@ -2070,7 +2351,8 @@ function transformIPLocationForBackend(ipLocation) {
|
|
|
2070
2351
|
delete transformed[key];
|
|
2071
2352
|
}
|
|
2072
2353
|
});
|
|
2073
|
-
|
|
2354
|
+
// Filter fields based on configuration using generic filter
|
|
2355
|
+
return filterFieldsByConfig(transformed, config, DEFAULT_ESSENTIAL_IP_FIELDS);
|
|
2074
2356
|
}
|
|
2075
2357
|
|
|
2076
2358
|
/**
|
|
@@ -2336,20 +2618,49 @@ class AnalyticsService {
|
|
|
2336
2618
|
* Track user journey with full context
|
|
2337
2619
|
*/
|
|
2338
2620
|
static async trackUserJourney({ sessionId, pageUrl, networkInfo, deviceInfo, location, attribution, ipLocation, userId, customData, pageVisits = 1, interactions = 0, }) {
|
|
2339
|
-
//
|
|
2340
|
-
const
|
|
2621
|
+
// Get field storage config (support both new and legacy format)
|
|
2622
|
+
const fieldStorage = this.config.fieldStorage || {};
|
|
2623
|
+
const ipLocationConfig = fieldStorage.ipLocation || this.config.ipLocationFields;
|
|
2624
|
+
// Transform and filter all data types based on configuration
|
|
2625
|
+
const transformedIPLocation = transformIPLocationForBackend(ipLocation, ipLocationConfig);
|
|
2626
|
+
const filteredDeviceInfo = filterFieldsByConfig(deviceInfo, fieldStorage.deviceInfo, DEFAULT_ESSENTIAL_DEVICE_FIELDS);
|
|
2627
|
+
const filteredNetworkInfo = filterFieldsByConfig(networkInfo, fieldStorage.networkInfo, DEFAULT_ESSENTIAL_NETWORK_FIELDS);
|
|
2628
|
+
// For location: In essential mode, remove duplicate fields that are already in customData.ipLocation
|
|
2629
|
+
// This prevents storing the same data twice (e.g., ip, country, city, region, timezone)
|
|
2630
|
+
const locationConfig = fieldStorage.location;
|
|
2631
|
+
const locationMode = locationConfig?.mode || 'essential';
|
|
2632
|
+
let filteredLocation = filterFieldsByConfig(location, locationConfig, DEFAULT_ESSENTIAL_LOCATION_FIELDS);
|
|
2633
|
+
// In essential mode, if we have IP location data, remove duplicate fields from location
|
|
2634
|
+
// to avoid storing the same data twice
|
|
2635
|
+
if (locationMode === 'essential' && transformedIPLocation && filteredLocation) {
|
|
2636
|
+
// Remove fields that are duplicated in customData.ipLocation
|
|
2637
|
+
const duplicateFields = ['ip', 'country', 'countryCode', 'city', 'region', 'timezone'];
|
|
2638
|
+
const minimalLocation = { ...filteredLocation };
|
|
2639
|
+
duplicateFields.forEach(field => {
|
|
2640
|
+
delete minimalLocation[field];
|
|
2641
|
+
});
|
|
2642
|
+
// Only keep essential location fields: lat, lon, source, ts
|
|
2643
|
+
filteredLocation = {
|
|
2644
|
+
lat: minimalLocation.lat,
|
|
2645
|
+
lon: minimalLocation.lon,
|
|
2646
|
+
source: minimalLocation.source,
|
|
2647
|
+
ts: minimalLocation.ts,
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
const filteredAttribution = filterFieldsByConfig(attribution, fieldStorage.attribution, DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS);
|
|
2341
2651
|
await this.trackEvent({
|
|
2342
2652
|
sessionId,
|
|
2343
2653
|
pageUrl,
|
|
2344
|
-
networkInfo,
|
|
2345
|
-
deviceInfo,
|
|
2346
|
-
location,
|
|
2347
|
-
attribution,
|
|
2348
|
-
ipLocation
|
|
2654
|
+
networkInfo: filteredNetworkInfo || undefined,
|
|
2655
|
+
deviceInfo: filteredDeviceInfo || undefined,
|
|
2656
|
+
location: filteredLocation || undefined,
|
|
2657
|
+
attribution: filteredAttribution || undefined,
|
|
2658
|
+
// Don't include raw ipLocation - we have the filtered/transformed version in customData
|
|
2659
|
+
ipLocation: undefined,
|
|
2349
2660
|
userId: userId ?? sessionId,
|
|
2350
2661
|
customData: {
|
|
2351
2662
|
...customData,
|
|
2352
|
-
// Store transformed IP location in customData for backend integration
|
|
2663
|
+
// Store transformed and filtered IP location in customData for backend integration
|
|
2353
2664
|
...(transformedIPLocation && { ipLocation: transformedIPLocation }),
|
|
2354
2665
|
},
|
|
2355
2666
|
eventName: 'page_view', // Auto-tracked as page view
|
|
@@ -2426,15 +2737,41 @@ class AnalyticsService {
|
|
|
2426
2737
|
const ipLocationData = locationData && typeof locationData === 'object'
|
|
2427
2738
|
? locationData?.ipLocationData
|
|
2428
2739
|
: undefined;
|
|
2429
|
-
//
|
|
2430
|
-
const
|
|
2740
|
+
// Get field storage config (support both new and legacy format)
|
|
2741
|
+
const fieldStorage = this.config.fieldStorage || {};
|
|
2742
|
+
const ipLocationConfig = fieldStorage.ipLocation || this.config.ipLocationFields;
|
|
2743
|
+
// Transform and filter all data types based on configuration
|
|
2744
|
+
const transformedIPLocation = transformIPLocationForBackend(ipLocationData, ipLocationConfig);
|
|
2745
|
+
const filteredDeviceInfo = filterFieldsByConfig(context?.deviceInfo || autoContext?.deviceInfo, fieldStorage.deviceInfo, DEFAULT_ESSENTIAL_DEVICE_FIELDS);
|
|
2746
|
+
const filteredNetworkInfo = filterFieldsByConfig(context?.networkInfo || autoContext?.networkInfo, fieldStorage.networkInfo, DEFAULT_ESSENTIAL_NETWORK_FIELDS);
|
|
2747
|
+
// For location: In essential mode, remove duplicate fields that are already in customData.ipLocation
|
|
2748
|
+
const locationConfig = fieldStorage.location;
|
|
2749
|
+
const locationMode = locationConfig?.mode || 'essential';
|
|
2750
|
+
let filteredLocation = filterFieldsByConfig((context?.location || autoContext?.location), locationConfig, DEFAULT_ESSENTIAL_LOCATION_FIELDS);
|
|
2751
|
+
// In essential mode, if we have IP location data, remove duplicate fields from location
|
|
2752
|
+
if (locationMode === 'essential' && transformedIPLocation && filteredLocation) {
|
|
2753
|
+
// Remove fields that are duplicated in customData.ipLocation
|
|
2754
|
+
const duplicateFields = ['ip', 'country', 'countryCode', 'city', 'region', 'timezone'];
|
|
2755
|
+
const minimalLocation = { ...filteredLocation };
|
|
2756
|
+
duplicateFields.forEach(field => {
|
|
2757
|
+
delete minimalLocation[field];
|
|
2758
|
+
});
|
|
2759
|
+
// Only keep essential location fields: lat, lon, source, ts
|
|
2760
|
+
filteredLocation = {
|
|
2761
|
+
lat: minimalLocation.lat,
|
|
2762
|
+
lon: minimalLocation.lon,
|
|
2763
|
+
source: minimalLocation.source,
|
|
2764
|
+
ts: minimalLocation.ts,
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
const filteredAttribution = filterFieldsByConfig(context?.attribution || autoContext?.attribution, fieldStorage.attribution, DEFAULT_ESSENTIAL_ATTRIBUTION_FIELDS);
|
|
2431
2768
|
await this.trackEvent({
|
|
2432
2769
|
sessionId: finalSessionId,
|
|
2433
2770
|
pageUrl: finalPageUrl,
|
|
2434
|
-
networkInfo:
|
|
2435
|
-
deviceInfo:
|
|
2436
|
-
location:
|
|
2437
|
-
attribution:
|
|
2771
|
+
networkInfo: filteredNetworkInfo || undefined,
|
|
2772
|
+
deviceInfo: filteredDeviceInfo || undefined,
|
|
2773
|
+
location: filteredLocation || undefined,
|
|
2774
|
+
attribution: filteredAttribution || undefined,
|
|
2438
2775
|
userId: context?.userId || finalSessionId,
|
|
2439
2776
|
eventName,
|
|
2440
2777
|
eventParameters: parameters || {},
|
|
@@ -2620,6 +2957,8 @@ function useAnalytics(options = {}) {
|
|
|
2620
2957
|
logLevel: config.logLevel,
|
|
2621
2958
|
enableMetrics: config.enableMetrics,
|
|
2622
2959
|
sessionTimeout: config.sessionTimeout,
|
|
2960
|
+
fieldStorage: config.fieldStorage,
|
|
2961
|
+
ipLocationFields: config.ipLocationFields, // Legacy support
|
|
2623
2962
|
});
|
|
2624
2963
|
}
|
|
2625
2964
|
}, [
|
|
@@ -2905,5 +3244,5 @@ function useAnalytics(options = {}) {
|
|
|
2905
3244
|
]);
|
|
2906
3245
|
}
|
|
2907
3246
|
|
|
2908
|
-
export { AnalyticsService, AttributionDetector, DeviceDetector, LocationDetector, NetworkDetector, QueueManager, checkAndSetLocationConsent, clearLocationConsent, clearSession, useAnalytics as default, getCompleteIPLocation, getIPFromRequest, getIPLocation, getLocationConsentTimestamp, getOrCreateSession, getOrCreateUserId, getPublicIP, getSession, hasLocationConsent, initDebug, loadJSON, loadSessionJSON, logger, saveJSON, saveSessionJSON, setLocationConsentGranted, trackPageVisit, transformIPLocationForBackend, updateSessionActivity, useAnalytics };
|
|
3247
|
+
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 };
|
|
2909
3248
|
//# sourceMappingURL=index.esm.js.map
|