forked from ZyntariHQ/Invoisio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeep-links.ts
More file actions
107 lines (93 loc) · 2.92 KB
/
Copy pathdeep-links.ts
File metadata and controls
107 lines (93 loc) · 2.92 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
import { Linking } from "react-native";
export type DeepLinkType =
| "invoice"
| "payment"
| "receipt"
| "dashboard"
| "create-invoice";
export interface DeepLinkData {
type: DeepLinkType;
id?: string;
params?: Record<string, string>;
}
export interface DeepLinkRouter {
push(href: string): void;
}
const WEB_HOSTS = new Set(["invoisio.com"]);
const ID_LINK_TYPES = new Set<DeepLinkType>(["invoice", "payment", "receipt"]);
const LINK_TYPES = new Set<DeepLinkType>([
...ID_LINK_TYPES,
"dashboard",
"create-invoice",
]);
function normaliseType(value: string): DeepLinkType | null {
const type = value === "pay" ? "payment" : value;
return LINK_TYPES.has(type as DeepLinkType) ? (type as DeepLinkType) : null;
}
/** Parse only links owned by Invoisio into a validated navigation target. */
export function parseDeepLink(url: string): DeepLinkData | null {
try {
const parsedUrl = new URL(url);
const isAppLink = parsedUrl.protocol === "invoisio:";
const isWebLink =
parsedUrl.protocol === "https:" &&
WEB_HOSTS.has(parsedUrl.hostname.toLowerCase());
if (!isAppLink && !isWebLink) return null;
// URL treats the first component after `invoisio://` as the hostname.
const segments = isAppLink
? [parsedUrl.hostname, ...parsedUrl.pathname.split("/")].filter(Boolean)
: parsedUrl.pathname.split("/").filter(Boolean);
const type = normaliseType(segments[0] ?? (isWebLink ? "dashboard" : ""));
if (!type || segments.length > 2) return null;
const rawId = segments[1];
const id = rawId ? decodeURIComponent(rawId) : undefined;
if (ID_LINK_TYPES.has(type) && !id) return null;
if (!ID_LINK_TYPES.has(type) && id) return null;
const params: Record<string, string> = {};
parsedUrl.searchParams.forEach((value, key) => {
params[key] = value;
});
return {
type,
...(id !== undefined && { id }),
...(Object.keys(params).length > 0 && { params }),
};
} catch {
return null;
}
}
/** Navigate a validated deep link to its app screen. */
export function navigateToDeepLink(
data: DeepLinkData,
router: DeepLinkRouter,
): boolean {
const { type, id } = data;
switch (type) {
case "invoice":
if (!id) return false;
router.push(`/invoices/${encodeURIComponent(id)}`);
return true;
case "payment":
if (!id) return false;
router.push(`/payments/${encodeURIComponent(id)}`);
return true;
case "receipt":
if (!id) return false;
router.push(`/receipts/${encodeURIComponent(id)}`);
return true;
case "dashboard":
router.push("/dashboard");
return true;
case "create-invoice":
router.push("/create-invoice");
return true;
}
}
export async function getInitialUrl(): Promise<string | null> {
try {
return await Linking.getInitialURL();
} catch (error) {
console.error("Failed to get initial URL:", error);
return null;
}
}