Apply Prettier formatting to maintain consistent code style across the codebase. Changes include formatting improvements for: - IPC handlers (database, file, material-type, resolver, user-erp-config, validation) - Type definitions (ipc-api.types) - React components (MaterialTypeManagementDialog) - React hooks (useAuth, useCleaner, useValidation) - Pages (SettingsPage) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { app, ipcMain, shell } from 'electron'
|
|
import * as fs from 'fs/promises'
|
|
import * as path from 'path'
|
|
import { createLogger } from '../services/logger'
|
|
import { IPC_CHANNELS } from '../../shared/ipc-channels'
|
|
import { ValidationError } from '../types/errors'
|
|
import { withErrorHandling, type IpcResult } from './index'
|
|
|
|
const log = createLogger('FileHandler')
|
|
|
|
function getAllowedRoots(): string[] {
|
|
return [path.resolve(app.getAppPath()), path.resolve(app.getPath('userData'))]
|
|
}
|
|
|
|
export function isPathWithinAllowedRoots(inputPath: string, roots: string[]): boolean {
|
|
const normalized = path.resolve(inputPath)
|
|
return roots.some((root) => {
|
|
const rel = path.relative(root, normalized)
|
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
})
|
|
}
|
|
|
|
function normalizeAndValidatePath(inputPath: string): string {
|
|
const normalized = path.resolve(inputPath)
|
|
const isAllowed = isPathWithinAllowedRoots(normalized, getAllowedRoots())
|
|
if (!isAllowed) {
|
|
throw new ValidationError('Path is outside allowed roots', 'VAL_INVALID_INPUT')
|
|
}
|
|
|
|
return normalized
|
|
}
|
|
|
|
export function registerFileHandlers(): void {
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.FILE_READ,
|
|
async (_event, filePath: string): Promise<IpcResult<string>> => {
|
|
return withErrorHandling(async () => {
|
|
const safePath = normalizeAndValidatePath(filePath)
|
|
log.debug('Reading file', { filePath: safePath })
|
|
return await fs.readFile(safePath, 'utf-8')
|
|
}, 'file:read')
|
|
}
|
|
)
|
|
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.FILE_WRITE,
|
|
async (_event, filePath: string, content: string): Promise<IpcResult<void>> => {
|
|
return withErrorHandling(async () => {
|
|
const safePath = normalizeAndValidatePath(filePath)
|
|
log.debug('Writing file', { filePath: safePath })
|
|
const dir = path.dirname(safePath)
|
|
await fs.mkdir(dir, { recursive: true })
|
|
await fs.writeFile(safePath, content, 'utf-8')
|
|
}, 'file:write')
|
|
}
|
|
)
|
|
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.FILE_EXISTS,
|
|
async (_event, filePath: string): Promise<IpcResult<boolean>> => {
|
|
return withErrorHandling(async () => {
|
|
const safePath = normalizeAndValidatePath(filePath)
|
|
try {
|
|
await fs.access(safePath)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}, 'file:exists')
|
|
}
|
|
)
|
|
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.FILE_LIST,
|
|
async (_event, dirPath: string): Promise<IpcResult<string[]>> => {
|
|
return withErrorHandling(async () => {
|
|
const safePath = normalizeAndValidatePath(dirPath)
|
|
log.debug('Listing directory', { dirPath: safePath })
|
|
const entries = await fs.readdir(safePath, { withFileTypes: true })
|
|
return entries
|
|
.filter((entry) => entry.isFile())
|
|
.map((entry) => entry.name)
|
|
.sort()
|
|
}, 'file:list')
|
|
}
|
|
)
|
|
|
|
ipcMain.handle(
|
|
IPC_CHANNELS.FILE_OPEN_PATH,
|
|
async (_event, filePath: string): Promise<IpcResult<void>> => {
|
|
return withErrorHandling(async () => {
|
|
const safePath = normalizeAndValidatePath(filePath)
|
|
log.debug('Opening path in explorer', { filePath: safePath })
|
|
await fs.access(safePath)
|
|
await shell.openPath(safePath)
|
|
}, 'file:openPath')
|
|
}
|
|
)
|
|
}
|