Skip to content

Commit db70ae9

Browse files
committed
feat(domain): improve domain handling with app url and canonical host
Add appUrlHref helper to handle NEXT_PUBLIC_APP_URL environment variable Use canonicalBaseDomain when available for more accurate domain resolution Simplify tenant selection href logic with appUrlHref fallback
1 parent ff2884d commit db70ae9

File tree

1 file changed

+38
-36
lines changed

1 file changed

+38
-36
lines changed

src/lib/domain.ts

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,26 @@ const appHost = (() => {
1919
}
2020
})();
2121

22+
const appUrlHref = (() => {
23+
const appUrl = process.env.NEXT_PUBLIC_APP_URL;
24+
if (!appUrl) return null;
25+
try {
26+
const parsed = new URL(appUrl);
27+
parsed.pathname = "";
28+
parsed.search = "";
29+
parsed.hash = "";
30+
return parsed.toString().replace(/\/$/, "");
31+
} catch {
32+
return null;
33+
}
34+
})();
35+
2236
export const getDomain = async () => {
2337
const headers = await getHeaders();
24-
const host = headers.get("host");
38+
const forwardedHost = headers.get("x-forwarded-host");
39+
const hostHeader = headers.get("host");
40+
// x-forwarded-host can be a comma-separated list; pick the first value.
41+
const host = forwardedHost?.split(",")[0]?.trim() || hostHeader;
2542

2643
let hostname: string | null = null;
2744
let port = "";
@@ -40,42 +57,24 @@ export const getDomain = async () => {
4057

4158
let baseDomain = host ?? "";
4259
let subdomain: string | null = null;
60+
const appRootLabel = appHost?.hostname?.split(".")[0] ?? null;
4361

4462
if (hostname) {
45-
if (appHost) {
46-
const { hostname: canonicalHost, port: canonicalPort } = appHost;
63+
const segments = normalizedHostname.split(".").filter(Boolean);
64+
const tenantCandidate = segments.length > 2 ? segments[0] : null;
65+
const looksLikeTenant =
66+
tenantCandidate !== null &&
67+
tenantCandidate !== "www" &&
68+
tenantCandidate !== appRootLabel &&
69+
tenantCandidate.length <= 20 &&
70+
/^[a-z0-9-]+$/.test(tenantCandidate) &&
71+
!tenantCandidate.includes("promisetracker");
4772

48-
if (normalizedHostname === canonicalHost) {
49-
// Exact match with the configured host (e.g. promisetracker-v2.dev.codeforafrica.org) — keep the root domain.
50-
baseDomain = `${hostname}${port}`;
51-
} else if (normalizedHostname.endsWith(`.${canonicalHost}`)) {
52-
// Host looks like `<tenant>.<canonical>` (e.g. ken.promisetracker-v2.dev.codeforafrica.org) — capture the tenant
53-
// and force the base domain to the canonical host so generated links stay stable.
54-
const suffixLength = canonicalHost.length + 1;
55-
const prefix = normalizedHostname.slice(0, -suffixLength);
56-
const segments = prefix.split(".").filter(Boolean);
57-
subdomain = segments.pop() ?? null;
58-
const effectivePort = canonicalPort || port;
59-
baseDomain = `${canonicalHost}${effectivePort}`;
60-
} else {
61-
// Host does not match our configured base (e.g. unexpected.example.com) — fall back to the raw host.
62-
baseDomain = `${hostname}${port}`;
63-
const segments = normalizedHostname.split(".");
64-
if (segments.length > 2) {
65-
// Treat the leading label as the tenant when there are 3+ segments.
66-
subdomain = segments.shift() ?? null;
67-
baseDomain = `${segments.join(".")}${port}`;
68-
}
69-
}
73+
if (looksLikeTenant) {
74+
subdomain = tenantCandidate;
75+
baseDomain = `${segments.slice(1).join(".")}${port || appHost?.port || ""}`;
7076
} else {
71-
// No configured app host — strip the leading label when the host is a
72-
// subdomain (e.g. api.localhost) so we still surface a link back to the root tenant selector.
73-
baseDomain = `${hostname}${port}`;
74-
const segments = normalizedHostname.split(".");
75-
if (segments.length > 2) {
76-
subdomain = segments.shift() ?? null;
77-
baseDomain = `${segments.join(".")}${port}`;
78-
}
77+
baseDomain = `${hostname}${port || appHost?.port || ""}`;
7978
}
8079
}
8180

@@ -90,9 +89,12 @@ export const getDomain = async () => {
9089
normalizedBaseDomain === "0.0.0.0";
9190

9291
const protocol = isLocalEnvironment ? "http" : "https";
93-
const tenantSelectionHref = baseDomain
94-
? `${protocol}://${baseDomain}`
95-
: "/";
92+
const tenantSelectionHref =
93+
baseDomain && !isLocalhost
94+
? `${protocol}://${baseDomain}`
95+
: baseDomain
96+
? `${protocol}://${baseDomain}`
97+
: appUrlHref ?? "/";
9698

9799
return {
98100
isLocalhost,

0 commit comments

Comments
 (0)