import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; /** * Forwards OS-level deep-link opens into the React Router navigation stack. * * Uses the global window.Capacitor object to avoid a hard npm dependency on * @capacitor/app (which is installed on the native build machine but not in * the web/CI build environment). * * When the OS launches the app via a URL (Amber NIP-55 callback, custom scheme, * or universal link), Capacitor fires an `appUrlOpen` event. This component * listens for it and navigates to the correct in-app route. * * Amber deep-link example: * org.skatespots.app://amber-callback?event= * → navigates to /amber-callback?event= */ // Type for the global Capacitor object injected by the native runtime interface CapacitorGlobal { isNativePlatform?: () => boolean; Plugins?: { App?: { addListener: ( event: string, handler: (data: { url: string }) => void ) => Promise<{ remove: () => void }>; }; }; } export function DeepLinkHandler() { const navigate = useNavigate(); useEffect(() => { const cap = (window as unknown as { Capacitor?: CapacitorGlobal }).Capacitor; if (!cap?.isNativePlatform?.()) return; const AppPlugin = cap?.Plugins?.App; if (!AppPlugin) return; let removeListener: (() => void) | undefined; AppPlugin.addListener('appUrlOpen', (event) => { try { const url = new URL(event.url); // Pre-capture Amber callback event param into sessionStorage so // AmberCallbackPage can find it after React Router renders the route. const eventParam = url.searchParams.get('event'); if (eventParam) { try { sessionStorage.setItem('amber_callback_event', eventParam); sessionStorage.setItem('amber_callback_url', event.url); sessionStorage.setItem('amber_callback_timestamp', Date.now().toString()); } catch { /* ignore storage errors */ } } // Extract path + query + fragment from the deep link URL const path = url.pathname + url.search + url.hash; if (path && path !== '/') { navigate(path, { replace: true }); } } catch { // Invalid URL — ignore } }).then((listener) => { removeListener = () => listener.remove(); }).catch(() => { // Plugin not available — ignore }); return () => { removeListener?.(); }; }, [navigate]); return null; }