UNPKG

@datalyr/react-native

Version:

Datalyr SDK for React Native & Expo - Server-side attribution tracking

489 lines (422 loc) 16.1 kB
import { Linking } from 'react-native'; import { Storage, STORAGE_KEYS, debugLog, errorLog, generateUUID } from './utils'; // Attribution parameter mapping const ATTRIBUTION_PARAMS = [ // Datalyr LYR tags (CRITICAL for your system!) 'lyr', 'datalyr', 'dl_tag', 'dl_campaign', // Facebook/Meta 'fbclid', 'fb_click_id', 'fb_action_ids', 'fb_action_types', // TikTok 'ttclid', 'tt_click_id', 'tiktok_click_id', // Google Ads 'gclid', 'wbraid', 'gbraid', 'dclid', // UTM Parameters (Standard) 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', // Partner tracking parameters 'partner_id', 'affiliate_id', 'referrer_id', 'source_id', // Other platforms 'twclid', 'li_click_id', 'msclkid', 'irclickid', // Custom attribution parameters 'click_id', 'campaign_id', 'ad_id', 'adset_id', 'creative_id', 'placement_id', 'keyword', 'matchtype', 'network', 'device', ]; export interface AttributionData { // Install Attribution install_time?: string; first_open_time?: string; // Datalyr LYR System (CRITICAL!) lyr?: string; // Main LYR tag for campaign tracking datalyr?: string; // Alternative LYR parameter dl_tag?: string; // Datalyr tag variant dl_campaign?: string; // Datalyr campaign identifier // Campaign Attribution (UTM) utm_source?: string; // Traffic source (facebook, google, tiktok) utm_medium?: string; // Marketing medium (cpc, social, email) utm_campaign?: string; // Campaign name utm_term?: string; // Paid keywords utm_content?: string; // Ad content/creative utm_id?: string; // Campaign ID utm_source_platform?: string; // Source platform details utm_creative_format?: string; // Creative format utm_marketing_tactic?: string; // Marketing tactic // Platform Click IDs fbclid?: string; // Facebook Click ID ttclid?: string; // TikTok Click ID gclid?: string; // Google Click ID twclid?: string; // Twitter Click ID li_click_id?: string; // LinkedIn Click ID msclkid?: string; // Microsoft Click ID // Partner & Affiliate Tracking partner_id?: string; // Partner identifier affiliate_id?: string; // Affiliate identifier referrer_id?: string; // Referrer identifier source_id?: string; // Source identifier // Campaign Details campaign_id?: string; // Campaign ID ad_id?: string; // Ad ID adset_id?: string; // Ad Set ID creative_id?: string; // Creative ID placement_id?: string; // Placement ID keyword?: string; // Keyword (for search) matchtype?: string; // Match type network?: string; // Ad network device?: string; // Device targeting // Standard Attribution Fields (mapped from UTM) campaign_source?: string; // Mapped from utm_source campaign_medium?: string; // Mapped from utm_medium campaign_name?: string; // Mapped from utm_campaign campaign_term?: string; // Mapped from utm_term campaign_content?: string; // Mapped from utm_content // Additional attribution data referrer?: string; // HTTP referrer deep_link_url?: string; // Full deep link URL install_referrer?: string; // Install referrer (Android) attribution_timestamp?: string; // When attribution was captured // Custom parameters [key: string]: any; } export class AttributionManager { private attributionData: AttributionData = {}; private isFirstLaunch: boolean = false; /** * Initialize attribution tracking */ async initialize(): Promise<void> { try { debugLog('Initializing attribution manager...'); // Check if this is first launch await this.checkFirstLaunch(); // Load existing attribution data await this.loadAttributionData(); // Set up deep link listener this.setupDeepLinkListener(); // Handle initial deep link (if app was opened from one) await this.handleInitialDeepLink(); debugLog('Attribution manager initialized', this.attributionData); } catch (error) { errorLog('Failed to initialize attribution manager:', error as Error); } } /** * Check if this is the first launch and track install */ private async checkFirstLaunch(): Promise<void> { try { const firstLaunchTime = await Storage.getItem<string>('@datalyr/first_launch_time'); if (!firstLaunchTime) { // This is the first launch this.isFirstLaunch = true; const installTime = new Date().toISOString(); this.attributionData.install_time = installTime; this.attributionData.first_open_time = installTime; await Storage.setItem('@datalyr/first_launch_time', installTime); debugLog('First launch detected, install time:', installTime); } else { this.isFirstLaunch = false; this.attributionData.install_time = firstLaunchTime; debugLog('Returning user, install time:', firstLaunchTime); } } catch (error) { errorLog('Error checking first launch:', error as Error); } } /** * Load persisted attribution data */ private async loadAttributionData(): Promise<void> { try { const savedData = await Storage.getItem<AttributionData>(STORAGE_KEYS.ATTRIBUTION_DATA); if (savedData) { this.attributionData = { ...this.attributionData, ...savedData }; debugLog('Loaded attribution data:', this.attributionData); } } catch (error) { errorLog('Error loading attribution data:', error as Error); } } /** * Save attribution data to storage */ private async saveAttributionData(): Promise<void> { try { await Storage.setItem(STORAGE_KEYS.ATTRIBUTION_DATA, this.attributionData); debugLog('Attribution data saved'); } catch (error) { errorLog('Error saving attribution data:', error as Error); } } /** * Set up deep link listener for when app is already running */ private setupDeepLinkListener(): void { try { Linking.addEventListener('url', this.handleDeepLink.bind(this)); debugLog('Deep link listener set up'); } catch (error) { errorLog('Error setting up deep link listener:', error as Error); } } /** * Handle initial deep link when app is opened from background */ private async handleInitialDeepLink(): Promise<void> { try { const initialUrl = await Linking.getInitialURL(); if (initialUrl) { await this.handleDeepLink({ url: initialUrl }); debugLog('Handled initial deep link:', initialUrl); } } catch (error) { errorLog('Error handling initial deep link:', error as Error); } } /** * Handle deep link URL and extract attribution parameters */ private async handleDeepLink(event: { url: string }): Promise<void> { try { const url = event.url; debugLog('Processing deep link:', url); // Store the deep link URL this.attributionData.deep_link_url = url; // Extract parameters from URL const urlParams = this.extractUrlParameters(url); // Process attribution parameters await this.processAttributionParameters(urlParams); // Save updated attribution data await this.saveAttributionData(); } catch (error) { errorLog('Error handling deep link:', error as Error); } } /** * Extract parameters from URL */ private extractUrlParameters(url: string): Record<string, string> { const params: Record<string, string> = {}; try { const urlObj = new URL(url); // Extract query parameters urlObj.searchParams.forEach((value, key) => { params[key.toLowerCase()] = value; }); // Also check fragment for parameters (some platforms use #) if (urlObj.hash) { const hashParams = new URLSearchParams(urlObj.hash.substring(1)); hashParams.forEach((value, key) => { params[key.toLowerCase()] = value; }); } debugLog('Extracted URL parameters:', params); return params; } catch (error) { errorLog('Error extracting URL parameters:', error as Error); return {}; } } /** * Process and store attribution parameters */ private async processAttributionParameters(params: Record<string, string>): Promise<void> { try { let hasNewAttribution = false; // Process each attribution parameter ATTRIBUTION_PARAMS.forEach(paramName => { const value = params[paramName.toLowerCase()]; if (value) { // Map to standard attribution fields switch (paramName.toLowerCase()) { // Datalyr LYR System (PRIORITY!) case 'lyr': case 'datalyr': case 'dl_tag': this.attributionData.lyr = value; debugLog(`🎯 LYR tag captured: ${value}`); break; case 'dl_campaign': this.attributionData.dl_campaign = value; break; // UTM Parameters (store both raw and mapped) case 'utm_source': this.attributionData.utm_source = value; this.attributionData.campaign_source = value; // Also map to standard field break; case 'utm_medium': this.attributionData.utm_medium = value; this.attributionData.campaign_medium = value; break; case 'utm_campaign': this.attributionData.utm_campaign = value; this.attributionData.campaign_name = value; break; case 'utm_term': this.attributionData.utm_term = value; this.attributionData.campaign_term = value; break; case 'utm_content': this.attributionData.utm_content = value; this.attributionData.campaign_content = value; break; case 'utm_id': this.attributionData.utm_id = value; break; case 'utm_source_platform': this.attributionData.utm_source_platform = value; break; case 'utm_creative_format': this.attributionData.utm_creative_format = value; break; case 'utm_marketing_tactic': this.attributionData.utm_marketing_tactic = value; break; // Platform Click IDs case 'fbclid': case 'fb_click_id': this.attributionData.fbclid = value; break; case 'ttclid': case 'tt_click_id': case 'tiktok_click_id': this.attributionData.ttclid = value; break; case 'gclid': this.attributionData.gclid = value; break; case 'twclid': this.attributionData.twclid = value; break; case 'li_click_id': this.attributionData.li_click_id = value; break; case 'msclkid': this.attributionData.msclkid = value; break; // Partner & Affiliate case 'partner_id': this.attributionData.partner_id = value; break; case 'affiliate_id': this.attributionData.affiliate_id = value; break; case 'referrer_id': this.attributionData.referrer_id = value; break; case 'source_id': this.attributionData.source_id = value; break; // Campaign Details case 'campaign_id': this.attributionData.campaign_id = value; break; case 'ad_id': this.attributionData.ad_id = value; break; case 'adset_id': this.attributionData.adset_id = value; break; case 'creative_id': this.attributionData.creative_id = value; break; case 'placement_id': this.attributionData.placement_id = value; break; case 'keyword': this.attributionData.keyword = value; break; case 'matchtype': this.attributionData.matchtype = value; break; case 'network': this.attributionData.network = value; break; case 'device': this.attributionData.device = value; break; default: // Store other parameters as-is (for custom tracking) this.attributionData[paramName] = value; } hasNewAttribution = true; debugLog(`Attribution parameter captured: ${paramName} = ${value}`); } }); if (hasNewAttribution) { // Store timestamp of when attribution was captured this.attributionData.attribution_timestamp = new Date().toISOString(); debugLog('Updated attribution data:', this.attributionData); } } catch (error) { errorLog('Error processing attribution parameters:', error as Error); } } /** * Get current attribution data */ getAttributionData(): AttributionData { return { ...this.attributionData }; } /** * Check if this is a first launch (install) */ isInstall(): boolean { return this.isFirstLaunch; } /** * Track install event with attribution data */ async trackInstall(): Promise<AttributionData> { if (this.isFirstLaunch) { debugLog('Tracking app install with attribution:', this.attributionData); // Add install-specific data const installData: AttributionData = { ...this.attributionData, event_type: 'install', install_id: generateUUID(), }; return installData; } return this.attributionData; } /** * Set custom attribution data */ async setAttributionData(data: Partial<AttributionData>): Promise<void> { this.attributionData = { ...this.attributionData, ...data }; await this.saveAttributionData(); debugLog('Custom attribution data set:', data); } /** * Clear attribution data (for testing) */ async clearAttributionData(): Promise<void> { this.attributionData = {}; await Storage.removeItem(STORAGE_KEYS.ATTRIBUTION_DATA); await Storage.removeItem('@datalyr/first_launch_time'); debugLog('Attribution data cleared'); } /** * Get attribution summary for debugging */ getAttributionSummary(): { hasAttribution: boolean; isInstall: boolean; source: string; campaign: string; clickIds: string[]; } { const clickIds = []; if (this.attributionData.fbclid) clickIds.push(`fbclid: ${this.attributionData.fbclid}`); if (this.attributionData.ttclid) clickIds.push(`ttclid: ${this.attributionData.ttclid}`); if (this.attributionData.gclid) clickIds.push(`gclid: ${this.attributionData.gclid}`); return { hasAttribution: Object.keys(this.attributionData).length > 2, // More than just install times isInstall: this.isFirstLaunch, source: this.attributionData.campaign_source || 'unknown', campaign: this.attributionData.campaign_name || 'unknown', clickIds, }; } } // Export singleton instance export const attributionManager = new AttributionManager();