host.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import { z } from 'zod';
  2. import { AlpnSchema, UtlsFingerprintSchema } from '@/schemas/protocols/security/tls';
  3. // A Host is a per-inbound override endpoint: at subscription time each enabled
  4. // host renders one extra share link/proxy with its own address/port/TLS, etc.,
  5. // superseding the legacy externalProxy array. The form schema mirrors the field
  6. // logic of schemas/protocols/stream/external-proxy.ts and reuses the shared
  7. // ALPN / uTLS primitives.
  8. export const HostSecuritySchema = z.enum(['same', 'tls', 'none', 'reality']);
  9. export type HostSecurity = z.infer<typeof HostSecuritySchema>;
  10. export const MihomoIpVersionSchema = z.enum(['dual', 'ipv4', 'ipv6', 'ipv4-prefer', 'ipv6-prefer']);
  11. export const SubTypeSchema = z.enum(['raw', 'json', 'clash']);
  12. // Tags are short uppercase identifiers (≤10 tags, each ≤36 chars). Enforced on
  13. // the frontend; the backend stores them verbatim.
  14. const HostTagSchema = z.string().regex(/^[A-Z0-9_:]+$/, 'pages.hosts.toasts.badTag').max(36);
  15. // HostFormValues is what the form edits and POSTs.
  16. export const HostFormSchema = z.object({
  17. id: z.number().optional(),
  18. inboundId: z.number().int().positive(),
  19. sortOrder: z.number().int().default(0),
  20. // Remark may contain {{VAR}} template tokens expanded per client at
  21. // subscription time, so the stored template gets a generous cap.
  22. remark: z.string().trim().min(1).max(256),
  23. serverDescription: z.string().max(64).default(''),
  24. isDisabled: z.boolean().default(false),
  25. isHidden: z.boolean().default(false),
  26. tags: z.array(HostTagSchema).max(10).default([]),
  27. address: z.string().default(''),
  28. port: z.number().int().min(0).max(65535).default(0),
  29. security: HostSecuritySchema.default('same'),
  30. sni: z.string().default(''),
  31. hostHeader: z.string().default(''),
  32. path: z.string().default(''),
  33. alpn: z.array(AlpnSchema).default([]),
  34. fingerprint: z.preprocess(
  35. (val) => (val === '' ? undefined : val),
  36. UtlsFingerprintSchema.optional(),
  37. ),
  38. overrideSniFromAddress: z.boolean().default(false),
  39. keepSniBlank: z.boolean().default(false),
  40. pinnedPeerCertSha256: z.array(z.string()).default([]),
  41. verifyPeerCertByName: z.boolean().default(false),
  42. allowInsecure: z.boolean().default(false),
  43. echConfigList: z.string().default(''),
  44. muxParams: z.string().default(''),
  45. sockoptParams: z.string().default(''),
  46. finalMask: z.string().default(''),
  47. // A comma-separated list of ports/ranges (e.g. "53,443,1000-2000"). Empty = none.
  48. vlessRoute: z
  49. .string()
  50. .trim()
  51. .regex(/^(\d{1,5}(-\d{1,5})?)(\s*,\s*\d{1,5}(-\d{1,5})?)*$/, 'pages.hosts.toasts.badVlessRoute')
  52. .or(z.literal(''))
  53. .default(''),
  54. excludeFromSubTypes: z.array(SubTypeSchema).default([]),
  55. // Visual-only assignment of nodes that resolve from this host (stored, not yet
  56. // wired into routing).
  57. nodeGuids: z.array(z.string()).default([]),
  58. mihomoIpVersion: z.preprocess(
  59. (val) => (val === '' ? undefined : val),
  60. MihomoIpVersionSchema.optional(),
  61. ),
  62. mihomoX25519: z.boolean().default(false),
  63. shuffleHost: z.boolean().default(false),
  64. });
  65. export type HostFormValues = z.infer<typeof HostFormSchema>;
  66. // HostRecord is the loose list/read projection from /panel/api/hosts. Slice and
  67. // free-JSON fields tolerate the backend serializing nil as null.
  68. export const HostRecordSchema = z.object({
  69. id: z.number(),
  70. inboundId: z.number(),
  71. sortOrder: z.number().optional(),
  72. remark: z.string().optional(),
  73. serverDescription: z.string().optional(),
  74. isDisabled: z.boolean().optional(),
  75. isHidden: z.boolean().optional(),
  76. tags: z.array(z.string()).nullish(),
  77. address: z.string().optional(),
  78. port: z.number().optional(),
  79. security: z.string().optional(),
  80. sni: z.string().optional(),
  81. hostHeader: z.string().optional(),
  82. path: z.string().optional(),
  83. alpn: z.array(z.string()).nullish(),
  84. fingerprint: z.string().optional(),
  85. overrideSniFromAddress: z.boolean().optional(),
  86. keepSniBlank: z.boolean().optional(),
  87. pinnedPeerCertSha256: z.array(z.string()).nullish(),
  88. verifyPeerCertByName: z.boolean().optional(),
  89. allowInsecure: z.boolean().optional(),
  90. echConfigList: z.string().optional(),
  91. muxParams: z.unknown().optional(),
  92. sockoptParams: z.unknown().optional(),
  93. finalMask: z.string().optional(),
  94. vlessRoute: z.string().optional(),
  95. excludeFromSubTypes: z.array(z.string()).nullish(),
  96. nodeGuids: z.array(z.string()).nullish(),
  97. mihomoIpVersion: z.string().optional(),
  98. mihomoX25519: z.boolean().optional(),
  99. shuffleHost: z.boolean().optional(),
  100. }).loose();
  101. export type HostRecord = z.infer<typeof HostRecordSchema>;
  102. export const HostListSchema = z.array(HostRecordSchema);