xray.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { z } from 'zod';
  2. import { DnsObjectSchema } from './dns';
  3. import {
  4. BalancerObjectSchema,
  5. BalancerStrategySettingsSchema,
  6. BalancerStrategyTypeSchema,
  7. RuleObjectSchema,
  8. } from './routing';
  9. export const XraySettingsValueSchema = z
  10. .object({
  11. inbounds: z.array(z.unknown()).optional(),
  12. outbounds: z
  13. .array(
  14. z
  15. .object({
  16. tag: z.string().optional(),
  17. protocol: z.string().optional(),
  18. settings: z.unknown().optional(),
  19. streamSettings: z.unknown().optional(),
  20. })
  21. .loose(),
  22. )
  23. .optional(),
  24. routing: z
  25. .object({
  26. rules: z.array(RuleObjectSchema).optional(),
  27. balancers: z.array(BalancerObjectSchema).optional(),
  28. domainStrategy: z.string().optional(),
  29. })
  30. .loose()
  31. .optional(),
  32. dns: DnsObjectSchema.optional(),
  33. log: z.record(z.string(), z.unknown()).optional(),
  34. policy: z
  35. .object({
  36. system: z.record(z.string(), z.boolean()).optional(),
  37. levels: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
  38. })
  39. .loose()
  40. .optional(),
  41. observatory: z.unknown().optional(),
  42. burstObservatory: z.unknown().optional(),
  43. fakedns: z.unknown().optional(),
  44. })
  45. .loose();
  46. export const XrayConfigPayloadSchema = z
  47. .object({
  48. xraySetting: XraySettingsValueSchema,
  49. inboundTags: z.array(z.string()).optional(),
  50. clientReverseTags: z.array(z.string()).optional(),
  51. outboundTestUrl: z.string().optional(),
  52. // Subscription outbounds are injected at runtime (not persisted in xraySetting).
  53. // They are provided here so the UI can display them and use their tags in
  54. // balancers / routing rules.
  55. subscriptionOutbounds: z.array(z.unknown()).optional(),
  56. subscriptionOutboundTags: z.array(z.string()).optional(),
  57. geodataSources: z.array(z.object({ url: z.string(), file: z.string() })).optional(),
  58. })
  59. .loose();
  60. export const OutboundTrafficRowSchema = z.object({
  61. tag: z.string(),
  62. up: z.number(),
  63. down: z.number(),
  64. });
  65. export const OutboundTrafficListSchema = z.array(OutboundTrafficRowSchema);
  66. export const OutboundTestResultSchema = z
  67. .object({
  68. tag: z.string().optional(),
  69. success: z.boolean(),
  70. delay: z.number().optional(),
  71. error: z.string().optional(),
  72. mode: z.string().optional(),
  73. // HTTP-mode extras: status answered by the test URL plus the httptrace
  74. // timing breakdown (dial to local inbound / target TLS via the outbound /
  75. // time to first byte).
  76. httpStatus: z.number().optional(),
  77. connectMs: z.number().optional(),
  78. tlsMs: z.number().optional(),
  79. ttfbMs: z.number().optional(),
  80. endpoints: z
  81. .array(
  82. z
  83. .object({
  84. address: z.string(),
  85. delay: z.number().optional(),
  86. success: z.boolean(),
  87. error: z.string().optional(),
  88. })
  89. .loose(),
  90. )
  91. .optional(),
  92. egress: z
  93. .object({
  94. ipv4: z.string().optional(),
  95. ipv6: z.string().optional(),
  96. country: z.string().optional(),
  97. warp: z.string().optional(),
  98. })
  99. .loose()
  100. .optional(),
  101. })
  102. .loose();
  103. // Batch results from /xray/testOutbounds, aligned with the request order.
  104. export const OutboundTestResultListSchema = z.array(OutboundTestResultSchema);
  105. export const RuleFormSchema = z.object({
  106. enabled: z.boolean(),
  107. comment: z.string(),
  108. domain: z.string(),
  109. ip: z.string(),
  110. port: z.string(),
  111. sourcePort: z.string(),
  112. vlessRoute: z.string(),
  113. network: z.string(),
  114. sourceIP: z.string(),
  115. user: z.string(),
  116. inboundTag: z.array(z.string()),
  117. protocol: z.array(z.string()),
  118. attrs: z.array(z.tuple([z.string(), z.string()])),
  119. outboundTag: z.string(),
  120. balancerTag: z.string(),
  121. });
  122. export const BalancerFormSchema = z.object({
  123. tag: z
  124. .string()
  125. .trim()
  126. .min(1, 'pages.xray.balancerTagRequired')
  127. .refine((val) => !val.startsWith('_bl_'), { error: 'pages.xray.balancer.reservedPrefix' }),
  128. strategy: BalancerStrategyTypeSchema.default('random'),
  129. selector: z.array(z.string()).min(1, 'pages.xray.balancerSelectorRequired'),
  130. fallbackTag: z.string().default(''),
  131. settings: BalancerStrategySettingsSchema.optional(),
  132. });
  133. export const OutboundTagSchema = z
  134. .string()
  135. .trim()
  136. .min(1, 'pages.xray.outboundTagRequired')
  137. .refine((val) => !val.startsWith('_bl_'), { error: 'pages.xray.balancer.reservedPrefix' });
  138. export type BalancerFormValues = z.infer<typeof BalancerFormSchema>;
  139. export type RuleFormValues = z.infer<typeof RuleFormSchema>;
  140. export type XraySettingsValue = z.infer<typeof XraySettingsValueSchema>;
  141. export type XrayConfigPayload = z.infer<typeof XrayConfigPayloadSchema>;
  142. export type OutboundTrafficRow = z.infer<typeof OutboundTrafficRowSchema>;
  143. export type OutboundTestResult = z.infer<typeof OutboundTestResultSchema>;