From 31845fa8f613e4fc43821501d49d01304b538af8 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 25 May 2026 19:51:39 +0200 Subject: [PATCH] refactor(frontend): tighten HttpUtil generics from any to unknown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the class-level default on Msg and the per-method defaults on HttpUtil.get/post/postWithModal from `any` to `unknown`, so callers that don't pass an explicit T get a narrowed response that must be schema- checked or type-cast before its shape is trusted. Drops the four file-level eslint-disable comments these defaults required. Fixes the nine direct `.obj.field` consumers that surfaced (IndexPage, XrayMetricsModal, NordModal, WarpModal, LogModal, VersionModal, XrayLogModal, CustomGeoSection) by giving each call site the explicit T it should have had from the start — typically a small ad-hoc shape, sometimes a string for the JSON-text-in-Msg.obj pattern used by NordModal/WarpModal/Xray nord/warp endpoints. PR3 of the planned Zod end-to-end rollout — schemas/inbound.ts and schemas/client.ts loose() removal stays parked until the protocol schemas land in Phase 3 to avoid silently dropping fields. --- frontend/src/pages/index/CustomGeoSection.tsx | 2 +- frontend/src/pages/index/IndexPage.tsx | 4 ++-- frontend/src/pages/index/LogModal.tsx | 2 +- frontend/src/pages/index/VersionModal.tsx | 2 +- frontend/src/pages/index/XrayLogModal.tsx | 2 +- frontend/src/pages/index/XrayMetricsModal.tsx | 19 ++++++++++--------- frontend/src/pages/xray/NordModal.tsx | 18 +++++++++--------- frontend/src/pages/xray/WarpModal.tsx | 14 +++++++------- frontend/src/utils/index.ts | 12 ++++-------- 9 files changed, 36 insertions(+), 39 deletions(-) diff --git a/frontend/src/pages/index/CustomGeoSection.tsx b/frontend/src/pages/index/CustomGeoSection.tsx index b87005a8..e57814a0 100644 --- a/frontend/src/pages/index/CustomGeoSection.tsx +++ b/frontend/src/pages/index/CustomGeoSection.tsx @@ -116,7 +116,7 @@ export default function CustomGeoSection({ active }: CustomGeoSectionProps) { async function updateAll() { setUpdatingAll(true); try { - const msg = await HttpUtil.post('/panel/api/custom-geo/update-all'); + const msg = await HttpUtil.post<{ succeeded?: unknown[]; failed?: unknown[] }>('/panel/api/custom-geo/update-all'); const ok = msg?.obj?.succeeded?.length || 0; const failed = msg?.obj?.failed?.length || 0; if (msg?.success || ok > 0) { diff --git a/frontend/src/pages/index/IndexPage.tsx b/frontend/src/pages/index/IndexPage.tsx index c3a58105..a5b41b5a 100644 --- a/frontend/src/pages/index/IndexPage.tsx +++ b/frontend/src/pages/index/IndexPage.tsx @@ -86,10 +86,10 @@ export default function IndexPage() { const [loadingTip, setLoadingTip] = useState(t('loading')); useEffect(() => { - HttpUtil.post('/panel/setting/defaultSettings').then((msg) => { + HttpUtil.post<{ ipLimitEnable?: boolean }>('/panel/setting/defaultSettings').then((msg) => { if (msg?.success && msg.obj) setIpLimitEnable(!!msg.obj.ipLimitEnable); }); - HttpUtil.get('/panel/api/server/getPanelUpdateInfo').then((msg) => { + HttpUtil.get('/panel/api/server/getPanelUpdateInfo').then((msg) => { if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj); }); }, []); diff --git a/frontend/src/pages/index/LogModal.tsx b/frontend/src/pages/index/LogModal.tsx index 1f074017..615754d9 100644 --- a/frontend/src/pages/index/LogModal.tsx +++ b/frontend/src/pages/index/LogModal.tsx @@ -69,7 +69,7 @@ export default function LogModal({ open, onClose }: LogModalProps) { const refresh = useCallback(async () => { setLoading(true); try { - const msg = await HttpUtil.post(`/panel/api/server/logs/${rows}`, { + const msg = await HttpUtil.post(`/panel/api/server/logs/${rows}`, { level, syslog, }); diff --git a/frontend/src/pages/index/VersionModal.tsx b/frontend/src/pages/index/VersionModal.tsx index 4135796a..c256ccc6 100644 --- a/frontend/src/pages/index/VersionModal.tsx +++ b/frontend/src/pages/index/VersionModal.tsx @@ -39,7 +39,7 @@ export default function VersionModal({ open, status, onClose, onBusy }: VersionM const fetchVersions = useCallback(async () => { setLoading(true); try { - const msg = await HttpUtil.get('/panel/api/server/getXrayVersion'); + const msg = await HttpUtil.get('/panel/api/server/getXrayVersion'); if (msg?.success) setVersions(msg.obj || []); } finally { setLoading(false); diff --git a/frontend/src/pages/index/XrayLogModal.tsx b/frontend/src/pages/index/XrayLogModal.tsx index dadde51e..b2ca34f4 100644 --- a/frontend/src/pages/index/XrayLogModal.tsx +++ b/frontend/src/pages/index/XrayLogModal.tsx @@ -62,7 +62,7 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) { const refresh = useCallback(async () => { setLoading(true); try { - const msg = await HttpUtil.post(`/panel/api/server/xraylogs/${rows}`, { + const msg = await HttpUtil.post(`/panel/api/server/xraylogs/${rows}`, { filter, showDirect, showBlocked, diff --git a/frontend/src/pages/index/XrayMetricsModal.tsx b/frontend/src/pages/index/XrayMetricsModal.tsx index 399372e9..1e0a1d15 100644 --- a/frontend/src/pages/index/XrayMetricsModal.tsx +++ b/frontend/src/pages/index/XrayMetricsModal.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Modal, Select, Tabs, Tag } from 'antd'; -import { HttpUtil, SizeFormatter } from '@/utils'; +import { HttpUtil, Msg, SizeFormatter } from '@/utils'; import Sparkline from '@/components/Sparkline'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import './XrayMetricsModal.css'; @@ -90,7 +90,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp const activeObsTag = obsTags.find((tg) => tg.tag === obsActiveTag) || null; - const applyHistory = useCallback((msg: { success?: boolean; obj?: { t: number; v: number }[] }, currentBucket: number) => { + const applyHistory = useCallback((msg: Msg<{ t: number; v: number }[]> | null | undefined, currentBucket: number) => { if (msg?.success && Array.isArray(msg.obj)) { const vals: number[] = []; const labs: string[] = []; @@ -112,7 +112,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp const fetchState = useCallback(async () => { try { - const msg = await HttpUtil.get('/panel/api/server/xrayMetricsState'); + const msg = await HttpUtil.get('/panel/api/server/xrayMetricsState'); if (msg?.success && msg.obj) setState(msg.obj); } catch (e) { console.error('Failed to fetch xray metrics state', e); @@ -121,12 +121,13 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp const fetchObservatory = useCallback(async () => { try { - const msg = await HttpUtil.get('/panel/api/server/xrayObservatory'); + const msg = await HttpUtil.get('/panel/api/server/xrayObservatory'); if (msg?.success && Array.isArray(msg.obj)) { - setObsTags(msg.obj); + const tags = msg.obj; + setObsTags(tags); setObsActiveTag((prev) => { - if (msg.obj.find((tg: ObservatoryTag) => tg.tag === prev)) return prev; - return msg.obj[0]?.tag || ''; + if (tags.find((tg) => tg.tag === prev)) return prev; + return tags[0]?.tag || ''; }); } else { setObsTags([]); @@ -141,7 +142,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp if (!activeMetric) return; try { const url = `/panel/api/server/xrayMetricsHistory/${activeMetric.key}/${bucket}`; - const msg = await HttpUtil.get(url); + const msg = await HttpUtil.get<{ t: number; v: number }[]>(url); applyHistory(msg, bucket); } catch (e) { console.error('Failed to fetch xray metrics bucket', e); @@ -158,7 +159,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp } try { const url = `/panel/api/server/xrayObservatoryHistory/${encodeURIComponent(obsActiveTag)}/${bucket}`; - const msg = await HttpUtil.get(url); + const msg = await HttpUtil.get<{ t: number; v: number }[]>(url); applyHistory(msg, bucket); } catch (e) { console.error('Failed to fetch observatory bucket', e); diff --git a/frontend/src/pages/xray/NordModal.tsx b/frontend/src/pages/xray/NordModal.tsx index 26306b10..5ef1a934 100644 --- a/frontend/src/pages/xray/NordModal.tsx +++ b/frontend/src/pages/xray/NordModal.tsx @@ -86,14 +86,14 @@ export default function NordModal({ }, [filteredServers]); const fetchCountries = useCallback(async () => { - const msg = await HttpUtil.post('/panel/xray/nord/countries'); - if (msg?.success) setCountries(JSON.parse(msg.obj)); + const msg = await HttpUtil.post('/panel/xray/nord/countries'); + if (msg?.success && msg.obj) setCountries(JSON.parse(msg.obj)); }, []); const fetchData = useCallback(async () => { setLoading(true); try { - const msg = await HttpUtil.post('/panel/xray/nord/data'); + const msg = await HttpUtil.post('/panel/xray/nord/data'); if (msg?.success) { const next = msg.obj ? JSON.parse(msg.obj) : null; setNordData(next); @@ -111,8 +111,8 @@ export default function NordModal({ async function login() { setLoading(true); try { - const msg = await HttpUtil.post('/panel/xray/nord/reg', { token }); - if (msg?.success) { + const msg = await HttpUtil.post('/panel/xray/nord/reg', { token }); + if (msg?.success && msg.obj) { setNordData(JSON.parse(msg.obj)); await fetchCountries(); } @@ -124,8 +124,8 @@ export default function NordModal({ async function saveKey() { setLoading(true); try { - const msg = await HttpUtil.post('/panel/xray/nord/setKey', { key: manualKey }); - if (msg?.success) { + const msg = await HttpUtil.post('/panel/xray/nord/setKey', { key: manualKey }); + if (msg?.success && msg.obj) { setNordData(JSON.parse(msg.obj)); await fetchCountries(); } @@ -164,8 +164,8 @@ export default function NordModal({ setServerId(null); setCityId(null); try { - const msg = await HttpUtil.post('/panel/xray/nord/servers', { countryId: newCountryId }); - if (!msg?.success) return; + const msg = await HttpUtil.post('/panel/xray/nord/servers', { countryId: newCountryId }); + if (!msg?.success || !msg.obj) return; const data = JSON.parse(msg.obj); const locations = data.locations || []; const locToCity: Record = {}; diff --git a/frontend/src/pages/xray/WarpModal.tsx b/frontend/src/pages/xray/WarpModal.tsx index 6eda6888..993227a4 100644 --- a/frontend/src/pages/xray/WarpModal.tsx +++ b/frontend/src/pages/xray/WarpModal.tsx @@ -108,7 +108,7 @@ export default function WarpModal({ const fetchData = useCallback(async () => { setLoading(true); try { - const msg = await HttpUtil.post('/panel/xray/warp/data'); + const msg = await HttpUtil.post('/panel/xray/warp/data'); if (msg?.success) { const raw = msg.obj; setWarpData(raw && raw.length > 0 ? JSON.parse(raw) : null); @@ -130,8 +130,8 @@ export default function WarpModal({ setLoading(true); try { const keys = Wireguard.generateKeypair(); - const msg = await HttpUtil.post('/panel/xray/warp/reg', keys); - if (msg?.success) { + const msg = await HttpUtil.post('/panel/xray/warp/reg', keys); + if (msg?.success && msg.obj) { const resp = JSON.parse(msg.obj); setWarpData(resp.data); setWarpConfig(resp.config); @@ -145,8 +145,8 @@ export default function WarpModal({ async function getConfig() { setLoading(true); try { - const msg = await HttpUtil.post('/panel/xray/warp/config'); - if (msg?.success) { + const msg = await HttpUtil.post('/panel/xray/warp/config'); + if (msg?.success && msg.obj) { const parsed = JSON.parse(msg.obj); setWarpConfig(parsed); collectConfig(warpData, parsed); @@ -161,8 +161,8 @@ export default function WarpModal({ setLoading(true); setLicenseError(''); try { - const msg = await HttpUtil.post('/panel/xray/warp/license', { license: warpPlus }); - if (msg?.success) { + const msg = await HttpUtil.post('/panel/xray/warp/license', { license: warpPlus }); + if (msg?.success && msg.obj) { setWarpData(JSON.parse(msg.obj)); setWarpConfig(null); setWarpPlus(''); diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 15507163..19b26db6 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -4,8 +4,7 @@ import { getMessage } from './messageBus'; type RespEnvelope = { success?: unknown; msg?: unknown; obj?: unknown }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class Msg { +export class Msg { success: boolean; msg: string; obj: T | null; @@ -50,8 +49,7 @@ export class HttpUtil { return typeof data === 'object' ? (data as Msg) : new Msg(false, 'unknown data:', data); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static async get(url: string, params?: unknown, options: HttpOptions = {}): Promise> { + static async get(url: string, params?: unknown, options: HttpOptions = {}): Promise> { const { silent, ...axiosOpts } = options; try { const resp = await axios.get(url, { params, ...axiosOpts }); @@ -67,8 +65,7 @@ export class HttpUtil { } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static async post(url: string, data?: unknown, options: HttpOptions = {}): Promise> { + static async post(url: string, data?: unknown, options: HttpOptions = {}): Promise> { const { silent, ...axiosOpts } = options; try { const resp = await axios.post(url, data, axiosOpts); @@ -84,8 +81,7 @@ export class HttpUtil { } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static async postWithModal(url: string, data?: unknown, modal?: HttpModal | null): Promise> { + static async postWithModal(url: string, data?: unknown, modal?: HttpModal | null): Promise> { if (modal) { modal.loading(true); }