Data Portability
Export and import achievement data for backups, cross-device sync, and cloud storage integration.
Overview
React Achievements provides comprehensive data portability:
- Export achievement data as JSON
- Import data with merge strategies
- Cloud storage integration (AWS S3, Azure Blob Storage)
- Cross-device sync via REST API
- Backup and restore functionality
Basic Export/Import
Export Data
import { useAchievements } from 'react-achievements';
function BackupButton() {
const { exportData } = useAchievements();
const handleExport = () => {
const data = exportData();
console.log(data); // JSON string
};
return <button onClick={handleExport}>Export Achievements</button>;
}
Import Data
function RestoreButton() {
const { importData } = useAchievements();
const handleImport = (jsonData) => {
const result = importData(jsonData);
if (result.success) {
console.log(`Imported ${result.count} achievements`);
} else {
console.error(`Import failed: ${result.error}`);
}
};
return <button onClick={() => handleImport(backupData)}>Restore</button>;
}
Export Options
Export Everything
const data = exportData(); // All achievements and metrics
Export Specific Achievements
const data = exportData({
achievementIds: ['score_100', 'level_10']
});
Export with Metadata
const data = exportData({
includeMetadata: true // Includes timestamps, version, etc.
});
Exported Format:
{
"version": "3.7.0",
"exportedAt": "2025-12-23T10:30:00.000Z",
"achievements": {
"score_100": {
"id": "score_100",
"title": "Century!",
"unlockedAt": "2025-12-22T15:20:00.000Z",
"isUnlocked": true
}
},
"metrics": {
"score": 150,
"level": 5,
"completedTutorial": true
}
}
Import Strategies
Replace Strategy (Default)
Completely replaces existing data:
importData(jsonData, { strategy: 'replace' });
Use when:
- Restoring from a known good backup
- Resetting all achievements
- Initial data load
Merge Strategy
Combines imported data with existing data:
importData(jsonData, { strategy: 'merge' });
Merge rules:
- Unlocked achievements stay unlocked
- Metrics take the higher value
- Timestamps preserve the earliest unlock time
Use when:
- Syncing between devices
- Importing partial backups
- Merging multiple saves
Additive Strategy
Only adds new achievements, never overwrites:
importData(jsonData, { strategy: 'additive' });
Use when:
- Adding achievements from another source
- Importing without losing progress
- Safe imports
Backup to File
Download as JSON File
function DownloadBackup() {
const { exportData } = useAchievements();
const handleDownload = () => {
const data = exportData({ includeMetadata: true });
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `achievements-backup-${Date.now()}.json`;
link.click();
URL.revokeObjectURL(url);
};
return <button onClick={handleDownload}>Download Backup</button>;
}
Upload from File
function UploadBackup() {
const { importData } = useAchievements();
const handleFileChange = (event) => {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const jsonData = e.target.result;
const result = importData(jsonData, { strategy: 'merge' });
if (result.success) {
alert(`Restored ${result.count} achievements!`);
}
};
reader.readAsText(file);
};
return <input type="file" accept=".json" onChange={handleFileChange} />;
}
Cloud Storage Integration
AWS S3 Integration
import { S3 } from 'aws-sdk';
async function backupToS3() {
const { exportData } = useAchievements();
const data = exportData({ includeMetadata: true });
const s3 = new S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: 'us-east-1'
});
const params = {
Bucket: 'my-achievements-backup',
Key: `users/${userId}/achievements-${Date.now()}.json`,
Body: data,
ContentType: 'application/json',
ServerSideEncryption: 'AES256' // Encrypt at rest
};
try {
const result = await s3.putObject(params).promise();
console.log('Backed up to S3:', result);
return { success: true, location: result.Location };
} catch (error) {
console.error('S3 backup failed:', error);
return { success: false, error };
}
}
Restore from S3
async function restoreFromS3(backupKey) {
const { importData } = useAchievements();
const s3 = new S3({ /* credentials */ });
const params = {
Bucket: 'my-achievements-backup',
Key: backupKey
};
try {
const data = await s3.getObject(params).promise();
const jsonData = data.Body.toString('utf-8');
const result = importData(jsonData, { strategy: 'merge' });
console.log(`Restored ${result.count} achievements from S3`);
return result;
} catch (error) {
console.error('S3 restore failed:', error);
return { success: false, error };
}
}
List Available Backups
async function listBackups(userId) {
const s3 = new S3({ /* credentials */ });
const params = {
Bucket: 'my-achievements-backup',
Prefix: `users/${userId}/`
};
const data = await s3.listObjectsV2(params).promise();
return data.Contents.map(item => ({
key: item.Key,
size: item.Size,
lastModified: item.LastModified
}));
}
Azure Blob Storage Integration
import { BlobServiceClient } from '@azure/storage-blob';
async function backupToAzure() {
const { exportData } = useAchievements();
const data = exportData({ includeMetadata: true });
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
const containerName = 'achievements-backup';
const containerClient = blobServiceClient.getContainerClient(containerName);
const blobName = `users/${userId}/achievements-${Date.now()}.json`;
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
try {
await blockBlobClient.upload(data, data.length, {
blobHTTPHeaders: { blobContentType: 'application/json' }
});
console.log('Backed up to Azure Blob Storage');
return { success: true, blobName };
} catch (error) {
console.error('Azure backup failed:', error);
return { success: false, error };
}
}
Restore from Azure
async function restoreFromAzure(blobName) {
const { importData } = useAchievements();
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
const containerClient = blobServiceClient.getContainerClient('achievements-backup');
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
try {
const downloadResponse = await blockBlobClient.download(0);
const jsonData = await streamToString(downloadResponse.readableStreamBody);
const result = importData(jsonData, { strategy: 'merge' });
console.log(`Restored ${result.count} achievements from Azure`);
return result;
} catch (error) {
console.error('Azure restore failed:', error);
return { success: false, error };
}
}
async function streamToString(readableStream) {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on('data', (data) => {
chunks.push(data.toString());
});
readableStream.on('end', () => {
resolve(chunks.join(''));
});
readableStream.on('error', reject);
});
}
Automatic Cloud Sync
Sync on Achievement Unlock
<AchievementProvider
achievements={achievements}
onAchievementUnlock={async (achievement) => {
// Backup to cloud whenever an achievement unlocks
await backupToS3();
}}
>
<YourApp />
</AchievementProvider>
Periodic Auto-Backup
function AppWithAutoBackup() {
const { exportData } = useAchievements();
useEffect(() => {
// Backup every 5 minutes
const interval = setInterval(async () => {
const data = exportData({ includeMetadata: true });
await backupToS3(data);
}, 5 * 60 * 1000);
return () => clearInterval(interval);
}, [exportData]);
return <YourApp />;
}
Sync on App Start
function AppWithSync() {
const { importData } = useAchievements();
const [synced, setSynced] = useState(false);
useEffect(() => {
async function syncFromCloud() {
try {
const latestBackup = await getLatestBackup(userId);
const result = await restoreFromS3(latestBackup.key);
if (result.success) {
console.log('Synced from cloud');
setSynced(true);
}
} catch (error) {
console.error('Sync failed:', error);
setSynced(true); // Continue anyway
}
}
syncFromCloud();
}, [importData]);
if (!synced) {
return <div>Syncing achievements...</div>;
}
return <YourApp />;
}
Cross-Device Sync
REST API Sync
Use REST API storage for automatic cross-device sync:
<AchievementProvider
achievements={achievements}
storage={StorageType.RestApi}
storageConfig={{
apiEndpoint: 'https://api.example.com/achievements',
userId: currentUser.id,
headers: {
'Authorization': `Bearer ${token}`
}
}}
>
<YourApp />
</AchievementProvider>
The REST API storage automatically:
- Syncs on every update
- Pulls latest data on app start
- Handles conflicts with merge strategy
Best Practices
1. Encrypt Sensitive Data
import CryptoJS from 'crypto-js';
function encryptData(data, secret) {
return CryptoJS.AES.encrypt(data, secret).toString();
}
function decryptData(encryptedData, secret) {
const bytes = CryptoJS.AES.decrypt(encryptedData, secret);
return bytes.toString(CryptoJS.enc.Utf8);
}
// Export with encryption
const data = exportData();
const encrypted = encryptData(data, userSecret);
await backupToS3(encrypted);
// Import with decryption
const encrypted = await restoreFromS3(backupKey);
const decrypted = decryptData(encrypted, userSecret);
importData(decrypted);
2. Version Your Backups
const backupKey = `users/${userId}/v${APP_VERSION}/achievements-${Date.now()}.json`;
3. Keep Multiple Backups
// Keep last 10 backups, delete older ones
async function pruneOldBackups(userId, keepCount = 10) {
const backups = await listBackups(userId);
if (backups.length > keepCount) {
const toDelete = backups
.sort((a, b) => b.lastModified - a.lastModified)
.slice(keepCount);
for (const backup of toDelete) {
await s3.deleteObject({ Bucket: bucket, Key: backup.key }).promise();
}
}
}
4. Validate Before Import
function validateBackupData(jsonData) {
try {
const parsed = JSON.parse(jsonData);
if (!parsed.version || !parsed.achievements) {
return { valid: false, error: 'Invalid backup format' };
}
// Check version compatibility
if (parsed.version !== CURRENT_VERSION) {
return { valid: false, error: 'Version mismatch' };
}
return { valid: true };
} catch (error) {
return { valid: false, error: 'Invalid JSON' };
}
}
// Use before importing
const validation = validateBackupData(data);
if (validation.valid) {
importData(data);
} else {
console.error('Invalid backup:', validation.error);
}
5. Handle Import Errors Gracefully
function safeImport(jsonData) {
try {
const result = importData(jsonData, { strategy: 'merge' });
if (!result.success) {
console.error('Import failed:', result.error);
// Show user-friendly message
showNotification('Could not restore backup', 'error');
return false;
}
showNotification(`Restored ${result.count} achievements`, 'success');
return true;
} catch (error) {
console.error('Import error:', error);
showNotification('Backup file is corrupted', 'error');
return false;
}
}
Conflict Resolution
Timestamp-Based Resolution
importData(jsonData, {
strategy: 'merge',
conflictResolution: 'latest' // Use most recent timestamp
});
Value-Based Resolution
importData(jsonData, {
strategy: 'merge',
conflictResolution: 'highest' // Use highest metric values
});
Custom Resolution
importData(jsonData, {
strategy: 'merge',
conflictResolution: (local, remote) => {
// Custom logic
return local.score > remote.score ? local : remote;
}
});
Complete Backup System Example
import { useAchievements } from 'react-achievements';
import { S3 } from 'aws-sdk';
function AchievementBackupSystem() {
const { exportData, importData } = useAchievements();
const [backups, setBackups] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadBackupList();
}, []);
async function loadBackupList() {
const list = await listBackups(userId);
setBackups(list);
}
async function createBackup() {
setLoading(true);
const data = exportData({ includeMetadata: true });
const result = await backupToS3(data);
if (result.success) {
await loadBackupList();
alert('Backup created successfully!');
} else {
alert('Backup failed: ' + result.error);
}
setLoading(false);
}
async function restore(backupKey) {
if (!confirm('This will merge the backup with your current progress. Continue?')) {
return;
}
setLoading(true);
const result = await restoreFromS3(backupKey);
if (result.success) {
alert(`Restored ${result.count} achievements!`);
} else {
alert('Restore failed: ' + result.error);
}
setLoading(false);
}
return (
<div className="backup-system">
<h2>Achievement Backups</h2>
<button onClick={createBackup} disabled={loading}>
{loading ? 'Processing...' : 'Create Backup'}
</button>
<h3>Available Backups</h3>
<ul>
{backups.map(backup => (
<li key={backup.key}>
{new Date(backup.lastModified).toLocaleString()}
{' '}
({(backup.size / 1024).toFixed(2)} KB)
<button onClick={() => restore(backup.key)}>Restore</button>
</li>
))}
</ul>
</div>
);
}
What's Next?
- Error Handling - Handle backup failures gracefully
- API Reference - Complete export/import API docs
- Simple API - Learn the recommended configuration approach