# Chapitre 4 : Synchronisation Offline
SmartCommon fournit un module complet pour la synchronisation offline-first des applications PWA Dolibarr.
## Vue d'ensemble
Le module sync permet de :
- Travailler hors connexion avec les données locales
- Synchroniser automatiquement quand la connexion revient
- Gérer les conflits de données entre client et serveur
## useSyncClient
Hook principal pour la synchronisation offline-first.
### Import
```javascript
import { useSyncClient } from '@cap-rel/smartcommon';
```
### Configuration
```javascript
function MyApp() {
const {
isOnline,
isSyncing,
pendingCount,
sync,
create,
update,
remove,
getConflicts,
resolveConflict
} = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty', 'contact', 'product']
});
return (
Status : {isOnline ? 'En ligne' : 'Hors ligne'}
Modifications en attente : {pendingCount}
);
}
```
### Paramètres
^ Paramètre ^ Type ^ Description ^
| apiUrl | string | URL de base de l'API de synchronisation |
| getAccessToken | function | Fonction retournant le token JWT |
| scope | string[] | Liste des entités à synchroniser |
### Valeurs retournées
^ Propriété ^ Type ^ Description ^
| isOnline | boolean | Statut de connexion |
| isSyncing | boolean | Synchronisation en cours |
| pendingCount | number | Nombre de modifications en attente |
| sync | function | Déclencher la synchronisation |
| create | function | Créer une entité (offline-capable) |
| update | function | Modifier une entité |
| remove | function | Supprimer une entité |
| upsert | function | Créer ou mettre à jour localement (cache) |
| getConflicts | function | Récupérer les conflits |
| resolveConflict | function | Résoudre un conflit |
### Créer une entité
```javascript
function CreateThirdpartyForm() {
const { create, pendingCount } = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty']
});
const handleCreate = async (data) => {
// Crée localement avec un ID temporaire
// Sera synchronisé quand la connexion revient
const tempId = await create('thirdparty', {
name: data.name,
email: data.email,
phone: data.phone
});
console.log('Créé avec ID temporaire:', tempId);
};
return (
);
}
```
### Modifier et supprimer
```javascript
function ThirdpartyActions({ thirdparty }) {
const { update, remove } = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty']
});
const handleUpdate = async () => {
await update('thirdparty', thirdparty.id, {
name: 'Nouveau nom'
});
};
const handleDelete = async () => {
await remove('thirdparty', thirdparty.id);
};
return (
);
}
```
### Upsert (cache local)
La méthode `upsert` permet de stocker des données localement sans déclencher de synchronisation vers le serveur. Elle crée l'entité si elle n'existe pas, ou la met à jour si elle existe déjà.
```javascript
function ThirdpartyDetail({ id }) {
const { upsert, getEntity } = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty']
});
const cacheServerData = async () => {
// Récupérer les données du serveur
const data = await api.private.get(`thirdparties/${id}`).json();
// Stocker localement sans déclencher de sync
await upsert('thirdparty', id, data);
};
// Avec queueChange = true, la modification sera synchronisée
const upsertAndSync = async (data) => {
await upsert('thirdparty', id, data, true);
};
// ...
}
```
#### Paramètres
^ Paramètre ^ Type ^ Défaut ^ Description ^
| table | string | - | Nom de la table |
| id | number/string | - | ID de l'entité |
| data | object | - | Données de l'entité |
| queueChange | boolean | false | Si true, ajoute la modification à la queue de sync |
### Synchronisation manuelle
```javascript
function SyncButton() {
const { sync, isSyncing, pendingCount, isOnline } = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty', 'contact']
});
const handleSync = async () => {
const result = await sync();
console.log('Synchronisé:', result);
};
return (
);
}
```
## ConflictResolver
Composant UI pour résoudre les conflits de synchronisation.
### Import
```javascript
import { ConflictResolver } from '@cap-rel/smartcommon';
```
### Utilisation
```javascript
function SyncManager() {
const {
getConflicts,
resolveConflict
} = useSyncClient({
apiUrl: '/api/smartauth',
getAccessToken: () => localStorage.getItem('access_token'),
scope: ['thirdparty']
});
const [conflicts, setConflicts] = useState([]);
useEffect(() => {
loadConflicts();
}, []);
const loadConflicts = async () => {
const list = await getConflicts();
setConflicts(list);
};
const handleResolve = async (conflictId, resolution) => {
await resolveConflict(conflictId, resolution);
await loadConflicts();
};
if (conflicts.length === 0) {
return
Aucun conflit
;
}
return (
);
}
```
### Props
^ Prop ^ Type ^ Description ^
| conflicts | array | Liste des conflits à afficher |
| onResolve | function | Callback appelé lors de la résolution |
### Structure d'un conflit
```javascript
{
id: 'conflict_123',
entity: 'thirdparty',
entityId: 456,
localData: { name: 'Version locale', ... },
serverData: { name: 'Version serveur', ... },
localTimestamp: 1707900000000,
serverTimestamp: 1707899000000
}
```
### Résolutions possibles
- **'local'** : Garder la version locale
- **'server'** : Garder la version serveur
- **'merge'** : Fusionner (si supporté)
## useOnlineStatus
Hook pour détecter le statut online/offline avec health check serveur optionnel.
### Import
```javascript
import { useOnlineStatus } from '@cap-rel/smartcommon';
```
### Utilisation simple
```javascript
function NetworkStatus() {
const { isOnline, isOffline } = useOnlineStatus();
return (
{isOnline ? 'En ligne' : 'Hors ligne'}
);
}
```
### Avec health check serveur
```javascript
function ServerStatus() {
const {
isOnline,
isServerReachable,
lastCheck,
checkNow
} = useOnlineStatus({
healthCheckUrl: '/api/health',
healthCheckInterval: 60000, // Vérifier toutes les 60s
stabilityDelay: 2000, // Attendre 2s avant de déclarer "en ligne"
timeout: 5000 // Timeout de 5s
});
return (
{thirdparties?.map(t => (
{t.name}
))}
);
}
```
## Points clés à retenir
1. **useSyncClient** pour les opérations CRUD offline-capable
2. **useOnlineStatus** pour détecter la connectivité
3. **useCachedQuery** pour le cache intelligent avec stratégies
4. **useAuthenticatedImage** pour les images protégées
5. **ConflictResolver** pour la résolution de conflits UI
6. Configurer les stores IndexedDB pour le cache
[[:15_training:module7-smartcommon-hooks:utilitaires|← Chapitre précédent]] | [[:15_training:module7-smartcommon-hooks:start|Retour au module]]