54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
/**
|
|
* Map an inbound deep link (custom scheme or universal/app link) to an in-app
|
|
* route for the Next router.
|
|
*
|
|
* Conventions:
|
|
* - Custom scheme: `ulti<app>://go/<route>` -> `/<route>`
|
|
* `ulti<app>://<seg>/<rest>` -> `/<seg>/<rest>`
|
|
* - Universal link: `https://<host>/app/<app>/<rest>` -> `/<rest>`
|
|
*
|
|
* The OAuth callback (`ulti<app>://oauth/callback`) is consumed by the native
|
|
* auth flow and intentionally returns null here.
|
|
*/
|
|
export function routeForDeepLink(rawUrl: string): string | null {
|
|
let u: URL
|
|
try {
|
|
u = new URL(rawUrl)
|
|
} catch {
|
|
return null
|
|
}
|
|
|
|
// Custom scheme (e.g. ultimail:, ultidrive:)
|
|
if (u.protocol.startsWith("ulti") && u.protocol.endsWith(":")) {
|
|
if (u.hostname === "oauth") return null // handled by native-auth
|
|
const host = u.hostname
|
|
const path = u.pathname.replace(/^\/+/, "")
|
|
let route: string
|
|
if (host === "go" || host === "") {
|
|
route = `/${path}`
|
|
} else {
|
|
route = `/${host}${path ? `/${path}` : ""}`
|
|
}
|
|
return normalize(route, u.search)
|
|
}
|
|
|
|
// Universal / App link: https://host/app/<app>/<rest>
|
|
if (u.protocol === "https:" || u.protocol === "http:") {
|
|
const parts = u.pathname.split("/").filter(Boolean)
|
|
const appIdx = parts.indexOf("app")
|
|
if (appIdx >= 0 && parts.length > appIdx + 1) {
|
|
const rest = parts.slice(appIdx + 2).join("/")
|
|
return normalize(`/${rest}`, u.search)
|
|
}
|
|
return normalize(u.pathname, u.search)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function normalize(route: string, search: string): string {
|
|
const cleaned = route.replace(/\/{2,}/g, "/")
|
|
const path = cleaned === "/" || cleaned === "" ? "/" : cleaned.replace(/\/$/, "")
|
|
return `${path}${search ?? ""}`
|
|
}
|