feat(sub): add Incy client integration and routing tab

Add an Incy quick-import button (incy://add) to the Android and iOS app menus on the subscription page, and a new Incy settings tab with routing enable + rules. Incy routing is delivered by injecting an incy://routing/onadd line into the raw subscription body, avoiding a collision with Happ's Routing header. Includes backend settings, regenerated OpenAPI/zod schemas, and translations for all locales.
This commit is contained in:
MHSanaei
2026-06-24 12:51:22 +02:00
parent 3fa4eddae3
commit 48c2fb27b8
26 changed files with 181 additions and 17 deletions
+4
View File
@@ -52,6 +52,8 @@ export const EXAMPLES: Record<string, unknown> = {
"subEnableRouting": false,
"subEncrypt": false,
"subHideSettings": false,
"subIncyEnableRouting": false,
"subIncyRoutingRules": "",
"subJsonEnable": false,
"subJsonFinalMask": "",
"subJsonMux": "",
@@ -152,6 +154,8 @@ export const EXAMPLES: Record<string, unknown> = {
"subEnableRouting": false,
"subEncrypt": false,
"subHideSettings": false,
"subIncyEnableRouting": false,
"subIncyRoutingRules": "",
"subJsonEnable": false,
"subJsonFinalMask": "",
"subJsonMux": "",
+20
View File
@@ -206,6 +206,14 @@ export const SCHEMAS: Record<string, unknown> = {
"description": "Hide server settings in happ subscription (Only for Happ)",
"type": "boolean"
},
"subIncyEnableRouting": {
"description": "Enable routing injection for the Incy client",
"type": "boolean"
},
"subIncyRoutingRules": {
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
"type": "string"
},
"subJsonEnable": {
"description": "Enable JSON subscription endpoint",
"type": "boolean"
@@ -431,6 +439,8 @@ export const SCHEMAS: Record<string, unknown> = {
"subEnableRouting",
"subEncrypt",
"subHideSettings",
"subIncyEnableRouting",
"subIncyRoutingRules",
"subJsonEnable",
"subJsonFinalMask",
"subJsonMux",
@@ -701,6 +711,14 @@ export const SCHEMAS: Record<string, unknown> = {
"description": "Hide server settings in happ subscription (Only for Happ)",
"type": "boolean"
},
"subIncyEnableRouting": {
"description": "Enable routing injection for the Incy client",
"type": "boolean"
},
"subIncyRoutingRules": {
"description": "Incy routing deep-link injected into the subscription body (Only for Incy)",
"type": "string"
},
"subJsonEnable": {
"description": "Enable JSON subscription endpoint",
"type": "boolean"
@@ -933,6 +951,8 @@ export const SCHEMAS: Record<string, unknown> = {
"subEnableRouting",
"subEncrypt",
"subHideSettings",
"subIncyEnableRouting",
"subIncyRoutingRules",
"subJsonEnable",
"subJsonFinalMask",
"subJsonMux",
+4
View File
@@ -58,6 +58,8 @@ export interface AllSetting {
subEnableRouting: boolean;
subEncrypt: boolean;
subHideSettings: boolean;
subIncyEnableRouting: boolean;
subIncyRoutingRules: string;
subJsonEnable: boolean;
subJsonFinalMask: string;
subJsonMux: string;
@@ -159,6 +161,8 @@ export interface AllSettingView {
subEnableRouting: boolean;
subEncrypt: boolean;
subHideSettings: boolean;
subIncyEnableRouting: boolean;
subIncyRoutingRules: string;
subJsonEnable: boolean;
subJsonFinalMask: string;
subJsonMux: string;
+4
View File
@@ -70,6 +70,8 @@ export const AllSettingSchema = z.object({
subEnableRouting: z.boolean(),
subEncrypt: z.boolean(),
subHideSettings: z.boolean(),
subIncyEnableRouting: z.boolean(),
subIncyRoutingRules: z.string(),
subJsonEnable: z.boolean(),
subJsonFinalMask: z.string(),
subJsonMux: z.string(),
@@ -172,6 +174,8 @@ export const AllSettingViewSchema = z.object({
subEnableRouting: z.boolean(),
subEncrypt: z.boolean(),
subHideSettings: z.boolean(),
subIncyEnableRouting: z.boolean(),
subIncyRoutingRules: z.string(),
subJsonEnable: z.boolean(),
subJsonFinalMask: z.string(),
subJsonMux: z.string(),
+2
View File
@@ -35,6 +35,8 @@ export class AllSetting {
subAnnounce = '';
subEnableRouting = false;
subRoutingRules = '';
subIncyEnableRouting = false;
subIncyRoutingRules = '';
subListen = '';
subPort = 2096;
subPath = '/sub/';
@@ -1,5 +1,5 @@
import { Input, InputNumber, Switch, Tabs } from 'antd';
import { BranchesOutlined, IdcardOutlined, InfoCircleOutlined, NodeIndexOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
import { BranchesOutlined, CompassOutlined, IdcardOutlined, InfoCircleOutlined, NodeIndexOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import type { AllSetting } from '@/models/setting';
import { SettingListItem } from '@/components/ui';
@@ -178,6 +178,21 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
</>
),
},
{
key: '7',
label: catTabLabel(<CompassOutlined />, 'Incy', isMobile),
children: (
<>
<SettingListItem paddings="small" title={t('pages.settings.subIncyEnableRouting')} description={t('pages.settings.subIncyEnableRoutingDesc')}>
<Switch checked={allSetting.subIncyEnableRouting} onChange={(v) => updateSetting({ subIncyEnableRouting: v })} />
</SettingListItem>
<SettingListItem paddings="small" title={t('pages.settings.subIncyRoutingRules')} description={t('pages.settings.subIncyRoutingRulesDesc')}>
<Input.TextArea value={allSetting.subIncyRoutingRules} placeholder="incy://routing/onadd/..."
onChange={(e) => updateSetting({ subIncyRoutingRules: e.target.value })} />
</SettingListItem>
</>
),
},
]} />
);
}
+4 -1
View File
@@ -139,6 +139,7 @@ export default function SubPage() {
);
const streisandUrl = useMemo(() => `streisand://import/${encodeURIComponent(subUrl)}`, []);
const happUrl = useMemo(() => `happ://add/${subUrl}`, []);
const incyUrl = useMemo(() => `incy://add/${subUrl}`, []);
const pageClass = useMemo(() => {
const classes = ['subscription-page'];
@@ -200,6 +201,7 @@ export default function SubPage() {
{ key: 'android-v2raytun', label: 'V2RayTun', onClick: () => copy(subUrl) },
{ key: 'android-npvtunnel', label: 'NPV Tunnel', onClick: () => copy(subUrl) },
{ key: 'android-happ', label: 'Happ', onClick: () => open(`happ://add/${subUrl}`) },
{ key: 'android-incy', label: 'Incy', onClick: () => open(`incy://add/${subUrl}`) },
], [copy, open]);
const iosMenuItems = useMemo(() => [
@@ -209,7 +211,8 @@ export default function SubPage() {
{ key: 'ios-v2raytun', label: 'V2RayTun', onClick: () => copy(subUrl) },
{ key: 'ios-npvtunnel', label: 'NPV Tunnel', onClick: () => copy(subUrl) },
{ key: 'ios-happ', label: 'Happ', onClick: () => open(happUrl) },
], [copy, open, shadowrocketUrl, v2boxUrl, streisandUrl, happUrl]);
{ key: 'ios-incy', label: 'Incy', onClick: () => open(incyUrl) },
], [copy, open, shadowrocketUrl, v2boxUrl, streisandUrl, happUrl, incyUrl]);
const langMenuItems = useMemo(
() => (LanguageManager.supportedLanguages as { value: string; name: string; icon: string }[]).map((l) => ({
+2
View File
@@ -39,6 +39,8 @@ export const AllSettingSchema = z.object({
subAnnounce: z.string().optional(),
subEnableRouting: z.boolean().optional(),
subRoutingRules: z.string().optional(),
subIncyEnableRouting: z.boolean().optional(),
subIncyRoutingRules: z.string().optional(),
subListen: z.string().optional(),
subPort: port.optional(),
subPath: absolutePath.optional(),