---
title: "Synchronisation offline"
weight: 190
---

# Synchronisation offline

SmartCommon fournit un module de synchronisation complet pour gérer le fonctionnement hors-ligne des applications PWA.

## Architecture

Le module sync est composé de :

| Élément | Type | Description |
| --- | --- | --- |
| `useSyncClient` | Hook React | Interface principale pour la synchronisation |
| `SyncEngine` | Classe | Moteur de synchronisation (push/pull/conflits) |
| `SyncStorage` | Classe | Couche IndexedDB pour le stockage local (Dexie) |
| `SyncApi` | Classe | Client HTTP avec auth JWT et retry automatique |
| `ConflictResolver` | Composant React | Interface de résolution de conflits |

## useSyncClient

Voir [Hooks - useSyncClient](/front/hooks#usesyncclient) pour la documentation complète du hook.

## Flux de synchronisation

### Push (local vers serveur)

```
1. L'utilisateur modifie des données localement (create/update/remove)
2. Les changements sont stockés dans IndexedDB (pending_changes)
3. Au prochain sync.push(), les changements sont envoyés au serveur
4. Le serveur confirme ou signale des conflits
5. Les temp_id locaux sont remplacés par les ID serveur
```

### Pull (serveur vers local)

```
1. sync.pull() demande les changements depuis lastSyncTime
2. Le serveur renvoie les entités modifiées
3. Les entités locales sont mises à jour
4. En cas de conflit (modification locale + serveur), un conflit est créé
```

## Gestion des conflits

Quand une entité est modifiée localement et sur le serveur, un conflit est créé.

### Résolution programmatique

```
const conflicts = await sync.getConflicts();

for (const conflict of conflicts) {
  // Garder la version client
  await sync.resolveConflict(conflict.conflict_id, 'client');

  // Garder la version serveur
  await sync.resolveConflict(conflict.conflict_id, 'server');

  // Fusionner manuellement
  await sync.resolveConflict(conflict.conflict_id, {
    ...conflict.server_data,
    label: conflict.client_data.label  // garder le label local
  });
}
```

### Résolution avec ConflictResolver

```
import { ConflictResolver } from '@cap-rel/smartcommon';

const MyConflictPage = () => {
  const sync = useSyncClient({ /* ... */ });
  const [conflicts, setConflicts] = useState([]);

  useEffect(() => {
    sync.getConflicts().then(setConflicts);
  }, []);

  if (conflicts.length === 0) return null;

  return (
    <ConflictResolver
      conflicts={conflicts}
      onResolve={async (conflictId, resolution) => {
        await sync.resolveConflict(conflictId, resolution);
        setConflicts(prev => prev.filter(c => c.conflict_id !== conflictId));
      }}
      onCancel={() => setConflicts([])}
    />
  );
};
```

Le composant `ConflictResolver` affiche :

- Comparaison côte à côte des données client et serveur
- Marqueurs sur les champs en conflit
- Trois options : garder client, garder serveur, fusionner champ par champ
- Navigation entre les conflits multiples

### Props de ConflictResolver

| Prop | Type | Description |
| --- | --- | --- |
| `conflicts` | array | Tableau de conflits (conflict_id, table, object_id, client_data, server_data, field_conflicts) |
| `onResolve` | function | Appelée quand un conflit est résolu |
| `onCancel` | function | Appelée pour fermer le résolveur |
| `renderField` | function | Rendu personnalisé d'un champ (optionnel) |
| `labels` | object | Libellés de l'interface (optionnel, français par défaut) |

## Schéma IndexedDB

Le module sync utilise les stores suivants :

| Store | Description |
| --- | --- |
| `entities` | Données synchronisées |
| `pending_changes` | Changements locaux en attente de push |
| `pending_conflicts` | Conflits non résolus |
| `sync_meta` | Métadonnées de synchronisation (lastSyncTime, etc.) |
| `local_tombstones` | Entités supprimées localement |

## Voir aussi
- [Hooks SmartCommon](/front/hooks) - Tous les hooks
- [PWA](/front/pwa) - Configuration PWA
- [Stockage de données](/front/stockage-de-donnees) - Stockage local
