// ============================================================ // storage-ipc.ts — 本地存储 IPC 处理器(主进程) // // 职责: // - 文件写入/读取/删除(Buffer 基础操作) // - 递归创建目录 // - shell.showItemInFolder(右键"在文件夹中查看") // - 获取应用数据目录路径 // - 所有操作在 heixiu-data/ 沙箱内进行 // // 安全: // - 路径校验:拒绝相对路径中的 ../ 穿越 // - 所有写入操作 base 于 getDataDir() // ============================================================ import { ipcMain, shell, app } from 'electron'; import fs from 'node:fs'; import path from 'node:path'; import { BIDIRECTIONAL } from '../../../shared/constants/ipc-channels'; import { logger } from '../logger'; // ---------- 内部工具 ---------- /** 获取 heixiu-data 根目录 */ function getDataDir(): string { return path.join(app.getPath('userData'), 'heixiu-data'); } /** * 将相对路径解析为绝对路径,并校验是否在 dataDir 内。 * 防止路径穿越攻击。 */ function resolveSafe(dataDir: string, relativePath: string): string { const resolved = path.resolve(dataDir, relativePath); if (!resolved.startsWith(dataDir + path.sep) && resolved !== dataDir) { throw new Error(`路径越界:${relativePath}`); } return resolved; } /** 确保目录存在(递归创建) */ function ensureDir(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } // ---------- 注册 IPC 处理器 ---------- export function registerStorageIpcHandlers(): void { const dataDir = getDataDir(); ensureDir(dataDir); logger.info('app', `本地存储目录:${dataDir}`); // ---- 获取数据目录 ---- ipcMain.handle(BIDIRECTIONAL.GET_DATA_DIR, () => { return { success: true, data: dataDir }; }); // ---- 文件写入 ---- ipcMain.handle( BIDIRECTIONAL.FILE_WRITE, async ( _event, params: { relativePath: string; data: string; encoding?: 'base64' | 'utf8' }, ) => { try { const filePath = resolveSafe(dataDir, params.relativePath); ensureDir(path.dirname(filePath)); const buffer = Buffer.from(params.data, params.encoding || 'base64'); fs.writeFileSync(filePath, buffer); console.log(`[storage IPC] 文件写入成功:${filePath}(${buffer.length} bytes)`); return { success: true, data: filePath }; } catch (err) { const msg = (err as Error).message; console.error(`[storage IPC] 文件写入失败:${params.relativePath} — ${msg}`); logger.warn('storage', `文件写入失败:${params.relativePath}`, err instanceof Error ? err : undefined); return { success: false, error: msg }; } }, ); // ---- 文件读取(返回 Base64) ---- ipcMain.handle(BIDIRECTIONAL.FILE_READ, async (_event, params: { relativePath: string }) => { try { const filePath = resolveSafe(dataDir, params.relativePath); if (!fs.existsSync(filePath)) { return { success: false, error: '文件不存在' }; } const buffer = fs.readFileSync(filePath); return { success: true, data: buffer.toString('base64') }; } catch (err) { logger.warn('storage', `文件读取失败:${params.relativePath}`, err instanceof Error ? err : undefined); return { success: false, error: (err as Error).message }; } }); // ---- 文件删除 ---- ipcMain.handle(BIDIRECTIONAL.FILE_DELETE, async (_event, params: { relativePath: string }) => { try { const filePath = resolveSafe(dataDir, params.relativePath); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } return { success: true }; } catch (err) { logger.warn('storage', `文件删除失败:${params.relativePath}`, err instanceof Error ? err : undefined); return { success: false, error: (err as Error).message }; } }); // ---- 创建目录 ---- ipcMain.handle(BIDIRECTIONAL.MAKE_DIR, async (_event, params: { relativePath: string }) => { try { const dirPath = resolveSafe(dataDir, params.relativePath); ensureDir(dirPath); return { success: true, data: dirPath }; } catch (err) { logger.warn('storage', `目录创建失败:${params.relativePath}`, err instanceof Error ? err : undefined); return { success: false, error: (err as Error).message }; } }); // ---- 在文件管理器中显示 ---- ipcMain.handle(BIDIRECTIONAL.SHOW_ITEM_IN_FOLDER, async (_event, params: { filePath: string }) => { try { shell.showItemInFolder(params.filePath); return { success: true }; } catch (err) { logger.warn('storage', `showItemInFolder 失败:${params.filePath}`, err instanceof Error ? err : undefined); return { success: false, error: (err as Error).message }; } }); // ---- 文件大小 ---- ipcMain.handle(BIDIRECTIONAL.FILE_SIZE, async (_event, params: { relativePath: string }) => { try { const filePath = resolveSafe(dataDir, params.relativePath); if (!fs.existsSync(filePath)) { return { success: false, error: '文件不存在' }; } const stat = fs.statSync(filePath); return { success: true, data: stat.size }; } catch (err) { return { success: false, error: (err as Error).message }; } }); // ---- 文件/目录是否存在 ---- ipcMain.handle(BIDIRECTIONAL.FILE_EXISTS, async (_event, params: { relativePath: string }) => { try { const filePath = resolveSafe(dataDir, params.relativePath); return { success: true, data: fs.existsSync(filePath) }; } catch { return { success: true, data: false }; } }); logger.info('app', '存储 IPC 处理器已注册'); }