feat(nord): support multi-server NordLynx outbounds (#6311)

* feat(nord): support multi-server NordLynx outbounds

* fix(nord): address verified PR review findings

Tighten the NordVPN multi-outbound implementation and its regression
coverage based on the verified review feedback.

- remove the redundant Xray validation test that duplicated the base
  branch and did not exercise multiple outbounds
- make NordModal tests wait for server loading and assert the modal close
  callback, duplicate-server state, and endpoint behavior
- add coverage for resolving the NordLynx public key from technology
  metadata instead of a numeric technology ID
- use a real httptest server for Nord integration tests through an
  injectable API base URL
- represent the All Cities sentinel consistently as null and reset it
  when a country changes

The existing NordVPN API contracts and persisted outbound schema remain
unchanged.
This commit is contained in:
Masterain
2026-09-03 02:20:10 +08:00
committed by GitHub
parent f727d04f65
commit f9cfd87cb2
23 changed files with 1186 additions and 215 deletions
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "لم يتم العثور على خوادم للدولة المحددة",
"noPublicKey": "الخادم المحدد لا يُعلن عن مفتاح NordLynx العام.",
"outboundAdded": "تمت إضافة صادر NordVPN",
"outboundUpdated": "تم تحديث صادر NordVPN"
"outboundUpdated": "تم تحديث صادر NordVPN",
"serverLoad": "حمل الخادم",
"addedServers": "الخوادم المضافة",
"alreadyAdded": "هذا الخادم موجود بالفعل في قائمة الصادرات. استخدم {reset} لتحديث مفتاحه."
},
"warp": {
"changeIp": "تغيير الـ IP",
+4 -1
View File
@@ -1970,7 +1970,10 @@
"noServers": "No servers found for the selected country",
"noPublicKey": "Selected server does not advertise a NordLynx public key.",
"outboundAdded": "NordVPN outbound added",
"outboundUpdated": "NordVPN outbound updated"
"outboundUpdated": "NordVPN outbound updated",
"serverLoad": "Server load",
"addedServers": "Added servers",
"alreadyAdded": "This server is already in the outbound list. Use {reset} to refresh its key."
},
"warp": {
"changeIp": "Change IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "No se encontraron servidores para el país seleccionado",
"noPublicKey": "El servidor seleccionado no anuncia una clave pública NordLynx.",
"outboundAdded": "Salida NordVPN añadida",
"outboundUpdated": "Salida NordVPN actualizada"
"outboundUpdated": "Salida NordVPN actualizada",
"serverLoad": "Carga del servidor",
"addedServers": "Servidores añadidos",
"alreadyAdded": "Este servidor ya está en la lista de salidas. Usa {reset} para actualizar su clave."
},
"warp": {
"changeIp": "Cambiar IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "سروری برای کشور انتخابی پیدا نشد",
"noPublicKey": "سرور انتخابی کلید عمومی NordLynx اعلام نمی‌کند.",
"outboundAdded": "خروجی NordVPN اضافه شد",
"outboundUpdated": "خروجی NordVPN به‌روزرسانی شد"
"outboundUpdated": "خروجی NordVPN به‌روزرسانی شد",
"serverLoad": "بار سرور",
"addedServers": "سرورهای اضافه‌شده",
"alreadyAdded": "این سرور از قبل در فهرست خروجی‌ها وجود دارد. برای تازه‌سازی کلید آن از {reset} استفاده کنید."
},
"warp": {
"changeIp": "تغییر IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Tidak ada server ditemukan untuk negara yang dipilih",
"noPublicKey": "Server yang dipilih tidak mengumumkan kunci publik NordLynx.",
"outboundAdded": "Outbound NordVPN ditambahkan",
"outboundUpdated": "Outbound NordVPN diperbarui"
"outboundUpdated": "Outbound NordVPN diperbarui",
"serverLoad": "Beban server",
"addedServers": "Server yang ditambahkan",
"alreadyAdded": "Server ini sudah ada dalam daftar outbound. Gunakan {reset} untuk memperbarui kuncinya."
},
"warp": {
"changeIp": "Ganti IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "選択した国のサーバーが見つかりません",
"noPublicKey": "選択したサーバーは NordLynx 公開鍵を公開していません。",
"outboundAdded": "NordVPN アウトバウンドを追加しました",
"outboundUpdated": "NordVPN アウトバウンドを更新しました"
"outboundUpdated": "NordVPN アウトバウンドを更新しました",
"serverLoad": "サーバー負荷",
"addedServers": "追加済みサーバー",
"alreadyAdded": "このサーバーはすでにアウトバウンド一覧にあります。{reset} で鍵を更新してください。"
},
"warp": {
"changeIp": "IP を変更",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Nenhum servidor encontrado para o país selecionado",
"noPublicKey": "O servidor selecionado não anuncia uma chave pública NordLynx.",
"outboundAdded": "Saída NordVPN adicionada",
"outboundUpdated": "Saída NordVPN atualizada"
"outboundUpdated": "Saída NordVPN atualizada",
"serverLoad": "Carga do servidor",
"addedServers": "Servidores adicionados",
"alreadyAdded": "Este servidor já está na lista de saídas. Use {reset} para atualizar sua chave."
},
"warp": {
"changeIp": "Alterar IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Серверов для выбранной страны не найдено",
"noPublicKey": "Выбранный сервер не сообщает публичный ключ NordLynx.",
"outboundAdded": "Исходящий NordVPN добавлен",
"outboundUpdated": "Исходящий NordVPN обновлён"
"outboundUpdated": "Исходящий NordVPN обновлён",
"serverLoad": "Нагрузка сервера",
"addedServers": "Добавленные серверы",
"alreadyAdded": "Этот сервер уже есть в списке исходящих подключений. Используйте {reset}, чтобы обновить его ключ."
},
"warp": {
"changeIp": "Сменить IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Seçilen ülke için sunucu bulunamadı.",
"noPublicKey": "Seçilen sunucu NordLynx genel anahtarı yayınlamıyor.",
"outboundAdded": "NordVPN giden bağlantı eklendi.",
"outboundUpdated": "NordVPN giden bağlantı güncellendi."
"outboundUpdated": "NordVPN giden bağlantı güncellendi.",
"serverLoad": "Sunucu yükü",
"addedServers": "Eklenen sunucular",
"alreadyAdded": "Bu sunucu zaten giden bağlantı listesinde. Anahtarını yenilemek için {reset} kullanın."
},
"warp": {
"changeIp": "IP Değiştir",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Серверів для обраної країни не знайдено",
"noPublicKey": "Обраний сервер не повідомляє публічного ключа NordLynx.",
"outboundAdded": "Вихідний NordVPN додано",
"outboundUpdated": "Вихідний NordVPN оновлено"
"outboundUpdated": "Вихідний NordVPN оновлено",
"serverLoad": "Навантаження сервера",
"addedServers": "Додані сервери",
"alreadyAdded": "Цей сервер уже є у списку вихідних підключень. Використайте {reset}, щоб оновити його ключ."
},
"warp": {
"changeIp": "Змінити IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "Không tìm thấy máy chủ cho quốc gia đã chọn",
"noPublicKey": "Máy chủ đã chọn không công bố khóa công khai NordLynx.",
"outboundAdded": "Đã thêm outbound NordVPN",
"outboundUpdated": "Đã cập nhật outbound NordVPN"
"outboundUpdated": "Đã cập nhật outbound NordVPN",
"serverLoad": "Tải máy chủ",
"addedServers": "Máy chủ đã thêm",
"alreadyAdded": "Máy chủ này đã có trong danh sách outbound. Dùng {reset} để làm mới khóa của máy chủ."
},
"warp": {
"changeIp": "Đổi IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "未找到选定国家/地区的服务器",
"noPublicKey": "选定的服务器未公布 NordLynx 公钥。",
"outboundAdded": "NordVPN 出站已添加",
"outboundUpdated": "NordVPN 出站已更新"
"outboundUpdated": "NordVPN 出站已更新",
"serverLoad": "服务器负载",
"addedServers": "已添加的服务器",
"alreadyAdded": "此服务器已在出站列表中。请使用{reset}刷新其密钥。"
},
"warp": {
"changeIp": "更换 IP",
+4 -1
View File
@@ -1852,7 +1852,10 @@
"noServers": "未找到選定國家/地區的伺服器",
"noPublicKey": "選定的伺服器未公布 NordLynx 公鑰。",
"outboundAdded": "NordVPN 出站已新增",
"outboundUpdated": "NordVPN 出站已更新"
"outboundUpdated": "NordVPN 出站已更新",
"serverLoad": "伺服器負載",
"addedServers": "已新增的伺服器",
"alreadyAdded": "此伺服器已在出站清單中。請使用{reset}重新整理其金鑰。"
},
"warp": {
"changeIp": "更換 IP",