'use client'; import { useState } from 'react'; import { buildSubscriptionUrls, buildShareLinks, buildBase64Subscription, buildJsonSubscription, type SubClient, type SubUrlInput, } from '@/lib/xray/subscription'; import type { Network, Security } from '@/lib/xray/outbounds'; import { ToolFrame } from './tool-frame'; import { TextField, SelectField, CheckboxField } from './shared/fields'; import { OutputBlock } from './shared/output-block'; type ClientProtocol = 'vless' | 'vmess' | 'trojan' | 'ss'; interface ClientRow { protocol: ClientProtocol; remark: string; address: string; port: string; credential: string; // id (vless/vmess) or password (trojan/ss) method: string; // ss network: Network; security: Security; sni: string; } const PROTOCOLS: readonly ClientProtocol[] = ['vless', 'vmess', 'trojan', 'ss']; const NETWORKS: readonly Network[] = ['tcp', 'kcp', 'ws', 'grpc', 'httpupgrade', 'xhttp']; const SECURITIES: readonly Security[] = ['none', 'tls', 'reality']; const addBtn = 'inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground'; const DEFAULT_CLIENTS: ClientRow[] = [ { protocol: 'vless', remark: 'HK-01', address: 'a.example.com', port: '443', credential: '11111111-2222-3333-4444-555555555555', method: '', network: 'tcp', security: 'reality', sni: 'www.microsoft.com', }, ]; function toClient(r: ClientRow): SubClient { const isUuid = r.protocol === 'vless' || r.protocol === 'vmess'; return { protocol: r.protocol, remark: r.remark, address: r.address, port: Number(r.port), id: isUuid ? r.credential : undefined, password: isUuid ? undefined : r.credential, method: r.protocol === 'ss' ? r.method : undefined, network: r.network, security: r.security, sni: r.sni || undefined, }; } export function SubscriptionBuilder() { const [scheme, setScheme] = useState<'http' | 'https'>('https'); const [host, setHost] = useState('sub.example.com'); const [port, setPort] = useState('2096'); const [subPath, setSubPath] = useState('/sub/'); const [jsonPath, setJsonPath] = useState('/json/'); const [subId, setSubId] = useState('user-1'); const [behindProxy, setBehindProxy] = useState(false); const [clients, setClients] = useState(DEFAULT_CLIENTS); function patch(i: number, p: Partial) { setClients((prev) => prev.map((c, j) => (i === j ? { ...c, ...p } : c))); } const urlInput: SubUrlInput = { scheme, host, port: Number(port), subPath, jsonPath, subId, behindProxy }; const urls = buildSubscriptionUrls(urlInput); const subClients = clients.filter((c) => c.address.trim()).map(toClient); function reset() { setScheme('https'); setHost('sub.example.com'); setPort('2096'); setSubPath('/sub/'); setJsonPath('/json/'); setSubId('user-1'); setBehindProxy(false); setClients(DEFAULT_CLIENTS); } return (
setScheme(v as 'http' | 'https')} options={['https', 'http']} />

Clients in this subscription

{clients.map((c, i) => (
patch(i, { protocol: v as ClientProtocol })} options={PROTOCOLS} /> patch(i, { remark: v })} /> patch(i, { address: v })} /> patch(i, { port: v })} inputMode="numeric" /> patch(i, { credential: v })} /> {c.protocol === 'ss' ? ( patch(i, { method: v })} /> ) : null} patch(i, { network: v as Network })} options={NETWORKS} /> patch(i, { security: v as Security })} options={SECURITIES} /> {c.security !== 'none' ? ( patch(i, { sni: v })} /> ) : null}
))}
); }