xhttp.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { z } from 'zod';
  2. import { WsHeaderMapSchema } from '@/schemas/protocols/stream/ws';
  3. export const XHttpModeSchema = z.enum(['auto', 'packet-up', 'stream-up', 'stream-one']);
  4. export type XHttpMode = z.infer<typeof XHttpModeSchema>;
  5. // xHTTP (SplitHTTPConfig) is xray-core's modern stream-multiplexed transport.
  6. // The field set is large because the schema mirrors what the server-side
  7. // listener reads — plus a few client-only fields (`uplinkHTTPMethod`,
  8. // `headers`) the panel embeds into share-link `extra` blobs even though the
  9. // server ignores them at runtime. Outbound has additional fields (uplinkChunk
  10. // sizes, noGRPCHeader, scMinPostsIntervalMs, xmux, downloadSettings) which
  11. // belong on the outbound class instead, not modeled here.
  12. // XMUX is the connection-multiplexing layer xHTTP uses to fan out
  13. // parallel requests over a small pool of upstream connections. Fields
  14. // are strings because they accept dash-range values like '16-32'.
  15. // maxConcurrency and maxConnections are mutually exclusive strategies
  16. // (xray-core rejects a config that sets both), so the bare schema
  17. // default keeps only one of them non-zero — a non-zero maxConnections
  18. // default resurrected on load made every re-save silently delete the
  19. // user's maxConcurrency.
  20. export const XHttpXmuxSchema = z.object({
  21. maxConcurrency: z.string().default('16-32'),
  22. maxConnections: z.union([z.string(), z.number()]).default(0),
  23. cMaxReuseTimes: z.union([z.string(), z.number()]).default(0),
  24. hMaxRequestTimes: z.string().default('600-900'),
  25. hMaxReusableSecs: z.string().default('1800-3000'),
  26. hKeepAlivePeriod: z.number().int().min(0).default(0),
  27. });
  28. export type XHttpXmux = z.infer<typeof XHttpXmuxSchema>;
  29. // Seed for freshly enabling XMUX on a config that had no xmux block:
  30. // mirrors xray-core's own maxConnections fallback rather than the
  31. // concurrency strategy. v26.7.28 lowered that fallback from 6 to 3 for
  32. // anti-TSPU, so track it here to keep a fresh panel config matching what
  33. // the core would have picked on its own.
  34. export const XMUX_FRESH_DEFAULTS: XHttpXmux = {
  35. ...XHttpXmuxSchema.parse({}),
  36. maxConcurrency: '',
  37. maxConnections: 3,
  38. };
  39. // Predefined sessionIDTable names xray-core accepts as a shorthand for a
  40. // charset (splithttp.PredefinedTable, xray-core #6258). A literal ASCII
  41. // charset string is also accepted.
  42. export const XHTTP_SESSION_ID_TABLES = [
  43. 'ALPHABET', 'Alphabet', 'BASE36', 'Base62', 'HEX',
  44. 'alphabet', 'base36', 'hex', 'number',
  45. ] as const;
  46. // xray-core #6258 renamed sessionPlacement/sessionKey to
  47. // sessionIDPlacement/sessionIDKey (no fallback kept in core) and added
  48. // sessionIDTable/sessionIDLength. Lift any legacy keys persisted by an older
  49. // panel onto the new names so a saved inbound/outbound never silently loses
  50. // its session setting, then drop the legacy keys so we never emit both.
  51. function migrateLegacyXhttp(v: unknown): unknown {
  52. if (v == null || typeof v !== 'object' || Array.isArray(v)) return v;
  53. const o = { ...(v as Record<string, unknown>) };
  54. if (o.sessionIDPlacement === undefined && o.sessionPlacement !== undefined) {
  55. o.sessionIDPlacement = o.sessionPlacement;
  56. }
  57. if (o.sessionIDKey === undefined && o.sessionKey !== undefined) {
  58. o.sessionIDKey = o.sessionKey;
  59. }
  60. delete o.sessionPlacement;
  61. delete o.sessionKey;
  62. return o;
  63. }
  64. export const XHttpStreamSettingsSchema = z.preprocess(migrateLegacyXhttp, z.object({
  65. path: z.string().default('/'),
  66. host: z.string().default(''),
  67. mode: XHttpModeSchema.default('auto'),
  68. xPaddingBytes: z.string().default('100-1000'),
  69. xPaddingObfsMode: z.boolean().default(false),
  70. xPaddingKey: z.string().default(''),
  71. xPaddingHeader: z.string().default(''),
  72. xPaddingPlacement: z.string().default(''),
  73. xPaddingMethod: z.string().default(''),
  74. sessionIDPlacement: z.string().default(''),
  75. sessionIDKey: z.string().default(''),
  76. // sessionIDTable: a predefined name (XHTTP_SESSION_ID_TABLES) or a literal
  77. // ASCII charset. sessionIDLength: dash-range string (e.g. '8-16'); only
  78. // honored when a table is set. xray-core enforces the room-size minimum.
  79. sessionIDTable: z.string().default(''),
  80. sessionIDLength: z.string().default(''),
  81. seqPlacement: z.string().default(''),
  82. seqKey: z.string().default(''),
  83. uplinkDataPlacement: z.string().default(''),
  84. uplinkDataKey: z.string().default(''),
  85. // Empty default on purpose: xray-core already defaults to 1MB/30ms, and
  86. // baking the literal values into every config and share link gives DPI a
  87. // stable fingerprint (#5141 — TSPU keys on scMinPostsIntervalMs=30).
  88. scMaxEachPostBytes: z.string().default(''),
  89. noSSEHeader: z.boolean().default(false),
  90. scMaxBufferedPosts: z.number().int().min(0).default(30),
  91. scStreamUpServerSecs: z.string().default('20-80'),
  92. serverMaxHeaderBytes: z.number().int().min(0).default(0),
  93. uplinkHTTPMethod: z.string().default(''),
  94. headers: WsHeaderMapSchema.default({}),
  95. // Client-side fields stored on inbound for subscription propagation.
  96. // The server listener ignores them at runtime, but the panel embeds
  97. // them in share-link `extra` blobs so the same xhttp config can
  98. // round-trip on both sides.
  99. // - scMinPostsIntervalMs: preserved when non-default (stripped at '' or '30')
  100. // - uplinkChunkSize & noGRPCHeader: outbound-only; stripped from inbound wire
  101. scMinPostsIntervalMs: z.string().default(''),
  102. uplinkChunkSize: z.number().int().min(0).default(0),
  103. noGRPCHeader: z.boolean().default(false),
  104. xmux: XHttpXmuxSchema.optional(),
  105. // UI-only toggle controlling whether the XMUX sub-form is expanded.
  106. // Never present on the wire — outbound modal strips it via the
  107. // form-to-wire adapter.
  108. enableXmux: z.boolean().default(false),
  109. }));
  110. export type XHttpStreamSettings = z.infer<typeof XHttpStreamSettingsSchema>;