feat: add server config validation

This commit is contained in:
nav
2026-04-08 20:03:28 +00:00
parent ac128d3827
commit 3ec57ce199
2 changed files with 107 additions and 0 deletions

104
plugin/core/config.ts Normal file
View File

@@ -0,0 +1,104 @@
export interface YonexusServerConfig {
followerIdentifiers: string[];
notifyBotToken: string;
adminUserId: string;
listenHost?: string;
listenPort: number;
publicWsUrl?: string;
}
export class YonexusServerConfigError extends Error {
readonly issues: string[];
constructor(issues: string[]) {
super(`Invalid Yonexus.Server config: ${issues.join("; ")}`);
this.name = "YonexusServerConfigError";
this.issues = issues;
}
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
function normalizeOptionalString(value: unknown): string | undefined {
if (value === undefined || value === null) {
return undefined;
}
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
function isValidPort(value: unknown): value is number {
return Number.isInteger(value) && value >= 1 && value <= 65535;
}
function isValidWsUrl(value: string): boolean {
try {
const url = new URL(value);
return url.protocol === "ws:" || url.protocol === "wss:";
} catch {
return false;
}
}
export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
const source = raw as Record<string, unknown> | null;
const issues: string[] = [];
const rawIdentifiers = source?.followerIdentifiers;
const followerIdentifiers = Array.isArray(rawIdentifiers)
? rawIdentifiers
.filter((value): value is string => typeof value === "string")
.map((value) => value.trim())
.filter((value) => value.length > 0)
: [];
if (!Array.isArray(rawIdentifiers) || followerIdentifiers.length === 0) {
issues.push("followerIdentifiers must contain at least one non-empty identifier");
}
if (new Set(followerIdentifiers).size !== followerIdentifiers.length) {
issues.push("followerIdentifiers must not contain duplicates");
}
const notifyBotToken = source?.notifyBotToken;
if (!isNonEmptyString(notifyBotToken)) {
issues.push("notifyBotToken is required");
}
const adminUserId = source?.adminUserId;
if (!isNonEmptyString(adminUserId)) {
issues.push("adminUserId is required");
}
const listenPort = source?.listenPort;
if (!isValidPort(listenPort)) {
issues.push("listenPort must be an integer between 1 and 65535");
}
const listenHost = normalizeOptionalString(source?.listenHost) ?? "0.0.0.0";
const publicWsUrl = normalizeOptionalString(source?.publicWsUrl);
if (publicWsUrl !== undefined && !isValidWsUrl(publicWsUrl)) {
issues.push("publicWsUrl must be a valid ws:// or wss:// URL when provided");
}
if (issues.length > 0) {
throw new YonexusServerConfigError(issues);
}
return {
followerIdentifiers,
notifyBotToken: notifyBotToken.trim(),
adminUserId: adminUserId.trim(),
listenHost,
listenPort,
publicWsUrl
};
}

View File

@@ -1,3 +1,6 @@
export { validateYonexusServerConfig, YonexusServerConfigError } from "./core/config.js";
export type { YonexusServerConfig } from "./core/config.js";
export interface YonexusServerPluginManifest { export interface YonexusServerPluginManifest {
readonly name: "Yonexus.Server"; readonly name: "Yonexus.Server";
readonly version: string; readonly version: string;