Quellcode durchsuchen

refactor(frontend): tighten HttpUtil generics from any to unknown

Switch the class-level default on Msg<T> 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.
MHSanaei vor 12 Stunden
Ursprung
Commit
31845fa8f6

+ 1 - 1
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) {

+ 2 - 2
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<PanelUpdateInfo>('/panel/api/server/getPanelUpdateInfo').then((msg) => {
       if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj);
     });
   }, []);

+ 1 - 1
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<string[]>(`/panel/api/server/logs/${rows}`, {
         level,
         syslog,
       });

+ 1 - 1
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<string[]>('/panel/api/server/getXrayVersion');
       if (msg?.success) setVersions(msg.obj || []);
     } finally {
       setLoading(false);

+ 1 - 1
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<XrayLogEntry[]>(`/panel/api/server/xraylogs/${rows}`, {
         filter,
         showDirect,
         showBlocked,

+ 10 - 9
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<XrayState>('/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<ObservatoryTag[]>('/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);

+ 9 - 9
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<string>('/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<string>('/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<string>('/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<string>('/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<string>('/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<number, City> = {};

+ 7 - 7
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<string>('/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<string>('/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<string>('/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<string>('/panel/xray/warp/license', { license: warpPlus });
+      if (msg?.success && msg.obj) {
         setWarpData(JSON.parse(msg.obj));
         setWarpConfig(null);
         setWarpPlus('');

+ 4 - 8
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<T = any> {
+export class Msg<T = unknown> {
   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<T = any>(url: string, params?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
+  static async get<T = unknown>(url: string, params?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
     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<T = any>(url: string, data?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
+  static async post<T = unknown>(url: string, data?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
     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<T = any>(url: string, data?: unknown, modal?: HttpModal | null): Promise<Msg<T>> {
+  static async postWithModal<T = unknown>(url: string, data?: unknown, modal?: HttpModal | null): Promise<Msg<T>> {
     if (modal) {
       modal.loading(true);
     }