243 lines
7.7 KiB
TypeScript
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
|
|
);
|
|
}
|