209 lines
6.0 KiB
TypeScript
209 lines
6.0 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { resolveRuntimePath } from '../runtime-paths';
|
|
|
|
export type ServerHttpProtocol = 'http' | 'https';
|
|
|
|
export interface LinkPreviewConfig {
|
|
enabled: boolean;
|
|
cacheTtlMinutes: number;
|
|
maxCacheSizeMb: number;
|
|
}
|
|
|
|
export interface ServerVariablesConfig {
|
|
klipyApiKey: string;
|
|
releaseManifestUrl: string;
|
|
serverPort: number;
|
|
serverProtocol: ServerHttpProtocol;
|
|
serverHost: string;
|
|
linkPreview: LinkPreviewConfig;
|
|
}
|
|
|
|
const DATA_DIR = resolveRuntimePath('data');
|
|
const VARIABLES_FILE = path.join(DATA_DIR, 'variables.json');
|
|
const DEFAULT_SERVER_PORT = 3001;
|
|
const DEFAULT_SERVER_PROTOCOL: ServerHttpProtocol = 'http';
|
|
const DEFAULT_LINK_PREVIEW_CACHE_TTL_MINUTES = 7200;
|
|
const DEFAULT_LINK_PREVIEW_MAX_CACHE_SIZE_MB = 50;
|
|
const HARD_MAX_CACHE_SIZE_MB = 50;
|
|
|
|
function normalizeKlipyApiKey(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function normalizeReleaseManifestUrl(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function normalizeServerHost(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function normalizeServerProtocol(
|
|
value: unknown,
|
|
fallback: ServerHttpProtocol = DEFAULT_SERVER_PROTOCOL
|
|
): ServerHttpProtocol {
|
|
if (typeof value === 'boolean') {
|
|
return value ? 'https' : 'http';
|
|
}
|
|
|
|
if (typeof value !== 'string') {
|
|
return fallback;
|
|
}
|
|
|
|
const normalized = value.trim().toLowerCase();
|
|
|
|
if (normalized === 'https' || normalized === 'true') {
|
|
return 'https';
|
|
}
|
|
|
|
if (normalized === 'http' || normalized === 'false') {
|
|
return 'http';
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
function normalizeServerPort(value: unknown, fallback = DEFAULT_SERVER_PORT): number {
|
|
const parsed = typeof value === 'number'
|
|
? value
|
|
: typeof value === 'string'
|
|
? Number.parseInt(value.trim(), 10)
|
|
: Number.NaN;
|
|
|
|
return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535
|
|
? parsed
|
|
: fallback;
|
|
}
|
|
|
|
function normalizeLinkPreviewConfig(value: unknown): LinkPreviewConfig {
|
|
const raw = (value && typeof value === 'object' && !Array.isArray(value))
|
|
? value as Record<string, unknown>
|
|
: {};
|
|
const enabled = typeof raw.enabled === 'boolean'
|
|
? raw.enabled
|
|
: true;
|
|
const cacheTtl = typeof raw.cacheTtlMinutes === 'number'
|
|
&& Number.isFinite(raw.cacheTtlMinutes)
|
|
&& raw.cacheTtlMinutes >= 0
|
|
? raw.cacheTtlMinutes
|
|
: DEFAULT_LINK_PREVIEW_CACHE_TTL_MINUTES;
|
|
const maxSize = typeof raw.maxCacheSizeMb === 'number'
|
|
&& Number.isFinite(raw.maxCacheSizeMb)
|
|
&& raw.maxCacheSizeMb >= 0
|
|
? Math.min(raw.maxCacheSizeMb, HARD_MAX_CACHE_SIZE_MB)
|
|
: DEFAULT_LINK_PREVIEW_MAX_CACHE_SIZE_MB;
|
|
|
|
return { enabled, cacheTtlMinutes: cacheTtl, maxCacheSizeMb: maxSize };
|
|
}
|
|
|
|
function hasEnvironmentOverride(value: string | undefined): value is string {
|
|
return typeof value === 'string' && value.trim().length > 0;
|
|
}
|
|
|
|
function readRawVariables(): { rawContents: string; parsed: Record<string, unknown> } {
|
|
if (!fs.existsSync(VARIABLES_FILE)) {
|
|
return { rawContents: '', parsed: {} };
|
|
}
|
|
|
|
const rawContents = fs.readFileSync(VARIABLES_FILE, 'utf8');
|
|
|
|
if (!rawContents.trim()) {
|
|
return { rawContents, parsed: {} };
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(rawContents) as unknown;
|
|
|
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
return { rawContents, parsed: parsed as Record<string, unknown> };
|
|
}
|
|
} catch (error) {
|
|
console.warn('[Config] Failed to parse variables.json. Recreating it with defaults.', error);
|
|
}
|
|
|
|
return { rawContents, parsed: {} };
|
|
}
|
|
|
|
export function getVariablesConfigPath(): string {
|
|
return VARIABLES_FILE;
|
|
}
|
|
|
|
export function ensureVariablesConfig(): ServerVariablesConfig {
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
const { rawContents, parsed } = readRawVariables();
|
|
const { serverIpAddress: legacyServerIpAddress, ...remainingParsed } = parsed;
|
|
const normalized = {
|
|
...remainingParsed,
|
|
klipyApiKey: normalizeKlipyApiKey(remainingParsed.klipyApiKey),
|
|
releaseManifestUrl: normalizeReleaseManifestUrl(remainingParsed.releaseManifestUrl),
|
|
serverPort: normalizeServerPort(remainingParsed.serverPort),
|
|
serverProtocol: normalizeServerProtocol(remainingParsed.serverProtocol),
|
|
serverHost: normalizeServerHost(remainingParsed.serverHost ?? legacyServerIpAddress),
|
|
linkPreview: normalizeLinkPreviewConfig(remainingParsed.linkPreview)
|
|
};
|
|
const nextContents = JSON.stringify(normalized, null, 2) + '\n';
|
|
|
|
if (!fs.existsSync(VARIABLES_FILE) || rawContents !== nextContents) {
|
|
fs.writeFileSync(VARIABLES_FILE, nextContents, 'utf8');
|
|
}
|
|
|
|
return {
|
|
klipyApiKey: normalized.klipyApiKey,
|
|
releaseManifestUrl: normalized.releaseManifestUrl,
|
|
serverPort: normalized.serverPort,
|
|
serverProtocol: normalized.serverProtocol,
|
|
serverHost: normalized.serverHost,
|
|
linkPreview: normalized.linkPreview
|
|
};
|
|
}
|
|
|
|
export function getVariablesConfig(): ServerVariablesConfig {
|
|
return ensureVariablesConfig();
|
|
}
|
|
|
|
export function getKlipyApiKey(): string {
|
|
return getVariablesConfig().klipyApiKey;
|
|
}
|
|
|
|
export function hasKlipyApiKey(): boolean {
|
|
return getKlipyApiKey().length > 0;
|
|
}
|
|
|
|
export function getReleaseManifestUrl(): string {
|
|
return getVariablesConfig().releaseManifestUrl;
|
|
}
|
|
|
|
export function getServerProtocol(): ServerHttpProtocol {
|
|
if (hasEnvironmentOverride(process.env.SSL)) {
|
|
return normalizeServerProtocol(process.env.SSL);
|
|
}
|
|
|
|
return getVariablesConfig().serverProtocol;
|
|
}
|
|
|
|
export function getServerPort(): number {
|
|
if (hasEnvironmentOverride(process.env.PORT)) {
|
|
return normalizeServerPort(process.env.PORT);
|
|
}
|
|
|
|
return getVariablesConfig().serverPort;
|
|
}
|
|
|
|
export function getServerHost(): string | undefined {
|
|
const serverHost = getVariablesConfig().serverHost;
|
|
|
|
return serverHost || undefined;
|
|
}
|
|
|
|
export function isHttpsServerEnabled(): boolean {
|
|
return getServerProtocol() === 'https';
|
|
}
|
|
|
|
export function getLinkPreviewConfig(): LinkPreviewConfig {
|
|
return getVariablesConfig().linkPreview;
|
|
}
|