BBPlease / src /static /offline-manager.js
hamza-ks's picture
Relaunch deploy: RF v5 (72.6%), voc2vec fallback, telemetry removed, secrets docs removed
84e6d52
Raw
History Blame Contribute Delete
6.95 kB
/**
* Offline Manager
* Handles offline detection and feedback queueing for the mobile app
*/
(function() {
'use strict';
const OFFLINE_QUEUE_KEY = 'bbplease_offline_feedback_queue';
const SYNC_INTERVAL = 30000; // Sync every 30 seconds when online
let syncIntervalId = null;
/**
* Initialize offline manager
*/
function init() {
// Listen for online/offline events
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
// Check initial state
if (navigator.onLine) {
syncPendingFeedback();
startSyncInterval();
} else {
handleOffline();
}
// Try to sync on page load
syncPendingFeedback();
}
/**
* Handle going online
*/
function handleOnline() {
console.log('๐Ÿ“ถ Online - syncing pending feedback...');
syncPendingFeedback();
startSyncInterval();
// Show notification if there was pending feedback
const queue = getQueue();
if (queue.length > 0) {
showToast(`Syncing ${queue.length} pending feedback...`);
}
}
/**
* Handle going offline
*/
function handleOffline() {
console.log('๐Ÿ“ด Offline - feedback will be queued');
stopSyncInterval();
showToast('You are offline. Feedback will be saved and synced when online.');
}
/**
* Start periodic sync interval
*/
function startSyncInterval() {
if (syncIntervalId) {
clearInterval(syncIntervalId);
}
syncIntervalId = setInterval(() => {
if (navigator.onLine) {
syncPendingFeedback();
}
}, SYNC_INTERVAL);
}
/**
* Stop sync interval
*/
function stopSyncInterval() {
if (syncIntervalId) {
clearInterval(syncIntervalId);
syncIntervalId = null;
}
}
/**
* Get offline queue from localStorage
*/
function getQueue() {
try {
const queueJson = localStorage.getItem(OFFLINE_QUEUE_KEY);
return queueJson ? JSON.parse(queueJson) : [];
} catch (e) {
console.error('Error reading offline queue:', e);
return [];
}
}
/**
* Save queue to localStorage
*/
function saveQueue(queue) {
try {
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(queue));
} catch (e) {
console.error('Error saving offline queue:', e);
}
}
/**
* Add feedback to offline queue
*/
function queueFeedback(feedbackData) {
const queue = getQueue();
const feedbackItem = {
...feedbackData,
queued_at: new Date().toISOString(),
id: feedbackData.audio_id || Date.now().toString()
};
queue.push(feedbackItem);
saveQueue(queue);
console.log('๐Ÿ“ฆ Feedback queued offline:', feedbackItem.id);
return feedbackItem.id;
}
/**
* Remove feedback from queue
*/
function removeFromQueue(feedbackId) {
const queue = getQueue();
const filtered = queue.filter(item => item.id !== feedbackId);
saveQueue(filtered);
return queue.length - filtered.length; // Return number removed
}
/**
* Sync pending feedback when online
*/
async function syncPendingFeedback() {
if (!navigator.onLine) {
return { synced: 0, failed: 0 };
}
const queue = getQueue();
if (queue.length === 0) {
return { synced: 0, failed: 0 };
}
console.log(`๐Ÿ”„ Syncing ${queue.length} queued feedback items...`);
let synced = 0;
let failed = 0;
const failedItems = [];
for (const item of queue) {
try {
const url = (window.APP_CONFIG && window.APP_CONFIG.getApiUrl)
? window.APP_CONFIG.getApiUrl('/feedback')
: '/feedback';
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
audio_id: item.audio_id,
predicted_label: item.predicted_label,
correct_label: item.correct_label,
is_correct: item.is_correct
})
});
if (response.ok) {
removeFromQueue(item.id);
synced++;
console.log('โœ… Synced feedback:', item.id);
} else {
failed++;
failedItems.push(item);
console.warn('โŒ Failed to sync feedback:', item.id, response.status);
}
} catch (error) {
failed++;
failedItems.push(item);
console.error('โŒ Error syncing feedback:', item.id, error);
}
}
// If some failed, keep them in queue but update the queue
if (failed > 0 && synced > 0) {
// Remove only successfully synced items
const remaining = queue.filter(item =>
failedItems.some(failed => failed.id === item.id)
);
saveQueue(remaining);
}
if (synced > 0) {
console.log(`โœ… Synced ${synced} feedback items`);
if (typeof showToast === 'function') {
showToast(`Synced ${synced} feedback items`);
}
}
return { synced, failed };
}
/**
* Check if currently online
*/
function isOnline() {
return navigator.onLine !== false;
}
/**
* Get queue status
*/
function getQueueStatus() {
const queue = getQueue();
return {
count: queue.length,
items: queue,
isOnline: isOnline()
};
}
/**
* Clear queue (use with caution)
*/
function clearQueue() {
saveQueue([]);
console.log('๐Ÿ—‘๏ธ Offline queue cleared');
}
// Export to window
window.OfflineManager = {
init: init,
queueFeedback: queueFeedback,
syncPendingFeedback: syncPendingFeedback,
getQueueStatus: getQueueStatus,
clearQueue: clearQueue,
isOnline: isOnline
};
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();