/** * Deep Link Router — TypeScript * Handles routing from push notification deep links to app screens */ export interface DeepLinkRoute { screen: string; params?: Record; } export type DeepLinkHandler = (route: DeepLinkRoute) => void; /** * Deep Link Router class */ export class DeepLinkRouter { private handlers = new Map(); private fallbackHandler?: DeepLinkHandler; /** * Register a handler for a specific screen */ register(screen: string, handler: DeepLinkHandler): void { this.handlers.set(screen, handler); } /** * Set a fallback handler for unregistered screens */ setFallback(handler: DeepLinkHandler): void { this.fallbackHandler = handler; } /** * Parse a deep link URL and extract route */ parseDeepLink(url: string): DeepLinkRoute | null { try { const urlObj = new URL(url); // Handle app-specific URLs: myapp://screen/params if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') { const pathParts = urlObj.pathname.split('/').filter(Boolean); const screen = pathParts[0] || 'home'; const params: Record = {}; // Parse query params urlObj.searchParams.forEach((value, key) => { params[key] = value; }); return { screen, params }; } // Handle web URLs with deep link params const deepLinkParam = urlObj.searchParams.get('dl'); if (deepLinkParam) { return this.parseDeepLink(deepLinkParam); } // Handle path-based routing: /screen/params const pathParts = urlObj.pathname.split('/').filter(Boolean); if (pathParts.length > 0) { const screen = pathParts[0]; const params: Record = {}; urlObj.searchParams.forEach((value, key) => { params[key] = value; }); return { screen, params }; } return null; } catch { return null; } } /** * Handle a deep link route */ handle(route: DeepLinkRoute): boolean { const handler = this.handlers.get(route.screen); if (handler) { handler(route); return true; } if (this.fallbackHandler) { this.fallbackHandler(route); return true; } console.warn(`[DeepLink] No handler for screen: ${route.screen}`); return false; } /** * Process a deep link URL end-to-end */ process(url: string): boolean { const route = this.parseDeepLink(url); if (!route) { console.warn(`[DeepLink] Failed to parse: ${url}`); return false; } return this.handle(route); } } /** * Create a broadcast deep link URL */ export function createBroadcastDeepLink( baseUrl: string, screen: string, params?: Record, broadcastId?: string ): string { const url = new URL(baseUrl); url.pathname = `/${screen}`; if (params) { Object.entries(params).forEach(([key, value]) => { url.searchParams.set(key, value); }); } if (broadcastId) { url.searchParams.set('broadcastId', broadcastId); } return url.toString(); } /** * Common deep link screens for broadcast/survey flows */ export const DeepLinkScreens = { // Broadcasts BROADCAST_DETAIL: 'broadcast', ANNOUNCEMENTS: 'announcements', // Surveys SURVEY: 'survey', SURVEY_LIST: 'surveys', // Product-specific (examples) SETTINGS: 'settings', PROFILE: 'profile', UPGRADE: 'upgrade', SUPPORT: 'support', // Fallback HOME: 'home', } as const; // Singleton instance for app-wide use export const deepLinkRouter = new DeepLinkRouter();