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

Текстовий редактор на 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 секунд. Наш досвід — понад 10 років у розробці десктоп-додатків, понад 40 виконаних проектів. Замовте аудит вашого додатку — виявимо вразливості за 1 день.

Орієнтовні терміни

Етап Час
Аналіз і проектування 1 день
Реалізація базового доступу (читання/запис/діалоги) 2-3 дні
Моніторинг і бінарні файли 1-2 дні
Тестування і налагодження 1-2 дні
Інтеграція в існуючий проект від 1 дня

Вартість розраховується індивідуально. Зв'яжіться з нами, щоб отримати консультацію. Замовте розробку файлового доступу — оцінимо проект за один день.