Files
reflector/www/app/lib/clientEnv.ts
Igor Monadical 5bf64b5a41 feat: docker-compose for production frontend (#664)
* docker-compose for production frontend

* fix: Remove external Redis port mapping for Coolify compatibility

Redis should only be accessible within the internal Docker network in Coolify deployments to avoid port conflicts with other applications.

* fix: Remove external port mapping for web service in Coolify

Coolify handles port exposure through its proxy (Traefik), so services should not expose ports directly in the docker-compose file.

* server side client envs

* missing vars

* nextjs experimental

* fix claude 'fix'

* remove build env vars compose

* docker

* remove ports for coolify

* review

* cleanup

---------

Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
2025-09-24 11:15:27 -04:00

89 lines
2.5 KiB
TypeScript

import {
assertExists,
assertExistsAndNonEmptyString,
NonEmptyString,
parseNonEmptyString,
} from "./utils";
import { isBuildPhase } from "./next";
import { getNextEnvVar } from "./nextBuild";
export const FEATURE_REQUIRE_LOGIN_ENV_NAME = "FEATURE_REQUIRE_LOGIN" as const;
export const FEATURE_PRIVACY_ENV_NAME = "FEATURE_PRIVACY" as const;
export const FEATURE_BROWSE_ENV_NAME = "FEATURE_BROWSE" as const;
export const FEATURE_SEND_TO_ZULIP_ENV_NAME = "FEATURE_SEND_TO_ZULIP" as const;
export const FEATURE_ROOMS_ENV_NAME = "FEATURE_ROOMS" as const;
const FEATURE_ENV_NAMES = [
FEATURE_REQUIRE_LOGIN_ENV_NAME,
FEATURE_PRIVACY_ENV_NAME,
FEATURE_BROWSE_ENV_NAME,
FEATURE_SEND_TO_ZULIP_ENV_NAME,
FEATURE_ROOMS_ENV_NAME,
] as const;
export type EnvFeaturePartial = {
[key in (typeof FEATURE_ENV_NAMES)[number]]: boolean;
};
// CONTRACT: isomorphic with JSON.stringify
export type ClientEnvCommon = EnvFeaturePartial & {
API_URL: NonEmptyString;
WEBSOCKET_URL: NonEmptyString | null;
};
let clientEnv: ClientEnvCommon | null = null;
export const getClientEnvClient = (): ClientEnvCommon => {
if (typeof window === "undefined") {
throw new Error(
"getClientEnv() called during SSR - this should only be called in browser environment",
);
}
if (clientEnv) return clientEnv;
clientEnv = assertExists(
JSON.parse(
assertExistsAndNonEmptyString(
document.body.dataset.env,
"document.body.dataset.env is missing",
),
),
"document.body.dataset.env is parsed to nullish",
);
return clientEnv!;
};
const parseBooleanString = (str: string | undefined): boolean => {
return str === "true";
};
export const getClientEnvServer = (): ClientEnvCommon => {
if (typeof window !== "undefined") {
throw new Error(
"getClientEnv() not called during SSR - this should only be called in server environment",
);
}
if (clientEnv) return clientEnv;
const features = FEATURE_ENV_NAMES.reduce((acc, x) => {
acc[x] = parseBooleanString(process.env[x]);
return acc;
}, {} as EnvFeaturePartial);
if (isBuildPhase) {
return {
API_URL: getNextEnvVar("API_URL"),
WEBSOCKET_URL: getNextEnvVar("WEBSOCKET_URL"),
...features,
};
}
clientEnv = {
API_URL: getNextEnvVar("API_URL"),
WEBSOCKET_URL: getNextEnvVar("WEBSOCKET_URL"),
...features,
};
return clientEnv;
};
export const getClientEnv =
typeof window === "undefined" ? getClientEnvServer : getClientEnvClient;