outbounds-tab-helpers.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import type { TFunction } from 'i18next';
  2. import { OutboundProtocols as Protocols } from '@/schemas/primitives';
  3. import { isUdpOutbound } from '@/hooks/useXraySetting';
  4. import type { OutboundTestMode, OutboundTestState, OutboundTrafficRow } from '@/hooks/useXraySetting';
  5. import type { OutboundRow } from './outbounds-tab-types';
  6. /**
  7. * Translate a table row's positional index into that outbound's index in the
  8. * full, unfiltered outbounds array. The table hides balancer-loopback outbounds
  9. * but keeps each visible row's original index in `key`, so any handler that
  10. * mutates the outbounds array (or probes an outbound by index) must map the
  11. * positional index back through `key` or it operates on the wrong outbound once
  12. * a hidden loopback precedes it.
  13. */
  14. export function originalOutboundIndex(rows: OutboundRow[], positionalIndex: number): number {
  15. const row = rows[positionalIndex];
  16. return row ? row.key : positionalIndex;
  17. }
  18. export function outboundAddresses(o: OutboundRow): string[] {
  19. const settings = o.settings as Record<string, unknown> | undefined;
  20. switch (o.protocol) {
  21. case Protocols.VMess: {
  22. const serverObj = settings?.vnext as Array<{ address: string; port: number }> | undefined;
  23. return serverObj ? serverObj.map((s) => `${s.address}:${s.port}`) : [];
  24. }
  25. case Protocols.VLESS:
  26. return [`${settings?.address || ''}:${settings?.port || ''}`];
  27. case Protocols.HTTP:
  28. case Protocols.Socks:
  29. case Protocols.Shadowsocks:
  30. case Protocols.Trojan: {
  31. const serverObj = settings?.servers as Array<{ address: string; port: number }> | undefined;
  32. return serverObj ? serverObj.map((s) => `${s.address}:${s.port}`) : [];
  33. }
  34. case Protocols.DNS: {
  35. const addr = (settings?.rewriteAddress as string) || (settings?.address as string) || '';
  36. const port = (settings?.rewritePort as string | number) || (settings?.port as string | number) || '';
  37. return addr || port ? [`${addr}:${port}`] : [];
  38. }
  39. case Protocols.Wireguard:
  40. return (((settings?.peers as Array<{ endpoint?: string }>) || []).map((p) => p.endpoint || '').filter(Boolean));
  41. default:
  42. return [];
  43. }
  44. }
  45. export function isUntestable(o: OutboundRow): boolean {
  46. if (!o) return true;
  47. if (o.protocol === Protocols.Blackhole || o.protocol === Protocols.Loopback || o.tag === 'blocked') return true;
  48. // freedom ("direct") and dns aren't proxies — a TCP dial has no endpoint and
  49. // an HTTP probe would only measure the host's own direct reachability, so
  50. // they're untestable in every mode.
  51. if (o.protocol === Protocols.Freedom || o.protocol === Protocols.DNS) return true;
  52. return false;
  53. }
  54. export function showSecurity(security?: string): boolean {
  55. return security === 'tls' || security === 'reality';
  56. }
  57. export function effectiveTestMode(o: unknown, mode: OutboundTestMode): OutboundTestMode {
  58. return mode === 'tcp' && isUdpOutbound(o) ? 'http' : mode;
  59. }
  60. export function testModeLabel(mode: string, t: TFunction): string {
  61. return mode === 'real' ? t('pages.xray.outbound.modeRealDelay') : mode.toUpperCase();
  62. }
  63. export function trafficFor(outboundsTraffic: OutboundTrafficRow[], o: OutboundRow): { up: number; down: number } {
  64. const tr = outboundsTraffic.find((x) => x.tag === o.tag);
  65. return { up: tr?.up || 0, down: tr?.down || 0 };
  66. }
  67. export function countryFlag(country?: string): string {
  68. const code = (country || '').trim().toUpperCase();
  69. if (!/^[A-Z]{2}$/.test(code)) return '';
  70. return String.fromCodePoint(...[...code].map((ch) => 0x1f1e6 + ch.charCodeAt(0) - 65));
  71. }
  72. export function countryName(country?: string, locale?: string): string {
  73. const code = (country || '').trim().toUpperCase();
  74. if (!/^[A-Z]{2}$/.test(code)) return '';
  75. try {
  76. return new Intl.DisplayNames(locale ? [locale] : undefined, { type: 'region' }).of(code) || code;
  77. } catch {
  78. return code;
  79. }
  80. }
  81. export function isTesting<K extends string | number>(states: Record<K, OutboundTestState>, idx: K): boolean {
  82. return !!states?.[idx]?.testing;
  83. }
  84. export function testResult<K extends string | number>(states: Record<K, OutboundTestState>, idx: K) {
  85. return states?.[idx]?.result || null;
  86. }