ultisuite-client/lib/native/deep-links.ts
R3D347HR4Y d6d18f911b
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
Lots of stuff and mobile app
2026-06-17 00:13:28 +02:00

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 ?? ""}`
}