feat: expand config with playback, rendering, and connection limits

This commit is contained in:
2026-07-13 17:00:15 +07:00
parent 8a013b0e62
commit b705b02084
2 changed files with 119 additions and 26 deletions
+50 -9
View File
@@ -3,43 +3,84 @@ import { loadConfig } from "./config";
describe("loadConfig", () => {
test("returns defaults when env vars are unset", () => {
const config = loadConfig({});
expect(config).toEqual({
expect(loadConfig({})).toEqual({
host: "0.0.0.0",
port: 22,
maxLoop: 5,
playbackMode: "loop",
allowUserControl: true,
switchDebounceMs: 120,
loginDelay: 1500,
maxConnections: 10,
maxTotalConnections: 1000,
maxAuthAttempts: 6,
handshakeTimeout: 10000,
maxDimension: 512,
frameResolution: 360,
brightnessThreshold: 40,
charset: "detailed",
invert: false,
logCredentials: false,
videoPath: undefined,
});
});
test("overrides defaults from env vars", () => {
const config = loadConfig({
expect(
loadConfig({
HOST: "127.0.0.1",
PORT: "2222",
MAX_LOOP: "3",
PLAYBACK_MODE: "random",
ALLOW_USER_CONTROL: "false",
SWITCH_DEBOUNCE_MS: "200",
LOGIN_DELAY: "500",
MAX_CONNECTIONS: "20",
MAX_TOTAL_CONNECTIONS: "50",
MAX_AUTH_ATTEMPTS: "3",
HANDSHAKE_TIMEOUT: "5000",
MAX_DIMENSION: "200",
FRAME_RESOLUTION: "480",
BRIGHTNESS_THRESHOLD: "60",
CHARSET: "blocks",
INVERT: "true",
LOG_CREDENTIALS: "true",
});
expect(config).toEqual({
VIDEO_PATH: "/videos/clip.mkv",
})
).toEqual({
host: "127.0.0.1",
port: 2222,
maxLoop: 3,
playbackMode: "random",
allowUserControl: false,
switchDebounceMs: 200,
loginDelay: 500,
maxConnections: 20,
maxTotalConnections: 50,
maxAuthAttempts: 3,
handshakeTimeout: 5000,
maxDimension: 200,
frameResolution: 480,
brightnessThreshold: 60,
charset: "blocks",
invert: true,
logCredentials: true,
videoPath: "/videos/clip.mkv",
});
});
test("logCredentials is false for any value other than the string 'true'", () => {
test("boolean env vars are true only for the exact string 'true'", () => {
expect(loadConfig({ LOG_CREDENTIALS: "1" }).logCredentials).toBe(false);
expect(loadConfig({ LOG_CREDENTIALS: "false" }).logCredentials).toBe(
false
);
expect(loadConfig({ INVERT: "yes" }).invert).toBe(false);
expect(loadConfig({ INVERT: "true" }).invert).toBe(true);
});
test("falls back to defaults for non-numeric and out-of-range values", () => {
expect(loadConfig({ PORT: "not-a-number" }).port).toBe(22);
expect(loadConfig({ PORT: "99999" }).port).toBe(65535);
expect(loadConfig({ MAX_DIMENSION: "0" }).maxDimension).toBe(1);
expect(
loadConfig({ BRIGHTNESS_THRESHOLD: "999" }).brightnessThreshold
).toBe(100);
});
});
+62 -10
View File
@@ -4,25 +4,77 @@ export interface Config {
host: string;
port: number;
maxLoop: number;
playbackMode: "loop" | "random";
allowUserControl: boolean;
switchDebounceMs: number;
loginDelay: number;
maxConnections: number;
maxTotalConnections: number;
maxAuthAttempts: number;
handshakeTimeout: number;
maxDimension: number;
frameResolution: number;
brightnessThreshold: number;
charset: string;
invert: boolean;
logCredentials: boolean;
videoPath?: string;
}
function parseIntEnv(
value: string | undefined,
fallback: number,
{ min, max }: { min?: number; max?: number } = {}
): number {
if (value === undefined || value.trim() === "") return fallback;
const parsed = parseInt(value, 10);
if (!Number.isFinite(parsed)) return fallback;
let result = parsed;
if (typeof min === "number") result = Math.max(min, result);
if (typeof max === "number") result = Math.min(max, result);
return result;
}
function parseBoolEnv(value: string | undefined): boolean {
return value === "true";
}
export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
const videoPath = env.VIDEO_PATH?.trim();
return {
host: env.HOST ?? "0.0.0.0",
port: env.PORT ? parseInt(env.PORT, 10) : 22,
maxLoop: env.MAX_LOOP ? parseInt(env.MAX_LOOP, 10) : 5,
loginDelay: env.LOGIN_DELAY ? parseInt(env.LOGIN_DELAY, 10) : 1500,
maxConnections: env.MAX_CONNECTIONS
? parseInt(env.MAX_CONNECTIONS, 10)
: 10,
brightnessThreshold: env.BRIGHTNESS_THRESHOLD
? parseInt(env.BRIGHTNESS_THRESHOLD, 10)
: 40,
logCredentials: env.LOG_CREDENTIALS === "true",
port: parseIntEnv(env.PORT, 22, { min: 1, max: 65535 }),
maxLoop: parseIntEnv(env.MAX_LOOP, 5, { min: 1 }),
playbackMode:
env.PLAYBACK_MODE?.trim().toLowerCase() === "random"
? "random"
: "loop",
allowUserControl: env.ALLOW_USER_CONTROL !== "false",
switchDebounceMs: parseIntEnv(env.SWITCH_DEBOUNCE_MS, 120, { min: 0 }),
loginDelay: parseIntEnv(env.LOGIN_DELAY, 1500, { min: 0 }),
maxConnections: parseIntEnv(env.MAX_CONNECTIONS, 10, { min: 1 }),
maxTotalConnections: parseIntEnv(env.MAX_TOTAL_CONNECTIONS, 1000, {
min: 1,
}),
maxAuthAttempts: parseIntEnv(env.MAX_AUTH_ATTEMPTS, 6, { min: 1 }),
handshakeTimeout: parseIntEnv(env.HANDSHAKE_TIMEOUT, 10000, { min: 0 }),
maxDimension: parseIntEnv(env.MAX_DIMENSION, 512, {
min: 1,
max: 4096,
}),
frameResolution: parseIntEnv(env.FRAME_RESOLUTION, 360, {
min: 16,
max: 1080,
}),
brightnessThreshold: parseIntEnv(env.BRIGHTNESS_THRESHOLD, 40, {
min: 0,
max: 100,
}),
charset: env.CHARSET?.trim() || "detailed",
invert: parseBoolEnv(env.INVERT),
logCredentials: parseBoolEnv(env.LOG_CREDENTIALS),
videoPath: videoPath ? videoPath : undefined,
};
}