68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
import { parseDocument, stringify } from "yaml";
|
|
|
|
export class CustomProxyValidationError extends Error {}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
/** Accept a single proxy, a one-item list, or the legacy `proxies:` wrapper. */
|
|
export function parseCustomProxyYaml(
|
|
source: string,
|
|
): Record<string, unknown> & { name: string; type: string } {
|
|
if (!source.trim()) throw new CustomProxyValidationError("请填写节点 YAML。");
|
|
if (source.length > 128 * 1024)
|
|
throw new CustomProxyValidationError("单个节点 YAML 不能超过 128 KiB。");
|
|
const doc = parseDocument(source, { prettyErrors: false, uniqueKeys: true });
|
|
if (doc.errors.length || doc.warnings.length) {
|
|
// Parser messages can contain credentials from the YAML; do not echo them.
|
|
throw new CustomProxyValidationError(
|
|
"YAML 格式错误,请检查缩进、引号和重复字段。",
|
|
);
|
|
}
|
|
let value: unknown;
|
|
try {
|
|
value = doc.toJS({ maxAliasCount: 100 });
|
|
} catch {
|
|
throw new CustomProxyValidationError(
|
|
"YAML 引用无效或过多,请使用独立的节点配置。",
|
|
);
|
|
}
|
|
if (isRecord(value) && "proxies" in value) value = value.proxies;
|
|
if (Array.isArray(value)) {
|
|
if (value.length !== 1)
|
|
throw new CustomProxyValidationError(
|
|
"每条节点记录只能包含一个 proxy,请将多个节点分别添加。",
|
|
);
|
|
value = value[0];
|
|
}
|
|
if (!isRecord(value))
|
|
throw new CustomProxyValidationError(
|
|
"请填写一个 Clash proxy 对象或单节点列表。",
|
|
);
|
|
if (typeof value.name !== "string" || !value.name.trim()) {
|
|
throw new CustomProxyValidationError(
|
|
"节点 YAML 必须包含非空的 name 字段(字符串)。",
|
|
);
|
|
}
|
|
if (typeof value.type !== "string" || !value.type.trim()) {
|
|
throw new CustomProxyValidationError(
|
|
"节点 YAML 必须包含非空的 type 字段,例如 anytls。",
|
|
);
|
|
}
|
|
return value as Record<string, unknown> & { name: string; type: string };
|
|
}
|
|
|
|
/** Re-serialize only for output; stored source and comments remain untouched. */
|
|
export function renderCustomProxyYaml(source: string) {
|
|
return stringify([parseCustomProxyYaml(source)], { lineWidth: 0 }).trimEnd();
|
|
}
|
|
|
|
export function extractProxyNameFromYaml(source: string) {
|
|
try {
|
|
return parseCustomProxyYaml(source).name;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|