📝 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
| Fichier | Rôle |
|---|---|
src/main/index.ts | Cycle de vie Electron, création fenêtre, tous les ipcMain.handle, minimize-to-tray |
src/main/rclone.ts | findRclone(), writeRcloneConfig(), testConnection(), startSync() |
src/main/credentials.ts | saveAllCredentials() / loadAllCredentials() — keytar + fallback chiffré |
src/main/tray.ts | createTray(), setTrayStatus(idle|syncing|error) |
src/main/scheduler.ts | startScheduler(cfg, onTick) — interval ou expression cron |
src/preload/index.ts | contextBridge.exposeInMainWorld('api', …) — surface IPC typée |
src/renderer/src/screens/Config.tsx | Picker dossier natif, formulaire R2, test connexion |
src/renderer/src/screens/Sync.tsx | Dry-run, log streaming, progress bar, stats |
src/renderer/src/screens/Scheduler.tsx | Sélecteur disabled / interval / cron |
Flux de données
Sync manuelle :
- Renderer →
api.sync.start({ localFolder, bucket, dryRun }) - Main :
startSync()spawnrclone sync <local> r2:<bucket> --progress - stdout/stderr →
webContents.send('sync:log', line) - Fin process →
webContents.send('sync:done', { code, timestamp, fileCount })
Sync schedulée :
- Renderer →
api.scheduler.set(cfg)→ mainstartScheduler(cfg, triggerSync) - node-cron tick →
triggerSync(false)→ idem flux sync manuelle
Test connexion :
- Renderer →
api.config.test(data) - Main : écrit rclone.conf →
rclone lsf r2:<bucket> --max-depth 1 - Retourne
{ ok, error? }
Dépendances externes
| Dépendance | Rôle | Note |
|---|---|---|
rclone (binaire système) | Moteur de sync S3 | Doit être dans PATH, non bundlé |
keytar | OS keychain (natif Node) | Recompilation native requise |
node-cron | Scheduler cron | Pure JS |
electron | Runtime desktop | v31 |
electron-vite | Build toolchain | 3 bundles : main/preload/renderer |
Fichiers générés (runtime)
| Chemin | Contenu |
|---|---|
userData/rclone.conf | Config rclone R2 isolée (credentials en clair dans ce fichier — protégé par permissions OS) |
userData/credentials.enc | Credentials chiffrés AES-256-GCM (fallback si pas de keychain) |
logs/rclone.log | Sortie rclone (toutes les sessions) |