forked from ZyntariHQ/Invoisio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseDeepLinks.ts
More file actions
125 lines (111 loc) · 3.54 KB
/
Copy pathuseDeepLinks.ts
File metadata and controls
125 lines (111 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { useCallback, useEffect, useRef, useState } from "react";
import { Linking, AppState, AppStateStatus } from "react-native";
import { useRouter } from "expo-router";
import {
parseDeepLink,
navigateToDeepLink,
getInitialUrl,
} from "../lib/deep-links";
import { useAuthStore } from "./use-auth-store";
/**
* Hook to handle deep links and universal links
*
* Supports:
* - Cold start deep links (app not running)
* - Background state deep links (app running in background)
* - Foreground state deep links (app actively open)
*/
export function useDeepLinks() {
const router = useRouter();
const { isAuthenticated, isLoading } = useAuthStore();
const [pendingDeepLink, setPendingDeepLink] = useState<string | null>(null);
const handledInitialUrl = useRef(false);
// Handle deep link navigation
const handleDeepLink = useCallback(
(url: string) => {
console.log("Deep link received:", url);
const data = parseDeepLink(url);
if (!data) {
console.warn("Invalid deep link:", url);
return;
}
// Wait for auth if needed
// Payment and receipt entry points are intentionally public so payers do
// not need a merchant account. Merchant-only links resume after sign-in.
const requiresAuth =
data.type === "invoice" || data.type === "create-invoice";
if (!isAuthenticated && requiresAuth) {
setPendingDeepLink(url);
return;
}
const success = navigateToDeepLink(data, router);
if (!success) {
console.warn("Failed to navigate to deep link:", data);
}
},
[isAuthenticated, router],
);
// Handle initial URL on cold start
useEffect(() => {
const handleInitialUrl = async () => {
if (handledInitialUrl.current) return;
handledInitialUrl.current = true;
const url = await getInitialUrl();
if (url) {
handleDeepLink(url);
}
};
void handleInitialUrl();
}, [handleDeepLink]);
// Handle foreground deep links
useEffect(() => {
const subscription = Linking.addEventListener("url", ({ url }) => {
handleDeepLink(url);
});
return () => {
subscription.remove();
};
}, [handleDeepLink]);
// Handle app state changes for background deep links
useEffect(() => {
const subscription = AppState.addEventListener(
"change",
(nextAppState: AppStateStatus) => {
// When app comes to foreground, check for pending deep links
if (nextAppState === "active" && pendingDeepLink && isAuthenticated) {
const data = parseDeepLink(pendingDeepLink);
if (data) {
navigateToDeepLink(data, router);
setPendingDeepLink(null);
}
}
},
);
return () => {
subscription.remove();
};
}, [pendingDeepLink, isAuthenticated, router]);
// Handle authentication becoming available
useEffect(() => {
if (isAuthenticated && pendingDeepLink) {
const data = parseDeepLink(pendingDeepLink);
if (data) {
navigateToDeepLink(data, router);
setPendingDeepLink(null);
}
}
}, [isAuthenticated, pendingDeepLink, router]);
// Once session restoration finishes, send unauthenticated merchants to
// sign-in while retaining the original destination for post-auth resume.
useEffect(() => {
if (!isLoading && !isAuthenticated && pendingDeepLink) {
router.push("/login");
}
}, [isAuthenticated, isLoading, pendingDeepLink, router]);
return {
pendingDeepLink,
clearPending: () => {
setPendingDeepLink(null);
},
};
}