export interface YonexusClientConfig { mainHost: string; identifier: string; /** * Optional. The client never sends pairing notifications (the server * does); accepted for back-compat but no longer required. */ notifyBotToken?: string; adminUserId?: string; } export class YonexusClientConfigError extends Error { readonly issues: string[]; constructor(issues: string[]) { super(`Invalid Yonexus.Client config: ${issues.join("; ")}`); this.name = "YonexusClientConfigError"; this.issues = issues; } } function isNonEmptyString(value: unknown): value is string { return typeof value === "string" && value.trim().length > 0; } function isValidWsUrl(value: string): boolean { try { const url = new URL(value); return url.protocol === "ws:" || url.protocol === "wss:"; } catch { return false; } } export function validateYonexusClientConfig(raw: unknown): YonexusClientConfig { const source = (raw && typeof raw === "object" ? raw : {}) as Record; const issues: string[] = []; const rawMainHost = source.mainHost; if (!isNonEmptyString(rawMainHost)) { issues.push("mainHost is required"); } else if (!isValidWsUrl(rawMainHost.trim())) { issues.push("mainHost must be a valid ws:// or wss:// URL"); } const rawIdentifier = source.identifier; if (!isNonEmptyString(rawIdentifier)) { issues.push("identifier is required"); } // Optional (back-compat): the client does not send notifications. const rawNotifyBotToken = source.notifyBotToken; const rawAdminUserId = source.adminUserId; if (issues.length > 0) { throw new YonexusClientConfigError(issues); } const mainHost = (rawMainHost as string).trim(); const identifier = (rawIdentifier as string).trim(); return { mainHost, identifier, notifyBotToken: isNonEmptyString(rawNotifyBotToken) ? rawNotifyBotToken.trim() : undefined, adminUserId: isNonEmptyString(rawAdminUserId) ? rawAdminUserId.trim() : undefined }; }