T4B_Chat/utilities/deepLinkUtils.ts

243 lines
7.7 KiB
TypeScript

/**
* 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
);
}