refactor: 删除分屏功能 + 代码审计修复 + 安全加固
## 删除分屏功能 - 删除 ViewMode 'split' 类型,仅保留 editor/preview - 删除 splitRatio 状态和 Resizer 组件 - 删除滚动同步模块 (scrollSync.ts, rehypeSourceLine.ts) - 更新快捷键: Ctrl+1 编辑, Ctrl+2 预览 ## Bug 修复 - 修复 Toast setTimeout 内存泄漏 (App.tsx) - 修复 ModifiedBanner reload 更新错误标签 (改用 modifiedFilePath 匹配) - 修复 FileWatcher error 未重置 isSelfWriting (file-watcher.ts) - 修复另存为时未 stop watcher (ipc-handlers.ts) ## 死代码清理 (10 项) - 删除 main/ipc-channels.ts (与 shared/ 重复) - 删除 main/file-system.ts 未使用的 formatBytes - 删除 constants.ts 6 个未使用常量 - 删除 fileUtils.ts 未使用的 formatBytes - 删除 Icons.tsx 5 个未使用图标 (SplitView/ChevronUp/ChevronDown/X/ArrowUp/ArrowDown) - 删除 useCodeMirror 未使用的 getContent/scrollTo - 删除 TabBar 未使用的 menuRef - 删除 Tab.scrollLeft/previewScrollTop 字段 - 删除 tabStore 未使用的 getTabIndex - 删除 Toast/ModifiedBanner 多余 React import ## 安全加固 - ipc-handlers: 添加路径遍历防护 (validatePath 函数) - preload: openExternal 仅允许 http/https 协议 - window-manager: 启用 sandbox: true - preload: removeAllListeners 改为精确取消订阅 (返回 Unsubscribe 函数) ## 状态持久化 - activeTabId 持久化到 IndexedDB (刷新后恢复正确标签) - sidebar 状态持久化到 IndexedDB (isVisible/sidebarWidth) ## 代码优化 - preload 使用 IPC_CHANNELS 常量替代硬编码字符串 - ipc-handlers _event 类型改为 IpcMainInvokeEvent - settingsRepository 删除 splitRatio 字段
This commit is contained in:
+44
-11
@@ -1,9 +1,22 @@
|
||||
import { ipcMain, dialog, BrowserWindow } from 'electron'
|
||||
import { ipcMain, dialog, BrowserWindow, type IpcMainInvokeEvent } from 'electron'
|
||||
import { readFileContent, saveFileContent, buildDirTree } from './file-system'
|
||||
import { FileWatcher, SidebarWatcher } from './file-watcher'
|
||||
import { IPC_CHANNELS } from './ipc-channels'
|
||||
import { IPC_CHANNELS } from '../shared/ipc-channels'
|
||||
import { stat } from 'fs/promises'
|
||||
import { join, basename } from 'path'
|
||||
import { join, basename, resolve, normalize, isAbsolute } from 'path'
|
||||
|
||||
// 安全校验:拒绝路径遍历攻击
|
||||
function validatePath(filePath: string): boolean {
|
||||
if (!filePath || typeof filePath !== 'string') return false
|
||||
// 拒绝空字节
|
||||
if (filePath.includes('\0')) return false
|
||||
// 规范化路径后检查是否包含 ..
|
||||
const normalized = normalize(filePath)
|
||||
if (normalized.includes('..')) return false
|
||||
// 必须是绝对路径
|
||||
if (!isAbsolute(normalized)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export function registerIpcHandlers(
|
||||
getMainWindow: () => BrowserWindow | null,
|
||||
@@ -38,12 +51,18 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
// 读取文件
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_READ, async (_event: unknown, filePath: string) => {
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_READ, async (_event: IpcMainInvokeEvent, filePath: string) => {
|
||||
if (!validatePath(filePath)) {
|
||||
return { success: false, error: '无效的文件路径' }
|
||||
}
|
||||
return readFileContent(filePath)
|
||||
})
|
||||
|
||||
// 保存文件
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_SAVE, async (_event: unknown, data: { filePath: string | null; content: string }) => {
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_SAVE, async (_event: IpcMainInvokeEvent, data: { filePath: string | null; content: string }) => {
|
||||
if (data.filePath && !validatePath(data.filePath)) {
|
||||
return { success: false, error: '无效的文件路径' }
|
||||
}
|
||||
const win = getMainWindow()
|
||||
try {
|
||||
if (data.filePath) {
|
||||
@@ -87,7 +106,7 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
// 另存为
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_SAVE_AS, async (_event: unknown, data: { content: string }) => {
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_SAVE_AS, async (_event: IpcMainInvokeEvent, data: { content: string }) => {
|
||||
const win = getMainWindow()
|
||||
if (!win) return { success: false, error: '窗口不可用' }
|
||||
try {
|
||||
@@ -95,16 +114,24 @@ export function registerIpcHandlers(
|
||||
filters: [{ name: 'Markdown 文件', extensions: ['md'] }]
|
||||
})
|
||||
if (!result.canceled) {
|
||||
fileWatcher.stop()
|
||||
fileWatcher.setSelfWriting(true)
|
||||
const saveResult = await saveFileContent(result.filePath, data.content)
|
||||
fileWatcher.setSelfWriting(false)
|
||||
if (saveResult.success) {
|
||||
state.activeFilePath = result.filePath
|
||||
fileWatcher.start(result.filePath)
|
||||
setTimeout(() => {
|
||||
if (state.activeFilePath === result.filePath) {
|
||||
fileWatcher.start(result.filePath)
|
||||
}
|
||||
}, 300)
|
||||
win.setTitle(`MarkLite - ${basename(result.filePath)}`)
|
||||
}
|
||||
return saveResult
|
||||
}
|
||||
return { success: false, canceled: true }
|
||||
} catch (err) {
|
||||
fileWatcher.setSelfWriting(false)
|
||||
return { success: false, error: (err as Error).message }
|
||||
}
|
||||
})
|
||||
@@ -113,7 +140,10 @@ export function registerIpcHandlers(
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_GET_CURRENT_PATH, () => state.activeFilePath)
|
||||
|
||||
// 文件统计
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_STATS, async (_event: unknown, filePath: string) => {
|
||||
ipcMain.handle(IPC_CHANNELS.FILE_STATS, async (_event: IpcMainInvokeEvent, filePath: string) => {
|
||||
if (!validatePath(filePath)) {
|
||||
return { success: false, error: '无效的文件路径' }
|
||||
}
|
||||
try {
|
||||
const fileStat = await stat(filePath)
|
||||
return { success: true, size: fileStat.size, mtime: fileStat.mtime.toISOString() }
|
||||
@@ -130,7 +160,10 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
// 目录树
|
||||
ipcMain.handle(IPC_CHANNELS.DIR_READ_TREE, async (_event: unknown, dirPath: string) => {
|
||||
ipcMain.handle(IPC_CHANNELS.DIR_READ_TREE, async (_event: IpcMainInvokeEvent, dirPath: string) => {
|
||||
if (!validatePath(dirPath)) {
|
||||
return { success: false, error: '无效的目录路径' }
|
||||
}
|
||||
try {
|
||||
const tree = await buildDirTree(dirPath)
|
||||
return { success: true, tree, rootPath: dirPath }
|
||||
@@ -151,7 +184,7 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
// 目录监听
|
||||
ipcMain.handle(IPC_CHANNELS.DIR_WATCH, (_event: unknown, dirPath: string) => {
|
||||
ipcMain.handle(IPC_CHANNELS.DIR_WATCH, (_event: IpcMainInvokeEvent, dirPath: string) => {
|
||||
sidebarWatcher.start(dirPath)
|
||||
})
|
||||
|
||||
@@ -160,7 +193,7 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
// 标签切换
|
||||
ipcMain.handle(IPC_CHANNELS.TAB_SWITCHED, (_event: unknown, filePath: string | null) => {
|
||||
ipcMain.handle(IPC_CHANNELS.TAB_SWITCHED, (_event: IpcMainInvokeEvent, filePath: string | null) => {
|
||||
state.activeFilePath = filePath || null
|
||||
fileWatcher.start(filePath || '')
|
||||
const win = getMainWindow()
|
||||
|
||||
Reference in New Issue
Block a user