feat(sub): add Incy app-management parameters (#6650)

* feat(sub): add Incy app-management parameters

The panel already pushes a set of Happ headers, but INCY documents its own
lowercase header names and its own value domains, so a Happ-shaped payload gets
ignored by the client (per-app mode is bypass|proxy, not on|bypass, and
per-app-proxy-enable has no Happ counterpart at all). Add a sibling Incy path
that emits exactly the documented headers.

Covered, per https://docs.incy.cc/en/app-management/:
- profile-description, sort-order, support-email, announce-url, premium-url
- banner text/button/URL and the two hex colours
- hide-url, hide-check, no-limit-enabled
- per-app split tunnelling (enable/mode/list)
- TCP fragmentation (enable/length/interval/packets)
- UDP noise packets (enable/type/packet/delay)
- DoH pre-resolution (enable/domain/IP)

Each string setting is tri-state: an empty value omits the header, so an
untouched panel never overrides the subscriber's own choice in the app. Values
are validated against the documented domains and dropped when they do not
match, and non-ASCII text is base64-wrapped the way the docs require for
Cyrillic. INCY identifies itself as INCY/<version>/<platform>, which gates the
headers behind the same auto-detect switch the Happ path uses.

Headers the panel already emits for every client (Profile-Title, Support-Url,
Profile-Web-Page-Url, Announce, Profile-Update-Interval, Subscription-Userinfo)
and Incy's routing line are left as they are.

The Premium API (theme, defaultPingProtocol, fallbackHosts, ...) is a separate
encrypted endpoint and stays out of scope here.

* fix(sub): keep Incy per-app list entries separate on the wire

The Incy settings textarea takes one package per line, as Incy documents for
per-app-proxy-list, but the header path ran the value through
sanitizeHeaderValue, which deletes CR/LF. "com.google.chrome\norg.telegram.messenger"
reached the client as the single bogus package
"com.google.chromeorg.telegram.messenger", so per-app split tunnelling silently
matched no app. Join comma- or line-separated entries as CSV instead.

Also drop three tests that could not fail: TestIncyExcludesHappOnlyHeaders
(ApplyIncyHeaders has no path that emits Happ headers, and the non-Happ UA
gate is already pinned by TestApplyHappHeaders_Gating) and two UI tests that
only asserted updateSetting received the key the JSX passes it.

---------

Co-authored-by: DIMFLIX <dimflix@users.noreply.github.com>
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
DIMFLIX
2026-09-27 02:06:17 +03:00
committed by GitHub
parent bd9ccde1f4
commit 71e38367c1
30 changed files with 2994 additions and 37 deletions
+70 -1
View File
@@ -1670,7 +1670,76 @@
"discordNotifyTimeDesc": "Con qué frecuencia el bot de Discord envía informes periódicos. Elige un intervalo predefinido o selecciona Personalizado para introducir una expresión crontab.",
"discordNotifyBackup": "Respaldo de Base de Datos",
"discordNotifyBackupDesc": "Incluir archivo de respaldo de base de datos con notificación de informe.",
"discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord"
"discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord",
"subIncyAppAutoDetect": "Detección automática de cabeceras de Incy",
"subIncyAppAutoDetectDesc": "Envía las cabeceras de Incy cuando el User-Agent del cliente es INCY.",
"subIncyNotSet": "Sin definir",
"subIncyOn": "Activado",
"subIncyOff": "Desactivado",
"subIncyGroupApp": "Aplicación",
"subIncyProfileDescription": "Descripción del perfil",
"subIncyProfileDescriptionDesc": "Descripción aparte de la suscripción. El texto no ASCII se envía en base64.",
"subIncySortOrder": "Orden de servidores",
"subIncySortOrderDesc": "Para esta suscripción en iOS/Android, global en escritorio.",
"subIncySortNone": "Orden de la suscripción",
"subIncySortPing": "Por ping",
"subIncySortName": "Por nombre",
"subIncySupportEmail": "Email de soporte",
"subIncySupportEmailDesc": "Añade un botón Email a la tarjeta de suscripción.",
"subIncyAnnounceUrl": "Enlace del anuncio",
"subIncyAnnounceUrlDesc": "Destino del anuncio de la pantalla principal.",
"subIncyPremiumUrl": "Enlace Premium",
"subIncyPremiumUrlDesc": "Botón Premium en la tarjeta; también manda en el botón del banner.",
"subIncyGroupBanners": "Banners",
"subIncyBannerText": "Texto del banner",
"subIncyBannerTextDesc": "Sustituye el texto del banner. Solo para proveedores premium.",
"subIncyBannerButtonText": "Texto del botón del banner",
"subIncyBannerButtonTextDesc": "Etiqueta del botón del banner. Vacío lo oculta.",
"subIncyBannerButtonUrl": "Enlace del botón del banner",
"subIncyBannerButtonUrlDesc": "URL del botón del banner.",
"subIncyBannerBgColor": "Fondo del banner",
"subIncyBannerBgColorDesc": "Color hex, p. ej. #E53E3E.",
"subIncyBannerButtonColor": "Color del botón del banner",
"subIncyBannerButtonColorDesc": "Color hex, p. ej. #38A169.",
"subIncyGroupPrivacy": "Privacidad",
"subIncyHideUrl": "Ocultar URL de la suscripción",
"subIncyHideUrlDesc": "Oculta la URL al compartir, copiar, en QR y en copias de seguridad.",
"subIncyHideCheck": "Ocultar el botón de comprobación",
"subIncyHideCheckDesc": "Quita el botón de comprobación de conexión de la pantalla principal.",
"subIncyNoLimit": "Modo No-Limit (iOS)",
"subIncyNoLimitDesc": "Modo de bajo consumo de memoria para no pasar del límite de 50 MB en iOS.",
"subIncyPerAppEnable": "Modo per-app (Android)",
"subIncyPerAppEnableDesc": "Activa el túnel por aplicación. Desactivado anula la elección del usuario.",
"subIncyPerAppMode": "Tipo de modo per-app",
"subIncyPerAppModeDesc": "proxy: solo las apps listadas usan la VPN; bypass: al contrario.",
"subIncyPerAppModeProxy": "Solo las apps listadas",
"subIncyPerAppModeBypass": "Las apps listadas se saltan",
"subIncyPerAppList": "Lista de apps",
"subIncyPerAppListDesc": "Nombres de paquete separados por comas o saltos de línea, o una URL a un archivo.",
"subIncyGroupNetwork": "Red",
"subIncyFragmentationEnable": "Fragmentación",
"subIncyFragmentationEnableDesc": "Activa la fragmentación TCP y anula la configuración global.",
"subIncyFragmentLength": "Longitud del fragmento",
"subIncyFragmentLengthDesc": "Rango en bytes, p. ej. 10-30.",
"subIncyFragmentInterval": "Intervalo de fragmentación",
"subIncyFragmentIntervalDesc": "Rango en milisegundos, p. ej. 20-40.",
"subIncyFragmentPackets": "Paquetes a fragmentar",
"subIncyFragmentPacketsDesc": "Qué paquetes TCP fragmentar: tlshello, 1-3, 1 o all.",
"subIncyNoisesEnable": "Paquetes de ruido",
"subIncyNoisesEnableDesc": "Envía ruido UDP aleatorio antes del handshake de la VPN.",
"subIncyNoisesType": "Tipo de ruido",
"subIncyNoisesTypeDesc": "rand, str o hex.",
"subIncyNoisesPacket": "Contenido del paquete de ruido",
"subIncyNoisesPacketDesc": "Carga útil; con rand, un rango de longitud como 10-20.",
"subIncyNoisesDelay": "Retardo del ruido",
"subIncyNoisesDelayDesc": "Rango de retardo en milisegundos, p. ej. 10-50.",
"subIncyResolveEnable": "Resolver dirección del servidor (DoH)",
"subIncyResolveEnableDesc": "Resuelve la dirección del servidor por DoH antes de conectar.",
"subIncyResolveDnsDomain": "Endpoint DoH",
"subIncyResolveDnsDomainDesc": "URL del servidor DoH, normalmente acaba en /dns-query.",
"subIncyResolveDnsIp": "IP del servidor DoH",
"subIncyResolveDnsIpDesc": "IP del servidor DoH para el arranque.",
"subIncyGroupRouting": "Enrutado"
},
"xray": {
"save": "Guardar configuración",