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

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація офлайн-режиму десктоп-застосунку
Складна
~3-5 робочих днів
Часті питання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

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

Оффлайн-режим — це не просто «показати заглушку при відсутності сети». Це архітектурне рішення: приложение має працювати повнофункціонально без сети та синхронізуватися, коли вона з'явиться. Між цими станами — черга операцій, дозвіл конфліктів та чесне відображення статусу.

Детектування стану мережі

// main/network-monitor.js
const { net } = require('electron');

class NetworkMonitor {
  constructor() {
    this.isOnline = true;
    this.checkInterval = null;
  }

  start(mainWindow) {
    this.window = mainWindow;
    this.checkInterval = setInterval(() => this.checkConnectivity(), 15000);
    this.checkConnectivity();
  }

  async checkConnectivity() {
    const wasOnline = this.isOnline;
    try {
      const response = await Promise.race([
        fetch('https://api.your-app.com/ping', { method: 'HEAD' }),
        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))
      ]);
      this.isOnline = response.ok;
    } catch {
      this.isOnline = false;
    }

    if (wasOnline !== this.isOnline) {
      this.window?.webContents.send('network:change', { isOnline: this.isOnline });
    }
  }

  stop() {
    clearInterval(this.checkInterval);
  }
}

module.exports = new NetworkMonitor();

Локальна база даних: SQLite

// main/db.js
const Database = require('better-sqlite3');
const path = require('path');
const { app } = require('electron');

const dbPath = path.join(app.getPath('userData'), 'app.db');
const db = new Database(dbPath);

db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');

db.exec(`
  CREATE TABLE IF NOT EXISTS documents (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    updated_at INTEGER NOT NULL,
    server_updated_at INTEGER,
    sync_status TEXT NOT NULL DEFAULT 'synced'
  );

  CREATE TABLE IF NOT EXISTS sync_queue (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    operation TEXT NOT NULL,
    entity_type TEXT NOT NULL,
    entity_id TEXT NOT NULL,
    payload TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    attempts INTEGER NOT NULL DEFAULT 0
  );
`);

module.exports = db;

Паттерн «оптимістичного оновлення»

// main/documents.js
const db = require('./db');

function createDocument(doc) {
  const id = doc.id || crypto.randomUUID();
  const now = Date.now();

  // Записуємо локально негайно
  db.prepare(`
    INSERT INTO documents (id, title, content, updated_at, sync_status)
    VALUES (@id, @title, @content, @updated_at, @sync_status)
  `).run({
    id, title: doc.title, content: doc.content,
    updated_at: now, sync_status: 'pending'
  });

  // Додаємо в чергу синхронізації
  db.prepare(`
    INSERT INTO sync_queue (operation, entity_type, entity_id, payload, created_at)
    VALUES ('create', 'document', @id, @payload, @created_at)
  `).run({
    id, payload: JSON.stringify(doc), created_at: now
  });

  return { id, title: doc.title, content: doc.content, sync_status: 'pending' };
}

module.exports = { createDocument };

Процес синхронізації

// main/sync.js
const db = require('./db');
const networkMonitor = require('./network-monitor');

class SyncManager {
  constructor() {
    this.isSyncing = false;
    networkMonitor.on('change', (isOnline) => {
      if (isOnline) this.sync();
    });
  }

  async sync() {
    if (this.isSyncing || !networkMonitor.isOnline) return;
    this.isSyncing = true;

    try {
      await this.pushPendingOperations();
      await this.pullRemoteChanges();
    } finally {
      this.isSyncing = false;
    }
  }

  async pushPendingOperations() {
    const pending = db.prepare(`
      SELECT * FROM sync_queue WHERE attempts < 3 ORDER BY created_at LIMIT 50
    `).all();

    for (const item of pending) {
      try {
        await this.executeOperation(item);
        db.prepare('DELETE FROM sync_queue WHERE id = ?').run(item.id);
        db.prepare(`
          UPDATE documents SET sync_status = 'synced' WHERE id = ?
        `).run(item.entity_id);
      } catch (error) {
        db.prepare(`
          UPDATE sync_queue SET attempts = attempts + 1 WHERE id = ?
        `).run(item.id);
      }
    }
  }

  async executeOperation(item) {
    const token = await getAuthToken();
    const response = await fetch(`https://api.your-app.com/${item.entity_type}s`, {
      method: item.operation === 'create' ? 'POST' : 'PUT',
      headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
      body: item.payload
    });

    if (!response.ok) throw new Error(`API error ${response.status}`);
    return response.json();
  }
}

module.exports = new SyncManager();

Індикатори статусу синхронізації

Користувач має завжди розуміти, чи збережені його дані:

  • Іконка хмари з хрестиком = немає мережі, незбережені зміни
  • Хмара зі стрілкою = синхронізація йде
  • Хмара з галочкою = все синхронізовано
  • Іконка попередження = конфлікти

Статус має бути в постійно видимій частині UI — заголовок або підвал.

Повна реалізація оффлайн-режиму з конфліктами, чергою та синхронізацією займає 3-5 днів для базового функціоналу.