/** * Deep Link Utilities * Handles deep linking functionality for FCM notifications and chat navigation */ import { Platform } from 'react-native'; import { ALLOWED_DOMAIN, NAVIGATION_DELAYS, URL_PATTERNS, DEEP_LINK_PREFIX } from './constants'; /** * Interface for notification data */ export interface NotificationData { channel_id?: string; conversation_id?: string; deep_link?: string; title?: string; body?: string; channel_name?: string; author_name?: string; notification_type?: string; } /** * Interface for navigation state */ export interface NavigationState { currentChannel: string | null; isNavigating: boolean; navigationAttempts: number; } /** * Deep Link Handler Class */ export class DeepLinkHandler { private updateWebViewUrl: (url: string) => void; private setIsNavigating: (navigating: boolean) => void; private setCurrentChannel: (channel: string | null) => void; private setNavigationAttempts: (attempts: number) => void; private navigationState: NavigationState; constructor( updateWebViewUrl: (url: string) => void, setIsNavigating: (navigating: boolean) => void, setCurrentChannel: (channel: string | null) => void, setNavigationAttempts: (attempts: number) => void, navigationState: NavigationState ) { this.updateWebViewUrl = updateWebViewUrl; this.setIsNavigating = setIsNavigating; this.setCurrentChannel = setCurrentChannel; this.setNavigationAttempts = setNavigationAttempts; this.navigationState = navigationState; } /** * Handle deep linking from notifications (killed app case) * @param data - Notification data containing channel information */ handleKilledAppDeepLink(data: NotificationData): void { console.log('🔗 Processing deep link from notification (killed app case):', data); const channelId = this.extractChannelId(data); if (!channelId) { console.warn('⚠️ No channel ID found in deep link data:', data); return; } // Minimal delay for killed app initialization setTimeout(() => { console.log('🔗 Setting initial WebView URL for killed app navigation to channel:', channelId); // Set navigation state this.setIsNavigating(true); this.setCurrentChannel(channelId); // Construct and set the target URL directly (works better for killed app) const chatUrl = this.buildChatUrl(channelId); console.log('🔗 Initial chat URL for killed app:', chatUrl); this.updateWebViewUrl(chatUrl); // Reset navigation state after shorter delay setTimeout(() => { this.setIsNavigating(false); }, NAVIGATION_DELAYS.KILLED_APP_RESET); }, NAVIGATION_DELAYS.KILLED_APP); } /** * Handle WebView URL update from notification (foreground/background) * @param data - Notification data containing channel information */ handleForegroundBackgroundDeepLink(data: NotificationData): void { console.log('🔗 Updating WebView URL from notification:', data); const channelId = this.extractChannelId(data); if (!channelId) { console.warn('⚠️ No channel ID found in notification data:', data); return; } // Check if we're already navigating to this channel if (this.navigationState.isNavigating && this.navigationState.currentChannel === channelId) { console.log('🔗 Already navigating to channel:', channelId); return; } console.log('🔗 Navigating to channel via WebView URL update:', channelId); // Set navigation state to prevent loops this.setIsNavigating(true); this.setNavigationAttempts(0); this.setCurrentChannel(channelId); // Step 1: First navigate to mail app (without specific channel) const mailAppUrl = `${ALLOWED_DOMAIN}/web${URL_PATTERNS.MAIL_APP}`; console.log('🔗 Step 1: Navigating to mail app:', mailAppUrl); this.updateWebViewUrl(mailAppUrl); // Step 2: After a minimal delay, navigate to specific channel setTimeout(() => { const chatUrl = this.buildChatUrl(channelId); console.log('🔗 Step 2: Navigating to specific channel:', chatUrl); this.updateWebViewUrl(chatUrl); // Reset navigation state after final navigation setTimeout(() => { this.setIsNavigating(false); }, NAVIGATION_DELAYS.NAVIGATION_RESET); }, NAVIGATION_DELAYS.TWO_STEP_NAVIGATION); console.log('✅ WebView navigation sequence started'); } /** * Navigate to specific channel (direct navigation for killed app) * @param channelId - Channel ID to navigate to */ navigateToChannel(channelId: string): void { if (!channelId) { console.warn('⚠️ No channel ID provided for navigation'); return; } // Check if we're already navigating to this channel if (this.navigationState.isNavigating && this.navigationState.currentChannel === channelId) { console.log('🔗 Already navigating to channel:', channelId); return; } console.log('🔗 Navigating to channel (killed app - direct navigation):', channelId); this.setCurrentChannel(channelId); this.setIsNavigating(true); this.setNavigationAttempts(0); // For killed app case, direct navigation works better const chatUrl = this.buildChatUrl(channelId); console.log('🔗 Chat URL (direct):', chatUrl); // Update WebView URL directly - this works well for killed app this.updateWebViewUrl(chatUrl); console.log('✅ WebView URL updated (direct navigation)'); // Reset navigation state after delay setTimeout(() => { this.setIsNavigating(false); }, NAVIGATION_DELAYS.KILLED_APP); } /** * Handle deep link URLs * @param deepLink - Deep link URL to process */ handleDeepLink(deepLink: string): void { console.log('🔗 Processing deep link:', deepLink); // Parse deep link: myapp://chat/{channel_id} if (deepLink.startsWith(DEEP_LINK_PREFIX)) { const channelId = deepLink.replace(DEEP_LINK_PREFIX, ''); console.log('🔗 Extracted channel ID:', channelId); this.navigateToChannel(channelId); } else { console.warn('⚠️ Unknown deep link format:', deepLink); } } /** * Extract channel ID from notification data * @param data - Notification data * @returns Channel ID or null if not found */ private extractChannelId(data: NotificationData): string | null { // Extract channel ID from different possible fields if (data.channel_id) { console.log('🔗 Found channel_id:', data.channel_id); return data.channel_id; } else if (data.conversation_id) { console.log('🔗 Found conversation_id:', data.conversation_id); return data.conversation_id; } else if (data.deep_link && data.deep_link.includes('chat/')) { const channelId = data.deep_link.replace(DEEP_LINK_PREFIX, ''); console.log('🔗 Extracted channel_id from deep_link:', channelId); return channelId; } return null; } /** * Build chat URL for specific channel * @param channelId - Channel ID * @returns Complete chat URL */ private buildChatUrl(channelId: string): string { return `${ALLOWED_DOMAIN}/web${URL_PATTERNS.MAIL_APP}${URL_PATTERNS.CHAT_CHANNEL(channelId)}`; } } /** * Create deep link handler instance */ export function createDeepLinkHandler( updateWebViewUrl: (url: string) => void, setIsNavigating: (navigating: boolean) => void, setCurrentChannel: (channel: string | null) => void, setNavigationAttempts: (attempts: number) => void, navigationState: NavigationState ): DeepLinkHandler { return new DeepLinkHandler( updateWebViewUrl, setIsNavigating, setCurrentChannel, setNavigationAttempts, navigationState ); }