9 changed files with 491 additions and 27 deletions
@ -0,0 +1,258 @@
@@ -0,0 +1,258 @@
|
||||
/** |
||||
* Utility to clean tracking parameters from URLs |
||||
* Removes common tracking query parameters like utm_*, ref, fbclid, gclid, etc. |
||||
*/ |
||||
|
||||
/** |
||||
* List of common tracking parameters to remove from URLs |
||||
*/ |
||||
const TRACKING_PARAMS = new Set([ |
||||
// UTM parameters
|
||||
'utm_source', |
||||
'utm_medium', |
||||
'utm_campaign', |
||||
'utm_term', |
||||
'utm_content', |
||||
'utm_id', |
||||
'utm_cid', |
||||
|
||||
// Generic tracking
|
||||
'ref', |
||||
'source', |
||||
'campaign', |
||||
'referrer', |
||||
'referer', |
||||
|
||||
// Social media tracking
|
||||
'fbclid', // Facebook
|
||||
'gclid', // Google Ads
|
||||
'msclkid', // Microsoft
|
||||
'twclid', // Twitter
|
||||
'li_fat_id', // LinkedIn
|
||||
'igshid', // Instagram
|
||||
|
||||
// Analytics
|
||||
'_ga', // Google Analytics
|
||||
'_gid', // Google Analytics
|
||||
'_gl', // Google Analytics Linker
|
||||
'mc_cid', // MailChimp
|
||||
'mc_eid', // MailChimp
|
||||
'icid', // Various
|
||||
'ncid', // Various
|
||||
|
||||
// Other common trackers
|
||||
'affiliate_id', |
||||
'affid', |
||||
'affiliate', |
||||
'partner_id', |
||||
'partner', |
||||
'click_id', |
||||
'clickid', |
||||
'clickId', |
||||
'click', |
||||
'tracking_id', |
||||
'trackingId', |
||||
'tracking', |
||||
'track', |
||||
'tid', |
||||
'trk', |
||||
'trkid', |
||||
|
||||
// E-commerce
|
||||
'promo', |
||||
'promocode', |
||||
'promo_code', |
||||
'discount', |
||||
'coupon', |
||||
'voucher', |
||||
|
||||
// Email marketing
|
||||
'email_source', |
||||
'email_campaign', |
||||
'email_medium', |
||||
|
||||
// Content
|
||||
'content_id', |
||||
'contentId', |
||||
'content', |
||||
|
||||
// A/B testing
|
||||
'ab_test', |
||||
'abtest', |
||||
'variant', |
||||
|
||||
// Time-based
|
||||
'timestamp', |
||||
'ts', |
||||
'time', |
||||
|
||||
// Miscellaneous
|
||||
'hash', |
||||
'anchor', |
||||
'position', |
||||
'pos', |
||||
'placement', |
||||
'placement_id', |
||||
'placementId', |
||||
'widget_id', |
||||
'widgetId', |
||||
'widget', |
||||
'context', |
||||
'ctx', |
||||
'origin', |
||||
'orig', |
||||
'return', |
||||
'return_to', |
||||
'returnTo', |
||||
'redirect', |
||||
'redirect_to', |
||||
'redirectTo', |
||||
'next', |
||||
'continue', |
||||
'callback', |
||||
'cb', |
||||
'state', |
||||
'session_id', |
||||
'sessionId', |
||||
'sid', |
||||
'token', |
||||
'key', |
||||
'api_key', |
||||
'apikey', |
||||
'apiKey', |
||||
'auth', |
||||
'auth_token', |
||||
'authToken', |
||||
'access_token', |
||||
'accessToken', |
||||
'refresh_token', |
||||
'refreshToken', |
||||
]); |
||||
|
||||
/** |
||||
* Check if a URL fragment (hash) contains tracking parameters |
||||
*
|
||||
* @param fragment - The URL fragment (without the #) |
||||
* @returns true if the fragment contains tracking parameters |
||||
*/ |
||||
function isTrackingFragment(fragment: string): boolean { |
||||
if (!fragment) return false; |
||||
|
||||
// Check if fragment is in key=value format (e.g., "ref=rss")
|
||||
const equalIndex = fragment.indexOf('='); |
||||
if (equalIndex > 0) { |
||||
const key = fragment.substring(0, equalIndex).toLowerCase(); |
||||
// Check if it's a known tracking parameter
|
||||
if (TRACKING_PARAMS.has(key)) { |
||||
return true; |
||||
} |
||||
// Check for tracking patterns
|
||||
if ( |
||||
key.startsWith('utm_') || |
||||
key.startsWith('tracking_') || |
||||
key.startsWith('track_') || |
||||
key.startsWith('click_') || |
||||
key.startsWith('affiliate_') || |
||||
key.startsWith('partner_') || |
||||
key.startsWith('ref_') || |
||||
key.startsWith('source_') |
||||
) { |
||||
return true; |
||||
} |
||||
} else { |
||||
// Fragment is just a key (e.g., "ref")
|
||||
const keyLower = fragment.toLowerCase(); |
||||
if (TRACKING_PARAMS.has(keyLower)) { |
||||
return true; |
||||
} |
||||
// Check for tracking patterns
|
||||
if ( |
||||
keyLower.startsWith('utm_') || |
||||
keyLower.startsWith('tracking_') || |
||||
keyLower.startsWith('track_') || |
||||
keyLower.startsWith('click_') || |
||||
keyLower.startsWith('affiliate_') || |
||||
keyLower.startsWith('partner_') || |
||||
keyLower.startsWith('ref_') || |
||||
keyLower.startsWith('source_') |
||||
) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Clean tracking parameters from a URL |
||||
* Removes tracking parameters from both query string and URL fragment (hash) |
||||
*
|
||||
* @param url - The URL to clean |
||||
* @returns The cleaned URL with tracking parameters removed |
||||
*/ |
||||
export function cleanTrackingParams(url: string): string { |
||||
if (!url || typeof url !== 'string') { |
||||
return url; |
||||
} |
||||
|
||||
try { |
||||
const urlObj = new URL(url); |
||||
|
||||
// Get all search parameters
|
||||
const params = urlObj.searchParams; |
||||
const keysToDelete: string[] = []; |
||||
|
||||
// Find all tracking parameters (case-insensitive)
|
||||
for (const [key, value] of params.entries()) { |
||||
const keyLower = key.toLowerCase(); |
||||
|
||||
// Check if this is a known tracking parameter
|
||||
if (TRACKING_PARAMS.has(keyLower)) { |
||||
keysToDelete.push(key); |
||||
continue; |
||||
} |
||||
|
||||
// Also check for common patterns (e.g., keys starting with tracking prefixes)
|
||||
if ( |
||||
keyLower.startsWith('utm_') || |
||||
keyLower.startsWith('tracking_') || |
||||
keyLower.startsWith('track_') || |
||||
keyLower.startsWith('click_') || |
||||
keyLower.startsWith('affiliate_') || |
||||
keyLower.startsWith('partner_') || |
||||
keyLower.startsWith('ref_') || |
||||
keyLower.startsWith('source_') || |
||||
keyLower.endsWith('_id') && ( |
||||
keyLower.includes('track') || |
||||
keyLower.includes('click') || |
||||
keyLower.includes('affiliate') || |
||||
keyLower.includes('partner') || |
||||
keyLower.includes('campaign') || |
||||
keyLower.includes('source') |
||||
) |
||||
) { |
||||
keysToDelete.push(key); |
||||
} |
||||
} |
||||
|
||||
// Remove all identified tracking parameters
|
||||
for (const key of keysToDelete) { |
||||
params.delete(key); |
||||
} |
||||
|
||||
// Reconstruct the URL
|
||||
urlObj.search = params.toString(); |
||||
|
||||
// Check and remove tracking parameters from URL fragment (hash)
|
||||
const fragment = urlObj.hash.substring(1); // Remove the # character
|
||||
if (fragment && isTrackingFragment(fragment)) { |
||||
urlObj.hash = ''; // Remove the fragment
|
||||
} |
||||
|
||||
return urlObj.toString(); |
||||
} catch (error) { |
||||
// If URL parsing fails, return the original URL
|
||||
console.warn('Failed to parse URL for cleaning:', url, error); |
||||
return url; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue