mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
feat: replace nextjs-config with environment variables (#632)
* chore: remove nextjs-config * build fix * update readme * explicit nextjs env vars + remove feature-unrelated things and obsolete vars from config * full config removal * remove force-dynamic from pages * compile fix * restore claude-deleted tests * better .env.example --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
@@ -6,10 +6,14 @@ import createFetchClient from "openapi-react-query";
|
||||
import { assertExistsAndNonEmptyString } from "./utils";
|
||||
import { isBuildPhase } from "./next";
|
||||
|
||||
const API_URL = !isBuildPhase
|
||||
export const API_URL = !isBuildPhase
|
||||
? assertExistsAndNonEmptyString(process.env.NEXT_PUBLIC_API_URL)
|
||||
: "http://localhost";
|
||||
|
||||
// TODO decide strict validation or not
|
||||
export const WEBSOCKET_URL =
|
||||
process.env.NEXT_PUBLIC_WEBSOCKET_URL || "ws://127.0.0.1:1250";
|
||||
|
||||
export const client = createClient<paths>({
|
||||
baseUrl: API_URL,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { assertExistsAndNonEmptyString } from "./utils";
|
||||
|
||||
export const REFRESH_ACCESS_TOKEN_ERROR = "RefreshAccessTokenError" as const;
|
||||
// 4 min is 1 min less than default authentic value. here we assume that authentic won't be set to access tokens < 4 min
|
||||
export const REFRESH_ACCESS_TOKEN_BEFORE = 4 * 60 * 1000;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { get } from "@vercel/edge-config";
|
||||
import { isBuildPhase } from "./next";
|
||||
|
||||
type EdgeConfig = {
|
||||
[domainWithDash: string]: {
|
||||
features: {
|
||||
[featureName in
|
||||
| "requireLogin"
|
||||
| "privacy"
|
||||
| "browse"
|
||||
| "sendToZulip"]: boolean;
|
||||
};
|
||||
auth_callback_url: string;
|
||||
websocket_url: string;
|
||||
api_url: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type DomainConfig = EdgeConfig["domainWithDash"];
|
||||
|
||||
// Edge config main keys can only be alphanumeric and _ or -
|
||||
export function edgeKeyToDomain(key: string) {
|
||||
return key.replaceAll("_", ".");
|
||||
}
|
||||
|
||||
export function edgeDomainToKey(domain: string) {
|
||||
return domain.replaceAll(".", "_");
|
||||
}
|
||||
|
||||
// get edge config server-side (prefer DomainContext when available), domain is the hostname
|
||||
export async function getConfig() {
|
||||
if (process.env.NEXT_PUBLIC_ENV === "development") {
|
||||
try {
|
||||
return require("../../config").localConfig;
|
||||
} catch (e) {
|
||||
// next build() WILL try to execute the require above even if conditionally protected
|
||||
// but thank god it at least runs catch{} block properly
|
||||
if (!isBuildPhase) throw new Error(e);
|
||||
return require("../../config-template").localConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const domain = new URL(process.env.NEXT_PUBLIC_SITE_URL!).hostname;
|
||||
let config = await get(edgeDomainToKey(domain));
|
||||
|
||||
if (typeof config !== "object") {
|
||||
console.warn("No config for this domain, falling back to default");
|
||||
config = await get(edgeDomainToKey("default"));
|
||||
}
|
||||
|
||||
if (typeof config !== "object") throw Error("Error fetching config");
|
||||
|
||||
return config as DomainConfig;
|
||||
}
|
||||
55
www/app/lib/features.ts
Normal file
55
www/app/lib/features.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export const FEATURES = [
|
||||
"requireLogin",
|
||||
"privacy",
|
||||
"browse",
|
||||
"sendToZulip",
|
||||
"rooms",
|
||||
] as const;
|
||||
|
||||
export type FeatureName = (typeof FEATURES)[number];
|
||||
|
||||
export type Features = Readonly<Record<FeatureName, boolean>>;
|
||||
|
||||
export const DEFAULT_FEATURES: Features = {
|
||||
requireLogin: false,
|
||||
privacy: true,
|
||||
browse: false,
|
||||
sendToZulip: false,
|
||||
rooms: false,
|
||||
} as const;
|
||||
|
||||
function parseBooleanEnv(
|
||||
value: string | undefined,
|
||||
defaultValue: boolean = false,
|
||||
): boolean {
|
||||
if (!value) return defaultValue;
|
||||
return value.toLowerCase() === "true";
|
||||
}
|
||||
|
||||
// WARNING: keep process.env.* as-is, next.js won't see them if you generate dynamically
|
||||
const features: Features = {
|
||||
requireLogin: parseBooleanEnv(
|
||||
process.env.NEXT_PUBLIC_FEATURE_REQUIRE_LOGIN,
|
||||
DEFAULT_FEATURES.requireLogin,
|
||||
),
|
||||
privacy: parseBooleanEnv(
|
||||
process.env.NEXT_PUBLIC_FEATURE_PRIVACY,
|
||||
DEFAULT_FEATURES.privacy,
|
||||
),
|
||||
browse: parseBooleanEnv(
|
||||
process.env.NEXT_PUBLIC_FEATURE_BROWSE,
|
||||
DEFAULT_FEATURES.browse,
|
||||
),
|
||||
sendToZulip: parseBooleanEnv(
|
||||
process.env.NEXT_PUBLIC_FEATURE_SEND_TO_ZULIP,
|
||||
DEFAULT_FEATURES.sendToZulip,
|
||||
),
|
||||
rooms: parseBooleanEnv(
|
||||
process.env.NEXT_PUBLIC_FEATURE_ROOMS,
|
||||
DEFAULT_FEATURES.rooms,
|
||||
),
|
||||
};
|
||||
|
||||
export const featureEnabled = (featureName: FeatureName): boolean => {
|
||||
return features[featureName];
|
||||
};
|
||||
@@ -72,3 +72,7 @@ export const assertCustomSession = <S extends Session>(s: S): CustomSession => {
|
||||
// no other checks for now
|
||||
return r as CustomSession;
|
||||
};
|
||||
|
||||
export type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
@@ -171,5 +171,6 @@ export const assertNotExists = <T>(
|
||||
|
||||
export const assertExistsAndNonEmptyString = (
|
||||
value: string | null | undefined,
|
||||
err?: string,
|
||||
): NonEmptyString =>
|
||||
parseNonEmptyString(assertExists(value, "Expected non-empty string"));
|
||||
parseNonEmptyString(assertExists(value, err || "Expected non-empty string"));
|
||||
|
||||
Reference in New Issue
Block a user