107 lines
4.6 KiB
TypeScript
107 lines
4.6 KiB
TypeScript
import { getDomainRoutingConfig, loadRequiredWebConfig, type DomainRoutingEnv, type EnvRecord } from "@toolctrl/config";
|
|
import { getCanonicalHostRedirectUrl, getHostRoutingContextFromRequest, getRequestRoutingHost } from "./app/HostRouting.server";
|
|
import { isAbsoluteWebHostPath, pathMatchesRoutePrefix, protectedWebRouteExactPaths, protectedWebRoutePrefixes } from "./app/PublicRouteSegments";
|
|
|
|
type UrlServerOptions = {
|
|
https?: boolean;
|
|
env?: EnvRecord & DomainRoutingEnv;
|
|
};
|
|
|
|
export const getBaseURL = (request: Request, options?: UrlServerOptions) => {
|
|
const config = loadRequiredWebConfig(options?.env);
|
|
const url = new URL(request.url);
|
|
const host = getRequestRoutingHost(request, url);
|
|
const forwardedProtocol = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
if (config.nodeEnv === "development") {
|
|
return `http://${host}`;
|
|
}
|
|
if (options?.https || config.nodeEnv === "production") {
|
|
return `https://${host}`;
|
|
} else if (forwardedProtocol) {
|
|
return `${forwardedProtocol}://${host}`;
|
|
} else {
|
|
return `${url.protocol}//${host}`;
|
|
}
|
|
};
|
|
|
|
export const getDomainName = (request: Request) => {
|
|
const url = new URL(request.url);
|
|
return getRequestRoutingHost(request, url);
|
|
};
|
|
|
|
export const getAbsoluteUrl = (request: Request, path: string, options?: UrlServerOptions) => {
|
|
const config = getDomainRoutingConfig(options?.env);
|
|
const context = getHostRoutingContextFromRequest(request, options?.env);
|
|
const targetPath = normalizeAbsoluteUrlInputToPath(path, config);
|
|
const host = getTargetHostForPath(targetPath, context, config);
|
|
const targetUrl = new URL(targetPath, `${config.protocol}://${host}`);
|
|
const canonicalRedirectUrl = getCanonicalHostRedirectUrl(targetUrl, config);
|
|
|
|
if (canonicalRedirectUrl) {
|
|
return canonicalRedirectUrl;
|
|
}
|
|
|
|
return targetUrl.toString();
|
|
};
|
|
|
|
function normalizeAbsoluteUrlInputToPath(path: string, config: ReturnType<typeof getDomainRoutingConfig>) {
|
|
const parsedUrl = new URL(path, `${config.protocol}://${config.appBaseDomain}`);
|
|
return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
|
|
}
|
|
|
|
function getTargetHostForPath(path: string, context: ReturnType<typeof getHostRoutingContextFromRequest>, config: ReturnType<typeof getDomainRoutingConfig>) {
|
|
const targetPath = new URL(path, `${config.protocol}://${config.appBaseDomain}`).pathname;
|
|
if (targetPath === "/v1" || targetPath.startsWith("/v1/")) {
|
|
return config.apiHost;
|
|
}
|
|
if (isAbsoluteWebHostPath(targetPath) || isProtectedWebHostPath(targetPath)) {
|
|
return config.webAppHost;
|
|
}
|
|
return context === "app" ? config.webAppHost : context === "admin" ? config.adminAppHost : context === "api" ? config.apiHost : config.appBaseDomain;
|
|
}
|
|
|
|
function isProtectedWebHostPath(pathname: string) {
|
|
return (
|
|
(protectedWebRouteExactPaths as readonly string[]).includes(pathname) ||
|
|
protectedWebRoutePrefixes.some((prefix) => pathMatchesRoutePrefix(pathname, prefix))
|
|
);
|
|
}
|
|
|
|
export const getAbsoluteCurrentUrl = (request: Request, options?: UrlServerOptions) => {
|
|
const config = getDomainRoutingConfig(options?.env);
|
|
const url = new URL(request.url);
|
|
const canonicalRedirectUrl = getCanonicalHostRedirectUrl(request, config);
|
|
|
|
if (canonicalRedirectUrl) {
|
|
return canonicalRedirectUrl;
|
|
}
|
|
|
|
const context = getHostRoutingContextFromRequest(request, options?.env);
|
|
|
|
if (context === "app") {
|
|
return `${config.protocol}://${config.webAppHost}${url.pathname}${url.search}${url.hash}`;
|
|
}
|
|
if (context === "admin") {
|
|
return `${config.protocol}://${config.adminAppHost}${url.pathname}${url.search}${url.hash}`;
|
|
}
|
|
if (context === "api") {
|
|
return `${config.protocol}://${config.apiHost}${url.pathname}${url.search}${url.hash}`;
|
|
}
|
|
|
|
return getAbsoluteUrl(request, `${url.pathname}${url.search}${url.hash}`, options);
|
|
};
|
|
|
|
export const getAbsoluteApiUrl = (request: Request, path: string, options?: UrlServerOptions) => {
|
|
const config = getDomainRoutingConfig(options?.env);
|
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
const targetUrl = new URL(normalizedPath, `${config.protocol}://${config.apiHost}`);
|
|
const strippedPath = targetUrl.pathname.replace(/^\/api\/v1(?=\/|$)/, "").replace(/^\/v1(?=\/|$)/, "");
|
|
const versionedPath = `/v1${strippedPath === "/" ? "" : strippedPath}`;
|
|
return new URL(`${versionedPath}${targetUrl.search}${targetUrl.hash}`, `${config.protocol}://${config.apiHost}`).toString();
|
|
};
|
|
|
|
export const getWebhooksUrl = (request: Request, options?: UrlServerOptions) => {
|
|
const config = loadRequiredWebConfig(options?.env);
|
|
return config.webhooksUrl ?? getBaseURL(request, options);
|
|
};
|