-
-
+ setPorts((prev) => ({ ...prev, mixedPort }))
+ }
+ />
+ {LISTENERS.filter(({ visible }) => visible).map(({ key, label }) => (
+ updateListener(key, { port })}
+ onEnabledChange={(enabled) => updateListener(key, { enabled })}
/>
-
-
- setMixedPort(+e.target.value?.replace(/\D+/, '').slice(0, 5))
- }
- slotProps={{ htmlInput: { style: { fontSize: 12 } } }}
- />
- setMixedPort(generateRandomPort())}
- title={t('settings.modals.clashPort.actions.random')}
- sx={{ mr: 0.5 }}
- >
-
-
-
-
-
-
-
-
-
-
- setSocksPort(+e.target.value?.replace(/\D+/, '').slice(0, 5))
- }
- disabled={!socksEnabled}
- slotProps={{ htmlInput: { style: { fontSize: 12 } } }}
- />
- setSocksPort(generateRandomPort())}
- title={t('settings.modals.clashPort.actions.random')}
- disabled={!socksEnabled}
- sx={{ mr: 0.5 }}
- >
-
-
- setSocksEnabled(c)}
- sx={{ ml: 0.5 }}
- />
-
-
-
-
-
-
-
- setHttpPort(+e.target.value?.replace(/\D+/, '').slice(0, 5))
- }
- disabled={!httpEnabled}
- slotProps={{ htmlInput: { style: { fontSize: 12 } } }}
- />
- setHttpPort(generateRandomPort())}
- title={t('settings.modals.clashPort.actions.random')}
- disabled={!httpEnabled}
- sx={{ mr: 0.5 }}
- >
-
-
- setHttpEnabled(c)}
- sx={{ ml: 0.5 }}
- />
-
-
-
- {OS !== 'windows' && (
-
-
-
-
- setRedirPort(+e.target.value?.replace(/\D+/, '').slice(0, 5))
- }
- disabled={!redirEnabled}
- slotProps={{ htmlInput: { style: { fontSize: 12 } } }}
- />
- setRedirPort(generateRandomPort())}
- title={t('settings.modals.clashPort.actions.random')}
- disabled={!redirEnabled}
- sx={{ mr: 0.5 }}
- >
-
-
- setRedirEnabled(c)}
- sx={{ ml: 0.5 }}
- />
-
-
- )}
-
- {OS === 'linux' && (
-
-
-
-
- setTproxyPort(+e.target.value?.replace(/\D+/, '').slice(0, 5))
- }
- disabled={!tproxyEnabled}
- slotProps={{ htmlInput: { style: { fontSize: 12 } } }}
- />
- setTproxyPort(generateRandomPort())}
- title={t('settings.modals.clashPort.actions.random')}
- disabled={!tproxyEnabled}
- sx={{ mr: 0.5 }}
- >
-
-
- setTproxyEnabled(c)}
- sx={{ ml: 0.5 }}
- />
-
-
- )}
+ ))}
)
diff --git a/src/locales/ar/settings.json b/src/locales/ar/settings.json
index 456b10271..a92d77c6d 100644
--- a/src/locales/ar/settings.json
+++ b/src/locales/ar/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "المنفذ المستخدم حاليًا: {{port}}",
+ "duplicatePort": "يجب أن تستخدم المستمعات المفعّلة منافذ مختلفة",
+ "invalidPort": "أدخل منفذًا بين 1 و65535",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "المنفذ {{oldPort}} مستخدم، لذا تعمل هذه الجلسة على {{newPort}}. لم يتغيّر إعدادك وسيُعاد تجربة {{oldPort}} عند التشغيل التالي.",
diff --git a/src/locales/de/settings.json b/src/locales/de/settings.json
index 0498d2d0e..a31ec24ff 100644
--- a/src/locales/de/settings.json
+++ b/src/locales/de/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "Aktuell verwendeter Port: {{port}}",
+ "duplicatePort": "Aktivierte Listener müssen unterschiedliche Ports verwenden",
+ "invalidPort": "Geben Sie einen Port zwischen 1 und 65535 ein",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "Port {{oldPort}} ist belegt, daher läuft diese Sitzung auf {{newPort}}. Die Einstellung bleibt unverändert und {{oldPort}} wird beim nächsten Start erneut versucht.",
diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json
index c614ef0b7..83fc780c8 100644
--- a/src/locales/en/settings.json
+++ b/src/locales/en/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "Currently using port {{port}}",
+ "duplicatePort": "Enabled listeners must use different ports",
+ "invalidPort": "Enter a port between 1 and 65535",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "Port {{oldPort}} was in use, so this session runs on {{newPort}}. Your setting is unchanged and {{oldPort}} is retried on the next start.",
diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json
index 2a3ba8c6b..4d00690cd 100644
--- a/src/locales/es/settings.json
+++ b/src/locales/es/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "Puerto actual: {{port}}",
+ "duplicatePort": "Los servicios de escucha habilitados deben usar puertos diferentes",
+ "invalidPort": "Introduce un puerto entre 1 y 65535",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "El puerto {{oldPort}} está en uso, por lo que esta sesión usa {{newPort}}. Tu configuración no ha cambiado y se volverá a intentar {{oldPort}} en el próximo inicio.",
diff --git a/src/locales/fa/settings.json b/src/locales/fa/settings.json
index 3332e07bd..573c5c2c3 100644
--- a/src/locales/fa/settings.json
+++ b/src/locales/fa/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "درگاه فعلی: {{port}}",
+ "duplicatePort": "شنوندههای فعال باید از درگاههای متفاوت استفاده کنند",
+ "invalidPort": "شمارهٔ درگاهی بین 1 و 65535 وارد کنید",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "پورت {{oldPort}} در حال استفاده بود، بنابراین این اجرا از {{newPort}} استفاده میکند. تنظیم شما تغییر نکرده و در اجرای بعدی {{oldPort}} دوباره امتحان میشود.",
diff --git a/src/locales/id/settings.json b/src/locales/id/settings.json
index d523c89c4..e7d425c08 100644
--- a/src/locales/id/settings.json
+++ b/src/locales/id/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "Port yang sedang digunakan: {{port}}",
+ "duplicatePort": "Listener yang diaktifkan harus menggunakan port yang berbeda",
+ "invalidPort": "Masukkan port antara 1 dan 65535",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "Port {{oldPort}} sedang digunakan, jadi sesi ini menggunakan {{newPort}}. Pengaturan Anda tidak berubah dan {{oldPort}} akan dicoba lagi saat mulai berikutnya.",
diff --git a/src/locales/jp/settings.json b/src/locales/jp/settings.json
index 1259f1409..6a8530aa4 100644
--- a/src/locales/jp/settings.json
+++ b/src/locales/jp/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "現在使用中のポート:{{port}}",
+ "duplicatePort": "有効なリスナーには異なるポートを指定してください",
+ "invalidPort": "1〜65535のポート番号を入力してください",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "ポート {{oldPort}} が使用中のため、今回は {{newPort}} で動作します。設定は変更されておらず、次回起動時に {{oldPort}} を再試行します。",
diff --git a/src/locales/ko/settings.json b/src/locales/ko/settings.json
index 9c60c69db..241f4f9fa 100644
--- a/src/locales/ko/settings.json
+++ b/src/locales/ko/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "현재 사용 중인 포트: {{port}}",
+ "duplicatePort": "활성화된 리스너는 서로 다른 포트를 사용해야 합니다",
+ "invalidPort": "1에서 65535 사이의 포트 번호를 입력하세요",
"saved": "포트 설정이 저장되었습니다",
"saveFailed": "포트 설정 저장에 실패했습니다",
"automaticFallback": "포트 {{oldPort}}이(가) 사용 중이어서 이번 실행은 {{newPort}}을(를) 사용합니다. 설정은 변경되지 않았으며 다음 시작 시 {{oldPort}}을(를) 다시 시도합니다.",
diff --git a/src/locales/ru/settings.json b/src/locales/ru/settings.json
index 6111fd6a9..e8541bdd8 100644
--- a/src/locales/ru/settings.json
+++ b/src/locales/ru/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Порт {{port}} уже используется",
+ "runningPort": "Сейчас используется порт {{port}}",
+ "duplicatePort": "Включённые слушатели должны использовать разные порты",
+ "invalidPort": "Введите порт от 1 до 65535",
"saved": "Настройки портов сохранены",
"saveFailed": "Не удалось сохранить настройки портов",
"automaticFallback": "Порт {{oldPort}} занят, поэтому этот запуск использует {{newPort}}. Настройка не изменена, при следующем запуске {{oldPort}} будет опробован снова.",
diff --git a/src/locales/tr/settings.json b/src/locales/tr/settings.json
index 958cd04a4..262f730c0 100644
--- a/src/locales/tr/settings.json
+++ b/src/locales/tr/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "{{port}} portu zaten kullanımda",
+ "runningPort": "Şu anda kullanılan port: {{port}}",
+ "duplicatePort": "Etkin dinleyiciler farklı portlar kullanmalıdır",
+ "invalidPort": "1 ile 65535 arasında bir port girin",
"saved": "Port ayarları kaydedildi",
"saveFailed": "Port ayarları kaydedilemedi",
"automaticFallback": "{{oldPort}} portu kullanımda olduğu için bu oturum {{newPort}} portunu kullanıyor. Ayarınız değişmedi ve sonraki başlatmada {{oldPort}} yeniden denenecek.",
diff --git a/src/locales/tt/settings.json b/src/locales/tt/settings.json
index 1bb591f7c..774b90f0b 100644
--- a/src/locales/tt/settings.json
+++ b/src/locales/tt/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "Хәзер {{port}} порты кулланыла",
+ "duplicatePort": "Кабызылган тыңлаучылар төрле портлар кулланырга тиеш",
+ "invalidPort": "1 белән 65535 арасындагы порт номерын кертегез",
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings",
"automaticFallback": "Port {{oldPort}} was in use, so this session runs on {{newPort}}. Your setting is unchanged and {{oldPort}} is retried on the next start.",
diff --git a/src/locales/zh/settings.json b/src/locales/zh/settings.json
index 7316ef9b7..38e37127d 100644
--- a/src/locales/zh/settings.json
+++ b/src/locales/zh/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "端口 {{port}} 已被占用",
+ "runningPort": "当前运行端口:{{port}}",
+ "duplicatePort": "已启用的监听端口不能重复",
+ "invalidPort": "请输入 1 至 65535 之间的端口号",
"saved": "端口设置已保存",
"saveFailed": "端口设置保存失败",
"automaticFallback": "端口 {{oldPort}} 已被占用,本次运行改用 {{newPort}}。设置未更改,下次启动会重新尝试 {{oldPort}}。",
diff --git a/src/locales/zhtw/settings.json b/src/locales/zhtw/settings.json
index 92fe5138f..35e956b28 100644
--- a/src/locales/zhtw/settings.json
+++ b/src/locales/zhtw/settings.json
@@ -290,6 +290,9 @@
},
"messages": {
"portInUse": "Port {{port}} is already in use",
+ "runningPort": "目前執行中的連接埠:{{port}}",
+ "duplicatePort": "已啟用的監聽連接埠不能重複",
+ "invalidPort": "請輸入 1 至 65535 之間的連接埠號碼",
"saved": "連結埠設定已儲存",
"saveFailed": "連結埠設定儲存失敗",
"automaticFallback": "連結埠 {{oldPort}} 已被佔用,本次執行改用 {{newPort}}。設定未變更,下次啟動會重新嘗試 {{oldPort}}。",
diff --git a/src/services/bus/create-bus-handlers.test.ts b/src/services/bus/create-bus-handlers.test.ts
index 763f694e0..43d910e40 100644
--- a/src/services/bus/create-bus-handlers.test.ts
+++ b/src/services/bus/create-bus-handlers.test.ts
@@ -182,34 +182,6 @@ it('offers service reinstallation for a startup path refusal even before listene
})
})
-it('routes every event exactly once through one handler', async () => {
- const { createEventBus } = await import('./create-bus-handlers')
- const dispatch = vi.fn<(action: AppStoreAction) => void>()
- const bus = createEventBus({
- dispatch,
- handleNotice: vi.fn(),
- revalidateKeys: vi.fn(),
- revalidateProfiles: vi.fn(),
- refreshProxyView: vi.fn(),
- readPendingFailures: vi.fn(),
- readRunState: vi.fn(),
- })
-
- bus.handlers['verge://run-state-changed']({ mode: 'Service' } as RunState)
- bus.handlers['profile-update-started']({ uid: 'p1' })
- bus.handlers['profile-update-completed']({ uid: 'p1' })
- bus.handlers['verge://timer-updated']('p1')
- bus.handlers['verge://test-all'](null)
-
- expect(dispatch.mock.calls.map(([action]) => action.type)).toEqual([
- 'runState/loaded',
- 'profileUpdate/started',
- 'profileUpdate/completed',
- 'profileUpdate/timerTick',
- 'testAll/requested',
- ])
-})
-
it('explains a startup core rejection instead of the generic fallback notice', async () => {
const reason =
'approved core was rejected: core path "C:\\" has an untrusted write ACE'
diff --git a/src/services/bus/use-event-bus.test.ts b/src/services/bus/use-event-bus.test.ts
index 34324b3e7..a0deed42c 100644
--- a/src/services/bus/use-event-bus.test.ts
+++ b/src/services/bus/use-event-bus.test.ts
@@ -46,21 +46,22 @@ it('registers exactly one listener per contract event plus test-all', () => {
useEventBus(handleNotice)
const names = listen.mock.calls.map(([name]) => name)
- expect(names).toEqual([
- 'verge://refresh-clash-config',
- 'verge://refresh-verge-config',
- 'verge://refresh-profiles',
- 'verge://refresh-proxy-config',
- 'verge://notice-message',
- 'profile-changed',
- 'verge://timer-updated',
- 'profile-update-started',
- 'profile-update-completed',
- 'verge://run-state-changed',
- 'verge://pending-failures-changed',
- 'verge://test-all',
- ])
- expect(new Set(names).size).toBe(names.length)
+ expect(names.sort()).toEqual(
+ [
+ 'verge://refresh-clash-config',
+ 'verge://refresh-verge-config',
+ 'verge://refresh-profiles',
+ 'verge://refresh-proxy-config',
+ 'verge://notice-message',
+ 'profile-changed',
+ 'verge://timer-updated',
+ 'profile-update-started',
+ 'profile-update-completed',
+ 'verge://run-state-changed',
+ 'verge://pending-failures-changed',
+ 'verge://test-all',
+ ].sort(),
+ )
})
it('drains event-only state only after every listener is registered', async () => {
diff --git a/src/types/generated/i18n-keys.ts b/src/types/generated/i18n-keys.ts
index 61ea582d8..4e566dce4 100644
--- a/src/types/generated/i18n-keys.ts
+++ b/src/types/generated/i18n-keys.ts
@@ -571,6 +571,9 @@ export const translationKeys = [
'settings.modals.clashPort.fields.tproxy',
'settings.modals.clashPort.actions.random',
'settings.modals.clashPort.messages.portInUse',
+ 'settings.modals.clashPort.messages.runningPort',
+ 'settings.modals.clashPort.messages.duplicatePort',
+ 'settings.modals.clashPort.messages.invalidPort',
'settings.modals.clashPort.messages.saved',
'settings.modals.clashPort.messages.saveFailed',
'settings.modals.clashPort.messages.automaticFallback',
diff --git a/src/types/generated/i18n-resources.ts b/src/types/generated/i18n-resources.ts
index ce45ea218..87aba3645 100644
--- a/src/types/generated/i18n-resources.ts
+++ b/src/types/generated/i18n-resources.ts
@@ -962,9 +962,12 @@ export interface TranslationResources {
messages: {
automaticFallback: string
automaticFallbackFailed: string
+ duplicatePort: string
+ invalidPort: string
portInUse: string
portTooHigh: string
portTooLow: string
+ runningPort: string
saved: string
saveFailed: string
}