Files
boc/iom/quixzoom-app/src/utils/offlineManager.js
T

201 lines
4.9 KiB
JavaScript
Raw Normal View History

/**
* Offline Manager
* Handle offline queue, sync, and storage
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
const QUEUE_KEY = '@quixzoom:observationQueue';
const SYNC_INTERVAL = 30000; // 30 seconds
/**
* Add observation to offline queue
*/
export async function queueObservation(observation) {
try {
const queue = await getQueue();
const queuedObservation = {
...observation,
id: `queued_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
queuedAt: new Date().toISOString(),
retryCount: 0,
};
queue.push(queuedObservation);
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
return queuedObservation.id;
} catch (error) {
console.error('Failed to queue observation:', error);
throw error;
}
}
/**
* Get offline queue
*/
export async function getQueue() {
try {
const queueJson = await AsyncStorage.getItem(QUEUE_KEY);
return queueJson ? JSON.parse(queueJson) : [];
} catch (error) {
console.error('Failed to get queue:', error);
return [];
}
}
/**
* Remove observation from queue
*/
export async function removeFromQueue(observationId) {
try {
const queue = await getQueue();
const filtered = queue.filter(obs => obs.id !== observationId);
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(filtered));
return filtered.length;
} catch (error) {
console.error('Failed to remove from queue:', error);
throw error;
}
}
/**
* Update observation in queue
*/
export async function updateInQueue(observationId, updates) {
try {
const queue = await getQueue();
const index = queue.findIndex(obs => obs.id === observationId);
if (index !== -1) {
queue[index] = { ...queue[index], ...updates };
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
}
} catch (error) {
console.error('Failed to update queue:', error);
}
}
/**
* Clear queue
*/
export async function clearQueue() {
try {
await AsyncStorage.removeItem(QUEUE_KEY);
} catch (error) {
console.error('Failed to clear queue:', error);
}
}
/**
* Get queue count
*/
export async function getQueueCount() {
const queue = await getQueue();
return queue.length;
}
/**
* Check if device is online
*/
export async function isOnline() {
const netInfo = await NetInfo.fetch();
return netInfo.isConnected && netInfo.isInternetReachable;
}
/**
* Sync queue with server
*/
export async function syncQueue(apiClient) {
try {
const online = await isOnline();
if (!online) {
console.log('Device is offline, skipping sync');
return { synced: 0, failed: 0 };
}
const queue = await getQueue();
if (queue.length === 0) {
return { synced: 0, failed: 0 };
}
console.log(`Syncing ${queue.length} observations...`);
let synced = 0;
let failed = 0;
for (const observation of queue) {
try {
// Attempt to upload
await apiClient.observations.create(observation);
await removeFromQueue(observation.id);
synced++;
} catch (error) {
console.error(`Failed to sync observation ${observation.id}:`, error);
// Increment retry count
const newRetryCount = (observation.retryCount || 0) + 1;
if (newRetryCount >= 5) {
// Max retries reached, remove from queue
await removeFromQueue(observation.id);
console.log(`Observation ${observation.id} removed after max retries`);
} else {
await updateInQueue(observation.id, { retryCount: newRetryCount });
}
failed++;
}
}
console.log(`Sync complete: ${synced} synced, ${failed} failed`);
return { synced, failed };
} catch (error) {
console.error('Sync failed:', error);
return { synced: 0, failed: 0, error: error.message };
}
}
/**
* Start automatic sync
*/
export function startAutoSync(apiClient, interval = SYNC_INTERVAL) {
console.log('Starting auto sync...');
// Initial sync
syncQueue(apiClient);
// Periodic sync
const syncInterval = setInterval(() => {
syncQueue(apiClient);
}, interval);
// Listen for network changes
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected && state.isInternetReachable) {
console.log('Network restored, triggering sync...');
syncQueue(apiClient);
}
});
// Return cleanup function
return () => {
clearInterval(syncInterval);
unsubscribe();
};
}
/**
* Get queue status
*/
export async function getQueueStatus() {
const queue = await getQueue();
const total = queue.length;
const pending = queue.filter(obs => (obs.retryCount || 0) === 0).length;
const retrying = queue.filter(obs => (obs.retryCount || 0) > 0).length;
return {
total,
pending,
retrying,
oldestObservation: total > 0 ? queue[0].queuedAt : null,
};
}