/** * 落雪音乐 HTTP 请求处理(主进程) * 绕过渲染进程的 CORS 限制 */ import { ipcMain } from 'electron'; import fetch, { type RequestInit } from 'node-fetch'; interface LxHttpRequest { url: string; options: { method?: string; headers?: Record; body?: string; form?: Record; formData?: Record; timeout?: number; }; requestId: string; } interface LxHttpResponse { statusCode: number; headers: Record; body: any; } // 取消控制器映射 const abortControllers = new Map(); /** * 初始化 HTTP 请求处理 */ export const initLxMusicHttp = () => { // 处理 HTTP 请求 ipcMain.handle( 'lx-music-http-request', async (_, request: LxHttpRequest): Promise => { const { url, options, requestId } = request; const controller = new AbortController(); // 保存取消控制器 abortControllers.set(requestId, controller); try { console.log(`[LxMusicHttp] 请求: ${options.method || 'GET'} ${url}`); const fetchOptions: RequestInit = { method: options.method || 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', ...(options.headers || {}) }, signal: controller.signal }; // 处理请求体 if (options.body) { fetchOptions.body = options.body; } else if (options.form) { const formData = new URLSearchParams(options.form); fetchOptions.body = formData.toString(); fetchOptions.headers = { ...fetchOptions.headers, 'Content-Type': 'application/x-www-form-urlencoded' }; } else if (options.formData) { // node-fetch 的 FormData 需要特殊处理 const FormData = (await import('form-data')).default; const formData = new FormData(); for (const [key, value] of Object.entries(options.formData)) { formData.append(key, value); } fetchOptions.body = formData as any; // FormData 会自动设置 Content-Type } // 设置超时 const timeout = options.timeout || 30000; const timeoutId = setTimeout(() => { console.warn(`[LxMusicHttp] 请求超时: ${url}`); controller.abort(); }, timeout); const response = await fetch(url, fetchOptions); clearTimeout(timeoutId); console.log(`[LxMusicHttp] 响应: ${response.status} ${url}`); // 读取响应体 const rawBody = await response.text(); // 尝试解析 JSON let parsedBody: any = rawBody; const contentType = response.headers.get('content-type') || ''; if ( contentType.includes('application/json') || rawBody.startsWith('{') || rawBody.startsWith('[') ) { try { parsedBody = JSON.parse(rawBody); } catch { // 解析失败则使用原始字符串 } } // 转换 headers 为普通对象 const headers: Record = {}; response.headers.forEach((value, key) => { headers[key] = value; }); const result: LxHttpResponse = { statusCode: response.status, headers, body: parsedBody }; return result; } catch (error: any) { console.error(`[LxMusicHttp] 请求失败: ${url}`, error.message); throw error; } finally { // 清理取消控制器 abortControllers.delete(requestId); } } ); // 处理请求取消 ipcMain.handle('lx-music-http-cancel', (_, requestId: string) => { const controller = abortControllers.get(requestId); if (controller) { console.log(`[LxMusicHttp] 取消请求: ${requestId}`); controller.abort(); abortControllers.delete(requestId); } }); console.log('[LxMusicHttp] HTTP 请求处理已初始化'); }; /** * 清理所有正在进行的请求 */ export const cleanupLxMusicHttp = () => { for (const [requestId, controller] of abortControllers.entries()) { console.log(`[LxMusicHttp] 清理请求: ${requestId}`); controller.abort(); } abortControllers.clear(); };