1- import { useEffect , useState } from 'react' ;
1+ import { useCallback , useEffect , useState } from 'react' ;
22import { Linking } from 'react-native' ;
33import { getDeferredLink } from '../api/getDeferredLink' ;
4+ import { resolveShortLink } from '../api/resolveShortLink' ;
45import type { DetourContextType , LinkType , RequiredConfig } from '../types' ;
56import { checkIsFirstEntrance , markFirstEntrance } from '../utils/appEntrance' ;
67import {
@@ -25,63 +26,84 @@ export const useDetour = ({
2526 const [ linkType , setLinkType ] = useState < LinkType | null > ( null ) ;
2627
2728 // Unified helper for parsing any link (API or Native)
28- const processLink = ( rawLink : string , typeOverride ?: LinkType ) => {
29- if ( isInfrastructureUrl ( rawLink ) ) {
30- console . log ( '🔗[Detour] Ignored infrastructure URL:' , rawLink ) ;
31- return ;
32- }
33-
34- const isUrl = rawLink . includes ( '://' ) || rawLink . startsWith ( '//' ) ;
35-
36- // Early return for standard relative/absolute path strings
37- if ( ! isUrl ) {
38- const path = rawLink . startsWith ( '/' ) ? rawLink : `/${ rawLink } ` ;
39- setLinkUrl ( path ) ;
40- setRoute ( path ) ;
41- setLinkType ( typeOverride ?? 'scheme' ) ;
42- return ;
43- }
44-
45- try {
46- const urlObj = new URL ( rawLink ) ;
47- setLinkUrl ( urlObj ) ;
48-
49- // Determine if it's a web URL (requiring app hash stripping)
50- // or a custom deep link scheme
51- const isWebUrl =
52- urlObj . protocol === 'http:' ||
53- urlObj . protocol === 'https:' ||
54- rawLink . startsWith ( '//' ) ;
55-
56- const detectedType : LinkType = isWebUrl ? 'verified' : 'scheme' ;
57- setLinkType ( typeOverride ?? detectedType ) ;
58-
59- if ( isWebUrl ) {
60- const pathNameWithoutAppHash = getRestOfPath ( urlObj . pathname ) ;
61- setRoute ( pathNameWithoutAppHash + ( urlObj . search ?? '' ) ) ;
62- } else {
63- // custom schemes
64- const deepLinkRoute = getRouteFromDeepLink ( urlObj ) ;
65- setRoute ( deepLinkRoute ) ;
29+ const processLink = useCallback (
30+ async ( rawLink : string , typeOverride ?: LinkType ) => {
31+ if ( isInfrastructureUrl ( rawLink ) ) {
32+ console . log ( '🔗[Detour] Ignored infrastructure URL:' , rawLink ) ;
33+ return ;
6634 }
67- } catch ( e ) {
68- console . warn (
69- '🔗[Detour] Failed to parse URL object, falling back to string' ,
70- e
71- ) ;
72- setLinkUrl ( rawLink ) ;
73- setRoute ( rawLink ) ;
74- setLinkType ( typeOverride ?? 'scheme' ) ;
75- }
76- } ;
35+
36+ const isUrl = rawLink . includes ( '://' ) || rawLink . startsWith ( '//' ) ;
37+
38+ // Early return for standard relative/absolute path strings
39+ if ( ! isUrl ) {
40+ const path = rawLink . startsWith ( '/' ) ? rawLink : `/${ rawLink } ` ;
41+ setLinkUrl ( path ) ;
42+ setRoute ( path ) ;
43+ return ;
44+ }
45+
46+ try {
47+ const urlObj = new URL ( rawLink ) ;
48+ setLinkUrl ( urlObj ) ;
49+
50+ // Determine if it's a web URL (requiring app hash stripping)
51+ // or a custom deep link scheme
52+ const isWebUrl =
53+ urlObj . protocol === 'http:' ||
54+ urlObj . protocol === 'https:' ||
55+ rawLink . startsWith ( '//' ) ;
56+
57+ const detectedType : LinkType = isWebUrl ? 'verified' : 'scheme' ;
58+ setLinkType ( typeOverride ?? detectedType ) ;
59+
60+ if ( isWebUrl ) {
61+ const pathSegments = urlObj . pathname . split ( '/' ) . filter ( Boolean ) ;
62+ const isSingleSegmentPath =
63+ pathSegments . length === 1 &&
64+ pathSegments [ 0 ] &&
65+ pathSegments [ 0 ] . length > 0 ;
66+
67+ // Attempt short link resolution for single-segment paths
68+ if ( isSingleSegmentPath ) {
69+ const resolved = await resolveShortLink ( {
70+ API_KEY ,
71+ appID,
72+ url : rawLink ,
73+ } ) ;
74+ if ( resolved ?. link ) {
75+ await processLink ( resolved . link ) ;
76+ return ;
77+ }
78+ console . log ( '🔗[Detour] Not resolved, using original URL' ) ;
79+ }
80+ const pathNameWithoutAppHash = getRestOfPath ( urlObj . pathname ) ;
81+ setRoute ( pathNameWithoutAppHash + ( urlObj . search ?? '' ) ) ;
82+ } else {
83+ // custom schemes
84+ const deepLinkRoute = getRouteFromDeepLink ( urlObj ) ;
85+ setRoute ( deepLinkRoute ) ;
86+ }
87+ } catch ( e ) {
88+ console . warn (
89+ '🔗[Detour] Failed to parse URL object, falling back to string' ,
90+ e
91+ ) ;
92+ setLinkUrl ( rawLink ) ;
93+ setRoute ( rawLink ) ;
94+ setLinkType ( typeOverride ?? 'scheme' ) ;
95+ }
96+ } ,
97+ [ API_KEY , appID ]
98+ ) ;
7799
78100 // 1. Listen for Universal Links (Running App)
79101 useEffect ( ( ) => {
80102 const subscription = Linking . addEventListener ( 'url' , ( { url } ) => {
81103 processLink ( url ) ;
82104 } ) ;
83105 return ( ) => subscription . remove ( ) ;
84- } , [ ] ) ;
106+ } , [ processLink ] ) ;
85107
86108 // 2. Handle Cold Start (Universal vs Deferred)
87109 useEffect ( ( ) => {
@@ -99,7 +121,7 @@ export const useDetour = ({
99121 const initialUrl = await Linking . getInitialURL ( ) ;
100122 if ( initialUrl && ! isInfrastructureUrl ( initialUrl ) ) {
101123 await markFirstEntrance ( storage ) ;
102- processLink ( initialUrl ) ;
124+ await processLink ( initialUrl ) ;
103125 return ;
104126 }
105127
@@ -116,15 +138,15 @@ export const useDetour = ({
116138 } ) ;
117139
118140 if ( apiLink ) {
119- processLink ( apiLink , 'deferred' ) ;
141+ await processLink ( apiLink , 'deferred' ) ;
120142 }
121143 } catch ( error ) {
122144 console . error ( '🔗[Detour:ERROR]' , error ) ;
123145 } finally {
124146 setProcessed ( true ) ;
125147 }
126148 } ) ( ) ;
127- } , [ API_KEY , appID , shouldUseClipboard , storage ] ) ;
149+ } , [ API_KEY , appID , processLink , shouldUseClipboard , storage ] ) ;
128150
129151 return {
130152 isLinkProcessed : processed ,
0 commit comments