📝 Note

pro/syncs3 app/architecture

electronrclonereactvitenode-cronkeytarcontextbridge

Architecture — syncS3-app

Liens : Decisions · Journal

Vue d’ensemble

Application Electron desktop (Windows + macOS). Un seul process renderer (React), un main process Node.js. Communication exclusivement via IPC contextBridge. rclone est un binaire système externe ; l’app le détecte au démarrage et lui passe une config R2 isolée.

┌─────────────────────────────────────────────────┐
│  Main Process (Node.js)                         │
│  ┌─────────┐ ┌────────────┐ ┌───────────────┐  │
│  │rclone.ts│ │credentials │ │  scheduler.ts  │  │
│  │ spawn   │ │  keytar /  │ │  node-cron     │  │
│  │ config  │ │  AES-GCM   │ │                │  │
│  └────┬────┘ └────────────┘ └───────┬───────┘  │
│       │         tray.ts             │           │
│       │    ┌───────────┐            │           │
│       │    │ systray   │◄───────────┘           │
│       │    └───────────┘                        │
│  index.ts — IPC handlers                        │
└──────────────────┬──────────────────────────────┘
                   │ contextBridge (preload)
┌──────────────────▼──────────────────────────────┐
│  Renderer (React 18 + Vite)                     │
│  App.tsx → Config | Sync | Scheduler screens    │
└─────────────────────────────────────────────────┘
           ↕ rclone binary (system PATH)
    r2:<bucket> via HTTPS (Cloudflare R2 S3 API)

Composants principaux

FichierRôle
src/main/index.tsCycle de vie Electron, création fenêtre, tous les ipcMain.handle, minimize-to-tray
src/main/rclone.tsfindRclone(), writeRcloneConfig(), testConnection(), startSync()
src/main/credentials.tssaveAllCredentials() / loadAllCredentials() — keytar + fallback chiffré
src/main/tray.tscreateTray(), setTrayStatus(idle|syncing|error)
src/main/scheduler.tsstartScheduler(cfg, onTick) — interval ou expression cron
src/preload/index.tscontextBridge.exposeInMainWorld('api', …) — surface IPC typée
src/renderer/src/screens/Config.tsxPicker dossier natif, formulaire R2, test connexion
src/renderer/src/screens/Sync.tsxDry-run, log streaming, progress bar, stats
src/renderer/src/screens/Scheduler.tsxSélecteur disabled / interval / cron

Flux de données

Sync manuelle :

  1. Renderer → api.sync.start({ localFolder, bucket, dryRun })
  2. Main : startSync() spawn rclone sync <local> r2:<bucket> --progress
  3. stdout/stderr → webContents.send('sync:log', line)
  4. Fin process → webContents.send('sync:done', { code, timestamp, fileCount })

Sync schedulée :

  1. Renderer → api.scheduler.set(cfg) → main startScheduler(cfg, triggerSync)
  2. node-cron tick → triggerSync(false) → idem flux sync manuelle

Test connexion :

  1. Renderer → api.config.test(data)
  2. Main : écrit rclone.conf → rclone lsf r2:<bucket> --max-depth 1
  3. Retourne { ok, error? }

Dépendances externes

DépendanceRôleNote
rclone (binaire système)Moteur de sync S3Doit être dans PATH, non bundlé
keytarOS keychain (natif Node)Recompilation native requise
node-cronScheduler cronPure JS
electronRuntime desktopv31
electron-viteBuild toolchain3 bundles : main/preload/renderer

Fichiers générés (runtime)

CheminContenu
userData/rclone.confConfig rclone R2 isolée (credentials en clair dans ce fichier — protégé par permissions OS)
userData/credentials.encCredentials chiffrés AES-256-GCM (fallback si pas de keychain)
logs/rclone.logSortie rclone (toutes les sessions)