Files
proxy-station/shared/src/schemas.ts
2026-08-25 11:35:05 +08:00

100 lines
3.4 KiB
TypeScript

import { z } from 'zod';
import { PROTOCOLS } from './protocols.js';
export const nodeInputSchema = z
.object({
name: z.string().min(1).max(64),
protocol: z.enum(PROTOCOLS),
server: z.string().min(1),
port: z.number().int().min(1).max(65535),
network: z.enum(['tcp', 'ws']).default('tcp'),
wsPath: z.string().startsWith('/').optional().nullable(),
wsHost: z.string().optional().nullable(),
tls: z.boolean().default(false),
sni: z.string().optional().nullable(),
skipCertVerify: z.boolean().default(false),
uuid: z.string().uuid().optional().nullable(),
alterId: z.number().int().min(0).default(0),
security: z.string().default('auto'),
password: z.string().optional().nullable(),
method: z.string().optional().nullable(),
obfsType: z.enum(['salamander']).optional().nullable(),
obfsPassword: z.string().optional().nullable(),
upMbps: z.number().int().positive().optional().nullable(),
downMbps: z.number().int().positive().optional().nullable(),
region: z.string().max(8).optional().nullable(),
tags: z.array(z.string()).default([]),
enabled: z.boolean().default(true),
sortOrder: z.number().int().default(0),
})
.superRefine((v, ctx) => {
if (v.protocol === 'vmess' && !v.uuid) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['uuid'], message: 'VMess 需要 UUID' });
}
if ((v.protocol === 'trojan' || v.protocol === 'shadowsocks' || v.protocol === 'hysteria2') && !v.password) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['password'], message: '该协议需要密码' });
}
if (v.protocol === 'shadowsocks' && !v.method) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['method'], message: 'Shadowsocks 需要加密方式' });
}
if (v.network === 'ws' && !v.wsPath) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['wsPath'], message: 'WebSocket 传输需要路径' });
}
});
export type NodeInput = z.input<typeof nodeInputSchema>;
export type NodeParsed = z.output<typeof nodeInputSchema>;
export const groupInputSchema = z.object({
name: z.string().min(1).max(64),
type: z.enum(['select', 'url-test', 'fallback']),
testUrl: z.string().url().optional().nullable(),
interval: z.number().int().positive().optional().nullable(),
tolerance: z.number().int().positive().optional().nullable(),
filterRegex: z.string().optional().nullable(),
includeAllNodes: z.boolean().default(false),
sortOrder: z.number().int().default(0),
members: z
.array(
z.object({
kind: z.enum(['node', 'group', 'builtin']),
nodeId: z.string().optional().nullable(),
refName: z.string().optional().nullable(),
})
)
.default([]),
});
export type GroupInput = z.input<typeof groupInputSchema>;
export const RULE_TYPES = [
'RULE-SET',
'DOMAIN-SET',
'DOMAIN',
'DOMAIN-SUFFIX',
'DOMAIN-KEYWORD',
'IP-CIDR',
'IP-CIDR6',
'IP-ASN',
'GEOIP',
'PROCESS-NAME',
'USER-AGENT',
'URL-REGEX',
'AND',
'OR',
'NOT',
'FINAL',
] as const;
export const ruleInputSchema = z.object({
type: z.enum(RULE_TYPES),
value: z.string().optional().nullable(),
policy: z.string().min(1),
params: z.array(z.string()).default([]),
enabled: z.boolean().default(true),
});
export type RuleInput = z.input<typeof ruleInputSchema>;
export const tokenInputSchema = z.object({
name: z.string().min(1).max(64),
});