From c5935820069a449c17b94849d5d123e190b95c39 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 10 Nov 2025 17:26:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(devtools):=20=E6=B7=BB=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E7=8E=AF=E5=A2=83=E8=B0=83=E8=AF=95=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增调试工具目录 src/devtools/,提供完整的生产环境调试能力: - apiDebugger: 拦截所有 API 请求/响应,记录日志 - notificationDebugger: 测试浏览器通知,检查权限 - socketDebugger: 监听所有 Socket 事件,诊断连接状态 - 全局 API: window.__DEBUG__ 提供便捷的控制台调试命令 功能特性: - 环境变量控制:REACT_APP_ENABLE_DEBUG=true 开启 - 动态导入:不影响生产环境性能 - 完整诊断:diagnose()、performance()、exportAll() - 易于移除:所有代码集中在 src/devtools/ 目录 Webpack 配置: - 添加 'debug' alias 强制解析到 node_modules/debug - 添加 @devtools alias 简化导入路径 - 避免与 npm debug 包的命名冲突 🔧 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.production | 9 + craco.config.js | 32 ++++ src/devtools/apiDebugger.js | 253 +++++++++++++++++++++++++ src/devtools/index.js | 268 +++++++++++++++++++++++++++ src/devtools/notificationDebugger.js | 166 +++++++++++++++++ src/devtools/socketDebugger.js | 194 +++++++++++++++++++ src/index.js | 13 ++ 7 files changed, 935 insertions(+) create mode 100644 src/devtools/apiDebugger.js create mode 100644 src/devtools/index.js create mode 100644 src/devtools/notificationDebugger.js create mode 100644 src/devtools/socketDebugger.js diff --git a/.env.production b/.env.production index ef983024..816ed644 100644 --- a/.env.production +++ b/.env.production @@ -16,6 +16,15 @@ NODE_ENV=production # Mock 配置(生产环境禁用 Mock) REACT_APP_ENABLE_MOCK=false +# 🔧 调试模式(生产环境临时调试用) +# 开启后会在全局暴露 window.__DEBUG__ 调试 API +# ⚠️ 警告: 调试模式会记录所有 API 请求/响应,调试完成后请立即关闭! +# 使用方法: +# 1. 设置为 true 并重新构建 +# 2. 在浏览器控制台使用 window.__DEBUG__.help() 查看命令 +# 3. 调试完成后设置为 false 并重新构建 +REACT_APP_ENABLE_DEBUG=false + # 后端 API 地址(生产环境) REACT_APP_API_URL=http://49.232.185.254:5001 diff --git a/craco.config.js b/craco.config.js index 9072329a..be5e0264 100644 --- a/craco.config.js +++ b/craco.config.js @@ -110,6 +110,9 @@ module.exports = { ...webpackConfig.resolve, alias: { ...webpackConfig.resolve.alias, + // 强制 'debug' 模块解析到 node_modules(避免与 src/devtools/ 冲突) + 'debug': path.resolve(__dirname, 'node_modules/debug'), + // 根目录别名 '@': path.resolve(__dirname, 'src'), @@ -119,6 +122,7 @@ module.exports = { '@constants': path.resolve(__dirname, 'src/constants'), '@contexts': path.resolve(__dirname, 'src/contexts'), '@data': path.resolve(__dirname, 'src/data'), + '@devtools': path.resolve(__dirname, 'src/devtools'), '@hooks': path.resolve(__dirname, 'src/hooks'), '@layouts': path.resolve(__dirname, 'src/layouts'), '@lib': path.resolve(__dirname, 'src/lib'), @@ -263,6 +267,34 @@ module.exports = { logLevel: 'debug', pathRewrite: { '^/concept-api': '' }, }, + '/bytedesk-api': { + target: 'http://43.143.189.195', + changeOrigin: true, + secure: false, + logLevel: 'debug', + pathRewrite: { '^/bytedesk-api': '' }, + }, + '/chat': { + target: 'http://43.143.189.195', + changeOrigin: true, + secure: false, + logLevel: 'debug', + // 不需要pathRewrite,保留/chat路径 + }, + '/config': { + target: 'http://43.143.189.195', + changeOrigin: true, + secure: false, + logLevel: 'debug', + // 不需要pathRewrite,保留/config路径 + }, + '/visitor': { + target: 'http://43.143.189.195', + changeOrigin: true, + secure: false, + logLevel: 'debug', + // 不需要pathRewrite,保留/visitor路径 + }, }, }), }, diff --git a/src/devtools/apiDebugger.js b/src/devtools/apiDebugger.js new file mode 100644 index 00000000..2256ae39 --- /dev/null +++ b/src/devtools/apiDebugger.js @@ -0,0 +1,253 @@ +// src/debug/apiDebugger.js +/** + * API 调试工具 + * 生产环境临时调试使用,后期可整体删除 src/debug/ 目录 + */ + +import axios from 'axios'; +import { getApiBase } from '@utils/apiConfig'; + +class ApiDebugger { + constructor() { + this.requestLog = []; + this.maxLogSize = 100; + this.isLogging = true; + } + + /** + * 初始化 Axios 拦截器 + */ + init() { + // 请求拦截器 + axios.interceptors.request.use( + (config) => { + if (this.isLogging) { + const logEntry = { + type: 'request', + timestamp: new Date().toISOString(), + method: config.method.toUpperCase(), + url: config.url, + baseURL: config.baseURL, + fullURL: this._getFullURL(config), + headers: config.headers, + data: config.data, + params: config.params, + }; + + this._addLog(logEntry); + + console.log( + `%c[API Request] ${logEntry.method} ${logEntry.fullURL}`, + 'color: #2196F3; font-weight: bold;', + { + headers: config.headers, + data: config.data, + params: config.params, + } + ); + } + return config; + }, + (error) => { + console.error('[API Request Error]', error); + return Promise.reject(error); + } + ); + + // 响应拦截器 + axios.interceptors.response.use( + (response) => { + if (this.isLogging) { + const logEntry = { + type: 'response', + timestamp: new Date().toISOString(), + method: response.config.method.toUpperCase(), + url: response.config.url, + fullURL: this._getFullURL(response.config), + status: response.status, + statusText: response.statusText, + headers: response.headers, + data: response.data, + }; + + this._addLog(logEntry); + + console.log( + `%c[API Response] ${logEntry.method} ${logEntry.fullURL} - ${logEntry.status}`, + 'color: #4CAF50; font-weight: bold;', + { + status: response.status, + data: response.data, + headers: response.headers, + } + ); + } + return response; + }, + (error) => { + if (this.isLogging) { + const logEntry = { + type: 'error', + timestamp: new Date().toISOString(), + method: error.config?.method?.toUpperCase() || 'UNKNOWN', + url: error.config?.url || 'UNKNOWN', + fullURL: error.config ? this._getFullURL(error.config) : 'UNKNOWN', + status: error.response?.status, + statusText: error.response?.statusText, + message: error.message, + data: error.response?.data, + }; + + this._addLog(logEntry); + + console.error( + `%c[API Error] ${logEntry.method} ${logEntry.fullURL}`, + 'color: #F44336; font-weight: bold;', + { + status: error.response?.status, + message: error.message, + data: error.response?.data, + } + ); + } + return Promise.reject(error); + } + ); + + console.log('%c[API Debugger] Initialized', 'color: #FF9800; font-weight: bold;'); + } + + /** + * 获取完整 URL + */ + _getFullURL(config) { + const baseURL = config.baseURL || ''; + const url = config.url || ''; + const fullURL = baseURL + url; + + // 添加查询参数 + if (config.params) { + const params = new URLSearchParams(config.params).toString(); + return params ? `${fullURL}?${params}` : fullURL; + } + + return fullURL; + } + + /** + * 添加日志 + */ + _addLog(entry) { + this.requestLog.unshift(entry); + if (this.requestLog.length > this.maxLogSize) { + this.requestLog = this.requestLog.slice(0, this.maxLogSize); + } + } + + /** + * 获取所有日志 + */ + getLogs(type = 'all') { + if (type === 'all') { + return this.requestLog; + } + return this.requestLog.filter((log) => log.type === type); + } + + /** + * 清空日志 + */ + clearLogs() { + this.requestLog = []; + console.log('[API Debugger] Logs cleared'); + } + + /** + * 导出日志为 JSON + */ + exportLogs() { + const blob = new Blob([JSON.stringify(this.requestLog, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `api-logs-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + console.log('[API Debugger] Logs exported'); + } + + /** + * 打印日志统计 + */ + printStats() { + const stats = { + total: this.requestLog.length, + requests: this.requestLog.filter((log) => log.type === 'request').length, + responses: this.requestLog.filter((log) => log.type === 'response').length, + errors: this.requestLog.filter((log) => log.type === 'error').length, + }; + + console.table(stats); + return stats; + } + + /** + * 手动发送 API 请求(测试用) + */ + async testRequest(method, endpoint, data = null, config = {}) { + const apiBase = getApiBase(); + const url = `${apiBase}${endpoint}`; + + console.log(`[API Debugger] Testing ${method.toUpperCase()} ${url}`); + + try { + const response = await axios({ + method, + url, + data, + ...config, + }); + + console.log('[API Debugger] Test succeeded:', response.data); + return response.data; + } catch (error) { + console.error('[API Debugger] Test failed:', error); + throw error; + } + } + + /** + * 开启/关闭日志记录 + */ + toggleLogging(enabled) { + this.isLogging = enabled; + console.log(`[API Debugger] Logging ${enabled ? 'enabled' : 'disabled'}`); + } + + /** + * 获取最近的错误 + */ + getRecentErrors(count = 10) { + return this.requestLog.filter((log) => log.type === 'error').slice(0, count); + } + + /** + * 按 URL 过滤日志 + */ + getLogsByURL(urlPattern) { + return this.requestLog.filter((log) => log.url && log.url.includes(urlPattern)); + } + + /** + * 按状态码过滤日志 + */ + getLogsByStatus(status) { + return this.requestLog.filter((log) => log.status === status); + } +} + +// 导出单例 +export const apiDebugger = new ApiDebugger(); +export default apiDebugger; diff --git a/src/devtools/index.js b/src/devtools/index.js new file mode 100644 index 00000000..bbd88bbe --- /dev/null +++ b/src/devtools/index.js @@ -0,0 +1,268 @@ +// src/debug/index.js +/** + * 调试工具统一入口 + * + * 使用方法: + * 1. 开启调试: 在 .env.production 中设置 REACT_APP_ENABLE_DEBUG=true + * 2. 使用控制台命令: window.__DEBUG__.api.getLogs() + * 3. 后期移除: 删除整个 src/debug/ 目录 + 从 src/index.js 移除导入 + * + * 全局 API: + * - window.__DEBUG__ - 调试 API 主对象 + * - window.__DEBUG__.api - API 调试工具 + * - window.__DEBUG__.notification - 通知调试工具 + * - window.__DEBUG__.socket - Socket 调试工具 + * - window.__DEBUG__.help() - 显示帮助信息 + * - window.__DEBUG__.exportAll() - 导出所有日志 + */ + +import { apiDebugger } from './apiDebugger'; +import { notificationDebugger } from './notificationDebugger'; +import { socketDebugger } from './socketDebugger'; + +class DebugToolkit { + constructor() { + this.api = apiDebugger; + this.notification = notificationDebugger; + this.socket = socketDebugger; + } + + /** + * 初始化所有调试工具 + */ + init() { + console.log( + '%c╔════════════════════════════════════════════════════════════════╗', + 'color: #FF9800; font-weight: bold;' + ); + console.log( + '%c║ 🔧 调试模式已启用 (Debug Mode Enabled) ║', + 'color: #FF9800; font-weight: bold;' + ); + console.log( + '%c╚════════════════════════════════════════════════════════════════╝', + 'color: #FF9800; font-weight: bold;' + ); + console.log(''); + + // 初始化各个调试工具 + this.api.init(); + this.notification.init(); + this.socket.init(); + + // 暴露到全局 + window.__DEBUG__ = this; + + // 打印帮助信息 + this._printWelcome(); + } + + /** + * 打印欢迎信息 + */ + _printWelcome() { + console.log('%c📚 调试工具使用指南:', 'color: #2196F3; font-weight: bold; font-size: 14px;'); + console.log(''); + console.log('%c1️⃣ API 调试:', 'color: #2196F3; font-weight: bold;'); + console.log(' __DEBUG__.api.getLogs() - 获取所有 API 日志'); + console.log(' __DEBUG__.api.getRecentErrors() - 获取最近的错误'); + console.log(' __DEBUG__.api.exportLogs() - 导出 API 日志'); + console.log(' __DEBUG__.api.testRequest(method, endpoint, data) - 测试 API 请求'); + console.log(''); + console.log('%c2️⃣ 通知调试:', 'color: #9C27B0; font-weight: bold;'); + console.log(' __DEBUG__.notification.getLogs() - 获取所有通知日志'); + console.log(' __DEBUG__.notification.forceNotification() - 发送测试通知'); + console.log(' __DEBUG__.notification.checkPermission() - 检查通知权限'); + console.log(' __DEBUG__.notification.exportLogs() - 导出通知日志'); + console.log(''); + console.log('%c3️⃣ Socket 调试:', 'color: #00BCD4; font-weight: bold;'); + console.log(' __DEBUG__.socket.getLogs() - 获取所有 Socket 日志'); + console.log(' __DEBUG__.socket.getStatus() - 获取连接状态'); + console.log(' __DEBUG__.socket.reconnect() - 手动重连'); + console.log(' __DEBUG__.socket.exportLogs() - 导出 Socket 日志'); + console.log(''); + console.log('%c4️⃣ 通用命令:', 'color: #4CAF50; font-weight: bold;'); + console.log(' __DEBUG__.help() - 显示帮助信息'); + console.log(' __DEBUG__.exportAll() - 导出所有日志'); + console.log(' __DEBUG__.printStats() - 打印所有统计信息'); + console.log(' __DEBUG__.clearAll() - 清空所有日志'); + console.log(''); + console.log( + '%c⚠️ 警告: 调试模式会记录所有 API 请求和响应,请勿在生产环境长期开启!', + 'color: #F44336; font-weight: bold;' + ); + console.log(''); + } + + /** + * 显示帮助信息 + */ + help() { + this._printWelcome(); + } + + /** + * 导出所有日志 + */ + exportAll() { + console.log('[Debug Toolkit] Exporting all logs...'); + + const allLogs = { + timestamp: new Date().toISOString(), + api: this.api.getLogs(), + notification: this.notification.getLogs(), + socket: this.socket.getLogs(), + }; + + const blob = new Blob([JSON.stringify(allLogs, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `debug-all-logs-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + + console.log('[Debug Toolkit] ✅ All logs exported'); + } + + /** + * 打印所有统计信息 + */ + printStats() { + console.log('\n%c=== 📊 调试统计信息 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;'); + console.log('\n%c[API 统计]', 'color: #2196F3; font-weight: bold;'); + const apiStats = this.api.printStats(); + + console.log('\n%c[通知统计]', 'color: #9C27B0; font-weight: bold;'); + const notificationStats = this.notification.printStats(); + + console.log('\n%c[Socket 统计]', 'color: #00BCD4; font-weight: bold;'); + const socketStats = this.socket.printStats(); + + return { + api: apiStats, + notification: notificationStats, + socket: socketStats, + }; + } + + /** + * 清空所有日志 + */ + clearAll() { + console.log('[Debug Toolkit] Clearing all logs...'); + this.api.clearLogs(); + this.notification.clearLogs(); + this.socket.clearLogs(); + console.log('[Debug Toolkit] ✅ All logs cleared'); + } + + /** + * 快速诊断(检查所有系统状态) + */ + diagnose() { + console.log('\n%c=== 🔍 系统诊断 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;'); + + // 1. Socket 状态 + console.log('\n%c[1/3] Socket 状态', 'color: #00BCD4; font-weight: bold;'); + const socketStatus = this.socket.getStatus(); + + // 2. 通知权限 + console.log('\n%c[2/3] 通知权限', 'color: #9C27B0; font-weight: bold;'); + const notificationStatus = this.notification.checkPermission(); + + // 3. API 错误 + console.log('\n%c[3/3] 最近的 API 错误', 'color: #F44336; font-weight: bold;'); + const recentErrors = this.api.getRecentErrors(5); + if (recentErrors.length > 0) { + console.table( + recentErrors.map((err) => ({ + 时间: err.timestamp, + 方法: err.method, + URL: err.url, + 状态码: err.status, + 错误信息: err.message, + })) + ); + } else { + console.log('✅ 没有 API 错误'); + } + + // 4. 汇总报告 + const report = { + timestamp: new Date().toISOString(), + socket: socketStatus, + notification: notificationStatus, + apiErrors: recentErrors.length, + }; + + console.log('\n%c=== 诊断报告 ===', 'color: #4CAF50; font-weight: bold;'); + console.table(report); + + return report; + } + + /** + * 性能监控 + */ + performance() { + console.log('\n%c=== ⚡ 性能监控 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;'); + + // 计算 API 平均响应时间 + const apiLogs = this.api.getLogs(); + const responseTimes = []; + + for (let i = 0; i < apiLogs.length - 1; i++) { + const log = apiLogs[i]; + const prevLog = apiLogs[i + 1]; + + if ( + log.type === 'response' && + prevLog.type === 'request' && + log.url === prevLog.url + ) { + const responseTime = + new Date(log.timestamp).getTime() - new Date(prevLog.timestamp).getTime(); + responseTimes.push({ + url: log.url, + method: log.method, + time: responseTime, + }); + } + } + + if (responseTimes.length > 0) { + const avgTime = + responseTimes.reduce((sum, item) => sum + item.time, 0) / responseTimes.length; + const maxTime = Math.max(...responseTimes.map((item) => item.time)); + const minTime = Math.min(...responseTimes.map((item) => item.time)); + + console.log('API 响应时间统计:'); + console.table({ + 平均响应时间: `${avgTime.toFixed(2)}ms`, + 最快响应: `${minTime}ms`, + 最慢响应: `${maxTime}ms`, + 请求总数: responseTimes.length, + }); + + // 显示最慢的 5 个请求 + console.log('\n最慢的 5 个请求:'); + const slowest = responseTimes.sort((a, b) => b.time - a.time).slice(0, 5); + console.table( + slowest.map((item) => ({ + 方法: item.method, + URL: item.url, + 响应时间: `${item.time}ms`, + })) + ); + } else { + console.log('暂无性能数据'); + } + } +} + +// 导出单例 +export const debugToolkit = new DebugToolkit(); +export default debugToolkit; diff --git a/src/devtools/notificationDebugger.js b/src/devtools/notificationDebugger.js new file mode 100644 index 00000000..f2929a22 --- /dev/null +++ b/src/devtools/notificationDebugger.js @@ -0,0 +1,166 @@ +// src/debug/notificationDebugger.js +/** + * 通知系统调试工具 + * 扩展现有的 window.__NOTIFY_DEBUG__,添加更多生产环境调试能力 + */ + +import { browserNotificationService } from '@services/browserNotificationService'; + +class NotificationDebugger { + constructor() { + this.eventLog = []; + this.maxLogSize = 100; + } + + /** + * 初始化调试工具 + */ + init() { + console.log('%c[Notification Debugger] Initialized', 'color: #FF9800; font-weight: bold;'); + } + + /** + * 记录通知事件 + */ + logEvent(eventType, data) { + const logEntry = { + type: eventType, + timestamp: new Date().toISOString(), + data, + }; + + this.eventLog.unshift(logEntry); + if (this.eventLog.length > this.maxLogSize) { + this.eventLog = this.eventLog.slice(0, this.maxLogSize); + } + + console.log( + `%c[Notification Event] ${eventType}`, + 'color: #9C27B0; font-weight: bold;', + data + ); + } + + /** + * 获取所有事件日志 + */ + getLogs() { + return this.eventLog; + } + + /** + * 清空日志 + */ + clearLogs() { + this.eventLog = []; + console.log('[Notification Debugger] Logs cleared'); + } + + /** + * 导出日志 + */ + exportLogs() { + const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `notification-logs-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + console.log('[Notification Debugger] Logs exported'); + } + + /** + * 强制发送浏览器通知(测试用) + */ + forceNotification(options = {}) { + const defaultOptions = { + title: '🧪 测试通知', + body: `测试时间: ${new Date().toLocaleString()}`, + tag: `test_${Date.now()}`, + requireInteraction: false, + autoClose: 5000, + }; + + const finalOptions = { ...defaultOptions, ...options }; + + console.log('[Notification Debugger] Sending test notification:', finalOptions); + + const notification = browserNotificationService.sendNotification(finalOptions); + + if (notification) { + console.log('[Notification Debugger] ✅ Notification sent successfully'); + } else { + console.error('[Notification Debugger] ❌ Failed to send notification'); + } + + return notification; + } + + /** + * 检查通知权限状态 + */ + checkPermission() { + const permission = browserNotificationService.getPermissionStatus(); + const isSupported = browserNotificationService.isSupported(); + + const status = { + supported: isSupported, + permission, + canSend: isSupported && permission === 'granted', + }; + + console.table(status); + return status; + } + + /** + * 请求通知权限 + */ + async requestPermission() { + console.log('[Notification Debugger] Requesting notification permission...'); + const result = await browserNotificationService.requestPermission(); + console.log(`[Notification Debugger] Permission result: ${result}`); + return result; + } + + /** + * 打印事件统计 + */ + printStats() { + const stats = { + total: this.eventLog.length, + byType: {}, + }; + + this.eventLog.forEach((log) => { + stats.byType[log.type] = (stats.byType[log.type] || 0) + 1; + }); + + console.log('=== Notification Stats ==='); + console.table(stats.byType); + console.log(`Total events: ${stats.total}`); + + return stats; + } + + /** + * 按类型过滤日志 + */ + getLogsByType(eventType) { + return this.eventLog.filter((log) => log.type === eventType); + } + + /** + * 获取最近的事件 + */ + getRecentEvents(count = 10) { + return this.eventLog.slice(0, count); + } +} + +// 导出单例 +export const notificationDebugger = new NotificationDebugger(); +export default notificationDebugger; diff --git a/src/devtools/socketDebugger.js b/src/devtools/socketDebugger.js new file mode 100644 index 00000000..3312ad68 --- /dev/null +++ b/src/devtools/socketDebugger.js @@ -0,0 +1,194 @@ +// src/debug/socketDebugger.js +/** + * Socket 调试工具 + * 扩展现有的 window.__SOCKET_DEBUG__,添加更多生产环境调试能力 + */ + +import { socket } from '@services/socket'; + +class SocketDebugger { + constructor() { + this.eventLog = []; + this.maxLogSize = 100; + } + + /** + * 初始化调试工具 + */ + init() { + // 监听所有 Socket 事件 + this._attachEventListeners(); + console.log('%c[Socket Debugger] Initialized', 'color: #FF9800; font-weight: bold;'); + } + + /** + * 附加事件监听器 + */ + _attachEventListeners() { + const events = [ + 'connect', + 'disconnect', + 'connect_error', + 'reconnect', + 'reconnect_failed', + 'new_event', + 'system_notification', + ]; + + events.forEach((event) => { + socket.on(event, (data) => { + this.logEvent(event, data); + }); + }); + } + + /** + * 记录 Socket 事件 + */ + logEvent(eventType, data) { + const logEntry = { + type: eventType, + timestamp: new Date().toISOString(), + data, + }; + + this.eventLog.unshift(logEntry); + if (this.eventLog.length > this.maxLogSize) { + this.eventLog = this.eventLog.slice(0, this.maxLogSize); + } + + console.log( + `%c[Socket Event] ${eventType}`, + 'color: #00BCD4; font-weight: bold;', + data + ); + } + + /** + * 获取所有事件日志 + */ + getLogs() { + return this.eventLog; + } + + /** + * 清空日志 + */ + clearLogs() { + this.eventLog = []; + console.log('[Socket Debugger] Logs cleared'); + } + + /** + * 导出日志 + */ + exportLogs() { + const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], { + type: 'application/json', + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `socket-logs-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); + console.log('[Socket Debugger] Logs exported'); + } + + /** + * 获取连接状态 + */ + getStatus() { + const status = { + connected: socket.connected || false, + type: window.SOCKET_TYPE || 'UNKNOWN', + reconnectAttempts: socket.getReconnectAttempts?.() || 0, + maxReconnectAttempts: socket.getMaxReconnectAttempts?.() || Infinity, + }; + + console.table(status); + return status; + } + + /** + * 手动触发连接 + */ + connect() { + console.log('[Socket Debugger] Manually connecting...'); + socket.connect(); + } + + /** + * 手动断开连接 + */ + disconnect() { + console.log('[Socket Debugger] Manually disconnecting...'); + socket.disconnect(); + } + + /** + * 手动重连 + */ + reconnect() { + console.log('[Socket Debugger] Manually reconnecting...'); + socket.disconnect(); + setTimeout(() => { + socket.connect(); + }, 1000); + } + + /** + * 发送测试事件 + */ + emitTest(eventName, data = {}) { + console.log(`[Socket Debugger] Emitting test event: ${eventName}`, data); + socket.emit(eventName, data); + } + + /** + * 打印事件统计 + */ + printStats() { + const stats = { + total: this.eventLog.length, + byType: {}, + }; + + this.eventLog.forEach((log) => { + stats.byType[log.type] = (stats.byType[log.type] || 0) + 1; + }); + + console.log('=== Socket Stats ==='); + console.table(stats.byType); + console.log(`Total events: ${stats.total}`); + + return stats; + } + + /** + * 按类型过滤日志 + */ + getLogsByType(eventType) { + return this.eventLog.filter((log) => log.type === eventType); + } + + /** + * 获取最近的事件 + */ + getRecentEvents(count = 10) { + return this.eventLog.slice(0, count); + } + + /** + * 获取错误事件 + */ + getErrors() { + return this.eventLog.filter( + (log) => log.type === 'connect_error' || log.type === 'reconnect_failed' + ); + } +} + +// 导出单例 +export const socketDebugger = new SocketDebugger(); +export default socketDebugger; diff --git a/src/index.js b/src/index.js index c4350120..d2c327b0 100755 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,19 @@ import App from './App'; import { browserNotificationService } from './services/browserNotificationService'; window.browserNotificationService = browserNotificationService; +// 🔧 条件导入调试工具(生产环境可选) +// 开启方式: 在 .env 文件中设置 REACT_APP_ENABLE_DEBUG=true +// 移除方式: 删除此段代码 + 删除 src/devtools/ 目录 +if (process.env.REACT_APP_ENABLE_DEBUG === 'true') { + import('./devtools').then(({ debugToolkit }) => { + debugToolkit.init(); + console.log( + '%c✅ 调试工具已加载!使用 window.__DEBUG__.help() 查看命令', + 'color: #4CAF50; font-weight: bold; font-size: 14px;' + ); + }); +} + // 注册 Service Worker(用于支持浏览器通知) function registerServiceWorker() { // ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突)