@@ -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+
2236export 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 - z 0 - 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