mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 07:37:15 +00:00
feat(sub): add Happ client integration, routing presets, and app management (#6434)
* feat(sub): add Happ client integration, routing presets, and app management Implement comprehensive Happ proxy client integration according to official developer specifications. - Fix header emission on disabled routing and hidden settings to send explicit '0' headers rather than omitting, allowing Happ clients to reset cached settings. - Add support for 'happ://routing/off' deeplink in routing validation. - Preserve '?serverDescription=' query parameters in link fragments without escaping to support Happ server subtitles across VMess, VLESS, Trojan and SS. - Add Happ application management headers: ProviderID, New-Url, Fallback-Url, Sub-Info banners, Sub-Expire notifications, No-Limit mode, hardware ID enforcement, TUN modes/types, route exclusions, APNS exclusions, and per-app proxy settings. - Add curated routing presets (Iran Bypass, China Direct, AdBlock, Global) and interactive visual rule generator in frontend settings. - Synchronize all 13 translation locales with native Persian, Russian, and Chinese translations. * fix(sub): keep Happ header overrides behind the auto-detect opt-in The Routing-Enable/Hide-Settings off values were emitted on the User-Agent alone, so every panel that upgraded would push "Routing-Enable: 0" — documented by happ.su as disabling routing globally — to every Happ client without the operator enabling anything. They now ride subHappAutoDetect like every other Happ header. Two further mismatches against the vendor spec: - serverDescription was written as a key of the VMess base64 JSON object. happ.su documents it as a "#Title?serverDescription=<base64>" link parameter or a JSON "meta" entry, so the caption never reached Happ while every other VMess consumer received an unknown key. Dropped rather than moved: emitting the documented form is unsafe here because our own parser base64-decodes the whole VMess body (internal/util/link/outbound.go). - The TUN Mode dropdown stored the literal "default", forwarded as "Tun-Mode: default", where happ.su documents system|gvisor only. It now stores the unset value so no header is sent. TUN Type "default" is a documented value and is unchanged. Each fix carries a test that fails without it.
This commit is contained in:
@@ -59,6 +59,29 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subEnableRouting": false,
|
||||
"subEncrypt": false,
|
||||
"subExpiredTemplate": "",
|
||||
"subHappAlwaysHwid": false,
|
||||
"subHappAutoConnect": false,
|
||||
"subHappAutoConnectType": "",
|
||||
"subHappAutoDetect": false,
|
||||
"subHappColorProfile": "",
|
||||
"subHappExcludeApns": false,
|
||||
"subHappExcludeRoutes": "",
|
||||
"subHappFallbackUrl": "",
|
||||
"subHappNewUrl": "",
|
||||
"subHappNoLimit": false,
|
||||
"subHappNotificationExpire": false,
|
||||
"subHappPerAppList": "",
|
||||
"subHappPerAppMode": "",
|
||||
"subHappPingType": "",
|
||||
"subHappProviderId": "",
|
||||
"subHappSubExpire": false,
|
||||
"subHappSubExpireButtonLink": "",
|
||||
"subHappSubInfoButtonLink": "",
|
||||
"subHappSubInfoButtonText": "",
|
||||
"subHappSubInfoColor": "",
|
||||
"subHappSubInfoText": "",
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
@@ -176,6 +199,29 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subEnableRouting": false,
|
||||
"subEncrypt": false,
|
||||
"subExpiredTemplate": "",
|
||||
"subHappAlwaysHwid": false,
|
||||
"subHappAutoConnect": false,
|
||||
"subHappAutoConnectType": "",
|
||||
"subHappAutoDetect": false,
|
||||
"subHappColorProfile": "",
|
||||
"subHappExcludeApns": false,
|
||||
"subHappExcludeRoutes": "",
|
||||
"subHappFallbackUrl": "",
|
||||
"subHappNewUrl": "",
|
||||
"subHappNoLimit": false,
|
||||
"subHappNotificationExpire": false,
|
||||
"subHappPerAppList": "",
|
||||
"subHappPerAppMode": "",
|
||||
"subHappPingType": "",
|
||||
"subHappProviderId": "",
|
||||
"subHappSubExpire": false,
|
||||
"subHappSubExpireButtonLink": "",
|
||||
"subHappSubInfoButtonLink": "",
|
||||
"subHappSubInfoButtonText": "",
|
||||
"subHappSubInfoColor": "",
|
||||
"subHappSubInfoText": "",
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
|
||||
@@ -194,6 +194,76 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -417,6 +487,29 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
@@ -685,6 +778,76 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -915,6 +1078,29 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
|
||||
@@ -67,6 +67,29 @@ export interface AllSetting {
|
||||
subEnableRouting: boolean;
|
||||
subEncrypt: boolean;
|
||||
subExpiredTemplate: string;
|
||||
subHappAlwaysHwid: boolean;
|
||||
subHappAutoConnect: boolean;
|
||||
subHappAutoConnectType: string;
|
||||
subHappAutoDetect: boolean;
|
||||
subHappColorProfile: string;
|
||||
subHappExcludeApns: boolean;
|
||||
subHappExcludeRoutes: string;
|
||||
subHappFallbackUrl: string;
|
||||
subHappNewUrl: string;
|
||||
subHappNoLimit: boolean;
|
||||
subHappNotificationExpire: boolean;
|
||||
subHappPerAppList: string;
|
||||
subHappPerAppMode: string;
|
||||
subHappPingType: string;
|
||||
subHappProviderId: string;
|
||||
subHappSubExpire: boolean;
|
||||
subHappSubExpireButtonLink: string;
|
||||
subHappSubInfoButtonLink: string;
|
||||
subHappSubInfoButtonText: string;
|
||||
subHappSubInfoColor: string;
|
||||
subHappSubInfoText: string;
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
@@ -185,6 +208,29 @@ export interface AllSettingView {
|
||||
subEnableRouting: boolean;
|
||||
subEncrypt: boolean;
|
||||
subExpiredTemplate: string;
|
||||
subHappAlwaysHwid: boolean;
|
||||
subHappAutoConnect: boolean;
|
||||
subHappAutoConnectType: string;
|
||||
subHappAutoDetect: boolean;
|
||||
subHappColorProfile: string;
|
||||
subHappExcludeApns: boolean;
|
||||
subHappExcludeRoutes: string;
|
||||
subHappFallbackUrl: string;
|
||||
subHappNewUrl: string;
|
||||
subHappNoLimit: boolean;
|
||||
subHappNotificationExpire: boolean;
|
||||
subHappPerAppList: string;
|
||||
subHappPerAppMode: string;
|
||||
subHappPingType: string;
|
||||
subHappProviderId: string;
|
||||
subHappSubExpire: boolean;
|
||||
subHappSubExpireButtonLink: string;
|
||||
subHappSubInfoButtonLink: string;
|
||||
subHappSubInfoButtonText: string;
|
||||
subHappSubInfoColor: string;
|
||||
subHappSubInfoText: string;
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
|
||||
@@ -83,6 +83,29 @@ export const AllSettingSchema = z.object({
|
||||
subEnableRouting: z.boolean(),
|
||||
subEncrypt: z.boolean(),
|
||||
subExpiredTemplate: z.string(),
|
||||
subHappAlwaysHwid: z.boolean(),
|
||||
subHappAutoConnect: z.boolean(),
|
||||
subHappAutoConnectType: z.string(),
|
||||
subHappAutoDetect: z.boolean(),
|
||||
subHappColorProfile: z.string(),
|
||||
subHappExcludeApns: z.boolean(),
|
||||
subHappExcludeRoutes: z.string(),
|
||||
subHappFallbackUrl: z.string(),
|
||||
subHappNewUrl: z.string(),
|
||||
subHappNoLimit: z.boolean(),
|
||||
subHappNotificationExpire: z.boolean(),
|
||||
subHappPerAppList: z.string(),
|
||||
subHappPerAppMode: z.string(),
|
||||
subHappPingType: z.string(),
|
||||
subHappProviderId: z.string(),
|
||||
subHappSubExpire: z.boolean(),
|
||||
subHappSubExpireButtonLink: z.string(),
|
||||
subHappSubInfoButtonLink: z.string(),
|
||||
subHappSubInfoButtonText: z.string(),
|
||||
subHappSubInfoColor: z.string(),
|
||||
subHappSubInfoText: z.string(),
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
@@ -202,6 +225,29 @@ export const AllSettingViewSchema = z.object({
|
||||
subEnableRouting: z.boolean(),
|
||||
subEncrypt: z.boolean(),
|
||||
subExpiredTemplate: z.string(),
|
||||
subHappAlwaysHwid: z.boolean(),
|
||||
subHappAutoConnect: z.boolean(),
|
||||
subHappAutoConnectType: z.string(),
|
||||
subHappAutoDetect: z.boolean(),
|
||||
subHappColorProfile: z.string(),
|
||||
subHappExcludeApns: z.boolean(),
|
||||
subHappExcludeRoutes: z.string(),
|
||||
subHappFallbackUrl: z.string(),
|
||||
subHappNewUrl: z.string(),
|
||||
subHappNoLimit: z.boolean(),
|
||||
subHappNotificationExpire: z.boolean(),
|
||||
subHappPerAppList: z.string(),
|
||||
subHappPerAppMode: z.string(),
|
||||
subHappPingType: z.string(),
|
||||
subHappProviderId: z.string(),
|
||||
subHappSubExpire: z.boolean(),
|
||||
subHappSubExpireButtonLink: z.string(),
|
||||
subHappSubInfoButtonLink: z.string(),
|
||||
subHappSubInfoButtonText: z.string(),
|
||||
subHappSubInfoColor: z.string(),
|
||||
subHappSubInfoText: z.string(),
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user