Files
MarkLite/src/main/file-watcher.ts
T
thzxx 9c92dcfa9d 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 字段
2026-05-28 13:42:11 +08:00

106 lines
2.7 KiB
TypeScript

import * as fs from 'fs'
import { BrowserWindow } from 'electron'
export class FileWatcher {
private watcher: fs.FSWatcher | null = null
private currentPath: string | null = null
private isSelfWriting = false
constructor(private getMainWindow: () => BrowserWindow | null) {}
start(filePath: string): void {
this.stop()
if (!filePath) return
try {
this.currentPath = filePath
this.watcher = fs.watch(filePath, (eventType) => {
if (eventType === 'change') {
if (this.isSelfWriting) return
const win = this.getMainWindow()
if (win && !win.isDestroyed()) {
win.webContents.send('file:externallyModified', filePath)
}
}
})
// L-02: 监听 error 事件,文件被删除时显式关闭并清理状态
this.watcher.on('error', () => {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
this.currentPath = null
this.isSelfWriting = false
})
} catch {
// silently ignore
}
}
stop(): void {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
this.currentPath = null
}
getCurrentPath(): string | null {
return this.currentPath
}
setSelfWriting(value: boolean): void {
this.isSelfWriting = value
}
}
export class SidebarWatcher {
private watcher: fs.FSWatcher | null = null
private watchPath: string | null = null
private refreshTimer: NodeJS.Timeout | null = null
constructor(private getMainWindow: () => BrowserWindow | null) {}
start(dirPath: string): void {
this.stop()
if (!dirPath) return
try {
this.watchPath = dirPath
this.watcher = fs.watch(dirPath, { recursive: true }, () => {
const win = this.getMainWindow()
if (win && !win.isDestroyed()) {
if (this.refreshTimer) clearTimeout(this.refreshTimer)
this.refreshTimer = setTimeout(() => {
// M-03: 延迟后再次检查窗口状态
const w = this.getMainWindow()
if (w && !w.isDestroyed()) {
w.webContents.send('sidebar:dirChanged')
}
}, 300)
}
})
// L-02: 监听 error 事件,显式关闭
this.watcher.on('error', () => {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
this.watchPath = null
})
} catch {
// silently ignore
}
}
stop(): void {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
if (this.refreshTimer) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.watchPath = null
}
}