---
source_hash: "8988d158"
title: "Offline Synchronization"
weight: 190
---

# Offline Synchronization

SmartCommon provides a complete synchronization module to manage the offline functionality of PWA applications.

## Architecture

The sync module consists of:

| Element | Type | Description |
| --- | --- | --- |
| `useSyncClient` | React Hook | Main interface for synchronization |
| `SyncEngine` | Class | Synchronization engine (push/pull/conflicts) |
| `SyncStorage` | Class | IndexedDB layer for local storage (Dexie) |
| `SyncApi` | Class | HTTP client with JWT auth and automatic retry |
| `ConflictResolver` | React Component | Conflict resolution interface |

## useSyncClient

See [Hooks - useSyncClient](/front/hooks#usesyncclient) for complete hook documentation.

## Synchronization Flow

### Push (local to server)

```
1. User modifies data locally (create/update/remove)
2. Changes are stored in IndexedDB (pending_changes)
3. On next sync.push(), changes are sent to the server
4. Server confirms or reports conflicts
5. Local temp_id are replaced with server IDs
```

### Pull (server to local)

```
1. sync.pull() requests changes since lastSyncTime
2. Server returns modified entities
3. Local entities are updated
4. In case of conflict (local + server modification), a conflict is created
```

## Conflict Management

When an entity is modified both locally and on the server, a conflict is created.

### Programmatic Resolution

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

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

  // Keep server version
  await sync.resolveConflict(conflict.conflict_id, 'server');

  // Manually merge
  await sync.resolveConflict(conflict.conflict_id, {
    ...conflict.server_data,
    label: conflict.client_data.label  // keep local label
  });
}
```

### Resolution with 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([])}
    />
  );
};
```

The `ConflictResolver` component displays:

- Side-by-side comparison of client and server data
- Markers on conflicting fields
- Three options: keep client, keep server, merge field by field
- Navigation between multiple conflicts

### ConflictResolver Props

| Prop | Type | Description |
| --- | --- | --- |
| `conflicts` | array | Array of conflicts (conflict_id, table, object_id, client_data, server_data, field_conflicts) |
| `onResolve` | function | Called when a conflict is resolved |
| `onCancel` | function | Called to close the resolver |
| `renderField` | function | Custom rendering of a field (optional) |
| `labels` | object | UI labels (optional, French by default) |

## IndexedDB Schema

The sync module uses the following stores:

| Store | Description |
| --- | --- |
| `entities` | Synchronized data |
| `pending_changes` | Local changes waiting for push |
| `pending_conflicts` | Unresolved conflicts |
| `sync_meta` | Synchronization metadata (lastSyncTime, etc.) |
| `local_tombstones` | Locally deleted entities |

## See Also
- [SmartCommon Hooks](/front/hooks) - All hooks
- [PWA](/front/pwa) - PWA Configuration
- [Data Storage](/front/stockage-de-donnees) - Local storage
