Реализация доступа к файловой системе в десктоп-приложении

Текстовый редактор на Electron несколько секунд подвисает при открытии файла больше 50 МБ. Пользователи жалуются на лаги при перетаскивании файлов. Причина — неоптимизированный IPC и отсутствие предварительной проверки размера. Оба фреймворка — Electron и Tauri — имеют свои нюансы. Разберёмся, как п

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация доступа к файловой системе в десктоп-приложении
Средний
~2-3 дня

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    983
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1247
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    984
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    998

Текстовый редактор на Electron несколько секунд подвисает при открытии файла больше 50 МБ. Пользователи жалуются на лаги при перетаскивании файлов. Причина — неоптимизированный IPC и отсутствие предварительной проверки размера. Оба фреймворка — Electron и Tauri — имеют свои нюансы. Разберёмся, как правильно реализовать доступ к файловой системе в десктоп-приложении, чтобы таких проблем не возникало.

Проблемы, которые решаем

Path traversal — когда renderer отправляет путь вида ../../etc/passwd. В Electron эту проблему решают проверкой на стороне main process. В Tauri — разграничением scope в capabilities/default.json. В одном из проектов мы обнаружили, что клиент передавал абсолютные пути без валидации — исправили за час, но могли потерять данные. Подробнее о Path traversal на Wikipedia.

Неэффективный IPC при больших файлах — передача через ipcRenderer.invoke файлов размером 100+ МБ блокирует рендерер. Решение — использовать стримы или передавать ArrayBuffer через SharedArrayBuffer. В Electron появилась поддержка MessagePort для передачи больших объёмов данных без копирования.

Сложности с мониторингом измененийfs.watch в Node.js нестабилен на macOS — пропускает события при большом количестве изменений. Альтернатива — polling с интервалом 1000 мс или использование chokidar с настройкой awaitWriteFinish. Для Tauri — плагин tauri-plugin-fs с watch, который использует inotify на Linux и FSEvents на macOS.

Как обеспечить безопасность файлового доступа?

В Electron весь файловый ввод-вывод выполняется в main process. Renderer запрашивает операции через IPC. Это предотвращает прямые атаки из UI. Используйте contextBridge для изоляции — не экспортируйте из preload ничего, кроме безопасных методов. В Tauri модель разрешений ещё строже: каждое действие (чтение, запись, удаление) нужно задекларировать в capabilities/default.json. Укажите scope на конкретные каталоги: app-data, documents, downloads. Никогда не давайте доступ ко всей файловой системе.

Пример настройки permissions в Tauri
{ "permissions": [ "fs:allow-read-text-file", "fs:allow-write-text-file", "fs:allow-read-dir", "fs:allow-watch", "fs:scope-app-data-recursive", "fs:scope-document-recursive", "dialog:allow-open", "dialog:allow-save" ] } 

Реализация в Electron

Классический подход — класс FileSystemService в main process. Он использует fs/promises и fs для работы с файлами, а также dialog для открытия/сохранения через нативный интерфейс. Важно: предупреждайте пользователя о файлах >50 MB, чтобы не повесить процесс. Для списка файлов используйте фильтр скрытых и сортировку: папки первыми, затем по алфавиту.

// main/fs-service.js const fs = require('fs/promises'); const fsSync = require('fs'); const path = require('path'); const { app, dialog } = require('electron'); class FileSystemService { async openFileDialog(win, options = {}) { const result = await dialog.showOpenDialog(win, { properties: ['openFile'], filters: options.filters ?? [{ name: 'All Files', extensions: ['*'] }], ...options }); if (result.canceled || result.filePaths.length === 0) return null; return this.readFile(result.filePaths[0]); } async openFolderDialog(win) { const result = await dialog.showOpenDialog(win, { properties: ['openDirectory'] }); if (result.canceled) return null; return result.filePaths[0]; } async readFile(filePath) { const stat = await fs.stat(filePath); if (stat.size > 50 * 1024 * 1024) { throw new Error(`File too large: ${(stat.size / 1024 / 1024).toFixed(1)} MB`); } const content = await fs.readFile(filePath, 'utf-8'); return { path: filePath, name: path.basename(filePath), ext: path.extname(filePath).slice(1), content, size: stat.size, modified: stat.mtimeMs }; } async saveFile(win, content, currentPath = null) { let savePath = currentPath; if (!savePath) { const result = await dialog.showSaveDialog(win, { defaultPath: path.join(app.getPath('documents'), 'untitled.txt') }); if (result.canceled) return null; savePath = result.filePath; } await fs.writeFile(savePath, content, 'utf-8'); return savePath; } async listDirectory(dirPath, options = {}) { const entries = await fs.readdir(dirPath, { withFileTypes: true }); const items = await Promise.all(entries.filter(e => options.showHidden || !e.name.startsWith('.')).map(async (entry) => { const fullPath = path.join(dirPath, entry.name); let stat; try { stat = await fs.stat(fullPath); } catch { return null; } return { name: entry.name, path: fullPath, isDirectory: entry.isDirectory(), size: entry.isFile() ? stat.size : 0, modified: stat.mtimeMs, ext: entry.isFile() ? path.extname(entry.name).slice(1) : null }; })); return items.filter(Boolean).sort((a, b) => { if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1; return a.name.localeCompare(b.name); }); } async copyDirectory(src, dest) { await fs.mkdir(dest, { recursive: true }); const entries = await fs.readdir(src, { withFileTypes: true }); await Promise.all(entries.map(entry => { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); return entry.isDirectory() ? this.copyDirectory(srcPath, destPath) : fs.copyFile(srcPath, destPath); })); } watchFile(filePath, callback) { const watcher = fsSync.watch(filePath, { persistent: false }, (eventType) => callback({ eventType, path: filePath })); return () => watcher.close(); } watchDirectory(dirPath, callback) { const watcher = fsSync.watch(dirPath, { recursive: true, persistent: false }, (eventType, filename) => { if (filename) callback({ eventType, path: path.join(dirPath, filename), filename }); }); return () => watcher.close(); } getAppPaths() { return { userData: app.getPath('userData'), documents: app.getPath('documents'), downloads: app.getPath('downloads'), temp: app.getPath('temp'), home: app.getPath('home') }; } } module.exports = new FileSystemService(); 

Drag & drop реализуется через обработку события drop в renderer с получением f.path (Electron добавляет это свойство). Чтобы не перегружать main process, передавайте через IPC только метаданные, а чтение выполняйте по запросу.

Реализация в Tauri

Подключаем плагины tauri-plugin-fs и tauri-plugin-dialog. Все операции с файлами требуют явных разрешений в capabilities/default.json.

# src-tauri/Cargo.toml [dependencies] tauri-plugin-fs = "2" tauri-plugin-dialog = "2" 

Пример чтения и записи:

// renderer/api/fs.ts import { readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; import { open, save } from '@tauri-apps/plugin-dialog'; export async function openAndReadFile() { const selected = await open({ multiple: false }); if (!selected) return null; const content = await readTextFile(selected as string); return { path: selected as string, content }; } export async function saveToFile(content: string, currentPath?: string) { const filePath = currentPath ?? await save({ filters: [{ name: 'Text', extensions: ['txt'] }] }); if (!filePath) return null; await writeTextFile(filePath as string, content); return filePath; } 

Для бинарных файлов используйте readFile и writeFile из того же плагина — они работают с Uint8Array.

Почему Tauri быстрее Electron?

Tauri компилирует бинарник с Rust и использует системный WebView, а не встроенный Chromium. Размер приложения в 2-3 раза меньше, а потребление памяти — до 70% ниже. Однако Tauri требует объявления всех операций в capabilities, что делает код безопаснее, но немного увеличивает время настройки.

Сравнение Electron и Tauri для файлового доступа

Характеристика Electron Tauri
API Node.js fs (полный доступ) tauri-plugin-fs (ограниченный scope)
Безопасность Требуется явное разделение процессов Модель разрешений из коробки
Размер приложения >100 MB (включая Chromium) <10 MB
Производительность Средняя (из-за Chromium) Высокая (нативный код)
Работа с бинарными файлами Через Buffer и ArrayBuffer Через Uint8Array, конвертация
Мониторинг файлов fs.watch / fs.watchFile tauri-plugin-fs watch
Кросс-платформенность Windows, macOS, Linux Windows, macOS, Linux (iOS, Android в разработке)

Процесс и объём работы

  1. Аналитика — изучаем ваше текущее приложение, требования к файловому доступу, определяем уязвимости. Создаём карту потока данных.
  2. Проектирование — разрабатываем схему IPC или permissions, выбираем библиотеки, проектируем API.
  3. Реализация — пишем код с модульным тестированием (jest для Node.js, vitest для Tauri). Используем Repository pattern для изоляции бизнес-логики.
  4. Тестирование — проверяем на реальных файлах (до 10 ГБ), имитируем атаки path traversal, тестируем на Windows, macOS, Linux.
  5. Деплой — собираем бинарники, настраиваем auto-update, документируем API.

В работе используем: Electron 28+, Tauri 2, TypeScript, ESLint, Prettier, Husky.

Кейс: для одного стартапа мы реализовали файловый менеджер на Electron с возможностью открытия файлов до 4 ГБ без подвисаний. Решение — чанковая передача через IPC с прогресс-баром и прерыванием по требованию. Время загрузки файла 500 МБ сократилось с 12 до 3 секунд. Закажите аудит вашего приложения — выявим уязвимости за 1 день.

Ориентировочные сроки

Этап Время
Анализ и проектирование 1 день
Реализация базового доступа (чтение/запись/диалоги) 2-3 дня
Мониторинг и бинарные файлы 1-2 дня
Тестирование и отладка 1-2 дня
Интеграция в существующий проект от 1 дня

Стоимость рассчитывается индивидуально. Свяжитесь с нами, чтобы получить консультацию. Закажите разработку файлового доступа — оценим проект за один день.