Files
Yonexus.Server/plugin/core/config.ts

109 lines
3.1 KiB
TypeScript

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 typeof value === "number" && 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 && typeof raw === "object" ? raw : {}) as Record<string, unknown>;
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 rawNotifyBotToken = source.notifyBotToken;
if (!isNonEmptyString(rawNotifyBotToken)) {
issues.push("notifyBotToken is required");
}
const rawAdminUserId = source.adminUserId;
if (!isNonEmptyString(rawAdminUserId)) {
issues.push("adminUserId is required");
}
const rawListenPort = source.listenPort;
if (!isValidPort(rawListenPort)) {
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);
}
const notifyBotToken = rawNotifyBotToken as string;
const adminUserId = rawAdminUserId as string;
const listenPort = rawListenPort as number;
return {
followerIdentifiers,
notifyBotToken: notifyBotToken.trim(),
adminUserId: adminUserId.trim(),
listenHost,
listenPort,
publicWsUrl
};
}