tuicConfig.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import type { HostEndpoint } from '@/lib/hosts/host-link';
  2. import { formatInboundLabel } from '@/lib/inbounds/label';
  3. import { preferPublicHost, resolveShareHost } from '@/lib/xray/inbound-link';
  4. import type { ClientRecord, InboundOption } from '@/hooks/useClients';
  5. export function isTuicClient(client: ClientRecord | null | undefined): boolean {
  6. if (!client) return false;
  7. return !!(client.uuid && client.password);
  8. }
  9. export function findTuicInbound(
  10. client: ClientRecord | null | undefined,
  11. inboundsById: Record<number, InboundOption>,
  12. ): InboundOption | undefined {
  13. return (client?.inboundIds || [])
  14. .map((id) => inboundsById[id])
  15. .find((ib) => ib?.protocol === 'tuic');
  16. }
  17. export function buildTuicClientConfig(
  18. client: ClientRecord,
  19. inbound: InboundOption | undefined,
  20. host = window.location.hostname,
  21. publicHost = '',
  22. hostEndpoint?: HostEndpoint,
  23. ): string {
  24. const endpointHost =
  25. hostEndpoint?.dest ||
  26. resolveShareHost(inbound ?? {}, inbound?.nodeAddress ?? '', preferPublicHost(host, publicHost));
  27. const inboundName = inbound ? formatInboundLabel(inbound.tag, inbound.remark) : '';
  28. const remark =
  29. [inboundName, hostEndpoint?.remark, client.email].filter(Boolean).join(' - ') || 'tuic-client';
  30. // A Host's SNI/ALPN/insecure override the inbound's, as the backend buildTuicProxy does.
  31. const tuicServer = inbound?.tuicServer;
  32. const alpn = hostEndpoint?.alpn?.length
  33. ? hostEndpoint.alpn
  34. : Array.isArray(tuicServer?.alpn) && tuicServer.alpn.length > 0
  35. ? tuicServer.alpn
  36. : ['h3', 'spdy/3.1'];
  37. const sni = hostEndpoint?.sni || tuicServer?.sni || endpointHost;
  38. const cc = tuicServer?.congestion_control || 'bbr';
  39. const udpRelay = tuicServer?.udp_relay_mode || 'native';
  40. const reduceRtt = tuicServer?.zero_rtt_handshake ?? true;
  41. const yamlQuote = (v: string) => JSON.stringify(v ?? '');
  42. const lines = [
  43. `# TUIC v5 Client Configuration (Clash / Mihomo / Clash Verge)`,
  44. `# ${remark}`,
  45. `proxies:`,
  46. ` - name: ${yamlQuote(remark)}`,
  47. ` type: tuic`,
  48. ` server: ${endpointHost}`,
  49. ` port: ${hostEndpoint?.port || inbound?.port || 8443}`,
  50. ` uuid: ${client.uuid || ''}`,
  51. ` password: ${yamlQuote(client.password || '')}`,
  52. ` alpn:`,
  53. ...alpn.map((a: string) => ` - ${a}`),
  54. ` sni: ${/["'\\#\s]/.test(sni) ? yamlQuote(sni) : sni}`,
  55. ` congestion-controller: ${cc}`,
  56. ` udp-relay-mode: ${udpRelay}`,
  57. ` reduce-rtt: ${reduceRtt}`,
  58. ];
  59. if (hostEndpoint?.allowInsecure) lines.push(' skip-cert-verify: true');
  60. return lines.join('\n');
  61. }