helpers.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { isSSMultiUser } from '@/lib/xray/protocol-capabilities';
  2. import { coerceInboundJsonField } from '@/models/dbinbound';
  3. import type { DBInboundRecord, StreamHints } from './types';
  4. export function readStreamHints(streamSettings: unknown): StreamHints {
  5. const stream = coerceInboundJsonField(streamSettings) as { network?: string; security?: string };
  6. return {
  7. network: stream.network ?? '',
  8. isTls: stream.security === 'tls',
  9. isReality: stream.security === 'reality',
  10. };
  11. }
  12. // Display label for a network value. All known transports render in
  13. // upper-case for visual consistency with the TCP/UDP/TLS/Reality tags
  14. // already shown alongside; compound names (`httpupgrade`, `splithttp`,
  15. // `xhttp`) get a tiny touch of casing so they don't read as one word.
  16. export function networkLabel(network: string): string {
  17. const n = (network || '').toLowerCase();
  18. if (!n) return 'TCP';
  19. switch (n) {
  20. case 'httpupgrade': return 'HTTPUpgrade';
  21. case 'splithttp': return 'SplitHTTP';
  22. case 'xhttp': return 'XHTTP';
  23. }
  24. return n.toUpperCase();
  25. }
  26. // Returns the underlying L4 protocol for transports whose name isn't
  27. // already TCP/UDP. `kcp` and `quic` both ride on UDP; everything else
  28. // (`ws`, `grpc`, `http`, `httpupgrade`, `xhttp`) is TCP-based and gets
  29. // no extra tag (the transport name implies TCP).
  30. export function networkL4(network: string): 'UDP' | '' {
  31. const n = (network || '').toLowerCase();
  32. if (n === 'kcp' || n === 'quic') return 'UDP';
  33. return '';
  34. }
  35. // Shadowsocks settings.network ("tcp" / "udp" / "tcp,udp") and Tunnel
  36. // settings.allowedNetwork (same shape, different field name) both carry
  37. // the L4 transport list independent of streamSettings. Returns a
  38. // comma-separated label.
  39. export function commaNetworkLabel(raw: string): string {
  40. const parts = (raw || 'tcp').toLowerCase().split(',').map((p) => p.trim()).filter(Boolean);
  41. if (parts.length === 0) return 'TCP';
  42. return parts.map(networkLabel).join(',');
  43. }
  44. export function shadowsocksNetworkLabel(settings: unknown): string {
  45. return commaNetworkLabel(readSettings(settings).network || '');
  46. }
  47. export function tunnelNetworkLabel(settings: unknown): string {
  48. return commaNetworkLabel(readSettings(settings).allowedNetwork || '');
  49. }
  50. // Mixed (socks+http combo) is always TCP at L4; settings.udp=true adds
  51. // UDP-associate support on the same port (SOCKS5 UDP).
  52. export function mixedNetworkLabel(settings: unknown): string {
  53. const st = coerceInboundJsonField(settings) as { udp?: boolean };
  54. return st.udp ? 'TCP,UDP' : 'TCP';
  55. }
  56. export function readSettings(settings: unknown): { method?: string; network?: string; allowedNetwork?: string } {
  57. return coerceInboundJsonField(settings) as { method?: string; network?: string; allowedNetwork?: string };
  58. }
  59. export function isInboundMultiUser(record: { protocol: string; settings: unknown }): boolean {
  60. switch (record.protocol) {
  61. case 'vmess':
  62. case 'vless':
  63. case 'trojan':
  64. case 'hysteria':
  65. case 'mtproto':
  66. case 'wireguard':
  67. return true;
  68. case 'shadowsocks':
  69. return isSSMultiUser({ protocol: 'shadowsocks', settings: readSettings(record.settings) });
  70. default:
  71. return false;
  72. }
  73. }
  74. export function showQrCodeMenu(dbInbound: DBInboundRecord): boolean {
  75. if (dbInbound.isSS) {
  76. return !isSSMultiUser({ protocol: 'shadowsocks', settings: readSettings(dbInbound.settings) });
  77. }
  78. return false;
  79. }