import { Linking } from 'react-native'; import { Logger } from './logger'; export enum DeepLinkType { CUSTOM_SCHEME = 'custom_scheme', // myapp:// UNIVERSAL_LINK = 'universal_link', // https:// (iOS Universal Links) APP_LINK = 'app_link', // https:// (Android App Links) HTTP_LINK = 'http_link', // http:// } /** * Helper class to automatically detect and handle deeplinks * * Supports multiple link types: * 1. Custom URL Schemes: myapp://product/123 * 2. Universal Links (iOS): https://my-site.com/product/123 * 3. App Links (Android): https://my-site.com/product/123 * 4. HTTP Links: http://my-site.com/product/123 * * Common use cases: * - Ad clicks (Facebook, Google, TikTok ads with http/https URLs) * - Push notification deep links * - Email marketing links * - SMS campaign links * - QR codes */ export class DeepLinkHelper { private static listener: any = null; private static onDeepLinkCallback: | ((url: string, linkType: DeepLinkType) => void) | null = null; /** * Determine the type of deeplink * @param url The URL to analyze * @returns The type of deeplink */ static getLinkType(url: string): DeepLinkType { try { if (url.startsWith('https://')) { // Universal Links (iOS) and App Links (Android) both use HTTPS return DeepLinkType.UNIVERSAL_LINK; } else if (url.startsWith('http://')) { return DeepLinkType.HTTP_LINK; } else { // Custom URL scheme (e.g., myapp://) return DeepLinkType.CUSTOM_SCHEME; } } catch (error) { Logger.logError('Error determining link type:', error); return DeepLinkType.CUSTOM_SCHEME; } } /** * Initialize deeplink detection * - Checks if app was opened with initial URL (custom scheme, universal link, or HTTP link) * - Sets up listener for deeplinks while app is running * @param callback Optional callback to be notified when deeplink is detected * @returns The initial URL if app was opened with deeplink, null otherwise */ static async initialize( callback?: (url: string, linkType: DeepLinkType) => void ): Promise { try { // Store callback if provided if (callback) { this.onDeepLinkCallback = callback; } // Check if app was opened with a deeplink // This works for: // - Custom schemes: myapp://product/123 // - Universal Links (iOS): https://my-site.com/product/123 // - App Links (Android): https://my-site.com/product/123 // - HTTP links: http://my-site.com/product/123 const initialUrl = await Linking.getInitialURL(); if (initialUrl) { const linkType = this.getLinkType(initialUrl); Logger.logDbg(`App opened with initial ${linkType}:`, initialUrl); this.handleDeepLink(initialUrl); } // ALWAYS set up listener for deeplinks while app is running // This ensures we can detect deeplinks even if app was opened with initial deeplink this.setupListener(); return initialUrl; } catch (error) { Logger.logError('Error initializing deeplink detection:', error); return null; } } /** * Set up listener for deeplinks received while app is running * Works for all link types: custom schemes, universal links, app links, HTTP links */ private static setupListener(): void { // Remove existing listener if any if (this.listener) { this.listener.remove(); } // Listen for deeplinks while app is running (e.g., user clicks link while app is open) this.listener = Linking.addEventListener('url', (event) => { const linkType = this.getLinkType(event.url); Logger.logDbg(`Deeplink received (${linkType}):`, event.url); this.handleDeepLink(event.url); }); Logger.logDbg( 'Deeplink listener set up (supports custom schemes, universal/app links, HTTP links)' ); } /** * Handle deeplink URL - notify callback if registered * @param url The deeplink URL */ private static handleDeepLink(url: string): void { if (this.onDeepLinkCallback) { const linkType = this.getLinkType(url); this.onDeepLinkCallback(url, linkType); } } /** * Remove deeplink listener and clear callback */ static cleanup(): void { if (this.listener) { this.listener.remove(); this.listener = null; } this.onDeepLinkCallback = null; Logger.logDbg('Deeplink listener and callback cleared'); } /** * Parse UTM parameters and other campaign data from URL * @param url The deeplink URL * @returns Object containing campaign parameters */ static parseCampaignParams(url: string): Record { try { const urlObj = new URL(url); const params: Record = {}; // Common campaign parameters const campaignParams = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'gclid', // Google Click ID 'fbclid', // Facebook Click ID 'ttclid', // TikTok Click ID 'campaign_id', 'ad_id', 'adset_id', 'creative_id', ]; campaignParams.forEach((param) => { const value = urlObj.searchParams.get(param); if (value) { params[param] = value; } }); return params; } catch (error) { Logger.logError('Error parsing campaign params from URL:', error); return {}; } } }