返回 oh-my-ppt
file-handlers.ts
根目录 / src / main / io / file-handlers.ts
1 import { ipcMain, shell } from 'electron'
2 import fs from 'fs'
3 import path from 'path'
4 import { pathToFileURL } from 'url'
5 import type { IpcContext } from '../ipc/context'
6 import { ensureSessionRuntimeCompatible } from '../session/runtime-assets'
7
8 export function registerFileHandlers(ctx: IpcContext): void {
9 const { parsePathPayload, normalizeSessionId, assertPathInAllowedRoots } = ctx
10
11 ipcMain.handle('file:open', async (_event, payload, legacySessionId?: string) => {
12 const parsed = parsePathPayload(payload, 'path')
13 const sessionId = parsed.sessionId ?? normalizeSessionId(legacySessionId)
14 const safePath = await assertPathInAllowedRoots({
15 filePath: parsed.filePath,
16 mode: 'read',
17 sessionId
18 })
19 return fs.promises.readFile(safePath, 'utf-8')
20 })
21
22 ipcMain.handle('file:reveal', async (_event, payload, legacySessionId?: string) => {
23 const parsed = parsePathPayload(payload, 'path')
24 const sessionId = parsed.sessionId ?? normalizeSessionId(legacySessionId)
25 const safePath = await assertPathInAllowedRoots({
26 filePath: parsed.filePath,
27 mode: 'read',
28 sessionId
29 })
30 shell.showItemInFolder(safePath)
31 return { success: true }
32 })
33
34 ipcMain.handle(
35 'file:openInBrowser',
36 async (_event, payloadOrPath, legacyHash?: string, legacySessionId?: string) => {
37 const parsed = parsePathPayload(payloadOrPath, 'path')
38 const sessionId = parsed.sessionId ?? normalizeSessionId(legacySessionId)
39 const hashRaw = typeof legacyHash === 'string' ? legacyHash : parsed.hash
40 const safePath = await assertPathInAllowedRoots({
41 filePath: parsed.filePath,
42 mode: 'read',
43 sessionId,
44 htmlOnly: true
45 })
46
47 // Ensure index-runtime.js is up-to-date before opening in browser
48 try {
49 const projectDir = path.dirname(safePath)
50 await ensureSessionRuntimeCompatible(ctx, projectDir)
51 } catch {
52 // ignore
53 }
54
55 const baseUrl = pathToFileURL(safePath).toString()
56 const hashValue =
57 typeof hashRaw === 'string' && hashRaw.trim().length > 0
58 ? hashRaw.startsWith('#')
59 ? hashRaw
60 : `#${hashRaw}`
61 : ''
62 await shell.openExternal(`${baseUrl}${hashValue}`)
63 return { success: true }
64 }
65 )
66
67 ipcMain.handle('file:save', async (_event, payload) => {
68 if (!payload || typeof payload !== 'object') {
69 throw new Error('file:save 参数无效')
70 }
71 const record = payload as { path?: unknown; content?: unknown; sessionId?: unknown }
72 const filePath = typeof record.path === 'string' ? record.path : ''
73 const content = typeof record.content === 'string' ? record.content : ''
74 const sessionId = normalizeSessionId(record.sessionId)
75 const safePath = await assertPathInAllowedRoots({
76 filePath,
77 mode: 'write',
78 sessionId
79 })
80 await fs.promises.writeFile(safePath, content, 'utf-8')
81 return { success: true }
82 })
83 }
84
84 lines TYPESCRIPT